反射(Reflect)

第16章 反射(Reflect)

学习目标

16.1 反射的概念

Java程序中,所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致。

例如:某些变量或形参的类型是Object类型,但是程序却需要调用该对象运行时类型的方法,该方法不是Object中的方法,那么如何解决呢?

为了解决这些问题,程序需要在运行时发现对象和类的真实信息,现在有两种方案:

方案1:在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接先使用instanceof运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可。

方案2:编译时根本无法预知该对象和类的真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。

因为加载完类之后,就产生了一个Class类型的对象,并将引用存储到方法区,那么每一个类在方法区内存都可以找到唯一Class对象与之对应,这个对象包含了完整的类的结构信息,我们可以通过这个对象获取类的结构。这种机制就像一面镜子,Class对象像是类在镜子中的镜像,通过观察这个镜像就可以知道类的结构,所以,把这种机制形象地称为反射机制。

非反射:类(原物)-->类信息

反射:Class对象(镜像)-->类(原物)

1666144034216

16.2 java.lang.Class类

要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API(1)java.lang.Class(2)java.lang.reflect.*。所以,Class对象是反射的根源。

16.2.1 哪些类型可以获取Class对象

哪些类型可以获取Class对象?所有Java类型

//(1)基本数据类型和void
例如:int.class
	 void.class
//(2)类和接口
例如:String.class
	Comparable.class
//(3)枚举
例如:ElementType.class
//(4)注解
例如:Override.class
//(5)数组
例如:int[].class

示例代码:

package com.atguigu.classtype;

import java.lang.annotation.ElementType;

public class JavaType {
    public static void main(String[] args) {
        //(1)基本数据类型和void
        Class c1 = int.class;
        Class c2 = void.class;

        System.out.println("c1 = " + c1);
        System.out.println("c2 = " + c2);
        //(2)类和接口
        Class c3 = String.class;
        Class c4 = Comparable.class;
        System.out.println("c3 = " + c3);
        System.out.println("c4 = " + c4);

        //(3)枚举
        Class c5 = ElementType.class;
        System.out.println("c5 = " + c5);

        //(4)注解
        Class c6 = Override.class;
        System.out.println("c6 = " + c6);

        //(5)数组
        Class c7 = int[].class;
        Class c9 = String[].class;
        Class c8 = int[][].class;
        System.out.println("c7 = " + c7);
        System.out.println("c8 = " + c8);
        System.out.println("c9 = " + c9);
    }
}

16.2.2 获取Class对象的四种方式

(1)类型名.class

要求编译期间已知类型

(2)对象.getClass()

获取对象的运行时类型

(3)Class.forName(类型全名称)

可以获取编译期间未知的类型

(4)ClassLoader的类加载器对象.loadClass(类型全名称)

可以用系统类加载对象或自定义加载器对象加载指定路径下的类型

package com.atguigu.classtype;

import org.junit.Test;

public class GetClassObject {
    @Test
    public void test01() throws ClassNotFoundException{
        Class c1 = GetClassObject.class;
        GetClassObject obj = new GetClassObject();
        Class c2 = obj.getClass();
        Class c3 = Class.forName("com.atguigu.classtype.GetClassObject");
        Class c4 = ClassLoader.getSystemClassLoader().loadClass("com.atguigu.classtype.GetClassObject");

        System.out.println("c1 = " + c1);
        System.out.println("c2 = " + c2);
        System.out.println("c3 = " + c3);
        System.out.println("c4 = " + c4);

        System.out.println(c1 == c2);
        System.out.println(c1 == c3);
        System.out.println(c1 == c4);
    }

    @Test
    public void test02(){
        int[] arr1 = {1,2,3,4,5};
        int[] arr2 = {10,34,5,66,34,22};
        int[][] arr3 = {{1,2,3,4},{4,5,6,7}};
        String[] arr4 = {"hello","world"};

        Class c1 = arr1.getClass();
        Class c2 = arr2.getClass();
        Class c3 = arr3.getClass();
        Class c4 = arr4.getClass();

        System.out.println("c1 = " + c1);
        System.out.println("c2 = " + c2);
        System.out.println("c3 = " + c3);
        System.out.println("c4 = " + c4);
        System.out.println(c1 == c2);
        System.out.println(c1 == c3);
        System.out.println(c1 == c4);
        System.out.println(c3 == c4);
    }
}

