Java 反射就是在运行的状态下,对于任意一个类,都能知道这个类的任意的属性和方法,并且能调用这些方法或者改变这些类的属性,因此 Java 被称为准动态语言。
上面说了 Java 是准动态的语言,下面我们来了解动态语言和静态语言的区别:
这篇文章主要介绍和理解前六条,关于动态代理放在 Spring 的 AOP 详解去仔细的介绍。
了解了反射的意义和反射提供的功能后,我们来具体的介绍反射:
优点:
可以实现动态的创建对象和编译,体现出很大的灵活性
缺点:
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM 我们希望做什么,并且它满足我们的要求。这类操作总是慢于直接执行的操作
Java 提供了一套反射的 API,允许在运行时检查和操作类、方法、字段等信息,主要涉及到的类包括 Class、Method、Field、Constructor 等。
这里先把常用的 API 罗列下来,方便后续的查阅和使用,这些类和方法后面都有具体的解析
在理解这些类的方法的时候,可以先从这些类的归属看起:
比如字段和方法是依托于对象的,所以我们使用字段或者调用方法的时候就需要传输是通过哪个对象来使用这个字段和调用这个方法,而构造器是属于整个类的,这些就不需要对象的承载。
API | 作用 |
---|---|
Class.forName(String className) | 通过类的完整路径名获取 Class 对象。 |
object.getClass() | 获取对象的 Class 对象 |
ClassLoader.loadClass(String className) | 通过类加载器加载类 |
API | 作用 |
---|---|
Class.getDeclaredMethods() | 获取所有声明的方法。 |
Class.getDeclaredMethod(String name, Class>… parameterTypes) | 获取指定声明的方法。 |
Method.invoke(Object obj, Object… args) | 调用方法。 |
API | 作用 |
---|---|
Class.getDeclaredFields() | 获取所有声明的字段 |
Class.getDeclaredField(String name) | 获取指定声明的字段 |
Field.get(Object obj) | 获取字段的值 |
Field.set(Object obj, Object value) | 设置字段的值 |
API | 作用 |
---|---|
Class.getDeclaredConstructors() | 获取所有声明的构造方法 |
Class.getDeclaredConstructor(Class>… parameterTypes) | 获取指定声明的构造方法 |
Constructor.newInstance(Object… initargs) | 通过构造方法创建新对象 |
观察上面的 API 我们可以发现,不管是 Method、Field 或者是 Constructor 在获取的时候都需要得到 Class 对象,这个 Class 对象是反射的源头。
在 Object,也就是所有类的父类中我们可以很容易的发现一个方法
public final native Class<?> getClass();
这个方法将会被**所有的子类继承,**通过这些子类的对象我们可以调用这个方法去获取 Class 对象。
public class getClass {
public static void main(String[] args) {
Person person = new Person();
// 调用实例的 getClass 方法获取这个类的 Class 对象
Class<? extends Person> aClass = person.getClass();
}
}
class Person{}
除了这种方法我们还可以通过类的全路径来获取到类的 Class 对象
public class getClass {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Person();
Class<? extends Person> aClass = person.getClass();
// 这个实例类是我在根路径上写的,这就已经是全路径了
Class<?> person1 = Class.forName("Person");
System.out.println(person1.hashCode());
}
}
class Person{}
如果已经知道想要获取的类是哪一个,可以直接通过 类名.class 来获取对应的 Class 对象,这种方式最为安全可靠,程序性能也是最高的
public class getClass {
public static void main(String[] args) throws ClassNotFoundException {
Class<Person> personClass = Person.class;
}
}
class Person{}
还能获取到父类的 Class 对象
// 通过 Class 实例获取父类的 Class 实例
Class<?> superclass = personClass1.getSuperclass();
除了这些方法还能通过类加载器来获取类的 Class 对象,这个放在后面叙述,通过这些方法获取到 Class 类的时候,我们就可以在运行时对其进行操作了。
通过这个类能得到的信息就是我们上面反射常用类中提到的属性,有类的属性、方法和构造器,某个类实现了哪些接口,对于每个类而言,都保留了一个不变的 Class 类型的对象,该对象包含了特定的某个结构的有关信息,比如 class、interface、enum、annotation、primitive、type / void、[] 等。
public class getClass {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Person();
Class<? extends Person> personClass1 = person.getClass();
Class<? extends Person> personClass2 = person.getClass();
Class<? extends Person> personClass3 = person.getClass();
Class<? extends Person> personClass4 = person.getClass();
Class<? extends Person> personClass5 = person.getClass();
Class<? extends Person> personClass6 = person.getClass();
// 输出这些类的 hashcode
System.out.println(personClass1.hashCode());
System.out.println(personClass2.hashCode());
System.out.println(personClass3.hashCode());
System.out.println(personClass4.hashCode());
System.out.println(personClass5.hashCode());
System.out.println(personClass6.hashCode());
}
}
class Person{}
# 相同的哈希值,是同一个对象
460141958
460141958
460141958
460141958
460141958
460141958
我们可以通过 Class 类中的 getDeclaredMethod 来获取类中的所有方法,也可以通过 getMethod
来获取全部的方法,也可以通过 getMethods
来获取所有方法的数组。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class getClass {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Person person = new Person();
Class<Person> personClass = Person.class;
person.setName("张三");
Method getName = personClass.getDeclaredMethod("getName");
System.out.println(getName.invoke(person));
}
}
class Person{
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
前面我们说过利用反射机制是可以调用类中的所有方法的,不论是共有还是私有,但如果直接通过上面的方法调用是会抛出:java.lang.IllegalAccessException
异常,我们需要手动开启访问权限才能调用私有方法。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class getClass {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Person person = new Person();
Class<Person> personClass = Person.class;
person.setName("张三");
Method getName = personClass.getDeclaredMethod("getName");
// 开放访问权限
getName.setAccessible(true);
System.out.println(getName.invoke(person));
}
}
class Person{
public String name;
private String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
getField 和 getDeclaredField 是用于获取类的字段(Field)的两个不同方法,它们之间有一些区别,这和上面 Method 是相同的,Method 同样也有这两种方法。
可见性:
- getField 用于获取类中的公有字段(public fields)。
- getDeclaredField 用于获取类中声明的所有字段,不论其可见性(包括公有、受保护、默认、私有等)。
继承关系:
- getField 会返回指定名称的公有字段,包括从父类继承而来的公有字段。
- getDeclaredField 只会返回当前类中声明的字段,不包括继承的字段。
import java.lang.reflect.Field;
public class getClass {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Person person = new Person();
Class<Person> personClass = Person.class;
person.setName("张三");
// 获取字段
Field name = personClass.getDeclaredField("name");
// 调用私有字段需要设置可见性
name.setAccessible(true);
Object o = name.get(person);
System.out.println(o);
}
}
class Person{
private String name;
private String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Constructor 相信大家都不陌生,就是我们经常使用的构造器,通过 Class 获取到的构造器同样可以构造对象,这个类可以帮助我们更方便的选择需要的构造器。
这是获取构造器的方法,我们可以通过指定**参数类型(Class)**的方式来指定构造器。
@CallerSensitive
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
return getConstructor0(parameterTypes, Member.DECLARED);
}
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class getClass {
public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getDeclaredConstructor(String.class);
Person person = constructor.newInstance("张三");
System.out.println(person.getName());
}
}
class Person{
private String name;
public String getName() {
return name;
}
public Person() {
}
public Person(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
}
为了更好的理解反射的机制,我们需要了解一些底层的知识,比如 Java 内存或者类的加载等等。
Java 的内存主要分为三块:堆、栈和方法区
加载:指的是将一个 class 字节码文件内容加载到内存中,并且将这些静态数据转换成方法区的运行时的数据结构,然后生成一个代表这个类的 java.lang.Class 对象,这个对象是存储在方法区的。
链接:将 Java 类的二进制代码合并到 JVM 运行状态之中的过程
**初始化:**执行初始化类构造器 () 方法的过程。类构造器() 方法是由编译器自动收集类中的所有变量的赋值动作和静态代码块中的语句合并而成的。(类构造器构造类的信息,不是构造类的对象)