Java基础面试题

面向对象的三大特性是什么?

  • 封装:属性私有化,对外提供GET、SET方法
  • 继承:子类继承父类的属性和方法。
  • 多态 :父类型引用指向子类型对象,例如Animal a = new Cate()

重载(Overload)和重写(Override)的区别?

答案

  • 重载:同一个类中,方法名相同,参数列表不同(类型、个数、顺序),与返回值无关。编译时多态。
  • 重写:子类中,方法名、参数列表、返回值类型(或子类)完全相同,访问权限不能更严格,不能抛出更大的异常。运行时多态。

接口和抽象类的区别?

答案

特性 抽象类 接口(JDK8+)
关键字 abstract class interface
构造方法 可以有 不能有
实例变量 可以有 只能有 static final 常量
普通方法 可以有具体方法 抽象方法、默认方法(default)、静态方法
继承/实现 单继承 多实现
访问修饰符 任意 方法默认 public abstract(可写可不写)
新增方法 直接加,不影响子类 加抽象方法会破坏实现类,但加默认方法不影响
使用场景 表示一种"is-a"关系 表示一种"can-do"能力或契约

为什么 Java 是单继承,但可以实现多个接口?

  • 多继承会带来菱形继承问题(如 C++),造成方法调用的歧义和状态的重复。
  • 接口没有状态(属性),只有方法声明,因此多接口相对安全。

Java 8 后默认方法带来状态,如果两个没有继承关系 的接口提供了同名同参数的默认方法,实现类必须显式覆盖该方法,否则编译错误。

java 复制代码
interface A {
    default void go() { System.out.println("A"); }
}
interface B {
    default void go() { System.out.println("B"); }
}
class C implements A, B {
    // 编译错误!必须覆盖 go() 方法
    @Override
    public void go() {
        A.super.go();  // 选择调用 A 的默认方法
        // 或 B.super.go();
    }
}

内部类有哪些?

  • 成员内部类:普通内部类,可以访问外部类的所有成员(包括 private)。
  • 静态内部类:不持有外部类引用,相当于独立类。
  • 局部内部类:定义在方法内部,只能在该方法内使用。
  • 匿名内部类:没有名字的类,常用于实现接口

静态内部类和非静态内部类(成员内部类)的区别?TODO

