注解与反射

Posted arno-vc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了注解与反射相关的知识,希望对你有一定的参考价值。

一、什么是注解

英文:annotation

注解与注释:注解与注释都是"注",但是注释是给人看的,而注解除了可以给人看,还可以给机器看的,被机器(编译器)读取

重要性:几乎是所有java框架的底层

二、内置注解:

1.@Override:表示一个方法打算重写超类(也就是父类)中的另一个方法声明

2.@Deprecated:表示一个方法很危险或者存在更好的选择,也就是常说的已废弃,通常是由于版本的更新迭代

3.@SuppressWarnings:通常用于抑制警告信息

三、元注解
  1. 作用:负责注解其他注解的注解

  2. meta-annotation

  3. 四个元注解:

    (1).@Target:用于描述注解的使用范围(通常为ElementType的静态字段值)

    (2).@Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE<CLASS<RUNTIME)

    (3).@Document:说明该注解将被包含在javadoc(API帮助文档)中

    (4).@Inherited:说明子类可以继承父类中的该注解

四、自定义注解
import java.lang.annotation.*;

public class MyTest {
    @myAnnotation(value=123,age=123)
    public void test(){
    }

    @myAnnotation(123)
    public void test1(){

    }
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface myAnnotation{
    String  name() default "123";
    int value();
    int age() default 123;
}

注意:

(1).使用@interface继承java.lang.annotation.Annotation接口

(2).注解的参数定义:类型 属性名() default 参数,其中默认的参数是可选的

(3).以value命名的属性:在使用注解并写入参数时可以不用写属性名

(4).注解的行为:通过反射实现

五、什么是反射
  1. java Reflectioin

  2. 重要性:是Java被视为动态语言的关键,反射机制允许程序借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

  3. 原理:Class c= Class.forName("java.lang.String")加载完类之后,在堆内存的方法区中就产生了一个CLass类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息.我们可以通过这个对象看到类的结构.这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射

    正常方式:引入需要的"包类"名称 → 通过new实例化 →取得实例化对象

    反射格式: 实例化对象 → getClass()方法 → 得到完整的"包类"名称

  4. 作用:

    (1).在运行时判断任意一个对象所属的类

    (2).在运行时构造任意一个类的对象

    (3).在运行时判断任意一个类所具有的成员变量和方法

    (4).在运行时获取泛型信息

    (5).在运行时调用任意一个对象的成员变量和方法

    (6).在运行时处理注解

    (7).生产动态代理(AOP相关)

  5. 关键:使用Class.forName(全限定类名)创建一个"Class对象",通过这个对象的方法可以获得实例

    注意:

    (1).一个类只有一个Class对象

    (2).一个类被加载后,类的整个结构都会被封装在Class对象

  6. 相关的api:

    (1).java.lang.Class:代表一个类

    (2).java.lang.reflect.Method:代表类的方法

    (3).java.lang.reflect.Field:代表类的成员变量

    (4).java.lang.reflect.Constructor:代表类的构造器

  7. Class与Object的关系:两者没有直接关系

    (1).Object是一切java类的父类

    (2).Class类是用于java反射机制的,一切java类都有一个对应的Class对象,他是一个Final类.Class类的实例表示,正在运行的Java应用程序中的类和接口

六、Class对象
  1. 获得:通过对象"照镜子"获得

  2. 注:

    (1).Class本身也是一个

    (2).Class对象只能由系统建立对象

    (3).一个加载的类在JVM中只会有一个Class实例

    (4).一个Class对象对应的是一个加载到JVM中的一个**.class文件**

    (5).每个类的实例都会记得自己是由哪个Class实例所生成

    (6).通过Class可以完整地得到一个类中的所有被加载的结构

    (7).Class类是Reflection的根源,针对任何你想动态加载,运行的类,唯有先获得相应的Class对象

  3. 获得Class类的实例

    (1).已知具体的类:Class<Student> studentClass = Student.class;

