【2026版】Java基础面试题
文章目录
- 1. Java 中的几种基本数据类型是什么?各自占用多少字节呢?对应的包装类型是什么?
- 2. String、StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
- 2.1 为什么String要设计成不可变的?
- 2.2 底层机制如何保证String不可变
- 2.3 StringBuffer 和 StringBuilder的使用
- 3. String s1 = new String("abc");这段代码创建了几个字符串对象?
- 4. == 比较的是什么?
- 5. hashCode( )有什么用?为什么重写equals( )时必须重写hashCode( )方法?
- 6. 包装类型的缓存机制了解吗?
- 7. 自动装箱与拆箱了解吗?原理是什么?
- 8. 深拷贝、浅拷贝、引用拷贝的区别了解吗?
- 9. 谈谈对 Java 注解的理解,解决了什么问题?
- 10. Exception 和 Error 有什么区别?
- 11. Java 反射?反射有什么缺点?你是怎么理解反射的(为什么框架需要反射)?
- 11.1 什么是Java反射?
- 11.2 反射的缺点(劣势)
- 11.3 为什么框架需要使用反射?
- 11.4 你是怎么理解反射的?
- 12. Java 泛型了解吗?什么是类型擦除?介绍一下常用的通配符?
- 12.1 Java 泛型是什么?
- 12.2 什么是类型擦除?
- 12.3 通配符(Wildcard)
- 13. 内部类了解吗?匿名内部类了解吗?
- 13.1 内部类是什么?
- 13.2 匿名内部类
- 14. BIO,NIO,AIO 有什么区别?
文章参考
https://javaguide.cn
、
https://www.mianshiya.com/
1. Java 中的几种基本数据类型是什么?各自占用多少字节呢?对应的包装类型是什么?
基本数据类型 | 包装类型 | 占用字节数 | 默认值 | 说明 |
---|---|---|---|---|
byte | Byte | 1 字节 | 0 | 有符号整型,范围 -128~127 |
short | Short | 2 字节 | 0 | 有符号整型,范围 -32,768~32,767 |
int | Integer | 4 字节 | 0 | 有符号整型,常用整数类型 |
long | Long | 8 字节 | 0L | 有符号长整型,范围极大 |
float | Float | 4 字节 | 0.0f | 单精度浮点型,尾数大约7位精度 |
double | Double | 8 字节 | 0.0d | 双精度浮点型,尾数约15位精度 |
char | Character | 2 字节 | ‘\u0000’ | 单个 16 位 Unicode 字符 |
boolean | Boolean | JVM决定 | false | 逻辑值,取值 true 或 false |
2. String、StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
特性 | String | StringBuffer | StringBuilder |
---|---|---|---|
可变性 | 不可变 | 可变 | 可变 |
线程安全 | 是(因为不可变) | 线程安全(方法有 synchronized) | 非线程安全(无同步机制) |
性能 | 较低(每次修改都会创建新对象) | 较低(因为线程安全开销) | 较高(无线程安全开销) |
使用场景 | 字符串内容不需要改变时 | 多线程环境下频繁修改字符串 | 单线程环境下频繁修改字符串 |
2.1 为什么String要设计成不可变的?
- 保证安全
String s1 = "hello";
String s2 = s1;s1 = s1.toUpperCase(); // s1变成了"HELLO"
System.out.println(s1); // HELLO
System.out.println(s2); // hello
解释:
s2 和 s1 最开始都指向 “hello”,但是调用 toUpperCase() 返回了一个新字符串,s1 变了,但 s2 还是 “hello”。
如果 String 是可变的,修改了 s1,s2 也会被影响,这会导致很多不可预期的问题。
- 字符串常量池共享实例,节省内存
String a = "abc";
String b = "abc";
System.out.println(a == b); // true
解释:
a 和 b 都指向字符串常量池中同一个 “abc” 对象。如果 String 是可变的,这种共享就很危险。
- 多线程场景中的线程安全
public class Test {public static void main(String[] args) {String shared = "test";Thread t1 = new Thread(() -> {String local = shared.toUpperCase();System.out.println(local);});Thread t2 = new Thread(() -> {String local = shared.toLowerCase();System.out.println(local);});t1.start();t2.start();}
}
解释:
两个线程都使用了同一个 String 实例 shared,但是他们操作的都是生成的新字符串,原字符串没变,线程间不会互相影响。
2.2 底层机制如何保证String不可变
底层使用final char[] value
存储字符串内容:
private final char value[];
保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
2.3 StringBuffer 和 StringBuilder的使用
public class StringBufferExample {public static void main(String[] args) {StringBuffer sb = new StringBuffer("Hello");sb.append(" World"); // 追加字符串sb.insert(5, ","); // 插入字符串sb.replace(6, 11, "Java"); // 替换字符串sb.delete(5, 6); // 删除字符System.out.println(sb.toString()); // 输出结果}
}
public class StringBuilderExample {public static void main(String[] args) {StringBuilder sb = new StringBuilder("Hello");sb.append(" World");sb.insert(5, ",");sb.replace(6, 11, "Java");sb.delete(5, 6);System.out.println(sb.toString());}
}
输出:
Hello Java
3. String s1 = new String(“abc”);这段代码创建了几个字符串对象?
-
字符串常量池中
不存在
“abc”:会创建 2 个 字符串对象。一个在字符串常量池中
,由 ldc 指令触发创建。一个在堆中
,由 new String() 创建,并使用常量池中的 “abc” 进行初始化。 -
字符串常量池中已
存在
“abc”:会创建 1 个 字符串对象。该对象在堆中
,由 new String() 创建,并使用常量池中的 “abc” 进行初始化。
4. == 比较的是什么?
- 基本数据类型比的是值
- 引用数据类型比较的是地址值
5. hashCode( )有什么用?为什么重写equals( )时必须重写hashCode( )方法?
hashCode()
是 Object 类中的一个方法,用来返回对象的哈希码值(一个整数)。
通过哈希码,集合可以快速判断两个对象是否有可能相等(先比较哈希码,若不同则一定不等,若相同才继续比较 equals())。
如果两个对象根据equals()
方法是相等的,那么它们的hashCode()值必须相等。这是为了保证基于哈希的集合(如HashMap、HashSet)的正确性。
解释:
- 当你向HashSet添加一个对象时,集合会先调用对象的hashCode(),定位桶(bucket)的位置。
- 在这个桶中,再用equals()方法判断是否有相等的对象,避免重复。
- 如果两个相等的对象hashCode()不同,会被放到不同桶中,导致集合无法正确识别重复元素,从而破坏集合的规范和数据完整性。
条件 | 要求 |
---|---|
如果 a.equals(b) 为 true | 则 a.hashCode() 必须等于 b.hashCode() |
如果 a.hashCode() == b.hashCode() | a.equals(b) 不一定为 true (哈希冲突允许) |
6. 包装类型的缓存机制了解吗?
Java中的包装类型缓存机制,主要是指对某些包装类(比如 Integer、Short、Byte、Character 和 Long)为了提高性能和节省内存,JVM会缓存一定范围内的对象实例
,避免重复创建。
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true,引用相同Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false,引用不同
📌为什么第一个为 true,而第二个为 false?
- Integer的缓存范围是:[-128, 127]
- 这是因为 Java 在内部做了缓存处理:这些值在频繁使用中可以共享。
所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
包装类型 | 缓存范围 |
---|---|
Byte | -128 ~ 127 |
Short | -128 ~ 127 |
Integer | -128 ~ 127 |
Long | -128 ~ 127 |
Character | 0 ~ 127 |
Boolean | true / false |
Float | 无缓存机制 |
Double | 无缓存机制 |
7. 自动装箱与拆箱了解吗?原理是什么?
类型 | 定义 | 示例 |
---|---|---|
自动装箱 | 基本类型 ➡️ 包装类 | Integer i = 10; |
自动拆箱 | 包装类 ➡️ 基本类型 | int x = i;(i 是 Integer) |
自动装箱
实际调用的是包装类的 valueOf()
方法:
Integer.valueOf(10) // 包含缓存机制
Long.valueOf(123L)
Boolean.valueOf(true)
自动拆箱
时,调用的是包装类的 xxxValue()
方法,例如:
int x = i.intValue();
long y = l.longValue();
8. 深拷贝、浅拷贝、引用拷贝的区别了解吗?
引用拷贝
是最简单的一种:只是拷贝对象的地址(引用),两个变量指向同一个对象。
浅拷贝
是 复制对象本身,但其中的字段如果是对象类型,仍然是引用共享。
深拷贝
是递归地复制对象本身以及其引用对象,完全独立的副本。
9. 谈谈对 Java 注解的理解,解决了什么问题?
Java 注解(Annotation)是 Java 提供的一种元数据机制,用于给代码元素(类、方法、字段等)添加额外信息。它不改变代码逻辑,但可以被编译器、工具和框架读取并处理。注释也能添加额外信息,但是注释不能被工具读取和处理。
解决的问题:
- 减少样板代码:Lombok 库的 @Getter/Setter、@Data注解,一个标注就自动生成了对应的 getter 和 setter 方法。
- 编译期间检查:通过@Override注解可以让编译器在编译期间检查子类是否正确覆盖父类方法。
- 依赖注入:@Autowired 表明某个成员变量或构造函数需要自动注入依赖对象,框架会根据类型自动查找并注入对应的实例,避免了手动创建和传递依赖 。
10. Exception 和 Error 有什么区别?
Exception(异常)
表示程序运行中发生了意料之外的情况,通常是可以被程序捕获并处理的。
Error(错误)
错误通常指的是程序无法处理的严重问题,通常来自于系统层面或虚拟机层面,程序不应该捕获或恢复。
11. Java 反射?反射有什么缺点?你是怎么理解反射的(为什么框架需要反射)?
11.1 什么是Java反射?
Java 反射 (Reflection) 是一种在程序运行时,动态地获取类的信息并操作类或对象(方法、属性)的能力。比如:
- 获取某个类的结构信息(方法、字段、构造器等)
- 调用某个对象的私有方法或访问私有字段
- 动态创建对象实例
- 动态调用方法
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod("setName", String.class);
method.invoke(obj, "Tom");
11.2 反射的缺点(劣势)
- 性能开销大
反射是在运行时解析字节码信息,速度比直接调用慢,尤其在大量反射操作中会显著影响性能。 - 破坏封装性
可以访问私有方法和字段,违背了面向对象的封装原则。 - 类型安全性差
编译时不会报错,只有运行时出问题,增加了出错风险,如 ClassCastException、NoSuchMethodException 等。
11.3 为什么框架需要使用反射?
-
依赖注入与控制反转(IoC)
以 Spring/Spring Boot 为代表的 IoC 框架,会在启动时扫描带有特定注解(如 @Component, @Service, @Repository, @Controller)的类,利用反射实例化对象(Bean),并通过反射注入依赖(如 @Autowired、构造器注入等)。 -
注解处理
注解本身只是个“标记”,得有人去读这个标记才知道要做什么。反射就是那个“读取器”。框架通过反射检查类、方法、字段上有没有特定的注解,然后根据注解信息执行相应的逻辑。比如,看到 @Value,就用反射读取注解内容,去配置文件找对应的值,再用反射把值设置给字段。 -
动态代理与 AOP
想在调用某个方法前后自动加点料(比如打日志、开事务、做权限检查)?AOP(面向切面编程)就是干这个的,而动态代理是实现 AOP 的常用手段。JDK 自带的动态代理(Proxy 和 InvocationHandler)就离不开反射。代理对象在内部调用真实对象的方法时,就是通过反射的 Method.invoke 来完成的。
public class DebugInvocationHandler implements InvocationHandler {private final Object target; // 真实对象public DebugInvocationHandler(Object target) { this.target = target; }// proxy: 代理对象, method: 被调用的方法, args: 方法参数public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("切面逻辑:调用方法 " + method.getName() + " 之前");// 通过反射调用真实对象的同名方法Object result = method.invoke(target, args);System.out.println("切面逻辑:调用方法 " + method.getName() + " 之后");return result;}
}
- 对象关系映射(ORM)
像 MyBatis、Hibernate 这种框架,能帮你把数据库查出来的一行行数据,自动变成一个个 Java 对象。它是怎么知道数据库字段对应哪个 Java 属性的?还是靠反射。它通过反射获取 Java 类的属性列表,然后把查询结果按名字或配置对应起来,再用反射调用 setter 或直接修改字段值。反过来,保存对象到数据库时,也是用反射读取属性值来拼 SQL。
11.4 你是怎么理解反射的?
我对反射的理解是,它是一种在程序运行时动态获取类的信息并操作类和对象的机制。通过反射,我们可以在不知道具体对象类型的前提下获取它的类名、方法、属性等信息,甚至可以调用方法或者修改属性值。在实际开发中,反射常用于框架设计,比如依赖注入、注解处理或者通用方法调用等场景。我觉得反射体现了Java等语言运行时灵活性,但同时也要注意它的性能开销和安全问题,不宜过度使用。
12. Java 泛型了解吗?什么是类型擦除?介绍一下常用的通配符?
12.1 Java 泛型是什么?
泛型允许在类、接口和方法中使用类型参数,使代码在编译时就能进行类型检查,避免了大量的强制类型转换。
泛型类
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{private T key;public Generic(T key) {this.key = key;}public T getKey(){return key;}
}
泛型接口
public interface Generator<T> {public T method();
}
泛型方法
如果是静态方法,需要自己定义E
public static < E > void printArray( E[] inputArray ){for ( E element : inputArray ){System.out.printf( "%s ", element );}System.out.println();}
12.2 什么是类型擦除?
Java 泛型是 伪泛型,其本质是 在编译阶段生效,运行时被擦除。这就是所谓的 类型擦除。
在编译后,所有的泛型信息都会被移除,替换为 原始类型(raw type) 或 限定类型。
📌泛型定义中只能用 extends 来指定类型参数的上界,不能用 super 指定下界。
类型擦除的结果:
-
List 会被擦除为 List。
-
若泛型有上界,如 T extends Number,则 T 会被擦除为 Number。
12.3 通配符(Wildcard)
<?>
无限制通配符(Unknown wildcard)
表示 任何类型都可以接受。适用于只读、不修改的场景。
public void printList(List<?> list) {for (Object obj : list) {System.out.println(obj);}
}
<? extends T>
上界通配符(Upper bound)
表示该集合中元素是 T 或 T 的子类,适合读取的场景,但不适合写入(只能写入 null)。
public void printNumbers(List<? extends Number> list) {for (Number n : list) {System.out.println(n);}
}
<? super T>
下界通配符(Lower bound)
表示该集合中元素是 T 或 T 的父类,适合写入的场景。(不适合读)
public void addIntegers(List<? super Integer> list) {list.add(1); // 可以安全写入 Integer 或其子类
}
13. 内部类了解吗?匿名内部类了解吗?
13.1 内部类是什么?
内部类是定义在另一个类里面的类。它可以访问外部类的成员(包括私有成员),常用来增强类的封装性和代码的组织性。内部类有几种类型:
-
成员内部类:定义在类的成员位置上。
-
静态内部类:带有 static 修饰符,不依赖外部类的实例。
-
局部内部类:定义在方法内部的类。
-
匿名内部类:没有名字的内部类,直接在创建对象的时候定义类的实现。
13.2 匿名内部类
匿名内部类就是没有名字的内部类,它直接在创建对象的时候定义类的实现,不用单独写一个实现类
public class Test {public static void main(String[] args) {// 创建线程,使用匿名内部类实现Runnable接口Runnable r = new Runnable() {@Overridepublic void run() {System.out.println("线程通过匿名内部类运行");}};new Thread(r).start();}
}
-
new Runnable() { … } 就是匿名内部类
-
它实现了 Runnable 接口的 run 方法
-
你不用写单独的类实现 Runnable
14. BIO,NIO,AIO 有什么区别?
BIO(Blocking I/O)
:
在BIO模式下,服务器为每个客户端连接创建一个独立的线程,每个线程负责处理一个I/O操作。线程在进行I/O操作时,如
果数据未准备好,会阻塞,直到操作完成后才继续。
- 例如,在Java中,使用 ServerSocket 和 socket 进行网络通信时,accept()、read()、write()等操作都是阻塞的,直
到请求或数据到来。
优点与缺点:
- 优点:实现简单,代码逻辑清晰,适合小型应用或低并发场景。
- 缺点:线程资源开销大,当连接数增多时,需要大量线程处理,每个线程会占用内存和CPU资源,易出现性能瓶颈。
适用场景:适用于并发连接数量少、业务逻辑相对简单的系统,如小型文件服务器、管理系统等。
NIO (Non-blocking I/O)
:
NIO模式通过非阻塞I/O和l/O多路复用实现高并发。服务器端通过selector管理多个通道(Channel),当某个通道有事件
(如可读、可写)时,Selector 会通知程序进行处理。
- 在Java中,java.nio.channels.Selector可以同时管理多个 SocketChannel,在一个线程中实现对多个连接的管理。
优点与缺点:
- 优点:可以减少线程数量,大幅度降低线程切换的开销,提升系统的资源利用率,特别适用于需要处理大量并发连接的
场景。 - 缺点:实现复杂度较高,编写和调试NIO程序需要处理大量状态和事件,对开发人员要求较高。
适用场景:适合高并发服务器应用,如聊天室、即时通讯服务器、大型网站的后台服务等。
AIO (Asynchronous I/O)
:
AIO模式是真正的异步I/O操作,操作系统在I/O操作完成后会通知应用程序。调用方在发起请求后,可以继续执行其他任务,
不需要轮询或等待I/O操作完成。
- 在Java中,AsynchronousSocketChannel是AlO的典型代表,通过回调函数处理读写操作完成后的结果。
优点与缺点:
- 优点:在I/O密集型应用中,AIO能提供更高的并发性和更低的响应时间,因为调用方在等待I/O时不会被阻塞。
- 缺点:实现复杂,对操作系统的支持依赖较大,一些操作系统在底层支持不够完善时,AIO的性能优势可能无法完全体
现。
适用场景:适用于对延迟和吞吐量有高要求的系统,如实时数据处理系统、大型交易系统、在线游戏服务器等。