特性 静态内部类 非静态内部类(成员内部类)
是否持有外部类引用 是(隐含 Outer.this
实例化方式 new Outer.StaticInner() outer.new Inner()
能否有静态成员 可以 不能(JDK 16 前)
能否访问外部类静态成员 可以 可以
能否访问外部类实例成员 不能(无外部类引用) 可以
使用场景 辅助类,与外部类实例无关 需要访问外部类实例状态

this()super() 可以同时出现在一个构造器中吗?为什么?

不能 。因为 this()super() 都必须作为构造器的第一条语句,二者矛盾。

  • this() 调用当前类的其他构造器。
  • super() 调用父类的构造器。
  • 如果都没有显式写出,编译器会默认插入 super()

代码块(静态、实例、构造器)的执行顺序?

父类 → 子类,静态 → 实例 → 构造器

  1. 父类静态代码块
  2. 子类静态代码块
  3. 父类实例代码块
  4. 父类构造器
  5. 子类实例代码块
  6. 子类构造器

每次创建对象时,实例代码块和构造器都会执行;静态代码块只在类首次加载时执行一次。

static 关键字的作用有哪些?

  • 静态变量:所有该类的对象共享同一个变量,内存中只有一份。
  • 静态方法 :不需要创建对象,直接通过 类名.方法名() 就能调用。
  • 静态代码块:类加载时执行一次,用于初始化静态资源。
  • 静态内部类 :被 static 修饰的内部类。它不依赖外部类的对象,可以独立创建实例。

final 修饰的变量就是常量吗?修饰引用类型时有什么陷阱?

  • final 修饰基本类型:值不可变,是真正的常量。
  • final 修饰引用类型:不能指向其他对象,但对象内部属性可以改变
java 复制代码
final StringBuilder sb = new StringBuilder("hello");
sb.append(" world");           // 允许
sb = new StringBuilder("new"); // 编译错误

Object 类中常用的方法有哪些?(至少说出 5 个)

  • equals(Object obj):判断对象是否相等。
  • hashCode():返回哈希码,与 equals 协同。
  • toString():返回对象的字符串表示,通常重写。
  • getClass():返回运行时类对象。
  • clone():创建并返回对象副本(需实现 Cloneable 接口)。
  • finalize():GC 前调用(已过时,不推荐使用)。
  • wait() / notify() / notifyAll():线程同步(多线程范畴,了解即可)。

枚举(enum)是什么?有什么优点?

  • 枚举是一种特殊的类,用于定义固定数量的常量集合(如季节、星期、状态码)。

  • 优点

    • 类型安全,编译期检查。
    • 可以添加字段、方法、构造器,实现接口。
    • 单例模式的首选方案(Enum 保证序列化/反射安全)。
    • 自带 values() 方法遍历所有枚举值。

    说明

    类型安全意味着:一个变量只能被赋予其类型所允许的值,任何不匹配的赋值或操作都会被编译器阻止,不会在运行时才出现意外。

    对于枚举而言:

    • 枚举变量只能指向该枚举类型中定义的常量之一,或者 null(虽然允许 null,但通常避免)。
    • 不能赋值为其他类型的值(如整数、字符串、其他枚举常量)。
    java 复制代码
    // 限制赋值:拒绝非法值
    public enum Season { SPRING, SUMMER, AUTUMN, WINTER }
    
    Season s = Season.SPRING;      // ✅ 正确
    Season s = SPRING;              // ✅ 正确(静态导入后)
    Season s = "SPRING";            // ❌ 编译错误:类型不匹配(String 无法转为 Season)
    Season s = 1;                   // ❌ 编译错误:int 无法转为 Season
    Season s = null;                // ✅ 允许(但使用时需判空)
    Season s = Season.MONDAY;       // ❌ 编译错误:MONDAY 不是 Season 的成员

Java 是值传递还是引用传递?

Java 只有值传递

  • 基本类型传递的是值的副本,修改形参不影响实参。
  • 引用类型传递的是对象地址的副本,形参和实参指向同一对象,因此修改对象的属性会影响原对象;但重新赋值形参不会影响实参。
java 复制代码
class Person {
    String name;
    Person(String name) { this.name = name; }
}

public class ReferenceTest {
    public static void changeName(Person p) {
        p.name = "李四";  // 通过地址副本找到同一个对象,修改属性
    }

    public static void reassign(Person p) {
        p = new Person("王五");  // 只改变形参的指向,不影响实参
    }

    public static void main(String[] args) {
        Person person = new Person("张三");
        changeName(person);
        System.out.println(person.name);  // 李四 ------ 对象属性被修改

        reassign(person);
        System.out.println(person.name);  // 仍然是李四,实参未指向新对象
    }
}

1. Java 的基本数据类型有哪些?对应的包装类是什么?

基本类型 字节数 包装类
byte 1 Byte
short 2 Short
int 4 Integer
long 8 Long
float 4 Float
double 8 Double
char 2(Unicode) Character
boolean 通常 1 字节 Boolean

注意:boolean 在 JVM 中通常用 int 表示,单个 boolean 占 4 字节,数组中的每个元素占 1 字节。

什么是自动装箱与拆箱?

  • 装箱 :基本类型 → 包装类(如 intInteger),编译期调用 Integer.valueOf()
  • 拆箱 :包装类 → 基本类型(如 Integerint),编译期调用 xxxValue()

包装类常量池

  • IntegerShortByteLongCharacter 都缓存了 -128 ~ 127 之间的值。
  • 例:Integer a = 100; Integer b = 100; a == btrueInteger c = 128; Integer d = 128; c == dfalse(超出缓存范围,创建新对象)。

为什么需要包装类?

  1. 支持泛型和集合:泛型和集合只能使用引用数据类型,无法使用基本类型。
  2. 引用数据类型可以表达 null
  3. 包装类提供了更丰富的API

Integer 的 valueOf() 和 parseInt() 的区别?

  • parseInt(String s) :返回 int 基本类型
    解析字符串为十进制有符号整数,无法解析时抛出 NumberFormatException
  • valueOf(String s) :返回 Integer 包装类对象
    内部实际调用了 parseInt() 获取 int 值,然后通过 Integer.valueOf(int) 装箱成 Integer 对象,因此会利用到 Integer 的缓存机制(-128~127)。

Integer 类中 int 的最大值和最小值是多少?如何获取?

  • 最大值 (MAX_VALUE)2^31 - 1,即 2,147,483,647 。获取:Integer.MAX_VALUE;
  • 最小值 (MIN_VALUE)-2^31,即 -2,147,483,648 。获取:Integer.MIN_VALUE;

对包装类进行算术运算(如 Integer i = 1; i++)发生了什么?

对包装类(如 Integer)进行算术运算(例如 i++i = i + 1),其本质是自动拆箱、基本类型运算、自动装箱三个步骤的结合。

short s = 1; s = s + 1;short s = 1; s += 1; 哪个正确?为什么?

  • s = s + 1 编译错误s + 1 自动提升为 int,赋值给 short 需要强制转型。
  • s += 1 正确+= 是 Java 规定的复合赋值运算符,隐含了强制转型,相当于 s = (short) (s + 1)

switch 语句支持哪些数据类型?

  • 原始支持:byteshortintchar
  • 包装类:ByteShortCharacterInteger(自动拆箱)。
  • String(Java 7+)。
  • enum(枚举)。

switch 语句能作用于 long 类型吗?

在 Java 5 及之前:不能。Java 7 开始支持 String 和枚举,但仍然不支持 long 。支持的类型:byteshortcharintStringenum 以及它们的包装类(ByteShortCharacterInteger)。

原因:switch 的底层使用 lookupswitchtableswitch 指令,这些指令只支持 int 及以下类型。

breakcontinue 的区别?支持标签吗?

  • break:跳出当前循环体(或 switch),不再执行剩余迭代。
  • continue:跳过本次循环剩余语句,继续下一次迭代。
  • Java 支持带标签的 breakcontinue ,可以跳出多层循环
java 复制代码
outer: for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (i == 1 && j == 1) break outer;
    }
}

&&& 的区别?||| 的区别?

&&是短路与:左边为 false 时,右边不执行;&是逻辑与,两边都会执行。

||是短路或:左边为 true 时,右边不执行;|是逻辑或,两边都会执行。

floatdouble 的精度问题?如何解决?

  • floatdouble 是二进制浮点数,无法精确表示某些十进制小数(如 0.1),导致计算误差(如 0.1 + 0.2 == 0.3 返回 false)。
  • 解决方法 :使用 BigDecimal,尤其是金融计算。
java 复制代码
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
System.out.println(bd1.add(bd2).equals(new BigDecimal("0.3"))); // true

注意:BigDecimal 构造器应使用 String 参数,避免使用 double 构造器。

String为什么不可变?有什么好处?

  1. 底层数组被 finalprivate 修饰
    String 内部使用一个数组来存储字符数据(JDK 8 及以前是 char[] value,JDK 9 及以后优化为 byte[] value)。这个数组被 private 修饰,外部无法直接访问;同时被 final 修饰,保证了数组的引用一旦初始化就永远不能指向其他数组。
  2. 没有提供任何修改内部数组的方法
  3. 类本身被 final 修饰
    String 类被 final 关键字修饰,意味着它不能被继承。这防止了子类通过继承来篡改 String 的不可变特性。

好处:1、实现字符串常量池,极大节省内存 2、保证线程安全

String、StringBuffer、StringBuilder 的区别?

答案

  • String :不可变(final 修饰字符数组),线程安全,适合少量字符串操作。
  • StringBuffer :可变,线程安全(方法使用 synchronized 修饰)。因为加锁的开销导致性能略低。适合多线程环境下的字符串操作。
  • StringBuilder:可变,非线程安全。性能最高,推荐在单线程下使用。

字符串常量池的作用?

  • 节省内存空间:通过共享相同内容的字符串,确保它们在内存中只保留一份,避免重复创建对象。
  • 提升程序性能 :可以直接使用 == 进行高效的引用地址比较,从而避免使用 .equals() 逐个字符比对的开销。

字符串常量池的位置(随 JDK 版本变化)

