Java基础-反射

对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。

除了int等基本类型外,其他类型基本都是class,包括interface

1
2
3
public final class Class {
private Class() {}
}

这个Class实例是JVM内部创建的,如果我们查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class实例,我们自己的Java程序是无法创建Class实例的。

Class类

JVM加载String类时,读取String.class到内存,然后为String创建一个Class类型的实例与之关联。

1
Class clz = new Class(String);

一个Class实例包含了该class的所有完整信息:

Class Instance String
name “java.lang.String”
package “java.lang”
super “java.lang.Object”
interface CharSequence…
field value[],hash…
method indexof()…

由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。

这种通过Class实例获取class信息的方法称为反射(Reflection)。

获取Class实例的三种方式

通过class的静态变量获取

1
Class clz = String.class;

通过实例的getClass()方法获取

1
2
String str = "bbdog";
Class clz = str.getClass();

通过Class.forName()方法获取

1
Class clz = Class.forName("java.lang.String");

必须是类的完成类名。

因为每种class只会在JVM中存在唯一Class实例,所以以上三种方式获取的都是同一个Class实例。

1
2
3
4
5
6
7
8
9
Class clz = String.class;

String str = "bbdog";
Class cla = str.getClass();

Class cls = Class.forName("java.lang.String");

System.out.println(clz == cla);//true
System.out.println(clz == cls);//true

instanceof==的区别还在于,instanceof体现了多态的思想,不仅可以匹配他的类型,还可以匹配它的子类型。

1
2
3
String str = "bbdog";
System.out.println(str instanceof String);//true
System.out.println(str instanceof Object);//true

通过Class创建对应实例

如果获取到了一个Class实例,我们就可以通过该Class实例来创建对应类型的实例:

1
2
3
4
// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();

上述代码相当于new String()。通过Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。

动态加载

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。例如:

1
2
3
4
5
6
7
8
9
10
11
12
// Main.java
public class Main {
public static void main(String[] args) {
if (args.length > 0) {
create(args[0]);
}
}

static void create(String name) {
Person p = new Person(name);
}
}

当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。

这就是JVM动态加载class的特性。

访问字段

访问字段

对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Hello{
@NotNull
public String name;
private int age;
}
//定义字段时候用到的属性就是Field的属性。修饰符、类型、字段名、注解
Field field = Hello.class.getDeclaredField("name");
System.out.println(field.getModifiers());//1
System.out.println(field.getType());//class java.lang.String
System.out.println(field.getName());//name
for (Annotation a : field.getAnnotations()){
System.out.println(a);
}
//@javax.validation.constraints.NotNull(message={javax.validation.constraints.NotNull.message}, groups=[], payload=[])

获取字段的值

1
2
3
4
5
6
Hello hello = new Hello("456");
Class c = hello.getClass();
Field f = c.getDeclaredField("name");
// f.setAccessible(true);
System.out.println(f.get(hello));
//456

如果字段权限是private可以通过Field.setAccessible(true)实现对私有字段的取值。这么做感觉有点破坏了封装的思想。

反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。

此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对javajavax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。

设置字段

不仅仅可以获取到字段,还可以设置字段值。

1
2
3
4
5
6
Hello hello = new Hello("456");
Class c = hello.getClass();
Field f = c.getDeclaredField("str");
f.setAccessible(true);
f.set(hello,"789");
System.out.println(f.get(hello));//789

方法

获取方法

