corejava-笔记——反射

反射

1.1 概述

反射是Java中提供的一种机制,它允许我们在程序运行的时候,动态获取一个类中的基本信息,并且可以调用类中的属性、方法、构造器。

类的基本信息:

  • 类的全限定类名(例:java.lang.System)
  • 类的直接父类的全限定类名
  • 类的直接实现接口
  • 类的修饰符

例:Student类型

package com.test.demo1;
public class Student {
	private String name;
	int age;
	public static int num;
	
	public Student() {}
	
	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public String getName() {return name;}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String sayHello(String name) {
		return "hello!" + name;
	}
}

对于上述Student类中我们除了能用眼睛看到这个类中有哪些属性、方法、构造器之外,在代码运行的时候,则可以通过反射机制获取到这个类中的属性、方法、构造器,以及调用他们。

	
	public void test1() throws Exception {
		// 查看类中声明的属性

		Class c = Class.forName("com.briup.day18.test2.Student");

		// 获取类中所有的属性(包括私有的)
		Field[] field = c.getDeclaredFields();
		for (Field f : field) {
			System.out.println("属性名:" + f.getName());
			System.out.println("属性的修饰符:" + Modifier.toString(f.getModifiers()));
			System.out.println("属性的类型:" + f.getType().getName());
			System.out.println("--------------");
		}
	}

运行结果:
属性名:name
属性的修饰符:private
属性的类型:java.lang.String
--------------
属性名:age
属性的修饰符:
属性的类型:int
--------------
属性名:num
属性的修饰符:public static
属性的类型:int
--------------

由上述代码可以看出,即使没有源代码(.java文件),我们也能通过反射来知道这个类中都声明了哪些属性
同样的,也可以使用类似的方法,获取到类中的方法信息和构造器信息,甚至是调用他们。

1.2 Class类型

java.lang.Class是API中提供的一个类,它可以表示java中所有的类型,包括基本类型和引用类型。
Object中的方法getClass方法的返回类型就是Class类型
public final native Class<?> getClass()
所以,obj.getClass()这句代码的含义就是:返回obj引用在运行时所指向对象的实际类型。

Object obj = new Object();
obj.getClass();
//输出 class java.lang.Object
public void test2() {
		System.out.println("使用Class对象表示基本数据类型");
		Class c;
		c = byte.class;
		System.out.println("对象c所代表的类型名字为:" + c.getName() + "\t是否为基本类型:" + c.isPrimitive());
		c = short.class;
		System.out.println("对象c所代表的类型名字为:" + c.getName() + "\t是否为基本类型:" + c.isPrimitive());
		c = int.class;
		System.out.println("对象c所代表的类型名字为:" + c.getName() + "\t是否为基本类型:" + c.isPrimitive());
		c = long.class;
		System.out.println("对象c所代表的类型名字为:" + c.getName() + "\t是否为基本类型:" + c.isPrimitive());
		c = float.class;
		System.out.println("对象c所代表的类型名字为:" + c.getName() + "\t是否为基本类型:" + c.isPrimitive());
		c = double.class;
		System.out.println("对象c所代表的类型名字为:" + c.getName() + "\t是否为基本类型:" + c.isPrimitive());
		c = char.class;
		System.out.println("对象c所代表的类型名字为:" + c.getName() + "\t是否为基本类型:" + c.isPrimitive());
		c = boolean.class;
		System.out.println("对象c所代表的类型名字为:" + c.getName() + "\t是否为基本类型:" + c.isPrimitive());

		// 使用Class类的对象表示类类型
		System.out.println("\n使用Class对象表示类类型");
		Class class1 = Student.class;
		System.out.println("当前Class对象表示的类类型名为:" + class1.getSimpleName());
		System.out.println("该类类型是:" + class1.getName());
		/*
		 * System.out.println(class1.getName()); System.out.println(class1);
		 */

		// 使用Class类的对象来表示接口类型
		System.out.println("使用Class类的对象来表示接口类型");
		Class class2 = List.class;
		System.out.println("该类类型为:" + class2.getName());
		System.out.println("该类型是否为接口类型:" + class2.isInterface());

		// 使用Class类的对象来表示数组类型
		System.out.println("使用Class类的对象来表示数组类型");
		Class class3;
		class3 = int[].class;
		System.out.println("当前对象所表示的数组类型为:" + class3.getName());
		System.out.println(class3.getSimpleName());
		class3 = int[][].class;
		System.out.println("当前对象所表示的数组类型为:" + class3.getName());
		System.out.println(class3.getSimpleName());
		class3 = Student[].class;
		System.out.println("当前对象所表示的数组类型为:" + class3.getName());
		System.out.println(class3.getSimpleName());
		System.out.println(class3.getComponentType());
		System.out.println(class3.getComponentType().getSimpleName());
	}