JDK 版本 存储位置 说明
JDK 6 及以前 方法区(永久代) 字符串常量池位于永久代(PermGen),与 Java 堆分离。
JDK 7 堆(Heap) 将字符串常量池从永久代移到堆中,因为永久代空间有限,移入堆可减少 OOM 风险。
JDK 8+ 堆(Heap) 永久代被元空间(Metaspace)取代,但字符串常量池仍保留在堆中。

总结(当前主流环境 JDK 8+) :字符串常量池存储在

String s = new String("abc") 创建了几个对象?

  • 若字符串常量池中没有 "abc",则创建 2 个 :一个在堆中的 String 对象,一个在常量池中的 "abc" 字符串实例(实际上池中存储的是引用,但通常说创建了对象)。
  • 若池中已存在,则只创建 1 个 (堆中的 String 对象)。

==equals() 的区别?

答案

  • == :对于基本类型(int, char, boolean 等),比较的是 ;对于引用类型(对象),比较的是内存地址(是否同一个对象)。
  • equals() :是 Object 类的方法,默认实现也是比较地址(==)。但很多类(如 StringIntegerDate)重写了 equals(),用于比较内容是否相等。

异常体系结构

复制代码
Throwable
├── Error(严重错误,程序无法处理,如 OOM、StackOverflowError)
└── Exception
    ├── 编译时异常(受检异常,如 IOException、SQLException)
    └── RuntimeException(如空指针异常、数组越界异常)

受检异常(Checked Exception)和非受检异常(Unchecked Exception)的区别?

核心区别在于编译器是否强制要求开发者处理该异常

对比项 受检异常 非受检异常
是否需要处理异常 必须 try-catchthrows 声明 不强制处理
继承关系 继承自 Exception 类,但不包括 RuntimeException 及其子类 包括 RuntimeException 及其子类,以及 Error 及其子类。
常见例子 IOException, ClassNotFoundException NullPointerException, 数组越界异常

3. finally 块一定会执行吗?

  • 正常情况下会执行(无论是否发生异常,也无论 try 中是否有 return)。
  • 以下几种情况不执行
    • trycatch 中调用 System.exit(0)
    • 虚拟机崩溃。
    • 守护线程被强制终止。

4. try-with-resources 是什么?

  • 用于自动关闭实现了 AutoCloseable 接口的资源(如流、数据库连接)。
  • 示例:
java 复制代码
try (FileInputStream fis = new FileInputStream("test.txt")) {
    // 使用 fis
} catch (IOException e) {
    e.printStackTrace();
}
// fis 自动关闭

finalfinallyfinalize 的区别?

答案

  • final :关键字。
    • 修饰类:类不能被继承。
    • 修饰方法:方法不能被重写。
    • 修饰变量:变量一旦赋值后不可修改(基本类型值不变,引用类型不能指向其他对象,但对象内部状态可变)。
  • finally :异常处理的一部分。无论是否捕获异常,finally 块中的代码都会执行。通常用于释放资源(关闭文件、数据库连接)。
  • finalize()Object 类的方法。垃圾回收器在回收对象前会调用此方法(仅一次)。JDK 9 已标记为弃用。

try-catch-finally 中,如果 catchfinally 都有 return,哪个生效?

finally 中的 return 会覆盖 catch 中的 return

同样,finally 中的 return 也会覆盖 try 中的 return

这是危险行为,编译器会警告,实际开发中应避免在 finally 中使用 return

throws 和 throw 的区别?

throw 在方法体内实际抛出异常对象,throws 在方法签名处声明可能抛出的异常类型。

大量异常 catch 块的顺序有什么要求?

在多个 catch 块中,子类异常必须写在父类异常之前,否则编译报错。

对性能而言,try-catch 放在循环内部和外部哪个好?为什么?

放在循环外部更好 。原因:如果 try-catch 放在循环内部,每次循环迭代都会进入和退出 try 块,JVM 需要反复检查异常表结构,增加额外开销;而放在外部只需建立一次异常处理上下文,避免了这种重复成本。此外,如果循环内抛出异常,外部 catch 可以统一处理后终止或继续循环,逻辑也更清晰。

如何自定义异常?什么时候需要自定义异常?

自定义异常只需继承 Exception(受检异常)或 RuntimeException(非受检异常),并提供构造方法(通常提供无参构造和带消息的构造)。

java 复制代码
// 自定义受检异常
public class BusinessException extends Exception {
    public BusinessException() {}
    public BusinessException(String message) {
        super(message);
    }
}

// 自定义非受检异常
public class ValidationException extends RuntimeException {
    public ValidationException(String message) {
        super(message);
    }
}

什么时候需要自定义异常?

​ 当标准异常(如 IllegalArgumentException)无法清晰表达业务错误含义时

异常处理中 e.printStackTrace() 有什么问题?生产环境应该怎么记录异常?

e.printStackTrace() 会输出到标准错误流(System.err)、难以集成日志系统且可能阻塞性能,生产环境应使用 SLF4J 等日志框架并调用 log.error("描述", e) 记录完整堆栈。

什么是泛型?泛型原理?类型擦除?List<String和List<Integer在运行中一样吗?什么情况下泛型擦除会出问题?

泛型是 JDK 5 引入的类型参数化机制,允许在定义类、接口、方法时使用类型参数(如 <T>),在使用时再传入具体类型(如 StringInteger)。

泛型的作用

提供编译时类型安全检查:避免运行时出现 ClassCastException

避免强制类型转换:使用泛型后,从集合或泛型对象中获取数据就是目标类型,不需要强制类型转换。

代码复用:通过泛型编写通用算法/数据结构(如 ArrayList<T>),适用于多种类型。

Java 泛型的实现原理可以概括为

编译时类型检查 + 运行时类型擦除。

类型擦除:编译后泛型信息被移除,替换为原始类型(Object或上限类型),并插入强制转换代码。

List 和 List 在运行时是同一个类型吗?为什么?

是,运行时会进行类型擦除,List<String>List<Integer> 在运行时都是List。

类型擦除会导致哪些问题?

1、无法用泛型类型做 instanceof 检查

java 复制代码
// 编译错误:Illegal generic type for instanceof
if (obj instanceof List<String>) { ... }

