注解

什么是注解

注解就是对程序做出解释,与我们在方法、类上的注释没什么区别,但是注解可以被其他程序所读取,进行信息处理,否则与注释没有太大区别.

内置注解

内置注解就是JDK所带的一些注解.常用的三个注解:

  • @Override

    修辞方法,表示打算重写超类中的方法声明.

  • @Deprecated

    表示废弃,这个注解可以修饰方法、属性、类表示不鼓励程序员使用这样的元素,通常是因为他们危险或有更好的选择.

  • @SuperWarnings

    抑制警告信息,我们写程序的时候,可能会报很多黄线的警告,但是不影响允许,我们就可以用这个注解来抑制隐藏他们.

    与前两个注解不同的是,我们必须给注解参数才可以使用它.

参数 说明
deprecation 使用了过时的类或方法的警告
unchecked 执行了未检查的转换时的警告 如:使用集合时未指定泛型
fallthrough 当在switch语句使用时发生case穿透
path 在类路径、源文件路径中有不存在路径的警告
serial 当在序列化的类上缺少serialVersionUID定义时的警告
finally 任何finally子句不能完成时的警告
all 关于以上所有的警告

自定义注解

public @interface 注解名 { 定义体 }

@interface MyAnnotation{
    //注解的参数名称: 参数类型 + 参数名 ();
    String name() default "";
    int age() default 0;
    int id() default -1;//如果为-1,代表不存在

    String[] schools();
}

需要注意:

  • 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
  • 每个方法实际上是声明了一个配置参数
  • 方法的名称就是参数的名称
  • 返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)
  • 可以通过default来声明参数的默认值
  • 如果只有一个参数成员,一般参数名为value
  • 使用注解元素时必须要有值,可以定义默认值,空字符串,0或者-1

元注解

在自定义注解时,需要使用Java提供的元注解,就是复制注解的其他注解.Java定义了四个标准的meta-annotation类型,它们被用来提供对其他注解类型的声明.

  • @Target

    用来描述注解的使用范围,就是注解可以被应用在哪个地方.

所修饰范围 取值ElementType
package包 PACKAGE
类、接口、枚举、Annotation类型 TYPE
类型成员(方法、构造方法、成员变量、枚举值) CONSTRUCTOR:用于描述构造器。FIELD:用于描述域。METHOD:用于描述方法
方法参数和本地变量 LOCAL_VARIABLE:用于描述局部变量。PARAMETER:用于描述参数
  • Retention

    告诉编译器需要在什么级别保存该注解信息,用于描述注解的生命周期

取值RetentionPolicy 作用
SOURCE 在源文件中有效(即源文件保留)
CLASS 在Class文件中有效(即Class保留)
RUNTIME 在运行时有效(即运行时保留)注:为RUNTIME时可以被反射机制所获取

一般情况下,我们使用RUNTIME即可.这样程序运行时我们也可以通过反射机制来读取到该注解.

  • @Document
  • @Inherited

反射

什么是反射

反射是Java迈向动态语言的一步,让Java成为了一个伪动态语言,反射机制允许程序在执行期间借助ReflecctionAPI取得类的任何信息,并能直接操作对象的内部属性及方法.

加载完类之后,方法区会存放类的信息,包含类的完整结构信息,我们可以通过它看到类的结构,所以我们称之为反射.

image-20210715112221144

Class类

使用反射,离不开java.lang.Class这个类.

