反射

目录

介绍

获得Class对象

反射构造函数

反射方法

反射字段

反射泛型

反射注解

反射参数

参考


介绍

反射能够使你在运行时检查类、接口、字段、泛型、注解、方法和方法参数的信息,不用在编译时知道类、方法的名字。也能通过反射实例化类,调用方法和设置、访问字段值。在jdk8中,甚至可以获得参数名

类的信息由Class类提供,然后可以通过Class对象获得类的所有信息,包括类名、类修饰符、包名、父类、被实现的接口、构造函数、方法、字段和注解。

很多信息都由java.lang.reflect包中对应的类来提供,比如Array对应数组,可以动态创建数组,访问数组;Constructor对应构造函数,可以获得构造函数的信息,创建对象;Field对应字段,这些字段可以来源于类和接口,可以访问和设置字段的值;Method对应方法,可以获得方法的信息,调用方法;Parameter对应参数。这些类都差不多,都可以获得相关的信息,也可以获得之上的注解信息。

获得Class对象

使用反射的第一步就是获得Class对象。如果在编译时已经知道了类型信息,可以直接通过类名获得Class对象:

Class myObjectClass = MyObject.class

如果编译时不知道,可以在运行时通过完整的类名来获得Class对象:

Class aClass=Class.forName("packageName.ClassName");
//比如Class.forName("com.xxx.MyClass");

对于数组的Class对象的获取也可以通过上述两种方法:

	public static void main(String[] arg) throws ClassNotFoundException  {
		Class class1=int[].class;
		Class class2=String[].class;
		Class class3=Class.forName("[I");
		Class class4=Class.forName("[Ljava.lang.String;");
		System.out.println(class1.getName());
		System.out.println(class2.getName());
		System.out.println(class3.getName());
		System.out.println(class4.getName());
	}

结果:

