Java进阶15 类加载器&反射&方法引用
一、类加载器
1、作用&加载时机
1.1 作用
类似搬运工,将编译生成的字节码.class文件(存储的物理文件)加载到内存中
1.2 加载时机(用到即加载)
-
创建类的实例(对象)
Student stu = new Student();
-
调用类的静态方法
Arrays.toString(arr);
-
访问类或者接口的静态变量,或者为该类静态变量赋值
interface Inter { int NUM = 10; } System.out.println(Inter.NUM); ------------------------------------------------------------------------------ class A { public static int num; } A.num = 20; System.out.println(A.num);
-
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
Class clazz = Class.forName("com.itheima.domain.Student"); clazz.newInstance();
-
初始化继承体系
class Fu {} class Zi extends Fu {} Zi z = new Zi(); 1). Zi 字节码文件 2). Fu 字节码文件
-
直接使用 java.exe 命令来运行某个主类(jdk12之后出现)
2、加载过程
但一个类被使用的时候,才会加载到内存。类加载的过程:加载、验证、准备、解析、初始化
分步解读
-
加载
-
验证
-
准备
-
解析
♥关于解析的一个细节补充♥
符号引用和直接引用
public class A { public static void main(String[] args) throws IOException { // 加载B类的类加载器 ClassLoader classLoader = B.class.getClassLoader(); // 观察: 看看C类有没有加载, 如果没加载, 成员变量的C是个啥... // 没有创建B类对象,C只是一个符号,没有地址,是符号引用 // 创建了B类对象,new部分才会执行,B类中c的new部分才会执行,此时C获取到地址,是直接引用 B b = new B(); // 为了让程序不停, 保留进程 System.in.read(); } } class B { //只有创建了类的对象,后半部分的代码才会执行 C c = new C(); } class C { }
-
初始化
3、类加载器的分类
类的字节码对象.getClassLoader(); 获取该类的, 类加载器对象.
3.1 Bootstrap class loader
虚拟机内置类加载器,内部是C++实现,通常表示为null,我们获取不到。
3.2 Platform class loader
平台类加载器,负责加载JDK中一些特殊的模块,负责加载lib\modules内部的类;(JDK9之前是Extension Class Loader)扩展类加载器,负责加载jre\lib\ext目录下的类。
3.3 Application class loader
负责加载自己写的类
3.4 自定义类加载器
上级为Application,目前不做讲解,自己设计框架的时候才会用得到
4、双亲委派机制
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
4.1 好处
-
双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载
-
保证了 Java 的核心 API 不被篡改
假设我们自己编写了一个 Object 类 1. java.lang.Object类 (真的) 2. java.lang.Object类 (假的) 双亲委派模型可以保证加载的是 JRE 里的那个 Object 类, 而不是我们自己写的 AppClassLoader ---> PlatformClassLoader ---> BootstrapClassLoader BootstrapClassLoader : 发现已经加载过了Object类了, 就不会加载我们自己写的了
4.2 常用方法
方法 | 说明 |
---|---|
public static ClassLoader getSystemClassLoader() | 获取系统类加载器 |
public InputStream getResourceAsStream(String name) | 加载某一个资源文件 |
public class ClassLoaderMethod {
public static void main(String[] args) throws IOException {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
InputStream is = SecureClassLoader.getSystemResourceAsStream("config.properties");
Properties properties = new Properties();
properties.load(is);
is.close();
String username = properties.getProperty("username");
String password = properties.getProperty("password");
System.out.println(username);
System.out.println(password);
}
}
二、反射(重点)
1、引入
1.1 框架(半成品软件)
可以理解为生活中的毛胚房,它需要我们对它进行进一步丰富,我们也需要它现有的框架体系
1.2 反射机制
是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。大白话的理解 : 反射其实就是对类的一种解刨。
主要操作类的字节码对象,获取到该类的相关成员(包括构造方法、成员变量、成员方法)并使用。
类 | 对象 | 使用 | |
---|---|---|---|
字节码文件 | Class类 | Class对象 | |
构造方法 | Constructor类 | Constructor对象 | 创建实例 |
成员变量 | Field类 | Field对象 | 赋值和获取 |
成员方法 | Method类 | Method对象 | 方法调用 |
1.3 使用场景
用于制作框架(框架可以理解为半成品的软件)
2、获取Class类对象(3种方式)
2.1 通过Class类的静态方法forName()
方法 | 说明 |
---|---|
public static Class<?> forName(String className) | 根据全类名,获取类的字节码对象 |
public class ReflectDemo1 {
public static void main(String[] args) throws Exception{
//1、Class类中的静态方法forName("全类名") 全类名:包名+类名
Class<?> class1 = Class.forName("com.itheima.domain.Student");
}
}
2.2 通过类名中静态属性获取
类名.class
public class ReflectDemo1 {
public static void main(String[] args) throws Exception{
//2、通过class属性来获取
Class<Student> class2 = Student.class;
}
}
2.3 通过Object类中的getClass方法
方法 | 说明 |
---|---|
public void Class<?> getClass() | 返回此Object的运行时类 |
public class ReflectDemo1 {
public static void main(String[] args) throws Exception{
//3、通过Object类的getClass方法来获取
Student stu = new Student();
Class<? extends Student> class3 = stu.getClass();
}
}
3、获取构造方法并使用
3.1 获取
方法 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor<T> getConstructor(Class<?>... parameterTypes) | 返回单个公共构造方法对象 |
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) | 返回单个构造方法对象 |
3.2 使用(创建对象)
方法 | 说明 |
---|---|
T newInstance(Object...initargs) | 根据指定的构造方法创建对象 |
setAccessible(boolean flag) | 设置为true,表示取消访问检查 |
3.3 Demo
public class ReflectDemo2 { /* 反射类的构造方法 */ public static void main(String[] args) throws Exception{ //1、获取类的字节码对象 Class<?> studengtClass = Class.forName("com.itheima.domain.Student"); //2、反射类的构造方法(对象) Constructor<?> constructor1 = studengtClass.getConstructor(); Constructor<?> constructor2 = studengtClass.getConstructor(String.class, int.class); System.out.println(constructor1); System.out.println(constructor2); //3、根据构造方法创建对象 Object o1 = constructor1.newInstance(); Object o2 = constructor2.newInstance("张三", 23); System.out.println(o1); System.out.println(o2); //暴力反射获取构造的方法 Constructor<?> declaredConstructor = studengtClass.getDeclaredConstructor(); //设置访问权限 declaredConstructor.setAccessible(true); //创建对象 Object o3 = declaredConstructor.newInstance("张三"); System.out.println(o3); } }
注意:通过反射获取构造方法对象时,如果构造方法是public权限可以直接获取;如果是非public权限,需要用带Declared的方法暴力反射获取;反射创建的时候也一样,如果是public的,可直接创建,如果是非public的,需要先设置访问权限为true,才能创建。
4、获取成员变量并使用
4.1 获取字段
方法 | 说明 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
4.2 使用(给成员变量赋值)
方法 | 说明 |
---|---|
void set(Object obj, Object value) | 赋值 |
Object get(Object obj) | 获取值 |
4.3 Demo
public class ReflectDemo3 {
/*
反射获取成员变量
注意:开发时严禁暴力反射的使用
*/
public static void main(String[] args) throws Exception{
//1、获取字节码对象
Class<Student> studentClass = Student.class;
Constructor<Student> constructor = studentClass.getConstructor();
Student stu = constructor.newInstance();
//2、通过字节码对象,反射内部的成员变量对象
Field nameField = studentClass.getDeclaredField("name");
nameField.setAccessible(true);
Field ageField = studentClass.getDeclaredField("age");
ageField.setAccessible(true);
//3、赋值
nameField.set(stu,"张三");
ageField.set(stu,23);
//4、获取
System.out.println(nameField.get(stu));
System.out.println(ageField.get(stu));
}
}
5、获取成员方法并使用
5.1 获取
方法 | 说明 |
---|---|
Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的 |
Method getMethod(String name, Class<?>... parameterTypes) | 返回单个公共成员方法对象 |
Method getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回单个成员方法对象 |
5.2 使用(执行方法)
方法 | 说明 |
---|---|
Object invoke(Object obj, Object... args) | 运行方法 |
-
参数1:用obj对象调用该方法
-
参数2:调用方法时传递的参数(没有 就不写)
-
返回值:方法的返回值(没有 就不写)
5.3 Demo
public class ReflectDemo4 {
/*
反射调用成员方法
*/
public static void main(String[] args) throws Exception{
//1、获取类的字节码对象
Class<?> studentClass = Class.forName("com.itheima.domain.Student");
Constructor<?> constructor = studentClass.getConstructor();
Object stu = constructor.newInstance();
//2、反射类的成员方法对象
Method eatMethod = studentClass.getMethod("eat", int.class);
//3、调用方法
eatMethod.invoke(stu,10);
}
}
6、小案例
需求:请向一个泛型为 Integer 的集合, 添加一个 String 字符串
思路:Java 中的泛型是假的, 只在编译的时候有效,运行时不在做泛型检查。实现思路就是直接拿字节码对象塞不同类型的数据进去,跳过编译器检查即可。
public class ReflectTest4 { public static void main(String[] args) throws Exception { ArrayList<Integer> list = new ArrayList<>(); Class listClass = list.getClass(); // 集合中, add方法的, 成员方法对象 Method addMethod = listClass.getMethod("add", Object.class); // 调用成员方法 addMethod.invoke(list, "abc"); System.out.println(list); } }
三、方法引用
方法引用是JDK8开始出现的,主要的作用是对Lambda表达式进行进一步的简化。
1、格式
方法引用使用一对冒号 : :
方法引用通过方法的名字来指向一个方法
方法引用可以使语言的构造更紧凑简洁,减少冗余代码
引用前
public class A { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); Collections.addAll(list,"a","b","c","d"); list.forEach(s -> System.out.println(s)); } }
引用后
public class A { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, "a", "b", "c", "d"); list.forEach(System.out::println); } }
2、引用方法
引用静态方法
类名 : :方法名
public class A { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, "a", "b", "c", "d"); //Lambda表达式写法 list.forEach(s -> A.method(s)); //方法引用 list.forEach(A::method); } //静态方法method public static void method(String s) { System.out.println(s.toUpperCase()); } }
引用普通成员方法
该类对象名 : : 方法名
public class MethodDemo { public static void main(String[] args) { Stream<String> s1 = Stream.of("A","B","C"); MethodDemo md = new MethodDemo(); s1.forEach(md::show); } public void show(String s){ System.out.println(s.toLowerCase()); } }
方法引用的参数问题(参数呢?)
根据可推导(参数只有一个,而被调用的方法,也恰好只要一个参数,那调用起来,只这一个参数可以能进入method方法)可省略原则。