1.3 获取Class对象

在Java中,每种类型(基本类型和引用类型)加载到内存之后,都会在内存中生成一个Class类型的对象,这个对象就代表这个具体的java类型,并且保存这个类型中的基本信息。

注:Java中的每种类型,都有且只有唯一的一个Class类型对象与之对应!并且在类加载的时候自动生成!

例:

Student stu = new Student();
Class class1 = stu.getClass();
Class class2 = Student.class;
System.out.println(class1 == class2);
//================
结果为true

获取基本类型的Class对象

只有一种方式:
Class c = int.class;

获取类类型的Class对象

有三种方式:

  • Class c1 = Student.class;
  • Class c2 = Class.forName("com.test.demo1.Student");
  • Student stu = new Student(); Class c2 = stu.getClass();

获取接口类型的Class对象

有两种方式:

  • Class c1 = List.class;
  • Class c2 = Class.forName("java.util.List");

获取数组类型的Class对象

有三种方式:

  • Class c1 = int[].class;
  • Class c2 = Class.forName("[I");
  • int[] arr = new int[4]; Class c3 = arr.getClass();

小结

综上所述,可以看出,获取一个类型Class对象的途径一般有三种:

  1. 使用类型名直接获取:类型名.class
  2. 使用Class类中的静态方法forName()获取:Class.forName(“全限定名”)
  3. 使用对象的getClass()方法获取:new Student().getClass()

1.4 获取类的信息

类名、父类名、修饰符、实现的接口…

//以开始的Student类型为例:

1.4.1 获取类的基本信息

package com.test.demo1;
interface Action {
	void run();
}
interface Mark {
	void start();
}
class Student implements Action,Mark{
	//与最开始的Student代码一致,添加以下代码
	@Override
	public void start() {
		System.out.println("实现Mark接口的start方法");
	}

	@Override
	public void run() {
		System.out.println("实现Action接口的run方法");
	}
}

public void test4() {
		Student stu = new Student();
		Class class1 = stu.getClass();
		
		System.out.println("类的全限定名:" + class1.getName());
		
		System.out.println("类的简单类名:" + class1.getSimpleName());
		
		System.out.println("类所属的包名:" + class1.getPackage().getName());
		
		System.out.println("类的父类型名字:" + class1.getSuperclass().getName());
		
		Class[] interfaces = class1.getInterfaces();
		System.out.println("类实现的所有接口:" + Arrays.toString(interfaces) );
		
		System.out.println("------");
		Class class2 = Object.class;
		Class class3 = Action.class;
		Class class4 = Mark.class;
		Class class5 = String.class;
		//判断class2代表的类型是不是class1代表类型的父类型
		System.out.println(class2.getSimpleName() + "是否是" + class1.getSimpleName() + "的父类型:" + class2.isAssignableFrom(class1));
		//判断class3代表的类型是不是class1代表类型的父类型
		System.out.println(class3.getSimpleName() + "是否是" + class1.getSimpleName() + "的父类型:" + class3.isAssignableFrom(class1));
		//判断class4代表的类型是不是class1代表类型的父类型
		System.out.println(class4.getSimpleName() + "是否是" + class1.getSimpleName() + "的父类型:" + class4.isAssignableFrom(class1));
		//判断class5代表的类型是不是class1代表类型的父类型
		System.out.println(class5.getSimpleName() + "是否是" + class1.getSimpleName() + "的父类型:" + class5.isAssignableFrom(class1));
}
上述代码运行结果:
类的全限定名:com.test.demo1.Student
类的简单类名:Student
类所属的包名:com.test.demo1
类的父类型名字:java.lang.Object
类实现的所有接口:[interface com.test.demo1.Action, interface com.test.demo1.Mark]
------
Object是否是Student的父类型:true
Action是否是Student的父类型:true
Mark是否是Student的父类型:true
String是否是Student的父类型:false

1.4.2 获取类中声明的属性

获取类中public修饰的属性,包含从父类中继承过来的public属性,Class类中提供了一下方法:

  • Field[] getFields()
  • Field getField(String name)

获取类中声明的属性(包含私有属性),但是不能获取父类中继承过来的属性

  • Field[] getDeclaredFields()
  • Field getDeclaredField(String name)

注:java.lang.reflect.Field表示类中的属性(属性的封装类)

