`
yuanlanjun
  • 浏览: 1187309 次
文章分类
社区版块
存档分类
最新评论

java 运行时类型识别(RTTI) - 2 - 反射

 
阅读更多

java 运行时类型识别(RTTI) - 1 - Class与instanceof

java 运行时类型识别(RTTI) - 2 - 反射


本文将叙述如何运行时查看类信息,其中包括变量,方法,以及通过反射修改变量,执行方法等

包括如何反射匿名内部类及如何执行其方法,但是笔者强烈不建议这么做,这里只是演示反射而已


下面是一个测试类

package reflect;

public class Outer {

	static{
		System.out.println("Testing..");
	}
	
	TestInterface ti = new TestInterface() {
		public String test() {
			return "this is ti";
		}
	};
	
	public Outer(String name){
		System.out.println("Outer");
	}

	public String toString(){
		System.out.println("Outer toString");
		return new TestInterface() {
			public String test() {
				return "this is a test!";
			}
		}.test();
	}
	
	@SuppressWarnings("unused")
	private void privateMethod(){
		System.out.println("privateMethod");
	}
	
	protected void protectedMethod(){
		System.out.println("protectedMethod");
	}
	
	void packageMethod(){
		System.out.println("packageMethod");
	}
	
	public static void staticMethod(){
		System.out.println("staticMethod");
	}
	
	public interface TestInterface {
		public String test();
	}
	
	public static class StaticInner {
		private static final String TAG = "StaticInnerTAG";
		public StaticInner(){
			System.out.println("StaticInner");
		}
		
		public String toString(){
			System.out.println("StaticInner toString");
			return TAG;
		}
	}
	
	private class Inner{
		String name;
		public Inner(){
			System.out.println("Inner");
		}
		
		public Inner(String name){
			System.out.println("reflect.Outer.Inner.Inner(String name)");
			this.name = name;
		}
	}
}

其中包括了普通内部类,静态内部类,内部匿名类,接口等

其中外部类Outer没有默认构造器,String reflect.Outer.Inner.getName()是private的……


外部类

Constructor,Method,Class.forName,newInstance

下面来看如何对上述的类进行反射

首先先看外部类Outer

public static void main(String[] args) {
	try {
		Class<?> outer = Class.forName("reflect.Outer");
		outer = Outer.class;
		Constructor<?>[] constructors = outer.getConstructors();
		for (Constructor<?> constructor : constructors) {
			Class<?>[] types = constructor.getParameterTypes();
			System.out.println(constructor.getName() + "(" + arrayToString(types) + ")");
		}
		
		Constructor<?> outerConstructor = outer.getConstructor(String.class);
		Outer outerInstance = (Outer) outerConstructor.newInstance("a outer");
		System.out.println(outerInstance);
	} catch (ClassNotFoundException e) {
		e.printStackTrace();
	} catch (SecurityException e) {
		e.printStackTrace();
	} catch (NoSuchMethodException e) {
		e.printStackTrace();
	} catch (IllegalArgumentException e) {
		e.printStackTrace();
	} catch (InstantiationException e) {
		e.printStackTrace();
	} catch (IllegalAccessException e) {
		e.printStackTrace();
	} catch (InvocationTargetException e) {
		e.printStackTrace();
	}
}

public static String arrayToString(Class<?>[] classes){
	StringBuilder stringBuilder = new StringBuilder();
	for (Class<?> type : classes) {
		stringBuilder.append(type.getCanonicalName());
		stringBuilder.append(",");
	}
	if(classes.length > 0){
		stringBuilder.deleteCharAt(stringBuilder.length() - 1);
	}
	return stringBuilder.toString();
}

打印结果

Testing..
reflect.Outer(java.lang.String)
Outer
Outer toString
this is a test!

下面简单分析下

Class<?> outer = Class.forName("reflect.Outer");
//outer = Outer.class;

我们对类reflect.Outer进行反射,这里使用Outer.class也可以获得Class对象

之前说过使用类字面量不会初始化该类,而Class.forName则会初始化,当使用Outer.class时候log如下

reflect.Outer(java.lang.String)
Testing..
Outer
Outer toString
this is a test!

顺序改变了,打印的时候Testing..实际是下面这句触发的

Outer outerInstance = (Outer) outerConstructor.newInstance("a outer");

这时候加载Outer这个类,然后进行初始化


接下来我们查看了Outer的所有构造器Constructor,并打印出所需要的参数(与普通方法区别开来)

构造器只有一个,参数是String

如何执行这个带有String作为参数的构造器来返回一个Outer实例

Constructor<?> outerConstructor = outer.getConstructor(String.class);
Outer outerInstance = (Outer) outerConstructor.newInstance("a outer");
System.out.println(outerInstance);

我们先取得我们需要的Constructor,然后,Constructor提供了newInstance方法,这样就可以获得Outer实例

最后打印实例,调用了toString函数

如果你尝试调用

outer.newInstance();

则会看到如下异常

java.lang.InstantiationException: reflect.Outer

at java.lang.Class.newInstance0(Class.java:357)

at java.lang.Class.newInstance(Class.java:325)

at reflect.Test.main(Test.java:30)


这是因为Outer没有提供默认构造器

在获得Constructor时其实是有下面两个选择的,至于区别,我们放在Method里说

outer.getConstructors();
outer.getDeclaredConstructors()

现在看看如何获得类的方法,顺便看看Declared的作用

不如直接打印出来,这样比较直观

Method[] methods = outer.getMethods();
for (Method method : methods) {
	Class<?>[] types = method.getParameterTypes();
	System.out.println(method.getReturnType().getName() + " " + outer.getName() + "." + method.getName() + "(" + arrayToString(types) + ")");
}
System.out.println("--------------------------------------");
methods = outer.getDeclaredMethods();
for (Method method : methods) {
	Class<?>[] types = method.getParameterTypes();
	System.out.println(method.getReturnType().getName() + " " + outer.getName() + "." + method.getName() + "(" + arrayToString(types) + ")");
}Class<?> outer = Outer.class;

结果如下

void reflect.Outer.staticMethod()
java.lang.String reflect.Outer.toString()
void reflect.Outer.wait(long)
void reflect.Outer.wait()
void reflect.Outer.wait(long,int)
boolean reflect.Outer.equals(java.lang.Object)
int reflect.Outer.hashCode()
java.lang.Class reflect.Outer.getClass()
void reflect.Outer.notify()
void reflect.Outer.notifyAll()
--------------------------------------
void reflect.Outer.privateMethod()
void reflect.Outer.protectedMethod()
void reflect.Outer.packageMethod()
void reflect.Outer.staticMethod()
java.lang.String reflect.Outer.toString()

调用getMethods时,Outer的方法只打印出了toString,其余结果均为父类Object的公有方法

调用getDeclaredMethods时,则只打印出在Outer中声明定义的方法

他们均打印出了公共的静态方法

所有带Declared和不带Declared的成对的方法都和上面的类似


如何利用反射执行一个方法,下面以void reflect.Outer.privateMethod()为例

Method method = outer.getDeclaredMethod("privateMethod");
method.setAccessible(true);
method.invoke(outerInstance, (Object[])null);
method.setAccessible(false);

首先privateMethod是私有方法,所以为了能得到对应的Method对象,我们需要调用getDeclaredMethod


为了能访问私有方法,我们需要进行setAccessible(true)的设置

然后需要有个对象来执行,我们选择刚才的outerInstance

这时控制台输出

privateMethod

干完活后,恢复其设置setAccessible(false)

下面是静态方法的执行

Method method = outer.getDeclaredMethod("staticMethod");
method.setAccessible(true);
method.invoke(Outer.class, (Object[])null);
method.setAccessible(false);

ps:使用method.invoke(outerInstance, (Object[])null);也能执行,但是不该这样


内部类

isInterface,Field,isAccessible,setAccessible,getModifiers,Modifier.toString()

下面看看内部类的反射

Class<?>[] classes = outer.getDeclaredClasses();
for (Class<?> clazz : classes) {
	if(clazz.isInterface()){
		System.out.println("interface : " + clazz.getName());
	}else{
		System.out.println("class : " + clazz.getName());
	}
}

输出

class : reflect.Outer$Inner
class : reflect.Outer$StaticInner
interface : reflect.Outer$TestInterface

看来反射提供的api功能还是很全的


下面是如何反射静态内部类

try {
	Class<StaticInner> staticInner = StaticInner.class;
	StaticInner staticInnerInstance = staticInner.newInstance();
}catch (SecurityException e) {
	e.printStackTrace();
} catch (IllegalArgumentException e) {
	e.printStackTrace();
} catch (InstantiationException e) {
	e.printStackTrace();
} catch (IllegalAccessException e) {
	e.printStackTrace();
}

也可以使用Class.forName方式,如果StaticInner是不可见的,那么上面的代码则无法编译,下面看看Class.forName

try {
	Class<StaticInner> staticInner = (Class<StaticInner>) Class.forName("reflect.Outer.StaticInner");
	StaticInner staticInnerInstance = staticInner.newInstance();
}catch (SecurityException e) {
	e.printStackTrace();
} catch (IllegalArgumentException e) {
	e.printStackTrace();
} catch (InstantiationException e) {
	e.printStackTrace();
} catch (IllegalAccessException e) {
	e.printStackTrace();
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}

如果你和上面的写法一样,那么很不幸,运行时会得到如下错误

java.lang.ClassNotFoundException: reflect.Outer.StaticInner

at java.net.URLClassLoader$1.run(URLClassLoader.java:217)

at java.security.AccessController.doPrivileged(Native Method)

at java.net.URLClassLoader.findClass(URLClassLoader.java:205)

at java.lang.ClassLoader.loadClass(ClassLoader.java:321)

at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)

at java.lang.ClassLoader.loadClass(ClassLoader.java:266)

at java.lang.Class.forName0(Native Method)

at java.lang.Class.forName(Class.java:186)

at reflect.Test.main(Test.java:11)


我们去bin目录下可以发现这样的文件Outer$StaticInner.class

反射代码只需做如下改动

Class<StaticInner> staticInner = (Class<StaticInner>) Class.forName("reflect.Outer$StaticInner");

.变成了$

这里newInstance之所以能执行,是因为StaticInner有默认构造器

下面看看StaticInner中生命了哪些变量

Class<StaticInner> staticInner = StaticInner.class;
Field[] fields = staticInner.getDeclaredFields();
for (Field field : fields) {
	Class<?> type = field.getType();
	field.setAccessible(true);
	System.out.println(Modifier.toString(field.getModifiers()) + " " + type.getName() + " " + field.getName());
}

StaticInner StaticInnerInstance = staticInner.newInstance();
Field field = staticInner.getDeclaredField("TAG");
boolean accessible = field.isAccessible();
if(!accessible){
	field.setAccessible(true);
}
System.out.println(field.get(StaticInnerInstance));
field.set(StaticInnerInstance, "TAG");
System.out.println(field.get(StaticInnerInstance));
if(!accessible){
	field.setAccessible(false);
}

结果如下

private java.lang.String TAG
StaticInner
StaticInnerTAG
TAG

StaticInner只声明了一个变量,名字为TAG,类型为String

即使是private变量,设置setAccessible(true)之后也能改变其值

普通内部类的反射和上面类似


最后说下匿名内部类

匿名内部类的作用是仅仅使用一次,所以也没有必要进行反射,实际上也很难进行反射

例子中有两个匿名内部类

TestInterface ti = new TestInterface() {
	public String test() {
		return "this is ti";
	}
};

public String toString(){
	System.out.println("Outer toString");
	return new TestInterface() {
		public String test() {
			return "this is a test!";
		}
	}.test();
}

在bin目录下发现有Outer$1.class和Outer$2.class


getDeclaredClasses发现也没有匿名内部类的信息


try {
	Class<Outer> outer = Outer.class;
	Class<?>[] classes = outer.getDeclaredClasses();
	for (Class<?> clazz : classes) {
		System.out.println(clazz.getName());
	}
}catch (SecurityException e) {
	e.printStackTrace();
} catch (IllegalArgumentException e) {
	e.printStackTrace();
}

结果

reflect.Outer$Inner
reflect.Outer$StaticInner
reflect.Outer$TestInterface

只有这三个,包括一个接口

如果非要对匿名内部类进行反射,也不是不可能

下面演示如何反射一个匿名内部类,仅仅是演示,没有任何意义,也不推荐这么做

我们以上面的toString方法里面的内部类做例子

try {
	Class<?> clazz = Class.forName("reflect.Outer$2");
	Constructor<?>[] constructors = clazz.getDeclaredConstructors();
	constructors[0].setAccessible(true);
	Class<?>[] types = constructors[0].getParameterTypes();
	for (Class<?> type : types) {
		System.out.println("Parameter Types:" + type.getName());
	}
	TestInterface testInterface = (TestInterface) constructors[0].newInstance(new Outer(""));
	Method[] methods = clazz.getDeclaredMethods();
	for (Method method : methods) {
		Class<?>[] parameterTypes = method.getParameterTypes();
		System.out.println(method.getName() + "(" + arrayToString(parameterTypes) + ")");
	}
	Method method = clazz.getDeclaredMethod("test");
	String string = (String)method.invoke(testInterface);
	System.out.println(string);
}catch (SecurityException e) {
	e.printStackTrace();
} catch (IllegalArgumentException e) {
	e.printStackTrace();
} catch (ClassNotFoundException e) {
	e.printStackTrace();
} catch (IllegalAccessException e) {
	e.printStackTrace();
} catch (InvocationTargetException e) {
	e.printStackTrace();
} catch (InstantiationException e) {
	e.printStackTrace();
} catch (NoSuchMethodException e) {
	e.printStackTrace();
}

结果如下

Parameter Types:reflect.Outer
Testing..
Outer
test()
this is a test!

下面看看代码都做了什么,依次说明

首先获取Outer$2的Class

然后获取其构造器,查看所需要的参数,我们发现内部匿名类也需要一个外部类的支持

然后我们newInstance生成实例,声明为一个接口,由于要传入外部类,这里我们直接new一个,所以会初始化外部类,打印第2,3句

接下来查看内部类的方法,找到test(),这是第4句打印的结果

最后用实例化好的内部匿名类执行这个方法,所以打印出了最后一句


执行第一个匿名内部类的方法代码简单些,同样不推荐这么做

try {
	Class<Outer> clazz = Outer.class;
	Outer o = new Outer("");
	//System.out.println(o.ti.test());
	Field field = clazz.getDeclaredField("ti");
	TestInterface testInterface = (TestInterface) field.get(o);
	System.out.println(testInterface.test());
}catch (SecurityException e) {
	e.printStackTrace();
} catch (IllegalArgumentException e) {
	e.printStackTrace();
} catch (NoSuchFieldException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
} catch (IllegalAccessException e) {
	e.printStackTrace();
}

为了演示反射,这里没有使用System.out.println(o.ti.test());


至此,反射演示结束

由于写本篇blog的时候多次修改代码,所以上面可能有些地方不太一致,如果发现,敬请指正

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics