反射-reflect

反射-reflect

1、引入

  • 到目前为止我们已经知道了当一个class文件被类加载器加载进内存时,会在JVM中将形成一份描述该class文件结构的元信息对象Class,通过该对象JVM就可以获知class文件的结构信息:如构造器,字段,方法等。
  • 那么,问题来了:
    • Class类到底是什么?
    • 我们该如何通过API去创建Class类的实例对象?
    • 又该如何去获取Class对象中的构造器,字段,方法这些信息呢?
    • 这就是接下来的反射要研究的内容。

2、Class类理解

由面向对象引发的思考:

在学习面向对象阶段的课程中我们了解到Java是一门面向对象的编程语言,并且在Java中,万物皆对象!(比如:基本数据类型也都有相应的包装类),那么问题来了:“类”这类事物是不是对象呢?又该如何表示呢?

既然万物皆对象,那么类名、构造器、字段、方法等这些信息当然也需要封装成一个对象,这就是Class类、Constructor类、Field类、Method类。

而通过Class类、Constructor类、Method类、Field类等类的实例对象就可以得相应的信息,甚至可以不用new关键字就创建一个实例,并设置或获取字段的值,执行对象中的方法,这就是反射技术。

在这里插入图片描述

通过上面的画图分析我们可以用自己的话总结一下什么是反射:

*反射就是在运行时期,动态的获取类中成员信息(构造器,字段,方法)的过程!*

3、获取类的字节码对象(Class类型对象)的三种方式

要想获取和操作类中的内容,首先要获取类的字节码对象

这些对象(类的字节码对象,也称为.class对象),都是Class类型的对象

获取字节码对象的方式:

1、对象名.getClass():返回的是某个引用指向的具体对象所属的运行是类,的字节码对象。获取到的是那个真正用来创建对象的子类的字节码对象。

2、类名.class:如果已经有了类名,可以通过.class的方式获取这个类的字节码对象。

3、通过Class.forName(String className):Class类中的一个静态方法,可以根据一个类的全类名,动态的加载某个类型。传入一个类的全类名,将类名描述的字节码文件,加载到内存中,形成一个字节码对象,并且把这个对象作为该方法的返回值。(在调用方法之前,是内存中没有这个字节码对象的)。字符串的来源非常广泛,来源于代码,可以来源于键盘录入、网络传输、文件读取、数据库。

*示例代码*

public static void main(String[] args) throws ClassNotFoundException {
		Person p = new Person("zhangsan", 23);
		//getClass的方法获取Person类型的字节码对象
		Class c1 = p.getClass();
		System.out.println(c1);
		
		//类名.class的方式获取Person的字节码对象
		Class c2 = Person.class;
		System.out.println(c2);
		
		//比较两个引用是否指向了同一个对象
		System.out.println(c1 == c2);
		
		//Class的静态forName方法获取字节码对象
		Class c3 = Class.forName("com.liuyue.reflect.Person");
		System.out.println(c3);
		System.out.println(c2 == c3);
	}

4、Class的newInstance()方法创建对象

*示例代码*

public class Demo02_Class的newInstance方法创建对象 {
	
	public static void main(String[] args) throws Exception{
		Class c1 = Date.class;
		Object obj = c1.newInstance();
		Date d1 = (Date)obj;
		System.out.println(d1);
		
		Class c2 = Class.forName("java.util.Date");
		Object obj2 = c2.newInstance();
		Date d2 = (Date)obj2;
		System.out.println(d2);
		
		
		Date date = new Date();
		Class c3 = date.getClass();
	    Object obj3 = c3.newInstance();
	    Date d3 = (Date)obj3;
	    System.out.println(d3);		
	}
}

5、反射构造方法

Class类获取构造器API:

// 获取多个
// 该方法只能获取当前Class所表示类的public修饰的构造器
public Constructor<?>[] getConstructors():
// 获取当前Class所表示类的所有的构造器,和访问权限无关
public Constructor<?>[] getDeclaredConstructors():


// 获取单个
// 获取当前Class所表示类中指定参数类型的public的构造器
// 如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
public Constructor<T>getConstructor(Class<?>...parameterTypes) :
// 获取当前Class所表示类中指定参数类型的构造器
// 如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) :

*使用反射来获取类的构造器的步骤:*

  1. 获取该类的字节码对象.
  2. 从该字节码对象中获取需要的构造器.

*需求:*

  1. 获取所有public构造器
  2. 获取所有构造器包括private
  3. 获取无参数public构造器
  4. 获取带有指定参数的public构造器
  5. 获取带有指定参数的构造器包括private

*不使用Declared获取带有指定参数的private构造器会报错*

*示例代码*

JavaBean类

public class Person {
	// 成员变量
	private Long id;
	public String name;
	public int age;

	// 构造方法
	public Person() {
		System.out.println("无参数public构造器被执行");
	}
	public Person(String name) {
		this.name = name;
		System.out.println("带有String的public构造器被执行");
	}
	// 私有的构造方法
	private Person(String name, int age) {
		this.name = name;
		this.age = age;
		System.out.println("带有String, int的private构造器被执行");
	}
	public Person(Long id, String name, int age) {
		super();
		this.id = id;
		this.name = name;
		this.age = age;
		System.out.println("带有Long, String, int的public构造器被执行");
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
	}

}

反射被public修饰的构造方法

public class Demo03_反射构造方法 {
	
	public static void main(String[] args) throws Exception{
		Class clazz = Person.class;
		//获取全部被public修饰的构造方法
		Constructor[] cs = clazz.getConstructors();   
		for(Constructor con:cs){
			System.out.println(con);
		}
		
        // 获取无参、被public修饰的构造方法
		Constructor con1 = clazz.getConstructor();
        // 获取指定有参、被public修饰的构造方法
		Constructor con2 = clazz.getConstructor(String.class);
	    Constructor con3 = clazz.getConstructor(String.class,int.class);		Constructor con4 = clazz.getConstructor(Long.class,String.class,int.class);
		
		System.out.println(con1);
		System.out.println(con2);
		System.out.println(con3);
		System.out.println(con4);
	}
}

反射所有的构造方法包括被private修饰的

public class Demo04_反射所有构造方法_包括私有 {
   // 反射私有的成员——暴力反射
   public static void main(String[] args) throws Exception{
   	Class clazz = Person.class;
   	// 反射到所有构造方法,包括private的
   	Constructor[] cons = clazz.getDeclaredConstructors();
   	
   	for(Constructor con:cons){
   		System.out.println(con);
   	}
   	
   	Constructor con = clazz.getDeclaredConstructor(String.class,int.class);
   	System.out.println(con);
   }
}

*调用反射到的构造方法创建对象:*

常用API:

public T newInstance(Object... initargs)

参数:initargs:表示调用构造器的实际参数

返回:返回创建的实例,T表示Class所表示类的类型

*注意:*

  • 如果使用public空参构造创建对象,那么可以直接使用Class类中的newInstance方法创建对象.
  • 调用私有的构造器前需要设置开启暴力访问: constructor.setAccessible(true);

*示例代码*

public class Demo05_调用反射到的构造方法创建对象 {
	
	public static void main(String[] args) throws Exception{
		
		Class clazz = Person.class;
		
        // 获得无参被public修饰的构造方法
		Constructor con1 = clazz.getConstructor();
        // 利用反射得到的构造方法创建对象
		Object obj1 = con1.newInstance();
		Person person1 = (Person)obj1;
		System.out.println(person1);
		
		// 获得有参被public修饰的构造方法
		Constructor con2 = clazz.getConstructor(Long.class,String.class,int.class);
		Object obj2 = con2.newInstance(1000L,"zhangsan",20);
		Person person2 = (Person)obj2;
		System.out.println(person2);
		
		//Object obj = clazz.newInstance(1000L,"zhangsan",20);
		
        // private
		Constructor con3 = clazz.getDeclaredConstructor(String.class,int.class);
		// 私有的构造方法不能直接使用
		con3.setAccessible(true);  //暴力反射
		Object obj3 = con3.newInstance("lisi",21);
		Person person3 = (Person)obj3;
		System.out.println(person3);		
	}
}

6、反射方法

*常用API:*

获取多个:
public Method[] getMethods():获取本类和继承过来的所有的public方法
public Method[] getDeclaredMethods():获取本类中所有的方法(包括private,不包括继承的,和访问权限无关)

获取单个:
public Method getMethod(String methodName, Class<?>... parameterTypes):获取指定public的方法(包括继承的)
               	methodName: 表示方法名
              	parameterTypes:表示方法参数的Class类型如String.class

public Method getDeclaredMethod(String name,Class<?>... parameterTypes):获取指定方法(包括private,不包括继承的)
             	methodName: 表示方法名字
              	parameterTypes:表示方法参数的Class类型如String.class

*Method类下的方法:*

getModifiers()         获得访问控制符
getReturnType()        获得返回值类型
getName()            获得方法名
getParameterTypes()    获得参数列表

*需求:*

1、获取所有public方法,包括继承的

2、获取所有方法,包括private,不包括继承的

3、获取指定方法,包括继承的

4、获取指定方法,包括private,不包括继承的

*示例代码*

