阿里P8教你Java注解与反射

作者: java搬砖从来不加班 | 来源:发表于2021-08-10 15:17 被阅读0次

    Ⅰ 什么是注解

    Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

    注解以@注解名的形式存在于代码中,例如@Override,还可以添加一些参数值,例如@Auth(value = "super")

    Ⅱ 内置注解

    Java 有10个内置 注解,6个注解是作用在代码上的,4个注解是负责注解其他注解的(即元注解),元注解提供对其他注解的类型说明。

    注解 作用 作用范围
    @Override 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。 作用在代码上
    @Deprecated 标记表示过时的,不推荐使用。可以用于修饰方法,属性,类。如果使用被此注解修饰的方法,属性或类,会报编译警告。 作用在代码上
    @SuppressWarnings 指示编译器去忽略注解中声明的警告。 作用在代码上
    @SafeVarargs Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。 作用在代码上
    @FunctionalInterface Java 8 开始支持,标识一个匿名函数或函数式接口。 作用在代码上
    @Repeatable Java 8 开始支持,标识某注解可以在同一个声明上使用多次。 作用在代码上
    @Retention 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。包含关系runtime>class>source。 作用在其他注解上,即元注解
    @Documented 标记这些注解是否包含在用户文档中。 作用在其他注解上,即元注解
    @Target 标记某个注解的使用范围,例如作用方法上,类上,属性上等等。 作用在其他注解上,即元注解
    @Inherited 说明子类可以集成父类中的此注解,默认注解并没有继承于任何子类 作用在其他注解上,即元注解

    Ⅲ 自定义注解

    使用@interface在这里插入代码片定义注解,而且自动继承java.lang.annotation.Annotation接口。

    • 格式为 public @interface 注解名 {定义内容}
    • 其中每一个方法实际是声明了一个参数
    • 方法的名称就是参数的名称
    • 返回值类型就是参数的类型,而且返回值类型只能是基本类型,Class,String,enum。
    • 可以通过default关键字来声明参数的默认值,一般会使用空字符串或者0
    • 如果只有一个参数,一般参数名为value,而且使用注解时,赋值可以不显示写出参数名,直接写参数值
    • 定义了参数,如果没有默认值,就一定要显示赋值

    Ⅳ 注解案例

    import java.lang.annotation.*;
    
    /**
     * @author Mr.nobody
     * @Description 自定义注解
     * @date 2020/8/30
     */
    @Target(ElementType.METHOD) // 此注解只能用在方法上。
    @Retention(RetentionPolicy.RUNTIME) // 此注解保存在运行时,可以通过反射访问。
    @Inherited // 说明子类可以集成父类中的此注解。
    @Documented // 此注解包含在用户文档中。
    public @interface CustomAnnotation {
        String value(); // 使用时需要显示赋值
        int id() default 0; // 有默认值,使用时可以不赋值
    }
    
    /**
     * @author Mr.nobody
     * @Description 测试注解
     * @date 2020/8/30
     */
    public class TestAnnotation {
    
        // @CustomAnnotation(value = "test") 只能注解在方法上,这里会报错
        private String str = "Hello World!";
    
        @CustomAnnotation(value = "test")
        public static void main(String[] args) {
            System.out.println(str);
        }
    }
    

    Ⅴ Java 反射机制

    讲解反射前,我们先来谈谈静态语言和动态语言。

    动态语言是一类在运行时可以改变其结构的语言。例如新的函数,对象,甚至代码可以被引进,已有的函数可以被删除或者结构上的一些变化。简单说即是在运行时代码可以根据某些条件改变自身结构。动态语言主要C#,Object-C,JavaScript,PHP,Python等。

    静态语言是运行时结构不可改变的,例如Java,C,C++等。

    Java不是动态语言,但是她可以称为准动态语言,因为Java可以利用反射机制获得类似动态语言的特性,Java的动态性让它在编程时更加灵活。

    反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性以及方法等。 类在被加载完之后,会在堆内存的方法区中生成一个Class类型的对象,一个类只有一个Class对象,这个对象包含了类的结构信息。我们可以通过这个对象看到类的结构。 例如可以通过如下方法获取String类的Class对象:Class c = Class.forName("java.lang.String"); 当然,每个类都隐式继承Object类,Object类有个getClass()方法也能获取Class对象。

    5.1 Java反射机制提供的功能

    1. 在运行时判断任意一个对象所属的类
    2. 在运行时构造任意一个类的对象
    3. 在运行时判断任意一个类具有的成员变量和方法
    4. 在运行时获取泛型信息
    5. 在运行时调用任意一个对象的成员变量和方法
    6. 在运行时处理注解
    7. 生成动态代理
    8. ...

    5.2 Java反射机制的优缺点

    • 优点:实现动态创建对象和编译,有更加的灵活性。
    • 缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们想要做什么然后它满足我们的要求,这类操作总是慢于直接执行相同的操作。

    5.3 Java反射相关的主要API

    • java.lang.Class:代表一个类
    • java.lang.reflect.Method:代表类的方法
    • java.lang.reflect.Field:代表类的成员变量
    • java.lang.reflect.Constructor:代表类的构造器
    • ...

    5.4 Class类

    通过Class对象可以得知某个类的属性,方法,构造器,注解,以及实现了哪些接口等等信息。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定的结构(class,interface,enum,annotation,private type,void,[])的有关信息。

    • Class本身也是一个类
    • Class对象只能系统建立
    • 一个加载的类在JVM中只有一个Class对象
    • 一个Class对象一般对应的是一个加载到JVM中的一个.class文件
    • 每个类的实例都会记得自己是右哪个Class实例所生成的
    • 通过Class可以完整地得到一个类中的所有被加载的结构
    • Class类是Reflection的根源,针对任何你想动态加载,运行的类,唯有先获得相应的Class对象

    Class类的常用方法:

    • static forName(String name):返回指定类名name的Class对象
    • Object newInstance():调用缺省构造方法,返回Class对象的一个实例
    • getName():返回Class对象所表示的实体(类,接口,数组类或void)的名称。
    • Class getSuperClass():返回当前Class对象的父类的Class对象
    • Class[] getInterfaces():获取当前Class对象的接口
    • ClassLoader getClassLoader():获取当前类的类加载器
    • Constructor[] getConstructors():获取一个包含某些Constructor对象的数组
    • Method getMethod(String name, Class.. T):返回一个Method对象,此对象的形参类型为paramType
    • Field[] getDeclaredFields():返回Field对象的一个数组

    获取运行时类的完整结构

    通过反射可以获取运行时类的完整结构:

    1. Field
    2. Method
    3. Constructor
    4. Superclass
    5. Interface
    6. Annatation
    7. ...

    调用指定的方法:Object invoke(Object obj, Object ... args)

    • 第一个Object对应原方法的返回值,若原方法无返回值,则返回null
    • 若原方法为静态方法,则参数obj可为null
    • 若原方法形参列表为空,则参数args为null
    • 若原方法声明为private,则调用invoke方法前,需要显示调用方法对象的setAccessible(true)方法,才可访问private方法。
    package com.nobody;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * @author Mr.nobody
     * @Description
     * @date 2020/9/4
     */
    public class Test01 {
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
            Class<?> aClass = Class.forName("com.nobody.Student");
    
            System.out.println(aClass.getName());
            System.out.println(aClass.getSimpleName());
    
            System.out.println("-----------------------");
            System.out.println("获取public的属性");
            Field[] fields = aClass.getFields();
            for (Field field : fields) {
                System.out.println(field);
            }
    
            System.out.println("-----------------------");
            System.out.println("获取全部的属性");
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                System.out.println(declaredField);
            }
    
            System.out.println("-----------------------");
            System.out.println("获取指定名称的属性");
            Field name = aClass.getDeclaredField("name");
            System.out.println(name);
    
            System.out.println("-----------------------");
            System.out.println("获取本类和父类的全部public方法");
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                System.out.println(method);
            }
    
            System.out.println("-----------------------");
            System.out.println("获取本类的方法");
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                System.out.println(declaredMethod);
            }
    
            System.out.println("-----------------------");
            System.out.println("获得指定名字的方法");
            Method getName = aClass.getDeclaredMethod("getName");
            System.out.println(getName);
            Method setName = aClass.getDeclaredMethod("setName", String.class);
            System.out.println(setName);
    
            System.out.println("-----------------------");
            System.out.println("获得构造器");
            Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
            for (Constructor<?> declaredConstructor : declaredConstructors) {
                System.out.println(declaredConstructor);
            }
    
            System.out.println("-----------------------");
            System.out.println("获取指定的构造器");
            Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class, int.class);
            System.out.println(declaredConstructor);
    
            System.out.println("-----------------------");
            System.out.println("生成类的实例");
            Student student = (Student) aClass.newInstance();
            Student student1 = (Student) declaredConstructor.newInstance("小明", 20);
            System.out.println(student);
            System.out.println(student1);
    
            System.out.println("-----------------------");
            System.out.println("调用实例的方法");
            setName.invoke(student, "小花");
            System.out.println(student);
    
            System.out.println("-----------------------");
            System.out.println("使用实例的属性");
            name.setAccessible(true); // 因为student的name变量是私有的,所以要加此行代码,关闭安全检测
            name.set(student, "小红");
            System.out.println(student);
        }
    
    }
    
    class Student {
    
        private String name;
    
        public int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        private void testMethod01() {
    
        }
    
        public Student() {
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    输出结果:

    com.nobody.Student
    Student
    -----------------------
    获取public的属性
    public int com.nobody.Student.age
    -----------------------
    获取全部的属性
    private java.lang.String com.nobody.Student.name
    public int com.nobody.Student.age
    -----------------------
    获取指定名称的属性
    private java.lang.String com.nobody.Student.name
    -----------------------
    获取本类和父类的全部public方法
    public java.lang.String com.nobody.Student.toString()
    public java.lang.String com.nobody.Student.getName()
    public void com.nobody.Student.setName(java.lang.String)
    public final void java.lang.Object.wait() throws java.lang.InterruptedException
    public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    public boolean java.lang.Object.equals(java.lang.Object)
    public native int java.lang.Object.hashCode()
    public final native java.lang.Class java.lang.Object.getClass()
    public final native void java.lang.Object.notify()
    public final native void java.lang.Object.notifyAll()
    -----------------------
    获取本类的方法
    public java.lang.String com.nobody.Student.toString()
    public java.lang.String com.nobody.Student.getName()
    public void com.nobody.Student.setName(java.lang.String)
    private void com.nobody.Student.testMethod01()
    -----------------------
    获得指定名字的方法
    public java.lang.String com.nobody.Student.getName()
    public void com.nobody.Student.setName(java.lang.String)
    -----------------------
    获得构造器
    public com.nobody.Student()
    public com.nobody.Student(java.lang.String,int)
    -----------------------
    获取指定的构造器
    public com.nobody.Student(java.lang.String,int)
    -----------------------
    生成类的实例
    Student{name='null', age=0}
    Student{name='小明', age=20}
    -----------------------
    调用实例的方法
    Student{name='小花', age=0}
    -----------------------
    使用实例的属性
    Student{name='小红', age=0}
    

    获取Class类的实例

    1. 若已知具体的类,可通过class属性获取,此方法最为安全可靠,程序性能最高。Class clazz = User.class;
    2. 若已知某个类的实例,可调用此实例的getClass()方法获取。Class clazz = user.getClass();
    3. 若已知一个类的全类名,且此类在类路径下,可通过Class类的静态方法forName()方法获取,可能会抛出ClassNotFoundException。Class clazz = Class.forName("com.nobody.User");
    4. 内置基本数据类型可以直接通过类名.Type获取
    5. 可以通过ClassLoader获取
    package com.nobody;
    
    /**
     * @author Mr.nobody
     * @Description
     * @date 2020/9/2
     */
    public class User extends Person {
    
        private String name;
    
        public static void main(String[] args) throws ClassNotFoundException {
    
            User user = new User();
    
            Class<User> userClass = User.class;
            Class<? extends User> aClass = user.getClass();
            Class<?> aClass1 = Class.forName("com.nobody.User");
    
            System.out.println(userClass.hashCode());
            System.out.println(aClass.hashCode());
            System.out.println(aClass1.hashCode());
    
            Class<? super User> superclass = userClass.getSuperclass();
            System.out.println(superclass);
    
            Class<Integer> type = Integer.TYPE;
            System.out.println(type);
    
        }
    }
    
    class Person {
    
    }
    

    输出结果:

    685325104
    685325104
    685325104
    class com.nobody.Person
    int
    复制代码
    

    有Class对象的类型

    1. class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
    2. interface:接口
    3. []:数组
    4. enum:枚举
    5. annotation:注解
    6. primitive type:基本数据类型(例如Integer,Double等)
    7. void:空类型
    package com.nobody;
    
    import java.lang.annotation.Documented;
    import java.util.List;
    
    /**
     * @author Mr.nobody
     * @Description
     * @date 2020/9/2
     */
    public class ClassTest {
    
        public static void main(String[] args) {
            Class<Object> objectClass = Object.class;
            Class<List> listClass = List.class;
            Class<String[]> aClass = String[].class;
            Class<String[][]> aClass1 = String[][].class;
            Class<Override> overrideClass = Override.class;
            Class<Documented> documentedClass = Documented.class;
            Class<Integer> integerClass = Integer.class;
            Class<Void> voidClass = void.class;
            Class<Class> classClass = Class.class;
    
            System.out.println(objectClass);
            System.out.println(listClass);
            System.out.println(aClass);
            System.out.println(aClass1);
            System.out.println(overrideClass);
            System.out.println(documentedClass);
            System.out.println(integerClass);
            System.out.println(voidClass);
            System.out.println(classClass);
        }
    }
    

    输出结果:

    class java.lang.Object
    interface java.util.List
    class [Ljava.lang.String;
    class [[Ljava.lang.String;
    interface java.lang.Override
    interface java.lang.annotation.Documented
    class java.lang.Integer
    void
    class java.lang.Class
    

    Ⅵ 类加载过程

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

    1. 类的加载(load):将类的class文件字节码加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,此过程由类加载器完成。
    2. 类的链接(Link):将类的二进制代码合并到JVM的运行状态之中的过程。
      1. 验证:确保加载的类的信息符合JVM规范,确保安全;
      2. 准备:正式为类变量(static)分配内存并设置变量默认初始值的阶段,这些内存都将在方法区中进行分配;
      3. 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程;
    3. 类的初始化(Initialize):JVM对类进行初始化。
      1. 执行类构造器<clinit>()方法的过程,类构造器<clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
      2. 当初始化一个类的时候,如果发现其父类还没被初始化,则先进行父类初始化。
      3. 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
    package com.nobody;
    
    /**
     * @author Mr.nobody
     * @Description
     * @date 2020/9/3
     */
    public class TestA {
    
        public static void main(String[] args) {
            Child child = new Child();
            System.out.println(">>> age = " + Child.age);
        }
    }
    
    class Child extends Father {
    
        static {
            System.out.println(">>> Child static block...");
            age = 20;
        }
    
        static int age = 18;
    
        public Child() {
            System.out.println(">>> Child constructor...");
        }
    
    }
    
    class Father {
    
        static {
            System.out.println(">>> Father static block...");
        }
    
        public Father () {
            System.out.println(">>> Father constructor...");
        }
    
    }
    

    输出结果为:

    >>> Father static block...
    >>> Child static block...
    >>> Father constructor...
    >>> Child constructor...
    >>> age = 18
    

    如果Child类的静态代码块和static int age = 18;语句位置交换,则最后age的值为20(0 -> 18 -> 20)。因为编译器会按定义顺序自动收集类中所有类变量的赋值动作静态代码块中的语句合并产生类构造器<clinit>()方法。

    class Child extends Father {
    
        static int age = 18;
    
        static {
            System.out.println(">>> Child static block...");
            age = 20;
        }
    
        public Child() {
            System.out.println(">>> Child constructor...");
        }
    }
    

    输出结果为:

    >>> Father static block...
    >>> Child static block...
    >>> Father constructor...
    >>> Child constructor...
    >>> age = 20
    

    5.1 何时会发生类初始化

    类的主动引用(一定会发生类的初始化)

    • 当虚拟机启动,先初始化main方法所在的类
    • new一个类的对象
    • 调用类的静态成员(除final常量外)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果其父类未被初始化,则先初始化其父类

    类的被动引用(不会发生类的初始化)

    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。例如,当通过子类引用父类的静态变量或静态方法,不会导致子类初始化。Child.age;,而age属性是在父类定义的。
    • 通过数组定义类引用,也不会触发此类的初始化。Child[] childs = new Child[5];
    • 引用常量不会触发此类的初始化,因为常量在链接阶段就存入调用类的常量池中了。

    Ⅶ 类加载器Classloader

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

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


    image.png
    package com.nobody;
    
    /**
     * @author Mr.nobody
     * @Description
     * @date 2020/9/3
     */
    public class TestClassLoader {
    
        public static void main(String[] args) throws ClassNotFoundException {
    
            // 获取系统类的加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            // 获取系统类加载器的父加载器,也就是扩展类加载器
            ClassLoader parent = systemClassLoader.getParent();
            // 获取扩展类加载器的父加载器,也就是根加载器,用C或C++编写
            ClassLoader parent1 = parent.getParent();
            System.out.println(systemClassLoader);
            System.out.println(parent);
            System.out.println(parent1);
    
            System.out.println("----------------------------------------");
    
            // 测试我们定义的类是哪个类加载器加载的
            ClassLoader classLoader = Class.forName("com.nobody.TestClassLoader").getClassLoader();
            // 测试JDK内置的的类是哪个类加载器加载的
            ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
            System.out.println(classLoader);
            System.out.println(classLoader1);
    
            // 获取系统类加载器可以加载的路径
           System.out.println(System.getProperty("java.class.path"));
        }
    
    }
    

    输出结果:

    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$ExtClassLoader@1b6d3586
    null  // 根加载器(引导类加载器)我们获取不到,所以是null
    ----------------------------------------
    sun.misc.Launcher$AppClassLoader@18b4aac2
    null
    
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\charsets.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\deploy.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\access-bridge-64.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\cldrdata.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\dnsns.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\jaccess.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\jfxrt.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\localedata.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\nashorn.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunec.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunjce_provider.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunmscapi.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\sunpkcs11.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\ext\zipfs.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\javaws.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\jce.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\jfr.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\jfxswt.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\jsse.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\management-agent.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\plugin.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\resources.jar;
    C:\Program Files\Java\jdk1.8.0_202\jre\lib\rt.jar;
    D:\IdeaProjects\nobody\project02\out\production\hello-world;
    D:\devTools\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar
    

    Ⅷ 性能对比分析

    Method,Field,Constructor都由setAccessible()方法,它的作用是开启或禁用访问安全检查。如果代码中用到反射,而且此代码被频繁调用,为了提高反射效率,则最好禁用访问安全检查,即设置为true。

    package com.nobody;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * @author Mr.nobody
     * @Description
     * @date 2020/9/5
     */
    public class Test02 {
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
            test01();
            test02();
            test03();
        }
    
        public static void test01() {
            Teacher t = new Teacher();
            long start = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; i++) {
                t.getName();
            }
            long end = System.currentTimeMillis();
            System.out.println("普通方式执行10亿次消耗:" + (end - start) + "ms");
        }
    
        public static void test02() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            Teacher t = new Teacher();
            Class<?> aClass = Class.forName("com.nobody.Teacher");
            Method getName = aClass.getDeclaredMethod("getName");
            long start = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; i++) {
                getName.invoke(t, null);
            }
            long end = System.currentTimeMillis();
            System.out.println("反射方式执行10亿次消耗:" + (end - start) + "ms");
        }
    
        public static void test03() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            Teacher t = new Teacher();
            Class<?> aClass = Class.forName("com.nobody.Teacher");
            Method getName = aClass.getDeclaredMethod("getName");
            getName.setAccessible(true);
            long start = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; i++) {
                getName.invoke(t, null);
            }
            long end = System.currentTimeMillis();
            System.out.println("关闭安全检查反射方式执行10亿次消耗:" + (end - start) + "ms");
        }
    
    }
    
    class Teacher {
    
        private String name;
    
        public String getName() {
            return name;
        }
    }
    

    输出结果:

    普通方式执行1亿次消耗:6ms
    反射方式执行1亿次消耗:4294ms
    关闭安全检查反射方式执行1亿次消耗:1963ms
    

    Ⅸ 反射操作泛型

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

    为了通过反射操作这些类型,Java新增了ParameterizedTypeGenericArrayTypeTypeVariableWildcardType几种类型来代表不能被归到Class类中的类型但是又和原始类型齐名的类型。

    • ParameterizedType:表示一种参数化类型,比如Collection<String>
    • GenericArrayType:表示种元素类型是参数化类型或者类型变量的数组类型
    • TypeVariable:是各种类型变量的公共父接口
    • WildcardType:代表种通配符类型表达式
    package com.nobody;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.Map;
    
    /**
     * @author Mr.nobody
     * @Description
     * @date 2020/9/5
     */
    public class Test03 {
    
        public void test01(Map<String, Integer> map, Person person) {
    
        }
    
        public Map<String, Student> test02() {
            return null;
        }
    
        public static void main(String[] args) throws NoSuchMethodException {
            Method test01 = Test03.class.getDeclaredMethod("test01", Map.class, Person.class);
            // 获取方法test01的参数类型
            Type[] genericParameterTypes = test01.getGenericParameterTypes();
            for (Type genericParameterType : genericParameterTypes) {
                System.out.println("<<< " + genericParameterType);
                // 如果参数类型等于参数化类型
                if (genericParameterType instanceof ParameterizedType) {
                    // 获得真实参数类型
                    Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                    for (Type actualTypeArgument : actualTypeArguments) {
                        System.out.println(actualTypeArgument);
                    }
                }
            }
    
            Method test02 = Test03.class.getDeclaredMethod("test02", null);
            // 获取方法test02的返回值类型
            Type genericReturnType = test02.getGenericReturnType();
            System.out.println("<<< " + genericReturnType);
            // 如果参数类型等于参数化类型
            if (genericReturnType instanceof ParameterizedType) {
                // 获得真实参数类型
                Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
    
        }
    }
    

    输出结果:

    <<< java.util.Map<java.lang.String, java.lang.Integer>
    class java.lang.String
    class java.lang.Integer
    <<< class com.nobody.Person
    <<< java.util.Map<java.lang.String, com.nobody.Student>
    class java.lang.String
    class com.nobody.Student
    

    Ⅹ 反射操作注解

    通过反射我们可以获取代码中的注解,并且获取注解的属性值,下面演示如何获取类和属性的注解,解析和数据库映射的相关信息。

    package com.nobody;
    
    import java.lang.annotation.*;
    
    /**
     * @author Mr.nobody
     * @Description
     * @date 2020/9/5
     */
    public class Test04 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
            Class<?> aClass = Class.forName("com.nobody.Book");
            // 获得Book类的注解
            Annotation[] annotations = aClass.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
            // 获取类的指定注解,并且获取注解的值
            Table annotation = aClass.getAnnotation(Table.class);
            String value = annotation.value();
            System.out.println("Book类映射的数据库表名:" + value);
    
            java.lang.reflect.Field bookName = aClass.getDeclaredField("bookName");
            Field annotation1 = bookName.getAnnotation(Field.class);
            System.out.println("bookName属性映射的数据库字段属性 - 列名:" + annotation1.colName()
                    + ",类型:" + annotation1.type() + ",长度:" + annotation1.length());
            java.lang.reflect.Field price = aClass.getDeclaredField("price");
            Field annotation2 = price.getAnnotation(Field.class);
            System.out.println("price属性映射的数据库字段属性 - 列名:" + annotation2.colName()
                    + ",类型:" + annotation2.type() + ",长度:" + annotation2.length());
        }
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Table {
        String value();
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Field {
        String colName();
    
        String type();
    
        int length();
    }
    
    @Table("t_book")
    class Book {
        @Field(colName = "name", type = "varchar", length = 15)
        String bookName;
        @Field(colName = "price", type = "int", length = 10)
        int price;
    
    }
    

    输出结果:

    @com.nobody.Table(value=t_book)
    Book类映射的数据库表名:t_book
    bookName属性映射的数据库字段属性 - 列名:name,类型:varchar,长度:15
    price属性映射的数据库字段属性 - 列名:price,类型:int,长度:10
    

    我是陈皮,一个在互联网 Coding 的 ITer,微信搜索「陈皮的JavaLib」第一时间阅读最新文章喔!

    本次分享到此结束啦~~

    相关文章

      网友评论

        本文标题:阿里P8教你Java注解与反射

        本文链接:https://www.haomeiwen.com/subject/xbaxbltx.html