Java基础学习1——(反射详解)

190 阅读14分钟

1.反射详解

一,学习内容概述

一,反射概述

1.概念

java语言属于静态语言,最基本的步骤我们都是写好代码(代码中的类和生成的对象都写死固定了)先进行编译,然后进行执行运行,而反射的出现,给了java动态语言的一些特性,通过反射,我们可以通过反射的API,写好代码(此时的代码中的类和对象是不确定的), 也就是说编译阶段我们不能确定我们要造哪个类的对象,但是也把代码进行编译了,反射就可以允许程序在执行期间借助于Reflection API获得任何类的任何信息(属性,方法,构造器等),运行时候确定执行哪个类,这就是反射。

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

2.简单使用示例

创建一个Person类,包含私有和公有属性,方法,构造器

package com.ethan1.reflectionTest;

/**
 * @author shkstart
 * @create 
 */
public class Person {

    private String name;
    public int age;

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Person(String name, int age) {

        this.name = name;
        this.age = age;
    }

    private Person(String name) {
        this.name = name;
    }

    public Person() {
        System.out.println("Person()");
    }

    public void show(){
        System.out.println("我是一个人");
    }

    private String showNation(String nation){
        System.out.println("我来自" + nation);
        return nation;
    }
}

进行反射的测试,与普通的创建对象和使用对象的属性和方法进行对比

package com.ethan1.reflectionTest;

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

import org.junit.Test;

public class ReflectionTest {
	@Test
	public void test2() throws Exception{
		//1.创建了一个Person类的类对象,通过该对象可以得到Person类的所有信息
		Class clazz = Person.class;
		//2.得到clazz对象中的person类的构造器
		Constructor cons = clazz.getConstructor(String.class,int.class);
		//3.利用该构造器生成person对象
		Person pObj = (Person) cons.newInstance("Tom",12);
		System.out.println(pObj.toString());
		//4.通过反射调用person类的public类型的属性,并给Person类生成的pObj对象的该属性赋值。
		Field field = clazz.getDeclaredField("age");
		field.set(pObj, 88);
		System.out.println(pObj.toString());
		//5.通过反射调用person类的public类型的show()方法,并执行
		Method show = clazz.getDeclaredMethod("show");
		show.invoke(pObj);
		System.out.println("*******************************");

        /*
         * 通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
         */
        //调用私有的构造器
		Constructor cons1 = clazz.getDeclaredConstructor(String.class);
		cons1.setAccessible(true);
		Person p1 = (Person) cons1.newInstance("ethan");
		System.out.println(p1);
		//调用私有的属性
		Field field2 = clazz.getDeclaredField("name");
		field2.setAccessible(true);
		field2.set(p1, "java");
		System.out.println(p1);
		
		//调用私有的方法
		@SuppressWarnings("unchecked")
		Method showNation = clazz.getDeclaredMethod("showNation",String.class);
		showNation.setAccessible(true);
		String nation = (String)showNation.invoke(p1, "zh_CN");
		System.out.println(nation);
		
	}
}

疑问

  1. 反射创建对象跟直接new对象,怎么使用?

    反射创建对象的前提是涉及到动态性和不确定性时候使用的,比如在javaweb时候,java代码已经在服务器后台运行着了,这时候你操作客户端不确定你是登陆网页还是注册网页,这时候就是不确定性和动态性,通过反射和客户端的相关url,可以动态的创建需要的对象。在不涉及不确定性时候,大多数还是通过new对象来实现的。

  2. 反射与面向对象中的封装性是否矛盾?

    不矛盾,面向对象的封装其实是建议性行为,私有的方法属性是供该类内部使用的,同样的功能是有更好的公有方法提供给你,一般私有的方法是供内部其他方法使用,不建议开发者使用的。而反射也不是说能够调用私有的,你就非得去使用私有的。只能说两者部分矛盾,更多的根据情况使用。

二,Class类的理解

1.理解

关于java.lang.Class类的理解