public class User {
	// 无参无返回的方法
	public void method1() {
		System.out.println("无参无返回的public方法被执行");
	}
	// 有参无返回的方法
	public void method2(String name) {
		System.out.println("有参无返回的public方法被执行 name= " + name);
	}
	// 无参有返回的方法
	public int method3() {
		System.out.println("无参有返回的public方法被执行");
		return 123;
	}
	// 有参有返回方法
	public String method4(String name) {
		System.out.println("有参有返回方法的public方法被执行");
		return "哈哈" + name;
	}
	// 私有方法
	private void method5() {
		System.out.println("private私有方法被执行");
	}
	//静态方法
	public static void method6(String name){
		System.out.println("静态方法被执行 name= "+ name);
	}
	//参数是基本数据类型的数组
	public void method7(int...arr){
		System.out.println("参数是基本数据类型的数组的方法被执行arr= "+ Arrays.toString(arr));
	}
	//参数是引用数据类型的数组
	public void method8(String...arr){
		System.out.println("参数是引用数据类型的数组的方法被执行arr= "+ Arrays.toString(arr));
	}
public class Demo06_反射方法 {
   
   public static void main(String[] args) throws Exception{
   	Class clazz = User.class;	
   	//getMethods() 获得public的方法和父类的方法
   	Method[] ms = clazz.getMethods();
   	for(Method m:ms){
   		//System.out.println(m);
   		System.out.println(m.getModifiers());  //修饰符
   		System.out.println(m.getReturnType().getName());  //返回值类型
   		System.out.println(m.getName()); //方法名
   		Class[] rs = m.getParameterTypes(); // 参数列表
   		for(Class c:rs){
   			System.out.println(c.getName());
   		}
   		System.out.println("-------------------------------------");
   	}
   	// getDeclaredMethods() 获得本类中的所有方法(包括private的)
   	Method[] ms = clazz.getDeclaredMethods();
   	for(Method m:ms){
   		System.out.println(m);
   	}
   	// getMethod() 获取具体某个方法
   	Method method = clazz.getMethod("method4", String.class);
   	System.out.println(method);

   	Method method2 = clazz.getDeclaredMethod("method5", null);
   	System.out.println(method2);	
   }
}

*执行反射到的方法*

*常用API*

public Object invoke(Object obj,Object... args):表示调用当前Method所表示的方法
      参数:
            obj: 表示被调用方法所属对象
            args:表示调用方法是传递的实际参数
      返回:
            方法的返回结果

*注意*

在调用私有方法之前开启暴力访问:Method.setAccessible(true);

*需求*

1、执行无参无返回的方法

2、执行有参无返回的方法

3、执行无参有返回的方法

4、执行有参有返回的方法

5、执行私有方法

*示例代码*

public class Demo07_执行反射到的方法 {
	public static void main(String[] args) throws Exception{
		/*
		User user = new User();
		Class clazz = user.getClass();
		*/
		Class clazz = User.class;
		User user = (User)clazz.newInstance();
		
		Method method1 = clazz.getMethod("method1",null);
		method1.invoke(user, null);
		
		Method method2 = clazz.getMethod("method2",String.class);
		method2.invoke(user,"Tom");
		
		Method method3 = clazz.getMethod("method3",null);
		Object obj3 = method3.invoke(user, null);
		System.out.println(obj3);
		
		Method method4 = clazz.getMethod("method4",String.class);
		Object obj4 = method4.invoke(user,"Jerry");
		System.out.println(obj4);
		
		Method method5 = clazz.getDeclaredMethod("method5",null);
		method5.setAccessible(true);  //暴力反射
		method5.invoke(user,null);
	}
}

7、反射调用静态方法和数组参数方法

  • 使用反射调用静态方法:

    • 静态方法不属于任何对象,静态方法属于类本身.
    • 此时把invoke方法的第一个参数设置为null即可.
  • 使用反射调用数组参数方法(可变参数):

    • 调用方法的时候把实际参数统统作为Object数组的元素即可.
    • Method对象.invoke(方法所属对象,new Object[]{所有实参 });

*示例代码*

public class Demo08_调用反射到的静态方法和数组参数方法 {
	
