注解和反射

注解

一、元注解

​ 元注解的作用就是注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型做说明。

4个元注解分别为:

  • @Target:用于描述注解的使用范围

    • ElementType.TYPE 针对类、接口

    • ElementType.FIELD 针对成员变量

    • ElementType.METHOD 针对成员方法

    • ElementType.PARAMETER 针对方法参数

    • ElementType.CONSTRUCTOR 针对构造器

    • ElementType.PACKAGE 针对包

    • ElementType.ANNOTATION_TYPE 针对注解

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

    • RetentionPolicy.SOURCE 源代码级别,由编译器处理,处理之后就不再保留

    • RetentionPolicy.CLASS 注解信息保留到类对应的 class 文件中

    • RetentionPolicy.RUNTIME 由 JVM 读取,运行时使用

  • @Document:说明该注解将被包含在javadoc(文档)中

  • @Inherited:说明子类可以继承父类中的该注解

二、自定义注解

public class Test {
    //如果注解的参数没有默认值,则必须给该参数赋值
    @MyAnnotation(schools = {"北京大学","清华大学"})
    public void test(){
    }
    //如果注解只有一个参数且参数名为value,则value可以省略不写(参数名不为value时不允许省略)
    @MyAnnotation2("aa")
    public void test2(){

    }
}
//注解的写法
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
    //注解的参数:类型+参数名() [default 默认值];
    String name() default "";
    int age() default 0;
    int id() default -1;  //如果默认值为-1,代表不存在
    String[] schools();
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
    //如果注解只有一个参数,一般参数名取为value
    String[] value();  //不成文的一个规范
}

反射

一、反射概述

​ Java不是动态语言,但Java可以称为“准动态语言”。因为Java的反射机制可以让Java获得类似动态语言的特性,即运行时代码可以根据一些条件来改变自身的结构。

​ 反射机制允许程序在运行期间借助于Reflection API获取任何类的内部信息(比如类名,类的接口,类的方法,字段,属性…),并且能够直接操作任意对象的内部属性及方法。

​ 加载完类之后,在堆内存中产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

反射机制提供的功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时处理注解
  • 生成动态代理
  • ……

二、Class对象

​ 一个类在内存中只有一个Class对象,不可能有多个。

​ 一个类被被加载后,类的整个结构都会被封装在Class对象中。

​ Class对象只能由系统创建,我们只负责使用。我们要想使用反射机制,首先得要获得相应的Class对象。

1. 获得Class对象的方式

(1)如果已知具体的类,通过类的class属性获取,是最为安全可靠且性能最高的方法。

Class clz = 类名.class;

(2)如果已知某个类的对象,调用此对象的getClass()方法获取Class对象。

Class clz = 对象名.getClass();

​ 【注】getSuperClass()方法可以返回当前对象的父类Class对象。

(3)已知一个类的全限定类名且该类在类路径下,可以通过Class类的静态方法forName()获取,需要处理异常ClassNotFoundException。

Class clz = Class.forName("类的全限定类名")

(4)内置基本数据类型可以直接使用类名.TYPE,比如:

Class clz = Integer.TYPE; //基本内置类型的包装类都有一个 TYPE属性

(5)还可以用ClassLoader。

2. 通过Class对象获取运行时类的完整结构

(1)获取类的名字

  • clz.getName() 获取包名+类名
  • clz.getSimpleName 获取类名

(2)获取类的属性

  • clz.getFields() 获取本类public属性

  • clz.getDeclaredFields() 可以获取到本类所有属性,包括私有属性

  • clz.getDeclaredField("属性名") 获取指定的属性

(3)获取类的方法

  • clz.getMethods() 获取本类及其父类的所有public方法

  • clz.getDeclaredMethods() 获取本类的所有方法,包括私有方法,不能获取父类的方法

  • clz.getMethod("方法名", ...方法参数类型.class)

    clz.getDeclaredMethod("方法名", ...方法参数类型.class)

    获取指定方法,若方法无参,则传递null

(4)获取类的构造方法

  • clz.getConstructors() 获取本类public构造方法
  • clz.getDeclaredConstructors() 获取本类所有构造方法
  • clz.getDeclaredConstructor(...构造方法参数类型.class) 获取指定构造方法

3. 获取到类的结构信息后能做什么

(1)通过反射创建类的对象

​ 方式1:调用Class对象的newInstance()方法

