day21_反射&枚举
课程目标
1. 【理解】类加载器
2. 【理解】什么是反射
3. 【掌握】获取Class对象的三种方式
4. 【掌握】反射获取构造方法并创建对象
5. 【掌握】反射获取成员变量并使用
6. 【掌握】反射获取成员方法并使用
7. 【掌握】反射综合案例
8. 【理解】枚举
类加载器
类加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的 【加载】,【连接】,【初始化】 这三个步骤来对类进行初始化。
如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化
一个类的生命周期包括了 “加载”、“验证”、“准备”、“解析”、“初始化”、“使用”、“卸载” 这七个阶段,
一般我们只研究前五个阶段,这五个阶段又可以分为 “加载”、“连接、验证,解析” 、 “初始化”
类的加载
类的加载就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象
用于加载二进制数据,类的加载主要做三件事情:
-
找到类文件(通过类的全限定名来获取定义此类的二进制字节流)
首先会根据各种途径(比如网络下载、数据库提取、从jar,zip中读取等)获取类的二进制数据
-
放入方法区(将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构)
把获取到的二进制数据读入内存,存储在运行时数据区的方法区,
这些二进制数据所代表的存储结构会被转化为方法区中运行时的数据结构 -
开个入口(生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口)
在方法区中创建相应的class对象(这里的class对象与平时所说的对象是不一样的,
当使用new创建实例对象时,就会通过class对象在堆中创建实例对象)用来封装保存在方法区内的数据结构
类的连接
-
【验证阶段】:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
文件格式验证:验证字节流是否符合class文件的规范,确保输入的字节流能正确解析并存储到方法区
元数据验证:对字节码描述的信息进行语义分析,保证其描述的信息符合规范要求。
字节码验证:这个阶段是比较复杂的,通过数据流和控制流分析,对类的方法体进行校验,确保程序的合法性 符号引用验证:这里的符号引用不单单指类的,也包括方法。
发生在符号引用转为直接引用的时候,也就是解析阶段,
对常量池中各种符号引用的信息进行匹配性校验,确保解析动作正确执行 -
【准备阶段】:负责为类的类变量分配内存,并设置默认初始化值
需要注意的是:
* 静态变量只会给默认值。 * 例:public static int value = 123; // 此时赋给value的值是0,不是123。 * 静态常量(static final修饰的)则会直接赋值。 * 例:public static final int value = 123; // 此时赋给value的值是123。
-
【解析阶段】:将类的二进制数据中的符号引用替换为直接引用
类的初始化
类的初始化的主要工作是为静态变量赋程序设定的初值。
类的初始化步骤
- 假如类还未被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还未被初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
- 注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3
类的初始化时机
- 创建类的实例
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
类加载器
类加载器的作用
负责将.class文件加载到内存中,并为之生成对应的 java.lang.Class 对象。虽然我们不用过分关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行!
简单来说:类加载器的作用,就是把class文件装进虚拟机
类加载机制
-
全盘负责
就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
-
父类委托
就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
-
缓存机制
保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区
双亲委派机制
-
当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
-
当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载, 一直到bootstrp ClassLoader.
-
当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
这样做的好处是什么?
1.避免重复加载。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2.为了安全。避免核心类,比如String被替换。
Java中的内置类加载器
当JVM启动的时候,Java缺省开始使用如下三种类加载器
-
Bootstrap ClassLoader
引导类加载器(根类加载器), 用来加载 Java 的核心库,是用原生代码来实现的,
比如:System.String等,jre的lib下rt.jar文件中 -
Platform ClassLoader
平台类加载器可以看到所有平台类 ,平台类包括由平台类加载器或其祖先定义的Java SE平台API,其实现类和JDK特定的运行时类
-
System ClassLoader
它也被称为应用程序类加载器(系统类加载器)(ApplicationClassLoader) ,与平台类加载器不同。 系统类加载器通常用于定义应用程序类路径,模块路径和JDK特定工具上的类
负责在jvm启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar和类路径
-
UserClass Loader
自定义类加载器
如果核心包中没有项目所需要的jar,还有一个扩展加载器:将扩展的jar进行加载
类加载器的继承关系:System的父加载器为Platform,而Platform的父加载器为Bootstrap
反射
反射概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;对于这种动态的获取信息以及动态调用对象的方法的功能称为:java语言的反射机制。
反射理解:
可以通你这个对象拿到字节码文件,通过子节码文件还原到类的本身(也就是说你拿到类的Class对象去使用这个类的成员方法,成员变量,构造方法)
获取Class类对象的三种方式
- 类名.class属性
- 对象名.getClass()方法
- Class.forName(全限定类名)方法
public class Person {
//私有成员变量
private String name;
//默认成员变量
int age;
//公有成员变量
public String address;
//公有无参构造方法
public Person() {
}
//私有的有参构造方法 1个参数
private Person(String name) {
this.name = name;
}
//默认的有参构造方法 2个参数
Person(String name, int age) {
this.name = name;
this.age = age;
}
//公有的有参构造方法 3个参数
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
//公有的无参无返回值方法
public void show() {
System.out.println("show");
}
//公有的有参无返回值方法
public void method(String s) {
System.out.println("method " + s);
}
//公有的有参有返回值方法
public String getString(String s, int i) {
return s + "---" + i;
}
//私有的无参无返回值方法
private void function() {
System.out.println("function");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address=" + address
+ "]";
}
}
/*
* 反射:就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法。
*
* Person p = new Person();
* p.使用
*
* 要想这样使用,首先你必须得到class文件对象,其实也就是得到Class类的对象。
* Class类:
* 成员变量 Field
* 构造方法 Constructor
* 成员方法 Method
*
* 获取class文件对象的方式:
* A:Object类的getClass()方法
* B:数据类型的静态属性class
* C:Class类中的静态方法
* public static Class forName(String className)
*
* 一般我们到底使用谁呢?
* A:自己玩 任选一种,第二种比较方便
* B:开发 第三种
* 为什么呢?因为第三种是一个字符串,而不是一个具体的类名。这样我们就可以把这样的字符串配置到配置文件中,灵活性较高。
*/
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1
Person p = new Person();
Class c = p.getClass();
Person p2 = new Person();
Class c2 = p2.getClass();
System.out.println(p == p2);// false
System.out.println(c == c2);// true
// 方式2
Class c3 = Person.class;
System.out.println(c == c3);
// 方式3
// ClassNotFoundException
Class c4 = Class.forName("cn.yanqi.Person");
System.out.println(c == c4);
}
}
获取构造方法
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor getConstructor(Class<?>… parameterTypes) | 返回单个公共构造方法对象 |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 返回单个构造方法对象 |
3.3.2 用于创建对象的方法
方法名 | 说明 |
---|---|
T newInstance(Object…initargs) | 根据指定的构造方法创建对象 |
3.3.3 代码实现
//获取无构造方法
public static void main(String[] args) throws Exception{
//获取字节码文件对象
Class c = Class.forName("cn.yanqi.fanse_14.Person");
/*
Constructor[] c1 = c.getConstructors();//获取public修饰的构造
for(Constructor cc : c1 ){
System.out.println(cc);
}
*/
/*
Constructor[] c2 = c.getDeclaredConstructors();//获取所有的构造,包括私有
for(Constructor cc : c2 ){
System.out.println(cc);
}
*/
//获取单个构造方法
Constructor con = c.getConstructor();
//相当于 Person p = new Person();
Object obj = con.newInstance();
System.out.println(obj);
}
获取带参public构造方法
//获取带参构造方法
public static void main(String[] args) throws Exception {
//获取字节码文件对象
Class c = Class.forName("cn.yanqi.fanse_14.Person");
//获取单个构造方法
Constructor con = c.getConstructor(String.class , int.class , String.class);
System.out.println(con);
Object obj = con.newInstance("江一燕", 33 ,"上海");
System.out.println(obj);
}
获取私带参构造方法
public static void main(String[] args) throws Exception {
Class c = Class.forName("cn.yanqi.fanse_14.Person");
//获取私有构造方法对象
Constructor con = c.getDeclaredConstructor(String.class);//获取所有(不管是什么修饰)
con.setAccessible(true);//暴力访问 值为true则指示反射的对象在使用时应该取消Java语言访问检查。
Object obj = con.newInstance("yanqi");
System.out.println(obj);
}
获取成员变量
3.4.1 Class类获取成员变量对象的方法
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
void set(Object obj,Object value) | 给obj对象的成员变量赋值为value |
3.4.2 代码演示
public static void main(String[] args) throws Exception {
// 获取字节码文件对象
Class c = Class.forName("cn.yanqi.fanse_14.Person");
// 获取所有的成员变量
//Field[] fields = c.getFields();//获取public 修饰的
Field[] fields = c.getDeclaredFields();//获取所有成员变量(不管是什么修饰)
for (Field field : fields) {
System.out.println(field);
}
// 通过无参构造方法创建对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
System.out.println(obj);
// 获取单个的成员变量
// 获取address并对其赋值
Field addressField = c.getField("address");
// 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
addressField.set(obj, "北京"); // 给obj对象的addressField字段设置值为"北京"
System.out.println(obj);
// 获取name并对其赋值
// NoSuchFieldException
Field nameField = c.getDeclaredField("name");
// IllegalAccessException
nameField.setAccessible(true);//可以直接访问到私有的
nameField.set(obj, "江一燕");
System.out.println(obj);
// 获取age并对其赋值
Field ageField = c.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(obj, 33);
System.out.println(obj);
}
获取成员方法
3.5.1 Class类获取成员方法对象的方法
方法名 | 说明 |
---|---|
Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的 |
Method getMethod(String name, Class<?>… parameterTypes) | 返回单个公共成员方法对象 |
Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象 |
Object invoke(Object obj,Object… args) | 调用obj对象的成员方法,参数是args,返回值是Object类型 |
public static void main(String[] args) throws Exception {
// 获取字节码文件对象
Class c = Class.forName("cn.yanqi.fanse_14.Person");
// 获取所有的方法
// Method[] methods = c.getMethods(); // 获取自己的包括父亲的公共方法
Method[] methods = c.getDeclaredMethods(); // 获取自己的所有的方法
for (Method method : methods) {
System.out.println(method);
}
Constructor con = c.getConstructor();
Object obj = con.newInstance();
// 获取单个方法并使用
// public void show()
// 第一个参数表示的方法名,第二个参数表示的是方法的参数的class类型
Method m1 = c.getMethod("show");
// public Object invoke(Object obj,Object... args)
// 返回值是Object接收,第一个参数表示对象是谁,第二参数表示调用该方法的实际参数
m1.invoke(obj); // 调用obj对象的m1方法 相当于p.show()方法
System.out.println("----------");
// public void method(String s)
Method m2 = c.getMethod("method", String.class);
m2.invoke(obj, "hello");
System.out.println("----------");
// public String getString(String s, int i)
Method m3 = c.getMethod("getString", String.class, int.class);
Object objString = m3.invoke(obj, "hello", 100);
System.out.println(objString);
System.out.println("----------");
// private void function()
Method m4 = c.getDeclaredMethod("function");
m4.setAccessible(true);
m4.invoke(obj);
}
案例-通过反射获取配置文件内容
public class Student {
public void show(){
System.out.println("学生学习了");
}
}
配置文件内容
ClassName=cn.yanqi_07.Student
MethodName=show
public static void main(String[] args) throws Exception {
// Student s = new Student();
// s.show();
//获取ApplicationConfig.properties配置文件,字符流缓冲
BufferedReader br = new BufferedReader(new FileReader("ApplicationConfig.properties"));
//创建properties属性集合对象
Properties pro = new Properties();
//加载配置文件
pro.load(br);
//关闭流
br.close();
//获取配置文件中的数据
String className = pro.getProperty("ClassName");
String methodName = pro.getProperty("MethodName");
//获取字节码class对象
Class c = Class.forName(className);//cn.properties.com.Student
//创建反射实例
Constructor con = c.getConstructor();
Object o = con.newInstance();
//调用方法
Method method = c.getMethod(methodName);//show
method.invoke(o);
}
枚举
枚举的概述
是指将变量的值一一的列出来,变量的值只限于列举出来的值的范围内。举例:一周只有7天,一年只有12个月等。
枚举格式
public enum 枚举类名{
枚举项1,枚举项2,枚举项3,枚举4。。。;
}
第一版
public enum Direction {
/*
* 通过JDK5提供的枚举来做枚举类
*/
FRONT, BEHIND, LEFT, RIGHT;
}
public class DirectionDemo {
public static void main(String[] args) {
Direction d = Direction.FRONT;
System.out.println(d); // FRONT
}
}
第二版
public enum Direction2 {
FRONT("前"), BEHIND("后"), LEFT("左"), RIGHT("右");
private String name;
private Direction2(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static void main(String[] args) {
Direction2 d2 = Direction2.FRONT;
System.out.println(d2);//FRONT
System.out.println(d2.getName());//前
d2 = Direction2.RIGHT;
System.out.println(d2);//RIGHT
System.out.println(d2.getName());//右
}
第三版
public enum Direction3 {
FRONT("前") {
@Override
public void show() {
System.out.println("前");
}
},
BEHIND("后") {
@Override
public void show() {
System.out.println("后");
}
},
LEFT("左") {
@Override
public void show() {
System.out.println("左");
}
},
RIGHT("右") {
@Override
public void show() {
System.out.println("右");
}
};
private String name;
private Direction3(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract void show();
}
public static void main(String[] args) {
Direction3 d3 = Direction3.FRONT;
System.out.println(d3);
System.out.println(d3.getName());
d3.show();
}