Java的反射

1、什么是反射?

Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。所谓的反射,实际上是获取到类的字节码.class文件,再通过Class对象获取类的方法和属性。

2、反射的四种方式

 第一种:通过Object的getClass()方法获取Class对象,如

第二种:通过类名.class来获取Class对象,如

第三种:通过Class类的public static Class<?> forName(String className);方法来获取Class对象,如

第四种:通过Class类的public static Class<?> forName(String name, boolean initialize, ClassLoader loader);来获取Class对象,其中的boolean initialize代表着是否初始化,在第三种方法中,默认是会执行类的静态代码块的,即加载class对象的时候就会执行类中的静态代码块, 如果想加载类但不想加载其静态代码块,那么可以将该布尔值设置为false,如:

三种方式相比,第一种已经获取到了该类的实例对象,无意义;第二种方式需要导包;第三种只需要知道该类的全路径即可,即使没有该类,也能通过编译,只是获取不到该Class对象,需要注意的是,第三种方式会抛出ClassNotFoundException。更推荐使用第三种方式,实际上我们平时写代码很少用到反射技术,但是在我们使用的一些主流框架中反射技术应用是非常广泛的。

3、反射获取类的属性及方法

在此处,我们提供一个测试类StudentReflection,该类提供了四种权限的属性、有参和无参的构造、四种权限的方法,下面详细描述测试情况,最终得出结论。

public class StudentReflection {
	/*参数*/
	public String sex;
	int age;
	protected String name;
	private int id;
	
	/*全参构造*/
	private StudentReflection(int id, String name, String sex, int age) {
		super();
		this.id = id;
		this.name = name;
		this.sex = sex;
		this.age = age;
	}
	/*无参构造*/
	public StudentReflection() {
		
	}
	/*公用方法*/
	public void pubMethod() {
		System.out.println("公用方法");
	}
	/*私有方法*/
	private void privMethod() {
		System.out.println("私有方法");
	}
	
	/*受保护的方法*/
	protected void proMethod() {
		System.out.println("受保护的方法");
	}
	
	/*默认权限的的方法*/
	void defMethod() {
		System.out.println("默认权限的的方法");
	}
	/*get和set方法*/
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	
}

a、属性的获取

首先,我们尝试获取所有参数,先通过getDeclaredFields()方法来获取到所有已声明的参数

输出结果如下:

可以看到,getDeclaredFields()方法可以获取到该类声明的任何权限的属性。

再尝试getFileds()方法

输出结果如下:

那么,getFileds()方法仅可以获取到被声明为public权限的属性。

然后,再进行单个属性的获取,先尝试getDeclaredField(String name)方法

测试结果如下:

可以看到,getDeclaredField(String name)可以获取到所有已声明的参数

再尝试getFiled(String name);方法

 运行结果为,除了sex属性外的其他三个属性,都会报错

可以看到,getFiled(String name);只能获取到公有域

最后,我们对获取到的单个属性进行赋值操作,赋值需要通过Field类的set(Object obj, Object value)方法来实现,其中第一个obj是反射对应的类对象,第二个为对应对应的Field需要设置的值。其中,obj对象可以通过class对象获取该类的构造函数,再调用newInstance()方法来获取实例化对象,如下:

赋值代码如下

赋值结果为:

Exception in thread "main" java.lang.IllegalAccessException: Class com.java.reflection.ReflectionTest can not access a member of class com.java.reflection.StudentReflection with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.set(Field.java:761)
	at com.java.reflection.ReflectionTest.main(ReflectionTest.java:52)

可以看到,报错信息的大致意思是,测试类不能访问被反射类的private修饰的属性,此处需要使用setAccessible(boolean flag)来设置对应的private属性是否可被访问。

之后,便可成功赋值。

对于通过getField方法获取到的filed对象,不会存在上述问题,直接调用set方法赋值即可。

在赋值完成后,要获取到属性的值,可以直接通过obj对象,强转为该类的类型,再调用get和set方法来获取。也可以通过Field的get(Object obj)方法来获取值,获取到的是一个Object对象,实际的类型为属性的类型。代码及运行结果如下:

b、获取及调用构造方法

通过上面对属性的操作已经能够看出,当获取类的属性、方法时,如果方法以declared开头,那么获取到的是无关权限的方法或者属性,否则只能获取到公有的信息。

获取构造方法代码如下,其中当构造方法有参数时,参数是一个以参数的class对象为元素的Class数组

运行结果如下:

Exception in thread "main" java.lang.NoSuchMethodException: com.java.reflection.StudentReflection.<init>(int, java.lang.String, java.lang.String, int)
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getConstructor(Class.java:1825)
	at com.java.reflection.ReflectionTest.main(ReflectionTest.java:76)

第76行,当通过getConstructor来获取private修饰的构造参数时,会抛出没有这个方法的异常。

对构造方法的调用,使用newInstance();方法即可,对于无参构造,方法中可以不传参或者传null,对于有参的构造,需要传入参数对应的数组。如下:

