Java反射操作私有成员或方法、实例化私有内部类
预备知识
- 通过
实例.getClass()
返回实例的运行时类类型的Class引用。 - 通过
类名.class
可以获得Class引用(前提是你可以import这个类,好处是使用.class
来创建对Class对象的引用时,不会自动地初始化该Class对象)。 - 通过
Class.forName(类名的全称)
也可以获得Class引用。其方法声明为public static Class<?> forName(String className)
,注意泛型为?。
递归地打印父类getSuperclass()
package com.prac;
class A{}
class B extends A {}
class C extends B {}
public class testRecur {
static void printInfo(Class cc){
System.out.println("name: "+cc.getName());
System.out.println("is interface? "+cc.isInterface());
System.out.println("simple name: "+cc.getSimpleName());
System.out.println("canonical name: "+cc.getCanonicalName());
System.out.println();
}
static void f(Class c){
printInfo(c);
try{
f(c.getSuperclass());
}catch(Exception e){
}
}
public static void main(String[] args) {
Class c = null;
try{
c = Class.forName("com.prac.C");
//c = C.Class;也能有同样效果
f(c);
}catch(ClassNotFoundException e){}
}
}
打印结果如下:
name: com.prac.C
is interface? false
simple name: C
canonical name: com.prac.C
name: com.prac.B
is interface? false
simple name: B
canonical name: com.prac.B
name: com.prac.A
is interface? false
simple name: A
canonical name: com.prac.A
name: java.lang.Object
is interface? false
simple name: Object
canonical name: java.lang.Object
很可惜,getSuperclass
这个方法只能看到native,方法声明为@HotSpotIntrinsicCandidate public native Class<? super T> getSuperclass();
Returns the {@code Class} representing the direct superclass of the entity (class, interface, primitive type or void) represented by this {@code Class}. If this {@code Class} represents either the {@code Object} class, an interface, a primitive type, or void, then null is returned. If this object represents an array class then the {@code Class} object representing the {@code Object} class is returned.
Class的newInstance方法
newInstance()只能调用无参构造方法。
package com.prac;
class A{
int i = 1;
}
class B extends A {
int i = 2;
}
class C extends B {
int i = 3;
}
public class testRecur {
public static void main(String[] args) {
Class c = C.class;
try {
C cInstance = c.newInstance();
System.out.println(cInstance.i);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
此时会有如上报错,此时newInstance返回的对象的类型为Object,但你可以通过强转C cInstance = (C)c.newInstance()
来实现。打印结果为3。
- 当声明初始化语句为
Class c = C.class
时,newInstance返回的对象的类型为Object。 - 当声明初始化语句为
Class<?> c = C.class
时,newInstance返回的对象的类型为Object。 - 当声明初始化语句为
Class<? extends B> c = C.class
时,newInstance返回的对象的类型为Object(虽然下面的错误提示为抓到了<? extends com.prac.B>
,但返回的类型就是Object)。
- 只有当声明初始化语句为
Class<C> c = C.class
时,newInstance返回的对象的类型才为C,这时就不用强转了。 - 注意,不能表现出如下的多态类似行为:
Class<B> c = C.class;
。因为此时需要的多态行为是:class<C>
是class<B>
的子类,但并不是这样,它们之间没有继承关系。
通过getSuperclass方法来newInstance也是一样的,返回的对象只能是基类Object。
package com.prac;
class A{
int i = 1;
}
class B extends A {
int i = 2;
}
class C extends B {
int i = 3;
}
public class testRecur {
public static void main(String[] args) {
Class<C> c = C.class;
Class<? super C> sup = c.getSuperclass();
try {
B bInstance = sup.newInstance();
System.out.println(cInstance.i);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
B bInstance = sup.newInstance()
报错,除非你强转为B,或者bInstance声明为Object。
总结:除非你的Class的泛型已经确定了一个类型(是完全确定,而不是谁的父类,谁的子类,如Class<T>)时,newInstance方法返回的类型才会是你的确定好的类型,不然返回对象永远都是Object类。
注意,newInstance方法只能调用默认的无参构造器,所以这个类必须有无参的构造器。
操作私有成员(取值,赋值)
public class person {
private String pname = "noName";
}
import java.lang.reflect.Field;
public class testFiled {
public static void main(String[] args){
person p = new person();
Class<person> personClazz = person.class;
Field pnameField = null;
if(null != personClazz){
try {
pnameField = personClazz.getDeclaredField("pname");
pnameField.setAccessible(true);//设置可访问性为真
pnameField.set(p,"jojo");
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (null != pnameField){
try {
System.out.println(pnameField.get(p));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
getDeclaredField
这个方法可以获得一个field类,返回的field类可以通用地操作person对象的pname成员。
源码里Field的set方法的声明为public void set(Object obj, Object value)
,方法解释如下:
Sets the field represented by this {@code Field} object on the specified object argument to the specified new value. The new value is automatically unwrapped if the underlying field has a primitive type.
对Object参数的特定field设置成特定的新值即第二个参数,第二个参数如果是一个基本类型,那么会自动拆箱。
调用私有方法
public class person {
private String pname = "noName";
private void printName(){
System.out.println(pname);
}
}
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class testMethod {
public static void main(String[] args){
person p = new person();
Class<person> personClazz = person.class;
Method pPrintMethod = null;
if(null != personClazz){
try {
pPrintMethod = personClazz.getDeclaredMethod("printName");
pPrintMethod.setAccessible(true);//设置可访问性为真
pPrintMethod.invoke(p);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
Method的invoke
的方法声明为public Object invoke(Object obj, Object... args)
,第二个参数可见为数组形参。上面还可以这样调用,pPrintMethod.invoke(p,new Object[]{})
,new Object[]{}返回一个空的Object数组。
Invokes the underlying method represented by this {@code Method} object, on the specified object with the specified parameters. Individual parameters are automatically unwrapped to match primitive formal parameters, and both primitive and reference parameters are subject to method invocation conversions as necessary.
创建private内部类的实例
package com.prac;
import org.jetbrains.annotations.Contract;
public class outer {
int i = 1;
private static class staticInner{
int j = 2;
void print(){
System.out.println("in staticInner");
}
}
private class normalInner{
int k =3;
void print(){
System.out.println("in normalInner");
}
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;
import com.prac.outer;
public class testInner {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
outer out = new outer();
Class<?> normalClazz = null;
try {
normalClazz = Class.forName("com.prac.outer$normalInner");
System.out.println(normalClazz.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Constructor<?> con = normalClazz.getDeclaredConstructor(outer.class);
con.setAccessible(true);
Object obj = con.newInstance(out);
try {
Field field = normalClazz.getDeclaredField("k");
field.setAccessible(true);
System.out.println(field.get(obj));
} catch (NoSuchFieldException e) {
System.out.println("get field failed");
e.printStackTrace();
}
}
}
打印结果为:
com.prac.outer$normalInner
3
normalClazz = Class.forName("com.prac.outer$normalInner")
注意内部类的写法。getDeclaredConstructor
的方法声明为public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
,是一个Class的数组,每个元素代表着参数类型。normalClazz.getDeclaredConstructor(outer.class)
这句是获得了非静态内部类的无参构造函数,但由于非静态内部类的构造函数里面总是会隐式地在第一个位置加一个外部类引用作为参数,所以这里这么写。con.newInstance(out)
这里调用获得到的构造函数,但是必须显式地传一个外部类进去,才可以调用这个无参构造函数。- 由于
Constructor<?>
制定的泛型是?(newInstance
的方法声明为public T newInstance(Object ... initargs)
,从这里可以看出返回类型为T,但由于创建时指定的是?),所以con.newInstance(out)
只能返回Object引用,虽然你已经创建了normalInner的实例。 - 现在有Object引用指向了normalInner的实例,通过获得Field,一样可以获得实例的成员变量。(这点很关键,因为原来我以为这种引用是无法获得成员的,但反射可能只跟对象的RTTI有关,无所谓引用是不是基类Object)
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;
import com.prac.outer;
public class testInner {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
outer out = new outer();
Class<?> staticClazz = null;
try {
staticClazz = Class.forName("com.prac.outer$staticInner");
System.out.println(staticClazz.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Constructor<?> con = staticClazz.getDeclaredConstructor();
con.setAccessible(true);
Object obj = con.newInstance();
try {
Field field = staticClazz.getDeclaredField("j");
field.setAccessible(true);
System.out.println(field.get(obj));
} catch (NoSuchFieldException e) {
System.out.println("get field failed");
e.printStackTrace();
}
}
}
同样,对私有静态内部类进行测试。打印结果:
com.prac.outer$staticInner
2