Java进阶15 类加载器&反射&方法引用

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方法)可省略原则。

  • 25
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值