输出结果如下:

c、获取及调用方法

获取普通方法的方式与获取构造方法的方式类似,同样,以declared开头的方法,可以获取所有已声明的方法,否则只能获取到公用的方法。代码如下:

获取到方法后,调用时,通过method对象来调用invoke(Object obj, Object... args)方法即可,其中obj为该类的实例化对象,后面的为不定长参数。如下:

运行结果如下:

此外,反射还可以调用类的main方法,与获取和调用普通方法无异,需要注意的是,main方法的参数为java.lang.String[]数组。String的包不可取错。

4、总结

通过上面的测试,我们可以看出,要使用反射机制,首先要获取到类的Class对象,最佳方式是通过Class.forName来获取。然后,对于类的属性和方法,当调用以declared开头的方法来获取时,获取到的均为所有的已声明的属性或方法,否则只能获取到公用的部分,对于declared开头的方法获取到的属性或方法,如果要进行调用或者使用,且其为private修饰,必须设置其可访问之后,再进行调用。

另外,需要注意的是,对于构造方法和普通方法的有参方法的获取及调用,格式有所不同,代码如下:获取有参构造:

调用有参构造:

获取有参方法:

调用有参方法:

最后,附上测试类的代码

package com.java.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionTest {

	public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
		/**
		 * 	反射获取类的Class对象的三种方式
		 * 1、通过类的实例对象调用getClass();
		 * 2、通过类名直接调用.class来获取Class对象,需要导包
		 * 3、通过类的全路径,Class.forName("全路径");来获取类的Class对象,此时会抛出一个异常,ClassNotFoundException
		 */
		/*第一种*/
		StudentReflection sr = new StudentReflection();
		Class<? extends StudentReflection> c1 = sr.getClass();
		System.out.println(c1.getName());
		/*第二种*/
		Class<StudentReflection> c2 = StudentReflection.class;
		System.out.println(c2.getName());
		/*第三种(推荐使用)*/
		Class<?> c3 = Class.forName("com.java.reflection.StudentReflection");
		System.out.println(c3.getName());
		
		/**
		 * 获取属性及操作属性
		 */
		Field[] declaredFields = c3.getDeclaredFields();
		for (Field field : declaredFields) {
			System.out.println(field);
		}
		
		Field[] fields = c3.getFields();
		for (Field field : fields) {
			System.out.println(field);
		}
		
		Field declaredField = c3.getDeclaredField("sex");
		Field declaredField2 = c3.getDeclaredField("age");
		Field declaredField3 = c3.getDeclaredField("name");
		Field declaredField4 = c3.getDeclaredField("id");
		System.out.println(declaredField);
		System.out.println(declaredField2);
		System.out.println(declaredField3);
		System.out.println(declaredField4);
		
		declaredField4.setAccessible(true);//设置private属性可访问
		Object obj = c3.getConstructor().newInstance();//获取构造方法来获取类对象
		/*设置属性值*/
		declaredField.set(obj, "男");
		declaredField2.set(obj, 10);
		declaredField3.set(obj, "张三");
		declaredField4.set(obj, 1);
		
		Field field = c3.getField("sex");
//		c3.getField("age");
//		c3.getField("name");
//		c3.getField("id");
		/*设置属性值*/
		field.set(obj, "女");
		/*获取属性值*/
		Object object = field.get(obj);
		System.out.println(object);
		
		
		/**
		 * 获取构造方法,调用构造方法
		 */
		Constructor<?>[] declaredConstructors = c3.getDeclaredConstructors();//获取所有构造
		Constructor<?> declaredConstructors2 = c3.getDeclaredConstructor(null);//获取所有无参构造
		Constructor<?> declaredConstructor = c3.getDeclaredConstructor(new Class[]{int.class,String.class,String.class,int.class});//获取指定有参构造
		
		Constructor<?> constructor = c3.getConstructor(null);//获取无参公有构造
//		Constructor<?> constructor1 = c3.getConstructor(new Class[]{int.class,String.class,String.class,int.class});//获取指定有参构造
		Constructor<?>[] constructors = c3.getConstructors();//获取所有公有构造
		Object initargs = constructor.newInstance(null);//对无参构造方法的调用
		
		declaredConstructor.setAccessible(true);
		StudentReflection newInstance = (StudentReflection)declaredConstructor.newInstance(new Object[] {1,"张三","男",10});//对有参且私有的构造方法的调用
		String name = newInstance.getName();
		System.out.println(name);
		
		/**
		 * 获取方法,调用方法
		 */
		Method[] declaredMethods = c3.getDeclaredMethods();//获取所有声明的方法
		Method declaredMethod = c3.getDeclaredMethod("privMethod", null);//获取名为“privMethod”的无参方法
		Method declaredMethod2 = c3.getDeclaredMethod("proMethod", String.class,int.class);//获取名为"proMethod",参数类型为String,int的方法
		
		Method method = c3.getMethod("pubMethod", null);//获取公用的名为“pubMethod”的无参方法
		Method[] methods = c3.getMethods();//获取所有公用的方法
		
		declaredMethod2.invoke(obj, "测试调用方法",5);
	}

}

