欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 注解与反射学习笔记

程序员文章站 2024-02-16 09:48:04
...

注解

什么是注解

  • Annotation是从JDK5.0开始引入的新技术

  • Annotation的作用:

    • 不是程序本身,可以对程序做出解释(这一点和注释(comment)没有什么区别)
    • 可以被其他程序(比如编译器)读取。
  • Annotation的格式:

    • 注解是以"@注释名"在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings(value=“unchecked”)
  • Annotation在哪里使用?

    • 可以附加在package、class、method、field等上面,相当于给它们添加了额外的辅助信息,可以使用反射机制编程来实现对这些元数据的访问

注解概念

​ Java 注解(Annotation),是 JDK5.0 引入的一种注释机制。Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。注解可以被看作是对 一个 类/方法 的一个扩展的模版,按照注解类中的规则,处理不同的逻辑。

注解的作用:

  • 编写文档:通过代码里标识的注解生成文档
  • 编译检查:通过代码里标识的注解让编译器实现基本的编译检查
  • 代码分析:通过代码里标识的注解对代码进行分析

Java 注解使本来可能需要很多配置文件,需要很多逻辑才能实现的内容,可以使用一个或者多个注解来替代,这样就使得编程更加简洁,代码更加清晰。Java 注解在框架代码如Spring boot中得到了广泛的应用。

内置注解:

  • @Override - 编译检查,告诉编译器这个是个覆盖父类的方法。如果父类删除了该方法,则子类会报错。
  • @Deprecated - 编译检查,表示被注解的元素已被弃用。
  • @SuppressWarnings - 编译检查,告诉编译器忽略警告。

元注解

  • 元注解顾名思义我们可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种。

@Retention

  • Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期

  • @Retention(RetentionPolicy.SOURCE),注解仅存在于源码中,在class字节码文件中不包含

  • @Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得

  • @Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到

  • 如果我们是自定义注解,则通过前面分析,我们自定义注解如果只存着源码中或者字节码文件中就无法发挥作用,而在运行期间能获取到注解才能实现我们目的,所以自定义注解中肯定是使用 @Retention(RetentionPolicy.RUNTIME)

    @Retention(RetentionPolicy.RUNTIME)
    @interface  MyAnnotation{
    
    }
    

@Target

  • Target的英文意思是目标,这也很容易理解,使用@Target元注解表示我们的注解作用的范围就比较具体了,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型
  • @Target(ElementType.TYPE) 作用接口、类、枚举、注解
  • @Target(ElementType.FIELD) 作用属性字段、枚举的常量
  • @Target(ElementType.METHOD) 作用方法
  • @Target(ElementType.PARAMETER) 作用方法参数
  • @Target(ElementType.CONSTRUCTOR) 作用构造函数
  • @Target(ElementType.LOCAL_VARIABLE)作用局部变量
  • @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
  • @Target(ElementType.PACKAGE) 作用于包
  • @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
  • @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)
  • 一般比较常用的是ElementType.TYPE类型
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface  MyAnnotation{

}

@Documented

  • Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Inherited

  • Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也继承了父类的注解。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@interface  MyAnnotation{

}

@Repeatable

  • Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。

注解的本质

  • 注解的本质就是一个Annotation接口
/**Annotation接口源码*/
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    Class<? extends Annotation> annotationType();
}
  • 通过以上源码,我们知道注解本身就是Annotation接口的子接口,也就是说注解中其实是可以有属性和方法,但是接口中的属性都是static final的,对于注解来说没什么意义,而我们定义接口的方法就相当于注解的属性,也就对应了前面说的为什么注解只有属性成员变量,其实他就是接口的方法,这就是为什么成员变量会有括号,不同于接口我们可以在注解的括号中给成员变量赋值。

注解属性类型

  • 注解属性类型可以有以下列出的类型
  • 1.基本数据类型
  • 2.String
  • 3.枚举类型
  • 4.注解类型
  • 5.Class类型
  • 6.以上类型的一维数组类型

注解成员变量赋值

  • 如果注解又多个属性,则可以在注解括号中用“,”号隔开分别给对应的属性赋值,如下例子,注解在父类中赋值属性
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
    String name() default "mao";
    int age() default 18;
}

@MyTestAnnotation(name = "father",age = 50)
public class Father {
}

获取注解属性

  • 前面我们说了很多注解如何定义,放在哪,现在我们可以开始学习注解属性的提取了,这才是使用注解的关键,获取属性的值才是使用注解的目的。
  • 如果获取注解属性,当然是反射啦,主要有三个基本的方法