    (2).已知某个类的实例:Class aClass = student.getClass();

    (3).已知类的全类名:Class<?> student1 = Class.forName("Student");

    (4).内置基本数据类型可以直接用基本包装类型的类名.Type:

    Class<Integer> integerClass = Integer.class;  //class Integer
    Class<Integer> type = Integer.TYPE;  //int
    Class<Integer> integerClass = int.class;
    

    (5).之后还可以利用ClassLoader

  4. 哪些类型可以有Class对象?

    (1).class:各种class

    Class c1 = Object.class;  //class java.lang.Object
    

    (2).interface

    Class c2 = Comparable.class;  //interface java.lang.Comparable
    

    (3).[]

    Class c3 = String[].class;  //class [Ljava.lang.String;
    

    注:维度相同的数组属于同一个class

    (4).enum

    Class c4 = ElementType.class;  //class java.lang.annotation.ElementType
    

    (5).annotation

    Class c5 = Override.class;  //interface java.lang.Override
    

    (6).Void

    Class c6 = Void.class;  //Void
    
七、从内存分析反射机制(韩顺平 JVM)

测试代码

public class MyTest {
    public static void main(String[] args) throws ClassNotFoundException {
        A a = new A();
        System.out.println(A.m);
    }
}

class A {
    static{
        System.out.println("静态初始化");
        m = 100;
    }
    static int m =300;
    static int n;

    A(){
        System.out.println("A的无参构造初始化!");
    }
}
/*
静态初始化完成
A的无参构造初始化!
100
*/
  1. 加载到内存时会产生一个类对于class对象

  2. 链接,链接结束后m=0

  3. 初始化

    <clinit>(){
        System.out.println("静态初始化");
        m =300;
    }
    
八、什么时候回发生类初始化
  1. 类的主动引用(会发生类的初始化)

    (1).当虚拟机启动,先初始化main方法所在的类

    (2).new一个类的对象

    (3).调用类的静态方法(除了final常量)和静态方法

    (4).使用java.lang.reflect包的方法对类进行反射调用

    (5).当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

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

    (1).当访问一个静态域时,只有真正声明这个域的类才会被初始化.如:当通过子类引用父类的静态变量,不会导致子类初始化

    (2).通过数组定义类引用,不会触发此类的初始化,因为这实际上仅仅只是开辟了一个内存空间

    (3).引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

  3. 测试

    (1).环境搭建:

    
    
    class B{
        static {
            System.out.println("B类初始化完成");
        }
        static int b = 100;
    }
    
    class A extends B{
        static{
            m = 100;
            System.out.println("A类初始化完成");
        }
        static int m =300;
        static final int n=300;
    
    }
    

    (2).编写测试类

    public class MyTest {
    
        static{
            System.out.println("MyTest类被加载!");
        }
    
        public MyTest(){
            System.out.println("MyTest初始化!");
        }
    
        public static void main(String[] args) throws ClassNotFoundException {
    //        A a = new  A();
            /*
            * 先加载main方法所在类
            * 加载父类B
            * 加载子类A
            * */
    //        System.out.println(A.b);
            /*
            * 先加载main方法所在类
            * 由于调用的A类的父类B,所以这里只加载父类B
            * */
    //        System.out.println(A.n);
            /*
            * 调用的静态常量,这里只加载了main方法所在类
            * */
            A[] a = new A[5];  //这里初始化数组的大小需要数值说明
            /*
            * 类数组的初始化,直发生main方法所在类的加载
            * */
        }
    }
    
九、类加载器的作用
  1. 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行数据,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口

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

  3. 分类:

    (1).引导类加载器:C++编写,是JVM自带的类加载器,负责Java平台核心库(如java.lang包对应的rt.jar),用来装载核心类库,该加载器无法直接获取

    (2).扩展类加载器:负责jre/lib/ext目录下的jar包,

