反射及其API

一、反射的概念

1、什么是反射?为什么要用反射?
咱们之前写代码时,先写类,然后用类创建对象,通过类名或对象名调用方法等成员。
    ==> 在编译期间就要确定这个类的名字,成员等。

比如:

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

        public Student(){
        }
        public Student(int id, String name){
            this.id = id;
            this.name = name;
        }
        public void setId(int id){
            this.id = id;
        }
        public int getId(){
             return id;
        }
        //...
    }

    public class TestStudent{
        public static void main(String[] args){
            Student stu1  = new Student();
            Student stu2  = new Student(1,"张三");
            System.out.println(stu1.getId());
        }
    }

现在我们在开发过程中,会遇到这样的情况,编译期间,这个类型未知 或者 编译期间不存在,
但是我们要先把 new对象,调用方法等代码先完成。需要在运行时,才能确定类型等。


类 --> 类的信息
类的Class对象 --> 类的信息

类的Class对象是类的镜像,把这个过程比喻反射。

2、反射的根源:Class类型的对象

public class TestReflect {
    public static void main(String[] args) {
        //先写创建对象的代码
        Properties pro  = new Properties();//Map系列,有key,value,key,value都是String类型
        try {
            pro.load(TestReflect.class.getClassLoader().getResourceAsStream("info.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            Class<?> clazz = Class.forName(pro.getProperty("className"));//"className"是info.properties文件中的某个key
            //pro.getProperty("className")得到但是info.properties文件中key为className的value值,即className=右边的值
        
            //new对象
            Object obj = clazz.newInstance();
            System.out.println(obj);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }
}

3.properties文件到底放哪里?
(1)放在src的根目录下

(2)放到某个包中

(3)src外面,模块的根目录下

public class TestLoaderProperties {
    public static void main(String[] args)throws Exception {
        Properties pro = new Properties();
        ClassLoader classLoader = TestLoaderProperties.class.getClassLoader();
        pro.load(classLoader.getResourceAsStream("info.properties"));//src根目录下
        System.out.println(pro);

        System.out.println("---------------------------");
        Properties pro2 = new Properties();
        ClassLoader classLoader2 = TestLoaderProperties.class.getClassLoader();
        pro2.load(classLoader2.getResourceAsStream("com/atguigu/reflect/pkg.properties"));//com.atguigu.reflect包下
        System.out.println(pro2);

        System.out.println("----------------------");
        Properties pro3 = new Properties();
        //如果是main方法中运行,那么就要加模块名,main的相对路径是相对于project
        pro3.load(new FileInputStream("day0831_teacher_code/out.properties"));//src外,模块根目录下
        System.out.println(pro3);
    }

    @Test
    public void test() throws Exception {
        //JUnit的相对路径是相对于模块
        Properties pro3 = new Properties();
        pro3.load(new FileInputStream("out.properties"));//src外,模块根目录下
        System.out.println(pro3);
    }
}

二、类加载的过程

3、Class对象怎么来的?
Java程序运行时,会把需要的类的.class文件等类信息先“加载”到内存中,
每一个Java的类型被“加载”后,会用一个Class对象来存储和表示。

4、类的加载,分为三个阶段:(了解)
(1)加载(load):先把.class的文件的数据读取到内存中
(2)连接(link)
A:检验合法性
    比如检验字节码文件的格式,包括版本等
    字节码文件的必须以cafebabe
B:准备相应的内存
    准备new Class对象,
    类的静态变量数据存储内存(方法区)  此时是默认值,还未初始化
    类的静态的常量,此时会直接初始化。  public static final int MAX_VALUE = 100;
C:解析
    把字节码文件中的符号引用,替换为地址引用
    例如:Student类
        public class Student {
            private int id;
            private String name;
        。。。。
    }
    比如:String,int都是符号引用,
    String和int也是Java的数据类型,在内存中也有他们的Class对象,此时要把String和int换成这两个类型在内存中Class对象的首地址。

    为什么要换成地址引用?
    这样的话,在运行时,不用现找了,直接根据地址取相应的信息。

    经过前两步的话,Class对象有了,但是这个类静态变量还未初始化。
(3)初始化
    运行这个类<clinit>方法

    经过前3步,Class对象有了,静态变量也初始化了。


5、类的加载是谁来完成的?
类加载由类加载器ClassLoader来完成。底层有专门的线程来负责类的加载。

类加载器又分为四种:
(1)引导类加载器:负责核心类库中的类,rt.jar下类
    说明:引导类加载器是C语言写的,所以Java层面得不到它的对象
(2)扩展类加载器(ExtClassLoader):负责JRE下lib目录下ext扩展目录中的jar
(3)应用程序类加载器(AppClassLoader):负责程序员自己写的类

(4)自定义类加载器:也是加载程序员自己写的类等,加载一些特殊的类,特殊目录的类
   
比如:字节码如果需要加密,那么在运行时,必须用对应的类加载器先解密才能正常加载。
     比如:字节码文件不是放在普通类路径下,例如IDEA的类路径是out。
           tomcat服务器等的类路径就比较特殊,那么就不能用普通的类加载器加载,就需要自定义类加载器加载,tomcat写了自定义类加载器。

6、如何获取类加载器?
  类的Class对象可以调用getClassLoader()方法得到类加载器对象。


7、类加载的协作模式:双亲委托模式,这样的模式设计是为了安全。
  双亲:父母双亲

双亲:
应用程序类加载器会把扩展类加载器视为双亲,但是不是继承。在应用程序类加载器中有一个parent(父母)的成员变量,来记录扩展类加载器。
扩展类加载器会把引导类加载器视为双亲,但是不是继承。在扩展类加载器中有一个parent(父母)的成员变量,来记录引导类加载器。

过程描述:
(1)当我们的应用程序类加载器接到加载任务时,先看一下内存中是否已经加载过这个类,如果加载过,就不重复加载了,直接返回Class对象。
如果没有加载过,先把任务交给“双亲”,即扩展类加载器。
例如:应用程序类加载器接到加载“java.lang.String”的任务,如果这个类没加载过,把任务给扩展类加载器
例如:应用程序类加载器接到加载“java.lang.Atguigu”的任务,如果这个类没加载过,把任务给扩展类加载器

(2)扩展类加载器接到加载任务后,也是先看一下内存中是否已经加载过这个类,如果加载过,就不重复加载了,直接返回Class对象。
如果没有加载过,再把任务交给“双亲”,即引导类加载器。
例如:扩展类加载器接到加载“java.lang.String”的任务,如果这个类没加载过,把任务给引导类加载器
例如:扩展类加载器接到加载“java.lang.Atguigu”的任务,如果这个类没加载过,把任务给引导类加载器

(3)引导类加载器接到加载任务后,也是先看一下内存中是否已经加载过这个类,如果加载过,就不重复加载了,直接返回Class对象。
如果没有加载过,尝试在它负责的目录下搜索这个类,
例如:引导类加载器接到加载“java.lang.String”的任务,在它负责的目录下加载,rt.jar中找,如果找到了,就返回Class对象。
例如:引导类加载器接到加载“java.lang.Atguigu”的任务,在它负责的目录下加载,rt.jar中找,如果找到了,就返回Class对象。
    如果没找到,把任务往回传,交给扩展类加载器

(4)扩展类加载器再次接到引导类回传的任务,它就会在它负责的目录下搜索这个类, 如果找到了,就返回Class对象。
    如果没找到,把任务再回传,交给应用程序类加载器

(5)应用程序类加载器再次接到扩展类加载器  回传的任务,它就会在它负责的目录下搜索这个类, 如果找到了,就返回Class对象。
    如果没找到,就报错ClassNotFoundException


总结:
    类加载的过程是类加载器来完成,它有四种(通常是3种),工作模式是双亲委托模式,目的是为了安全。
    类加载的结果是得到一个类的Class对象。

public class TestClassLoad {
    public static void main(String[] args) {
        System.out.println(MyClass.MAX_VALUE);
        System.out.println(MyClass.a);

        System.out.println("-------------------------------");
        //String.class得到的是String类的Class对象
        ClassLoader classLoader = String.class.getClassLoader();
        System.out.println("加载String类类加载器:" + classLoader);//null

        //TestClassLoad.class得到的是TestClassLoad类的Class对象
        ClassLoader loader = TestClassLoad.class.getClassLoader();
        System.out.println("程序员自定义类TestClassLoad类的类加载器对象:" + loader);//sun.misc.Launcher$AppClassLoader@18b4aac2
        ClassLoader parent = loader.getParent();
        System.out.println("app类加载器的parent:" + parent);

        System.out.println("-------------------------------");
        try {
            //Class.forName("com.atguigu.loader.TestLoader")得到TestLoader的Class对象
            Class clazz = Class.forName("com.atguigu.loader.TestLoader");
            System.out.println("在JRE/lib/ext目录下的TestLoader类的类加载器:" + clazz.getClassLoader()); //sun.misc.Launcher$ExtClassLoader@45ee12a7
            ClassLoader ext = clazz.getClassLoader();
            System.out.println("ext扩展类加载器的的parent:"+ ext.getParent());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        try {
            //Class.forName("java.lang.Atguigu")得到Atguigu的Class对象
            Class clazz = Class.forName("java.lang.Atguigu");//ClassNotFoundException:
            System.out.println("Atguigu类的Class对象:" + clazz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class MyClass{
    public static final int MAX_VALUE = 100; //类加载的准备阶段直接赋值
    public static int a = 100;//类加载的初始化阶段完成赋值
    static{
        System.out.println("静态代码块");//类初始化执行
    }
}

三、反射的根源类Class
1、为什么会有Class类型?
Java种的数据类型,特别是引用数据类型,比如:类,
它们有很多共同特征:
    以类为例
    都有类名、修饰符,父类、父接口,成员变量、成员方法、构造器....
    可以创建对象,调用方法等操作
Java的类是用来对一类具有相同特性的事物的抽象描述。

Class这个类是对Java的数据类型的抽象描述。
Class这个类的对象,就是代表一个具体的Java的数据类型。

java.lang.String类型,在Java的内存中有一个Class对象来表示它,并且这个对象是唯一的。
java.lang.Integer类型,在Java的内存中也有一个Class对象来表示它,并且这个对象也是唯一的。
    如果两个变量的类型是相同的,那么它们使用的Class对象是同一个。

2、哪些Java类型有Class对象?
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。
枚举是一种类,注解是一种接口。
每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。

结论:所有Java数据类型都有Class对象。

3、如何获取Class对象?
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
获取的方式有四种:(必须掌握)
(1)类型名.class
    适用于编译器就已知并存在的类型
(2)对象.getClass()
    Object类中有这个方法获取对象的运行时类型
    适用于多态引用时获取对象的运行时类型
(3)Class.forName("类型的全名称")
    适用于编译期间未知或不存在的类型
(4)类加载器对象.loadClass("类型的全名称")

public class TestClass {
    @Test
    public void test06(){
        //TestClass.class得到TestClass的Class对象
        //TestClass.class.getClassLoader()得到加载TestClass的类加载器对象
        ClassLoader classLoader = TestClass.class.getClassLoader();

        try {
            //用这个类加载器对象,去加载别的类
            Class<?> c1 = classLoader.loadClass("com.atguigu.bean.Teacher");
            Class c2 = Teacher.class;
            System.out.println(c1 == c2);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }


    @Test
    public void test05(){
        try {
            Class<?> c1 = Class.forName("java.lang.String");
            Object obj = "hello";//多态引用
            Class<?> c2 = obj.getClass();
            Class<?> c3 = String.class;

            System.out.println(c1 == c2);
            System.out.println(c1 == c3);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
    @Test
    public void test04(){
        int[] arr1 = {1,2,3,4,5};
        int[] arr2 = new int[10];

        Class c1 = arr1.getClass();
        Class c2 = arr2.getClass();
        System.out.println(c1 == c2);//true  只看元素的类型和维度,不看元素的值和数组的长度
    }

    @Test
    public void test03(){
        Object obj = "hello";//多态引用
        Class<?> c1 = obj.getClass();  //获取的是obj的运行时类型,是String类型的Class对象
        Class stringClass = String.class;

        System.out.println(c1 == stringClass);//true
    }

    @Test
    public void test02(){
        Class c1 = "hello".getClass();//表示"hello"这个对象的运行时类型String的Class对象
        Class stringClass = String.class;  //得到的是表示String类型的Class对象

        //比较对象的地址,是同一个对象,才会返回true
        System.out.println(c1 == stringClass);//true 它们都是java.lang.String,在内存中,只有唯一的一个Class对象来表示String类型
    }

    //演示所有的Java的数据类型都要自己的Class对象
    @Test
    public void test01(){
        //类
        Class stringClass = String.class;
        Class integerClass = Integer.class;

        //基本数据类型和void
        Class intClass = int.class;
        Class voidClass = void.class;

        System.out.println(stringClass == integerClass) ;//false
        System.out.println(intClass == integerClass) ;//false

        Class intArrayClass = int[].class;
        Class intArray2Class = int[][].class;
        System.out.println(intArrayClass == intArray2Class);//false

        Class stringArrayClass =  String[].class;
        System.out.println(intArrayClass == stringArrayClass);//false
        System.out.println(stringClass == stringArrayClass);//false

        //接口
        Class  s1 =  Serializable.class;
        //注解
        Class  o1 =   Override.class;
        //枚举
        Class m1 = Month.class;

    }
}

四、反射的应用
1、获取类的信息
拿到了某个类的Class对象,就能把这个类看透。

public class TestClassInfo {
    public static void main(String[] args)throws Exception {
        Class<?> clazz = Class.forName("java.lang.String");//这里拿String来演示,可以是任意的其他类型

        //通过Class对象获取类的信息
        //(1)获取包
        Package pkg = clazz.getPackage();
        System.out.println("pkg = " + pkg);

        //(2)获取类型名称
        String name = clazz.getName();
        System.out.println("name = " + name); //全名称 = 包  + 类名
        
        //(3)修饰符
        int modifiers = clazz.getModifiers();
        System.out.println("modifiers = " + modifiers);//17
        /*
        java.lang.reflect包下Modifier,关于所有JAVA的修饰符的描述类
                                                      十六进制      十进制     二进制
        public static final int PUBLIC           = 0x00000001;      1           1
        public static final int PRIVATE          = 0x00000002;      2           10
        public static final int PROTECTED        = 0x00000004;      4           100
        public static final int STATIC           = 0x00000008;      8           1000
        public static final int FINAL            = 0x00000010;      16          10000

        每一个修饰符是都是2的n次方,它们的二进制的特点是,其中一位是1,其他的都是0

        public:     00000000 00000000 00000000 00000001
        final :     00000000 00000000 00000000 00010000
        |按位或:    00000000 00000000 00000000 00010001  17

        17的二进制:00000000 00000000 00000000 00010001
        &
        public:    00000000 00000000 00000000 00000001
                   00000000 00000000 00000000 00000001 非0,说明有public

         17的二进制:00000000 00000000 00000000 00010001
        &
        private:    00000000 00000000 00000000 00000010
                    00000000 00000000 00000000 00000000 0,说明没有private
         */
        System.out.println(Modifier.toString(modifiers));//public final

        //(4)父类
        Class<?> superclass = clazz.getSuperclass();
        System.out.println("父类:" + superclass);

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

        //(6)成员变量们
        /*
        Field getField(String name):根据属性名获取一个公共的属性
        Field getDeclaredField(String name):根据属性名获取一个声明的属性
        Field[] getFields() :获取所有公共的属性
        Field[] getDeclaredFields() :获取所有声明的属性
         */
        System.out.println("获取所有的属性:");
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }

        //获取其中一个属性
        Field valueField = clazz.getDeclaredField("value");
        int mod = valueField.getModifiers();
        System.out.println("mod = " + mod);//18
        System.out.println("value属性的修饰符:" + Modifier.toString(mod));//private final
        System.out.println("value的名称:" + valueField.getName());
        System.out.println("value的数据类型:" + valueField.getType());//[C  [表示一维数组,C表示char类型

        //(7)获取所有的构造器
        /*
        Constructor<T> getConstructor(Class<?>... parameterTypes):获取某一个公共的构造器
        Constructor<?>[] getConstructors():所有所有公共的构造器
        Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)  :获取某个声明的构造器
        Constructor<?>[] getDeclaredConstructors()  :获取某个所有声明的构造器
         */
        System.out.println("所有的构造器:");
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }

        //获取某一个构造器
        //Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)  :获取某个声明的构造器
        //(Class<?>... parameterTypes),可变参数,因为构造器的形参的个数是有0~n的可能
        //构造器可以重载,靠构造器的形参列表区分,所以要获取某个构造器,需要指定件构造器形参列表的类型
        //public java.lang.String(byte[])
       Constructor c = clazz.getDeclaredConstructor(byte[].class);//得到形参为byte[]类型String类的构造器
        System.out.println("形参是byte[]类型的构造器:" + c);

        //(8)所有的方法
        /*
        Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取某个声明的方法,通过指定方法的名称和方法的形参列表来确定一个方法
        Method[] getDeclaredMethods():获取所有已声明的方法
        Method getMethod(String name, Class<?>... parameterTypes):获取某个公共的方法,通过指定方法的名称和方法的形参列表来确定一个方法
        Method[] getMethods():获取所有已公共的方法
         */
        System.out.println("所有的方法们:");
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
        }
    }
}

2、在运行时创建任意类型的对象
任意类型:不包括基本数据类型、void、抽象类、接口等
任意类型:是指能创建对象的类型。

简单的描述一下:
    后面大家会写JavaEE的项目。
    (1)后面的写的服务器端的代码,是运行在web服务器(例如:tomcat)中,
    服务器端会有很多的类,其中一些类是由web服务器帮我们创建对象的。例如:servlet类(代表jsp页面)
    (2)后面的写的项目会用到spring,mybatis等框架,我们类也会交给spring框架帮我们管理,
    我们的类对象由spring框架帮我们创建。

    web服务器和spring,mybatis等框架代码已经写好了,我们的类是后来写的。
    在web服务器和spring,mybatis等框架中提前写好了new对象的代码,具体new什么类的对象,是后来我们通过
    xx.xml文件告诉服务器或框架,然后他们读取这个配置文件,就可以获取到“类名”,就可以获取到对应的Class对象,
    然后new对象。


方式一:直接使用Class类的newInstance()方法创建对象
Class类中有一个使用频率比较高的方法:
    T newInstance() :创建此 Class 对象所表示的类的一个新实例。
        如果此Class对象代表的是String类型,那么 newInstance()创建的就是String的对象
        如果此Class对象代表的是Student类型,那么 newInstance()创建的就是Student的对象
        如果此Class对象代表的是Date类型,那么 newInstance()创建的就是Date的对象

    要通过这种方式创建实例对象,要求这个类型必须有 公共的无参构造,否则会报IllegalAccessException, InstantiationException异常。
            报IllegalAccessException:构造器的权限修饰符有问题
            InstantiationException异常:没有无参构造
方式二:先获取这个类的构造器对象,然后通过构造器对象的newInstance方法创建对象
    这种方式,构造器可以是无参,可以是有参,可以是任意一种权限修饰的情况都可以。

public class TestNewInstance {
    @Test
    public void test3() throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        /*
        假设框架的配置文件叫做: info.properties
        里面有key=value等各种配置信息,其中一个key是className,它的value是你要创建对象的类型名称
        例如:className=java.util.Date
         */
        //(1)第一步,先读取配置文件
        //如果info.properties文件在src的根目录下
        //src是源代码文件,它里面的所有的东西都会被编译器编译到类路径下(out目录下) 即src下的配置文件会和.class文件在一起
        //.class文件是可以通过“应用程序类加载器”加载的,那么src下的配置文件也可以通过“应用程序类加载器”加载
        Properties properties = new Properties();
        /*
        TestNewInstance类是程序员自己写的类,所以由“应用程序类加载器”加载
        TestNewInstance.class得到它的Class对象
        TestNewInstance.class.getClassLoader()得到类加载器对象,即“应用程序类加载器”对象
         */
        ClassLoader classLoader = TestNewInstance.class.getClassLoader();//获取应用程序类加载器对象
        properties.load(classLoader.getResourceAsStream("info.properties"));

        //经过上面的代码,"info.properties"中的数据就被存到properties的map中,以(key,value)形式存储
        //(2)第二步:根据key获取value,来得到要加载的类的名称
        String className = properties.getProperty("className");

        //(3)第三步:在内存中获取它的Class对象
        Class clazz = Class.forName(className);//如果这个类没在内存中,应用程序类加载器会尝试加载,如果找不到会报ClassNotFoundException

        //(4)获取某个构造器对象
        //例如:我要得到public Teacher(String name,double salary)构造器
        Constructor c = clazz.getDeclaredConstructor(String.class,double.class);//String.class代表形参的类型是String类型

        //(5)通过构造器的对象调用
        //java.lang.reflect.Constructor类T newInstance(Object... initargs)
        //newInstance(Object... initargs)的参数最终就是传给你调用的构造器的实参
        //c.newInstance("张三",15000);  这里的“张三”相当于你 new Teacher("张三",15000)

        //如果被调用的构造器是因为权限修饰符的可见性问题,无法创建对象,可以这么做
        c.setAccessible(true);//跳过了权限修饰符的检查

        Object obj = c.newInstance("张三",15000);

        System.out.println(obj);
    }

    @Test
    public void test2() throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        /*
        假设框架的配置文件叫做: info.properties
        里面有key=value等各种配置信息,其中一个key是className,它的value是你要创建对象的类型名称
        例如:className=java.util.Date
         */
        //(1)第一步,先读取配置文件
        //如果info.properties文件在src的根目录下
        //src是源代码文件,它里面的所有的东西都会被编译器编译到类路径下(out目录下) 即src下的配置文件会和.class文件在一起
        //.class文件是可以通过“应用程序类加载器”加载的,那么src下的配置文件也可以通过“应用程序类加载器”加载
        Properties properties = new Properties();
        /*
        TestNewInstance类是程序员自己写的类,所以由“应用程序类加载器”加载
        TestNewInstance.class得到它的Class对象
        TestNewInstance.class.getClassLoader()得到类加载器对象,即“应用程序类加载器”对象
         */
        ClassLoader classLoader = TestNewInstance.class.getClassLoader();//获取应用程序类加载器对象
        properties.load(classLoader.getResourceAsStream("info.properties"));

        //经过上面的代码,"info.properties"中的数据就被存到properties的map中,以(key,value)形式存储
        //(2)第二步:根据key获取value,来得到要加载的类的名称
        String className = properties.getProperty("className");

        //(3)第三步:在内存中获取它的Class对象
        Class clazz = Class.forName(className);//如果这个类没在内存中,应用程序类加载器会尝试加载,如果找不到会报ClassNotFoundException

        //(4)获取某个构造器对象
        //例如:我要得到Teacher类的public Teacher(String name)构造器
        Constructor c = clazz.getDeclaredConstructor(String.class);//String.class代表形参的类型是String类型

        //(5)通过构造器的对象调用
        //java.lang.reflect.Constructor类T newInstance(Object... initargs)
        //newInstance(Object... initargs)的参数最终就是传给你调用的构造器的实参
        //c.newInstance("张三");  这里的“张三”相当于你 new Teacher("张三")
        Object obj = c.newInstance("张三");

        System.out.println(obj);
    }


    @Test
    public void test() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        /*
        假设框架的配置文件叫做: info.properties
        里面有key=value等各种配置信息,其中一个key是className,它的value是你要创建对象的类型名称
        例如:className=java.util.Date
         */
        //(1)第一步,先读取配置文件
        //如果info.properties文件在src的根目录下
        //src是源代码文件,它里面的所有的东西都会被编译器编译到类路径下(out目录下) 即src下的配置文件会和.class文件在一起
        //.class文件是可以通过“应用程序类加载器”加载的,那么src下的配置文件也可以通过“应用程序类加载器”加载
        Properties properties = new Properties();
        /*
        TestNewInstance类是程序员自己写的类,所以由“应用程序类加载器”加载
        TestNewInstance.class得到它的Class对象
        TestNewInstance.class.getClassLoader()得到类加载器对象,即“应用程序类加载器”对象
         */
        ClassLoader classLoader = TestNewInstance.class.getClassLoader();//获取应用程序类加载器对象
        properties.load(classLoader.getResourceAsStream("info.properties"));

        //经过上面的代码,"info.properties"中的数据就被存到properties的map中,以(key,value)形式存储
        //(2)第二步:根据key获取value,来得到要加载的类的名称
        String className = properties.getProperty("className");

        //(3)第三步:在内存中获取它的Class对象
        Class clazz = Class.forName(className);//如果这个类没在内存中,应用程序类加载器会尝试加载,如果找不到会报ClassNotFoundException

        //(4)第四步:创建对象
//        通过这个类的Class对象,调用newInstance()方法创建对象
        Object obj = clazz.newInstance();
        System.out.println(obj);

    }
}

3、在运行时可以访问/操作任意对象的属性
步骤:
(1)先获取到Class对象
(2)要得到你要操作/访问的属性的Field对象
(3)如果你要访问的是静态变量,那么调用Field对象的set/get方法就可以访问该静态变量的值
    如果你要访问的是非静态变量,那么还需要创建这个类的对象,然后再Field对象的set/get方法就可以访问该非静态变量的值

public class TestAccessField {
    @Test
    public void test03() throws NoSuchFieldException, IllegalAccessException {
        String str = "hello";
        //修改str的字符串内容,假设把e修改为a

        Class<? extends String> stringClass = str.getClass();//得到String类的Class对象
        Field valueField = stringClass.getDeclaredField("value");//得到String类中的private final char value[];
        //因为value属性在String类中是私有的,需要跳过权限修饰符检查
        valueField.setAccessible(true);
        Object o = valueField.get(str);//得到str这个字符串的value数组
        char[] arr = (char[]) o;//强制转换为char[]类型
        arr[1] = 'a';

        System.out.println(str);//hallo
    }

    @Test
    public void test02() throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
        //(1)第一步,先读取配置文件
        Properties properties = new Properties();
        ClassLoader classLoader = TestNewInstance.class.getClassLoader();//获取应用程序类加载器对象
        properties.load(classLoader.getResourceAsStream("info.properties"));

        //(2)第二步:根据key获取value,来得到要加载的类的名称
        String className = properties.getProperty("className");

        //(3)第三步:在内存中获取它的Class对象
        Class clazz = Class.forName(className);//如果这个类没在内存中,应用程序类加载器会尝试加载,如果找不到会报ClassNotFoundException

        //(4)第四步:获取要操作的属性的Field对象
        Field companyField = clazz.getDeclaredField("company");

        //因为company属性是静态的,所以不需要创建Teacher的对象
        //(5)调用Field对象的set和get方法操作company属性
        //如果属性是私有的,可以通过下面的操作,避免权限修饰符检查
        companyField.setAccessible(true);
        companyField.set(null,"尚硅谷");
        Object value = companyField.get(null);
        System.out.println("value = " + value);

    }

    @Test
    public void test01() throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
        //(1)第一步,先读取配置文件
        Properties properties = new Properties();
        ClassLoader classLoader = TestNewInstance.class.getClassLoader();//获取应用程序类加载器对象
        properties.load(classLoader.getResourceAsStream("info.properties"));

        //(2)第二步:根据key获取value,来得到要加载的类的名称
        String className = properties.getProperty("className");

        //(3)第三步:在内存中获取它的Class对象
        Class clazz = Class.forName(className);//如果这个类没在内存中,应用程序类加载器会尝试加载,如果找不到会报ClassNotFoundException

        //(4)第四步:获取要操作的属性的Field对象
        Field nameField = clazz.getDeclaredField("name");

        //(5)因为name属性是非静态的,所以要创建Teacher的对象才能操作它
        //这里Teacher有 公共的无参构造,
        Object teacher = clazz.newInstance();
        
        //(6)调用Field对象的set和get方法操作name属性
        //如果属性是私有的,可以通过下面的操作,避免权限修饰符检查
        nameField.setAccessible(true);
        nameField.set(teacher,"李四");
        Object value = nameField.get(teacher);
        System.out.println("value = " + value);
        System.out.println(teacher);
    }
}

4、在运行时调用任意方法

演示通过反射调用com.atguigu.test.Father类中的各个方法

public class TestInvokeMethod {
    @Test
    public void test4() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        //(1)第一步,通过我们讲的四种方式之一,获取到Father类的Class对象
        Class clazz = Class.forName("com.atguigu.test.Father");

        //(2)第二步:获取到你要调用的方法的Method对象
        // public int sum(int a, int b)是一个非静态方法
        Method sumMethod = clazz.getDeclaredMethod("sum",int.class,int.class);

        //(3)第三步,如果是非静态方法,那么需要先创建Father类的对象
        Object obj = clazz.newInstance();//创建Father类的对象,因为clazz是代表Father类,要求Father类有公共的无参构造
        Object value = sumMethod.invoke(obj, 1, 5);//这里obj的意思是通过Father对象obj调用fun方法,等价于 ((Father)obj).fun();
        //如果sum方法有返回值,通过invoke调用,返回值就传回来了
        System.out.println("value = " + value);
    }

    @Test
    public void test3() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        //(1)第一步,通过我们讲的四种方式之一,获取到Father类的Class对象
        Class clazz = Class.forName("com.atguigu.test.Father");

        //(2)第二步:获取到你要调用的方法的Method对象
        // public void fun()是一个非静态方法
        Method funMethod = clazz.getDeclaredMethod("fun");

        //(3)第三步,如果是非静态方法,那么需要先创建Father类的对象
        Object obj = clazz.newInstance();//创建Father类的对象,因为clazz是代表Father类,要求Father类有公共的无参构造
        funMethod.invoke(obj);//这里obj的意思是通过Father对象obj调用fun方法,等价于 ((Father)obj).fun();


    }

    @Test
    public void test2() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //(1)第一步,通过我们讲的四种方式之一,获取到Father类的Class对象
        Class clazz = Class.forName("com.atguigu.test.Father");

        //(2)第二步:获取到你要调用的方法的Method对象
        //  public static void test(int a)是一个静态方法
        Method testMethod = clazz.getDeclaredMethod("test",int.class); //int.class表示要获取有一个int类型的形参的test方法

        //(3)第三步,如果是静态方法,那么可以直接调用
        testMethod.invoke(null, 100);//这里null的意思是不需要Father类的对象
                                                //这里的100是给test方法的形参赋值的100

    }