16.3 类加载

类在内存中完整的生命周期:加载-->使用-->卸载

16.3.1 类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载。

类的加载又分为三个阶段:

(1)加载:load

就是指将类型的class字节码数据读入内存

(2)连接:link

①验证:校验合法性等

②准备:准备对应的内存(方法区),创建Class对象,为类变量赋默认值,为静态常量赋初始值。

③解析:把字节码中的符号引用替换为对应的直接地址引用

(3)初始化:initialize(类初始化)即执行类初始化方法,大多数情况下,类的加载就完成了类的初始化,有些情况下,会延迟类的初始化。

1666144047926

16.3.2 类初始化

1、哪些操作会导致类的初始化?

(1)运行主方法所在的类,要先完成类初始化,再执行main方法

(2)第一次使用某个类型就是在new它的对象,此时这个类没有初始化的话,先完成类初始化再做实例初始化

(3)调用某个类的静态成员(类变量和类方法),此时这个类没有初始化的话,先完成类初始化

(4)子类初始化时,发现它的父类还没有初始化的话,那么先初始化父类

(5)通过反射操作某个类时,如果这个类没有初始化,也会导致该类先初始化

类初始化执行的是(),该方法由(1)类变量的显式赋值代码(2)静态代码块中的代码构成

package com.atguigu.init;

class Father{
    static{
        System.out.println("main方法所在的类的父类(1)");//初始化子类时,会初始化父类
    }
}

public class ClassInitialize extends Father{
    static{
        System.out.println("main方法所在的类(2)");//主方法所在的类会初始化
    }

    public static void main(String[] args) throws ClassNotFoundException {
        new A();//第一次使用A就是创建它的对象,会初始化A类

        B.test();//直接使用B类的静态成员会初始化B类

        Class clazz = Class.forName("com.atguigu.test02.C");//通过反射操作C类,会初始化C类
    }
}
class A{
    static{
        System.out.println("A类初始化");
    }
}
class B{
    static{
        System.out.println("B类初始化");
    }
    public static void test(){
        System.out.println("B类的静态方法");
    }
}
class C{
    static{
        System.out.println("C类初始化");
    }
}

2、哪些使用类的操作,但是不会导致类的初始化?

(1)使用某个类的静态的常量(static final)

(2)通过子类调用父类的静态变量,静态方法,只会导致父类初始化,不会导致子类初始化,即只有声明静态成员的类才会初始化

(3)用某个类型声明数组并创建数组对象时,不会导致这个类初始化

public class TestClinit2 {
	public static void main(String[] args) {
		System.out.println(D.NUM);//D类不会初始化,因为NUM是final的
		
		System.out.println(F.num);
		F.test();//F类不会初始化,E类会初始化,因为num和test()是在E类中声明的
		
		//G类不会初始化,此时还没有正式用的G类
		G[] arr = new G[5];//没有创建G的对象,创建的是准备用来装G对象的数组对象
        //G[]是一种新的类型,是数组类想,动态编译生成的一种新的类型
        //G[].class
	}
}
class D{
	public static final int NUM = 10;
	static{
		System.out.println("D类的初始化");
	}
}
class E{
	static int num = 10;
	static{
		System.out.println("E父类的初始化");
	}
	public static void test(){
		System.out.println("父类的静态方法");
	}
}
class F extends E{
	static{
		System.out.println("F子类的初始化");
	}
}

class G{
	static{
		System.out.println("G类的初始化");
	}
}

16.3.3 类加载器

很多开发人员都遇到过java.lang.ClassNotFoundException或java.lang.NoClassDefError,想要更好的解决这类问题,或者在一些特殊的应用场景,比如需要支持类的动态加载或需要对编译后的字节码文件进行加密解密操作,那么需要你自定义类加载器,因此了解类加载器及其类加载机制也就成了每一个Java开发人员的必备技能之一。

1、类加载器分为:

(1)引导类加载器(Bootstrap Classloader)又称为根类加载器