/**是否存在对应 Annotation 对象*/
  public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return GenericDeclaration.super.isAnnotationPresent(annotationClass);
    }

 /**获取 Annotation 对象*/
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().annotations.get(annotationClass);
    }
 /**获取所有 Annotation 对象数组*/   
 public Annotation[] getAnnotations() {
        return AnnotationParser.toArray(annotationData().annotations);
    }    

使用注解进行参数配置

  • 下面我们看一个银行转账的例子,假设银行有个转账业务,转账的限额可能会根据汇率的变化而变化,我们可以利用注解灵活配置转账的限额,而不用每次都去修改我们的业务代码
/**定义限额注解*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BankTransferMoney {
    double maxMoney() default 10000;
}
/**转账处理业务类*/
public class BankService {
    /**
     * @param money 转账金额
     */
    @BankTransferMoney(maxMoney = 15000)
    public static void TransferMoney(double money){
        System.out.println(processAnnotationMoney(money));

    }
    private static String processAnnotationMoney(double money) {
        try {
            Method transferMoney = BankService.class.getDeclaredMethod("TransferMoney",double.class);
            boolean annotationPresent = transferMoney.isAnnotationPresent(BankTransferMoney.class);
            if(annotationPresent){
                BankTransferMoney annotation = transferMoney.getAnnotation(BankTransferMoney.class);
                double l = annotation.maxMoney();
                if(money>l){
                   return "转账金额大于限额,转账失败";
                }else {
                    return"转账金额为:"+money+",转账成功";
                }
            }
        } catch ( NoSuchMethodException e) {
            e.printStackTrace();
        }
        return "转账处理失败";
    }
    public static void main(String[] args){
        TransferMoney(10000);
    }
}

运行结果:

Java 注解与反射学习笔记

  • 通过上面的例子,只要汇率变化,我们就改变注解的配置值就可以直接改变当前最大限额。

第三方框架的应用

  • 作为一个Android 开发者,平常我们所使用的第三方框架ButterKnife,Retrofit2,Dagger2等都有注解的应用,如果我们要了解这些框架的原理,则注解的基础知识则是必不可少的。

注解的作用

  • 提供信息给编译器: 编译器可以利用注解来检测出错误或者警告信息,打印出日志。
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来自动生成代码、文档或者做其它相应的自动处理。
  • 运行时处理: 某些注解可以在程序运行的时候接受代码的提取,自动做相应的操作。
  • 正如官方文档的那句话所说,注解能够提供元数据,转账例子中处理获取注解值的过程是我们开发者直接写的注解提取逻辑,处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。上面转账例子中的processAnnotationMoney方法就可以理解为APT工具类。

反射

什么是Java反射机制

ava反射是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。

Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。

换言之,Java可以加载一个运行时才得知名称的class,获得其完整结构。

Java Reflection

  • Reflection(反射)是JAva被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API获取任何内部的信息,并能直接操作任意对象的内部属性及方法。

    class1 = Class.forName("com.lvr.reflection.Person");
    
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个时候就包含了完整的类的结构信息。可以通过这个对象卡到类的结构。这个对象就像一面镜子,通过这个镜子就可以看到类的结构,所以,形象的称为:反射

Java 注解与反射学习笔记

Java 反射机制的功能

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

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

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

4.在运行时调用任意一个对象的方法。

5.生成动态代理。

Java 反射机制的应用场景

1.逆向代码 ,例如反编译

2.与注解相结合的框架 例如Retrofit

3.单纯的反射机制应用框架 例如EventBus

4.动态生成类框架 例如Gson

通过Java反射查看类信息

Class类的常用方法

Java 注解与反射学习笔记

获得Class对象
每个类被加载之后,系统就会为该类生成一个对应的Class对象。通过该Class对象就可以访问到JVM中的这个类。

在Java程序中获得Class对象通常有如下三种方式:

1.使用Class类的forName(String clazzName)静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定名(必须添加完整包名)。

2.调用某个类的class属性来获取该类对应的Class对象。

3.调用某个对象的getClass()方法。该方法是java.lang.Object类中的一个方法。

  //方式一:通过对象的getClass获得
     Person person=new Student();
        Class c1 = person.getClass();
        //方式二:过Class类的静态方法——forName()来实现
        Class c2 = Class.forName("com.yaco.test.Student");
        //方式三:通过类的class属性
        Class<Student> c3 = Student.class;

获取class对象的属性、方法、构造函数等

1.获取class对象的成员变量

Field[] allFields = class1.getDeclaredFields();//获取class对象的所有属性
Field[] publicFields = class1.getFields();//获取class对象的public属性
Field ageField = class1.getDeclaredField("age");//获取class指定属性
Field desField = class1.getField("des");//获取class指定的public属性