[I
[Ljava.lang.String;
[I
[Ljava.lang.String;

在使用Class.forName时注意写法,有关内容请参考:java中数组是对象吗?Java Reflection - Arrays

获得Class对象后可以获得很多的信息,也可以实例化对象,但是只会调用无参构造函数。

App app=App.class.newInstance();

反射构造函数

获得Class之后就可以反射构造函数,得到Constructor类,通过这个类就可以实例化其他的构造函数。

public class App 
{
	public static void main(String[] arg) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException  {
		Constructor<?> constructor=C.class.getConstructor(int.class); 
		constructor.newInstance(3);
	}
}
class C{
	public C(){System.out.println("C()");}
	public C(int c){System.out.println("C("+c+")");}
}

只有public构造函数才能通过getConstructor来获取。但是可以通过getDeclaredConstructor函数访问其他访问权限的构造函数:

public class App 
{
	public static void main(String[] arg) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException  {
        //通过getDeclared...可以获得非public的构造函数
		Constructor<?> constructor=C.class.getDeclaredConstructor(int.class); 
        //关闭访问权限检查,因此可以通过私有构造函数创建对象
		constructor.setAccessible(true);
        //通过私有构造函数创建对象
		constructor.newInstance(3);
	}
}
class C{
	public C(){System.out.println("C()");}
	private C(int c){System.out.println("C("+c+")");}
}

反射方法

同样,可Constructor类似,通过Class对象获得Method对象,然后通过Method对象可以获得相关的信息,和调用方法。通过getMethod只能获得public类型的方法,通过setAccessible可以使之调用该私有方法。下面直接给出代码:

public class App 
{
	public static void main(String[] arg) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
		Method method=App.class.getMethod("method", null);
		method.invoke(new App(), null);
		
		Method method2=App.class.getDeclaredMethod("method1", int.class);
		//不进行访问权限检查
		method2.setAccessible(true);
		method2.invoke(new App(), 3333);
	}

    public void method() {
    	System.out.println("call method");
    }
    private void method1(int a) {
    	System.out.println("call private method1("+a+")");
    }
}

输出:

call method
call private method1(3333)

反射字段

可以获得字段的信息,即使是私有的也可以获得,在mybatis和spring框架中都可以获得私有字段。

与上述类似,反射就是这么简单:

public class App 
{
	public List<String> afield; 
	A<String,Double> a;
	public static void main(String[] arg) throws Exception{
		//创建一个对象,通过Field可以访问字段
		App app=new App();
		app.afield=new ArrayList<>();
		//获得字段对应的Field
		Field field=App.class.getField("afield");
		//通过field添加数据
		((List<String>)field.get(app)).add("aaaa");
		//打印数据验证
		System.out.println(Arrays.toString(app.afield.toArray()));
		//获得该字段的名字
		System.out.println(field.getName());
		//获得该地段的泛型信息
		ParameterizedType parameterizedType=(ParameterizedType)field.getGenericType();
		//打印类型参数的类型
		System.out.println(parameterizedType.getActualTypeArguments()[0].getTypeName());
	}
}

输出:

[aaaa]
afield
java.lang.String

也许会问,泛型信息不是在编译后被擦除了吗?怎么还能获取泛型信息?其实运行时确实还是保存着泛型信息,下面介绍。

反射泛型

很多文章说,编译时会将泛型信息擦除。其实,这并不完全正确,运行时,在类的字段、方法的参数和返回类型中仍然保留泛型,但是你不能通过被引用的对象来获取泛型信息。下面给出具体代码:

public class App 
{
	A<String,Double> a;
	public static void main(String[] arg) throws Exception{
		//因为非public,因此使用getDeclare...方法获得字段
		Field field=App.class.getDeclaredField("a");
        //获得泛型类型信息
		ParameterizedType parameterizedType=(ParameterizedType)field.getGenericType();
        //获得泛型中每个类型参数
		Type[] types=parameterizedType.getActualTypeArguments();
        //打印所有类型参数的名字
		for(Type t:types) {
			Class c=(Class)t;
			System.out.println(c.getName());
		}
        //打印字段的声明,包含泛型声明
		System.out.println(field.toGenericString());
	}
}
class A<T,Y>{
	
}

输出:

java.lang.String
java.lang.Double
com.luo.my.test.A<java.lang.String, java.lang.Double> com.luo.my.test.App.a

反射注解

神奇,通过注解我们也可以获得注解信息,但是这要求注解必须在运行时存在(也就是被@Retention(RetentionPolicy.RUNTIME注解)。同样获取注解有两种方法:getAnnotation和getDeclaredAnnotation。如果你查看java.lang.reflect.AccessibleObject的源码,可以发现,其实他们没啥区别,,,就是说使用哪个都行。下面给出发射注解的代码:

public class App 
{
	public static void main(String[] arg) throws Exception{
		Annotation annotation=A.class.getAnnotation(MyAnnotation.class);
		MyAnnotation myAnnotation=(MyAnnotation)annotation;
		System.out.println(myAnnotation.name());
	}
}
@MyAnnotation(name="A")
class A<T,Y>{
}
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
	public String name();
}

反射参数

Parameter代表参数,可以获得参数类型、参数上的注解等信息。我们知道,代码编译生成的字节码是不包含方法名的信息,因此jdk7之前不能反射方法名,甚至jdk7连Parameter这个类也没有。但是jdk8后,有了Parameter类,它的getName方法可以获得方法名,但是获得的是合成的参数名argN,因为字节码中还是没有参数名信息。然而jdk8后编译器也提供了-parameter来将参数名编译到字节码中,因此可以获得正确的参数名。

//javac编译时加上参数-parameter才能得到真正的方法名。
Class<String> clz = String.class;
for (Method m : clz.getDeclaredMethods()) {
   System.err.println(m.getName());
   for (Parameter p : m.getParameters()) {
      System.err.println("  " + p.getName());
   }
}

在jdk8之前没有Parameter,也就不能通过Parameter来获得方法名,但是可以通过ASM 或BCEL来获得参数名,即使使用默认的编译设置(通过在默认设置编译源码时添加了debug信息来获得参数名的,具体如何实现,不晓滴)。而jdk8后可以通过Parameter来获得参数名,但是需要设置-parameter参数,然而有人提供了Paranamer这个库来在默认设置下获取方法名。据说实现是通过注解来实现的,注解可以影响编译行为,在编译时加入一个静态方法来获得参数名。即使我说的不对,然而也可以解释了为什么springMVC可以不通过注解就获得控制器参数名的原因了。关于参数名,可参考:Java 8 Named Method Parameters

参考

http://tutorials.jenkov.com/java-reflection/index.html

参数名:https://www.beyondjava.net/reading-java-8-method-parameter-named-reflection

https://stackoverflow.com/questions/21455403/how-to-get-method-parameter-names-in-java-8-using-reflection

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值