学习笔记:Java之反射

感觉好高大上的概念

学习来源慕课网:反射——Java高级开发必须懂的


万物皆对象

先定义一个类

1
2
3
4
5
6
// package com.example.reflect
class Foo {
public void print() {
System.out.println("Foo");
}
}

除了基础类型和类的静态成员外,Java一切皆对象,包括使用class关键词定义的类,他是Class类的对象,可以通过以下三种方法获取它,分别为通过类的.class成员,通过类的实例对象的.getClass()方法以及通过Class对象的.forName()方法获得,这三者结果等价

1
2
3
4
5
6
7
8
9
10
11
12
Foo foo = new Foo();
// 方法一
Class c1 = Foo.class;
// 方法二
Class c2 = foo.getClass();
// 方法三
Class c3 = Class.forName("com.example.reflect.Foo");

// true
System.out.println(c1 == c2);
// true
System.out.println(c2 == c3);

在运行时动态加载类

从源码到运行,java程序经历了编译和运行两个阶段,而常使用的new来初始化对象是在编译阶段,而反射则可以让类在运行时初始化为对象,这样如果项目设计的比较好的话添加新的类就不要重新编译整个项目,而是只用编译新添加的文件就可以了

使用Class类的.newInstance()方法在运行时动态初始化一个对象

举个例子,先定义一个接口

1
2
3
public interface Person {
void say();
}

实现一个类

1
2
3
4
5
6
public class Superman implements Person{
@Override
public void say() {
System.out.println("I'm Superman!");
}
}

主函数根据输入的参数动态加载类

1
2
3
4
5
6
7
8
9
10
11
public class DynamicLoad {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
Person p = (Person)c.newInstance();
p.say();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}

编译这三个文件