2.获取class对象的方法

Method[] methods = class1.getDeclaredMethods();//获取class对象的所有声明方法
Method[] allMethods = class1.getMethods();//获取class对象的所有public方法 包括父类的方法
Method method = class1.getMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的public方法
Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的方法

3.获取class对象的构造函数

Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的所有声明构造函数
Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数
Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数
Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数

4.其他方法

Annotation[] annotations = (Annotation[]) class1.getAnnotations();//获取class对象的所有注解
Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//获取class对象指定注解
Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的 Type
Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的所有接口的type集合

获取class对象的信息

boolean isPrimitive = class1.isPrimitive();//判断是否是基础类型
boolean isArray = class1.isArray();//判断是否是集合类
boolean isAnnotation = class1.isAnnotation();//判断是否是注解类
boolean isInterface = class1.isInterface();//判断是否是接口类
boolean isEnum = class1.isEnum();//判断是否是枚举类
boolean isAnonymousClass = class1.isAnonymousClass();//判断是否是匿名内部类
boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰
String className = class1.getName();//获取class名字 包含包名路径
Package aPackage = class1.getPackage();//获取class的包信息
String simpleName = class1.getSimpleName();//获取class类名
int modifiers = class1.getModifiers();//获取class访问权限
Class<?>[] declaredClasses = class1.getDeclaredClasses();//内部类
Class<?> declaringClass = class1.getDeclaringClass();//外部类

那些类型可以有Class对象

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。

  • interface:接口

  • []:数组

  • enum:枚举

  • annotation:注解@interface

  • primitive type:基本数据类型

  • void

Java内存分析

Java 注解与反射学习笔记

类加载机制

把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
在Java语言里,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性,Java里天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点来实现的。

类的声命周期:

加载,验证,准备,解析,初始化,使用和卸载。其中验证,准备,解析三个部分统称为连接。

这七个阶段发生顺序如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E7Jw3fe7-1598270553147)(注解与反射.assets\image-20200509153903549.png)]

加载,验证,准备,初始化,卸载这5个阶段的顺序是确定的,而解析阶段则不一定:它在某些情况下可以在初始化完成后在开始,这是为了支持Java语言的运行时绑定。

其中加载,验证,准备,解析及初始化是属于类加载机制中的步骤。注意此处的加载不等同于类加载。

触发类加载的条件:

①.遇到new,getstatic,putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段的时候(被final修饰,已在编译期把结果放入常量池的静态字段除外),以及调用一个类的静态方法的时候。

②.使用java.lang.reflect包的方法对类进行反射调用的时候。

③.当初始化一个类的时候,发现其父类还没有进行过初始化,则需要先出发父类的初始化。

④.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

⑤.当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出发初始化

类加载的具体过程:

加载:
①.通过一个类的全限定名来获取定义此类的二进制字节流

②.将这个字节流所代表的静态存储结构转换为方法区内的运行时数据结构

③.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证:

是连接阶段的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

包含四个阶段的校验动作

a.文件格式验证
验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。
b.元数据验证
对类的元数据信息进行语义校验,是否不存在不符合Java语言规范的元数据信息
c.字节码验证
最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。
d.符号引用验证
最后一个阶段的校验发生在虚拟机将符号引用转换为直接引用的时候,这个转换动作将在连接的第三个阶段——解析阶段中发生。
符号验证的目的是确保解析动作能正常进行。

准备:
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。这些变量所使用的内存都将在方法区中分配。只包括类变量。初始值“通常情况”下是数据类型的零值。

“特殊情况”下,如果类字段的字段属性表中存在ConstantValue属性,那么在准备阶段变量的值就会被初始化为ConstantValue属性所指定的值。

解析:
虚拟机将常量池内的符号引用替换为直接引用的过程。

“动态解析”的含义就是必须等到程序实际运行到这条指令的时候,解析动作才能进行。相对的,其余可触发解析的指令都是“静态”的,可以在刚刚完成加载阶段,还没有开始执行代码时就进行解析。

初始化:

类加载过程中的最后一步。

初始化阶段是执行类构造器()方法的过程。

()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的

()与类的构造函数不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的()方法执行之前,父类的()方法已经执行完毕。

简单地说,初始化就是对类变量进行赋值及执行静态代码块。

什么时候类会发生初始化

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

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

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

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(常量在加载阶段就存入调用类的常量池汇中)

类加载器

通过上述的了解,我们已经知道了类加载机制的大概流程及各个部分的功能。其中加载部分的功能是将类的class文件读入内存,并为之创建一个java.lang.Class对象。这部分功能就是由类加载器来实现的。

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

类加载器分类:

