反射机制
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("一天被气得睡不着");
} }效果图如下:
-
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)、通过反射对类中的方法进行操作
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、哪些类型可以有类对象?
- 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 就出来相应的东西
最后:有兴趣的可以利用反射玩一下其他的,如:集合中不是支持泛型吗,那这样的向集合中添加元素就只能采用泛型的类型了
但是:可以使用反射机制对这个方法进行修改,即:添加元素时参数类型不是泛型规定的类型,照样可以做到把元素添加进去