它负责加载jre/rt.jar核心库
它本身不是Java代码实现的,也不是ClassLoader的子类,获取它的对象时往往返回null

(2)扩展类加载器(Extension ClassLoader)

它负责加载jre/lib/ext扩展库
它是ClassLoader的子类

(3)应用程序类加载器(Application Classloader)

它负责加载项目的classpath路径下的类
它是ClassLoader的子类

(4)自定义类加载器

当你的程序需要加载“特定”目录下的类,可以自定义类加载器;
当你的程序的字节码文件需要加密时,那么往往会提供一个自定义类加载器对其进行解码
后面会见到的自定义类加载器:tomcat中

2、Java系统类加载器的双亲委托模式

简单描述:

下一级的类加载器,如果接到任务时,会先搜索是否加载过,如果没有,会先把任务往上传,如果都没有加载过,一直到根加载器,如果根加载器在它负责的路径下没有找到,会往回传,如果一路回传到最后一级都没有找到,那么会报ClassNotFoundException或NoClassDefError,如果在某一级找到了,就直接返回Class对象。

应用程序类加载器 把 扩展类加载器视为父加载器,

扩展类加载器 把 引导类加载器视为父加载器。

不是继承关系,是组合的方式实现的。

16.3.4 查看某个类的类加载器对象

(1)获取默认的系统类加载器

ClassLoader ClassLoader.getSystemClassLoader()

(2)查看某个类是哪个类加载器加载的

ClassLoader Class对象.getClassLoader()
//如果是根加载器加载的类,则会得到null

(3)获取某个类加载器的父加载器

ClassLoader ClassLoader对象.getParent()

示例代码:

package com.atguigu.loader;

import org.junit.Test;

public class TestClassLoader {
    @Test
    public void test01(){
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("systemClassLoader = " + systemClassLoader);
    }

    @Test
    public void test02()throws Exception{
        ClassLoader c1 = String.class.getClassLoader();
        System.out.println("加载String类的类加载器:" + c1);

        ClassLoader c2 = Class.forName("sun.util.resources.cldr.zh.TimeZoneNames_zh").getClassLoader();
        System.out.println("加载sun.util.resources.cldr.zh.TimeZoneNames_zh类的类加载器:" + c2);

        ClassLoader c3 = TestClassLoader.class.getClassLoader();
        System.out.println("加载当前类的类加载器:" + c3);
    }

    @Test
    public void test03(){
        ClassLoader c1 = TestClassLoader.class.getClassLoader();
        System.out.println("加载当前类的类加载器c1=" + c1);

        ClassLoader c2 = c1.getParent();
        System.out.println("c1.parent = " + c2);

        ClassLoader c3 = c2.getParent();
        System.out.println("c2.parent = " + c3);

    }
}

16.3.5 演示导出jar包至jre/lib/ext

演示导出jar包放到“D:\ProgramFiles\Java\jdk1.8.0_271\jre\lib\ext”目录。具体操作步骤请看《尚硅谷-第18章-IDE开发工具Idea使用》第18.13。

新建模块,包含如下类型:

1666144060508

package com.atguigu.ext.demo;

public class AtGuiguClass {
    public static void printInfo(String info){
        System.out.println("AtGuiguClass.method");
        System.out.println("info = " + info);
    }

    public int doubleNum(int num){
        return 2 * num;
    }

}

package com.atguigu.ext.demo;

public class AtGuiguDemo {
    private static String info;
    private String title;
    private int num;

    public AtGuiguDemo(String title, int num) {
        this.title = title;
        this.num = num;
    }

    public static String getInfo() {
        return info;
    }

    public static void setInfo(String info) {
        AtGuiguDemo.info = info;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "AtGuiguDemo{" +
                "title='" + title + '\'' +
                ", num=" + num +
                '}';
    }
}


1666144078043

16.4 反射的基本应用

16.4.1 获取类型的详细信息

可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)

示例代码获取常规信息:

package com.atguigu.reflect;

