Java笔记:反射,注解

一、反射

1. 反射机制

反射机制的相关类除了一个java.lang.Class,其余都在java.lang.reflect包下。
反射机制用于读取class字节码文件,需要注意,JVM加载字节码到内存中时都只会保存一份,多次读取class文件时不用担心也会加载多次。
反射机制相关的常用类:

  • java.lang.Class:代表整个类的字节码,表示一个类型。
  • java.lang.reflect.Method:代表字节码中的方法字节码,表示一个方法。
  • java.lang.reflect.Constructor:代表字节码中的构造方法字节码,表示一个构造方法。
  • java.lang.reflect.Field:代表字节码中的属性字节码,表示一个属性。

2. 反射类字节码Class(类/类型)

获取类的字节码(java.lang.Class类)有三种方式:

  • 第一种方式:通过Class类的静态方法forName,例如Class c1 = Class.forName("java.lang.String");就表示获取到了String这个类的class字节码,注意,这是Class类下的一个静态方法,参数需要是完整的包名。另外,Class.forName方法的使用会导致类的加载,也就是说如果希望只是执行一个类的静态代码块,并不执行其他的代码,就可以使用这个方法来进行类的加载,此时,自然就会去执行对应的静态代码了。
  • 第二种方式:通过Object类的getClass方法,例如String s = "abc"; Class c2 = s.getClass();,即通过任何类对象的getClass方法就可以拿到对应了类字节码了,并且因为JVM只会在内存中加载一份相同类的字节码,所以这个例子的c2和第一种方式的c1使用双等号判断返回结果是true。
  • 第三种方式:通过类的class属性,例如Class c3 = String.class;,java中任何类型都有class属性。

Class中常用的方法:

  • String getName():返回类的完整类名(包含包路径)。
  • String getsimpleName():返回类的简类名(类定义名称)。
  • Field[] getFields():获取Class中所有public类型的Field对象(属性)。
  • Field[] getDeclaredFields():获取Class中所有的Field对象(属性)。
  • Field getDeclaredField(String name):获取指定名称的Field对象(属性)。
  • Method[] getDeclaredMethods():获取所有的Method对象(方法)。
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes):根据方法名称和参数类型列表获取Method对象(方法),例如“userClass.getDeclaredMethod("login", String.class, int.class);”。
  • Constructor<?>[] getDeclaredConstructors():获取所有的Constructor对象(构造方法)。
  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取指定参数类型列表的Constructor对象(构造方法),例如“userClass.getDeclaredConstructor(String.class, int.class);”。
  • Class<? super T> getSuperclass():获取类的父类。
  • Class<?>[] getInterfaces():获取所有类实现的接口。

3. 反射属性字节码Field(字段/属性)

获取Field需要先获取到对应的类字节码Class,然后才能从类中获取到对应的Field(java.lang.reflect.Field)。
Field常用方法:

  • Class<?> getType():返回属性的数据类型。
  • String getName():返回属性的名称。
  • int getModifiers():返回修饰符列表的代号,此代号可以使用java.lang.reflect.Modifier的静态方法toString方法传入代号获取到具体的修饰符名称列表。
  • void set(Object obj, Object value):给指定对象的Field对象赋予值value。
  • Object get(Object obj):获取指定对象的Field值(属性值)。
  • void setAccessible(boolean flag):设置为true时表示打破封装,如果不调用这个方法设置为true的话就没办法获取对象的私有属性了,调用这个方法之后就可以获取到所有的属性了,包括私有属性。

4. 反射方法字节码Method(方法)

获取Method需要先获取到对应的类字节码Class,然后才能从类中获取到对应的Method(java.lang.reflect.Method)。

Method中的常用方法:

  • Class<?> getReturnType():获取返回值的数据类型。
  • Class<?>[] getParameterTypes():获取方法的参数类型列表。
  • int getModifiers():返回修饰符列表的代号,此代号可以使用java.lang.reflect.Modifier的静态方法toString方法传入代号获取到具体的修饰符名称列表。
  • Object invoke(Object obj, Object... args):调用指定对象的方法。

5. 反射构造方法Constructor(构造方法)

获取Constructor需要先获取到对应的类字节码Class,然后才能从类中获取到对应的Method(java.lang.reflect.Constructor)。

Constructor中的常用方法:

  • int getModifiers():返回修饰符列表的代号,此代号可以使用java.lang.reflect.Modifier的静态方法toString方法传入代号获取到具体的修饰符名称列表。
  • T newInstance(Object... initargs):使用newInstance方法创建一个实例对象。

二、注解

1. 定义注解(Annotation)

