Java反射

反射的历史由来

反射这一概念最早由编程开发人员Smith在1982年提出,主要指应用程序访问、检测、修改自身状态与行为的能力。这一概念的提出立刻吸引了编程界的极大关注,各种研究工作随之展开,随之而来引发编程革命,出现了多种支持反射机制的面向对象语言。

什么是反射?

Java 反射(Reflection)机制就是在运行状态中,对于 任意 一个类,都能够获取到这个类的所有属性和方法。对于 任意 一个对象,都能够调用它的**任意一个方法和属性(包括私有的方法和属性)。这种动态获取的信息以及动态调用对象的方法的功能**就称为 Java 语言的反射机制。

通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。

Java 程序中对象的类型都是在编译期就确定下来的。但通过 Java 反射机制动态创建的对象,这个对象的类型在编译期是未知的。

反射的核心是 JVM 在 运行时 才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。反射机制就是通过 java.lang.Class 类来实现的,在 Java 中,Object 类是所有类的根类,而 Class 类就是描述 Java 类的类。

Class 本身就是一个类,Class 就是这个类的名称,所以 Object 也包括 Class 类。

反射的主要功能

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用 private 方法);
  • 在运行时调用任意一个对象的方法;
  • 修改构造函数、方法、属性的可见性。

反射的主要用途

反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Action 之类的),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类(运行时动态加载),调用不同的方法,这个时候就必须用到反射。对与框架开发人员来说,反射虽小但作用非常大,它是各种容器实现的核心。

反射的优势

  • 提高灵活性和扩展性,降低耦合性,提高自适应能力
  • 允许程序创建和控制任何类的对象,无需硬编码目标类

反射的使用

反射机制中会用到一些类,在了解反射是如何使用之前,先介绍一下这些类。

说明
Object Java 中所有类的超类
Class 在反射中表示内存中的一个 Java 类,Class 可以代表的实例类型包括,类和接口、基本数据类型、数组
Constructor 封装了类的构造函数的属性信息,包括访问权限和动态调用信息
Field 提供类或接口的成员变量属性信息,包括访问权限和动态修改
Method 提供类或接口的方法属性信息,包括访问权限和动态调用信息
Modifier 封装了修饰属性,public、protected、static、final、synchronized、abstract 等

声明一个接口和一个简单的具体类作为示例代码:

声明一个接口:

1
2
3
public interface IAddress {
void address();
}

具体的类,请关注各个方法的修饰属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Person implements IAddress {
private int age;
public String name;

public Person() {}

public Person(int age, String name) {
this.age = age;
this.name = name;
}

public int getAge() {return age;}

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

public String getName() {return name;}

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

public Person setData(int age, String name) {
this.age = age;
this.name = name;
return this;
}

@Override
public void address() {System.out.println("I am from China.");}

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

获取Class对象

想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class 文件对象),通过字节码文件对象,能够获取想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等)。每一个类对应一个字节码文件,也就对应着一个 Class 类型的对象,也就是字节码文件对象。

反射的各种功能都需要通过 Class 对象来实现,因此,需要知道如何获取 Class对象,主要有以下几种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
// 方法1:实例对象.getClass()。创建对象阶段获取
Person person = new Person();
Class<? extends Person> clazz1 = person.getClass();

// 方法2:类名.class。字节码阶段获取
Class<Person> clazz2 = Person.class;

// 方法3:Class.forName("类名字符串") 。源文件阶段获取
Class<?> clazz3 = Class.forName("com.zhaolq.mars.demo.a.Person"); // 此过程由ClassLoader加载

// 在运行期间,一个类只有一个 Class 对象产生。
System.out.println(clazz1.equals(clazz2)); // true
System.out.println(clazz2.equals(clazz3)); // true
System.out.println(clazz1.equals(clazz3)); // true
}
}

判断是否为某个类的实例

一般地,可以使用 instanceof 关键字来判断是否为某个类的实例。同时也可以借助反射中 Class 对象的 isInstance() 方法来判断是否为某个类的实例,它是一个 Native 方法:

1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
Person person = new Person();
if (person instanceof Person) {
System.out.println("111111");
}
if (Person.class.isInstance(person)) {
System.out.println("222222");
}
}
}