package com.atguigu.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class TestClassInfo {
    public static void main(String[] args) throws Exception {
        //1、先得到某个类型的Class对象
        Class clazz = String.class;
        //比喻clazz好比是镜子中的影子

        //2、获取类信息
        //(1)获取包对象,即所有java的包,都是Package的对象
        Package pkg = clazz.getPackage();
        System.out.println("包名:" + pkg.getName());

        //(2)获取修饰符
        //其实修饰符是Modifier,里面有很多常量值
        /*
         * 0x是十六进制
         * PUBLIC           = 0x00000001;  1    1
         * PRIVATE          = 0x00000002;  2	10
         * PROTECTED        = 0x00000004;  4	100
         * STATIC           = 0x00000008;  8	1000
         * FINAL            = 0x00000010;  16	10000
         * ...
         *
         * 设计的理念,就是用二进制的某一位是1,来代表一种修饰符,整个二进制中只有一位是1,其余都是0
         *
         * mod = 17          0x00000011
         * if ((mod & PUBLIC) != 0)  说明修饰符中有public
         * if ((mod & FINAL) != 0)   说明修饰符中有final
         */
        int mod = clazz.getModifiers();
        System.out.println("类的修饰符有:" + Modifier.toString(mod));

        //(3)类型名
        String name = clazz.getName();
        System.out.println("类名:" + name);

        //(4)父类,父类也有父类对应的Class对象
        Class superclass = clazz.getSuperclass();
        System.out.println("父类:" + superclass);

        //(5)父接口们
        System.out.println("父接口们:");
        Class[] interfaces = clazz.getInterfaces();
        for (Class iter : interfaces) {
            System.out.println(iter);
        }

        //(6)类的属性,  你声明的一个属性,它是Field的对象
/*		Field clazz.getField(name)  根据属性名获取一个属性对象,但是只能得到公共的
		Field[] clazz.getFields();  获取所有公共的属性
		Field clazz.getDeclaredField(name)  根据属性名获取一个属性对象,可以获取已声明的
		Field[] clazz.getDeclaredFields()	获取所有已声明的属性
		*/
//        Field valueField = clazz.getDeclaredField("value");
//		System.out.println("valueField = " +valueField);

        System.out.println("------------------------------");
        System.out.println("成员如下:");
        System.out.println("属性有:");
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            //修饰符、数据类型、属性名
            int modifiers = field.getModifiers();
            System.out.println("属性的修饰符:" + Modifier.toString(modifiers));

            String name2 = field.getName();
            System.out.println("属性名:" + name2);

            Class<?> type = field.getType();
            System.out.println("属性的数据类型:" + type);
        }
        System.out.println("-------------------------");
        //(7)构造器们
        System.out.println("构造器列表:");
        Constructor[] constructors = clazz.getDeclaredConstructors();
        for (int i=0; i<constructors.length; i++) {
            Constructor constructor = constructors[i];
            System.out.println("第" + (i+1) +"个构造器:");
            //修饰符、构造器名称、构造器形参列表  、抛出异常列表
            int modifiers = constructor.getModifiers();
            System.out.println("构造器的修饰符:" + Modifier.toString(modifiers));

            String name2 = constructor.getName();
            System.out.println("构造器名:" + name2);

            //形参列表
            System.out.println("形参列表:");
            Class[] parameterTypes = constructor.getParameterTypes();
            for (Class parameterType : parameterTypes) {
                System.out.println(parameterType);
            }

            //异常列表
            System.out.println("异常列表:");
            Class<?>[] exceptionTypes = constructor.getExceptionTypes();
            for (Class<?> exceptionType : exceptionTypes) {
                System.out.println(exceptionType);
            }
            System.out.println();
        }
        System.out.println("---------------------------------");
        //(8)方法们
        System.out.println("方法列表:");
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (int i=0; i<declaredMethods.length; i++) {
            Method method = declaredMethods[i];
            System.out.println("第" + (i+1) +"个方法:");
            //修饰符、返回值类型、方法名、形参列表 、异常列表
            int modifiers = method.getModifiers();
            System.out.println("方法的修饰符:" + Modifier.toString(modifiers));

            Class<?> returnType = method.getReturnType();
            System.out.println("返回值类型:" + returnType);

            String name2 = method.getName();
            System.out.println("方法名:" + name2);

            //形参列表
            System.out.println("形参列表:");
            Class[] parameterTypes = method.getParameterTypes();
            for (Class parameterType : parameterTypes) {
                System.out.println(parameterType);
            }

            //异常列表
            System.out.println("异常列表:");
            Class<?>[] exceptionTypes = method.getExceptionTypes();
            for (Class<?> exceptionType : exceptionTypes) {
                System.out.println(exceptionType);
            }
            System.out.println();
        }

    }
}