​ 前提:

​ 必须要有一个无参构造器;

​ 类的构造器的访问权限要够。

`User user = (User) clz.newInstance(); //本质上调用了类的无参构造器`

​ 方式2:通过指定的构造器创建

​ 首先通过Class对象获取指定的构造器,然后调用Constructor对象的newInstance(...构造方法参数值)方法。

Constructor constructor = clz.getDeclaredConstructor(String.class, int.class);
User user = (User) constructor.newInstance("小明", 18);

(2)通过反射调用方法

User user = (User) clz.newInstance();
//通过反射获取一个方法
Method setName = clz.getMethod("setName", String.class);

//invoke: 激活的意思  (对象, "方法需要的参数")
setName.invoke(user,"小张");
System.out.println(user.getName());//输出:小张

Object invoke(Object obj, Object ... args)

  • invoke方法的返回值对应原方法的返回值,若原方法无返回值,则返回null。
  • 若原方法为静态方法,此时形参Object obj可为null。
  • 若原方法形参列表为空,则Object ... args为null。
  • 若原方法声明为private,则需要在调用此invoke()方法之前,显示调用Method对象的setAccessible(true)方法,这样才可以访问私有方法。

(3)通过反射操作属性

User user= (User) clz.newInstance();
Field name = clz.getDeclaredField("name");

//不能直接操作私有属性,我们需要关闭程序的安全检查,Field或Method对象调用setAccessible(true)方法
//setAccessible 默认为false,如果没有关闭将会报没有访问private的权限
//can not access a member of class com.javacto.reflection.User with modifiers "private"
name.setAccessible(true);
name.set(user,"小红"); //修改属性值
System.out.println(user.getName());//输出:小红

【注】使用反射机制会影响程序的运行效率。关闭安全检查在一定程度上能改善一些反射的效率问题。

三、通过反射获取泛型信息

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

​ 思考该怎么获取泛型信息呢? (之前有说过,类加载的时候就产生了Class对象,故class对象里面应该是有保留泛型信息的)

public class Test {

    /**
     * 通过泛型传参
     * @param map
     * @param list
     */
    public void test01(Map<String,User> map, List <User> list){
        System.out.println("test01");
    }

    /**
     * 通过泛型返回值
     * @return
     */
    public Map<String,User> test02(){
        System.out.println("test02");
        return null;
    }


    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test.class.getMethod("test01", Map.class, List.class);
        //获取参数类型  即Map 和 List
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("1:"+genericParameterType);

            //判断genericParameterType参数类型 是否属于 ParameterizedType 参数化类型
            if (genericParameterType instanceof ParameterizedType){
                //如果属于参数化类型,获得他的真实类型 getActualTypeArguments
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();

                //再次输出真实的泛型信息
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("2:"+actualTypeArgument);
                }
            }
        }
        
        //获得返回值类型
        method = Test.class.getMethod("test02",null);
        Type genericReturnType = method.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType){
            //如果genericReturnType返回值类型属于参数化类型,获得他的真实类型 getActualTypeArguments
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            
            //再次输出真实的泛型信息
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println("3:"+actualTypeArgument);
            }
        }
    }
}

四、通过反射获取注解信息

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class clz = Class.forName("com.javacto.reflection.Student");

        //通过反射获取注解
        Annotation[] annotations = clz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //获得注解的value的值   获取指定注解值
        MyTable myTable =(MyTable) clz.getAnnotation(MyTable.class);
        String value = myTable.value();
        System.out.println(value);

        //获得类指定的注解
        System.out.println("=====获得类指定的注解======");
        Field field= clz.getDeclaredField("name");
        MyField annotation = field.getAnnotation(MyField.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }

}

@MyTable("db_students")
class Student{
    @MyField(columnName = "db_id",type = "int",length = 10)
    private int id;
    @MyField(columnName = "db_age",type = "int",length = 10)
    private int age;
    @MyField(columnName = "db_name",type = "varchar",length = 50)
    private String name;

    public Student() {
    }

    public Student(int id, int age, String name) {
        this.id = id;
        this.age = age;
        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;
    }

    public String getName() {
        return name;
    }

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

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

//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable{
    String value();
}

//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
    String columnName(); //列名
    String type(); //类型
    int length(); //长度
}
posted @ 2021-07-26 20:26  Java程序员的进阶之路  阅读(104)  评论(0编辑  收藏  举报