0%

java反射基础--访问类字段及调用类方法

Class 类

在学习 java 反射之前,我们先来看看 Class类。我们知道所有的 class 的本质都是一个数据类型。而 JVM 在第一次读取到一种class 类型时会将其加载进内存。每加载一种class,JVM都会为其创建一个Class类型的实例,并关联起来。这里我们需要注意 Class 它只是一个名字比较特殊的class类,我们可以理解它长这个样子:

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

JVM为每个加载了的class都创建了对应的Class实例,并且这个实例中保存了该class的所有信息。也就是说,如果我们获取到了某个Class实例,我们就可以通过这个实例获取到对应的class所有信息。

如何获取到一个 class的 Class实例?

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package test1;
public class reflect {
public static void main(String[] args) {
Person p = new Person();
/*获取其Class实例处*/
}
}
class Person {
private int age;
private int getAge() {
return age;
}

}

这里我们介绍三种:

一、直接通过一个class的静态变量来获取

1
Class cls = Person.class;

二、通过一个实例对象来获取,getClass()

1
Class cls = p.getClass();

三、通过class的完整类名来获取,class.forName()

1
2
Class cls = Class.forName("test1.Person");
// 完整路径包括包名以及类名

因为Class 实例在JVM中是唯一的,所以上述方法获取到的Class实例都是相等的(同一个实例)。

利用 Class 实例来进行访问类字段

首先我们缕一缕思路,首先我们要获得这个Class实例,然后我们需要通过Class实例获得特定的field(字段),最后通过指定方法进行修改查看,我们实例化的对象(p)的该field。
获取这个Class实例我们已经知道了,那我们该如何获取字段呢?

通过Class实例获取到 field

这里有四种方法:

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

我一般使用最多的还是getDeclaredField(name) 因为它可以访问到private修饰的field。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package test1;
import java.lang.reflect.Field;

public class reflect {
public static void main(String[] args) throws NoSuchFieldException{
Person p = new Person();
/*获取其Class实例处*/
Class cls = Person.class;
Field age = cls.getDeclaredField("age");
System.out.print(age);
}
}
class Person {
private int age;
private int getAge() {
return age;
}

}
// private int test1.Person.age

同时我们注意到,进行Field 赋值的时候我们需要导入一个java.lang.reflect.Field 包,并且使用getDeclaredField 我们需要捕获异常。

通过 Feild实例 来进行修改和查看字段

这里我们获取字段基本信息如:

  • getName():返回字段名称,例如,”name”;
  • getType():返回字段类型,也是一个Class实例,例如,String.class;
  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。

但是我们用的更多的还是利用Feild实例来获取实例化的class值(这里的p)。
此时我们可以使用 get() 和set()方法。但是我们前面获取的是private修饰的 讲道理是不可以被我们调用的。但是我们利用反射,且在使用前加入age.setAccessible(true); 就可以访问。(当然,有办法设置为不能使用setAccessible)。那我们直接来看看代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package test1;
import java.lang.reflect.Field;

public class reflect {

public static void main(String[] args) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
Person p = new Person();
/*主要代码*/
Class cls = Person.class;
Field age = cls.getDeclaredField("age");
age.setAccessible(true);
age.set(p, 20);
Object r = age.get(p);
System.out.print(r);
}
}
class Person {
private int age;
private int getAge() {
return age;
}

}

这里我们注意到,我们又throws一个异常一般我们根据idea或者eclipse自动添加捕获异常即可。

利用 Class 实例来调用类方法

想要调用类方法和访问字段类似,首先我们需要根据Class实例获取到 Method对象。

获取Method对象

类似得到field一样,我们也有四种方法:

  • Method getMethod(name, Class…):获取某个public的Method(包括父类)
  • Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有public的Method(包括父类)
    Method[]
  • getDeclaredMethods():获取当前类的所有Method(不包括父类)

这里我经常使用的是getDeclaredMethod()
这里我们需要注意的是,前2个函数中都有2个参数,其中name是必须的而 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
package test1;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class reflect {

public static void main(String[] args) throws NoSuchMethodException, SecurityException{
Person p = new Person();
/*主要代码*/
Class cls = Person.class;
Method getAage = cls.getDeclaredMethod("getAge");
System.out.println(getAage);
Method setAage = cls.getDeclaredMethod("setAge",int.class);
System.out.println(setAage);
}
}
class Person {
private int age = 112;
private void setAge(int age) {
this.age =age;
}
private int getAge() {
return age;
}

}

同样这里我们也可以根据Method对象获取到一些基本信息,如:

  • getName():返回方法名称,例如:”getScore”;
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

但是,我们最想使用的还是调用指定函数

使用 invoke调用函数

和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用:

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
package test1;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class reflect {

public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
Person p = new Person();
/*主要代码*/
Class cls = Person.class;
Method getAage = cls.getDeclaredMethod("getAge");
getAage.setAccessible(true);
Object get = getAage.invoke(p);
System.out.println(get);
Method setAage = cls.getDeclaredMethod("setAge",int.class);
setAage.setAccessible(true);
setAage.invoke(p, 666);
get = getAage.invoke(p);
System.out.println(get);
}
}
class Person {
private int age = 112;
private void setAge(int age) {
this.age =age;
}
private int getAge() {
return age;
}

}
// 112
// 666

多态

这里顺便提一下,假如我们获取到一个父类的Class,然后getMethod作用于子类,且调用的方法子类有覆写。此时调用的是子类的方法。如:

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
package test1;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class reflect {

public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
Person1 p = new Student();
/*主要代码*/
Class cls = Person1.class;
Method hello = cls.getDeclaredMethod("hello");
hello.setAccessible(true);
hello.invoke(p);

}}

class Person1 {
public void hello() {
System.out.println("Person:hello");
}
}

class Student extends Person1 {
public void hello() {
System.out.println("Student:hello");
}
}
// Student:hello

调用构造方法

我们通常使用new来创建实例,但是其实我们也可以利用反射来创建实例。

1
Person p = Person.class.newInstance();

这里使用Class.newInstance,也是有局限的,要求只能是无参数构造方法。如果是有参或者不是public就无法这样来调用。
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和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
package test1;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;



public class reflect {

public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InstantiationException, InvocationTargetException{
/*主要代码*/
Class cls = Person.class;
Constructor cons = cls.getDeclaredConstructor(int.class);
cons.setAccessible(true);
Person p = (Person)cons.newInstance(20);
System.out.print(p.age);

}
}
class Person {
public int age;

private void name() {

} Person(int age) {
// TODO Auto-generated constructor stub
this.age=age;
}

}
// 20

好啦,这就先记到这里。

【参考链接】


-------------本文结束感谢您的阅读-------------