	public static void main(String[] args) throws Exception{
		
		Class clazz = User.class;
		User user = (User)clazz.newInstance();
		
		Method method6 = clazz.getMethod("method6",String.class);
		//method6.invoke(user,"张三");
		method6.invoke(null,"李四"); //静态方法不属于任何对象,所以第一个参数给null也可以
	
	    Method method7 = clazz.getMethod("method7",int[].class);
	    method7.invoke(user,new Object[]{new int[]{1,2,3,4}});
	    
	    Method method8 = clazz.getMethod("method8",String[].class);
	    method8.invoke(user,new Object[]{new String[]{"a","b","c"}});
	}
}

8、反射属性

*API*

获取多个
public Field[] getFields() 获取所有public 修饰的变量
public Field[] getDeclaredFields() 获取所有的 变量 (包含私有)

获取单个
public Field getField(String name) 获取指定的 public修饰的变量
public Field getDeclaredField(String name) 获取指定的任意变量(包含私有)

通过方法,给指定对象的指定成员变量赋值或者获取值
public void set(Object obj, Object value)在指定对象obj中,将此Field设置为指定值
public Object get(Object obj)返回指定对象obj中,此 Field的值

*Field类的方法*

getModifiers();  //修饰符
getType();       //类型
getName();       //变量名
get(Object obj);     // 获取值

*注意:*私有成员变量,通过*setAccessible*(boolean flag)方法暴力访问)

*需求:*

获取所有public字段

获取所有字段包括private

获取指定public字段并设置值

获取指定private字段并设置值

*示例代码*

public class Person {
	// 成员变量
	private Long id;
	public String name;
	public int age;
}
		Class clazz = Person.class;
		Object instance = clazz.newInstance();
		System.out.println("==获取所有public字段==");
		Field[] fields = clazz.getFields();
		for (Field field : fields) {
			System.out.println(field);
		}
		System.out.println("==获取所有字段包括private==");
		Field[] declaredFields = clazz.getDeclaredFields();
		for (Field field : declaredFields) {
			System.out.println(field);
		}
		System.out.println("==获取指定public字段并设置值=");
		Field field = clazz.getField("name");
		field.set(instance, "Andy");
		System.out.println(field.get(instance));
		System.out.println("==获取指定private字段并设置值=");
		Field declaredField = clazz.getDeclaredField("id");
		// 开启暴力访问
		declaredField.setAccessible(true);
		declaredField.set(instance, 1L);
		System.out.println(declaredField.get(instance));

9、反射其他的API

String getName():获取全限定名
String getSimpleName():获取简单类名,不包含包名
Package getPackage():获取该类的包
Class getSuperclass():获取父类的Class
getGenericSuperclass():获取父类
boolean isArray():是否为数组类型
boolean isEnum():是否为枚举类型
boolean isInterface():是否为接口类型
boolean isPrimitive():是否为基本类型
boolean isSynthetic():是否为引用类型
boolean isAnnotation():是否为注解类型
boolean isAnnotationPresent(Class annotationClass):当前类是否加了指定类型注解

*示例代码*

Class clazz = Person.class;
System.out.println("getName():"+clazz.getName());
System.out.println("getSimpleName():"+clazz.getSimpleName());
System.out.println("getPackage():"+clazz.getPackage());
System.out.println("getModifiers():"+clazz.getModifiers());
System.out.println("getSuperclass():"+clazz.getSuperclass());
System.out.println("getInterfaces():"+Arrays.toString(clazz.getInterfaces()));
System.out.println("getGenericSuperclass():"+clazz.getGenericSuperclass());
System.out.println("isArray():"+clazz.isArray());
System.out.println("isEnum():"+clazz.isEnum());
System.out.println("isInterface():"+clazz.isInterface());

10、反射应用—泛型擦除

//有如下集合

ArrayList list = new ArrayList<>();

list.add(666);

//设计代码,将字符串类型的"abc",添加到上述带有Integer泛型的集合list中

*示例代码*

public class Demo11_反射练习_泛型擦除 {
  
	public static void main(String[] args) throws Exception{
		ArrayList<Integer> list = new ArrayList<Integer>();
		list.add(666);
		
		// 要把 "abc" 也加入到list中
		//list.add("abc");  // 编译期检查泛型——"abc" 不能加入到list中。可以在运行期加入
		/**
		 * 在编译阶段,检查add方法的实际参数,如果在编译阶段,不要调用add方法,
		 * 就会避免掉在编译阶段,对实际参数数据类型的检查
		 * 在运行阶段,调用add方法
		 * 使用反射的方式,调用某个方法,在写代码的阶段,根本不知道将来调用哪个方法
		 * 编译器也就没有办法在编译阶段对代码进行检查
		 * 
		 * 这种方式叫做“泛型擦除”
		 * 在java中,只会在编译阶段,对泛型进行检查,到了运行阶段,对泛型不检查
		 * 称这种泛型为:伪泛型
		 */
		Class clazz = list.getClass();
		
		Method method = clazz.getMethod("add",Object.class);
		method.invoke(list, "abc");		
		System.out.println(list);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值