获取父类和接口列表

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz = Person.class;
// 获取该类的父类
Class superClazz = clazz.getSuperclass();
System.out.println(superClazz.getName());
// 获取该类实现的接口
Class[] interfaces = clazz.getInterfaces();
for (Class clz : interfaces) {
System.out.println(clz.getName());
}
}
}

创建实例

通过反射来生成对象主要有两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) throws Exception {
// 方法1:使用 Class 对象的 newInstance() 方法
Class<?> clazz1 = Person.class;
Object obj1 = clazz1.newInstance(); // java9开始弃用,可替换为 clazz.getDeclaredConstructor().newInstance()
((Person) obj1).setData(16, "Shelly");
System.out.println(obj1);

// 方法2:通过 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance() 方法
Class<?> clazz2 = Person.class;
Constructor<?> constructor = clazz2.getConstructor(int.class, String.class);
Object obj2 = constructor.newInstance(18, "Shirley");
System.out.println(obj2);
}
}

获取方法

获取某个 Class 对象的方法集合,主要有以下几个方法:

  • Method getMethod(String name, Class<?>... parameterTypes): 获取“名称是name,参数是parameterTypes”的public的函数(包括从基类继承的、从接口实现的所有public函数)。
  • Method[] getMethods(): 获取全部的public的函数(包括从基类继承的, 从接口实现的所有public函数)。
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取”名称是name,参数是parameterTypes”,并且是类自身声明的函数,包含public、protected和private方法。
  • Method[] getDeclaredMethods(): 获取全部的类自身声明的函数,包含public、protected和private方法。
  • Method getEnclosingMethod(): 如果这个类是“其它类中某个方法的内部类”,调用getEnclosingMethod()就是这个类所在的方法,若不存在,返回null。没懂…

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Test {
public static void main(String[] args) throws Exception {
Class<?> clazz = Person.class;

System.out.println("指定的public方法:");
Method method = clazz.getMethod("toString");
System.out.println(Modifier.toString(method.getModifiers()) + " " + method.getReturnType() + " " + method.getName());

System.out.println();
System.out.println("全部的public方法:");
Method[] methods = clazz.getMethods();
for (Method method2 : methods) {
System.out.println(Modifier.toString(method2.getModifiers()) + " " + method2.getReturnType() + " " + method2.getName());
}

System.out.println();
System.out.println("指定自身声明的方法,包括private:");
Method declaredMethod = clazz.getDeclaredMethod("address");
System.out.println(Modifier.toString(declaredMethod.getModifiers()) + " " + declaredMethod.getReturnType() + " " + declaredMethod.getName());

System.out.println();
System.out.println("类自身声明的全部的方法,包括private:");
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod2 : declaredMethods) {
System.out.println(Modifier.toString(declaredMethod2.getModifiers()) + " " + declaredMethod2.getReturnType() + " " + declaredMethod2.getName());
}
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
指定的public方法:
public class java.lang.String toString

全部的public方法:
public class java.lang.String toString
public class java.lang.String getName
public void setName
public void address
public int getAge
public void setAge
public class com.zhaolq.mars.demo.a.Person setData
public final native void wait
public final void wait
public final void wait
public boolean equals
public native int hashCode
public final native class java.lang.Class getClass
public final native void notify
public final native void notifyAll

指定自身声明的方法,包括private:
public void address

类自身声明的全部的方法,包括private:
public class java.lang.String toString
public class java.lang.String getName
public void setName
public void address
public int getAge
public void setAge
public class com.zhaolq.mars.demo.a.Person setData

获取构造方法

获取某个 Class 对象的构造方法,主要有以下几个方法:

  • Constructor<T> getConstructor(Class<?>... parameterTypes): 获取 参数是parameterTypes 的public的构造函数。 parameterTypes 参数是一个Class对象数组,这些对象按声明的顺序标识构造函数的形参类型。
  • Constructor<?>[] getConstructors(): 获取全部的public构造函数。
  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 获取 参数是parameterTypes 的,并且是类自身声明的构造函数,包含public、protected和private。
  • Constructor<?>[] getDeclaredConstructors(): 获取类自身声明的全部的构造函数,包含public、protected和private。
  • Constructor<?> getEnclosingConstructor(): 如果这个类是“其它类的构造函数中的内部类”,调用 getEnclosingConstructor() 就是这个类所在的构造函数,若不存在,返回null。没懂…

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Test {
public static void main(String[] args) throws Exception {
Class<?> cls1 = Test.class;

System.out.println("指定的public构造函数:");
Class[] classArr = {String.class, Integer.class};
Constructor constructor1 = cls1.getConstructor(classArr);
System.out.println(constructor1);

System.out.println();
System.out.println("全部的public构造函数:");
Constructor[] constructor2 = cls1.getConstructors();
for (int i = 0; i < constructor2.length; i++) {
System.out.println(constructor2[i].toGenericString());
}

System.out.println();
System.out.println("指定的自身声明的构造函数,包括private:");
Constructor constructor3 = cls1.getDeclaredConstructor(String.class, Integer.class);
System.out.println(constructor3);

System.out.println();
System.out.println("类自身声明的全部的构造函数,包括private:");
Constructor[] constructor4 = cls1.getDeclaredConstructors();
for (int i = 0; i < constructor4.length; i++) {
System.out.println(constructor4[i].toGenericString());
}

System.out.println();
System.out.println("获取一个内部类的构造函数");
Constructor constructor5 = String.class.getEnclosingConstructor(); // null
System.out.println(constructor5);
}

public Test() {}

public Test(String str, Integer i) {}

protected Test(String str, String i) {}

private Test(String str, Boolean i) {}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
指定的public构造函数:
public com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.Integer)

全部的public构造函数:
public com.zhaolq.mars.demo.a.Test()
public com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.Integer)

