JAVA基础——反射

前言

Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。

理解

假如你写了一段代码:Object o=new Object();运行了起来!

首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。

jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。

上面的流程就是你自己写好的代码扔给jvm去跑,跑完就over了,jvm关闭,你的程序也停止了。

反射能够做到什么那?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载。

上面是说反射在创建实例方面能带来的改变,当然,无论反射和new方式,有一点是绕不过的,就是Class对象。每个类只有一个class对象,作为方法区类的数据结构的接口。

反射能够获取类的所有属性和方法,也是依靠Class对象。

使用

获取类名

1
2
3
4
5
6
7
8
9
package cn.alunbar;

public class Alunbar {
public static void main(String arts[]){
Class alunbarClass = Alunbar.class;
System.out.println(alunbarClass.getName());
System.out.println(alunbarClass.getSimpleName());
}
}

getName()方法获取的类名包含包信息。getSimpleName()方法只是获取类名,不包含包信息。

获取类修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Alunbar {
public static void main(String arts[]){
Class alunbarClass = Alunbar.class;
System.out.println(alunbarClass.getModifiers());
System.out.println(Modifier.isPublic(alunbarClass.getModifiers()));

Class birdClass = Bird.class;
System.out.println(birdClass.getModifiers());
System.out.println(Modifier.isPublic(birdClass.getModifiers()));

}

private class Bird{

}
}

类修饰符有public、private等类型,getModifiers()可以获取一个类的修饰符

获取包信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.alunbar;

public class Alunbar {
public static void main(String arts[]){

Class birdClass = Bird.class;
System.out.println(birdClass.getPackage());

}

private class Bird{

}
}

getPackage()方法获取包信息

获取父类的Class对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Alunbar {
public static void main(String arts[]){

Class birdClass = Bird.class;
Class superclass = birdClass.getSuperclass();
System.out.println(superclass.getSimpleName());
}

private class Bird extends Animal{

}

private class Animal{

}
}

getSuperclass()方法返回的父类的Class对象。

获取接口信息

获取接口信息的方法:

Class[] interfaces = birdClass.getInterfaces();

一个类可以实现多个接口,所以getInterfaces()方法返回的是Class[]数组。 注意:getInterfaces()只返回指定类实现的接口,不会返父类实现的接口。

获取构造函数Constructor

获取构造函数的方法:

Class birdClass = Bird.class;
Constructor[] constructors = birdClass.getConstructors();

一个类会有多个构造函数,getConstructors()返回的是Constructor[]数组,包含了所有声明的用public修饰的构造函数。

如果你已经知道了某个构造的参数,可以通过下面的方法获取到回应的构造函数对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Alunbar {
public static void main(String arts[]){

Class birdClass = Bird.class;
try{
Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
}catch(NoSuchMethodException e){

}
}

private class Bird {
public Bird(){

}

public Bird(String eat){

}
}
}

上面获取构造函数的方式有2点需要注意:
1、只能获取到public修饰的构造函数。
2、需要捕获NoSuchMethodException异常。

获取构造函数的参数

获取到构造函数的对象之后,可以通过getParameterTypes()获取到构造函数的参数。

1
2
Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
Class[] parameterTypes = constructors.getParameterTypes();

初始化对象

通过反射获取到构造器之后,通过newInstance()方法就可以生成类对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Alunbar {
public static void main(String arts[]){

Class birdClass = Bird.class;
try{
Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
Bird bird = (Bird)constructors.newInstance("eat tea");

}catch(Exception e){
System.out.println("没有对应的构造函数");
}
}

class Bird {
public Bird(){

}

protected Bird(String eat){

}
}
}

newinstance()方法接受可选数量的参数,必须为所调用的构造函数提供准确的参数。如果构造函数要求String的参数,在调用newinstance()方法是,必须提供String类型的参数。

获取Methods方法信息

下面代码是通过反射可以获取到该类的声明的成员方法信息:

1
2
3
4
Method[] metchods = birdClass.getMethods();
Method[] metchods1 = birdClass.getDeclaredMethods();
Method eatMetchod = birdClass.getMethod("eat", new Class[]{int.class});
Method eatMetchod1 = birdClass.getDeclaredMethod("eat", new Class[]{int.class});

无参的getMethods()获取到所有public修饰的方法,返回的是Method[]数组。 无参的getDeclaredMethods()方法到的是所有的成员方法,和修饰符无关。 对于有参的getMethods()方法,必须提供要获取的方法名以及方法名的参数。

如果要获取的方法没有参数,则用null替代:

1
Method eatMetchod = birdClass.getMethod("eat", null);

无参的getMethods()和getDeclaredMethods()都只能获取到类声明的成员方法,不能获取到继承父类的方法。

获取成员方法参数

1
2
Class birdClass = Bird.class;
Class[] parameterTypes = eatMetchod1.getParameterTypes();

获取成员方法返回类型

1
2
Class birdClass = Bird.class;
Class returnType = eatMetchod1.getReturnType();

invoke()方法

