反射-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) :
*使用反射来获取类的构造器的步骤:*
- 获取该类的字节码对象.
- 从该字节码对象中获取需要的构造器.
*需求:*
- 获取所有public构造器
- 获取所有构造器包括private
- 获取无参数public构造器
- 获取带有指定参数的public构造器
- 获取带有指定参数的构造器包括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);
}
}