public void test5() {
		Class class1 = Student.class;

		// 获取类中声明的属性(包含私有属性)
		Field[] fields = class1.getDeclaredFields();

		for (Field field : fields) {
			System.out.println("属性名:" + field.getName());
			System.out.println("属性修饰符:" + Modifier.toString(field.getModifiers()));
			System.out.println("属性类型:" + field.getType());
			System.out.println("-----");
		}

		// 获取类中声明的public属性,包含父类继承来的属性
		Field[] fields2 = class1.getFields();
		System.out.println("Student类中所有的public属性:");
		for (Field field : fields2) {
			System.out.println(field.getName());
		}

	}
运行结果:
属性名:name
属性修饰符:private
属性类型:class java.lang.String
-----
属性名:age
属性修饰符:
属性类型:int
-----
属性名:num
属性修饰符:public static
属性类型:int
-----
Student类中所有的public属性:
num

1.4.3 获取类中声明的方法

获取当前类中的public方法,包含从父类中继承的public方法

  • Method[] getMethods()
  • Method getMethod(String name, Class<?>... parameterTypes)
    获取当前类中声明的方法(包含私有方法),但是不能获取父类中继承过来的方法
  • Method[] getDeclaredMethods()
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes)

注:java.lang.reflect.Method表示类中的方法(方法的封装类)
以下例子中Student类里的sayHello方法定义为了私有方法,其他不变

	public void test6() {
		Class class1 = Student.class;
		Method[] methods = class1.getMethods();
		System.out.println("获取当前类中的public方法,包含从父类中继承的public方法:");
		for (Method method : methods) {
			System.out.print(method.getName() + " ");
		}
		System.out.println();
		System.out.println("获取当前类中的方法(包含私有方法):");
		Method[] methods2 = class1.getDeclaredMethods();
		for (Method method : methods2) {
			System.out.println("方法名:" + method.getName());
			System.out.println("方法的修饰符:" + Modifier.toString(method.getModifiers()));
			System.out.println("方法的返回类型:" + method.getReturnType().getSimpleName());
			System.out.println("方法参数个数:" + method.getParameterCount());
			// 获取方法的参数列表
			Class[] pType = method.getParameterTypes();
			// 输出参数列表
			System.out.println("方法参数分别是:" + Arrays.toString(pType));

			// 获取方法所抛出的异常类型
			Class[] exceptionTypes = method.getExceptionTypes();
			System.out.println("方法抛出异常个数为:" + exceptionTypes.length);
			System.out.println("方法抛出的异常:" + Arrays.toString(exceptionTypes));
			System.out.println("-----------");
		}
	}
运行结果:
获取当前类中的public方法,包含从父类中继承的public方法:
run getName start setName wait wait wait equals toString hashCode getClass notify notifyAll 
获取当前类中的方法(包含私有方法):
方法名:run
方法的修饰符:public
方法的返回类型:void
方法参数个数:0
方法参数分别是:[]
方法抛出异常个数为:0
方法抛出的异常:[]
-----------
方法名:getName
方法的修饰符:public
方法的返回类型:String
方法参数个数:0
方法参数分别是:[]
方法抛出异常个数为:0
方法抛出的异常:[]
-----------
方法名:start
方法的修饰符:public
方法的返回类型:void
方法参数个数:0
方法参数分别是:[]
方法抛出异常个数为:0
方法抛出的异常:[]
-----------
方法名:setName
方法的修饰符:public
方法的返回类型:void
方法参数个数:1
方法参数分别是:[class java.lang.String]
方法抛出异常个数为:0
方法抛出的异常:[]
-----------
方法名:sayHello
方法的修饰符:private
方法的返回类型:String
方法参数个数:1
方法参数分别是:[class java.lang.String]
方法抛出异常个数为:0
方法抛出的异常:[]
-----------

1.4.4 获取类中声明的构造器

获取当前类中的public构造器:

  • public Constructor<?>[] getConstructors()
  • public Constructor<T> getConstructor(Class<?>... parameterTypes)

获取当前类中的左右构造器,包含私有的:

  • public Constructor<?>[] getDeclaredConstructors()
  • public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

注:java.lang.reflect.Constructor(构造器的封装类)

