每日 Java 面试题分享【第 19 天】
欢迎来到每日 Java 面试题分享栏目!
订阅专栏,不错过每一天的练习
今日分享 3 道面试题目!
评论区复述一遍印象更深刻噢~
目录
- 问题一:Java Object 类中有什么方法,有什么作用?
- 问题二:Java 中的字节码是什么?
- 问题三:什么是 BIO、NIO、AIO?
问题一:Java Object 类中有什么方法,有什么作用?
Java Object
类的方法及作用
Object
类是 Java 所有类的祖先,位于 java.lang.Object
,它为所有 Java 对象提供了一些基本的方法。
1. Object
类的主要方法
(1)equals(Object obj)
—比较对象是否相等
作用:比较当前对象与参数对象是否 " 逻辑相等 "。
默认实现(在 Object
类中):
- 使用
==
比较,即比较对象的内存地址,而不是内容。 - 如果需要比较对象内容,需要重写
equals()
方法(如String
、Integer
等类已重写)。
示例:
class Person {
String name;
Person(String name) { this.name = name; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return name.equals(person.name);
}
}
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
System.out.println(p1.equals(p2)); // true(重写后,比较内容)
(2)hashCode()
—获取对象的哈希值
作用:返回对象的哈希码,用于哈希表(如 HashMap
、HashSet
)存储对象时计算索引。
- 默认实现:基于对象内存地址计算哈希值。
- 通常与
equals()
一起重写,保证相等的对象哈希值相同(否则HashSet
等容器可能无法正确存储)。
示例:
@Override
public int hashCode() {
return name.hashCode(); // 让相同 name 的对象 hashCode 也相同
}
(3)toString()
—返回对象的字符串表示
作用:返回对象的字符串描述,默认格式为 类名@哈希码
。
- 建议重写,以便更清晰地输出对象信息。
示例:
class Person {
String name;
Person(String name) { this.name = name; }
@Override
public String toString() {
return "Person{name='" + name + "'}";
}
}
Person p = new Person("Alice");
System.out.println(p); // Person{name='Alice'}
(4)getClass()
—获取对象的 Class 对象
作用:返回对象的运行时 Class
对象,可以用于反射。
示例:
Person p = new Person("Alice");
System.out.println(p.getClass().getName()); // 输出类的全限定名
(5)clone()
—复制对象
作用:返回对象的浅拷贝(仅拷贝基本类型,引用类型不会复制对象)。
- 默认
Object
实现是浅拷贝,必须实现Cloneable
接口并重写clone()
方法。
示例:
class Person implements Cloneable {
String name;
Person(String name) { this.name = name; }
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Person p1 = new Person("Alice");
Person p2 = (Person) p1.clone();
System.out.println(p1 == p2); // false,不是同一个对象
(6)finalize()
—对象被回收前的操作
作用:对象被垃圾回收前执行的清理方法(通常不推荐使用)。
- 被废弃(JDK 9 开始标记为
deprecated
),可以用try-with-resources
或close()
代替。
示例:
@Override
protected void finalize() throws Throwable {
System.out.println("对象即将被回收");
}
(7)wait() / notify() / notifyAll()
—线程通信
这些方法用于线程同步,必须在同步代码块中调用。
wait()
:让当前线程进入等待状态,直到被notify()
唤醒。notify()
:随机唤醒一个等待的线程。notifyAll()
:唤醒所有等待的线程。
示例:
synchronized (this) {
this.wait(); // 让当前线程等待
this.notify(); // 唤醒一个等待的线程
}
2. 总结
方法 | 作用 | 是否需要重写 |
---|---|---|
equals(Object obj) | 比较对象是否相等(默认比较地址) | 通常需要 |
hashCode() | 计算对象哈希值(用于 HashMap) | 通常需要 |
toString() | 返回对象字符串信息 | 通常需要 |
getClass() | 获取对象的 Class 信息 | 一般不重写 |
clone() | 复制对象(默认浅拷贝) | 按需重写 |
finalize() | 对象回收前执行(已废弃) | 不建议使用 |
wait() / notify() / notifyAll() | 线程通信 | 不可重写 |
重点
equals()
和hashCode()
一起重写,确保 HashMap、HashSet 正常存储对象。toString()
适当重写,方便调试和日志输出。clone()
需要实现Cloneable
接口,并自己实现深拷贝。finalize()
已过时,不推荐使用。wait()
/notify()
用于线程同步,必须在synchronized
代码块中调用。
这就是 Object
类的方法及其作用!
问题二:Java 中的字节码是什么?
Java 中的字节码(Bytecode)
1. 什么是字节码?
Java 字节码(Bytecode) 是一种 中间表示(Intermediate Representation,IR),它不是直接运行在 CPU 上的机器码,而是运行在 Java 虚拟机(JVM) 上的指令集。
当我们编写 Java 代码并编译(javac
)后,Java 源代码(.java
文件)会被编译器转换成 字节码文件(.class
文件),然后由 JVM 解释或 JIT 编译后执行。
2. Java 字节码的特点
- 平台无关性:Java 字节码可以在任何安装了 JVM 的环境中运行,而不需要重新编译。
- 面向对象:字节码仍然保留了 Java 的对象模型,如类、方法、继承、接口等。
- 堆栈指令集(Stack-based Instruction Set):JVM 采用基于操作数栈的架构(而非寄存器架构),所有操作通过入栈、出栈完成。
3. 字节码执行流程
- 编译:Java 源代码(
.java
)→ 编译器(javac
) → 字节码(.class
) - 加载:JVM 通过类加载器(ClassLoader) 加载
.class
文件到方法区。 - 解释或编译:
- 解释执行:字节码由 解释器(Interpreter) 逐条翻译成机器码并执行(启动快但慢)。
- JIT(即时编译,Just-In-Time Compiler):JVM 可能将热点代码转换为机器码,提高执行效率。
4. Java 字节码示例
例如,编译以下代码:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, Bytecode!");
}
}
使用 javap -c HelloWorld.class
反编译:
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // 获取 System.out
3: ldc #3 // 加载字符串 "Hello, Bytecode!"
5: invokevirtual #4 // 调用 println 方法
8: return
getstatic
:获取System.out
ldc
:加载字符串常量invokevirtual
:调用println()
方法
5. 字节码优化
JVM 使用 JIT(Just-In-Time 编译) 和垃圾回收(GC) 进行优化:
- 热点代码 会被 JIT 编译成机器码,提高运行效率。
- 死代码消除、方法内联 等优化提升性能。
6. 总结
特点 | 说明 |
---|---|
平台无关 | 编译后可运行在任意 JVM 上 |
基于栈架构 | 通过栈操作指令执行计算 |
JIT 编译优化 | 热点代码会被 JIT 转换为机器码,提高性能 |
面向对象 | 支持类、方法、继承等 |
Java 字节码是 Java 跨平台特性的关键,也是 JVM 执行 Java 代码的中间步骤!🚀
问题三:什么是 BIO、NIO、AIO?
BIO、NIO、AIO 的概念与区别
在 Java 中,BIO(Blocking I/O)、NIO(Non-blocking I/O)、AIO(Asynchronous I/O) 是三种不同的 I/O(输入/输出)模型,主要用于网络编程或文件操作。它们的核心区别在于 数据读写时的阻塞行为 和 线程管理方式。
1. BIO(Blocking I/O)—阻塞式 I/O
概念
- 同步 & 阻塞:每个连接都需要一个独立的线程处理,数据读取/写入时会阻塞,直到操作完成。
- 适用于:小规模连接(如传统 C/S 架构),如 Web 服务器、数据库连接等。
- 缺点:
- 线程资源消耗大,每个连接都需要一个线程,连接数过多时,系统负担重。
- 低并发,大量连接时,线程切换成本高,容易导致 " 线程过载 "。
示例
ServerSocket server = new ServerSocket(8080);
while (true) {
Socket socket = server.accept(); // 阻塞等待连接
new Thread(() -> {
try {
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = in.read(buffer); // 阻塞等待数据
System.out.println(new String(buffer, 0, len));
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
server.accept()
阻塞等待客户端连接。in.read(buffer)
阻塞等待数据到来。- 每个请求都创建新线程,连接数多时消耗大。
2. NIO(Non-blocking I/O)—非阻塞式 I/O
概念
- 同步 & 非阻塞:采用 多路复用(Selector) 机制,一个线程可以管理多个连接,提升并发性能。
- 适用于:高并发场景(如聊天系统、游戏服务器)。
- 特点:
- 非阻塞,但仍是同步 I/O(线程需要主动轮询检查数据)。
- 采用
Selector
监听多个Channel
,避免一个线程对应一个连接的弊端。
示例
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 设置非阻塞模式
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 监听连接事件
while (true) {
selector.select(); // 轮询监听事件(阻塞)
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
if (key.isAcceptable()) { // 处理新连接
SocketChannel socketChannel = serverChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) { // 处理可读数据
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
System.out.println(new String(buffer.array()));
}
}
keys.clear();
}
Selector
监听多个Channel
,避免多个线程处理多个连接。- 数据读取非阻塞,如果没有数据,不会卡住线程。
- 适用于高并发服务器(如 Netty)。
3. AIO(Asynchronous I/O)— 异步 I/O
概念
- 异步 & 非阻塞:基于 操作系统内核 异步通知机制,I/O 操作完成后会回调相应的处理逻辑,不需要手动轮询检查。
- 适用于:超高并发(如高吞吐量服务器、消息推送)。
- 特点:
- 真正的异步,数据准备好后,操作系统会自动回调处理数据,避免轮询。
- 比 NIO 更高效,但依赖于操作系统的异步 I/O 支持(Windows、Linux 支持良好)。
示例
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open()
.bind(new InetSocketAddress(8080));
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel channel, Void attachment) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
buffer.flip();
System.out.println(new String(buffer.array()));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
}
});
server.accept()
是非阻塞的,连接建立后自动执行回调completed()
。channel.read()
也是异步的,数据到达后触发completed()
,无需轮询检查。- 适用于超高并发,如 Netty 也有 AIO 支持。
4. BIO、NIO、AIO 区别总结
特性 | BIO(同步阻塞) | NIO(同步非阻塞) | AIO(异步非阻塞) |
---|---|---|---|
线程模型 | 一个连接占用一个线程 | 一个线程管理多个连接 | 真正的异步,I/O 完成后回调 |
阻塞方式 | 阻塞(线程等待) | 非阻塞(轮询检查) | 非阻塞(OS 直接回调) |
并发能力 | 低(线程数多时资源耗尽) | 中等(Selector 机制) | 高(事件驱动,线程数少) |
适用场景 | 小规模连接(如传统 Web) | 高并发(如聊天室、消息推送) | 超高并发(如 Netty、分布式系统) |
JDK 支持 | JDK 1.0 | JDK 1.4(java.nio ) | JDK 1.7(java.nio.channels ) |
5. 何时使用 BIO、NIO、AIO?
-
BIO:
- 适用于连接数较少,并发要求不高的应用(如 Web 服务器、小型服务端)。
- 优点:实现简单,调试方便。
- 缺点:高并发下资源消耗大,性能低。
-
NIO:
- 适用于高并发场景,如聊天室、游戏服务器、即时通讯。
- 优点:支持单线程管理多个连接,性能比 BIO 高。
- 缺点:代码复杂度增加,需要手动管理
Selector
和Channel
。
-
AIO:
- 适用于超高并发,如Netty、分布式架构、消息推送。
- 优点:完全异步,系统资源占用少,适合高吞吐场景。
- 缺点:依赖操作系统支持,适用范围比 NIO 更局限。
如果是高并发网络编程,推荐使用 Netty(基于 NIO 和 AIO 的封装),性能更强!🚀
总结
今天的 3 道 Java 面试题,您是否掌握了呢?持续关注我们的每日分享,深入学习 Java 面试的各个细节,快速提升技术能力!如果有任何疑问,欢迎在评论区留言,我们会第一时间解答!
明天见!🎉