-
javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕
反射机制
1、反射机制是什么?————英文单词是:reflect。在java.lang包下———这才是java最牛逼的技术
-
首先提前知道一句话————在java中,有了对象,于是有了类,那么有了类之后,也就有了反射
- 因此:学反射机制,需要做到一件事————把思想从以前的类中跳出来,将思想再拔高一个层次——站在类的头上思考问题
-
那么到底什么是反射机制?
- 就是用来动态的操作类( 含静态操作类本身【 名字、修饰符、注解... 】、动态操作类中的成员信息【 属性、方法 ....】 )
-
为什么需要学习反射?
- 1、因为这是javaSE中最重要的一门技术,可以说前面说的所有东西,甚至后面的javaSE知识部分,都是为了这门技术服务
-
2、因为这是后面学java框架的必备技术,在框架的底层中就是应用了反射机制( 如:为什么配置一些诸如xml的文件,系统就可以帮忙去加载相应的东西,就是应用了反射这门技术 )
- 多说一嘴:在javaSE中最核心的知识便是————反射、枚举、【 注解 】( 其他的知识都是为了这些知识服务 )————这些东西是学框架必会的技术( 不然看源码都看不懂,这样也就只能限于用框架而不懂框架了 )
2、反射机制中有什么?
-
Class ———— 用来描述类本身( 注:是大写的C,不是小写的c【 小写是关键字 】 )————这也是最重要的一个( 因为它包含了后面说的那些东西 )
-
类本身有什么?
- 修饰符( 权限、特征 )、类名、继承关系、实现关系、类成员( 属性、一般方法、构造方法 )
-
类本身有什么?
- Package ———— 用来描述类所属的包
- Field ———— 用来描述类中的属性
- Method ———— 用来描述类中的方法
- Constructor ———— 用来描述类中的构造方法
- Annotation ———— 用来描述类中的注解 ( 这个在后续会讲解 )
1、扯了这么多卵球烦的东西,还是来实操吧( 不再单独列出有哪些方法,而是直接上代码 )
- 学了Class,其他的也就没问题了
-
-
1)、对于类本身的操作
-
在这里需要解释一个东西
-
Class是获取类对象,那么以前 new 类名() 的方式创建的类的对象( 即:类的实例 ),怎么理解这两个?
-
反射不是在类之上吗,所以Class获取的是:类这个模板的对象( 是整体的对象 );而 new 类名() 它创建的是类这种类型的某一个对象 ( 是类中的一个 个体 )————测试一下嘛:看Class创建的多个类对象一不一样 和 new创建的类的多个对象一不一样
- 在这里就不展示了,自己动手做( 答案是:Class创建的类对象是一样的 【 注意:用的是同一个类啊,不是同一个类,如:Person和Teacher类,然后用Class创建类对象,这一样个锤子 】——而:new创建出来的类的对象肯定不一样,这在前面阶段就已经知道原因了( new是在堆内存中 )
-
反射不是在类之上吗,所以Class获取的是:类这个模板的对象( 是整体的对象 );而 new 类名() 它创建的是类这种类型的某一个对象 ( 是类中的一个 个体 )————测试一下嘛:看Class创建的多个类对象一不一样 和 new创建的类的多个对象一不一样
-
Class是获取类对象,那么以前 new 类名() 的方式创建的类的对象( 即:类的实例 ),怎么理解这两个?
-
package cn.xieGongZi.Class.playClassOneSelf; // 对类本身进行操作 public class Demo { public static void main(String[] args) { // 获取反射对象( 即:类对象 ) // 1、类名.class 最常用( 这个class是关键字,小写的那个 ) Class<Teacher> tc = Teacher.class; // 这里为什么需要用泛型?在下面揭晓 // 2、类的对象.getClass // Class<? extends Teacher> tc = new Teacher().getClass(); // 3、Class.forName( " 全路径包名.类名 " ) ———— 要处理异常,不然万一()里面的内容手贱写错了,找不到这个类呢 // Class<?> tc = Class.forName("cn.xieGongZi.Class.Teacher"); // 获取了这个对象之后可以干的事情 // 1、获取类本身的东西 System.out.println("对类本身进行操作"); String name = tc.getName(); System.out.println( "获取类的全路径名( 含包名 ):" + name ); // 获取这个类单纯的名字,不要什么包名之类的 String simpleName = tc.getSimpleName(); System.out.println( "获取类的简单名字 ( 即:没有什么包名之类的 ): " + simpleName); // 2、获取类的修饰符 int modifiers = tc.getModifiers(); // 注意:结果是一个整型哦 // 0 表示默认不写 // 1 表示public // 2 表示private // 3 表示protected——————以上表示权限修饰符的,特征修饰符的( 如:4 表示static )就不展示了 System.out.println( "获取类的修饰符:" + modifiers); // 3、获取这个类实现的接口是哪些 ———— 注意:返回值是一个Class类型的数组 Class<?>[] classImpl = tc.getInterfaces(); for (Class<?> interfaceName : classImpl) { System.out.println( "获取类实现的接口名( 但:这是全路径名[ 简单名还需要用getSimpleName() ] ): " + interfaceName.getSimpleName()); } // 4、获取类继承了哪些类 Class<? super Teacher> superclass = tc.getSuperclass(); System.out.println("获取类继承的类名:" + superclass.getSimpleName()); // 对于类本身需要玩的东西,好像就没了——————其他的方法就是字面意思( 如:判断类是不是接口啊、是不是枚举啊之类的 ) // 5、给类创建对象 try { Teacher teacher = tc.newInstance(); // 在这里创建类的对象的时候,如果在前面创建类对象的时候,没有用泛型 // 那么这里创建类的对象时就需要进行造型了 // 所以:这就是创建类对象的时候采用泛型的原因 System.out.println("这个对象是通过反射机制帮忙创建出来的:" + teacher); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } class Person{ private String name; private char sex; public int age; String address; public void study() { System.out.println("是人类学个锤子,快来嗨"); } public void heiHei() { System.out.println("是人类就要你说嗯来,我说嘿"); } } interface Fly{ } interface Run{ } class Teacher extends Person implements Fly,Run { public String phone; public String school; private boolean isCool; private String nature; public void eat(){ System.out.println("人类的儿子 —— 老师吃得贼球好"); } public void sleep(){ System.out.println("一天被气得睡不着"); } }
效果图如下:
-
在这里需要解释一个东西
-
1)、对于类本身的操作
-
-
2)、对于类中的成员进行操作
- (1)、属性的获取、属性的修改
-
package cn.xieGongZi.Class.playClassInMember; import java.lang.reflect.Field; // 对类中的成员进行操作 public class Play { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException { System.out.println( "对类中的成员进行操作 "); System.out.println(); // 获取类对象 Class<Teacher> tc = Teacher.class; System.out.println("对类的属性进行操作:查看有哪些属性 及 修改属性值"); // 1、获取类属性的名字————只获取 类本身的、继承过来的 public修饰的 属性 Field[] fields = tc.getFields(); for (Field field : fields) { System.out.println( "这个方法获取的是:类本身以及继承过来的 公有的( public ) 属性名字:" + field.getName()); } System.out.println(); // 获取 类本身的 所有属性( 含私有的 ) Field[] allField = tc.getDeclaredFields(); for (Field field : allField) { System.out.println( "这个方法是获取 类本身的 所有属性( 含private修饰的 ):" + field.getName() ); } // 那想要获取父类中的属性呢?( 含private修饰的 ) Field[] parentFields = tc.getSuperclass().getDeclaredFields(); for (Field parentField : parentFields) { System.out.println( "这是Teacher的父类属性:" + parentField .getName() ); } System.out.println(); System.out.println("对类中的属性进行操作:赋值"); Field phone = tc.getField("phone"); // getField()会抛一个异常:NoSuchFieldException // 没有这样的属性( 怕输错了找不到嘛 ) Teacher teacher = tc.newInstance(); // 创建类的对象,因为:利用set()方法给属性赋值时 需要 类的对象来做参数 phone.set( teacher,"邪公子"); // newInstance()会抛两个异常————IllegalAccessException, InstantiationException // IllegalAccessException————指赋的这个值,类不能接收。为什么不可以接收,马上揭晓 // InstantiationException————对象实例化异常 System.out.println( "现在类中的phone属性就有值了:" + teacher.phone ); // 这是public修饰的属性嘛,所以能改也没什么好奇怪的 // 可是要搞事情啊 ———— 修改类中private修饰的属性 // 老规矩:先拿到属性涩————可是要拿的属性包含private修饰的属性( 不然咋体现反射的魅力呢 ) Field nature = tc.getDeclaredField("nature"); // 这个方法不是可以获取 类本身的 所有属性吗( private修饰的不就都包含进来了 ) // 这是它的重载方法而已 // 现在就可以进行修改属性了 nature.setAccessible(true); // 提供修改属性的权限,这一步很重要,修改private修饰的属性就必须要这一步 nature.set( teacher,"这是一个帅气、又欠揍的人" ); // 但是这样直接修改属性会抛异常:即 IllegalAccessException ———— 因为这是私有属性 // 因此:这里还需要另一个方法:setAccessible( boolean b ) ———— 这个方法是:提供修改这个属性的权限 System.out.println("这是通过反射机制修改的private属性:" + nature.get(teacher) ); // nature.get(teacher) 相当于:teacher.getNature ———— 反着过来的而已 // 只是在这里:主要是为了玩反射,所以Teacher中并没有设置get和set方法 // 这个get()方法的意思就是:返回 该字段( 调它的字段 ) 在给定对象中的值 // 以上便是反射对类属性的查看和修改 } } class Person{ private String name; private char sex; public int age; String address; public void study() { System.out.println("是人类学个锤子,快来嗨"); } public void heiHei() { System.out.println("是人类就要你说嗯来,我说嘿"); } } interface Fly{ } interface Run{ } class Teacher extends Person implements Fly,Run{ public String phone; public String school; private boolean isCool; private String nature; public void eat() { System.out.println("人类的儿子——老师吃得贼球好"); } public void sleep() { System.out.println("一天被气得睡不着"); } }
-
2)、对于类中的成员进行操作
-
-
(2)、通过反射对类中的方法进行操作
-
package cn.xieGongZi.Class.playClassinMethod; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; // 通过反射玩儿类中的方法 public class Play { public static void main(String[] args) { System.out.println("通过反射玩儿类中的方法"); Class<Teacher> tc = Teacher.class; // 1、查看类有哪些方法 Method[] methods = tc.getMethods(); for (Method method : methods) { System.out.println("这是查看 类本身的、继承过来的 public修饰的方法:" + method.getName()); } System.out.println(); // 2、查看 类本身的 所有方法 ( 含private修饰的 ) Method[] allMethods = tc.getDeclaredMethods(); for (Method method : allMethods) { System.out.println( "这是查看 类本身的 所有方法 ( 含 private修饰的 ):" + method.getName()); } System.out.println(); // 3、让类中的方法给我运行起来( 这是运行public修饰的方法 ) try { Teacher teacher = tc.newInstance(); System.out.println("通过反射运行了类中public修饰的方法"); teacher.eat(); teacher.study(); // 以上这些都是运行类中的public方法 ———— 但太low,体现不出反射的精髓 } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } System.out.println(); // 4、如果想要让private修饰的方法也运行呢? try { Method sleepMethod = tc.getDeclaredMethod("sleep", null); // getDeclaredMethod( “sleep",null )方法的参数说明: // 第一个 类中方法的名字 第二个 类中方法的参数类型 // 对第二个参数的补充: // 没参数是null,如果有参数,则:需要通过参数类型.class进行说明 // 如:int就是int.class、float就是float.class————支持多个参数 sleepMethod.setAccessible(true); // 给操作private修饰的方法提供可以修改的权限 System.out.println("运行了类中private修饰的方法"); sleepMethod.invoke(tc.newInstance(), null); // 虽然可以运行,但是这个违背了java的设计原则 // 即:private封装之后的方法只能在本类中调用 // 因此:不建议使用这种反射 // 在这里只是为了说明反射可以做到而已 } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } class Person{ public void study() { System.out.println("是人类学个锤子,快来嗨"); } private void heiHei() { // 这里改成private来进行测试 System.out.println("是人类就要你说嗯来,我说嘿"); } } interface Fly{ } interface Run{ } class Teacher extends Person implements Fly, Run { public void eat() { System.out.println("人类的儿子 —— 老师吃得贼球好"); } private void sleep() { // 改成私有的方法 System.out.println("一天被气得睡不着"); } }
-
-
(2)、通过反射对类中的方法进行操作
2、哪些类型可以有类对象?
- 1)、基本数据类型对应的包装类
- 2)、String类型
- 3)、注解类型
- 4)、数组( 含一维和二维 )
- 5)、枚举类型
- 6)、类
- 7)、接口
- 8)、Void类型
package cn.xieGongZi.haveClassType;
import java.lang.annotation.RetentionPolicy;
public class Demo {
public static void main(String[] args) {
// 包装类类型
Class<Integer> c1 = Integer.class;
// String类型
Class<String> c2 = String.class;
// 数组
Class<Integer[]> c3 = Integer[].class;
Class<Byte[][]> c4 = Byte[][].class;
// 枚举类型
Class<RetentionPolicy> c5 = RetentionPolicy.class; // 这个枚举类在注解中会见到
// 接口类型
Class<Comparable> c6 = Comparable.class;
// void类型
Class<Void> c7 = void.class;
// 注解类型
Class<Override> c8 = Override.class;
// 类
Class<Demo> c9 = Demo.class; // 当前测试的这个类嘛
// 测试看一下嘛
System.out.println( c1 );
System.out.println( c2 );
System.out.println( c3 );
System.out.println( c4 );
System.out.println( c5 );
System.out.println( c6 );
System.out.println( c7 );
System.out.println( c8 );
System.out.println( c9 );
}
}
效果如下:
扩展
3、类加载过程( 即:ClassLoader ) ———— 了解即可
还是用代码举个例子:
package cn.xieGongZi.classLoader;
public class Test {
public static void main(String[] args) {
// 建一个A类的对象
new A();
}
}
// 假如有一个类 A
class A{
// 有一个变量
static int a;
// 有一个静态代码块
static {
System.out.println("静态代码块");
a = 20;
}
// 第二个静态代码块
static {
a = 50;
System.out.println( a );
}
// 来个无参构造
public A() {
System.out.println("这个无参构造");
}
}
效果如下:
画个图分析一下:
-
-
2)、类什么时候会被初始化? ———— 类的主动引用会导致初始化
- 当虚拟机启动的时候,会先初始化main()方法
- new 一个类的对象
- 调用一个类的静态成员 ( 含静态方法、静态属性【 但是:调用final修饰的常量不会触发初始化 】 )
- 利用java.lang.reflect包下的方法 进行 反射调用
- 多提一嘴:当在初始化一个类时,发现其父类没有初始化,那么就会先初始化父类
-
3)、类在什么情况下不会初始化? ———— 类的被动引用不会导致初始化
- 当访问一个静态域 ( 一块静态元素区 )时,只有声明这个域的类才会被初始化,反之:另外类访问这个静态域,那么:这里说的另外类就不会初始化,如:子类引用父类的静态常量,则:子类不会被初始化
- 通过数组定义类引用 ( 即:这个数组类型是一个类类型 ),不会导致这个类被初始化
- 调用一个类的静态常量时,不会导致这个类被初始化 ( 因为:常量在链接阶段就已经在常量池中了 )
4、类加载器
-
类加载器的作用
- 就是为了把class文件加载到内存中,从而把静态数据弄成方法区中运行时的数据结构,同时在堆空间中生成一个java.lang.Class类对象,作为方法区中的数据访问入口
-
-
多说一嘴:类缓存
-
指的是:标准的JavaSE类加载器,可以根据要求查找类,如果发现这个类已经在类加载器中去了,那么:这个类就会再加载 ( 缓存 )一段时间
- 但是:GC回收机制是可以回收这些Class对象的
-
指的是:标准的JavaSE类加载器,可以根据要求查找类,如果发现这个类已经在类加载器中去了,那么:这个类就会再加载 ( 缓存 )一段时间
-
多说一嘴:类缓存
-
玩玩儿类加载器
-
package cn.xieGongZi.classLoader.thinkClassLoader; public class Demo { public static void main(String[] args) { // 查看当前类是谁加载的 ClassLoader thisClass = Demo.class.getClassLoader(); System.out.println( "当前类是 " + thisClass + " 加载的" ); // 查看这个类加载器的父类加载器 ———— 即:扩展类加载器 ClassLoader fatherClassLoader = thisClass.getParent(); System.out.println( "当前类的父类加载器是 " + fatherClassLoader ); // 查看这个加载器的父类的父类加载器 ———— 即:根加载器( 注意:根加载器是C++写的,获取不到 ,即为null ) ClassLoader grandFather = fatherClassLoader.getParent(); System.out.println( "这个类加载的祖类加载器( 根加载器 )是 " + grandFather ); // 看看JDK内置的类是谁加载的 ———— 举个例子:Object类 ClassLoader JDKClassLoader = Object.class.getClassLoader(); System.out.println( "JDK内置的类是 " + JDKClassLoader + "加载的" ); System.out.println(); // 来查看一下系统类加载器可以加载的路径是哪些? String classLoadPath = System.getProperty("java.class.path"); System.out.println( classLoadPath ); /* 类加载可以加载的路径如下( 反之:也可以得出,类需要在以下路径中才可以被加载 ) D:\IntallationList\JDK IntallationList\jre\lib\charsets.jar; D:\IntallationList\JDK IntallationList\jre\lib\deploy.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\access-bridge-64.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\cldrdata.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\dnsns.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\jaccess.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\jfxrt.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\localedata.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\nashorn.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\sunec.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\sunjce_provider.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\sunmscapi.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\sunpkcs11.jar; D:\IntallationList\JDK IntallationList\jre\lib\ext\zipfs.jar; D:\IntallationList\JDK IntallationList\jre\lib\javaws.jar; D:\IntallationList\JDK IntallationList\jre\lib\jce.jar; D:\IntallationList\JDK IntallationList\jre\lib\jfr.jar; D:\IntallationList\JDK IntallationList\jre\lib\jfxswt.jar; D:\IntallationList\JDK IntallationList\jre\lib\jsse.jar; D:\IntallationList\JDK IntallationList\jre\lib\management-agent.jar; D:\IntallationList\JDK IntallationList\jre\lib\plugin.jar; D:\IntallationList\JDK IntallationList\jre\lib\resources.jar; D:\IntallationList\JDK IntallationList\jre\lib\rt.jar; D:\JavaTrainStudy\javaTrainStudy\out\production\study-09-reflect; // 自己写的类也被加载到了 D:\IntallationList\IdeaIntallationList\IntelliJ IDEA 2020.3\lib\idea_rt.jar */ } }
-
-
这里顺便补充一下:什么叫双亲委派机制
-
就是从类加载器 自底向上依次查看类加载器中有没有加载特定类的包
-
如:java.lang.String,系统类加载器先看有没有加载这个包,没有的话就会去找扩展类加载器看有没有加载,还没有的话,就会去根加载器中查看
-
所以这里就引申出另一个答案:为什么取类名的时候,不能和java中现有的类名重复
- 就是因为这个双亲委派机制,它会依次这样去找,发现自己就有,所以自己定义的那个 和 java本身有的类 重名的类就不会生效
-
所以这里就引申出另一个答案:为什么取类名的时候,不能和java中现有的类名重复
-
如:java.lang.String,系统类加载器先看有没有加载这个包,没有的话就会去找扩展类加载器看有没有加载,还没有的话,就会去根加载器中查看
-
就是从类加载器 自底向上依次查看类加载器中有没有加载特定类的包
-
反射机制也就这么回事儿,也没什么好说的了,主要的就是这个Class,那些什么Package、Constuctor......都是包含在Class中的,因此:只要通过 类对象.get 就出来相应的东西
最后:有兴趣的可以利用反射玩一下其他的,如:集合中不是支持泛型吗,那这样的向集合中添加元素就只能采用泛型的类型了
但是:可以使用反射机制对这个方法进行修改,即:添加元素时参数类型不是泛型规定的类型,照样可以做到把元素添加进去
作者:紫邪情
出 处:https://www.cnblogs.com/xiegongzi/p/15154820.html