5、new和newInstance()的区别

 首先,当我们new一个对象时,JVM做了以下的事情

  1. 若对应类的class文件未加载,加载对应的class文件,进行类的链接、初始化操作。
  2. 根据方法区中的类信息向堆内存申请空间。
  3. 调用构造函数。

  对于第一步中的类加载,又可分为三步:

  1. 装载
  2. 连接
  3. 初始化

    其中装载阶段又三个基本动作组成:

  1.     通过类型的完全限定名,产生一个代表该类型的二进制数据流
  2.     解析这个二进制数据流为方法区内的内部数据结
  3.     构创建一个表示该类型的java.lang.Class类的实例

    另外如果一个类装载器在预先装载的时遇到缺失或错误的class文件,它需要等到程序首次主动使用该类时才报告错误。

    连接阶段又分为三部分:

  1. 验证,确认类型符合Java语言的语义,检查各个类之间的二进制兼容性(比如final的类不用拥有子类等),另外还需要进行符号引用的验证。
  2. 准备,Java虚拟机为类变量分配内存,设置默认初始值。
  3. 解析(可选的) ,在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程。

 初始化阶段,当一个类被主动使用时,Java虚拟就会对其初始化,一般加载类时都会进行初始化,如下五种情况为主动使用:

  1. 遇到new, getstatic, putstatic 或者 invokestatic 4条字节码指令时,如果类没有进行过初始化,则需要先进行初始化。这些场景包括:使用new关键字实例化对象,读取或者设置一个类的静态字段以及调用一个类的静态方法的时候。

  2. 使用java.lang.reflect包的方法进行反射调用的时候,如果类没有初始化,需要进行初始化。

  3. 当初始化一个类的时候发现其父类还没有初始化,需要对父类进行初始化。

  4. JVM启动时,用户指定的包含main方法的那个类,需要首先进行初始化。

  5. JDK1.7中动态语言的支持,解析java.lang.invoke.MethodHandle的结果为REF_getStatic, REF_putStatic, REF_invokeStatic方法的句柄时,对应的类没有初始化的时候。

当类初始化时,会执行类中的静态代码块。

而当我们使用Class.forName,再newInstance时,将new时JVM的操作分为了两步。首先forName时,仅加载并初始化该类,然后如果需要实例化该对象,再调用newInstance方法。在forName时,会初始化类,此时会执行类中的静态代码块,这也是为什么JDBC中,通常加载MySql驱动时,只需要Class.forName("com.mysql.jdbc.Driver")即可,这是因为该Driver类的静态代码块中,已经包含了如下语句:

 

知道了new和Instance时JVM的实际操作,我们再来看二者的区别(引用自https://www.cnblogs.com/w-wfy/p/6265965.html):

用newInstance与用new是区别的,区别在于创建对象的方式不一样,前者是使用类加载机制,那么为什么会有两种创建对象方式?这个就要从可伸缩、可扩展,可重用等软件思想上解释了。Java中工厂模式经常使用newInstance来创建对象,因此从为什么要使用工厂模式上也可以找到具体答案。
例如:
Class c = Class.forName(“A”);
factory = (AInterface)c.newInstance();
其中AInterface是A的接口,如果下面这样写,你可能会理解:
String className = “A”;
Class c = Class.forName(className);
factory = (AInterface)c.newInstance();
进一步,如果下面写,你可能会理解:
String className = readfromXMlConfig;//从xml 配置文件中获得字符串
Class c = Class.forName(className);factory = (AInterface)c.newInstance();
上面代码就消灭了A类名称,优点:无论A类怎么变化,上述代码不变,甚至可以更换A的兄弟类B , C , D….等,只要他们继承Ainterface就可以。
从jvm的角度看,我们使用new的时候,这个要new的类可以没有加载;
但是使用newInstance时候,就必须保证:1、这个类已经加载;2、这个类已经连接了。而完成上面两个步骤的正是class的静态方法forName()方法,这个静态方法调用了启动类加载器(就是加载javaAPI的那个加载器)。
有了上面jvm上的理解,那么我们可以这样说,newInstance实际上是把new这个方式分解为两步,即,首先调用class的加载方法加载某个类,然后实例化。
这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了我们降耦的手段。

[补充:]
newInstance: 弱类型。低效率。
new: 强类型。相对高效。能调用任何public构造。
newInstance()是实现IOC、反射、依赖倒置 等技术方法的必然选择,new 只能实现具体类的实例化,不适合于接口编程。类里面就是通过这个类的默认构造函数构建了一个对象,如果没有默认构造函数就抛出InstantiationException, 如果没有访问默认构造函数的权限就抛出IllegalAccessException
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值