    (3).系统类加载器:负责java-classpath目录下的类加载器,是最常用的加载器

注:七、八、九三块内容都是对于类的内存分析

十、获取运行时类的完整结构

可以获得的结构:Field,Method,COnstructor,Superclass,Interface,Annotation

相关函数:

  1. getName():全类名(包名+类名)
  2. getSimpleName():获得类名
  3. getFields():找到所有public属性
  4. getDeclaredFields():找到本类所有属性
  5. getField(String name):找到所有public属性中指定的属性
  6. getDeclaredField(String name):找到本类所有属性中指定的属性
  7. getMethods():获得所有public方法
  8. getDeclaredMethods():获得本类的所有方法
  9. getMethod(String name,para…):获得所有public方法中指定的方法
  10. getDeclaredMethod(String name,para…):获得本类中指定的方法
  11. getConstructors():获得所有public构造方法
  12. getDeclaredConstructors():获得所有构造方法
  13. getDeclaredConstructor(para…):获得指定的构造器

注意:这里的"所有"的含义,有时指包括子类和父类,有时指包括public和private(对于带有Declared的内容)

测试:

(1).环境搭建

class T{

    public String T1;
    private String T2;
    public T(){
    }

    private T(String a){

    }

    public void method3(){

    }

    public void method4(){

    }
}

class TestClass extends T{
    public String field1;
    private String field2;

    public TestClass(){
    }

    private TestClass(String a){

    }

    private void method1(){

    }

    public void method2(){

    }
}

(2).测试

public class MyTest {

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        TestClass testClass = new TestClass();
        Class aClass = testClass.getClass();

        System.out.println(aClass.getName());
        System.out.println("================");
        System.out.println(aClass.getSimpleName());
        System.out.println("================");
        Field[] fields = aClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("================");
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
        System.out.println("================");
        System.out.println(aClass.getField("field1"));
        System.out.println("================");
        System.out.println(aClass.getDeclaredField("field2"));
        System.out.println("================");
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("================");
        Method[] declaredMethods = aClass.getDeclaredMethods();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
        System.out.println("================");
        System.out.println(aClass.getMethod("method2"));
        System.out.println("================");
        System.out.println(aClass.getDeclaredMethod("method1"));
        System.out.println("================");
        Constructor[] constructors = aClass.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("================");
        Constructor[] declaredConstructors = aClass.getDeclaredConstructors();
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
        System.out.println("================");
        System.out.println(aClass.getDeclaredConstructor());
    }
}

注:在实际开发中,我们用的并不多

十一、通过反射动态创建对象(间接)

环境

class T{

    public String T1;
    private String T2;
    public T(){
    }

    public T(String t1, String t2) {
        T1 = t1;
        T2 = t2;
    }

    public void method3(){

    }

    public void method4(){

    }
}

class TestClass extends T{
    private String field1;
    public String field2;

    public TestClass(){
    }

    public TestClass(String field1, String field2) {
        this.field1 = field1;
        this.field2 = field2;
    }

    public TestClass(String t1, String t2, String field1, String field2) {
        super(t1, t2);
        this.field1 = field1;
        this.field2 = field2;
    }

    public String method1(){
        return field1;
    }

    public String method2(){
        return field2;
    }