    @Test
    public void test1() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //(1)第一步,通过我们讲的四种方式之一,获取到Father类的Class对象
        Class clazz = Class.forName("com.atguigu.test.Father");

        //(2)第二步:获取到你要调用的方法的Method对象
        // public static void test()是一个静态方法
        Method testMethod = clazz.getDeclaredMethod("test");

        //(3)第三步,如果是静态方法,那么可以直接调用
        testMethod.invoke(null);//这里null的意思是不需要Father类的对象


    }
}

5、获取泛型父类的信息

演示获取子类Sub的泛型父类Father的泛型信息,子类Sub在继承父类Father时,指定了泛型信息Father<String,Integer>

public class TestGenericFather {
    public static void main(String[] args)throws Exception {
        //(1)第一步,获取Sub子类的Class对象
        Class clazz = Class.forName("com.atguigu.reflect.kuo.Sub");

        //(2)第二步:获取泛型父类的Type对象
        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);//这种方式得不到泛型信息

        Type genericSuperclass = clazz.getGenericSuperclass();
        /*
        JDK1.5引入了泛型,原来所有的类型都用Class对象表示,但是因为有了泛型之后,
        有些类型就不能用Class对象表示,例如:ArrayList<String>,ArrayList<?>等,
        JDK就新增了Type接口等类型,来表示各种具有泛型的类型。

        Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
        Type的实现类有Class,还有一些子接口:GenericArrayType, ParameterizedType, TypeVariable<D>, WildcardType
            GenericArrayType:代表T[]泛型数组
            ParameterizedType:代表ArrayList<String>参数化类型
            TypeVariable<D>:代表T等泛型类型
            WildcardType:代表带通配符的泛型类型 ArrayList<?>,Arraylist<? extends 上限>,ArrayList<? super 下限>

        Father<String,Integer>属于ParameterizedType参数化类型
         */
        ParameterizedType pt = (ParameterizedType) genericSuperclass;//向下转型,目的是调用子接口的getActualTypeArguments方法
        Type[] actualTypeArguments = pt.getActualTypeArguments();//获取泛型的具体类型参数,也就是<>中的具体类型
        for (Type actualTypeArgument : actualTypeArguments) {
            System.out.println(actualTypeArgument);
        }