只能检查原始类型:obj instanceof List

2、不能创建泛型数组

java 复制代码
// 编译错误:Cannot create a generic array of T
T[] array = new T[10];

因为擦除后类型信息丢失,运行时无法知道应该分配什么类型的数组。可以创建 ArrayList<T> 代替。

3、不能实例化泛型类型(new T()new T[]

java 复制代码
public <T> void create() {
    T obj = new T();   // 编译错误
}

泛型有哪些使用场景?

  1. 泛型类

场景:定义容器类、集合框架、工具类,使其能处理任意类型。

java 复制代码
public class Box<T> {
    private T item;
    public void set(T t) { this.item = t; }
    public T get() { return item; }
}
// 使用:Box<String> box = new Box<>();
  1. 泛型接口

场景:定义生成器、比较器、工厂等通用契约,让实现类指定具体类型。

java 复制代码
public interface Comparator<T> {
    int compare(T o1, T o2);
}
// 实现:class StringComparator implements Comparator<String>
  1. 泛型方法

场景:编写独立于类的通用算法,例如交换数组元素、集合转换等。

java 复制代码
public static <T> T getMiddle(T... a) {
    return a[a.length / 2];
}
// 调用:String s = getMiddle("a", "b", "c");

什么是原始类型(Raw Type)?使用原始类型有什么风险?

原始类型 是指在使用泛型类或泛型接口时,省略了类型参数的写法。例如:

java 复制代码
List list = new ArrayList();        // 原始类型,等价于 List<Object>
Comparable c = "hello";             // 原始类型

原始类型主要是为了 兼容 JDK 5 之前没有泛型的旧代码

风险:丧失编译时的类型安全检查,可能导致运行时异常。

泛型通配符 ?? extends T? super T 的区别?

  • ?:无界通配符,表示任意类型,只能读不能写(除了 null)。
  • ? extends T:上界通配符,表示类型是 TT 的子类。可以读为 T,但不能写入(因为不知道具体子类型)。
  • ? super T:下界通配符,表示类型是 TT 的父类。可以写入 T 及其子类,但读取时只能得到 Object
  • PECS 原则 :Producer Extends, Consumer Super(生产数据用 extends,消费数据用 super)。

怎么理解生产者

在泛型编程中,当我们声明一个 List<? extends Number> 时,这个列表是作为参数传入当前方法的。对于当前方法而言,它不需要往里面塞东西,而是需要从中**获取(提取)已有的数据来进行计算或处理。
因为它是向当前方法
提供、产出(Produce)**数据的源头,所以被称为"生产者"。

1. 无界通配符 ?:我只负责"看",不负责"装"

场景:你需要写一个通用的工具方法,比如打印任意类型的集合,或者判断集合是否为空。你根本不关心集合里装的是猫、狗还是数字。

java 复制代码
public void printList(List<?> list) {
    // ✅ 能读:因为不管里面是什么,它一定是一个 Object
    for (Object obj : list) {
        System.out.println(obj);
    }
    
    // ❌ 不能写:编译器彻底懵了,它不知道这个 List 原本装的是 String 还是 Integer
    // list.add("hello"); // 编译报错!万一你传进来的是 List<Integer> 呢?
    
    // ✅ 唯一能写的只有 null,因为 null 是所有类型的子集
    list.add(null); 
}

核心逻辑 :用了 ?,等于告诉编译器:"我也不知道这里面是啥"。所以编译器为了绝对安全,除了 null,禁止你往里面放任何东西。

2. 上界通配符 ? extends T:只进不出(Producer)

场景 :假设有一个动物继承体系:Animal(父类)、Dog(子类)、Cat(子类)。你需要写一个方法,接收一堆动物并让它们叫。

java 复制代码
public void makeSound(List<? extends Animal> animals) {
    // ✅ 能读:虽然我不知道具体是 Dog 还是 Cat,但我知道它们肯定都是 Animal
    Animal a = animals.get(0); 
    a.shout(); 
    
    // ❌ 不能写:编译器会想,"万一你传进来的是一个专门装 Cat 的笼子(List<Cat>),
    // 我却让你塞进去一只 Dog,那这个笼子不就乱套了吗?"
    // animals.add(new Dog()); // 编译报错!
}

核心逻辑extends 限制了上限。因为无法确定具体的子类是谁,为了防止把"狗"塞进"猫"的笼子里,编译器直接禁止写入。它就像一个生产者(Producer),只负责往外提供数据。

3. 下界通配符 ? super T:只出不进(Consumer)

场景 :你需要写一个方法,往一个集合里批量添加一堆 Dog 对象。

java 复制代码
public void addDogs(List<? super Dog> dogs) {
    // ✅ 能写:编译器会想,"这个笼子要么是装 Dog 的,要么是装 Dog 的父类(比如 Animal 或 Object)的。
    // 那我往里面塞 Dog 或者 Dog 的子类(比如 Puppy),肯定都是安全的!"
    dogs.add(new Dog());
    dogs.add(new Puppy()); 
    
    // ❌ 不能精准读:编译器会想,"这个笼子可能是 List<Object>,
    // 我如果让你读出来直接当 Dog 用,万一里面其实混进了一个 Cat 怎么办?"
    // Dog d = dogs.get(0); // 编译报错!
    
    // ✅ 只能读成 Object,因为 Object 是所有类的老祖宗,最安全
    Object obj = dogs.get(0);
}

核心逻辑super 限制了下限。因为保证了容器里的东西一定比 Dog 更"大"或相等,所以往里塞 Dog 绝对没问题。它就像一个消费者(Consumer),只负责把数据吃进去。

无界通配符 ? 和 Object 作为类型参数有什么区别?

  • <?> (无界通配符):代表一个**"未知但确定"**的具体类型。它表示这个容器内部装的是某种特定的数据类型(可能是 String,也可能是 Integer),只是编译器目前不知道具体是哪一种。
  • Object :是一个明确且具体 的类型,它是 Java 类继承树的顶层父类。List<Object> 明确表示这个列表里存放的就是 Object 及其子类对象。

什么是反射?优缺点?应用场景?

答案

  • 反射:在运行时动态获取类的完整信息(构造器、方法、属性)并操作对象,甚至可以访问私有成员。
  • 核心类ClassConstructorMethodField

优点:在运行时动态创建对象、调用方法或访问字段,极大提升了代码的灵活性和框架的通用性。

缺点:性能较低(比直接调用慢,因为需要解析元数据)。破坏封装性,有安全隐患。

应用场景

  • Spring / MyBatis / Hibernate等框架开发:例如动态创建 Bean、依赖注入、ORM 映射。
  • 动态代理(JDK 动态代理)
  • 序列化与反序列化(JSON/XML 转换)

如何获取 Class对象?

  • 类名.class:Class<Person> clazz = Person.class;
  • 对象.getClass():Person p = new Person(); Class<?> clazz = p.getClass();
  • Class.forName("全限定类名"):Class<?> clazz = Class.forName("com.example.Person");

如何通过反射创建对象?有哪两种方式?

方式一:使用 Class 类的 newInstance() 方法

java 复制代码
Class<?> clazz = Person.class;
Object obj = clazz.newInstance();  // 要求类必须有 public 无参构造方法
  • 特点 :只能调用 无参构造方法 ,且构造方法必须是 public 的。
  • 注意 :从 JDK 9 开始,此方法已被标记为 过时,推荐使用方式二。

方式二:使用 Constructor 类的 newInstance() 方法

java 复制代码
Class<?> clazz = Person.class;
// 获取指定参数类型的构造方法
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("张三", 25);  // 可传入实际参数
  • 特点 :可以调用 任意有参/无参构造方法 (包括 private 构造方法,需先 setAccessible(true))。
  • 优势 :更灵活、更安全,是 推荐的反射创建对象方式

如何通过反射获取类的构造方法、方法和字段?

  1. 获取 Class 对象(反射的入口)
  2. 使用 Class 对象提供的 API 获取对应成员
  3. 通过 setAccessible(true) 访问私有成员
java 复制代码
// 获取Class对象
Class<?> aClass = Class.forName("com.atguigu.product.Reflect");
// 创建对象
Constructor<?> constructor = aClass.getDeclaredConstructor(String.class, int.class);
Object obj = constructor.newInstance("zhangsan", 16);
System.out.println(obj);
// 获取方法
Method method = aClass.getDeclaredMethod("doSome", String.class, String.class);
method.setAccessible(true);
method.invoke(obj,"userName","passWd");
// 获取属性
Field field = aClass.getDeclaredField("name");
field.setAccessible(true);
Object o = field.get(obj);
field.set(obj,"lisi");
System.out.println(field.get(obj));

反射中 setAccessible(true) 的作用是什么?有什么风险和注意事项?

通过 setAccessible(true) 访问私有成员,破坏封装性、有安全隐患(恶意代码可能通过反射获取敏感数据)、性能较低(JVM 禁用优化(如内联),调用速度比直接访问慢很多)。

反射与注解:如何获取类、方法、字段上的注解信息?

使用 ClassMethodFieldgetAnnotation() ˌænəˈteɪʃngetAnnotations() 方法即可获取运行时注解信息。

注:被获取的注解必须使用 @Retention(RetentionPolicy.RUNTIME) 元注解标注,否则运行时无法读取。

java 复制代码
// 定义注解(运行时保留)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@interface MyAnnotation {
    String value();
}