Class类提供了以下几个方法来获取Method

  • Method getMethod(name, Class...):获取某个publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有publicMethod(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
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
public class Hello{
private String str;

public Hello(String str){
this.str = str;
}
public Hello(){

}

public int addNumber(int a, int b){
return a + b;
}
private String getStr(){
return str;
}
public static String printHello(){
return "Hello";
}
}

Hello hello = new Hello("456");
Class c = hello.getClass();
Method method = c.getMethod("addNumber", int.class, int.class);
System.out.println(method);//public int com.bbdog.demo.Hello.addNumber(int,int)
Method method1 = c.getDeclaredMethod("getStr");
System.out.println(method1);//private java.lang.String com.bbdog.demo.Hello.getStr()

System.out.println(method.getModifiers());//1
System.out.println(method.getReturnType());//int
System.out.println(method.getName());//addNumber
for (Parameter p:method.getParameters()){
System.out.println(p.getParameterizedType() + " " + p.getName());
}
//int a
//int b

调用方法

调用public方法

1
2
3
4
Hello hello = new Hello();
Class c = hello.getClass();
Method method = c.getMethod("addNumber", int.class, int.class);
System.out.println(method.invoke(hello,1,2));//3

调用静态方法

1
2
3
4
Hello hello = new Hello();
Class c = hello.getClass();
Method method = c.getMethod("printHello");
System.out.println(method.invoke(null));//Hello

区别于调用public方法,invoke时不需要传对象的实例。所以实例对象的位置传null,pringHello()方法没有传入参数,不需要填写其他参数。

调用非public方法

1
2
3
4
5
Hello hello = new Hello("bbdog");
Class c = hello.getClass();
Method method = c.getDeclaredMethod("getStr");
method.setAccessible(true);
System.out.println(method.invoke(hello));//bbdog

与通过反射获取字段相同,使用Method.setAccessible(true)可以调用非Public方法。同时如果JVM运行期间存在SecurityManager,也还是会出错的。

多态

setAccessible(true)的存在无视了封装的思想。那多态呢。

父类有getStr()方法,子类重写了getStr()方法,那么使用反射的方式调用getStr()实际上调用的是哪个呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {
public static void main(String[] args) throws Exception {
Method method = Hello.class.getDeclaredMethod("getStr");
method.setAccessible(true);
method.invoke(new Hello());//Hello:getStr
}
}
class Lang {
public getStr() {
System.out.println("Hello:getStr");
}
}

class Hello extends Lang {
public getStr() {
System.out.println("Lang:getStr");
}
}

可见,还是保留了封装思想的。

构造方法

1
Hello hello = Hello.class.newInstance();

这是反射获取对象实例的方法,但这种方法具有局限性,就是只能调用对象的非public无参构造函数

但是反射还给我们提供了一个Constructor对象,可以通过Constructor获得对象实例;

写法和获取普通方法类似,但是因为构造方法名只能和类名相同,所以它不需要传入方法名。

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 Hello{
private String str;

public Hello(String str){
this.str = str;
}
public Hello(){

}

public int addNumber(int a, int b){
return a + b;
}
private String getStr(){
return str;
}

}

//-----------------------------------------------
Constructor cons = Hello.class.getConstructor(String.class);
Hello hello = (Hello) cons.newInstance("hello");
hello.getStr();//hello

调用非public方法时,与method调用时相同,使用Constructor.setAccessible(true)即可。

继承

获取父类

1
2
3
4
5
6
Class hello = Hello.class;
System.out.println(hello.getName());//com.bbdog.demo.Hello
Class lang = hello.getSuperclass();
System.out.println(lang.getName());//com.bbdog.demo.Lang
Class object = lang.getSuperclass();
System.out.println(object.getName());//java.lang.Object

获取接口

1
2
3
4
5
6
Class s = Integer.class;
Class[] is = s.getInterfaces();
for (Class i : is) {
System.out.println(i);
}
//interface java.lang.Comparable

判断继承关系

判断继承关系我们常用instanceof,

1
2
3
4
5
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true

如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom():

1
2
3
4
5
6
7
8
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer

动态代理

动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

我们先定义了一个接口,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

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 Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}

interface Hello {
void morning(String name);
}
  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;

  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:

    1. 使用的ClassLoader,通常就是接口类的ClassLoader
    2. 需要实现的接口数组,至少需要传入一个接口进去;
    3. 用来处理接口方法调用的InvocationHandler实例。
  3. 将返回的Object强制转型为接口。

动态代理实际上是JDK在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
public class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
handler.invoke(
this,
Hello.class.getMethod("morning"),
new Object[] { name });
}
}

其实就是JDK帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。