指定的自身声明的构造函数,包括private:
public com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.Integer)

类自身声明的全部的构造函数,包括private:
protected com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.String)
private com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.Boolean)
public com.zhaolq.mars.demo.a.Test()
public com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.Integer)

获取一个内部类的构造函数
null

获取成员变量

获取某个 Class 对象的成员变量,主要有以下几个方法:

  • Field getField(String name): 获取 名称是name 的public的成员变量(包括从基类继承的, 从接口实现的所有public成员变量)。
  • Field[] getFields(): 获取全部的public成员变量(包括从基类继承的、从接口实现的所有public成员变量)。
  • Field getDeclaredField(String name): 获取 名称是name,并且是类自身声明的成员变量,包含public、protected和private成员变量。
  • Field[] getDeclaredFields(): 获取全部的类自身声明的成员变量,包含public、protected和private成员变量。

获取其他常用信息

Class 类提供了大量的实例方法来获取该 Class 对象所对应的详细信息,Class 类大致包含如下方法,其中每个方法都包含多个重载版本,因此我们只是做简单的介绍,详细请参考 JDK 文档。

获取类内信息

  • 构造器: Constructor<T> getConstructor(Class<?>... parameterTypes)
  • 包含的方法: Method getMethod(String name, Class<?>... parameterTypes)
  • 包含的属性: Field getField(String name)
  • 包含的Annotation: <A extends Annotation> A getAnnotation(Class<A> annotationClass)
  • 内部类: Class<?>[] getDeclaredClasses()
  • 外部类: Class<?> getDeclaringClass()
  • 所实现的接口: Class<?>[] getInterfaces()
  • 修饰符: int getModifiers()
  • 所在包: Package getPackage()
  • 类名: String getName()
  • 简称: String getSimpleName()

判断类本身信息的方法

  • 是否注解类型: boolean isAnnotation()
  • 是否使用了该Annotation修饰: boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
  • 是否匿名类: boolean isAnonymousClass()
  • 是否数组: boolean isArray()
  • 是否枚举: boolean isEnum()
  • 是否接口: boolean isInterface()
  • obj 是否是该 Class 的实例: boolean isInstance(Object obj)此方法是Java语言 instanceof 运算符的动态等价物
  • public boolean isLocalClass() // 类是不是本地类。本地类,就是定义在方法内部的类。
  • public boolean isMemberClass() // 当且仅当基础类是成员类时才返回true
  • public native boolean isPrimitive() // 类是不是“基本类型”,包括void和8个基本类型
  • public boolean isSynthetic() // 如果此类是合成类,则返回true;否则返回false。

