JavaSE基础知识分享(十四)

写在前面

今天继续讲Java中的类加载器和lambda表达式的知识!

类加载器和反射

类的加载

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三步来实现对这个类进行初始化。

  1. 加载
    .class 文件读入内存,并为之创建一个 Class 对象。任何类被使用时系统都会建立一个 Class 对象。

  2. 连接

    • 验证:检查类的内部结构是否正确,并与其他类协调一致。
    • 准备:为类的静态成员分配内存,并设置默认初始化值。
    • 解析:将类的二进制数据中的符号引用替换为直接引用。
  3. 初始化
    执行类的静态初始化器和静态代码块。

一个类的初始化时机

  1. 创建类的实例。
  2. 访问类的静态变量,或者为静态变量赋值。
  3. 调用类的静态方法。
  4. 使用反射方式强制创建某个类或接口对应的 java.lang.Class 对象。
  5. 初始化某个类的子类。
  6. 直接使用 java.exe 命令运行某个主类。

类加载器

负责将 .class 文件加载到内存中,并为之生成对应的 Class 对象。了解类加载机制可以帮助更好地理解程序的运行。

类加载器的组成

  1. Bootstrap ClassLoader(根类加载器)
    负责加载 Java 核心类,如 SystemString 等。它在 JDK 的 JRE lib 目录下的 rt.jar 文件中。

  2. Extension ClassLoader(扩展类加载器)
    负责加载 JRE 扩展目录中的 JAR 包。在 JDK 的 JRE lib 目录下的 ext 目录中。

  3. System ClassLoader(系统类加载器)
    负责加载来自 java 命令的 .class 文件,以及 classpath 环境变量所指定的 JAR 包和类路径。

Java 反射机制

Java 反射机制允许在运行时动态获取类的信息以及调用对象的方法。

通过反射获取构造方法并使用

  • 获取构造方法

    • getConstructors()
    • getDeclaredConstructors()
  • 创建对象

    con.newInstance("zhangsan", 20);
    

通过反射获取成员变量并使用

  • 获取所有成员

    • getFields()
    • getDeclaredFields()
  • 获取单个成员

    • getField()
    • getDeclaredField()
  • 修改成员的值

    field.set(obj, value);
    

通过反射获取成员方法并使用

  • 获取所有方法

    • getMethods()
    • getDeclaredMethods()
  • 获取单个方法

    • getMethod()
    • getDeclaredMethod()
  • 暴力访问

    method.setAccessible(true);
    

Lambda 表达式

从 JDK 1.8 开始,Lambda 表达式简化了代码开发,支持函数式编程。

写 Lambda 表达式的场景

  1. 必须有相应的函数接口,函数接口是指内部有且仅有一个抽象方法的接口。
  2. 编译器通过类型推断机制可以推断出参数列表的类型。

Lambda 基本语法

Lambda 表达式由 -> 操作符分隔为两部分:

  • 左侧:指定 Lambda 表达式需要的所有参数(对应接口中的形参)。
  • 右侧:指定 Lambda 体,即要执行的功能(方法体)。

Lambda 表达式的分类

  1. 无参数,无返回值。
  2. 有一个参数,无返回值(若只有一个参数,小括号可以省略)。
  3. 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句。
  4. 若 Lambda 体中只有一条语句,return 和大括号都可以省略。
  5. Lambda 表达式的参数列表的数据类型可以省略,JVM 编译器通过上下文推断出数据类型。

Java 内置函数式接口

  • Predicate<T>:断言型接口

    @FunctionalInterface
    public interface Predicate<T> {
        boolean test(T t);
    }
    
  • Function<T, R>:函数型接口

    @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
    }
    
  • Supplier<T>:供给型接口

    @FunctionalInterface
    public interface Supplier<T> {
        T get();
    }
    
  • Consumer<T>:消费型接口

    @FunctionalInterface
    public interface Consumer<T> {
        void accept(T t);
    }
    

Lambda 用法再简洁之方法引用

  • 对象的方法引用

    对象引用::方法名
    
  • 静态方法引用

    类名::静态方法名
    
  • 构造方法引用

    类名::new
    
  • 数组创建引用

    元素类型[]::new
    

容易形成的误区

Lambda 表达式底层被封装成了主类的一个私有方法,并通过 invokedynamic 指令进行调用,简化了匿名内部类的写法。Lambda 表达式使代码更简洁,但也可能降低可读性。

优缺点

  • 优点

    • 减少代码书写,减少匿名内部类的创建,节省内存占用。
    • 使用时无需记忆接口和抽象函数。
  • 缺点

    • 易读性较差,需要熟悉 Lambda 表达式和抽象函数中参数的类型。
    • 不方便进行调试