概念

优势

  • 跨平台:JVM。Java 源码编译后生成 .class ​ 文件(字节码文件),JVM 可直接将字节码文件翻译成机器语言从而运行

    • Java 程序跨平台,但 JVM 不是跨平台
  • 面向对象

  • 生态完善

  • 内存管理:自动垃圾回收

  • 多线程支持

劣势

  • JVM 性能低于原生编译语言
  • 语法繁琐

JVM/JDK/JRE

  • JVM:Java 虚拟机

    • 主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 指令集和 OS 的系统调用。(即将 Java 字节码解释或编译成机器码)
    • 执行 Java 程序程序
    • 内存管理、垃圾回收、安全性
  • JDK:Java 开发工具包

    • 包含 JVM、编译器(javac)、调试器(jdb),标准库和工具库
    • JDK 提供开发、编译、调试和运行 Java 程序所需的全部工具和环境
  • JRE:Java 运行时环境

    • Java 程序运行所需最小环境
    • 包含 JVM 和一组 Java 类库,不包含开发工具,只提供运行环境

解释性和编译性

Java 同时具有解释性和编译性

image

  • 编译性:在程序执行之前,整个源代码会被编译成机器码或者字节码,生成可执行文件。执行时直接运行编译后的代码,速度快,但跨平台性较差。

    • Java 源代码首先被编译成字节码,JIT 会把编译过的机器码保存起来,以备下次使用。
  • 解释性:在程序执行时,逐行解释执行源代码,不生成独立的可执行文件。通常由解释器动态解释并执行代码,跨平台性好,但执行速度相对较慢。

    • JVM 中的一个方法调用计数器,当累计计数大于一定值的时候,就使用 JIT 进行编译生成机器码文件,否则就用解释器进行解释执行
    • 字节码也是经过解释器进行解释运行的

数据类型

基本数据类型 + 引用数据类型

  • 基本数据类型:预定义,不需要实例化
  • 引用数据类型:必须实例化,进行内存分配,性能低于基本数据类型。存在空指针问题

image

数据类型的转换方式

  • 自动类型转换(隐式转换):当目标类型的范围大于源类型时,Java 会自动将源类型转换为目标类型,不需要显式的类型转换。
  • 强制类型转换(显示转换):当目标类型的范围小于源类型时,需要使用强制类型转换将源类型转换为目标类型。这可能导致数据丢失或溢出。
  • 字符串转换:将字符串表示的数据转换为其他类型数据的方法。
  • 数值之间转换:数值类型之间的转换方法,如将整型转换为字符型、将字符型转换为整型等。

类型互转的问题

  • 数据丢失
  • 数据溢出
  • 精度损失
  • 类型不匹配抛出异常

BigDecimal

高精度数字,涉及金额计算要使用高精度运算

包装类

装箱和拆箱

  • 装箱(Boxing):基本数据类型-> 包装类
  • 拆箱(Unboxing):包装类-> 基本数据类型

发生时间:

  • 赋值时
  • 方法调用时,传入参数

包装类存在意义

  • 可以把属性也就是数据跟处理这些数据的方法结合在一起
  • 在 Java 中绝大部分方法或类都是用来处理类类型对象的(如 ArrayList),必须包装成类才能被接受。
  • 在泛型中需要使用引用类型
  • 基本类型需要先转换为包装类,才能通过 toString() ​ 转换为字符串

Integer 缓存机制

Integer 类内部实现了一个静态缓存池,用于存储特定范围(默认为 $[-128,127]$)内的整数值对应的 Integer 对象。

  • 当使用 Integer.valueOf(int)​(/自动装箱)获取 Integer 对象时,将直接复用缓存中的现有对象,不会新建。
  • 但是如果使用 new ​ 则仍然会创建新对象

面向对象

面向对象是一种编程范式,它将现实世界中的事物抽象为对象,对象具有属性(称为字段或属性)和行为(称为方法)。面向对象编程的设计思想是以对象为中心,通过对象之间的交互来完成程序的功能,具有灵活性和可扩展性,通过封装和继承可以更好地应对需求变化。