以下例子中将Student的有参构造器定义为私有的,其他不变

	public void test7() {
		Student stu = new Student();
		Class class1 = stu.getClass();

		// 获取类中所有的public构造器
		Constructor[] cons = class1.getConstructors();
		System.out.println("获取所有public构造器");
		for (Constructor con : cons) {
			// 构造器名字
			System.out.println("构造器名:" + con.getName());
			System.out.println("构造器的修饰符:" + Modifier.toString(con.getModifiers()));
			// 构造器参数列表
			Class[] parameterTypes = con.getParameterTypes();
			System.out.println("构造器参数个数:" + parameterTypes.length);
			System.out.println("构造器参数:" + Arrays.toString(parameterTypes));

			// 构造器的抛出异常
			Class[] exceptionTypes = con.getExceptionTypes();
			System.out.println("构造器抛出异常:" + Arrays.toString(exceptionTypes));
			System.out.println("--------");
		}

		Constructor[] cons2 = class1.getDeclaredConstructors();
		System.out.println("获取所有的构造器,包含私有构造器:");
		System.out.println(Arrays.toString(cons2));
	}
运行结果:
获取所有public构造器
构造器名:com.test.demo1.Student
构造器的修饰符:public
构造器参数个数:0
构造器参数:[]
构造器抛出异常:[]
--------
获取所有的构造器,包含私有构造器:
[public com.test.demo1.Student(), private com.test.demo1.Student(java.lang.String,int)]

1.5 反射访问属性

	public void test8() throws Exception {
		
		Student stu = new Student();
		Class class1 = stu.getClass();
		//获取Student类中的私有属性name
		Field f1 = class1.getDeclaredField("name");
		//设置私有属性的可见性为true,默认为false
		f1.setAccessible(true);
		//使用反射的方式,给指定对象的name属性赋值
		//相当于stu.name = "win"
		f1.set(stu, "win");
		//用反射的方式,获取这个属性的值
		//相当于stu.name
		System.out.println(f1.get(stu));
		
		System.out.println("------");
		//获取类中默认类型的属性age
		Field f2 = class1.getDeclaredField("age");
		//赋值
		f2.set(stu, 18);
		//取值
		System.out.println(f2.get(stu));
		
		System.out.println("-------");
		
		//获取类中public类型的属性num
		Field f3 = class1.getField("num");
		//赋值
		f3.set(null, 100);
		//取值
		System.out.println(f3.get(null));
		//这里可以使用null是因为num属性是静态属性
	}

1.6 反射调用方法

public void test9() throws Exception {
		Student stu = new Student();
		Class class1 = stu.getClass();
		// 获取Student类中的toString方法,该方法没有参数,从父类继承的方法
		Method m1 = class1.getMethod("toString", null);
		// 使用反射的方式,调用stu对象中的这个方法,并接收方法返回结果
		Object result = m1.invoke(stu, null);
		// 输出toString方法返回的结果
		System.out.println(result);

		System.out.println("---------");

		// 获取类中的sayHello方法,并传递一个String类型的参数
		Method m2 = class1.getDeclaredMethod("sayHello", String.class);
		// 因为sayHello方法是私有方法
		m2.setAccessible(true);
		Object result2 = m2.invoke(stu, "win");
		System.out.println(result2);

		System.out.println("---------");

	}

注:
public Object invoke(Object obj, Object... args)
obj,表示要调用那个对象的方法,如果是静态的方法可以直接传null
args,可变参数,表示要调用的方法的参数列表
m.invoke(obj, null)中的m表示通过Class对象获取到的类中的某一个指定的方法
如果是私有方法,在调用之前必须设置可见性为true。m.setAccessible(true)

1.7 反射创建对象

使用反射的方式可以创建对象,也就是需要反射调用构造器

1.7.1 反射调用无参构造器创建对象
//反射调用类中的无参构造器进行创建对象
		Class class1 = Student.class;
		Object obj = class1.newInstance();
		System.out.println(obj);
		
运行结果:
com.test.demo1.Student@33723e30

Object obj = class1.newInstance();
相当于
Object obj = new Student();

1.7.2 反射调用有参构造器创建对象
//反射调用类中的有参构造器进行创建对象
		Class class2 = Student.class;
		//1. 先获取有参构造器
		Constructor con = class2.getDeclaredConstructor(String.class, int.class);
		//2. 设置私有构造器可见性为true
		con.setAccessible(true);
		//3. 调用有参构造器创建对象,并传入对应参数
		Object newInstance = con.newInstance("win", 20);
		System.out.println(newInstance);

注:
上述调用的有参构造器是私有的,所以在调用之前必须要设置可见性

调用有参构造器创建对象和调用无参构造器创建对象区别在于,有参的需要先获取构造器再newInstance创建,而无参的可以直接newInstance创建

思考,使用反射的方式访问属性、调用方法、创建对象,和普通方式比较,有什么优势?

如果属性和方法以及构造方法都是私有的,那么我们可以通过反射的方式直接去访问或者创建对象
但是如果是普通的方式,私有的属性和方法只能在本类中使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值