zoukankan      html  css  js  c++  java
  • Java注解和反射笔记

    Java注解和反射笔记

    1 注解

    1.1 定义

    Annotation是从JDK1.5开始引入的技术

    作用

    • 不是程序本身,可以对程序作出解释
    • 可以被其他程序(编译器等)读取

    格式

    • @注释名,可以添加一些数值
    • 注解可以附加在package,class,method,field上面,可以通过反射机制实现对这些元数据的访问

    1.2 内置注解

    • @Override:定义在java.lang.Override中,只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明
    • @Deprecated:定义在java.lang.Deprecated中,可以修饰方法,属性,类,表示不建议使用这样的元素,有更好的选择
    • @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息

    1.3 元注解

    元注解的作用是负责注解其他注解,Java定义了4个标注的meta-annotation类型

    • @Target:用于描述注解的使用范围(类,方法,属性等)
    • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
      • SOURCE < CLASS < RUNTIME
    • @Documented:说明该注解将被包含在javadoc中
    • @Inherited:说明子类可以继承父类中的该注解

    1.4 自定义注解

    • 使用@interface自定义注解,自动继承java.lang.annotation.Annotation接口
    • 其中的每个方法实际上是声明了配置参数,方法的名称就是参数的名称,返回值的类型就是参数的类型
    • 用default来声明参数的默认值
    • 如果只有一个参数成员,一般参数名为value,且定义为value后,使用时可以省略参数名value,直接写值
    Copy@Target(value = {ElementType.METHOD, ElementType.TYPE})
    @Retention(value = RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface MyAnnotation {
        
        //注解的参数:参数类型+参数名()
        String name() default "";	//默认为空
        
        int age() default 0;
        
        int id() default -1;	//默认值为-1,代表不存在
        
        String[] jobs();
    }
    

    2 静态语言和动态语言

    • 动态语言
      • 在运行时可以改变其结构,新的函数、对象、代码可以被引进,已有的函数可以被删除或是其他结构上的变化
      • Object-C,C#,JavaScript,PHP,Python
    • 静态语言
      • 运行时结构不可改变
      • C,C++,Java

    Java不是动态语言,但是可以利用反射机制获得类似动态语言的特性

    3 反射

    3.1 概述

    反射机制允许程序在执行期间借助于Reflection API获得任何类的内部信息,并能直接操作任意对象的内部属性及方法

    CopyClass c = Class.forName("java.lang.String");
    

    加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息,通过这个类可以看到类的结构

    • 正常方式
      • 引入需要的包类名称 ---> 通过new实例化 ---> 取得实例化对象
    • 反射方式
      • 实例化对象 ---> getClass()方法 ---> 得到完整的包类结构信息

    3.2 Class类

    对于每个类而言,jre都会为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构的信息

    • Class本身也是一个类
    • Class对象只能由系统建立对象
    • 一个加载的类在JVM中只会有一个Class实例
    • 一个Class对象对应的是一个加载到JVM中一个.class文件
    • 每个类的实例都会记得自己是由哪个Class实例所生成的
    • Class类是Reflection的根源,针对任何想动态加载、运行的类,只有先获得相应的Class对象

    3.3 获得反射对象

    首先,哪些类型有Class对象

    • class:外部类,成员内部类,静态内部类,局部内部类,匿名内部类
    • interface:接口
    • []:数组
      • 只要元素类型和维度一样,就是同一个Class,不管数据长度
    • enum:枚举
    • annotation:注解@interface
    • primitive type:基本数据类型
    • void

    获得Class对象的方法

    • 已知具体的类,通过类的class属性获取,这种方法最安全快速

      • CopyClass clazz = Person.class;
        
    • 已知某个类的实例,调用该实例的getClass()方法获取Class对象

      • CopyClass clazz = persion.getClass();
        
    • 已知类的全类名,通过Class类的静态方法forName()获取,可能抛出异常

      • CopyClass clazz = Class.forName("com.hjc.pojo.User");
        
    • 内置基本数据类型可以直接用类名.TYPE

    • 利用ClassLoader

    Copypublic class Test {
        public static void main(String[] args) throws ClassNotFoundException {
            Person person = new Student();
            System.out.println(person.name);
            
            //通过对象获得
            Class c1 = person.getClass();
            
            //通过forName获得
            Class c2 = Class.forName("com.hjc.reflection.Student");
            
            //通过类名.class获得
            Class c3 = Student.class;
            
            //基本数据类型
            Class c4 = Integer.TYPE;
            
            //获得父类
            Class c5 = c1.getSuperClass();
        }
    }
    
    class Person {
        String name;
        
        //省略构造函数和set/get方法
    }
    
    class Student extends Person {
        public Student() {
            this.name = "student";
        }
    }
    
    class Teacher extends Person {
        public Teacher() {
            this.name = "teacher";
        }
    }
    

    3.4 类加载过程

    当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过三个步骤对该类进行初始化

    img

    • 加载
      • 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,生成一个代表这个类的java.lang.Class对象
    • 链接
      • 将Java类的二进制代码合并到JVM的运行状态之中
        • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
        • 准备:正式为类变量(static)分配内存并设置类变量默认初始值,这些内存都在方法区中进行分配
        • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)
    • 初始化
      • 执行类构造器()方法的过程
      • 当初始化一个类的时候,如果发现父类还没有初始化,则要先触发父类的初始化
      • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步

    3.5 类初始化

    什么时候会发生类初始化

    • 类的主动引用(一定会发生类的初始化)
      • 当虚拟机启动,先初始化main方法所在的类
      • new一个类的对象
      • 调用类的静态成员(除了final常量)和静态方法
      • 使用java.lang.reflect包的方法对类进行反射调用
      • 当初始化一个类,如果其父类没有被初始化,则会先初始化其父类
    • 类的被动引用(不会发生类的初始化)
      • 当访问一个静态域时,只有真正声明这个域的类才会被初始化
        • 比如,通过子类引用父类的静态变量,不会导致子类初始化
      • 通过数组定义类引用,不会触发此类的初始化
      • 引用常量不会触发此类的初始化
        • 常量在链接阶段就存入调用类的常量池中了
    Copypublic class Test {
        
        static {
            System.out.println("Main类被加载");
        }
        
        public static void main(String[] args) throws ClassNotFoundException {
            //主动引用,会初始化类
            B b = new B();
            
            //反射,会初始化类
            Class.forName("com.hjc.reflection.B");
            
            //B不会被加载,会加载A
            System.out.println(B.n);
            
            //B不被加载
            B[] array = new B[10];
            System.out.println(B.M);
        }
    }
    
    class A {
        
        static {
            System.out.println("父类被加载");
        }
        
        static int n = 2;
    }
    
    classs B extends A {
        
        static {
            System.out.println("子类被加载");
            m = 300;
        }
        
        static int m = 100;
        static final int M = 1;
    }
    

    3.6 类加载器

    类加载器的作用

    • 将class文件字节码内容加载到内存中,并将这些静态数据转换程方法区的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口

    类缓存

    • 标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,但JVM垃圾回收机制可以回收这些Class对象

    img

    类加载器

    • 引导类加载器
      • 用C++编写,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库,该加载器无法直接获取
    • 扩展类加载器
      • 负责jre/lib/ext目录下的jar包装入工作库
    • 系统类加载器
      • 负责java -classpath下的jar包装入工作库,是最常用的加载器
    Copypublic class Test {
        public static void main(String[] args) throws ClassNotFoundException {
            //获取系统类加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            System.out.println(systemClassLoader);
            
            //获取系统类加载器的父类,扩展类加载器
            ClassLoader parent = systemClassLoader.getParent();
            System.out.println(parent);
            
            //获取扩展类加载器的父类,引导类加载器,无法获取
            parent = parent.getParent();
            System.out.println(parent);
            
            //测试当前类是由哪个类加载器加载的
            ClassLoader classLoader = Class.forName("com.hjc.reflection.Test").getClassLoader();
            System.out.println(ClassLoader);
            
            //测试jdk内置的类
            classLoader = Class.forName("java.lang.Object").getClassLoader();
            System.out.println(ClassLoader);
        }
    }
    

    3.7 获取运行时类的完整结构

    获得了类的Class对象,那么我们就可以得到类的运行时信息

    Copypublic class Test {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            Class c = Class.forName("com.hjc.reflection.User");
            
            //获得类的名字
            System.out.println(c.getName());	//包名+类名
            System.out.println(c.getSimpleName());	//类名
            
            //获得类的属性
            Field[] fields = c.getFields();	//只能得到public属性
            for (Field field : fields) {
                System.out.println(field);
            }
            
            fields = c.getDeclaredFields();	//可以得到全部属性
            for (Field field : fields) {
                System.out.println(field);
            }
            
            //获得指定属性
            Field name = c.getField("name");	//找不到,因为name是private
            System.out.println(name);
            
            Field name = c.getDeclaredField("name");	//可以找到
            System.out.println(name);
            
            //获得类的方法
            Method[] methods = c.getMethods();	//获得本类及其父类的全部public方法
            for (Method method : methods) {
                System.out.println(method);
            }
            
            methods = c.getDeclaredMethods();	//获得本类的全部方法
            for (Method method : methods) {
                System.out.println(method);
            }
            
            //获得类的指定方法
            Method getName = c.getMethod("getName", null);	//方法名+参数
            Method setName = c.getMethod("setName", String.class);
            System.out.println(getName);
            System.out.println(setName);
            
            //获得类的构造器
            Constructor[] constructors = c.getConstructors();	//获得public
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }
            
            constructors = c.getDeclaredConstructors();		//获得全部
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }
            
            //获得指定构造器
            Constructor declaredConstructor = c.getDeclaredConstructor(int.class, String.class, int.class);
            System.out.println(declaredConstructor);
        }
    }
    
    class User {
        private int id;
        private String name;
        private int age;
        
        //省略构造方法和set/get方法
    }
    

    3.8 动态创建对象执行方法

    Copypublic class Test {
        public static void main(String[] args) throws ClassNotFoundException {
            Class c = Class.forName("com.hjc.reflection.User");
            
            //构造一个对象
            User user = (User) c.newInstance();	//本质上调用了类的无参构造器
            System.out.println(user);
            
            //如果没有无参构造器,通过有参构造器创建对象
            Constructor constructor = c.getDeclaredConstructor(int.class, String.class, int.class);
            User user = (User) constructor.newInstance(1, "test", 18);
            System.out.println(user);
            
            //反射调用方法
            Method setName = c.getDeclaredMethod("setName", String.class);
            setName.invoke(user, "test1");	//invoke传递对象和方法参数值
            System.out.println(user);
            
            //反射操作属性
            Field name = c.getDeclaredField("name");
            //不能直接操作私有属性,需要关闭程序的安全检测
            //调用属性或者方法的setAccessible(true)
            name.setAccessible(true);
            name.set(user, "test2");
            System.out.println(user);
        }
    }
    

    3.9 性能分析

    Copypublic class Test {
        //普通方式
        public void test1() {
            User user = new User();
            long startTime = System.currentTimeMillis();
            
            for (int i = 0; i < 1000000000; i++) {
                user.getName();
            }
            
            long endTime = System.currentTimeMillis();
            
            System.out.println("普通方式执行十亿次:" + (endTime - startTime) + "ms");
        }
        
        //反射方式
        public void test2() throws NoSuchMethodException {
            User user = new User();
            Class c = user.getClass();
            Method getName = c.getDeclaredMethod("getName", null);
            
            long startTime = System.currentTimeMillis();
            
            for (int i = 0; i < 1000000000; i++) {
                getName.invoke(user, null);
            }
            
            long endTime = System.currentTimeMillis();
            
            System.out.println("反射方式执行十亿次:" + (endTime - startTime) + "ms");
        }
        
        //反射方式,关闭检测
        public void test3() throws NoSuchMethodException {
            User user = new User();
            Class c = user.getClass();
            Method getName = c.getDeclaredMethod("getName", null);
            getName.setAccessible(true);
            
            long startTime = System.currentTimeMillis();
            
            for (int i = 0; i < 1000000000; i++) {
                getName.invoke(user, null);
            }
            
            long endTime = System.currentTimeMillis();
            
            System.out.println("关闭检测后,反射方式执行十亿次:" + (endTime - startTime) + "ms");
        }
        
        public static void main(String[] args) {
            test1();
            test2();
            test3();
        }
    }
    

    3.10 反射操作泛型

    Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是一旦编译完成,所有和泛型有关的类型全部擦除

    为了通过反射操作泛型,Java引入了几个类

    • ParameterizedType:表示一种参数化类型,如Collection
    • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
    • TypeVariable:各种类型变量的公共父接口
    • WildcardType:代表一种通配符类型表达式
    Copypublic class Test {
        
        public void test1(Map<String, User> map, List<User> list) {
            System.out.println("test1");
        }
        
        public Map<String, User> test2() {
            System.out.println("test2");
            return null;
        }
        
        public static void main(String[] args) {
            Method method = Test.class.getMethod("test1", Map.class, List.class);
            Type[] types = method.getGenericParameterTypes();
            for (Type type : types) {
                System.out.println(type);
                if (type instanceof ParameterizedType) {
                    Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
                    for (Type actualType : actualTypes) {
                        System.out.println(actualType);
                    }
                }
            }
            
            method = Test.class.getMethod("test2", null);
            Type returnType = method.getGenericReturnType();
            System.out.println(resultType);
            if (returnType instanceof ParameterizedType) {
                Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
                for (Type actualType : actualTypes) {
                    System.out.println(actualType);
                }
            }
        }
    }
    

    3.11 反射操作注解

    很多框架都是通过反射获取注解信息,来帮我们解决了很多事情

    public class Test {
        public static void main(String[] args) throws ClassNotFoundException {
            Class c = Class.forName("com.hjc.reflection.Student");
            
            //通过反射获得注解
            Annotation[] annotations = c.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
            
            //获得注解的值
            Table1 table1 = (Table1) c.getAnnotation(Table1.class);
            String value = table1.value();
            System.out.println(value);
            
            //获得指定的注解
            Field f = c.getDeclaredField("name");
            Field1 f1 = f.getAnnotation(Field1.class);
            System.out.println(f1.columnName());
            System.out.println(f1.type());
            System.out.println(f1.length());
        }
    }
    
    @Table1("db_student")
    class Student {
        @Field1(columnName = "db_id", type = "int", length = 10)
        private int id;
        @Field1(columnName = "db_name", type = "varchar", length = 3)
        private String name;
        @Field1(columnName = "db_age", type = "int", length = 10)
        private int age;
        
        //省略构造器和set/get方法
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Table1 {
        String value();
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Field1 {
        String columnName();
        String type();
        int length();
    }
    
  • 相关阅读:
    Java编程思想读书笔记 第十章 内部类
    利用lambda和条件表达式构造匿名递归函数
    概率论与数理统计-课程小报告
    leetcode226 翻转二叉树
    leetcode199 二叉树的右视图
    leetcode114- 二叉树展开为链表
    leetcode145 二叉树的后序遍历 特别注意迭代
    leet144 二叉树的前序遍历
    leetcode113 路径总和2 特别关注
    leetcode 112 路径总和 特别关注
  • 原文地址:https://www.cnblogs.com/wtao0730/p/14373463.html
Copyright © 2011-2022 走看看