16.4.2 创建任意引用类型的对象

两种方式:

1、直接通过Class对象来实例化(要求必须有公共的无参构造)

2、通过获取构造器对象来进行实例化

方式一的步骤:

(1)获取该类型的Class对象(2)创建对象

方式二的步骤:

(1)获取该类型的Class对象(2)获取构造器对象(3)创建对象

如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

示例代码:

package com.atguigu.reflect;

import org.junit.Test;

import java.lang.reflect.Constructor;

public class TestCreateObject {
    @Test
    public void test1() throws Exception{
//        AtGuiguClass obj = new AtGuiguClass();//编译期间无法创建

        Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguClass");
        //clazz代表com.atguigu.ext.demo.AtGuiguClass类型
        //clazz.newInstance()创建的就是AtGuiguClass的对象
        Object obj = clazz.newInstance();
        System.out.println(obj);
    }

    @Test
    public void test2()throws Exception{
        Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguDemo");
        //java.lang.InstantiationException: com.atguigu.ext.demo.AtGuiguDemo
        //Caused by: java.lang.NoSuchMethodException: com.atguigu.ext.demo.AtGuiguDemo.<init>()
        //即说明AtGuiguDemo没有无参构造,就没有无参实例初始化方法<init>
        Object stu = clazz.newInstance();
        System.out.println(stu);
    }

    @Test
    public void test3()throws Exception{
        //(1)获取Class对象
        Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguDemo");
        /*
         * 获取AtGuiguDemo类型中的有参构造
         * 如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的
         * 例如:public AtGuiguDemo(String title, int num)
         */
        //(2)获取构造器对象
        Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,int.class);

        //(3)创建实例对象
        // T newInstance(Object... initargs)  这个Object...是在创建对象时,给有参构造的实参列表
        Object obj = constructor.newInstance("尚硅谷",2022);
        System.out.println(obj);
    }
}

16.4.3 操作任意类型的属性

(1)获取该类型的Class对象

Class clazz = Class.forName("包.类名");

(2)获取属性对象

Field field = clazz.getDeclaredField("属性名");

(3)如果属性的权限修饰符不是public,那么需要设置属性可访问

field.setAccessible(true);