不同的类加载器负责加载不同的类。主要分为两类。

**启动类加载器(Bootstrap ClassLoader):**由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中,即负责加载Java的核心类。

**其他类加载器:**由Java语言实现,继承自抽象类ClassLoader。如:

**扩展类加载器(Extension ClassLoader):**负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库,即负责加载Java扩展的核心类之外的类。

**应用程序类加载器(Application ClassLoader):**负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器,通过ClassLoader.getSystemClassLoader()方法直接获取。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

以上2大类,3小类类加载器基本上负责了所有Java类的加载。下面我们来具体了解上述几个类加载器实现类加载过程时相互配合协作的流程。

  //获取系统类的加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        //获取系统类的加载器的父类加载器-->扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);
        //获取扩展类加载器的父类加载器-->根加载器
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);
        //测试当前类是哪个加载器加载的
        Class c1 = Test7.class;
        ClassLoader classLoader = c1.getClassLoader();
        System.out.println(classLoader);
        //测试JDK内置类的加载器
        Class c2 = Object.class;
        ClassLoader loader = c2.getClassLoader();
        System.out.println(loader);

        //如何获得系统类加载器可以加载的路径
        System.out.println(System.getProperty("java.class.path"));

双亲委派模型

双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

Java 注解与反射学习笔记

这样的好处是不同层次的类加载器具有不同优先级,比如所有Java对象的超级父类java.lang.Object,位于rt.jar,无论哪个类加载器加载该类,最终都是由启动类加载器进行加载,保证安全。即使用户自己编写一个java.lang.Object类并放入程序中,虽能正常编译,但不会被加载运行,保证不会出现混乱。

双亲委派模型的代码实现

ClassLoader中loadClass方法实现了双亲委派模型

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //检查该类是否已经加载过
        Class c = findLoadedClass(name);
        if (c == null) {
            //如果该类没有加载,则进入该分支
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //当父类的加载器不为空,则通过父类的loadClass来加载该类
                    c = parent.loadClass(name, false);
                } else {
                    //当父类的加载器为空,则调用启动类加载器来加载该类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //非空父类的类加载器无法找到相应的类,则抛出异常
            }

            if (c == null) {
                //当父类加载器无法加载时,则调用findClass方法来加载该类
                long t1 = System.nanoTime();
                c = findClass(name); //用户可通过覆写该方法,来自定义类加载器

                //用于统计类加载器相关的信息
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            //对类进行link操作
            resolveClass(c);
        }
        return c;
    }
}

整个流程大致如下:

a.首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。

b.如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。

c.如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

获取运行时类的完整结构

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

Field、Method、Constructor、Superclass、Interface、Annotation

  • 实现的全部接口
  • 所继承的父类
  • 全部的构造器
  • 全部的方法
  • 全部的Field
  • 注解 …
   Class c1 = User.class;
        //获得类的名字
        System.out.println(c1.getName()); //获得包名+类名
        //获得类的简单名字
        System.out.println(c1.getSimpleName()); //获得
        //获得类的属性
        Field[] fields = c1.getFields(); //只能获取public属性
        for (Field field : fields) {
            System.out.println(field);
        }
        for (Field declaredField : c1.getDeclaredFields()) { //可以找到全部的属性
            System.out.println(declaredField);
        }
        //获得指定属性的值
        Field name = c1.getDeclaredField("name");
        System.out.println(name);
        //获得类的方法
        Method[] methods = c1.getMethods(); //获得本类及其父类的全部public方法
        for (Method method : methods) {
            System.out.println(method);
        }
        Method[] declaredMethods = c1.getDeclaredMethods();//获得本类的所有方法
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
        }
        //获得指定方法
        Method getName = c1.getMethod("getName", null);
        Method setName = c1.getMethod("setName", String.class);
        System.out.println(getName);
        System.out.println(setName);

        //获得构造器
        Constructor[] constructors = c1.getConstructors(); //获取public构造方法
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        constructors=c1.getDeclaredConstructors();//获得全部的构造方法
        for (Constructor constructor : constructors) {
            System.out.println("#"+constructor);
        }
        //获得指定的构造器
        Constructor declaredConstructor = c1.getDeclaredConstructor(String.class,int.class,int.class);
        System.out.println(declaredConstructor);
    }

有了Class对象,能做什么?

  • 创建类的对象:调用Class对象的newInstance()方法
    • 类必须有无参构造器
      • 类的构造器的访问权限需要足够

**思考?**难道没有无参构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例操作。

具体步骤:

  1. 通过Class类的getDeclaredConstructor(Class…parameterTypes)取得本类的指定形参类型的构造器
  2. 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
  3. 通过Constructor实例化对象

