反射
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对象的途径一般有三种:
- 使用类型名直接获取:类型名.class
- 使用Class类中的静态方法forName()获取:Class.forName(“全限定名”)
- 使用对象的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创建
思考,使用反射的方式访问属性、调用方法、创建对象,和普通方式比较,有什么优势?
如果属性和方法以及构造方法都是私有的,那么我们可以通过反射的方式直接去访问或者创建对象
但是如果是普通的方式,私有的属性和方法只能在本类中使用