(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象

Object obj = clazz.newInstance(); //有公共的无参构造

Object obj = 构造器对象.newInstance(实参...);//通过特定构造器对象创建实例对象

(4)设置属性值

field.set(obj,"属性值");

如果操作静态变量,那么实例对象可以省略,用null表示

(5)获取属性值

Object value = field.get(obj);

如果操作静态变量,那么实例对象可以省略,用null表示

示例代码:

package com.atguigu.reflect;

public class Student {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

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


package com.atguigu.reflect;

import java.lang.reflect.Field;

public class TestField {
    public static void main(String[] args)throws Exception {
        //1、获取Student的Class对象
        Class clazz = Class.forName("com.atguigu.reflect.Student");

        //2、获取属性对象,例如:id属性
        Field idField = clazz.getDeclaredField("id");

        //3、如果id是私有的等在当前类中不可访问access的,我们需要做如下操作
        idField.setAccessible(true);

        //4、创建实例对象,即,创建Student对象
        Object stu = clazz.newInstance();

        //5、获取属性值
        /*
         * 以前:int 变量= 学生对象.getId()
         * 现在:Object id属性对象.get(学生对象)
         */
        Object value = idField.get(stu);
        System.out.println("id = "+ value);

        //6、设置属性值
        /*
         * 以前:学生对象.setId(值)
         * 现在:id属性对象.set(学生对象,值)
         */
        idField.set(stu, 2);

        value = idField.get(stu);
        System.out.println("id = "+ value);
    }
}

16.4.4 调用任意类型的方法

(1)获取该类型的Class对象

Class clazz = Class.forName("包.类名");

(2)获取方法对象

Method method = clazz.getDeclaredMethod("方法名",方法的形参类型列表);

(3)创建实例对象

Object obj = clazz.newInstance();

(4)调用方法

Object result = method.invoke(obj, 方法的实参值列表);

如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

如果方法是静态方法,实例对象也可以省略,用null代替

示例代码:

package com.atguigu.reflect;

import org.junit.Test;

import java.lang.reflect.Method;

public class TestMethod {
    @Test
    public void test()throws Exception {
        // 1、获取Student的Class对象
        Class<?> clazz = Class.forName("com.atguigu.reflect.Student");

        //2、获取方法对象
        /*
         * 在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载
         *
         * 例如:void setName(String name)
         */
        Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);

        //3、创建实例对象
        Object stu = clazz.newInstance();

        //4、调用方法
        /*
         * 以前:学生对象.setName(值)
         * 现在:方法对象.invoke(学生对象,值)
         */
        Object setNameMethodReturnValue = setNameMethod.invoke(stu, "张三");

        System.out.println("stu = " + stu);
        //setName方法返回值类型void,没有返回值,所以setNameMethodReturnValue为null
        System.out.println("setNameMethodReturnValue = " + setNameMethodReturnValue);

        Method getNameMethod = clazz.getDeclaredMethod("getName");
        Object getNameMethodReturnValue = getNameMethod.invoke(stu);
        //getName方法返回值类型String,有返回值,getNameMethod.invoke的返回值就是getName方法的返回值
        System.out.println("getNameMethodReturnValue = " + getNameMethodReturnValue);//张三
    }

    @Test
    public void test02()throws Exception{
        Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguClass");
        Method printInfoMethod = clazz.getMethod("printInfo", String.class);
        //printInfo方法是静态方法
        printInfoMethod.invoke(null,"尚硅谷");
    }
}


16.5 自定义注解

注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如:

@SuppressWarnings(value=”unchecked”)
@Override
@Deprecated

注解Annotation是从JDK5.0开始引入。

虽然说注解也是一种注释,因为它们都不会改变程序原有的逻辑,只是对程序增加了某些注释性信息。不过它又不同于单行注释和多行注释,对于单行注释和多行注释是给程序员看的,而注解是可以被编译器或其他程序读取的一种注释,程序还可以根据注解的不同,做出相应的处理。所以注解是插入到代码中以便有工具可以对它们进行处理的标签。

一个完整的注解应该包含三个部分:
(1)声明
(2)使用
(3)读取

16.5.1 元注解

JDK1.5在java.lang.annotation包定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。

(1)@Target:用于描述注解的使用范围

  • 可以通过枚举类型ElementType的10个常量对象来指定
  • TYPE,METHOD,CONSTRUCTOR,PACKAGE.....

(2)@Retention:用于描述注解的生命周期

  • 可以通过枚举类型RetentionPolicy的3个常量对象来指定
  • SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时)
  • 唯有RUNTIME阶段才能被反射读取到

(3)@Documented:表明这个注解应该被 javadoc工具记录。

(4)@Inherited:允许子类继承父类中的注解

示例代码:

package java.lang;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

16.5.2 自定义注解

1、声明自定义注解

【元注解】
【修饰符】 @interface 注解名{
    【成员列表】
}

  • 自定义注解可以通过四个元注解@Retention,@Target,@Inherited,@Documented,分别说明它的声明周期,使用位置,是否被继承,是否被生成到API文档中。
  • Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组
  • 可以使用 default 关键字为抽象方法指定默认返回值
  • 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式是“方法名 = 返回值”,如果只有一个抽象方法需要赋值,且方法名为value,可以省略“value=”,所以如果注解只有一个抽象方法成员,建议使用方法名value。
package com.atguigu.annotation;

import java.lang.annotation.*;

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

package com.atguigu.annotation;

import java.lang.annotation.*;

@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String columnName();
    String columnType();
}

2、使用自定义注解

package com.atguigu.annotation;