1
javac com/example/reflect/dynamic/*.java

运行

1
java com.example.reflect.dynamic.DynamicLoad com.example.reflect.dynamic.Superman

输出结果如下

1
I'm Superman!

此时再添加一个类,也实现了接口的方法

1
2
3
4
5
6
public class Batman implements Person{
@Override
public void say() {
System.out.println("I'm Batman!");
}
}

此时只需要编译这个文件即可

1
javac com/example/reflect/dynamic/Batman.java

运行

1
java com.example.reflect.dynamic.DynamicLoad com.example.reflect.dynamic.Batman

结果

1
I'm Batman!

这样可以使程序的灵活度更高。


获取类的方法名

类的所有方法都是java.lang.reflect.Method类的对象,可以使用Class类的.getMethods().getDeclaredMethods()方法获得,其中,前者可以获取该类和它继承的所有类的public类型的方法,而后者可以获取该类全部的方法,公开私有保护方法。

而Method类中提供了查询函数信息的各种方法,常用的如下

  • getName() 获取方法名称
  • getReturnType() 获取返回值类型,也是一个Class类的对象
  • getParameterTypes() 获取方法参数,是一个数组,每个成员也是Class类的对象

下面的程序输出传入对象的所有方法

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 static void getClassMethodInfo(Object obj) {
// 首先要获取类的类类型
Class c = obj.getClass();

// 所有自己声明的方法,全部方法,不管类型如何
Method[] methods2 = c.getDeclaredMethods();

for (Method method : methods2) {
// 获取返回值类型
Class returnType = method.getReturnType();
System.out.print(returnType.getSimpleName() + " ");

// 获取方法名
System.out.print(method.getName() + " ( ");

// 获取方法参数
Class[] paramTypes = method.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++) {
System.out.print(paramTypes[j].getSimpleName());
if (j != paramTypes.length - 1) {
System.out.print(", ");
}
}
System.out.println(" )");

}

}

如果传入的是String类型的,则输出如下,因为太多了,这里就选择靠前的

1
2
3
4
5
6
7
8
9
10
11
12
13
void checkPackageAccess ( ClassLoader, boolean )
Class forName ( String )
Class forName ( String, boolean, ClassLoader )
Class forName0 ( String, boolean, ClassLoader, Class )
String toString ( )
ProtectionDomain getProtectionDomain ( )
boolean isAssignableFrom ( Class )
boolean isInstance ( Object )
int getModifiers ( )
boolean isInterface ( )
boolean isArray ( )
boolean isPrimitive ( )
...

获取该类成员的信息

类的所有成员信息都是 java.lang.reflect.Field 类的对象,通过Class的.getFields().getDeclaredFields(),返回的是一个数组,和之前的一样,前者是获取包含父类父父类..的public类型成员,而后者只包含该类的全部成员,一下程序打印一个类的所有成员

1
2
3
4
5
6
7
8
9
10
11
12
public static void getClassMemberInfo(Class c) {
Field[] fields = c.getDeclaredFields();

for (Field field : fields) {
// 成员类型
Class fieldType = field.getType();
// 成员名称
String name = field.getName();

System.out.println(fieldType.getSimpleName() + " " + name);
}
}

如果是Integer类型的话输出如下

1
2
3
4
5
6
7
8
9
10
11
int MIN_VALUE
int MAX_VALUE
Class TYPE
char[] digits
char[] DigitTens
char[] DigitOnes
int[] sizeTable
int value
int SIZE
int BYTES
long serialVersionUID

获取该类构造函数的信息

类的所有构造函数都是 java.lang.reflect.Constructor 类的对象,通过Class类的 .getDeclaredConstructors()方法获得,一下程序打印出所有的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void getClassConstructorInfo(Class c) {
// 获取所有构造函数
Constructor[] constructors = c.getDeclaredConstructors();
for (Constructor constructor : constructors) {
String name = constructor.getName();
System.out.print(name + " ( ");

Class[] types = constructor.getParameterTypes();
for (int i = 0; i < types.length; i++) {
System.out.print(types[i].getSimpleName());
if (i != types.length - 1) {
System.out.print(", ");
}
}
System.out.println(" )");
}
}

如果是String类型的话输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java.lang.String ( byte[], int, int )
java.lang.String ( byte[], Charset )
java.lang.String ( byte[], String )
java.lang.String ( byte[], int, int, Charset )
java.lang.String ( byte[], int, int, String )
java.lang.String ( char[], boolean )
java.lang.String ( StringBuilder )
java.lang.String ( StringBuffer )
java.lang.String ( byte[] )
java.lang.String ( int[], int, int )
java.lang.String ( )
java.lang.String ( char[] )
java.lang.String ( String )
java.lang.String ( char[], int, int )
java.lang.String ( byte[], int )
java.lang.String ( byte[], int, int, int )

用非常规方式操作类的实例

先创建一个类吧

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.jdbc.reflecttest;
public class Runner {
private String name;
public Runner(){
}

public Runner(String name) {
this.name = name;
}
public void run(int length) {
System.out.println("I'm " + this.name + ", I've run " + length + "km!");
}
}

首先要对类进行实例化,这里不使用关键词 new
通过 Class.forName方法获得该类,然后有两种选择,第一种是直接 newInstance,不过这种方法无法调用有参构造方法,所以使用的类中必须要有无参构造方法,否则会报错;还有一种是获得它的构造函数,再通过它来初始化,方法分别如下

1
2
3
4
5
6
7
8
9
10
11
12
13
Class runnerClass = Class.forName("com.example.jdbc.reflecttest.Runner");

// 第一种方法
Runner runner1 = (Runner) runnerClass.newInstance();
// 这里直接对它private的值进行赋值
Field runner1Name = runnerClass.getDeclaredField("name");
// 这句可有可无
runner1Name.setAccessible(true);
runner1Name.set(runner1, "Runner1");

// 第二种方法,直接获取有参构造函数
Constructor runner2Constructor = runnerClass.getConstructor(String.class);
Runner runner2 = (Runner) runner2Constructor.newInstance("Runner2");

其次,要使用它的成员方法,也不直接调用,而是采用如下的手段

1
2
runnerClass.getMethod("run", int.class).invoke(runner1, 12);
runnerClass.getDeclaredMethod("run", int.class).invoke(runner2, 21);

两个方法的不同之处就是后者可以使用私有方法而前者不行


泛型的意义

泛型的约束只在编译阶段有效,而在运行阶段时泛型就没有用了,测试程序如下

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ArrayList<String> arr = new ArrayList<>();

// 这样写在编译阶段会报错
// arr.add(2);

ArrayList.class.getMethod("add", Object.class).invoke(arr, 12);
System.out.println(arr.size());
System.out.println(arr);
}

这里先声明了一个类型为String的泛型数组,但是通过反射的技巧调用其add方法向其中添加一个整形数,编译不会报错,因为反射仅限于运行阶段