类的加载过程: 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾),这是编译的过程。 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,此过程就称为类的加载。 加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。 换句话说,Class的实例就对应着一个运行时类,这样理解:类其实也是Class类的实例对象。 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类,并且获取到的运行时类是同一个。

如何获取运行时的类,比如Person类? 可以靠java.lang.Class类(我自称元类类似元注解,修饰类的类。)去生成想要的Person类的class对象,方式如下,这个class对象其实就是包含了Person类的所有信息。这个对象其实是我们所需要的类,并且这个对象类的API可以使得我们获取Per'son类的所有信息。

Class clazz = Person.class;

Class clazz1 = Class.forName("com.ethan1.reflectionTest.Person");

一句话,加载到内存中的目标类就是Class类生成的实例对象A,通过A可以完成目标类该有的所有操作,得到目标类的所有信息。

2.生成Class实例对象的方式

//方式一:调用运行时类的属性:.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);
        //方式二:通过运行时类的对象,调用getClass()
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);

        //方式三:调用Class的静态方法:forName(String classPath)
        Class clazz3 = Class.forName("com.atguigu.java.Person");
//        clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3);

        System.out.println(clazz1 == clazz2);//true
        System.out.println(clazz1 == clazz3);//true
        //方式四:使用类的加载器:ClassLoader  (了解)
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
        System.out.println(clazz4);

        System.out.println(clazz1 == clazz4);

3.Class类实例除了是类,还可以是哪些结构

只要是被加载进内存中的,都可以生成Class实例。 哪些类型可以有Class对象?

(1) class:外部类, 成员(成员内部类, 静态内部类), 局部内部类, 匿名内部类

(2) interface: 接口

(3) []:数组

(4) enum:枚举

(5) annotation:注解@interface

(6) primitive type:基本数据类型

(7) void

//Class实例可以是哪些结构的说明:
    @Test
    public void test4(){
        Class c1 = Object.class;//类
        Class c2 = Comparable.class;//接口
        Class c3 = String[].class;//数组
        Class c4 = int[][].class;//数组
        Class c5 = ElementType.class;//枚举
        Class c6 = Override.class;//注解
        Class c7 = int.class;//基本数据类型
        Class c8 = void.class;//空类型
        Class c9 = Class.class;

        int[] a = new int[10];
        int[] b = new int[100];
        Class c10 = a.getClass();
        Class c11 = b.getClass();
        // 只要数组的元素类型与维度一样,就是同一个Class
        System.out.println(c10 == c11);//true

    }

三,类的加载过程(了解)

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。

加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与

链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。

验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题

准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。

解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

初始化: 执行类构造器()方法的过程。

类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器) 。

当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

代码示例:

/*
    在上述链接准备阶段,先把static变量m默认赋值为0
    然后初始化阶段先把m赋值为300,再赋值为100,所以m输出100.
    静态代码块跟static变量谁放前面谁先执行。
    
*/
public class ClassLoadingTest {
public static void main(String[] args) {
        System.out.println(A.m);//最后输出是100
    }
}
class A {
    static {
        m = 300;
    }
    static int m = 100;
}

1.类的加载器(了解)

类加载的作用: 将class文件字节码内容加载到内存中, 并将这些静态数据转换成方法区的运行时数据结构, 然后在堆中生成一个代表这个类的java.lang.Class对象, 作为方法区中类数据的访问入口。

类缓存: 标准的JavaSE类加载器可以按要求查找类, 但一旦某个类被加载到类加载器中, 它将维持加载(缓存) 一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

代码示例

/**
 * 了解类的加载器
 */
public class ClassLoaderTest {

    @Test
    public void test1(){
        //对于自定义类,使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);
        //调用系统类加载器的getParent():获取扩展类加载器
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);
        //调用扩展类加载器的getParent():无法获取引导类加载器
        //引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);