        System.out.println("-------------------------");
        Type[] genericInterfaces = clazz.getGenericInterfaces();
        for (Type genericInterface : genericInterfaces) {
            if(genericInterface instanceof ParameterizedType){
                ParameterizedType p = (ParameterizedType) genericInterface;
                Type[] arr = p.getActualTypeArguments();
                System.out.println(Arrays.toString(arr));
            }
        }
    }
}

class Father<T,U>{

}
class Sub extends Father<ArrayList<String>,Integer>   implements Comparable<Sub>, Serializable {

    @Override
    public int compareTo(Sub o) {
        return 0;
    }
}

6、获取内部类和外部类的信息

演示获取Outer类的内部类信息

Class类:
    Class<?>[] getClasses():获取所有公共的内部类和内部接口
    Class<?>[] getDeclaredClasses()  :获取所有声明的内部类和内部接口

     Class<?> getEnclosingClass() :获取外部类或外部接口的信息
 

public class TestInnerOuter {
    public static void main(String[] args) {
        //第一步:得到Outer类的Class对象
        Class clazz = Outer.class;

        //第二步:获取它的内部类信息
        Class[] arr = clazz.getDeclaredClasses();
        for (Class c : arr) {
            System.out.println(c);
        }

        System.out.println("-----------------------");
        //获取HashMap的所有内部类
        Class hashMapClass = HashMap.class;
        //获取HashMap的内部类
        Class[] declaredClasses = hashMapClass.getDeclaredClasses();
        for (Class declaredClass : declaredClasses) {
            System.out.println(declaredClass);
        }

        System.out.println("------------------------");
        //演示获取Inner类的外部类信息
        //第一步:获取Inner的Class对象
        Class innerClass = Outer.Inner.class;
        //第二步:得到外部类新
        Class enclosingClass = innerClass.getEnclosingClass();
        System.out.println(enclosingClass);


        System.out.println("------------------------");
        List<Integer> list = Arrays.asList(1, 2, 3, 4);
        Class<? extends List> listClass = list.getClass();
        Class<?> c = listClass.getEnclosingClass();
        System.out.println("listClass=" + listClass);
        System.out.println("listClass的外部类=" + c);
    }
}