特点

  • 封装将对象的属性(数据)和行为(方法)结合在一起,对外隐藏对象的内部细节,仅通过对象提供的接口与外界交互。封装的目的是增强安全性和简化编程,使得对象更加独立。
  • 继承:一种可以使得子类自动共享父类数据结构和方法的机制。它是代码复用的重要手段,通过继承可以建立类与类之间的层次关系,使得结构更加清晰。
  • 多态:允许不同类的对象对同一消息作出响应。 即同一个接口,使用不同的实例而执行不同操作。多态性可以分为编译时多态(重载)和运行时多态(重写)。它使得程序具有良好的灵活性和扩展性。

多态

应用

  • 方法重载 Overload

    • 同名方法,但是有不同的参数列表(参数类型、数量、顺序),编译器在编译时会选择性调用
  • 方法重写 Override

    • 子类能够提供对父类中同名方法的具体实现。运行时根据对象实际类型决定调用的方法
  • 接口

    • 多个类可以实现同一个接口,并且用接口类型的引用来调用这些类的方法
  • 向上转型(认子作父)

    • 使用父类类型引用指向子类类型,可以使用多种子类
  • 向下转型

    • 将父类类型引用转回子类类型,需要确定能够转换

作用

  • 可以使子类替换父类
  • 提高代码扩展性,基于接口而非实现编程

面向对象的设计原则

  1. 单一职责原则(SRP):一个类应该只有一个引起它变化的原因,即一个类应该只负责一项职责。
  2. 开放封闭原则(OCP):软件实体应该对扩展开放,对修改封闭。
  3. 里氏替换原则(LSP):子类对象应该能够替换掉所有父类对象。
  4. 接口隔离原则(ISP):客户端不应该依赖那些它不需要的接口,即接口应该小而专。(如:通过接口抽象层来实现底层和高层模块之间的解耦)
  5. 依赖倒置原则(DIP):高层模块不应该依赖低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。
  6. 最少知识原则(Law of Demeter):一个对象应当对其他对象有最少的了解,只与其直接的朋友交互。

抽象类

类定义 abstract

  • 抽象类不能被实例化(即不能使用 new ​ 创建),只能被继承

    • 抽象类可以拥有构造器(由子类间接调用的,和普通父类一样)
  • 抽象类的方法可以没有实现(也可以有)

  • 一般作为基类,被其他类继承和扩展使用(不能加 final ​ 修饰)

抽象类和接口

特点:

  • 抽象类:用于描述类的共同特性和行为,可以有成员变量、构造方法和具体方法。适用于有明显继承关系的场景。
  • 接口:用于定义行为规范,可以多实现,只能有常量和抽象方法。适用于定义类的能力或功能。

区别:

  • 一个类可以实现多个接口,但只能继承一个抽象类

  • 接口只有定义,没有实现(Java 8 后可以使用 default​ 定义方法体);抽象类可以选择实现或不实现

  • 访问修饰符

    • 接口成员变量默认为 public static final ​ 且必须有初值,无法被修改;成员方法为 public abstract
    • 抽象类成员变量默认为 default​,可被重新赋值;抽象方法被 abstract ​ 修饰
  • 变量:抽象类可以包含实例变量和静态变量,而接口只能包含静态常量。

接口

接口里可以定义的方法

  • 抽象方法;所有实现接口的类都必须实现这些方法。抽象方法默认是 public​ 和 abstract​,修饰符可以省略。
  • 默认方法(Java 8):允许接口提供具体实现,可以被实现类重写
  • 静态方法(Java 8):属于接口本身,通过接口名直接调用,不需要实现类
  • 私有方法(Java 9):辅助接口中的默认方法或其他私有方法(静态方法只能调用静态私有方法)实现功能,不能被实现类访问,只能在接口内部使用

接口中不能有构造方法,接口不拥有实例

静态变量和静态方法

与类本身关联的,不与类的实例关联。在内存中只存在一份,可以被类的所有实例共享。

静态变量

static 修饰,特点:

  • 共享性:所有该类的实例共享同一个静态变量
  • 静态变量在类被加载时初始化,只会对其进行一次分配内存。
  • 静态变量可以直接通过类名访问,也可以通过实例访问

静态方法

static 修饰,特点:

  • 无实例依赖
  • 可访问静态成员,不可访问非静态成员
  • 多态性:静态方法不支持重写(Override),但可以被隐藏(Hide)。

