第16章 类加载器&反射(Reflect)
学习目标
- 了解反射的概念
- 了解类的加载过程
- 理解类初始化过程
- 了解类加载器
- 掌握获取Class对象的四种方式
- 能够运用反射获取类型的详细信息
- 能够运用反射动态创建对象
- 能够运用反射动态获取成员变量并使用
- 能够运用反射动态获取成员方法并使用
16.1 类加载
16.1.1 类加载器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0UokHKE0-1680573734788)(尚硅谷-JavaSE-第16章 反射(Reflect)]-陈叶.assets/1654407247363.png)
16.1.2 类加载时机
- 创建类的实例(对象)
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
代码演示
import com.atguigu.bean.BeseStudent;
import com.atguigu.bean.Student;
public class Demo {
/*
- 创建类的实例(对象)
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
*/
public static void main(String[] args) throws Exception {
// - 创建类的实例(对象)
new Student("张三",23).toString();
// - 调用类的类方法
Student.show();
// - 访问类或者接口的类变量,或者为该类变量赋值
Student.sex = "男";
// - 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
Class.forName("com.atguigu.bean.Student");
// - 初始化某个类的子类
new BeseStudent();
// - 直接使用java.exe命令来运行某个主类
// java com.atguigu.bean.Student
}
}
Student类
package com.atguigu.bean;
public class Student {
private String name;
private Integer age;
public static String sex;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public static void show(){
System.out.println("static------show()");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
BaseStudent类
package com.atguigu.bean;
public class BeseStudent extends Student {
}
16.1.3 类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载。
类的加载又分为三个阶段:
(1)加载:load
就是指将类型的class字节码数据读入内存
(2)连接:link
①验证:校验合法性等(如:是否符合JVM的规范等)
②准备:准备对应的内存(方法区),创建Class对象,为类变量(被static修饰的变量)赋默认值,为静态 常量赋初始值。
③解析:把字节码中的符号引用替换为对应的直接地址引用(如果本来中用到了其他的引用类型,此时需要找到对应的类)
(3)初始化:initialize(类初始化)给类变量赋值以及初始化其他资源。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vvhC6pbj-1680573734790)(尚硅谷-JavaSE-第16章 反射(Reflect)]-陈叶.assets/1654480327277.png)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QlVewgdd-1680573734790)(尚硅谷-JavaSE-第16章 反射(Reflect)]-陈叶.assets/1663486365966.png)
16.1.4 类加载器分类(Classloader)
引导类加载器(Bootstrap Classloader)
扩展类加载器(Extension ClassLoader)
应用程序类加载器(Application Classloader)
自定义类加载器
每个类加载器都有自己加载的范围
1、引导类加载器(Bootstrap Classloader)又称为根类加载器、启动类加载器
虚拟机的内置类加载器,通常表示为null ,没有父类,JVM启动时自动启动
它负责加载核心类库,如JRE的核心库rt.jar(jre/lib/rt.jar)
2、扩展类加载器(Extension ClassLoader)
它负责加载JRE目录下lib文件夹中的ext扩展库(jre/lib/ext)
它是ClassLoader的子类
3、应用程序类加载器(Application ClassLoader)又称为系统类加载器
它负责加载程序员自己编写的类,接口等(项目的classpath路径下的类)
它是ClassLoader的子类
4、自定义类加载器
通常情况下,我们不需要自定义类加载器
两种情况需要:
1.当你的程序的字节码文件需要加密时,那么往往会提供一个自定义类加载器对其进行解码
2.当你的程序需要加载“特定”目录下的类,可以自定义类加载器;
后面会见到的自定义类加载器:tomcat中,它的classes放在WEB-INF/classes文件夹
16.1.5 类加载器之间的关系
应用程序类加载器的父加载器---> 扩展类加载器
扩展类加载器的父加载器---> 引导类加载器
引导类加载器为根类加载器,无父类加载器
注意:此处不是真正的"继承关系",是逻辑继承,实际是组合的方式实现
16.1.6 Java系统类加载器的双亲委托模式
双亲委托模式描述
系统需要加载某个类时
1.应用类加载器优先接到任务
优先在方法区搜索此类是否加载过
如果找到了-->该类已经加载过了,直接返回它的Class对象
如果没找到-->会把任务往上传(传给父加载器扩展类加载器)
2.扩展类加载器接到任务
优先在方法区搜索此类是否加载过
如果找到了-->该类已经加载过了,直接返回它的Class对象
如果没找到-->会把任务往上传(传给父加载器引导类加载器)
3.引导类加载器接到任务
优先在方法区搜索此类是否加载过
如果找到了-->该类已经加载过了,直接返回它的Class对象
如果没找到-->已经在顶层了,不能继续把任务往上传
会在自己负责的区域搜索此类是否加载过
如果找到了-->该类已经加载过了,直接返回它的Class对象
如果没找到-->就把任务往下传(传给子类加载器扩展类加载器)
4.扩展类加载器接到任务
会在自己负责的区域搜索此类是否加载过
如果找到了-->该类已经加载过了,直接返回它的Class对象
如果没找到-->就把任务往下传(传给子类应用类加载器)
5.应用类加载器先接到任务
会在自己负责的区域搜索此类是否加载过
如果找到了-->该类已经加载过了,直接返回它的Class对象
如果没找到-->就把任务往下传,就报错(ClassNotFoundException或NoClassDefError)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Agvoz7qW-1680573734791)(尚硅谷-JavaSE-第16章 反射(Reflect)]-陈叶.assets/1654419501642.png)
16.1.7 类加载器的常用方法
1.查看某类对应的加载器
ClassLoader Class对象.getClassLoader();//如果是根加载器加载的类,则会得到null
2.获取某个类加载器的父加载器
ClassLoader ClassLoader对象.getParent()
3.获取默认的系统类加载器
ClassLoader ClassLoader.getSystemClassLoader()
4.加载某个资源文件
public InputStream getResourceAsStream(String name)
示例代码
引入Properties类的用法(配置文件类)
package com.atguigu.reflect.myreflect03;
public class TestClassLoader {
public static void main(String[] args) {
// 1.查看某类对应的加载器
ClassLoader classLoader01 = TestClassLoader.class.getClassLoader();
System.out.println(classLoader01);
// 2.获取某个类加载器的父加载器
ClassLoader classLoader02 = classLoader01.getParent();
System.out.println(classLoader02);
ClassLoader classLoader03 = classLoader02.getParent();
System.out.println(classLoader03);
// 3.获取默认的系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
// 加载某个资源
InputStream is = systemClassLoader.getResourceAsStream("data.properties");
Properties properties = new Properties();
properties.load(is);
System.out.println(properties.getProperty("username"));
System.out.println(properties.getProperty("password"));
}
}
data.properties配置文件
username=aaa
password=admin
内容的要求
key=value
16.2 反射的概念
Java的反射(reflection)机制是指在程序的运行状态中,
可以构造任意一个类的对象,
可以了解任意一个对象所属的类,
可以了解任意一个类的成员变量和方法,
可以调用任意一个对象的属性和方法。
这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。
注意:
要想使用Java的反射机制,首先先获得一个类对应的Class字节码文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v3FJ9mKw-1680573734792)(尚硅谷-JavaSE-第16章 反射(Reflect)]-陈叶.assets/1654586697182.png)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uY63QP5r-1680573734792)(尚硅谷-JavaSE-第16章 反射(Reflect)]-陈叶.assets/1663492241536.png)
引入案例
需求
1.定义一个Student类,调用它的study()
2.定义一个Teacher类,调用它的teach()
3.定义一个Worker类,调用它的work()
代码演示
import com.atguigu.bean.Student;
import com.atguigu.bean.Teacher;
import com.atguigu.bean.Worker;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Properties;
public class Demo {
public static void main(String[] args) throws Exception {
// 学习反射之前的实现方式
new Student().study();
new Teacher().teach();
new Worker().work();
// 使用反射技术 动态获取信息
// 1.1 加载配置文件,获取信息
InputStream is = Demo.class.getClassLoader().getResourceAsStream("data.properties");
Properties properties = new Properties();
properties.load(is);
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
// 1.2 通过反射完成任务
Class<?> clazz = Class.forName(className);
Object obj = clazz.newInstance();
clazz.getMethod(methodName).invoke(obj);
}
}
Student类
package com.atguigu.bean;
public class Student {
public void study(){
System.out.println("----study()");
}
}
Teacher类
package com.atguigu.bean;
public class Teacher {
public void teach(){
System.out.println("----teach()");
}
}
Worker类
package com.atguigu.bean;
public class Worker {
public void work(){
System.out.println("----work()");
}
}
16.3 获取Class对象(重点)
java.lang.Class类
要想通过反射获取一个类,必须先要获取到该类的Class对象。
16.3.1 获取Class对象的四种方式
获取Class文件的四种方式
1.处于.java文件(Class.forName(类型全名称))
2.处于.class文件(类型名.class)
3.已创建对象(对象.getClass())
4.通过类加载器创建(ClassLoader的类加载器对象.loadClass(类型全名称))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G1bSIi2P-1680573734793)(尚硅谷-JavaSE-第16章 反射(Reflect)]-陈叶.assets/1663495044188.png)
代码演示:
package com.atguigu.reflect.myreflect04;
/*
获取Class文件的四种方式
1.处于.java文件(Class.forName(类型全名称))
2.处于.class文件(类型名.class)
3.已创建对象(对象.getClass())
4.通过类加载器创建(ClassLoader的类加载器对象.loadClass(类型全名称))
*/
public class TestGetClass {
public static void main(String[] args) throws Exception{
// 1.处于.java文件
Class<?> clazz01 = Class.forName("com.atguigu.reflect.myreflect04.Person");
System.out.println(clazz01);
// 2.处于.class文件
Class<?> clazz02 = Person.class;
System.out.println(clazz02);
// 3.已创建对象
Person person = new Person();
Class clazz03 = person.getClass();
System.out.println(clazz03);
// 4.通过类加载器创建
Class<?> clazz04 = TestGetClass.class.getClassLoader()
.loadClass("com.atguigu.reflect.myreflect04.Person");
System.out.println(clazz04);
System.out.println(clazz01 == clazz02);
System.out.println(clazz02 == clazz03);
System.out.println(clazz03 == clazz04);
}
}
16.4 反射的基本应用
1.获取任意类的对象
2.获取任意类的属性并赋值
3.获取任意类的方法并调用
4.获取任意类的其他信息(包,修饰符,注解等)
16.4.1 获取任意引用类型的对象(Constructor类)
获取任意类的对象两种方式:
1、直接通过Class对象来实例化(只能获取公共的无参构造创建的对象)
(1)获取该类型的Class对象(2)创建对象
2、通过获取构造器对象来进行实例化
(1)获取该类型的Class对象(2)获取构造器对象(3)创建对象
代码演示
package com.atguigu.test;
import com.atguigu.bean.Person;
import java.lang.reflect.Constructor;
import java.util.Properties;
public class Demo {
/*
获取任意类的对象
两种方式:
1、直接通过Class对象来实例化(只能获取公共的无参构造创建的对象)
(1)获取该类型的Class对象(2)创建对象
2、通过获取构造器对象来进行实例化
(1)获取该类型的Class对象(2)获取构造器对象(3)创建对象
*/
public static void main(String[] args) throws Exception {
// 1、直接通过Class对象来实例化(只能获取公共的无参构造创建的对象)
// (1)获取该类型的Class对象(2)创建对象
Class<?> clazz = Class.forName("com.atguigu.bean.Person");
Person p = (Person) clazz.newInstance();
System.out.println(p);
// 2、通过获取构造器对象来进行实例化
// (1)获取该类型的Class对象(2)获取构造器对象(3)创建对象
// 2.1 通过空参构造器创建对象
Constructor con = clazz.getConstructor();// 获取空参构造器
Person p2 = (Person) con.newInstance();
System.out.println(p2);
// 2.2 通过有参构造器创建对象
Constructor con2 = clazz.getConstructor(String.class, Integer.class);// 获取有参构造器
Person p3 = (Person) con2.newInstance("张三", 23);
System.out.println(p3);
// 报错 Person没有参数为String类型的有参构造
Constructor con3 = clazz.getConstructor(String.class);// 获取有参构造器
Person p4 = (Person) con3.newInstance("张三");
System.out.println(p4);
}
}
Person类
package com.atguigu.bean;
public class Person {
public String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
16.4.2 获取任意类型的属性并赋值(Filed类)
获取任意类的属性并赋值
1.获取该类型的Class对象
2.获取属性对象
3.设置属性值
注意: 如果属性的权限修饰符不是public,那么需要设置属性可访问
代码演示
package com.atguigu.test;
import com.atguigu.bean.Person;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Properties;
public class Demo {
/*
获取任意类的属性并赋值
1.获取该类型的Class对象
2.获取属性对象
3.设置属性值
注意: 如果属性的权限修饰符不是public,那么需要设置属性可访问
*/
public static void main(String[] args) throws Exception {
// 1 获取该类型的Class对象
Class<?> clazz = Class.forName("com.atguigu.bean.Person");
Person p = (Person) clazz.newInstance();
// 2.1获取属性对象并设置(name 属性)
Field field1 = clazz.getField("name");
field1.set(p,"张三");
System.out.println(p);
// 2.2获取属性对象并设置(age 属性 私有)
// Field field2 = clazz.getField("age");// 报错 NoSuchFieldException age属性私有
Field field2 = clazz.getDeclaredField("age");
field2.setAccessible(true);
field2.set(p,23);// 直接设置会报错 IllegalAccessException 需设置可访问
System.out.println(p);
}
}
16.4.3 获取任意类型的方法并调用(Method)
获取任意类型的方法并调用
1.获取该类型的Class对象
2.获取方法对象(在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载)
3.调用方法
注意:如果方法是静态方法,实例对象也可以省略,用null代替
代码演示
package com.atguigu.test;
import com.atguigu.bean.Person;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;
public class Demo {
/*
获取任意类型的方法并调用
1.获取该类型的Class对象
2.获取方法对象(在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载)
3.调用方法
注意:如果方法是静态方法,实例对象也可以省略,用null代替
*/
public static void main(String[] args) throws Exception {
// 1.获取该类型的Class对象
Class clazz = Class.forName("com.atguigu.bean.Person");
Person p = (Person) clazz.newInstance();
// 2.获取方法对象(在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载)
// 3.调用方法
// 2.1 获取一个公共的空参方法 eat()
Method method1 = clazz.getMethod("eat");
method1.invoke(p);
// 2.2 获取一个公共的有参方法 eat(int num)
Method method2 = clazz.getMethod("eat", int.class);
method2.invoke( p,3);
// 2.3 获取一个私有的方法
// Method method3 = clazz.getMethod("sleep");// 报错 NoSuchMethodException,sleep 方法是私有的
// method3.invoke(p);
Method method3 = clazz.getDeclaredMethod("sleep");
// method3.invoke(p); // 报错 IllegalAccessException,私有方法 需设置为可访问
method3.setAccessible(true);
method3.invoke(p);
// 2.4 获取一个静态的方法
Method method4 = clazz.getMethod("show");
// method4.invoke(p);
method4.invoke(null);// 如果方法是静态方法,实例对象也可以省略,用null代替
}
}
16.4.4 获取任意类型的其他信息(了解)
获取任意类型的其他信息
包 getPackage()、修饰符 getModifiers()、类型名 getName()
父类 getSuperclass()、父接口 getInterfaces()
修饰符
- 0x是十六进制
* PUBLIC = 0x00000001; 1 1
* PRIVATE = 0x00000002; 2 10
* PROTECTED = 0x00000004; 4 100
* STATIC = 0x00000008; 8 1000
* FINAL = 0x00000010; 16 10000
设计的理念,就是用二进制的某一位是1,来代表一种修饰符,整个二进制中只有一位是1,其余都是0
代码演示
package com.atguigu.test;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class Demo {
/*
获取任意类型的其他信息
包 getPackage()、修饰符 getModifiers()、类型名 getName()
父类 getSuperclass()、父接口 getInterfaces()
*/
public static void main(String[] args) throws Exception {
// 1. 获取该类型的Class对象
Class<?> clazz = Class.forName("com.atguigu.bean.Person");
// 2. 包 getPackage()、修饰符 getModifiers()、类型名 getName()
Package aPackage = clazz.getPackage();
System.out.println("aPackage = " + aPackage);
int modifiers = clazz.getModifiers();
System.out.println("modifiers = " + modifiers);
System.out.println("Modifiers= " + Modifier.toString(modifiers));
String name = clazz.getName();
System.out.println("name = " + name);
// 3. 父类 getSuperclass()、父接口 getInterfaces()
Class<?> superclass = clazz.getSuperclass();
System.out.println("superclass = " + superclass);
Class<?>[] interfaces = clazz.getInterfaces();
System.out.println("interfaces = " + Arrays.toString(interfaces));
}
}
16.5 反射的综合案例
案例需求
定义一个ArrayList<Integer>对象,请使用反射在这个集合中添加一个字符串元素
补充知识:
泛型只在编译期有效,在运行期会被擦除掉,生成.class文件之后泛型就没有了
代码演示
package com.atguigu.test;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Demo {
/*
定义一个ArrayList<Integer>对象,请使用反射在这个集合中添加一个字符串元素
*/
public static void main(String[] args) throws Exception {
// 1.定义一个ArrayList<Integer>对象
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 2.使用反射在这个集合中添加一个字符串元素
Class<?> clazz = Class.forName("java.util.ArrayList");
Method method = clazz.getMethod("add", Object.class);
method.invoke(list,"abc");
System.out.println(list);
}
}
今晚面试名单(44人):
罗宝腾,刘鼎昂,李振,周超,克热米拉,吴金洋,蒋迎春,代佳伟,郭振东
王晨旭 马宏志 陈顺胜 张尚辉 王朝朝
罗轩 许佳豪 胡苗苗 马振辉 赵笑江
方诗宇 余组 田志雄 余彦康 关仕博
李想 郭声远 魏巍 袁成真 王涛
万键豪 闵子傲 张衎 李栋梁 熊烈
王俊凡 董慧国 胡璟 张磊 仲兵兵
蔡锐 蒋超 万佳伦 胡文博 周奥哲
class Demo {
/*
定义一个ArrayList<Integer>对象,请使用反射在这个集合中添加一个字符串元素
*/
public static void main(String[] args) throws Exception {
// 1.定义一个ArrayList<Integer>对象
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 2.使用反射在这个集合中添加一个字符串元素
Class<?> clazz = Class.forName("java.util.ArrayList");
Method method = clazz.getMethod("add", Object.class);
method.invoke(list,"abc");
System.out.println(list);
}
}
##### 今晚面试名单(44人):
罗宝腾,刘鼎昂,李振,周超,克热米拉,吴金洋,蒋迎春,代佳伟,郭振东
王晨旭 马宏志 陈顺胜 张尚辉 王朝朝
罗轩 许佳豪 胡苗苗 马振辉 赵笑江
方诗宇 余组 田志雄 余彦康 关仕博
李想 郭声远 魏巍 袁成真 王涛
万键豪 闵子傲 张衎 李栋梁 熊烈
王俊凡 董慧国 胡璟 张磊 仲兵兵
蔡锐 蒋超 万佳伦 胡文博 周奥哲
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BPPclGEN-1680573734794)(尚硅谷-JavaSE-第16章 反射(Reflect)]-陈叶.assets/1663559386521.png)