java反射提供invoke()方法,在运行时根据业务需要调用相应的方法,这种情况在运行时非常常见,只要通过反射获取到方法名之后,就可以调用对应的方法:

1
2
3
4
Class birdClass = Bird.class;
Constructor constructors1 = birdClass.getConstructor();
Method eatMetchod = birdClass.getMethod("eat", new Class[]{int.class});
System.out.println(eatMetchod.invoke(constructors1.newInstance(), 2));

invoke方法有两个参数,第一个参数是要调用方法的对象,上面的代码中就是Bird的对象,第二个参数是调用方法要传入的参数。如果有多个参数,则用数组。

如果调用的是static方法,invoke()方法第一个参数就用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
public class Alunbar {
public static void main(String arts[]){
try{
Class birdClass = Bird.class;
Constructor constructors1 = birdClass.getConstructor();
Method eatMetchod = birdClass.getMethod("eat", new Class[]{int.class});
System.out.println(eatMetchod.invoke(null, 2));
}catch(Exception e){
e.printStackTrace();
System.out.println("没有对应的构造函数");
}
}
}

class Bird{
public static int eat(int eat){
return eat;
}
public Bird(){

}

public Bird(String eat){

}

private void talk(){}
}

class Animal{
public void run(){

}
}

使用反射可以在运行时检查和调用类声明的成员方法,可以用来检测某个类是否有getter和setter方法。getter和setter是java bean必须有的方法。 getter和setter方法有下面的一些规律: getter方法以get为前缀,无参,有返回值 setter方法以set为前缀,有一个参数,返回值可有可无, 下面的代码提供了检测一个类是否有getter和setter方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void printGettersSetters(Class aClass){
Method[] methods = aClass.getMethods();

for(Method method : methods){
if(isGetter(method)) System.out.println("getter: " + method);
if(isSetter(method)) System.out.println("setter: " + method);
}
}

public static boolean isGetter(Method method){
if(!method.getName().startsWith("get")) return false;
if(method.getParameterTypes().length != 0) return false;
if(void.class.equals(method.getReturnType()) return false;
return true;
}

public static boolean isSetter(Method method){
if(!method.getName().startsWith("set")) return false;
if(method.getParameterTypes().length != 1) return false;
return true;
}

获取成员变量

通过反射可以在运行时获取到类的所有成员变量,还可以给成员变量赋值和获取成员变量的值。

1
2
3
4
5
Class birdClass = Bird.class;
Field[] fields1 = birdClass.getFields();
Field[] fields2 = birdClass.getDeclaredFields();
Field fields3 = birdClass.getField("age");
Field fields4 = birdClass.getDeclaredField("age");

getFields()方法获取所有public修饰的成员变量,getField()方法需要传入变量名,并且变量必须是public修饰符修饰。
getDeclaredFields方法获取所有生命的成员变量,不管是public还是private。

获取成员变量类型

1
2
Field fields4 = birdClass.getDeclaredField("age");
Object fieldType = fields4.getType();

成员变量赋值和取值

一旦获取到成员变量的Field引用,就可以获取通过get()方法获取变量值,通过set()方法给变量赋值:

1
2
3
4
5
Class birdClass = Bird.class;
Field fields3 = birdClass.getField("age");
Bird bird = new Bird();
Object value = fields3.get(bird);
fields3.set(bird, value);

访问私有变量

有很多文章讨论禁止通过反射访问一个对象的私有变量,但是到目前为止所有的jdk还是允许通过反射访问私有变量。

使用 Class.getDeclaredField(String name)或者Class.getDeclaredFields()才能获取到私有变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package field;

import java.lang.reflect.Field;

public class PrivateField {
protected String name;

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

public class PrivateFieldTest {
public static void main(String args[])throws Exception{
Class privateFieldClass = PrivateField.class;
Field privateName = privateFieldClass.getDeclaredField("name");
privateName.setAccessible(false);
PrivateField privateField = new PrivateField("Alunbar");
String privateFieldValue = (String) privateName.get(privateField);
System.out.println("私有变量值:" + privateFieldValue);
}
}

上面的代码有点需要注意:必须调用setAccessible(true)方法,这是针对私有变量而言,public和protected等都不需要。这个方法是允许通过反射访问类的私有变量。

访问私有方法

和私有变量一样,私有方法也是不允许其他的类随意调用的,但是通过反射可以饶过这一限制。 使用Class.getDeclaredMethod(String name, Class[] parameterTypes)或者Class.getDeclaredMethods()方法获取到私有方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PrivateMethod {
private String accesPrivateMethod(){
return "成功访问私有方法";
}
}

public class PrivateMethodTest {
public static void main(String args[])throws Exception{
Class privateMethodClass = PrivateMethod.class;

Method privateStringMethod = privateMethodClass.getDeclaredMethod("accesPrivateMethod", null);
privateStringMethod.setAccessible(true);
String returnValue = (String)privateStringMethod.invoke(new PrivateMethod(), null);

System.out.println("returnValue = " + returnValue);
}
}

和访问私有变量一样,也要调用setAccessible(true)方法,允许通过反射访问类的私有方法。

访问类注解信息

通过反射可以在运行时获取到类、方法、变量和参数的注解信息。

访问类的所有注解信息:

1
2
3
4
5
6
7
8
9
10
Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

访问类特定的注解信息:

1
2
3
4
5
6
7
8
Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}

访问方法注解信息:

1
2
3
4
5
6
7
8
9
10
Method method = ... //obtain method object
Annotation[] annotations = method.getDeclaredAnnotations();

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

访问特定方法注解信息:

1
2
3
4
5
6
7
8
Method method = ... // obtain method object
Annotation annotation = method.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}