class Outer{
    class Inner{

    }
    static class NeiClass{

    }
}

7、反射操作数组

在java.lang.reflect包 下Array 类提供了动态创建和访问 Java 数组的方法。
    static Object newInstance(Class<?> componentType, int length)
        Class<?> componentType:数组元素的类型
        int length:数组的长度

public class TestArray {
    public static void main(String[] args) {
        MyArrayList<String> list = new MyArrayList<>(String.class,5);
        list.add("hello");
        list.add("world");
        list.add("java");
        list.add("mysql");
        list.add("atguigu");
        list.add("chai");

        String str = list.get(1);
        System.out.println(str);
    }

    @Test
    public void test(){
        Object arr = Array.newInstance(int.class, 5);
        Array.set(arr,0,10);
        Array.set(arr,1,20);

        System.out.println(Array.get(arr,0));
        System.out.println(Array.get(arr,1));
        System.out.println(Array.get(arr,2));
    }
}
import java.lang.reflect.Array;
import java.util.Arrays;

public class MyArrayList<T> {
    private T[] arr;
    private int total;

    public MyArrayList(Class tClassType,int length){
        arr = (T[]) Array.newInstance(tClassType, 5);
    }

    public void add(T element){
        if(total>=arr.length){
            arr = Arrays.copyOf(arr, arr.length*2);
        }
        arr[total++] = element;
    }
    public T get(int index){
        return arr[index];
    }
}