非静态内部类和静态内部类

  • 非静态内部类依赖于外部类的实例,静态内部类不依赖
  • 非静态内部类可访问外部类的成员变量和方法,静态内部类只能访问静态成员
  • 非静态内部类不能定义静态成员,而静态内部类可以定义静态成员
  • 非静态内部类在外部类实例化后才能实例化,而静态内部类可以独立实例化
  • 非静态内部类可以访问外部类的私有成员,而静态内部类不能直接访问外部类的私有成员,需要通过实例化外部类来访问

非静态内部类访问外部方法

编译器生成字节码时为非静态内部类维护一个指向内部类实例的引用。这个引用能使非静态内部类能够访问外部类的实例变量和方法

编译器会在生成非静态内部类的构造方法时,将外部类实例作为参数传入,并在内部类的实例化过程中建立外部类实例与内部类实例之间的联系,从而能够直接访问外部方法

关键字

final

  • 修饰类:不能被继承
  • 修饰方法:不能在子类中被重写
  • 修饰变量:变量一旦赋值,就不能再改变(对于引用数据类型,不能改变引用的对象,但是可以改变对象本身的内容)

深拷贝和浅拷贝

区别

  • 浅拷贝:只复制对象本身和其内部值字段,不复制对象内部的引用类型字段(复制引用,而不是复制引用的对象)
  • 深拷贝:会同时将对象内部的所有引用类型字段内容进行复制

深拷贝实现方式

Cloneable 接口

要求对象及其所有应用类型字段都实现 Cloneable 接口并重写 clone() 方法,并在对象的 clone() 中,递归克隆引用类型字段

public class MyClass implements Cloneable {
    private String field1;
    private OtherClass object1;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 数值对象直接复制
        MyClass clone = (MyClass) super.clone();
        // 深拷贝内部的引用对象
        clone.object1 = (OtherClass) object1.clone();
        return clone;
    }
}

// 也需要实现Cloneable接口
class OtherClass implements Cloneable {
    private String field2;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

序列化和反序列化

将对象序列化为字节流,再从字节流反序列化为对象。要求对象和所有引用类型都实现 Serializable 接口

public class MyClass implements Serializable {
    private String field1;
    private OtherClass object1;

    public MyClass deepCopy() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            oos.flush();
            oos.close();

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (MyClass) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}

// 也需要实现Serializable接口
class OtherClass implements Serializable {
    private String field2;

}

手动递归复制

public class MyClass {
    private String field1;
    private OtherClass object1;

    public MyClass deepCopy() {
        MyClass clone = new MyClass();
        clone.field1 = this.field1;
        // 会产生新的对象返回
        clone.object1 = this.object1.deepCopy();
        return clone;
    }
}

class OtherClass {
    private String field2;

    public OtherClass deepCopy() {
        OtherClass clone = new OtherClass();
        clone.field2 = field2;
        return clone;
    }
}

泛型

  • 允许类、接口、方法在定义时使用一个或多个类型参数,这些类型参数在使用时可以被指定为具体类型
  • 编译时提供更强的类型检查,并且在编译后能保留类型信息,避免在运行时出现类型转换异常
private static <T extends Number> double add(T a, T b) {
    System.out.println(a.doubleValue() + b.doubleValue());
    return a.doubleValue() + b.doubleValue();
}
// 默认存放Object类型,取出时需要显示转换
List list = new ArrayList();
// 能够约束存入的数据类型,取出时即可用;也可以在编译时提供提示
List<Integer> list = new ArrayList<>();

对象

创建对象方式

new

MyClass obj = new MyClass();

Class 类的 newInstance()

反射

try {
    MyClass obj = (MyClass) Class.forName("test.MyClass").newInstance();
} catch (Exception e) {
    throw new RuntimeException(e);
}

Constructor 类的 newInstance()

反射

try {
    Constructor<MyClass> constructor = MyClass.class.getConstructor();
    MyClass obj = constructor.newInstance();
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException |
         InvocationTargetException e) {
    throw new RuntimeException(e);
}

clone()

需要实现 Cloneable 接口

MyClass obj1 = new MyClass();
MyClass obj2 = (MyClass) obj1.clone();

反序列化

实现 Serializable 接口

try {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);
    oos.flush();
    oos.close();
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return (MyClass) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
    return null;
}

回收机制