使用反射获取泛型信息

为了通过反射操作泛型以迎合实际开发的需要,Java 新增了 java.lang.reflect.ParameterizedTypejava.lang.reflect.GenericArrayTypejava.lang.reflect.TypeVariablejava.lang.reflect.WildcardType 几种类型来代表不能归一到 Class 类型但是又和原始类型同样重要的类型。

  • ParameterizedType: 一种参数化类型,比如Collection
  • GenericArrayType: 一种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable: 各种类型变量的公共接口
  • WildcardType: 一种通配符类型表达式,如?? extends Number? super Integer

其他

1
2
3
4
5
6
7
8
9
10
11
12
// 获取类的"annotationClass"类型的注解 (包括从基类继承的、从接口实现的所有public成员变量)
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
// 获取类的全部注解 (包括从基类继承的、从接口实现的所有public成员变量)
public Annotation[] getAnnotations()
public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass)
// 获取类的"annotationClass"类型的,并且是类自身声明的注解,包含public、protected和private成员变量。
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)
// 获取类自身声明的全部注解 (包含public、protected和private成员变量)
public Annotation[] getDeclaredAnnotations()
public <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass)

Map<Class<? extends Annotation>, Annotation> getDeclaredAnnotationMap()

调用方法

当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。invoke() 方法的原型为:

1
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) throws Exception {
Class<?> clazz = Person.class;
// 创建Person实例
Object obj = clazz.getDeclaredConstructor().newInstance();
// 获取Person类的SetData方法
Method method = clazz.getMethod("setDate", int.class, String.class);
// 调用method的对应的方法 => setDate(16, "Shelly")
Object result = method.invoke(obj, 16, "Shelly");
System.out.println(result);
}
}

创建数组

数组在 Java 里是比较特殊的一种类型,它可以赋值给一个 Object Reference。下面我们看一看利用反射创建数组的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) throws Exception {
// 使用`java.lang.reflect.Array`反射创建长度为25的字符串数组.
Class<?> clazz = Class.forName("java.lang.String");
Object array = Array.newInstance(clazz, 10);
// 往数组里添加内容
Array.set(array, 0, "Hello");
Array.set(array, 1, "Java");
Array.set(array, 2, "Kotlin");
Array.set(array, 3, "Android");
// 获取某一项的内容
System.out.println(Array.get(array, 3));
}
}

其中的 Array 类为 java.lang.reflect.Array 类。我们通过 Array.newInstance() 创建数组对象,而 newArray 方法是一个 native 方法,它的原型是:

1
2
3
4
5
6
public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException {
return newArray(componentType, length);
}


private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException;

反射的一些注意事项

由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。

另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

小结

反射作用:

  • java反射机制可以获取类名、类的对象、类的属性、方法、继承关系、Annotation注解等信息

优点:

  • 提高Java程序的灵活性和扩展性,降低稠合性,提高自适应能力
  • 允许程序创建和控制任何类的对象,无需硬编码目标类

缺点:

  • 对性能有影响。使用反射是一种解释操作,需要动态访问JVM以满足需求。
  • 一般来说,此类访问操作慢于直接执行java代码。若无必要的情况下,不建议基于反射特性进行编码。

小测

1、 Java是一门支持反射的语言,基于反射为Java提供了丰富的动态性支持,下面关于Java反射的描述,哪些是错误的? (多选)

A、Java反射主要涉及的类如Class, Method, Filed,等,他们都在java.lang.reflet包下

B、通过反射可以动态的实现一个接口,形成一个新的类, 并可以用这个类创建对象, 调用对象方法

C、通过反射, 可以突破Java语言提供的对象成员, 类成员的保护机制, 访问一般方式不能访问的成员

D、Java反射机制提供了字节码修改的技术, 可以动态的修剪一个类

解释

1、Class类在java.lang下

2、这是java的动态代理特性

3、反射可以更改类的访问限制;private、protect等关键字是编译期的定义

4、反射可以新增类,但不能修改本身存在的类的字节码

5、通过反射可以调用私有方法

答案:AD