8、反射与自定义注解的配合使用

(1)自定义注解
语法格式:
【修饰符】 @interface 注解名{
}

语法格式:
【修饰符】 @interface 注解名{
    抽象方法;
}

语法格式:
【修饰符】 @interface 注解名{
    返回值类型 方法名()  default 默认返回值;
}

说明:返回值类型有要求,8种基本数据类型,String类型,Class类型,注解类型,或者它们的数组,其他类型不可以

(2)使用自定义注解
如果使用的注解没有抽象方法,或者抽象方法有默认返回值,那么在使用注解时,就不用为抽象方法指定返回值
否则,如果使用的注解有抽象方法,并且抽象方法没有指定默认返回值,就必须在注解名后面的()中为没有抽象方法指定返回值。

说明:如果抽象方法名是value,在使用注解时,指定方法的返回值时,可以省略value=。
但是如果方法名是其他的单词,或者抽象方法是多个,就不能省略  方法名=

(3)看一下系统的注解的声明
@Override:java.lang包

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
    //没有抽象方法
}

@SuppressWarnings

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

(4)元注解
加在注解上面的注解,称为元注解。
一共有四个元注解:
@Target:指明该注解使用的 位置
    它的位置由ElementType枚举类的10个常量对象来指定
@Retention:指明该注解的生命周期
    它的生命周期由RetentionPolicy枚举类的3个常量对象来指定
        SOURCE(源代码)
        CLASS(字节码)
        RUNTIME(运行时)  只有活到运行时的注解才能被反射读取。