@Table("t_stu")
public class Student {
    @Column(columnName = "sid",columnType = "int")
    private int id;
    @Column(columnName = "sname",columnType = "varchar(20)")
    private String name;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

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


3、读取和处理自定义注解

自定义注解必须配上注解的信息处理流程才有意义。

我们自己定义的注解,只能使用反射的代码读取。所以自定义注解的声明周期必须是RetentionPolicy.RUNTIME。

package com.atguigu.annotation;

import java.lang.reflect.Field;

public class TestAnnotation {
    public static void main(String[] args) {
        Class studentClass = Student.class;
        Table tableAnnotation = (Table) studentClass.getAnnotation(Table.class);
        String tableName = "";
        if(tableAnnotation != null){
            tableName = tableAnnotation.value();
        }

        Field[] declaredFields = studentClass.getDeclaredFields();
        String[] columns = new String[declaredFields.length];
        int index = 0;
        for (Field declaredField : declaredFields) {
            Column column = declaredField.getAnnotation(Column.class);
            if(column!= null) {
                columns[index++] = column.columnName();
            }
        }
        
        String sql = "select ";
        for (int i=0; i<index; i++) {
            sql += columns[i];
            if(i<index-1){
                sql += ",";
            }
        }
        sql += " from " + tableName;
        System.out.println("sql = " + sql);
    }
}


16.6 反射的其他应用(选讲)

16.6.1 获取泛型父类信息(可选)

示例代码获取泛型父类信息:

/* Type:
 * (1)Class
 * (2)ParameterizedType   
 * 		例如:Father<String,Integer>
 * 			ArrayList<String>
 * (3)TypeVariable
 * 		例如:T,U,E,K,V
 * (4)WildcardType
 * 		例如:
 * 		ArrayList<?>
 * 		ArrayList<? super 下限>
 * 		ArrayList<? extends 上限>
 * (5)GenericArrayType
 * 		例如:T[]
 * 	
 */
public class TestGeneric {
	public static void main(String[] args) {
		//需求:在运行时,获取Son类型的泛型父类的泛型实参<String,Integer>
		
		//(1)还是先获取Class对象
		Class clazz = Son.class;//四种形式任意一种都可以
		
		//(2)获取泛型父类
//		Class sc = clazz.getSuperclass();
//		System.out.println(sc);
		/*
		 * getSuperclass()只能得到父类名,无法得到父类的泛型实参列表
		 */
		Type type = clazz.getGenericSuperclass();
		
		// Father<String,Integer>属于ParameterizedType
		ParameterizedType pt = (ParameterizedType) type;
		
		//(3)获取泛型父类的泛型实参列表
		Type[] typeArray = pt.getActualTypeArguments();
		for (Type type2 : typeArray) {
			System.out.println(type2);
		}
	}
}
//泛型形参:<T,U>
class Father<T,U>{
	
}
//泛型实参:<String,Integer>
class Son extends Father<String,Integer>{
	
}

16.6.2 动态创建和操作任意类型的数组(可选)

在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过使用Array类来动态的创建数组,操作数组元素等。

Array类提供了如下几个方法:

public static Object newInstance(Class<?> componentType, int... dimensions):创建一个具有指定的组件类型和维度的新数组。

public static void setXxx(Object array,int index,xxx value):将array数组中[index]元素的值修改为value。此处的Xxx对应8种基本数据类型,如果该属性的类型是引用数据类型,则直接使用set(Object array,int index, Object value)方法。

public static xxx getXxx(Object array,int index,xxx value):将array数组中[index]元素的值返回。此处的Xxx对应8种基本数据类型,如果该属性的类型是引用数据类型,则直接使用get(Object array,int index)方法。

public class TestArray {
    @Test
    public void test1(){
        //演示:创建一个元素类型是String类型数组
        String[] arr = new String[5];
    }
    @Test
    public void test2(){
        //演示:创建一个元素类型是String类型数组
        //第一步:确定数组的元素类型,并且获取它的Class对象
        Class clazz = String.class;
        //第二步:确定长度
        int length = 5;
        //第三步:创建数组
        Object arr = Array.newInstance(clazz, 5);
        //arr是一个数组,是String[],长度为5
        System.out.println("数组类型:" + arr.getClass());
        Array.set(arr, 0, "尚硅谷");
        Array.set(arr, 1, "佟刚");
        System.out.println(Array.get(arr, 0));
        System.out.println(Array.get(arr, 1));
    }

