反射入门案例

反射是指程序在运行期间可以拿到一个对象的所有信息。

比如,我们可以通过反射技术,在外部编写配置文件,在不修改源码的情况下来控制程序。它符合设计模式的ocp原则(开闭原则,简单地说就是不修改源码来扩展功能)

先来看一个入门案例

首先有一个Cat类

public class Cat {
    private String name = "招财猫";

    public int age = 10;

    public Cat() {
    }

    public Cat(String name) {
        this.name = name;
    }

    public void hi() {
        System.out.println("hi " + name);
    }
}

有这么一个外部配置文件,里面存放了一个全路径类名和一个方法名

这个配置文件希望我创建这个Cat对象,并且调用其hi方法

classpath=com.tanjiaming99.reflection.Cat
method=hi

首先读取这两个属性出来

Properties properties = new Properties();
// 在类路径下
properties.load(new FileInputStream("src\\re.properties"));
String classpath = properties.get("classpath").toString();
String method = properties.get("method").toString();
// 验证一下,拿到类的全路径和方法名
System.out.println(classpath);
System.out.println(method);

输出结果为:

com.tanjiaming99.reflection.Cat
hi

接下来就要使用反射机制来解决了

  1. 加载类,它会返回Class类型的对象

    Class<?> cls = Class.forName(classpath);

  2. 通过cls得到加载类com.tanjiaming99.reflection.Cat的对象实例

    Object o = cls.newInstance();

    它的运行类型是Cat,可以在这里强转回去。

  3. 通过cls得到加载类com.tanjiaming99.reflection.Cat的名字为method的Method对象

    Method method1 = cls.getMethod(method);

  4. 通过Method对象method1调用

    method1.invoke(o);

    需要把对象的实例放进去,这样就会调用了。

结果:

hi 招财猫


反射机制允许程序在执行中借助反射(Reflection)的API取得任意类的内部信息(比如成员变量、构造器、成员方法等等……)并且能够操作对象属性和方法。反射(Reflection)是以后学习框架的核心与灵魂。

类加载之后,在中产生了唯一一个Class类型的对象,这个对象包含了类的完整结构信息,通过这个对象可以得到类的结构。

原理图

image20210425203446454.png

Java程序在计算机中有三个阶段,分别为:代码阶段(编译阶段)Class类阶段(加载阶段)Runtime运行阶段

在代码阶段,我们所编译的代码会被javac进行编译,生成*.class*字节码文件,它里面包含代码的所有信息,属性、方法、构造器、泛型、异常等等信息,都在里面。

然后,字节码文件会被类加载器ClassLoader加载成一个Class对象,它存储在中,它同样是有这个类的所有信息!

这个Class对象其实是一种可操作的数据结构,把里面的东西当作一个对象进行处理,下面展示一些常见的。

成员变量 Field[ ]

构造器 Constructor[ ]

成员方法 Method[ ]

类加载完后,就生成一个对象了(我们以前熟悉的对象),它也是在堆中,该对象知道它是属于哪个Class对象,所以我们可以通过实例知道其关联的Class对象。

得到这个Class对象,就可以为所欲为了,做什么都行,创建对象、调用方法、操作属性都可以!

反射的优缺点

优点:可以动态创建和使用对象(也是框架底层核心),使用灵活。

缺点:执行速度慢,基本上解释执行。

?为什么很慢,不知道【x

反射相关类

  1. java.lang.Class :代表一个类,Class对象表示某个类加载后在堆中的对象,就是上面加载阶段的Class对象
  2. java.lang.reflect.Method:这个和下面的都是代码类的具体信息,它代表类的方法
  3. java.lang.reflect.Field:代表类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造方法

2的使用在上面的入门有展示了,其实都是差不多的

比如要获得成员变量(可接上入门案例),直接类对象.getXXX

特意为Car增加一个public的age属性(为什么是public,后面再解释,是方法的原因)

Field age = cls.getField("age");

得到具体的值,需要提供实例方法

System.out.println(age.get(o));


构造器的话,一样的使用。

不过要注意,传入的参数是构造器形参的class对象

Constructor<?> constructor = cls.getConstructor(String.class);

感觉也是一个大概的了解,这块会继续调整的。

Class类具体分析

  1. Class也是一个类,也继承自Object类。
  2. Class类对象不是new出来的,而是系统创建的,由类加载器的loadClass加载出来的。
  3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个Class对象生成
  5. 通过Class可以通过一系统API完整地得到一个类的完整结构
  6. Class对象存放在堆
  7. 类的字节码二进制数据,存放在方法区,有的地方称为类的元数据(包括方法代码、变量名、方法名、访问权限等等所有东西。。。)(对于二进制文件的操作很麻烦,但是对于数据结构的操作就很容易,所以就有Class对象,它也一样有着所有的东西)

Class常用方法

得到某个类的Class对象:Class.forName()
String classpath = "com.tanjiaming99.reflection.Car";
// 打印cls,可以知道它是哪个类的Class对象,就是Car了
Class<?> cls = Class.forName(classpath);
System.out.println(cls);
// 想要它的运行时类型,cls.getClass()
System.out.println(cls.getClass());

结果:

class com.tanjiaming99.reflection.Car
class java.lang.Class

得到包名 cls.getPackage().getName();
System.out.println(cls.getPackage().getName());

com.tanjiaming99.reflection

得到全类名 cls.getName()
System.out.println(cls.getName());

com.tanjiaming99.reflection.Car

创建对象实例 cls.newInstance()

这个可是实实在在的Car类型了,可以转换

Car car =(Car) cls.newInstance();
System.out.println(car);

Car{brand='宝马', price=5000000, color='白色'}

得到属性 cls.getField(fieldName);
Field brand = cls.getField("brand");
// 得到它的属性名,需要传入实例对象
System.out.println(brand.get(car));
给定做设值 field.set(object, value)
brand.set(car, "奔奔驰");
得到所有的属性(后面要再补)

这就拿到这个Class的所有字段属性

Field[] fields = cls.getFields();

获取Class类对象

其实可以按照不同阶段来分析

比如在代码阶段,可以通过Class.forName(className);

在加载阶段,由于类对象已经存在了,所以直接类.class

Runtime运行阶段,可以通过对象得到,对象.getClass()

另外,还能通过类加载器得到某个类的Class对象


  1. 已知一个类的全类名(一般通过外部配置文件可以读取来的),且该类在路径下,可以通过Class类的静态方法forName()获取,但可能有ClassNotFountException。

  2. 若已知具体的类,直接类.class,常用于参数传递

  3. 之前说,对象实例都会知道自己的Class对象,那么若已知某个类的实例,调用该实例的**getClass()**方法就可以获得类对象。

  4. 通过类加载器,它需要变成Class之后才能getClassLoader(),然后再通过类加载器loadClass(classpath)

  5. 对于基本数据类型,直接.class;对于包装类,直接.TYPE

好像,基本所有的都有Class对象……

image20210425214343866.png

类加载原理

静态加载和动态加载

写在Java代码中,类似

Cat cat = new Cat();

这样创建的对象,在类加载的时候会把它加载进来,这叫做静态加载。

如果是使用反射机制,Class.forName()这样创建的对象,你还没有运行它,它就不会被加载,这种方式叫做动态加载。