调用指定的方法

通过反射,调用类中的方法,通过Method完成,

  1. 通过Class类的getMethod(String name,CLass …ParameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的的参数类型。
  2. 之后使用Object invoke(Object object,Object[] args)并进行调用,并向方法中传递设置的Object对象的参数信息

Java 注解与反射学习笔记

Object invoke(Object object,Object[] args)

  • Object对应原方法的返回值,若原方法无返回值,此时返回null
  • 若原方法为静态方法,只是形参Object object可为null
  • 若原方法形参列表为空,则Object[] args为null
  • 若原方法声明为private,则需要在调用此invoke()方法前,显示调用方法对象的setAccessible(true)方法,将可访private方法

setAssessible()

  • Method和Field、Constructor对象都有setAccessible()方法

  • setAccessible作用是启动和禁用访问安全检查开关

  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。

    • ​ 提高反射的效率,如果代码必使用反射,而该句代码需要频繁调用,那么请设置为true
    • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施Java语言访问检查

  public static void main(String[] args) throws Exception {
        //获得Class对象
        Class c1 = Class.forName("com.yaco.test.User");
        //构造一个对象
        User user = (User) c1.newInstance();  //调用了类的无参构造器
        System.out.println(user);
        //通过构造器创建一个对象
        Constructor constructor = c1.getConstructor(String.class, int.class, int.class);
        User user1 = (User) constructor.newInstance("小楠", 111, 222);
        System.out.println(user1);

        //通过反射调用普通方法
        User user2 = (User) c1.newInstance();
        //通过反射获得一个方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        //invoke:**
        //参数(对象,"方法的值")
        setName.invoke(user2,"小楠");
        System.out.println(user2.getName());
        //通过反射操作属性
        User user3 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        //不能直接操作私有属性,需要关闭程序的安全监测,属性或者方法的setAccessible(true)
        name.setAccessible(true);
        name.set(user3,"小楠2");
        System.out.println(user3.getName());

    }

性能分析测试

    //普通方法调用
    public static void test(){
        User user = new User();

        long startTime =System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }


        long endTime=System.currentTimeMillis();

        System.out.println("普通方法执行10亿次"+(endTime-startTime)+"ms");
    }

    //反射方式调用
    public static void test1() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class userClass = user.getClass();

        Method getName = userClass.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 static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class userClass = user.getClass();

        Method getName = userClass.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) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test();
        test1();
        test2();
    }

测试结果
Java 注解与反射学习笔记

反射操作泛型

  • Java采用泛型擦除的机制引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除。
  • 为了通过反射操作这些类型,Java新增了ParameteriZedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
    • ParameteriZedType:表示一种参数化类型,比如Collection
    • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
    • TypeVariable:是各种类型变量的公共父接口
    • WildcardType:代表一种通配符类型表达式
//通过反射获取泛型
public class Test11 {
    public void  test(Map<String,User> map, List<User> list){
        System.out.println("test");
    }
    public Map<String, User> test1(){
        System.out.println("test1");
        return null;
    }


    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test11.class.getMethod("test", Map.class, List.class);
        //getGenericParameterTypes:得到泛型的参数类型
        Type[] genericParameterTypes = method.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=Test11.class.getMethod("test1",null);
        //得到泛型返回值类型
        Type genericReturnType = method.getGenericReturnType();

        if (genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println("======");
                System.out.println(actualTypeArgument);
            }
        }

    }

反射操作注解

  • getAnnotation
  • getAnnotations

注解:

/**
 * @author XIANS
 */
//类名的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Table{
    String value();
}
/**
 * @author XIANS
 */ //属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field{
    String columnName();
    String type();
    int length();
}

实体类

@Table("db_personnel")
class Personnel{
    @Field(columnName = "db_name",type = "varchar",length = 5)
    private String name;
    @Field(columnName = "db_id",type = "int",length = 10)
    private int id;
    @Field(columnName = "db_age",type = "int",length = 20)
    private int age;

    public Personnel() {
    }

    public Personnel(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Personnel{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }
}

测试代码:

//通过反射操作注解
public class Test12 {
    public static void main(String[] args) throws NoSuchFieldException {
        Class c1 = Personnel.class;
        //通过反射获得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //获得注解value的值
        Table table = (Table) c1.getAnnotation(Table.class);
        System.out.println(table.value());
        //获得类指定的注解
        java.lang.reflect.Field field = c1.getDeclaredField("name");
        Field annotation = field.getAnnotation(Field.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.length());
        System.out.println(annotation.type());
    }
}

全部测试一遍即可

相关标签: java