    @Test
    public void test3(){
        Sub sub = new Sub();
        sub.add("hello");
        sub.add("world");
        sub.add("java");

        sub.iterate();
        String s = sub.get(0);
        System.out.println("s = " + s);
    }

}

abstract class Father<T>{
    private T[] arr;
    private int size;

    public Father() {
        //在这个构造器中,为arr数组初始化
//        arr = new T[5];//不能直接new
        //(1)第一步确定T的类型
        Class<?> aClass = this.getClass();//this是谁的对象,是Father子类的对象,例如:Sub,那么这里aClass就是代表子类类型的Class
                                            //创建子类对象时,在子类的构造器首行一定会调用super(),执行该无参构造的代码
                                        //this在构造器中,表示的是正在new的对象

        //aClass代表的是Sub类型,相当于Sub.class,你也可以理解为:class Sub extends Father<String>

        Type genericSuperclass = aClass.getGenericSuperclass();
        //aClass.getGenericSuperclass() ==> Father<String>
        /*
        Type是所有形式的类型的超级父接口:
        (1)GenericArrayType;代表T[]类型
        (2)ParameterizedType:代表 Father<String>,ArrayList<String>
                    参数化类型,参数具体化的类型
                    Father<T>,<String>是给<T>传参数,
         (3)TypeVariable:代表T,U,K,V
                    类型变量,不确定的类型
          (4)WildcardType:代表  ArrayList<?>等<>中有?通配符的类型
          (5)Class   :代表是Father,不包含的泛型部分
         */
//        Class<?> superclass = aClass.getSuperclass();//Father,不带泛型

        ParameterizedType pt = (ParameterizedType) genericSuperclass;
        //这里强制的目的是为了调用getActualTypeArguments(),因为Type太笼统了,无法调用这个方法

        Type[] actualTypeArguments = pt.getActualTypeArguments();
        //<String>,因为我们可能其他地方有多个泛型,这里只有一个

        Class tType = (Class) actualTypeArguments[0];
        //tType ==> String.class ==>  T的类型
        /*
        不是所有情况都可以强制为Class,这里能强制为Class,是因为我这里Father<String>,这个String是用Class对象表示
        如果Father<ArrayList<String>>这种形式,就不能强制为Class,应该强制为ParameterizedType
         */

        //(2)第二步确定长度
        int length = 5;
        arr = (T[])Array.newInstance(tType,length); //arr是T[]类型的数组,即这里是String[]的数组
    }

    public void add(T t){
        if(size >= arr.length){//数组满了,就不添加了
            return;
        }
        arr[size++] = t;
    }

    public void iterate(){
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

    public T get(int index){
        return arr[index];
    }
}
class Sub extends Father<String>{

}

16.6.3 获取内部类或外部类信息(可选)

public Class<?>[] getClasses():返回所有公共内部类和内部接口。包括从超类继承的公共类和接口成员以及该类声明的公共类和接口成员。

public Class<?>[] getDeclaredClasses():返回 Class 对象的一个数组,这些对象反映声明为此 Class 对象所表示的类的成员的所有类和接口。包括该类所声明的公共、保护、默认(包)访问及私有类和接口,但不包括继承的类和接口。

public Class<?> getDeclaringClass():如果此 Class 对象所表示的类或接口是一个内部类或内部接口,则返回它的外部类或外部接口,否则返回null。

Class<?> getEnclosingClass() :返回某个内部类的外部类

	@Test
	public void test5(){
		Class<?> clazz = Map.class;
		Class<?>[] inners = clazz.getDeclaredClasses();
		for (Class<?> inner : inners) {
			System.out.println(inner);
		}
		
		Class<?> ec = Map.Entry.class;
		Class<?> outer = ec.getDeclaringClass();
		System.out.println(outer);
	}

posted @ 2022-10-19 09:51  傻孩子不吃辣  阅读(180)  评论(0)    收藏  举报