@Documented:注解是否可以被javadoc.exe识别读取到API文档中
@Inherited:是否可以被子类继承

public class TestAnnotation {
    @SuppressWarnings(value="unused")
    public static void main(String[] args) {
        int a = 1;

        //尝试获取MyClass类上面的注解
        //第一步:先得到MyClass类的Class对象
       Class clazz =  MyClass.class;
       //第二步:获取类上的注解
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
            if(annotation instanceof MyAnnotation){
                MyAnnotation my = (MyAnnotation) annotation;//向下转型,为了调用MyAnnotation中声明的抽象方法
                System.out.println(my.info());
                System.out.println(my.value());
            }
        }

        System.out.println("------------------------");
        //演示MySub子类继承了父类的MyAnnotation注解
        Class<MySub> mySubClass = MySub.class;
        System.out.println(mySubClass.getAnnotation(MyAnnotation.class));
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Inherited  //可以被子类继承
@interface MyAnnotation{
    String value();
    String info() default "atguigu";
}


@interface OtherAnnotation{

}

@OtherAnnotation
@MyAnnotation(value="尚硅谷",info="北京尚硅谷")
class MyClass{

}

class MySub extends MyClass{

}

class Base{
    public void method(){
        System.out.println("父类的方法");
    }
}

class Son extends Base{
    @Override //因为Override这个注解没有抽象方法,所以直接使用@Override ,不需要加(),指定抽象方法的返回值
    public void method() {
        super.method();
    }
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值