注解,或者称之为注释,也是一种引用数据类型,编译之后也会生成class文件,具体用法见示例:

[修饰符列表] @interface 注解类型名{
    // 属性定义
}

注解定义示例:

// 无属性的注解定义
public @interface MyAnnotation{
    // 这里面什么都不写,表示没有属性
}


// 有属性的注解定义
public @interface MyAnnotation2{
    // 定义一个没有默认值的属性,在使用这个注解的时候就必须给这个属性传值
    // 注意,注解的属性定义是有小括号的,但它不是方法,就只是属性
    String name();
    
    // 使用default给属性指定默认值,有默认值的属性在使用时就可以不用给这个属性传值了
    int id() default 2333;
}


// 属性只有一个,且为value时,使用时可以不用指定属性名称
public @interface MyAnnotation3{
    String value();
}

注解使用示例:

// 使用:直接在类、方法、属性、形参、注解等上面使用形如”@注解类型名“的格式即可。
// 注解的使用其实就像修饰符一样在定义的前面加上就可以,但是通常的使用习惯是在定义上一行进行添加
public class AnnotationTest {
    public static void main(String[] args) {

    }

    // 相当于:@MyAnnotation private int id;
    @MyAnnotation
    private int id;

    // 注解只有一个属性,且属性名为value时,可以不用指定属性名
    @MyAnnotation3("hello")
    public AnnotationTest() {
    }

    // 定义了没有默认值的属性的注解,就必须给这个属性传值,有默认值的属性可以传,也可以不传
    @MyAnnotation2(name = "zhangsan")
    public static void func() {
        @MyAnnotation2(name = "lisi", id = 666)
        int i = 10;
    }

    // 相当于:@MyAnnotation public void func2(@MyAnnotation String name)
    @MyAnnotation
    public void func2(@MyAnnotation String name) {
        System.out.println(name);
    }
}

注解属性类型:定义注解的属性时,属性的类型可以是byte、short、int、long、float、double、boolean、char、String、Class、枚举类型,以及这几种类型的数组形式,不能是其他的类型。有一个小技巧,属性如果是数组,并且使用时传入的数组元素只有一个的话,定义数组的大括号是可以不写的。

2. Java内置注解

内置注解在java.lang包下,常用的有:

  • @Override:这个注解只能注解方法,并且只是给编译器在编译阶段做参考用的,和运行阶段的代码没有关系,编译器在编译时会检查这个方法是否是重写的父类方法,如果不是则会报错。
  • @Deprecated:表示被标注的类、方法等元素已经过时了,不建议使用。在IDEA中,被标注的方法等会出现一条横线,提示你这个方法已过时。

3. 元注解

用来标注“注解类型”的注解,即注解的注解,称之为元注解,在java.lang.annotation包下。常用的元注解有:

  • @Target(ANNOTATION_TYPE):用来指定被标注的注解可以出现在哪些位置上,参数为枚举类型ElementType的数组,具体有哪些枚举值可以参考帮助文档,如“@Target(ElementType.METHOD)”表示被标注的注解只能出现在方法上。
  • @Retention(RUNTIME):用来指定被标注的注解最终保存在哪里,参数是一个枚举类型RetentionPolicy,有三个枚举值,“@Retention(RetentionPolicy.SOURCE)”表示被标注的注解保存在java源文件中,并不会出现在编译之后的class文件中,“@Retention(RetentionPolicy.CLASS)”表示被标注的注解保存在class文件中,“@Retention(RetentionPolicy.RUNTIME)”表示被标注的注解保存在class文件中,并且可以被反射机制读取出来。

4. 反射注解

以类的注解为例,首先获取到类的字节码对象后,使用Class对象的方法进行反射,常用的方法有:

  • boolean isAnnotationPresent(MyAnnotation.class):判断一个类是否有指定的注解,这里需要传入一个指定注解的类字节码对象。
  • getAnnotation(MyAnnotation.class):获取类的指定注解对象,这里需要传入一个指定注解的类字节码对象。

属性获取:通过反射拿到注解对象之后,就可以通过调用方法(其实是属性)的形式获取属性值,因为注解定义属性时,本身就自带小括号,所以看起来就是在调用方法了,如“String name = annotationObj.name();”
注:方法、属性等的注解获取也是和上面的方法一样,而且调用的方法等大多也都是一样的。

5. 注解的作用

注解通常是通过反射机制去检查被注解的类、方法等是否满足要求,比如@Override就是检查被注解的方法是否是重写父类的方法,如果不是就会编译报错。

原文地址:https://www.cnblogs.com/guyuyun/p/13193546.html