反射
在Java中反射是极其重要的知识,在后期接触的大量框架的底层都运用了反射技术,因此掌握反射技术将帮助我们更好地理解这些框架的底层原理,以便灵活地掌握框架的使用
反射是指对于任何一个Class类(编译后的.class文件),在“运行的时候”都可以直接得到这个类的全部成分
一个.class
文件就是一个Class
对象
在运行时,我们可以直接得到这个类的
- 构造器对象 Constructor
- 类的成员变量对象Field
- 类的成员方法对象Method
这种运行时动态后去类信息以及动态调用类中成分的能力称为Java的反射机制
反射的关键
反射的第一步也是关键的一步就是先得到编译后的Class类对象 , 然后就可以得到Class的全部成分(因为类中的所有信息都在.class
文件中)
认识Class类
先定义一个类 ,接下来用
package com.reflect;
public class Person {
//私有成员变量
private String name;
private int age;
private Person(int age){
this.age = age;
}
//构造方法
public Person(){
};
public Person(String name){
this.name = name;
}
public Person(String name , int age){
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
//成员方法
public String getName(){
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
获取Class类对象
编译生成的.class文件, 将这个文件提取到内存中 , 就可以拿到这个Class对象
第一种方式
通过Class类的静态方法forName(className)
来将.class
文件加载到内存中,
该方法需要一个参数, 是这个类的全限名: 包名 + 类名
package com.reflect;
public class Reflect_ForName {
public static void main(String[] args) throws ClassNotFoundException {
//通过forName(全限名:包名 + 类名)
Class c = Class.forName("com.reflect.Person");
System.out.println(c);
}
}
第二种方式
直接使用类名.class
第三种方式
Runtime运行阶段, 通过对象.getClass(String name)
方法
getClass()
是Object
的一个方法, 所以的对象都可以直接调用该方法.
当我已经有了一个确切的对象后, 我就可以直接通过此方法获得此类的Class对象(此时.class
肯定已经加载到内存中了)
package com.reflect;
public class Reflect_ForName {
public static void main(String[] args) throws ClassNotFoundException {
// 第一种方式
//通过forName(全限名:包名 + 类名)
Class c = Class.forName("com.reflect.Person");
System.out.println(c);
// 第二种方式: 类名.class
Class c1 = Person.class;
System.out.println(c1);
// 第三种方式: 对象.getClass()
Person p = new Person("张三");
Class c2 = p.getClass();
System.out.println(c2);
}
}
反射的应用
通过反射创建对象
- 第一步,获取Class对象
- 第二部, 获取构造器对象
- 通过构造器对象创建对象
Class中与构造亲有关的API
Constructor<?>[] getConstructors();
//返回这个类的所有构造器对象数组(只能拿public的)
Constructor<?>[] getDeclaredConstructors();
//返回所有的构造器对象,存在就能拿到
Constructor<T> getConstructor(Class<?>...parameterTypes);
//返回单个构造器对象(只能拿public的)
Constructor<T> getDeclaredConstructor(Class<?>...parameterType);
//返回单个构造器对象 , 存在就能拿到
获取构造器的几种方法
建议使用getDeclaredConstructor()
或getDeclaredConstructors()
package com.reflect;
import java.lang.reflect.Constructor;
public class getConstructor {
public static void main(String[] args) throws NoSuchMethodException {
// 1. 获取Class对象
Class c = Person.class;
// 获取所有的pubic 构造方法
Constructor[] constructors = c.getConstructors();
for (Constructor sc:constructors) {
System.out.println(sc.getName() + "---" + sc.getParameterCount());
}
// 获取特定的构造方法
Constructor noPara = c.getConstructor();
System.out.println(noPara.getName() + "----" + noPara.getParameterCount());
// 定位某个有参构造器
Constructor twoPara = c.getDeclaredConstructor(String.class,int.class);
System.out.println(twoPara.getName() + "----" + twoPara.getParameterCount());
}
}
通过构造器获取对象
通过Constructor
的newInstance()
方法 ,通过此方法得到的对象是一个Object
, 需要强制转换为目标对象类型
package com.reflect;
import com.sun.org.apache.bcel.internal.Const;
import java.lang.reflect.Constructor;
public class getObject {
public static void main(String[] args) throws Exception {
Class c = Person.class;
// 获取public无参构造方法
Constructor constructor = c.getDeclaredConstructor();
Person p = (Person) constructor.newInstance();
System.out.println(p);
// 获取有参构造方法
Constructor paraconst = c.getDeclaredConstructor(String.class, int.class);
// 此时通过有参构造器创建对象 , 需要传参数
Person ps = (Person) paraconst.newInstance("张三",18);
System.out.println(ps.getAge());
// 加入构造方法私有, 我们也可以构造对象, 暴力反射 ,但是不建议这么做
Constructor privatecon = c.getDeclaredConstructor(int.class);
privatecon.setAccessible(true);//强制打开权限
Person pp = (Person) privatecon.newInstance(22);
System.out.println(pp.getAge());
}
}
如果类中的成分是private
,不能直接使用
可以使用setAccessible(true)
强制打开权限,暴力反射, 适用于类中的所有成分
获取成员变量
Class中与Field有关的API
Field[] getFields();
//返回所有成员变量的数组(只返回public)
Field[] getDeclaredFields();
//返回所有成员变量对象的数组,存在就能拿到
Field getField(String name);
//返回单个成员变量(只能public)
Field getDeclaredField(String name);
//返回单个成员变量 , 存在就能拿到
常量也是成员变量
获取成员变量
package com.reflect;
import java.lang.reflect.Field;
public class getField {
public static void main(String[] args) throws Exception {
Class c = Class.forName("com.reflect.Person");
// 获取所有属性
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + "---" + field.getType());
}
// 定位某个属性
Field field = c.getDeclaredField("age");
System.out.println(field.getName() + "----" + field.getType());
}
}
给成员变量赋值, 因为成员变量不能孤立存在, 所以需要指定一个对象
成员方法和属性的调用都需要结合具体的对象
//Field类中的方法
set(Object obj, Object value);
//给成员变量赋值
get(Object obj);
//获取成员变量的值
获取成员方法
Class中与Method相关的API
Method[] getMethods();
//返回所有成员方法对象的数组(只能拿public)
//可以拿到父类的方法, 例如Object的
Methode[] getDeclaredMethods();
//返回所有成员方法的数组,存在就能拿到
Method getMethod(String name,Class<?>...parameterTypes);
//返回单个成员方法(只能public)
Method getDeclaredMethod();
//返回单个成员方法对象,存在就能拿到
拿到方法对象
package com.reflect;
import java.lang.reflect.Method;
public class getMethod{
public static void main(String[] args) throws NoSuchMethodException {
Class c = Person.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法名称" + method.getName() + "方法返回值类型" + method.getReturnType() + "参数数量" + method.getParameterCount());
}
// 拿到指定的方法
// 通过方法名称获取指定方法
Method md = c.getMethod("getName");
// 通过方法名和参数类型获取
Method mx = c.getMethod("getAge", int.class);
}
}
拿到这些方法是为了调用这些方法, 接下里就是调用的方法
Method中用于触发执行的方法
Object invoke(Object obj, Object...args);
//第一个参数:obj是对象,通过此对象来调用方法
//第二个参数:方法的参数,如果没有可以不写
//返回值: 就是运行方法后的返回值,如果方法没有返回值, 则该方法的返回值为null
package com.reflect;
import java.lang.reflect.Method;
public class getMethod{
public static void main(String[] args) throws Exception {
Class c = Person.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法名称" + method.getName() + "方法返回值类型" + method.getReturnType() + "参数数量" + method.getParameterCount());
}
// 拿到指定的方法
// 通过方法名称获取指定方法
Method mget = c.getMethod("getName");
// 通过方法名和参数类型获取
Method mset = c.getMethod("setName",String.class);
// 触发方法
Person p = new Person();
mset.invoke(p,"张三");
String rValue = (String) mget.invoke(p);
System.out.println(rValue);
}
}
反射的作用
- 可以在运行阶段得到一个类的全部成分然后操作
- 可以破坏封装性
- 也可以破坏泛型的约束性
- 最主要的用途: 做Java高级框架
绕过编译阶段为集合添加数据
- 反射是作用在运行时的技术, 此时集合的泛型将不能产生约束了 , 此时是可以为集合存入其他任意类型的元素
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
//list.add("zhang");
list.add(99);
- 泛型只是在编译阶段可以约束集合, 只能操作某种数据类型, 在编译成Class文件进入运行阶段 时, 其真实类型都是ArrayList, 泛型相当于被擦除了
演示一波,
package com.reflect;
import java.util.ArrayList;
public class ArrayListAdd {
public static void main(String[] args) {
/*
验证不同集合是调用同一个Class文件
* */
ArrayList<String> arrayList1 = new ArrayList<>();
ArrayList<Integer> arrayList2 = new ArrayList<>();
System.out.println(arrayList1.getClass());
System.out.println(arrayList2.getClass());
System.out.println(arrayList1.getClass() == arrayList2.getClass());
}
}
从运行结果可以看出, 这两个集合的泛型不同, 但是都对应同一个Class文件, 就可以证明: 集合在运行时, 泛型被擦除.
package com.reflect;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ArrayListAdd {
public static void main(String[] args) throws Exception{
/*
验证不同集合是调用同一个Class文件
* */
ArrayList<String> arrayList1 = new ArrayList<>();
ArrayList<Integer> arrayList2 = new ArrayList<>();
System.out.println(arrayList1.getClass());
System.out.println(arrayList2.getClass());
System.out.println(arrayList1.getClass() == arrayList2.getClass());
System.out.println("================");
// 通过反射向泛型集合中强行注入其他类型元素
ArrayList<Integer> list3 = new ArrayList<>();
list3.add(13);
list3.add(17);
Class c = list3.getClass();
// 定位集合的add方法
Method addString = c.getDeclaredMethod("add",Object.class);
// 运行add方法
// add方法的返回值是一个Boolean, 用来反映是否添加成功
Boolean rs = (Boolean) addString.invoke(list3,"嘿嘿嘿");
System.out.println(rs);
System.out.println(list3);
}
}
其实, 还有一个简单的方法, 来向集合中添加其他类型元素
ArrayList list4 = list3;
list4.add(false);
list4.add("哈哈哈");
System.out.println(list3);
反射做通用框架的底层原理
后期接触到框架再说
参考视频:
作者:黑马程序员
https://www.bilibili.com/video/BV1Cv411372m?p=188&vd_source=46e82c24af5db4922a4bbca7090ecda3