        ClassLoader classLoader3 = String.class.getClassLoader();
        System.out.println(classLoader3);

    }
    /*
    Properties:用来读取配置文件。

     */
    @Test
    public void test2() throws Exception {

        Properties pros =  new Properties();
        //此时的文件默认在当前的module下。
        //读取配置文件的方式一:
//        FileInputStream fis = new FileInputStream("jdbc.properties");
//        FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
//        pros.load(fis);

        //读取配置文件的方式二:使用ClassLoader
        //配置文件默认识别为:当前module的src下
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
        pros.load(is);


        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user = " + user + ",password = " + password);



    }

}

最重要的一点是利用ClassLoader获取配置文件,获取类路径下的指定文件的输入流

InputStream inputStream = Test2.class.getClassLoader().getResourceAsStream("jdbc.properties");

public void test2() throws Exception {

     Properties pro = new Properties();
     //读取配置文件方式1:
//     File f = new File("./src/jdbc.properties");
//     FileInputStream  is = new FileInputStream(f);
     //读取配置文件方式2:
     InputStream inputStream = Test2.class.getClassLoader().getResourceAsStream("jdbc.properties");
     pro.load(inputStream);
     String user = pro.getProperty("user");
     String passwd = pro.getProperty("password");
     System.out.println("user="+user+",passwd="+passwd);

    }

四,创建运行时类的对象

创建运行时类的对象跟上面的生成Class实例对象进行区分:

创建运行时类的对象:其实就是指我们想要的目标类的对象,就类似我们new Object(),只不过是利用反射实现的

生成Class实例对象:指的是我们加载到内存中的目标类,它包含的所有信息,其实也成为了一个对象,这个对象是Class类的对象。

直接代码示例:

public class NewInstanceTest {

   @Test
   public void test1() throws IllegalAccessException, InstantiationException {

       Class<Person> clazz = Person.class;
       /*
       newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。

       要想此方法正常的创建运行时类的对象,要求:
       1.运行时类必须提供空参的构造器
       2.空参的构造器的访问权限得够。通常,设置为public。


       在javabean中要求提供一个public的空参构造器。原因:
       1.便于通过反射,创建运行时类的对象
       2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

        */
       Person obj = clazz.newInstance();
       System.out.println(obj);

   }

   //体会反射的动态性
   @Test
   public void test2(){

       for(int i = 0;i < 100;i++){
           int num = new Random().nextInt(3);//0,1,2
           String classPath = "";
           switch(num){
               case 0:
                   classPath = "java.util.Date";
                   break;
               case 1:
                   classPath = "java.lang.Object";
                   break;
               case 2:
                   classPath = "com.atguigu.java.Person";
                   break;
           }

           try {
               Object obj = getInstance(classPath);
               System.out.println(obj);
           } catch (Exception e) {
               e.printStackTrace();
           }
       }



   }

   /*
   创建一个指定类的对象。
   classPath:指定类的全类名
    */
   public Object getInstance(String classPath) throws Exception {
      Class clazz =  Class.forName(classPath);
      return clazz.newInstance();
   }

}

五,获取运行时类的完整结构(了解)

1. 获取类的属性及其相关

  1. 首先定义一个类,进行测试。
package com.ethan.reflection;

/**
 * @author ethan
 * @create 2020-08-03 20:27
 */
@Myannotation(value = "hi")
public class Person extends Creature<String> implements Comparable<String>,MyInterface{
    private String name;
    int age;
    public int id;

    public Person() {
    }
    @Myannotation(value = "ab")
    private Person(String name){
        this.name = name;
    }
    Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    @Myannotation
    private String showNation(String nation){
        System.out.println("国籍为:"+nation);
        return nation;
    }
    public String display(String interests){
        return interests;
    }
    @Override
    public void info() {
        System.out.println("我是一个人");
    }

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

  1. Person类的父类
package com.ethan.reflection;

import java.io.Serializable;

/**
 * @author ethan
 * @create 2020-08-03 20:25
 */
public class Creature<T> implements Serializable{
    private char gender;
    public double weight;