访问参数注解信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Method method = ... //obtain method object
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();

int i=0;
for(Annotation[] annotations : parameterAnnotations){
Class parameterType = parameterTypes[i++];

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("param: " + parameterType.getName());
System.out.println("name : " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
}

Method.getParameterAnnotations()方法返回的是一个二维的Annotation数组,其中包含每个方法参数的注解数组。

访问类所有变量注解信息:

1
2
3
4
5
6
7
8
9
10
Field field = ... //obtain field object
Annotation[] annotations = field.getDeclaredAnnotations();

for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}

访问类某个特定变量的注解信息:

1
2
3
4
5
6
7
8
Field field = ... // obtain method object
Annotation annotation = field.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}

获取泛型信息

很多人认为java类在编译的时候会把泛型信息给擦除掉,所以在运行时是无法获取到泛型信息的。其实在某些情况下,还是可以通过反射在运行时获取到泛型信息的。

获取到java.lang.reflect.Method对象,就有可能获取到某个方法的泛型返回信息。

泛型方法返回类型
下面的类中定义了一个返回值中有泛型的方法:

1
2
3
4
5
6
7
8
public class MyClass {

protected List<String> stringList = ...;

public List<String> getStringList(){
return this.stringList;
}
}

下面的代码使用反射检测getStringList()方法返回的是List而不是List

1
2
3
4
5
6
7
8
9
10
11
12
Method method = MyClass.class.getMethod("getStringList", null);

Type returnType = method.getGenericReturnType();

if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
}
}

上面这段代码会打印:typeArgClass = java.lang.String

泛型方法参数类型

下面的类定义了一个有泛型参数的方法setStringList():

1
2
3
4
5
6
7
public class MyClass {
protected List<String> stringList = ...;

public void setStringList(List<String> list){
this.stringList = list;
}
}

Method类提供了getGenericParameterTypes()方法获取方法的泛型参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
method = Myclass.class.getMethod("setStringList", List.class);

Type[] genericParameterTypes = method.getGenericParameterTypes();

for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}

上面的代码会打印出parameterArgType = java.lang.String

泛型变量类型

通过反射也可以获取到类的成员泛型变量信息——静态变量或实例变量。下面的类定义了一个泛型变量:

1
2
3
public class MyClass {
public List<String> stringList = ...;
}

通过反射的Filed对象获取到泛型变量的类型信息:

1
2
3
4
5
6
7
8
9
10
11
12
Field field = MyClass.class.getField("stringList");

Type genericFieldType = field.getGenericType();

if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}

Field对象提供了getGenericType()方法获取到泛型变量。 上面的代码会打印出:fieldArgClass = java.lang.String

问题

  1. 性能问题

    java反射的性能并不好,原因主要是编译器没法对反射相关的代码做优化。

  2. 安全问题

    我们知道单例模式的设计过程中,会强调将构造器设计为私有,因为这样可以防止从外部构造对象。但是反射可以获取类中的域、方法、构造器,修改访问权限。所以这样并不一定是安全的。

代理

文章目录
  1. 1. 前言
  2. 2. 理解
  3. 3. 使用
    1. 3.1. 获取类名
    2. 3.2. 获取类修饰符
    3. 3.3. 获取包信息
    4. 3.4. 获取父类的Class对象
    5. 3.5. 获取接口信息
    6. 3.6. 获取构造函数Constructor
    7. 3.7. 获取构造函数的参数
    8. 3.8. 初始化对象
    9. 3.9. 获取Methods方法信息
    10. 3.10. 获取成员方法参数
    11. 3.11. 获取成员方法返回类型
    12. 3.12. invoke()方法
    13. 3.13. 获取成员变量
    14. 3.14. 获取成员变量类型
    15. 3.15. 成员变量赋值和取值
    16. 3.16. 访问私有变量
    17. 3.17. 访问私有方法
    18. 3.18. 访问类注解信息
    19. 3.19. 访问类特定的注解信息:
    20. 3.20. 访问方法注解信息:
    21. 3.21. 访问特定方法注解信息:
    22. 3.22. 访问参数注解信息:
    23. 3.23. 访问类所有变量注解信息:
    24. 3.24. 访问类某个特定变量的注解信息:
    25. 3.25. 获取泛型信息
    26. 3.26. 泛型方法参数类型
    27. 3.27. 泛型变量类型
  4. 4. 问题
  5. 5. 代理
|