一、面向对象
1、深拷贝和浅拷贝的区别
在 Java 中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种对象拷贝方式,它们在处理引用类型属性时有显著差异。
浅拷贝会创建一个新对象,但属性如果是引用类型,新旧对象共享同一个引用地址。实现方式通常是实现 Cloneable 接口并重写 clone() 方法。
class Person implements Cloneable {
String name;
int age;
Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Address {
String city;
public Address(String city) {
this.city = city;
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("北京");
Person person1 = new Person("测试用户", 18, address);
Person person2 = (Person) person1.clone();
System.out.println(person1.address == person2.address); // true
}
}
深拷贝则递归复制所有引用对象,确保新旧对象完全独立。可以通过手动复制或序列化与反序列化实现。
手动拷贝示例:
class Person {
String name;
int age;
Address address;
public Person(Person person) {
this.name = person.name;
this.age = person.age;
this.address = new Address(person.address.city);
}
}
class Address {
String city;
public Address(String city) {
this.city = city;
}
}
public class Main {
public static void main(String[] args) {
Address address = new Address("北京");
Person person1 = new Person("测试用户", 18, address);
Person person2 = new Person(person1);
System.out.println(person1.address == person2.address); // false
}
}
序列化与反序列化示例:
import java.io.*;
class Person implements Serializable {
String name;
int age;
Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public Person deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Person) ois.readObject();
}
}
class Address implements Serializable {
String city;
public Address(String city) {
this.city = city;
}
}
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Address address = new Address("北京");
Person person1 = new Person("测试用户", 18, address);
Person person2 = person1.deepClone();
System.out.println(person1.address == person2.address); // false
}
}
2、Java 创建对象有哪几种方式?
主要有四种常见方式:
- new 关键字:最直接的方式,调用构造方法。
Person person = new Person(); - 反射机制:运行时动态创建,常用于框架开发。
Class clazz = Class.forName("Person"); Person person = (Person) clazz.newInstance(); - clone 拷贝:需实现 Cloneable 接口。
Person person = new Person(); Person person2 = (Person) person.clone(); - 序列化机制:通过字节流恢复对象。
Person person = new Person(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt")); oos.writeObject(person); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt")); Person person2 = (Person) ois.readObject();
二、String
1、String 和 StringBuilder、StringBuffer 的区别?
String 是不可变的,适合内容不变的场景;StringBuilder 线程不安全但性能高,适合单线程频繁修改;StringBuffer 线程安全但性能略低。
String 特点:
- 对象不可变,每次修改生成新对象。
- 适合作为 HashMap 的 Key。
StringBuilder 特点:
- 底层数组可修改,不生成新对象。
- 非线程安全,适合循环拼接字符串。
StringBuffer 特点:
- 类似 StringBuilder,但方法加了 synchronized,线程安全。
- 多线程场景下使用,但现代开发中较少见。
2、String 是不可变类吗?
是的,String 是不可变类。这带来了安全性、缓存重用和哈希值固定等优势。
如何保证不可变?
- 内部使用
private final char value[]存储数据。 - 不提供修改内容的公共方法,如 concat 返回新对象。
- 类本身声明为 final,防止继承篡改。
public final class String {
private final char value[];
}
三、异常处理
1、Java 中的异常体系?
Throwable 是所有错误和异常的基类,分为 Error 和 Exception。
- Error:严重错误,JVM 无法处理,如 OutOfMemoryError。
- Exception:程序可处理的异常,分为编译时异常(Checked)和运行时异常(Runtime)。
- Checked:必须捕获或声明抛出,如 IOException。
- Runtime:由逻辑错误导致,如 NullPointerException,编译器不强制处理。
2、异常的处理方式
- 抛出异常:使用 throw 抛出,throws 声明。
public void test() throws Exception { throw new Exception("抛出异常"); } - 捕获处理:try-catch-finally 结构。
try { // 可能出错的代码 } catch (Exception e) { // 处理 } finally { // 必执行 }
注意:如果 catch 和 finally 都抛出异常,finally 中的异常会覆盖 catch 中的异常。建议保存 catch 异常并在 finally 中处理,避免丢失信息。
四、I/O
1、Java 中 IO 流分为几种?
可按方向、单位、功能划分:
- 方向:输入流(Input)、输出流(Output)。
- 单位:字节流(Byte)、字符流(Character)。
- 功能:节点流(Node)、处理流(Processing)、管道流(Pipe)。
IO 流设计使用了装饰器模式,在不改变原有结构前提下动态增强功能,如 BufferedInputStream 包装 FileInputStream。
缓冲区溢出预防: 合理设置大小,控制写入量,利用 ByteBuffer 的 remaining() 检查容量。
ByteBuffer buffer = ByteBuffer.allocate(1024);
if (buffer.remaining() >= receivedData.length) {
buffer.put(receivedData);
}
2、有了字节流为什么还要有字符流?
字符流直接操作字符,避免了字节转换的耗时和编码问题。文本文件推荐用字符流,二进制文件(如图片、视频)推荐用字节流。
3、BIO、NIO、AIO 之间的区别?
- BIO:阻塞式,连接少时使用。
- NIO:非阻塞,Selector 监控多通道,连接多且短时使用。
- AIO:异步,回调通知,连接多且长时使用。
五、序列化
1、什么是序列化?什么是反序列化?
序列化是将对象转为字节流以便存储或传输;反序列化则是还原对象。
步骤:
- 实现 Serializable 接口。
- 使用 ObjectOutputStream 写入。
- 调用 writeObject 方法。
2、有几种序列化方式?
- Java 原生序列化:ObjectOutputStream/InputStream。
- Json 序列化:常用 Jackson 等库,跨语言友好。
- ProtoBuff 序列化:高效压缩,适合高性能场景。
六、网络编程
1、了解过 Socket 网络套接字吗?
Socket 是通信端点,基于 TCP/UDP 实现进程间通信。
TCP 客户端示例:
Socket socket = new Socket("127.0.0.1", 8080);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello, Server!");
TCP 服务端示例:
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
// 处理读写...
RPC 框架:如 gRPC、Dubbo、Feign,屏蔽底层细节,支持高效序列化。
七、泛型
1、Java 泛型了解多少?
泛型提高类型安全,避免强制转换。有三种用法:泛型类、泛型接口、泛型方法。
泛型类:
public class Generic<T> {
private T key;
public Generic(T key) { this.key = key; }
public T getKey() { return key; }
}
通配符:
?:不确定类型。T:具体类型。K/V:键值对。E:元素。
类型擦除: Java 泛型在编译期进行类型检查,运行时被擦除。这是为了兼容 JDK5 之前的版本。例如 LinkedList 在运行时等同于 LinkedList。
八、反射
1、什么是反射?原理?应用?
反射允许运行时检查和操作类的字段、方法。通过 Class 对象获取元信息。
应用场景:
- Spring 框架管理 Bean。
- 动态代理实现 AOP。
- 测试框架发现测试方法。
- 通用工具类(如 BeanUtils)。
优缺点:
- 优点:动态操作、通用性强、突破访问限制。
- 缺点:性能损耗大、破坏封装性。
九、Lambda 表达式
1、Lambda 表达式了解多少?
Lambda 简化匿名方法,支持函数式编程。
简化线程:
new Thread(() -> System.out.println("Hello World")).start();
配合 Stream 流:
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
其中 n -> n % 2 == 0 即为 Lambda 表达式。