    private void breath(){
        System.out.println("生物呼吸");
    }
    private void eat(){
        System.out.println("生物吃东西");
    }
}
  1. person类实现的接口
public interface MyInterface {
    void info();
}
  1. Person类的注解
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Myannotation {
    String value() default "hello";
}
  1. 进行属性的测试
public class FieldTest {
    @Test
    public void test1() throws ClassNotFoundException {
        // Class clazz = Person.class;
       Class clazz = Class.forName("com.ethan.reflection.Person");
        //获取属性结构
        //getFields()获取当前运行时类和其父类中声明的public权限的属性
        Field[] fieldList = clazz.getFields();
        for(Field field :fieldList){
            System.out.println(field);
        }
        //getFields()获取当前运行时类中声明的所有的属性,不包含父类的
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }


    }
    /*
        获取属性的权限修饰符,变量名,数据类型
     */
    @Test
    public void test2(){
        Class clazz = Person.class;
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //1.获取权限修饰符
            int modifiers = field.getModifiers();
            System.out.println(Modifier.toString(modifiers));
            //2.获取变量名
            String fieldName = field.getName();
            System.out.println(fieldName);

            //3.获取数据类型
            Class fieldType = field.getType();
            System.out.println(fieldType);


        }
    }
}

2. 获取方法的结构及其相关

public class MethodTest {
    @Test
    public void test1(){
        Class clazz = Person.class;
        /*
            获取运行时类的所有权限为public的方法,包括其父类和接口的
         */
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        /*
            获取运行时类中声明的所有方法,不包括其父类的
         */
        Method[] methods1 = clazz.getDeclaredMethods();
        for (Method method : methods1) {
            System.out.println(method);
        }

    }
 /*
    @Xxxx
    权限修饰符  返回值类型  方法名(参数类型1 形参名1,...) throws XxxException{}
     */
    @Test
    public void test2(){
        Class clazz = Person.class;
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for(Method m : declaredMethods){
            //1.获取方法声明的注解
            Annotation[] annos = m.getAnnotations();
            for(Annotation a : annos){
                System.out.println(a);
            }

            //2.权限修饰符
            System.out.print(Modifier.toString(m.getModifiers()) + "\t");

            //3.返回值类型
            System.out.print(m.getReturnType().getName() + "\t");

            //4.方法名
            System.out.print(m.getName());
            System.out.print("(");
            //5.形参列表
            Class[] parameterTypes = m.getParameterTypes();
            if(!(parameterTypes == null && parameterTypes.length == 0)){
                for(int i = 0;i < parameterTypes.length;i++){

                    if(i == parameterTypes.length - 1){
                        System.out.print(parameterTypes[i].getName() + " args_" + i);
                        break;
                    }

                    System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
                }
            }

            System.out.print(")");

            //6.抛出的异常
            Class[] exceptionTypes = m.getExceptionTypes();
            if(exceptionTypes.length > 0){
                System.out.print("throws ");
                for(int i = 0;i < exceptionTypes.length;i++){
                    if(i == exceptionTypes.length - 1){
                        System.out.print(exceptionTypes[i].getName());
                        break;
                    }

                    System.out.print(exceptionTypes[i].getName() + ",");
                }
            }


            System.out.println();
        }



    }

}

3.获取其他相关的结构

public class OtherTest {

    /*
    获取构造器结构

     */
    @Test
    public void test1(){

        Class clazz = Person.class;
        //getConstructors():获取当前运行时类中声明为public的构造器
        Constructor[] constructors = clazz.getConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }

        System.out.println();
        //getDeclaredConstructors():获取当前运行时类中声明的所有的构造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for(Constructor c : declaredConstructors){
            System.out.println(c);
        }

    }

    /*
    获取运行时类的父类

     */
    @Test
    public void test2(){
        Class clazz = Person.class;

        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);
    }

    /*
    获取运行时类的带泛型的父类

     */
    @Test
    public void test3(){
        Class clazz = Person.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
    }

    /*
    获取运行时类的带泛型的父类的泛型


    代码:逻辑性代码  vs 功能性代码
     */
    @Test
    public void test4(){
        Class clazz = Person.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        //获取泛型类型
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
//        System.out.println(actualTypeArguments[0].getTypeName());
        System.out.println(((Class)actualTypeArguments[0]).getName());
    }

    /*
    获取运行时类实现的接口
     */
    @Test
    public void test5(){
        Class clazz = Person.class;

        Class[] interfaces = clazz.getInterfaces();
        for(Class c : interfaces){
            System.out.println(c);
        }

        System.out.println();
        //获取运行时类的父类实现的接口
        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for(Class c : interfaces1){
            System.out.println(c);
        }

    }
    /*
        获取运行时类所在的包

     */
    @Test
    public void test6(){
        Class clazz = Person.class;

        Package pack = clazz.getPackage();
        System.out.println(pack);
    }

    /*
        获取运行时类声明的注解

     */
    @Test
    public void test7(){
        Class clazz = Person.class;

        Annotation[] annotations = clazz.getAnnotations();
        for(Annotation annos : annotations){
            System.out.println(annos);
        }
    }

}

六,调用运行时类的指定结构

这里指的是属性和方法,用的较多。

public class ReflectionTest {

    /*

        不需要掌握
     */
    @Test
    public void testField() throws Exception {
        Class clazz = Person.class;

        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();


        //获取指定的属性:要求运行时类中属性声明为public
        //通常不采用此方法
        Field id = clazz.getField("id");

        /*
        设置当前属性的值

        set():参数1:指明设置哪个对象的属性   参数2:将此属性值设置为多少
         */

        id.set(p,1001);

        /*
        获取当前属性的值
        get():参数1:获取哪个对象的当前属性值
         */
        int pId = (int) id.get(p);
        System.out.println(pId);


    }
    /*
    如何操作运行时类中的指定的属性 -- 需要掌握
     */
    @Test
    public void testField1() throws Exception {
        Class clazz = Person.class;

        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();

        //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
        Field name = clazz.getDeclaredField("name");

        //2.保证当前属性是可访问的
        name.setAccessible(true);
        //3.获取、设置指定对象的此属性值
        name.set(p,"Tom");

        System.out.println(name.get(p));
    }

    /*
    如何操作运行时类中的指定的方法 -- 需要掌握
     */
    @Test
    public void testMethod() throws Exception {

        Class clazz = Person.class;

        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();

        /*
        1.获取指定的某个方法
        getDeclaredMethod():参数1 :指明获取的方法的名称  参数2:指明获取的方法的形参列表
         */
        Method show = clazz.getDeclaredMethod("show", String.class);
        //2.保证当前方法是可访问的
        show.setAccessible(true);

        /*
        3. 调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
        invoke()的返回值即为对应类中调用的方法的返回值。
         */
        Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
        System.out.println(returnValue);

        System.out.println("*************如何调用静态方法*****************");

        // private static void showDesc()

        Method showDesc = clazz.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
        //如果调用的运行时类中的方法没有返回值,则此invoke()返回null
//        Object returnVal = showDesc.invoke(null);
        Object returnVal = showDesc.invoke(Person.class);
        System.out.println(returnVal);//null

    }

    /*
    如何调用运行时类中的指定的构造器
     */
    @Test
    public void testConstructor() throws Exception {
        Class clazz = Person.class;

        //private Person(String name)
        /*
        1.获取指定的构造器
        getDeclaredConstructor():参数:指明构造器的参数列表
         */

        Constructor constructor = clazz.getDeclaredConstructor(String.class);

        //2.保证此构造器是可访问的
        constructor.setAccessible(true);

        //3.调用此构造器创建运行时类的对象
        Person per = (Person) constructor.newInstance("Tom");
        System.out.println(per);

    }

}