    @Override
    public String toString() {
        return "TestClass{" +
                "T1=‘" + T1 + ‘‘‘ +
                ", field1=‘" + field1 + ‘‘‘ +
                ", field2=‘" + field2 + ‘‘‘ +
                ‘}‘;
    }
}
  1. 通过newInstance构造

    //直接调用newInstance构造
    TestClass testClass = aClass.newInstance();
    System.out.println(testClass);
    
  2. 通过构造器构造

    //调用构造器构造
            Constructor constructor = aClass.getDeclaredConstructor(String.class, String.class);
            TestClass o = (TestClass) constructor.newInstance("123", "456");  //注意参数仍然要间接调用newInstance
            System.out.println(o);
    
  3. 操作方法

    //调用private方法
            Method method1 = aClass.getDeclaredMethod("method1");
            method1.setAccessible(true);  //设定可以访问
            System.out.println(method1.invoke(o));
    
  4. 操作属性

    //调用private字段
            Field field1 = aClass.getDeclaredField("field1");
            field1.setAccessible(true);
            field1.set(o,"变了");
            System.out.println(o.method1());
    

    注意:

    (1).通过反射构造下一切都是间接的调用

    (2).构造时需要使用newInstance(),如果要使用有参构造,可以先获得constructor再调用newInstance(),其参数需要放在new Instance中

    (3).调用函数需要使用invoke()激活

    (4).参数的设置和获取使用set/get方法

    (5).对于访问控制修饰符的限制,可以使用setAccessible()修改,当打开的情况下可以提高反射的效率

十二、性能分析

  1. 测试环境搭建

    class TestClass extends T{
        public String field2;
    
        public String method2(){
            return field2;
        }
    }
    
  2. 测试类

    public class MyTest {
    
        public static void test1(){
            TestClass testClass = new TestClass();
            long startTime = System.currentTimeMillis();
            for(int i=1;i<=100000000;i++){
                testClass.method2();
            }
            long endTime = System.currentTimeMillis();
            System.out.println("直接调用:" + (endTime-startTime) + "ms");
        }
    
        public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            TestClass testClass = new TestClass();
            long startTime = System.currentTimeMillis();
            Class aClass = testClass.getClass();
            Method method2 = aClass.getDeclaredMethod("method2");
            for(int i=1;i<=100000000;i++){
                method2.invoke(testClass,null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("直接调用:" + (endTime-startTime) + "ms");
        }
    
        public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            TestClass testClass = new TestClass();
            long startTime = System.currentTimeMillis();
            Class aClass = testClass.getClass();
            Method method2 = aClass.getDeclaredMethod("method2");
            method2.setAccessible(true);
            for(int i=1;i<=100000000;i++){
                method2.invoke(testClass,null);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("直接调用:" + (endTime-startTime) + "ms");
        }
    
        public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
            test1();
            test2();
            test3();
        }
    }
    
  3. 输出结果

    直接调用:4ms
    反射调用:438ms
    反射调用(关闭检测):249ms
    
  4. 结论

    (1).反射调用远远慢于直接调用,且这个差距是数量级的(约100倍)

    (2).反射调用可以通过关闭检测来提高效率(约2倍)

十三、反射操作泛型

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

具体 也是使用先关的函数:看视频我人傻了(可能也和没有特意学泛型有关)

重要的参数:

  1. ParameterizedType:表示一种参数化类型
  2. GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
  3. TYpeVariable:各种类型变量的公共父接口
  4. WildcardType:代表一种通配符类型表达式

十四、获得注解信息

测试:

  1. 环境搭建

    @tableAnnotation("table")
    class Table{
        @fieldAnnotation(name="name",type="String",length=10)
        public int field;
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface tableAnnotation{
        String value();
    }
    
    @Target({ElementType.TYPE,ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface fieldAnnotation{
        String name();
        String type();
        int length();
    }
    
  2. 测试

    public class User {
        public static void main(String[] args) throws NoSuchFieldException {
            Class aClass = Table.class;
            //获得类注解的参数
            tableAnnotation annotation = (tableAnnotation) aClass.getAnnotation(tableAnnotation.class);
            System.out.println(annotation.value());
    
            //获得字段注解的参数
            Field field = aClass.getField("field");
    //        System.out.println(field);
            fieldAnnotation annotation1 = field.getAnnotation(fieldAnnotation.class);
            System.out.println(annotation1.length());
        }
    
    }
    

以上是关于注解与反射的主要内容,如果未能解决你的问题,请参考以下文章

注解与反射

理解Android中的注解与反射

Java 注解与反射 基础

反射与注解的使用

Java注解与反射

java反射与注解结合使用(根据传入对象输出查询sql)