通过 new 创建的对象,由垃圾回收器(GC, Garbage Collector)回收。垃圾回收器周期性地检测不被引用的对象,并将其回收释放内存

  • 引用计数法:对象的引用计数为 0,则可以回收
  • 可达性分析算法:从根对象(如方法区中类静态属性、方法中局部变量)出发,通过对象之间的引用链进行遍历。回收不可达的对象
  • 终结器(Finalizer):不推荐。重写 Finalizer() 方法,会在回收该对象前调用,可以在该方法中进行清理操作。但是它的执行时间不确定,因此不推荐使用

获取私有对象

private 成员变量或方法。不能直接访问

getter

实现 getter() 方法,使得能够安全获取

反射

绕过限制

import java.lang.reflect.Field;

public class MyClass {
    private String field1;

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
        MyClass obj = new MyClass();
        // 获取 Class 对象
        Class<? extends MyClass> clazz = obj.getClass();
        // 获取私有字段
        Field privateField = clazz.getDeclaredField("field1");
        // 设置可访问性
        privateField.setAccessible(true);
        // 获取私有字段的值
        String value = (String) privateField.get(obj);
        System.out.println(value);
    }
}

反射

概述

反射:动态获取信息、动态调用对象方法

在运行状态中,对于一个类,能够获取这个类中的所有属性和方法;对于任意对象,都能获取或调用其任意方法和属性

特性:

  • 运行时类信息访问:在程序运行时获取类的完整结构信息,包括类名、包名、父类、实现接口、构造函数、方法、字段等
  • 动态对象创建:可以使用反射 API 动态地创建对象实例,即使在编译时不知道具体的类名
    • 使用 Class 类的 newInstance() 或 Constructor 的 newInstance()
  • 动态方法调用:可以在运行时动态调用对象方法,包括私有方法
    • 使用 Method 类的 invoke()
  • 访问和修改字段值:允许在运行时访问和修改字段值(即使是私有字段)
    • 使用 Field 类的 get()set()

应用场景

主要用于加载各类驱动、配置

加载数据库驱动

动态地根据实际情况(数据库类型)加载驱动类

Class.forName("com.mysql.cj.jdbc.Driver");

加载配置文件

Spring IoC 通过配置文件配置 Bean(XML 方法)

  • 读取 XML 和 properties 配置文件
  • 解析配置文件,获得实体类字节码字符串和相关属性信息
  • 反射获取 Class 实例
  • 动态配置实例属性

注解

原理

  • 注解本质是一个继承了 Annotation 的特殊接口(声明式接口)
  • 具体实现类是 Java 运行时生成的动态代理类
  • 调用 AnnotationInvocationHandlerinvoke(),该方法会从 Map memberValues 读取对应值
    • memberValues 存在于常量池
public @interface MyAnnotation {
	String value;
}

编译后,会转换为一个继承 Annotation 的接口

类型

  • @Retention(RetentionPolicy.SOURCE) 源码级别注解:仅存在于源码
  • @Retention(RetentionPolicy.CLASS) 类文件级别注解:保留在.class 文件,运行时不可见
  • @Retention(RetentionPolicy.RUNTIME) 运行时注解:可以通过反射在运行时访问

只有运行时注解才可以通过反射解析

运行时注解

当注解被标记为  RUNTIME  时,Java 编译器会在生成的  .class  文件中保存注解信息。这些信息存储在字节码的属性表(Attribute Table)中

  • RuntimeVisibleAnnotations :存储运行时可见的注解信息。
  • RuntimeInvisibleAnnotations :存储运行时不可见的注解信息。
  • RuntimeVisibleParameterAnnotations  和  RuntimeInvisibleParameterAnnotations :存储方法参数上的注解信息。

通过工具(如  javap -v)可以查看  .class  文件中的注解信息

注解解析

通过反射 API 可以获取类、方法、字段等元素上的注解

Class<?> clazz = MyClass.class;
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
if (annotation != null) {
    System.out.println(annotation.value());
}

反射机制的核心类是  java.lang.reflect.AnnotatedElement,它是所有可以被注解修饰的元素(如  ClassMethodField  等)的父接口。该接口提供了以下方法:

  • getAnnotation(Class<T> annotationClass):获取指定类型的注解。
  • getAnnotations():获取所有注解。
  • isAnnotationPresent(Class<? extends Annotation> annotationClass):判断是否包含指定注解。