// 使用注解
@MyAnnotation("Class")
public class Demo {
    @MyAnnotation("Field")
    private String name;

    @MyAnnotation("Method")
    public void run() {}
}

// 反射获取注解信息
public class AnnotationTest {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Demo.class;

        // 1. 获取类上的注解
        MyAnnotation classAnno = clazz.getAnnotation(MyAnnotation.class);
        System.out.println(classAnno.value()); // "Class"

        // 2. 获取字段上的注解
        Field field = clazz.getDeclaredField("name");
        MyAnnotation fieldAnno = field.getAnnotation(MyAnnotation.class);
        System.out.println(fieldAnno.value()); // "Field"

        // 3. 获取方法上的注解
        Method method = clazz.getDeclaredMethod("run");
        MyAnnotation methodAnno = method.getAnnotation(MyAnnotation.class);
        System.out.println(methodAnno.value()); // "Method"

        // 获取所有注解(包括继承的)
        Annotation[] allAnno = clazz.getAnnotations();
        // 仅获取直接声明的
        Annotation[] declaredAnno = clazz.getDeclaredAnnotations();
    }
}

注解(Annotation)的作用与使用场景?

  • 提供元数据信息,供编译器或运行时框架读取。
  • 场景
    • 编译检查(如 @Override 防止重写错误)
    • 框架配置(Spring 的 @Autowired@Service
    • 测试(JUnit 的 @Test
    • 代码生成(如 Lombok 的 @Data 生成 getter/setter)

注解按运行机制分为哪几类?@Retention 的作用是什么?取值有哪些?

保留策略 对应 @Retention 取值 注解信息保留范围 典型应用
源码注解 SOURCE 仅保留在源代码中,编译时被丢弃 @Override@SuppressWarnings(仅编译期检查,不进入字节码)
编译时注解 CLASS(默认值) 保留在 .class 字节码文件中,但运行时 JVM 无法读取 注解处理器(APT)在编译期处理,如 Lombok、ButterKnife;运行时不使用
运行时注解 RUNTIME 保留在字节码中,且运行时可通过反射读取 Spring (@Autowired)、JUnit (@Test)、自定义框架注解

@Retention 的作用

@Retention 是一个元注解 (用于注解的注解),用来指定被标注的注解可以保留到哪个阶段,即注解的生命周期。

@Retention 的取值

java 复制代码
public enum RetentionPolicy {
    SOURCE,   // 仅源代码,编译后丢弃
    CLASS,    // 保留到字节码,但运行时不可见(默认值)
    RUNTIME   // 保留到运行时,可通过反射获取
}

注意 :若要注解在运行时通过反射被读取,必须使用 @Retention(RetentionPolicy.RUNTIME)

@Target 元注解的作用是什么?常用的 ElementType 有哪些?

@Target 元注解的作用

@Target 用于限制注解可以应用在哪些 Java 元素上 (如类、方法、字段等)。如果不指定 @Target,该注解可以用于任何位置。

常用的 ElementType 取值

ElementType 说明 示例
TYPE 类、接口、枚举、注解类型 public class MyClass {}
FIELD 字段(包括枚举常量) private String name;
METHOD 方法 public void run() {}
PARAMETER 方法参数 void say(String name) 中的 name
CONSTRUCTOR 构造方法 public MyClass() {}
LOCAL_VARIABLE 局部变量 int i = 0;
ANNOTATION_TYPE 注解类型 用于元注解
PACKAGE package-info.java
TYPE_PARAMETER (Java 8+) 泛型类型参数 class Box<T> { } 中的 T
TYPE_USE (Java 8+) 类型使用(任何类型出现的地方) List<@NonNull String>

说明TYPE_USE 可出现在类型使用的任何位置(如泛型、强制转换、异常等),用于细化类型检查。

注解可以继承吗?@Inherited 的作用是什么?

注解本身不支持继承 :在 Java 中,注解不能使用 extends 关键字去继承另一个注解(注解的声明上不允许使用 extends)。每个注解都隐式继承自 java.lang.annotation.Annotation 接口,但这是语言内置的,不是用户可扩展的继承关系。

@Inherited 元注解的作用

@Inherited 用于控制注解是否对子类产生继承效果 。当一个注解被 @Inherited 修饰后:

  • 如果该注解被用在某个 上,那么它的子类将自动继承该注解(即子类上也存在该注解)。
  • 只对类继承有效,对接口、方法、字段等无效。
  • 如果子类自己显式使用了该注解,则子类的注解会覆盖(不继承)父类的。

示例

java 复制代码
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation {}

@MyAnnotation
class Parent {}

class Child extends Parent {}  // Child 也自动拥有 @MyAnnotation

如何定义一个自定义注解?注解中可以有哪些类型的属性?

使用 @interface 关键字声明,并通常配合元注解(如 @Retention@Target)来指定保留策略和作用目标。

java 复制代码
// 定义一个运行时注解,可用于类和方法
@Retention(RetentionPolicy.RUNTIME)  // 保留到运行时
@Target({ElementType.TYPE, ElementType.METHOD})  // 可用于类和方法
public @interface MyAnnotation {
    // 属性
    String value() default "";       // 带有默认值
    int count() default 0;
    String[] tags() default {};
}

注解中可以包含哪些类型的属性?

注解中定义的属性实质上是无参数方法,其返回值类型只能是以下类型之一:

类型 示例
基本类型(8种) intbooleandouble
String String name();
Class Class<?> type();
枚举(enum Level level();
注解类型 MyAnnotation2 ref();
上述类型的数组 String[] tags();int[] ids();

注意

  • 不支持包装类型(如 IntegerBoolean),也不支持 Object 或集合类型(如 List)。
  • 可以为属性提供默认值(使用 default 关键字),也可以不提供(使用时必须显式赋值,除非有默认值)。
  • 特殊属性 value:如果注解只有一个 value 属性,使用时可以省略属性名,直接写值。

注解的属性可以有默认值吗?如何设置?

注解的属性可以有默认值 ,使用 default 关键字来设置。

Java IO 流分类?

  • 按流向 :输入流(InputStream/Reader)、输出流(OutputStream/Writer)。
  • 按单位 :字节流(InputStream/OutputStream)、字符流(Reader/Writer)。
  • 按功能 :节点流(直接连接数据源,如 FileInputStream)、处理流(包装其他流,如 BufferedInputStream)。

字符流和字节流的区别?

  • 字节流以 字节(8 bit) 为单位,适合处理二进制数据(图片、音视频等)。
  • 字符流以 字符(Unicode code unit) 为单位,适合处理文本数据,自动处理编码转换。
  • 字节流没有缓冲区,字符流内部使用缓冲区(需要 flush)。

什么是节点流?什么是处理流?它们的区别是什么?

  • 节点流 是直接连接数据源(比如文件、内存数组、网络连接)的流,它负责最底层的实际读写操作。像 FileInputStreamByteArrayOutputStream 这些就是节点流。
  • 处理流 不能独立存在,它必须包装在另一个流之上,用于增强功能。比如 BufferedInputStream 提供缓冲、ObjectOutputStream 支持对象序列化、InputStreamReader 实现字节转字符等。

BIO、NIO、AIO 的区别?

模型 描述 特点 适用场景
BIO(同步阻塞) 一个连接一个线程,线程在读写数据时阻塞 简单,但线程开销大,并发低 连接数少、长连接的场景(如传统 Tomcat 7)
NIO(同步非阻塞) 基于 Selector,一个线程管理多个 Channel,轮询事件 高并发,编程复杂 Netty、Tomcat 8+
AIO(异步非阻塞) 由 OS 完成读写后回调通知 适合大量读写操作且耗时长的场景 目前在 Java 中应用较少

NIO 核心组件

  • Channel (通道):双向数据传输的通道。
  • Buffer (缓冲区):所有数据都通过 Buffer 进行读写。
  • Selector (选择器):一个线程可以管理成千上万个 Channel。它通过轮询的方式检查哪些 Channel 已经准备好进行 I/O 操作(如可读、可写)。

Java IO 的四大抽象基类是什么?

抽象类 数据单位 方向 核心方法
InputStream 字节 输入 int read()read(byte[] b)
OutputStream 字节 输出 void write(int b)write(byte[] b)
Reader 字符 输入 int read()read(char[] c)
Writer 字符 输出 void write(int c)write(char[] c)

InputStream 的 read() 方法为什么返回 int 而不是 byte?

  • byte 的取值范围是 -128 ~ 127 ,其中 -1 是一个合法的字节值(对应十六进制 0xFF),选用byte时无法区分是字节值还是流结束。
  • int 的取值范围更大,可以用 -1 专门表示流结束,而 0 ~ 255 表示实际读取到的无符号字节值。

什么是序列化?transient 关键字的作用?

  • 序列化 :将类实现了Serializable 接口的 Java 对象转换为字节流,用于持久化或网络传输;反序列化是将字节流转换成Java对象。
  • transient 修饰的字段不会被序列化 ,反序列化后其值为默认值(null0 等),适用于敏感信息字段。

hashCode()equals() 的关系?

重写 equals 时必须重写 hashCode,否则在Hash集合(如 HashSetHashMap)中可能无法正确去重。

  • 如果两个对象 equals 相等,则 hashCode 必须相等。
  • 反之,hashCode 相等不一定 equals 相等(哈希冲突)。

2. 浅拷贝和深拷贝的区别?

  • 浅拷贝:复制对象时,只复制基本类型和引用地址,不复制引用指向的对象。克隆后的对象与原对象共享引用类型属性。
  • 深拷贝:递归复制所有引用对象,使拷贝对象与原对象完全独立。
  • 实现:实现 Cloneable 接口并重写 clone() 方法(浅拷贝);深拷贝可通过序列化或手动递归实现。

3. 如何实现对象克隆?

  • 实现 Cloneable 接口,重写 clone() 方法(浅拷贝)。
  • 使用序列化(Serializable)实现深拷贝。

Java 8 中 java.time 包如何格式化日期?

java 复制代码
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(dtf);          // 2025-03-01 14:30:00
LocalDateTime parsed = LocalDateTime.parse("2025-03-01 14:30:00", dtf);

DateTimeFormatter 是线程安全的,可以定义为 static final 全局使用。

SimpleDateFormat 是线程安全的吗?如何处理?

不是线程安全的 ,因为其内部使用了 Calendar 对象,多线程并发调用 format()parse() 会产生竞态条件。

解决方案

  • 每次使用时 new 一个实例(开销小)。
  • 使用 ThreadLocal 包装。
  • 改用 Java 8+ 的 DateTimeFormatter(不可变、线程安全)。
java 复制代码
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
String format = formatter.format(now);
System.out.println(format);

LocalDateTime localDateTime = LocalDateTime.parse(format, formatter);
System.out.println(localDateTime);

14. 什么是 native 方法?用途是什么?

  • native 关键字修饰的方法表示由本地代码(如 C/C++)实现,不提供 Java 方法体。
  • 用途
    • 调用底层操作系统 API。
    • 提高性能。某些计算密集型的算法(如矩阵运算、加密)用 C/C++ 实现,性能优于 Java。

15. Java 中的 Math.round(-1.5) 返回值是多少?

  • 四舍五入规则:向正无穷方向取整 (或常说"加 0.5 后向下取整")。
    Math.round(-1.5) = -1(因为 -1.5 + 0.5 = -1.0,向下取整得 -1)。
    验证:Math.round(-1.6) = -2
  • 对于 float 返回 intdouble 返回 long

函数式接口有哪几个?

函数式接口是只包含一个抽象方法 的接口,可以用 @FunctionalInterface 注解标记(非强制)。Java 8 在 java.util.function 包中定义了四大基础函数式接口,以及许多扩展版本。

一、四大基础函数式接口
接口名 抽象方法 参数 返回值 用途
Function<T,R> R apply(T t) 1个 (T) R 类型转换:T → R
Predicate<T> boolean test(T t) 1个 (T) boolean 条件判断/过滤
Consumer<T> void accept(T t) 1个 (T) void 消费一个参数(无返回值)
Supplier<T> T get() T 提供/生产数据
其他常见函数式接口(非 java.util.function 包)
接口 抽象方法 说明
Runnable void run() 无参无返回值,常用于线程任务
Callable<V> V call() throws Exception 有返回值,可抛异常
Comparator<T> int compare(T o1, T o2) 比较两个对象

单例模式的核心目标是什么?有哪些实现方式?

  • 目标:确保一个类仅存在一个实例,并提供全局唯一的访问入口。
  • 常见实现:饿汉式、懒汉式(线程不安全/安全)、双重检查锁(DCL)、静态内部类、枚举。
java 复制代码
// 懒汉式(同步方法,不推荐,性能差)
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

// 双检:DCL
public class Singleton {
    private static volatile Singleton instance;  // 必须 volatile
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {          // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {  // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

// 饿汉式(简单,类加载时即创建,线程安全)
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

// 静态内部类实现(延迟加载,线程安全,推荐)
public class Singleton {
    private Singleton() {}

    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

// 枚举实现(最简洁、绝对防止多次实例化,且防反射/序列化)
public enum Singleton {
    INSTANCE;
    
    // 可添加业务方法
    public void doSomething() {
        System.out.println("枚举单例");
    }
}
// 调用:Singleton.INSTANCE.doSomething();

总结对比表格

实现方式 线程安全 懒加载 效率 防反射/反序列化 推荐程度
饿汉式 ❌(可被反射破坏) 适合简单场景
懒汉式(线程不安全) 不推荐
懒汉式(同步方法) 不推荐
DCL 可用,需注意 volatile
静态内部类 推荐
枚举 最安全推荐

枚举单例的优势是什么?

天然避免线程安全问题,且能防止反射破坏单例、防止序列化/反序列化破坏单例。

  1. 线程安全:JVM 在类加载时创建枚举实例,天然线程安全。
  2. 防反射:Java 禁止通过反射创建枚举实例,会直接抛异常。
  3. 防反序列化:反序列化时只根据名称返回已有常量,不会新建对象。

double-check单例为什么要加volatile?

为了防止指令重排序,new Singleton() 不是原子操作,可能先分配内存、再将引用赋值给 instance、最后初始化对象。不加 volatile 时,其他线程可能拿到未初始化完成的对象。

volatile如何解决?

volatile禁止指令重排序 (通过内存屏障),保证对象完全初始化后 才将引用赋值给 instance,从而避免其他线程拿到半成品对象。

不加volatile关键词存在的问题:

instance = new Singleton(); 在 JVM 中不是原子操作,大致分为三步:

  1. 分配内存空间
  2. 调用构造方法,初始化对象
  3. instance 引用指向内存地址

正常情况下,步骤 2 和 3 的顺序是 2 → 3。但 指令重排序 可能导致顺序变为 3 → 2(先分配地址,后初始化)。

如果此时另一个线程调用 getInstance(),发现 instance != null(指向了地址),就直接返回该对象,但该对象的构造方法可能还未执行 ,内部字段都是默认值(如 0null),使用时就会出错。

枚举单例如何防止反射攻击?

反射的 newInstance() 会直接禁止创建枚举实例,抛出 IllegalArgumentException

项目中你用的哪种?

看场景:一般无特殊要求用静态内部类 ;如果需要防御反射/序列化破坏,或作为工具类单例,用枚举

静态内部类单例的实现原理是什么?和饿汉式有什么区别?

原理 :外部类加载时不加载内部类,首次调用 getInstance() 时 JVM 才会加载内部类并创建实例,利用 JVM 类加载机制保证线程安全和唯一性。

与饿汉式的区别:

维度 静态内部类 饿汉式
加载时机 懒加载(首次使用时) 立即加载(类加载时)
内存占用 不浪费 可能浪费(未使用也创建)

简单工厂、工厂方法、抽象工厂的区别是什么?

模式 简单工厂 工厂方法 抽象工厂
核心职责 一个工厂类负责创建所有类型的产品。 每个具体工厂只负责创建一种具体产品。 一个工厂负责创建一族(多个相关联)的产品。
扩展性 差。新增产品需要修改工厂类代码(违反开闭原则)。 好。新增产品只需新增对应的工厂类(符合开闭原则)。 横向扩展好,纵向扩展差。新增产品族容易,但新增产品种类需要修改所有工厂接口(违反开闭原则)。
解决问题 解决单一产品的简单创建,客户端无需知道具体类名。 解决单一产品等级结构(如不同品牌的手机)的创建。 解决多产品族(如不同品牌的手机+对应的充电器)的配套创建。

简单工厂:

java 复制代码
// 1. 产品接口
interface Phone {
    void call();
}
// 2. 具体产品
class IPhone implements Phone {
    public void call() { System.out.println("iPhone 打电话"); }
}
class HuaweiPhone implements Phone {
    public void call() { System.out.println("华为 打电话"); }
}
// 3. 简单工厂 (核心)
class SimpleFactory {
    // 只要传入名字,我就给你造出来
    public Phone createPhone(String type) {
        if ("apple".equals(type)) {
            return new IPhone();
        } else if ("huawei".equals(type)) {
            return new HuaweiPhone();
        } else {
            throw new IllegalArgumentException("不支持的手机");
        }
    }
}
// 4. 客户端调用
// SimpleFactory factory = new SimpleFactory();
// Phone p = factory.createPhone("apple");

工厂方法:

java 复制代码
// 1. 工厂接口 (每个工厂只负责造一种手机)
interface PhoneFactory {
    Phone createPhone();
}
// 2. 具体工厂
class IPhoneFactory implements PhoneFactory {
    public Phone createPhone() {
        return new IPhone(); // 专门造 iPhone
    }
}
class HuaweiFactory implements PhoneFactory {
    public Phone createPhone() {
        return new HuaweiPhone(); // 专门造 华为
    }
}
// 3. 客户端调用
// PhoneFactory factory = new IPhoneFactory();
// Phone p = factory.createPhone();

抽象工厂

java 复制代码
// 1. 定义多个产品接口
interface Phone { void call(); }
interface Charger { void charge(); }
// 2. 具体产品 (苹果族)
class IPhone implements Phone { public void call() { System.out.println("iPhone 打电话"); } }
class AppleCharger implements Charger { public void charge() { System.out.println("苹果 充电器"); } }
// 3. 具体产品 (华为一族)
class HuaweiPhone implements Phone { public void call() { System.out.println("华为 打电话"); } }
class HuaweiCharger implements Charger { public void charge() { System.out.println("华为 充电器"); } }

// 4. 抽象工厂 (核心:能造一整套东西)
interface AbstractFactory {
    Phone createPhone();
    Charger createCharger();
}
// 5. 具体工厂 (苹果全家桶工厂)
class AppleFactory implements AbstractFactory {
    public Phone createPhone() { return new IPhone(); }
    public Charger createCharger() { return new AppleCharger(); }
}
// 6. 具体工厂 (华为全家桶工厂)
class HuaweiFactory implements AbstractFactory {
    public Phone createPhone() { return new HuaweiPhone(); }
    public Charger createCharger() { return new HuaweiCharger(); }
}

// 7. 客户端调用
// AbstractFactory factory = new AppleFactory();
// Phone p = factory.createPhone();    // 造出 iPhone
// Charger c = factory.createCharger(); // 造出 苹果充电器

抽象工厂模式的应用场景?举例说明。

当你的系统需要创建"一整套"相互关联或依赖的产品,并且要保证它们能配套使用时使用抽象工厂。

举例:数据库的适配

很多大型系统为了兼容不同的客户环境,需要同时支持 MySQL、Oracle、DB2 等多种数据库。数据库操作通常涉及连接(Connection)命令执行(Command/Statement)

  • 定义抽象工厂 DBFactory,包含 createConnection()createCommand()
  • MySQL 工厂:专门创建 MySQL 连接 + MySQL 命令。
  • Oracle 工厂:专门创建 Oracle 连接 + Oracle 命令。

代理模式的核心作用是什么?分为哪几类?

代理模式的核心作用是:在不修改目标对象代码的前提下,通过引入代理对象来控制对目标对象的访问,从而实现对目标对象功能的增强或访问控制。

分为静态代理和动态代理,动态代理又包括 JDK 动态代理(基于接口)和 CGLIB 动态代理(基于子类)。Spring AOP 正是根据目标类是否实现接口,自动选择 JDK 或 CGLIB 代理。

相关推荐
宋哥转AI1 小时前
Java后端转AI Agent:技术栈全景图与从ReAct到多Agent协作实战
java·人工智能·agent
Mr.Entropy1 小时前
ecplise 导出maven依赖jar
java·maven·jar
Drone_xjw1 小时前
Qt国际化多语言配置详解-入门到精通
开发语言·qt·命令模式
爱吃提升1 小时前
Python 多线程 threading + 多进程 multiprocessing 完整实操教程
开发语言·python
ANnianStriver1 小时前
PetLumina 05 — App 端 UI 效果应用
java·ui·ai编程
不懂的浪漫1 小时前
10|Netty native epoll 与零拷贝:从 Java NIO 再往下看一层![
java·netty·nio
plainGeekDev1 小时前
SharedPreferences → DataStore
android·java·kotlin
许彰午1 小时前
24_Java NIO核心组件
java·python·nio
不会C语言的男孩1 小时前
C++ Primer 第18章:用于大型程序的工具
开发语言·c++