Java学习笔记-反射(二)
18.4 类加载
18.4.1 基本说明
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载
-
静态加载:
编译时加载相关的类,如果没有则报错,依赖性太强
-
动态加载:
运行时加载需要的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性
-
举例说明
18.4.2 类加载时机及过程图
-
类加载时机
- 当创建对象时(new)-- 静态加载
- 当子类被加载时,父类也加载 – 静态加载
- 调用类中的静态成员时 – 静态加载
- 通过反射 – 动态加载(Class.forName(…))
-
类加载过程图
注意:这是类加载阶段,跟对象没有关系,初始化也是与类相关的,如静态成员
18.4.3 类加载各阶段完成任务
1. 加载阶段
JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件,也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中
,并生成
一个代表类的java.lang.Class对象
2. 连接阶段
-
验证verification
- 目的为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 包括:文件格式验证(是否以魔数 oxcafebabe 开头),元数据验证,字节码验证和符号引用验证
- 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
-
准备preparation
-
JVM 会在该阶段对
静态变量
,分配内存并默认初始化(对应数据类型的默认初始值,如0,0L,null,false等),这些变量所使用的内存都将在方法区中进行分配,注意是对静态变量,类加载与实例变量无关
-
举例说明
public class ClassLoad02 {public static void main(String[] args) {} } class A {//属性-成员变量-字段//老韩分析类加载的链接阶段-准备 属性是如何处理//1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存//2. n2 是静态变量,分配内存 n2 是默认初始化 0 ,而不是20//3. n3 是static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30public int n1 = 10;public static int n2 = 20;public static final int n3 = 30; }
-
-
解析Resolution
- 虚拟机将常量池中的符号引用替换为直接引用的过程
3. 初始化阶段
-
到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是
执行 <clinit>() 方法的过程
-
<clinit> ()
方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量
的赋值动作和静态代码块中的语句
,并进行合并
-
虚拟机会保证一个类的
<clinit>()
方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类, 那么只会有一个线程去执行这个类的<clinit>()
方法,其他线程都需要阻塞等待,直到活动线程执行<clinit> ()
方法完毕
public class ClassLoad03 {public static void main(String[] args) throws ClassNotFoundException {//老韩分析//1. 加载B类,并生成 B的class对象//2. 链接 num = 0//3. 初始化阶段// 依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并/*clinit() {// 下面的代码是从B类中顺序收集的System.out.println("B 静态代码块被执行");num = 300;num = 100;}合并: num = 100*///new B();//类加载//System.out.println(B.num);//100, 如果直接使用类的静态属性,也会导致类的加载//看看加载类的时候,是有同步机制控制/*protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{//正因为有这个机制,才能保证某个类在内存中, 只有一份Class对象synchronized (getClassLoadingLock(name)) {//....}}*/B b = new B();}
}class B {static {System.out.println("B 静态代码块被执行");num = 300;}static int num = 100;public B() {//构造器System.out.println("B() 构造器被执行");}
}
18.5 反射的运用
使用反射技术获取成员变量对象并使用:反射的第一步是先得到类对象
,然后从类对象中获取类的成分对象
18.5.1 获取类的结构信息
-
java.lang.Class 类
-
第一组:获取构造器
方法名 作用 Constructor<?>[] getConstructors()
获取本类所有public修饰的构造器 Constructor<?>[] getDeclaredConstructors()
获取本类中所有构造器 getConstructor(Class<?>... parameterTypes)
返回单个构造器对象(只能拿public的) getDeclaredConstructor(Class<?>… parameterTypes) 返回单个构造器对象,存在就能拿到 -
第二组:获取成员变量
方法名 作用 Field[] getFields()
获取所有public修饰的属性,包含本类以及父类的 Field[] getDeclaredFields()
获取本类中所有属性(包括private的) Field getField(String name)
返回单个成员变量对象(只能拿public的) Field getDeclaredField(String name)
返回单个成员变量对象,存在就能拿到 -
第三组:获取成员方法
方法名 作用 Method[] getMethods()
获取所有public修饰的方法,包含本类以及父类的 Method[] getDeclaredMethods()
获取本类中所有方法(包括private的) Method getMethod(String name, Class<?>... parameterTypes)
根据形参类型返回单个Method对象,只能public修饰 Method getDeclaredMethod(String name, Class<?>... parameterTypes)
根据形参类型返回单个Method对象,无论什么修饰 -
其他方法
方法名 作用 getName
获取全类名(包名+类名) getSimpleName
获取简单类名 getPackage
以Package形式返回包信息 getSuperClass
以Class形式返回父类信息 getInterfaces
以Class[]形式返回接口信息 getAnnotations
以Annotation[形式返回注解信息
-
-
java.lang.reflect.Field 类
方法名 作用 getModifiers
以int形式返回修饰符, 默认修饰符是0
,public是1
,private是2
,protected是4
,static是8
,final是16
,public(1) + static (8) = 9
getType
以Class形式返回类型 getName
返回属性名 -
java.lang.reflect.Method 类
方法名 作用 getModifiers
以int形式返回修饰符, 默认修饰符是0
,public是1
,private是2
,protected是4
,static是8
,final是16
,public(1) + static (8) = 9
getReturnType
以Class形式获取返回类型 getName
返回方法名 getParameterTypes
以Class[]返回参数类型数组 -
java.lang.reflect.Constructor 类
方法名 作用 getModifiers
以int形式返回修饰符 getName
返回构造器名(全类名) getParameterTypes
以Class[]返回参数类型数组
举例说明
public class ReflectionUtils {//第一组方法API@Testpublic void api_01() throws Exception {//得到Class对象Class<?> personCls = Class.forName("com.hspedu.reflection.Person");//getName:获取全类名System.out.println(personCls.getName());//com.hspedu.reflection.Person//getSimpleName:获取简单类名System.out.println(personCls.getSimpleName());//Person//getFields:获取所有public修饰的属性,包含本类以及父类的Field[] fields = personCls.getFields();for (Field field : fields) {//增强forSystem.out.println("本类以及父类的属性=" + field.getName());}//getDeclaredFields:获取本类中所有属性Field[] declaredFields = personCls.getDeclaredFields();for (Field declaredField : declaredFields) {System.out.println("本类中所有属性=" + declaredField.getName());}//getMethods:获取所有public修饰的方法,包含本类以及父类的Method[] methods = personCls.getMethods();for (Method method : methods) {System.out.println("本类以及父类的方法=" + method.getName());}//getDeclaredMethods:获取本类中所有方法Method[] declaredMethods = personCls.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {System.out.println("本类中所有方法=" + declaredMethod.getName());}//getConstructors: 获取所有public修饰的构造器,包含本类Constructor<?>[] constructors = personCls.getConstructors();for (Constructor<?> constructor : constructors) {System.out.println("本类的构造器=" + constructor.getName());}//getDeclaredConstructors:获取本类中所有构造器Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();for (Constructor<?> declaredConstructor : declaredConstructors) {System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名}//getPackage:以Package形式返回 包信息System.out.println(personCls.getPackage());//com.hspedu.reflection//getSuperClass:以Class形式返回父类信息Class<?> superclass = personCls.getSuperclass();System.out.println("父类的class对象=" + superclass);////getInterfaces:以Class[]形式返回接口信息Class<?>[] interfaces = personCls.getInterfaces();for (Class<?> anInterface : interfaces) {System.out.println("接口信息=" + anInterface);}//getAnnotations:以Annotation[] 形式返回注解信息Annotation[] annotations = personCls.getAnnotations();for (Annotation annotation : annotations) {System.out.println("注解信息=" + annotation);//注解}}@Testpublic void api_02() throws ClassNotFoundException, NoSuchMethodException {//得到Class对象Class<?> personCls = Class.forName("com.hspedu.reflection.Person");//getDeclaredFields:获取本类中所有属性//规定 说明: 默认修饰符 是0 , public 是1 ,private 是 2 ,protected 是 4 , static 是 8 ,final 是 16Field[] declaredFields = personCls.getDeclaredFields();for (Field declaredField : declaredFields) {System.out.println("本类中所有属性=" + declaredField.getName()+ " 该属性的修饰符值=" + declaredField.getModifiers()+ " 该属性的类型=" + declaredField.getType());}//getDeclaredMethods:获取本类中所有方法Method[] declaredMethods = personCls.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {System.out.println("本类中所有方法=" + declaredMethod.getName()+ " 该方法的访问修饰符值=" + declaredMethod.getModifiers()+ " 该方法返回类型" + declaredMethod.getReturnType());//输出当前这个方法的形参数组情况Class<?>[] parameterTypes = declaredMethod.getParameterTypes();for (Class<?> parameterType : parameterTypes) {System.out.println("该方法的形参类型=" + parameterType);}}//getDeclaredConstructors:获取本类中所有构造器Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();for (Constructor<?> declaredConstructor : declaredConstructors) {System.out.println("====================");System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();for (Class<?> parameterType : parameterTypes) {System.out.println("该构造器的形参类型=" + parameterType);}}}}class A {public String hobby;public void hi() {}public A() {}public A(String name) {}
}interface IA {}
interface IB {}@Deprecated
class Person extends A implements IA, IB {//属性public String name;protected static int age; // 4 + 8 = 12String job;private double sal;//构造器public Person() {}public Person(String name) {}//私有的private Person(String name, int age) {}//方法public void m1(String name, int age, double sal) {}protected String m2() { return null; }void m3() {}private void m4() {}
}
18.5.2 通过反射创建对象
-
方法一:Class类相关方法
方法名 作用 newInstance()
调用类中的无参构造器,获取对应类的对象 getConstructor(Class..clazz)
根据参数列表,获取对应的public构造器对象 getDecalaredConstructor(Class..clazz)
根据参数列表,获取对应的所有构造器对象 -
方法二:Constructor类相关方法
方法名 作用 T newInstance(Object… initargs) 根据指定的构造器创建对象 setAccessible()
设置为true,表示取消访问检查,进行爆破,即可调用类中的私有成员和属性 注意:如果
要使用私有构造函数进行对象的创建
,则需要进行爆破:Constructor对象.setAccessible(true)
举例
public class ReflecCreateInstance {public static void main(String[] args) throws Exception {//1. 先获取到User类的Class对象Class<?> userClass = Class.forName("com.hspedu.reflection.User");//2. 通过public的无参构造器创建实例Object o = userClass.newInstance();System.out.println(o);//3. 通过public的有参构造器创建实例/*constructor 对象就是public User(String name) {//public的有参构造器this.name = name;}*///3.1 先得到对应构造器Constructor<?> constructor = userClass.getConstructor(String.class);//3.2 创建实例,并传入实参Object hsp = constructor.newInstance("hsp");System.out.println("hsp=" + hsp);//4. 通过非public的有参构造器创建实例//4.1 得到private的构造器对象Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);//4.2 创建实例//暴破【暴力破解】 , 使用反射可以访问private构造器/方法/属性, 反射面前,都是纸老虎constructor1.setAccessible(true);Object user2 = constructor1.newInstance(100, "张三丰");System.out.println("user2=" + user2);}
}class User { //User类private int age = 10;private String name = "韩顺平教育";public User() {//无参 public}public User(String name) {//public的有参构造器this.name = name;}private User(int age, String name) {//private 有参构造器this.age = age;this.name = name;}public String toString() {return "User [age=" + age + ", name=" + name + "]";}
}
18.5.3 通过反射访问类中的成员
1. 利用反射访问属性
基本介绍
-
获取Field对象
通过Class对象获取Field对象,以下属于Class类的方法
方法名 作用 Field[] getFields()
获取所有public修饰的属性,包含本类以及父类的 Field[] getDeclaredFields()
获取本类中所有属性(包括private的) Field getField(String name)
返回单个成员变量对象(只能拿public的) Field getDeclaredField(String name)
返回单个成员变量对象,存在就能拿到 -
访问属性
Field类中用于取值、赋值的方法,以下属于Field类的方法
方法名 作用 void set(Object obj, Object value)
赋值,其中obj代表要赋值的对象,value为要赋的值 Object get(Object obj)
获取值,其中obj为要获取值的对象 注意:
如果是静态属性,其中set和get中的参数obj可以写成null
- 如果要对私有属性进行set赋值,则需要进行爆破:
Field对象.setAccessible(true)
举例说明
public class ReflecAccessProperty {public static void main(String[] args) throws Exception {//1. 得到Student类对应的 Class对象Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");//2. 创建对象Object o = stuClass.newInstance();//o 的运行类型就是StudentSystem.out.println(o.getClass());//Student//3. 使用反射得到age 属性对象Field age = stuClass.getField("age");age.set(o, 88);//通过反射来操作属性System.out.println(o);//System.out.println(age.get(o));//返回age属性的值//4. 使用反射操作name 属性Field name = stuClass.getDeclaredField("name");//对name 进行暴破, 可以操作private 属性name.setAccessible(true);//name.set(o, "老韩");name.set(null, "老韩~");//因为name是static属性,因此 o 也可以写出nullSystem.out.println(o);System.out.println(name.get(o)); //获取属性值System.out.println(name.get(null));//获取属性值, 要求name是static}
}class Student {//类public int age;private static String name;public Student() {}//构造器public String toString() {return "Student [age=" + age + ", name=" + name + "]";}
}
2. 利用反射访问方法
基本介绍
-
获取Method对象
通过Class对象获取Method对象,以下属于Class类的方法
方法名 作用 Method[] getMethods()
获取所有public修饰的方法,包含本类以及父类的 Method[] getDeclaredMethods()
获取本类中所有方法(包括private的) getMethod(String name, Class<?>... parameterTypes)
根据方法名name,形参类型返回单个Method对象,只能public修饰 getDeclaredMethod(String name, Class<?>... parameterTypes)
根据方法名name,形参类型返回单个Method对象,可以得到private的 -
访问方法
Method类中用于调用对象方法的方法,以下属于Method类的方法
方法名 作用 Object invoke (Object obj, Object... args)
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:调用的方法的返回值注意:
如果是静态属性,其中invoke中的参数obj可以写成null
- 如果要调用私有方法,则需要进行爆破:
Method对象.setAccessible(true)
举例说明
public class ReflecAccessMethod {public static void main(String[] args) throws Exception {//1. 得到Boss类对应的Class对象Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss");//2. 创建对象Object o = bossCls.newInstance();//3. 调用public的hi方法//Method hi = bossCls.getMethod("hi", String.class);//OK//3.1 得到hi方法对象Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK//3.2 调用hi.invoke(o, "韩顺平教育~");//4. 调用private static 方法//4.1 得到 say 方法对象Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);//4.2 因为say方法是private, 所以需要暴破,原理和前面讲的构造器和属性一样say.setAccessible(true);System.out.println(say.invoke(o, 100, "张三", '男'));//4.3 因为say方法是static的,还可以这样调用 ,可以传入nullSystem.out.println(say.invoke(null, 200, "李四", '女'));//5. 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致Object reVal = say.invoke(null, 300, "王五", '男');System.out.println("reVal 的运行类型=" + reVal.getClass());//String//在演示一个返回的案例Method m1 = bossCls.getDeclaredMethod("m1");Object reVal2 = m1.invoke(o);System.out.println("reVal2的运行类型=" + reVal2.getClass());//Monster}
}class Monster {}
class Boss {//类public int age;private static String name;public Boss() {}//构造器public Monster m1() {return new Monster();}private static String say(int n, String s, char c) {//静态方法return n + " " + s + " " + c;}public void hi(String s) {//普通public方法System.out.println("hi " + s);}
}
18.5.4 反射作用理解
-
绕过编译阶段为集合添加数据
反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的
ArrayList<Integer> list = new ArrayList<>(); list.add(100); // list.add(“字符串"); // 报错 list.add(99);
泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,泛型相当于被擦除了
public class ReflectDemo {public static void main(String[] args) throws Exception {// 需求:反射实现泛型擦除后,加入其他类型的元素ArrayList<String> lists1 = new ArrayList<>();ArrayList<Integer> lists2 = new ArrayList<>();System.out.println(lists1.getClass()); // ArrayList.classSystem.out.println(lists2.getClass());System.out.println(lists1.getClass() == lists2.getClass()); // trueSystem.out.println("---------------------------");ArrayList<Integer> lists3 = new ArrayList<>();lists3.add(23);Class c = lists3.getClass(); // ArrayList.class ===> public boolean add(E e)// 定位c类中的add方法Method add = c.getDeclaredMethod("add", Object.class);boolean rs = (boolean) add.invoke(lists3, "字符串");System.out.println(rs);System.out.println(lists3);ArrayList list4 = lists3;list4.add("这样将lists3赋值给list4,然后通过list4添加其他类型元素也相当于擦除泛型");list4.add(false);System.out.println(lists3);} }
-
通用框架的底层原理
需求:给你任意一个对象,在不清楚对象字段的情况可以,可以把对象的字段名称和对应值存储到文件中去
分析:
① 定义一个方法,可以接收任意类的对象。
② 每次收到一个对象后,需要解析这个对象的全部成员变量名称。
③ 这个对象可能是任意的,那么怎么样才可以知道这个对象的全部成员变量名称呢?
④ 使用反射获取对象的Class类对象,然后获取全部成员变量信息。
⑤ 遍历成员变量信息,然后提取本成员变量在对象中的具体值
⑥ 存入成员变量名称和值到文件中去即可。public class MybatisUtil {/**保存任意类型的对象* @param obj*/public static void save(Object obj){try (PrintStream ps = new PrintStream(new FileOutputStream("src/data.txt", true));){// 1、提取这个对象的全部成员变量:只有反射可以解决// c.getSimpleName()获取当前类名 c.getName获取全限名:包名+类名Class c = obj.getClass(); ps.println("================" + c.getSimpleName() + "================");// 2、提取它的全部成员变量Field[] fields = c.getDeclaredFields();// 3、获取成员变量的信息for (Field field : fields) {String name = field.getName();// 提取本成员变量在obj对象中的值(取值)field.setAccessible(true);String value = field.get(obj) + "";ps.println(name + "=" + value);}} catch (Exception e) {e.printStackTrace();}} }
public class ReflectDemo {public static void main(String[] args) throws Exception {Student s = new Student();s.setName("猪八戒");s.setClassName("西天跑路1班");s.setAge(1000);s.setHobby("吃,睡");s.setSex('男');MybatisUtil.save(s);Teacher t = new Teacher();t.setName("波仔");t.setSex('男');t.setSalary(6000);MybatisUtil.save(t);} }
18.5.5 注解 & 反射
-
注解解析
注解的操作中经常需要进行解析,
注解的解析就是判断是否存在注解
,存在注解就解析出内容
与注解解析相关的接口
Annotation
:注解的顶级接口,注解都是Annotation类型的对象AnnotatedElement
:该接口定义了与注解解析相关的解析方法
方法 说明 Annotation[] getDeclaredAnnotations()
获得当前对象上使用的所有注解,返回注解数组 T getDeclaredAnnotation(Class<T> annotationClass)
根据注解类型获得对应注解对象 boolean isAnnotationPresent(Class<Annotation> annotationClass)
判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false 注意:所有的类成分Class, Method , Field , Constructor,都实现了AnnotatedElement接口,它们都拥有解析注解的能力,即只要能被注解修饰的对象,都有上述这些方法,例如
Class对象,Field对象,Method对象等
解析注解的技巧:
注解在哪个成分上,我们就先拿哪个成分对象
。比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解 -
举例说明
@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Book {String value();double price() default 100;String[] author(); }
public class AnnotationDemo3 {@Testpublic void parseClass(){// a.先得到类对象Class c = BookStore.class;// b.判断这个类上面是否存在这个注解if(c.isAnnotationPresent(Book.class)){//c.直接获取该注解对象Book book = (Book) c.getDeclaredAnnotation(Book.class);System.out.println(book.value());System.out.println(book.price());System.out.println(Arrays.toString(book.author()));}}@Testpublic void parseMethod() throws NoSuchMethodException {// a.先得到类对象Class c = BookStore.class;Method m = c.getDeclaredMethod("test");// b.判断这个类上面是否存在这个注解if(m.isAnnotationPresent(Book.class)){//c.直接获取该注解对象Book book = (Book) m.getDeclaredAnnotation(Book.class);System.out.println(book.value());System.out.println(book.price());System.out.println(Arrays.toString(book.author()));}} }@Book(value = "《情深深雨濛濛》", price = 99.9, author = {"琼瑶", "dlei"}) class BookStore{@Book(value = "《三少爷的剑》", price = 399.9, author = {"古龙", "熊耀华"})public void test(){} }
-
注解解析应用:模拟JUnit框架
@Target({ElementType.METHOD}) // 元注解 @Retention(RetentionPolicy.RUNTIME) // 一直活着,在运行阶段这个注解也不消失 public @interface MyTest { }
public class AnnotationDemo4 {public void test1(){System.out.println("===test1===");}@MyTestpublic void test2(){System.out.println("===test2===");}@MyTestpublic void test3(){System.out.println("===test3===");}public static void main(String[] args) throws Exception {AnnotationDemo4 t = new AnnotationDemo4();// a.获取类对象Class c = AnnotationDemo4.class;// b.提取全部方法Method[] methods = c.getDeclaredMethods();// c.遍历方法,看是否有MyTest注解,有就跑它for (Method method : methods) {if(method.isAnnotationPresent(MyTest.class)){// 跑它method.invoke(t);}}} }