如何获取Class

  1. 通过Class.forName()获取(最常用)

    public class TestReflect {
        public static void main(String[] args) {
            try {
                //获取User的Class对象,参数为需要获取类对象的全类名
               Class aClass = Class.forName("sml.reflect.User");
            //因为是动态编译,所有我们需要抛出类未找到的异常   
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
  2. 通过getClass()获取

    public class TestReflect {
        public static void main(String[] args) {
            //new一个user对象
            User user = new User();
            //通过user对象来获取User类对象
            Class aClass = user.getClass();
        }
    }
  3. 通过.class获取

    public class TestReflect {
        public static void main(String[] args) {
            //通过导包获取类名点class来获取类对象
            Class aClass = User.class;
        }
    }

反射的基本操作

获取到Class对象之后,可以获取类的信息,这里列举常用的:

  1. 获取类名

    Class aClass = Class.forName("sml.reflect.User");
     //获取全类名
    String name = aClass.getName();
     //获取简单的类名
    String simpleName = aClass.getSimpleName();
  2. 获取类的字段、某些变量

    Class aClass = Class.forName("sml.reflect.User");
    
    //获取该类的所有public字段,包括父类的
    Field[] fields = aClass.getFields();
    //根据字段名获取该类的public字段
    Field field = aClass.getField("age");
    
    //获取该类的所有字段,不包括父类(仅自定义)
    Field[] fields1 = aClass.getDeclaredFields();
    //根据字段名获取该类的字段
    Field field1 = aClass.getDeclaredField("name");

    注意:我们仔细看注释,不带Declared的方法职能获取到public字段,且包括父类的,带Declared的方法可以获取到所有的自定义的字段!

  3. 获取类的方法

    Class aClass = Class.forName("sml.reflect.User");
    
    //获取该类的所有public方法,包括父类的
    Method[] methods = aClass.getMethods();
    //根据方法名获取该类的public方法
    Method method = aClass.getMethod("getName");
    //如果该类为重写方法,可以在第二个参数加上重写方法的参数类型,不写为无参数的方法
    Method paramMethod = aClass.getMethod("getName",String.class);
    
    //获取该类的所有方法,不包括父类(仅自定义)
    Method[] declaredMethods = aClass.getDeclaredMethods();
    //根据方法名获取该类的方法
    Method declaredMethod = aClass.getDeclaredMethod("getName");

    注:获取方法的方式与获取字段的方法一样,在这里我们需要注意的是重写的方法,一个类中存在俩个或多个方法名是一样的,因此在根据方法名获取方法时,提供第二个参数,为可变参数。参数类型为我们获取方法的参数类型的类,如果不写默认为无参方法。

  4. 获取类的构造器

    Class aClass = Class.forName("sml.reflect.User");
    
    //获取该类的所有构造器,包括父类
    Constructor[] constructors = aClass.getConstructors();
    //根据构造器的参数类型来获取指定构造器,不写为无参构造器
    Constructor constructor = aClass.getConstructor();
    Constructor constructor1 = aClass.getConstructor(String.class,int.class);
    
    //获取该类的所有构造器,不包括父类
    Constructor[] declaredConstructors = aClass.getDeclaredConstructors();
    //根据构造器的参数类型来获取指定的自定义构造器,不写为无参构造器
    Constructor declaredConstructor = aClass.getDeclaredConstructor();
    Constructor declaredConstructor1 = aClass.getDeclaredConstructor(String.class, int.class);

    注:在我们获取类构造器和类方法时涉及到可变参数的知识,大家可以自行百度一下,或者查阅官方文档,也不难,就是在我们不确定参数有几个时,就可以写成可变参数,我们在使用时可以传多个参数。注意我们写的参数都为类对象!

  5. 类的实例化

    Class aClass = Class.forName("sml.reflect.User");
    
    //通过class类直接实例化,使用的是User类的无参构造器
    User user = (User) aClass.newInstance();
                
    //获取构造器来进行实例化,这里获取有参构造器
    Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class, int.class);
    //根据构造器进行实例化
    User user1 = (User) declaredConstructor.newInstance("sml",18);

    注:我们在使用类对象直接实例化时,一定要确保需实例化的类中存在无参构造器,否则会报错。默认获取的是Object类型,因此最后需要进行下类型转化。

  6. 方法的调用

    Class aClass = Class.forName("sml.reflect.User");
    
    User user = (User) aClass.newInstance();
    //获取setName方法
    Method setName = aClass.getDeclaredMethod("setName", String.class);
    //通过获取的方法来调用(invoke),invoke方法有俩个参数
    //第一个是调用底层方法的对象,也就是通过哪个对象来调用方法
    //第二个为可变参数,是用于方法调用的参数
    setName.invoke(user,"sml");

    注:如果我们调用的方法为私有方法,虽然编译器通过,在运行时会报错的(java.lang.IllegalAccessException),这是因为java的安全检查。我们可以使用setAccessible(true)这个方法来跳过安全检查。

    Class aClass = Class.forName("sml.reflect.User");
    
    User user = (User) aClass.newInstance();
    Method setName = aClass.getDeclaredMethod("setName", String.class);
    //若setName为私有方法,跳过安全检查
    setName.setAccessible(true);
    setName.invoke(user,"sml");
  7. 字段的操作

    Class aClass = Class.forName("sml.reflect.User");
    
    User user = (User) aClass.newInstance();
    //获取name字段
    Field name = aClass.getDeclaredField("name");
    //通过该字段的set方法来改变该字段的值,该字段有俩个参数
    //第一个为应该修改其字段的参数
    //第二个为被修改字段的新值
    
    //如果是私有字段,同样需要添加setAccessible(true)
    //setName.setAccessible(true);
    name.set(user,"sml");
  8. 泛型的操作(Generic)

    Java采用泛型擦除机制来引入泛型,也就是说java的泛型仅仅只是给编译器javac使用的,确保数据安全免去强制类型转换的麻烦,一旦完成编译,这泛型等于没有.

    为了通过反射操作这些类型以迎合实际开发需求,Java新增了ParameterizedTypeGenericArrayTypeTypeVariableWildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型,这四种类型实现了Type接口.

    类型 含义
    ParameterizedType 参数化类型,带有类型参数的类型,即常说的泛型,如:List
    TypeVariable 类型变量,如参数化类型Map<E,Y>中的Y,K等类型变量,表示泛指任何类
    GenericArrayType (泛型)数组类型,比如List[],T[]这种,注意,这不是一般数组,而是表示一种{元素类型是参数化类型或者类型变量的}数组类型
    WildcardType 代表通配符表达式,或泛型表达式,比如[?],[? super T],[? extends T],虽然WildcardType是Type的一个子接口,但并不是Java类型中的一种
    public class TestReflect {
        //测试方法,返回类型与参数都为泛型
        public static Map<String,User> GenericityTest(List<User> list,User user){
            return null;
        }
    
        public static void main(String[] args) {
            try {
                //先获取到该类
                Class aClass = Class.forName("sml.reflect.TestReflect");
                //获取到测试方法
                Method genericityTest = aClass.getDeclaredMethod("GenericityTest", List.class,User.class);
                //获取到类型参数数组,就是获取方法所有的参数类型
                Type[] genericParameterTypes = genericityTest.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);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    上面演示的是获取方法参数,下面为获取返回类型

    //获取方法所有的参数类型
    Type[] genericParameterTypes = genericityTest.getGenericParameterTypes();
    //获取返回值的参数类型,返回值只有一个,所有不是数组
    Type genericReturnType = genericityTest.getGenericReturnType();
  9. 注解的操作

    public class AnnotationTest {
    
        @TestAnnotation("sml")
        public static void main(String[] args) {
    
            try {
                Class aClass = Class.forName("sml.annotation.AnnotationTest");
                Method main = aClass.getDeclaredMethod("main",String[].class);
                //根据我们的main方法获取main方法上的注解
                TestAnnotation declaredAnnotation = main.getDeclaredAnnotation(TestAnnotation.class);
                //获取到他的值
                String value = declaredAnnotation.value();
                System.out.println(value);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    如果我们需要获取类上的注解,只需要获取到类对象,然后.getDeclaredAnnotation()即可,其实不管是获取类上的注解还是字段上的注解都是一样的方法.

参考

文章1:https://blog.csdn.net/weixin_45056780/article/details/105127722

文章2:https://blog.csdn.net/zhangzhiyuan88/article/details/113404626