作用域

  1. 类级别作用域:用于描述类的注解,通常放置在类定义的上面,可以用来指定类的一些属性,如类的访问级别、继承关系、注释等。
  2. 方法级别作用域:用于描述方法的注解,通常放置在方法定义的上面,可以用来指定方法的一些属性,如方法的访问级别、返回值类型、异常类型、注释等。
  3. 字段级别作用域:用于描述字段的注解,通常放置在字段定义的上面,可以用来指定字段的一些属性,如字段的访问级别、默认值、注释等。
  4. 构造函数作用域
  5. 局部变量作用域

异常

异常类别

  • Error(错误):表示运行时环境的错误。错误是程序无法处理的严重问题,如系统崩溃、虚拟机错误、动态链接失败等。通常,程序不应该尝试捕获这类错误。例如,OutOfMemoryError、StackOverflowError 等。
  • Exception(异常):表示程序本身可以处理的异常条件。异常分为两大类:
    • 非运行时异常:这类异常在编译时期就必须被捕获或者声明抛出。它们通常是外部错误,如文件不存在(FileNotFoundException)、类未找到(ClassNotFoundException)等。非运行时异常强制程序员处理这些可能出现的问题,增强了程序的健壮性。
    • 运行时异常:这类异常包括运行时异常(RuntimeException)和错误(Error)。运行时异常由程序错误导致,如空指针访问(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。运行时异常是不需要在编译时强制捕获或声明的。

异常处理

try-catch

用于捕获并处理可能抛出的异常。try 块中包含可能抛出异常的代码,catch 块用于捕获并处理特定类型的异常。可以有多个 catch 块来处理不同类型的异常

try {
    // 可能抛出异常的代码
} catch (ExceptionType1 e1) {
    // 处理异常类型1的逻辑
} catch (ExceptionType2 e2) {
    // 处理异常类型2的逻辑
} catch (ExceptionType3 e3) {
    // 处理异常类型3的逻辑
} finally {
    // 可选的finally块,用于定义无论是否发生异常都会执行的代码
}
  • finally:用于定义无论是否发生异常都会执行的代码块。通常用于释放资源,确保资源的正确关闭
    • finally 块中的 return 语句会覆盖 try 块中的 return 返回
  • try 块中的代码将按顺序执行,如果抛出异常,将在 catch 块中进行匹配和处理,然后程序将继续执行 catch 块之后的代码。如果没有匹配的 catch 块,异常将被传递给上一层调用的方法

throw

用于手动抛出异常。可以根据需要在代码中使用 throw 语句主动抛出特定类型的异常

throw new ExceptionType("Exception message");

throws

用于在方法声明中声明可能抛出的异常类型。

如果一个方法可能抛出异常,但不想在方法内部进行处理,可以使用 throws 关键字将异常传递给调用者来处理

public void methodName() throws ExceptionType {
    // 方法体
}

object

==equals

  • == 对于基础对象比较值,对于引用对象比较地址值(注:比如当两个字符串字面值相等时,使用 == 比较不一定为 true
  • equals 如果没有重写,则与 == 等价;否则根据 equals 的逻辑进行比较

hashcodeequals

重写  equals  方法的类,也必需要重写  hashCode  方法,并且需要遵循以下规定

  • 一致性:如果两个对象使用  equals  方法比较结果为  true,那么它们的  hashCode  值必须相同。也就是说,如果  obj1.equals(obj2)  返回  true,那么  obj1.hashCode()  必须等于  obj2.hashCode()
  • 非一致性:如果两个对象的  hashCode  值相同,它们使用  equals  方法比较的结果不一定为  true。即  obj1.hashCode() == obj2.hashCode()  时,obj1.equals(obj2)  可能为  false,这种情况称为哈希冲突。

StringStringBufferStringBuilder

  • 可变性 :String  是不可变的(Immutable),一旦创建,内容无法修改,每次修改都会生成一个新的对象。StringBuilder  和  StringBuffer  是可变的(Mutable),可以直接对字符串内容进行修改而不会创建新对象。
  • 线程安全性 :String  因为不可变,天然线程安全。StringBuilder  不是线程安全的,适用于单线程环境。StringBuffer  是线程安全的,其方法通过  synchronized  关键字实现同步,适用于多线程环境。
  • 性能 :String  性能最低,尤其是在频繁修改字符串时会生成大量临时对象,增加内存开销和垃圾回收压力。StringBuilder  性能最高,因为它没有线程安全的开销,适合单线程下的字符串操作。StringBuffer  性能略低于  StringBuilder,因为它的线程安全机制引入了同步开销。
  • 使用场景 :如果字符串内容固定或不常变化,优先使用  String。如果需要频繁修改字符串且在单线程环境下,使用  StringBuilder。如果需要频繁修改字符串且在多线程环境下,使用  StringBuffer
// String的不可变性
String str = "abc";
str = str + "def"; // 新建对象,str指向新对象

// StringBuilder(单线程高效)
StringBuilder sb = new StringBuilder();
sb.append("abc").append("def"); // 直接修改内部数组

// StringBuffer(多线程安全)
StringBuffer sbf = new StringBuffer();
sbf.append("abc").append("def"); // 同步方法保证线程安全
特性 String StringBuilder StringBuffer
不可变性 不可变 可变 可变
线程安全 是(因不可变) 是(同步方法)
性能 低(频繁修改时) 高(单线程) 中(多线程安全)
适用场景 静态字符串 单线程动态字符串 多线程动态字符串

序列化

对象迁移

把一个对象从一个 JVM 转移到另一个 JVM

  • 使用序列化和反序列化:将对象序列化为字节流,并将其发送到另一个 JVM,然后在另一个 JVM 中反序列化字节流恢复对象。这可以通过 Java 的 ObjectOutputStream 和 ObjectInputStream 来实现。
  • 使用消息传递机制:利用消息传递机制,比如使用消息队列(如 RabbitMQ、Kafka)或者通过网络套接字进行通信,将对象从一个 JVM 发送到另一个。这需要自定义协议来序列化对象并在另一个 JVM 中反序列化。
  • 使用远程方法调用(RPC):可以使用远程方法调用框架,如 gRPC,来实现对象在不同 JVM 之间的传输。远程方法调用可以让你在分布式系统中调用远程 JVM 上的对象的方法。
  • 使用共享数据库或缓存:将对象存储在共享数据库(如 MySQL、PostgreSQL)或共享缓存(如 Redis)中,让不同的 JVM 可以访问这些共享数据。这种方法适用于需要共享数据但不需要直接传输对象的场景。

序列化选型

Java 默认的序列化虽然实现方便,但却存在安全漏洞、不跨语言以及性能差等缺陷。

  • 无法跨语言: Java 序列化目前只适用基于 Java 语言实现的框架,其它语言大部分都没有使用 Java 的序列化框架,也没有实现 Java 序列化这套协议。因此,如果是两个基于不同语言编写的应用程序相互通信,则无法实现两个应用服务之间传输对象的序列化与反序列化。
  • 容易被攻击:Java 序列化是不安全的,象是通过在 ObjectInputStream 上调用 readObject() 方法进行反序列化的,这个方法其实是一个构造器,它可以将类路径上几乎所有实现了 Serializable 接口的对象都实例化。这也就意味着,在反序列化字节流的过程中,该方法可以执行任意类型的代码,这是非常危险的。
  • 序列化后的流太大:序列化后的二进制流大小能体现序列化的性能。序列化后的二进制数组越大,占用的存储空间就越多,存储硬件的成本就越高。如果我们是进行网络传输,则占用的带宽就更多,这时就会影响到系统的吞吐量。

考虑用主流序列化框架,比如 FastJson、Protobuf 来替代 Java 序列化

Java8 特性

特性名称 描述 示例或说明
Lambda 表达式 简化匿名内部类,支持函数式编程 (a, b) -> a + b  代替匿名类实现接口
函数式接口 仅含一个抽象方法的接口,可用  @FunctionalInterface  注解标记 RunnableComparator, 或自定义接口  @FunctionalInterface interface MyFunc { void run(); }
Stream API 提供链式操作处理集合数据,支持并行处理 list.stream().filter(x -> x > 0).collect(Collectors.toList())
Optional 类 封装可能为  null  的对象,减少空指针异常 Optional.ofNullable(value).orElse("default")
方法引用 简化 Lambda 表达式,直接引用现有方法 System.out::println  等价于  x -> System.out.println(x)
接口的默认方法与静态方法 接口可定义默认实现和静态方法,增强扩展性 interface A { default void print() { System.out.println("默认方法"); } }
并行数组排序 使用多线程加速数组排序 Arrays.parallelSort(array)
重复注解 允许同一位置多次使用相同注解 @Repeatable  注解配合容器注解使用
类型注解 注解可应用于更多位置(如泛型、异常等) List<@NonNull String> list
CompletableFuture 增强异步编程能力,支持链式调用和组合操作 CompletableFuture.supplyAsync(() -> "result").thenAccept(System.out::println)

Lambda 表达式

创建匿名函数,简化函数式接口写法

  • (parameters) -> expression:当 Lambda 体只有一个表达式时使用,表达式的结果会作为返回值。
  • (parameters) -> { statements; }:当 Lambda 体包含多条语句时,需要使用大括号将语句括起来,若有返回值则需要使用  return  语句。

Stream 流

能进行并行处理

例子

过滤并收集满足条件的元素

从一个列表中筛选出所有长度大于 3 的字符串,并收集到一个新的列表中

List<String> originalList = Arrays.asList("apple", "fig", "banana", "kiwi");
List<String> filteredList = originalList.stream()
                                        .filter(s -> s.length() > 3)
                                        .collect(Collectors.toList());

计算列表中所有数字的总和

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                 .mapToInt(Integer::intValue)
                 .sum();

并行流

并行流(ParallelStream)就是将源数据分为多个子流对象进行多线程操作,然后将处理的结果再汇总为一个流对象,底层是使用通用的 fork/join 池来实现,即将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果

completableFuture

  • Future 用于表示异步计算的结果,只能通过阻塞或者轮询的方式获取结果,而且不支持设置回调方法,Java 8 之前若要设置回调一般会使用 guavaListenableFuture,但回调的引入又会导致回调地狱
  • CompletableFutureFuture 进行了扩展,可以通过设置回调的方式处理计算结果,同时也支持组合操作,支持进一步的编排,同时一定程度解决了回调地狱的问题

Java21 特性

新语言特性

  • Switch 语句的模式匹配:该功能在 Java 21 中也得到了增强。它允许在switchcase标签中使用模式匹配,使操作更加灵活和类型安全,减少了样板代码和潜在错误。例如,对于不同类型的账户类,可以在switch语句中直接根据账户类型的模式来获取相应的余额,如case savingsAccount sa -> result = sa.getSavings();
  • 数组模式:将模式匹配扩展到数组中,使开发者能够在条件语句中更高效地解构和检查数组内容。例如,if (arr instanceof int[] {1, 2, 3}),可以直接判断数组arr是否匹配指定的模式。
  • 字符串模板(预览版):提供了一种更可读、更易维护的方式来构建复杂字符串,支持在字符串字面量中直接嵌入表达式。例如,以前可能需要使用"hello " + name + ", welcome to the geeksforgeeks!"这样的方式来拼接字符串,在 Java 21 中可以使用hello {name}, welcome to the geeksforgeeks!这种更简洁的写法

新并发特性

  • 虚拟线程:这是 Java 21 引入的一种轻量级并发的新选择。它通过共享堆栈的方式,大大降低了内存消耗,同时提高了应用程序的吞吐量和响应速度。可以使用静态构建方法、构建器或ExecutorService来创建和使用虚拟线程。
  • Scoped Values(范围值):提供了一种在线程间共享不可变数据的新方式,避免使用传统的线程局部存储,促进了更好的封装性和线程安全,可用于在不通过方法参数传递的情况下,传递上下文信息,如用户会话或配置设置。

Native 方法

它允许 Java 代码调用外部的本地代码,即用 C、C++或其他语言编写的代码。native 关键字是 Java 语言中的一种声明,用于标记一个方法的实现将在外部定义。

在 Java 类中,native 方法看起来与其他方法相似,只是其方法体由 native 关键字代替,没有实际的实现代码。例如:

public class NativeExample {
    public native void nativeMethod();
}

要实现 native 方法,需要完成以下步骤:

  1. 生成 JNI 头文件:使用 javah 工具从 Java 类生成 C/C++的头文件,这个头文件包含了所有 native 方法的原型。
  2. 编写本地代码:使用 C/C++编写本地方法的实现,并确保方法签名与生成的头文件中的原型匹配。
  3. 编译本地代码:将 C/C++代码编译成动态链接库(DLL,在 Windows 上),共享库(SO,在 Linux 上)
  4. 加载本地库:在 Java 程序中,使用 System.loadLibrary() 方法来加载编译好的本地库,这样 JVM 就能找到并调用 native 方法的实现了。

参考资料