Java反射机制基础

什么是反射?

The name reflection is used to describe code which is able to inspect other code in the same system (or itself).

反射这个词,用来描述那些能够检查代码的那些代码。

For example, say you have an object of an unknown type in Java, and you would like to call a ‘doSomething’ method on it if one exists. Java’s static typing system isn’t really designed to support this unless the object conforms to a known interface, but using reflection, your code can look at the object and find out if it has a method called ‘doSomething’ and then call it if you want to.

假设现在你有一个对象,但是你不知道它的类型,然而你却想要调用它的一些方法。原本的java并不能实现这个功能,但是有了反射,你就可以了。

简单来说,反射就是在程序运行的时候(注意是运行的时候!)获得一个类的属性和方法、或者在运行的时候动态创建一个类。

听着是不是还是有点不知所以,那我现在问你,如何编写一个方法来知道一个类里面有多少方法呢?这个普通的代码是无法做到的,但是依靠反射可以很简单完成。

那么反射有什么好处呢?

  • 在运行时判断类的一些信息
  • 在运行时构造新的类
  • 在运行时调用类的方法(甚至是private的)

那么反射有什么坏处呢?

  • 运行慢

反射的主要用途

Java核心技术卷一中,对反射的说明是这样的:

反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者,而不是应用程序员。

所以如果你之前没有接触过反射,这也很正常,因为它确实不是普通应用程序员应该考虑的问题。然而,许许多多的框架,都用到了反射这个机制。

在日常生活中最常见的,如IDEA中的这个功能:

1569303911274

输入点号,就会自动弹出这个对象(或者类)所支持的方法,是不是非常方便呢?这里其实也是用到了反射机制。

还有像Spring这种框架,你需要做的是配置一些xml,然后就会根据配置的xml来生成类和对象,这也是用到了反射机制。所以其实理解反射是一件非常重要的事情。

基本使用

首先,由于Java万物皆对象这一理念,所以自然抽象了一个类,来专门管理类。这个类叫Class,注意它的第一个字母是大写的,和小写的class是有区别的。

获取Class对象

有以下三种方法可以获得Class对象:

  1. [最常用]直接使用Class类的静态方法forName,这里如果编写过JDBC的应该会用过这个方法。
1
2
3
4
5
6
7
8
9
10
Class cls = Class.forName("");

//以下为JDBC示例
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
  1. 直接调用对象的属性即可。
1
2
Class cls = int.class;
Class cls = String.class;
  1. 使用对象的getClass()方法:
1
2
String s = "hello";
Class cls = s.getClass();

判断是否是某个类的实例

1
2
3
4
5
String s = "hello";
Solution solution = new Solution();
Class cls = s.getClass();
System.out.println(cls.isInstance(s)); //true
System.out.println(cls.isInstance(solution)); //false

创建对象

  1. 直接使用Class对象自带的newInstance()方法。
1
2
3
String s = "hello";
Class cls = s.getClass();
Object newString = cls.newInstance();
  1. [推荐]首先获得类的构造器,然后使用构造器来获取一个对象。
1
2
3
4
5
String s = "hello";
Class cls = s.getClass();
Constructor constructor = cls.getConstructor(cls);
Object newString = constructor.newInstance("new String!");
System.out.println(newString);

获得方法

分别有以下四种方法来获取一个类所具有的所有方法或者获取一个指定的方法。

1
2
3
4
5
6
7
public Method getMethod(String name, Class<?>... parameterTypes)

public Method[] getMethods()

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

public Method[] getDeclaredMethods()

首先为了测试,我先瞎写了一个类,不显示地继承自任何类(即,它继承自Object类)

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyMath {
private int add(int a, int b) {
return a + b;
}

public void sub(int a, int b) {

}

public static void test(String s) {

}
}

然后开始测试这几个方法:

1
2
3
4
5
6
MyMath myMath = new MyMath();
Class<?> cls = myMath.getClass();
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method);
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
public static void com.test.MyMath.test(java.lang.String)
public void com.test.MyMath.sub(int,int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

由此可见getMethods()这个方法把所有的方法都打印了出来。但是唯独没有声明的私有方法。

而换成getDeclaredMethods(),输出结果就变成了以下这个:

1
2
3
private int com.test.MyMath.add(int,int)
public static void com.test.MyMath.test(java.lang.String)
public void com.test.MyMath.sub(int,int)

可以看到就算声明了private,也会被打印出来。

获取成员属性

1
2
3
4
public Field getField(String name)
public Field[] getFields()
public Field getDeclaredField(String name)
public Field[] getDeclaredFields()

这举一反三,应该还是很容易理解的吧。

调用方法

因为只是基础部分,所以简单来说,就是使用invoke来调用一个方法就可以啦。

1
2
3
4
5
6
7
MyMath myMath = new MyMath();
Class<?> cls = myMath.getClass();
Object obj = cls.newInstance();
Method method = cls.getDeclaredMethod("add", int.class, int.class);
method.setAccessible(true);
Object result = method.invoke(obj, 1, 4);
System.out.println(result);

这段代码需要注意的是第五行,由于这个方法其实是一个private方法,所以如果你直接去调用,是会有异常的,这个时候需要设置方法的属性,这样才可以调用private方法。

至此,大概就是反射基础内容的全部了,我们可以通过创建Class对象,在代码运行的时候来获取类的成员变量,方法等(得益于java面向对象的概念,它已经帮我们封装完成了,直接使用即可),以此来达成我们想要的目的。