1. JavaSE基础概念回顾
JavaSE(Java Platform, Standard Edition)是Java平台的标准版,也是Java技术体系中最基础、最核心的部分。作为Java开发者,掌握JavaSE是进入Java世界的必经之路。很多人可能觉得JavaSE太基础而不够重视,但根据我十多年的开发经验来看,JavaSE的扎实程度直接决定了你能否成为真正优秀的Java开发者。
JavaSE包含了Java语言最基础也最重要的特性:面向对象编程、集合框架、IO/NIO、多线程、网络编程等。这些不仅是日常开发中最常用的技术点,也是面试中最常被考察的内容。我见过太多开发者因为基础不牢而在实际工作中遇到各种"奇怪"的问题,最后发现根源都在于对JavaSE的理解不够深入。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. JavaSE核心知识点详解
2.1 面向对象编程的三大特性
封装、继承和多态是Java面向对象编程的三大基石。很多初学者虽然能背出定义,但在实际应用中却常常犯错误。
封装不仅仅是把属性设为private然后提供getter/setter那么简单。真正的封装要考虑:
- 哪些属性应该暴露,哪些应该隐藏
- 暴露的方式是否安全
- 如何保证对象状态的完整性
举个例子,我们有一个BankAccount类:
java复制public class BankAccount {
private double balance;
// 不安全的做法
public void setBalance(double balance) {
this.balance = balance;
}
// 更好的做法
public void deposit(double amount) {
if(amount > 0) {
this.balance += amount;
}
}
public void withdraw(double amount) {
if(amount > 0 && amount <= balance) {
this.balance -= amount;
}
}
}
2.2 集合框架的底层实现
Java集合框架是日常开发中使用最频繁的部分之一,但很多人只是停留在会用的层面。了解各种集合的底层实现原理,能帮助我们在不同场景下做出最优选择。
以ArrayList和LinkedList为例:
- ArrayList底层是数组,随机访问快(O(1)),但插入删除慢(O(n))
- LinkedList底层是链表,插入删除快(O(1)),但随机访问慢(O(n))
但实际情况更复杂。当我们在ArrayList中间插入元素时,确实需要移动后续元素,但如果是在末尾插入,ArrayList的性能其实很好。而LinkedList虽然理论上插入是O(1),但实际上每次插入都需要创建新的节点对象,这在频繁操作时可能带来GC压力。
2.3 异常处理的最佳实践
Java的异常处理看似简单,但有很多细节需要注意:
- 不要捕获Exception基类
java复制// 不好的做法
try {
// some code
} catch (Exception e) {
// handle
}
// 好的做法
try {
// some code
} catch (SpecificException e) {
// handle specific case
} catch (AnotherException e) {
// handle another case
}
- 不要忽略异常
java复制// 非常不好的做法
try {
// some code
} catch (Exception e) {
// 空的catch块
}
- 使用try-with-resources管理资源
java复制// Java 7之前
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("file.txt"));
// use br
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
// handle
}
}
}
// Java 7之后
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
// use br
}
3. JavaSE中的并发编程
3.1 线程基础与生命周期
理解线程的生命周期对编写正确的并发程序至关重要。Java线程有以下几种状态:
- NEW:新建但未启动
- RUNNABLE:可运行状态(包括正在运行和就绪)
- BLOCKED:等待获取监视器锁
- WAITING:无限期等待
- TIMED_WAITING:有限期等待
- TERMINATED:终止
常见的误区是认为线程调用start()后立即运行。实际上,start()只是将线程状态改为RUNNABLE,具体何时运行由操作系统调度决定。
3.2 synchronized关键字深入理解
synchronized是Java中最基本的同步机制,但它的实现原理很多人并不清楚。
- synchronized方法
java复制public synchronized void method() {
// 同步代码
}
等价于:
java复制public void method() {
synchronized(this) {
// 同步代码
}
}
- synchronized静态方法
java复制public static synchronized void staticMethod() {
// 同步代码
}
等价于:
java复制public static void staticMethod() {
synchronized(ClassName.class) {
// 同步代码
}
}
synchronized的实现是基于对象头中的Mark Word和监视器(monitor)机制。在JDK1.6之后,synchronized进行了很多优化,如偏向锁、轻量级锁等,使得在无竞争或低竞争情况下性能大幅提升。
3.3 volatile关键字与内存可见性
volatile保证了变量的可见性和禁止指令重排序,但不保证原子性。这是很多人的理解误区。
java复制public class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写操作
}
public void reader() {
if (flag) { // 读操作
// do something
}
}
}
在这个例子中,volatile保证了当writer线程修改flag后,reader线程能立即看到最新的值。但如果是对变量进行复合操作(如i++),volatile就不能保证原子性了。
4. JavaSE中的IO与NIO
4.1 传统IO的优缺点
Java传统的IO是基于流的模型,主要分为:
- 字节流:InputStream/OutputStream
- 字符流:Reader/Writer
传统IO简单易用,但在处理大量连接时性能较差,因为它是阻塞式的。每个连接都需要一个单独的线程处理,当连接数增多时,线程上下文切换的开销会变得很大。
4.2 NIO的核心组件
Java NIO(New IO)引入了非阻塞IO和选择器(Selector)的概念,主要组件包括:
- Channel:类似于流,但可以同时读写
- Buffer:数据容器
- Selector:多路复用器
NIO的工作流程:
- 创建Selector
- 创建ServerSocketChannel并配置为非阻塞
- 将channel注册到selector,指定感兴趣的事件
- 循环调用selector.select()等待事件
- 处理selectedKeys
java复制Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
if (key.isAcceptable()) {
// 处理连接
} else if (key.isReadable()) {
// 处理读
}
iter.remove();
}
}
4.3 文件操作的最佳实践
无论是使用传统IO还是NIO,文件操作都有一些需要注意的地方:
- 使用try-with-resources确保资源关闭
- 处理大文件时使用缓冲
- 注意文件编码问题
- 考虑使用Files工具类(Java 7+)
java复制// 读取文件内容到字符串(Java 11+)
String content = Files.readString(Path.of("file.txt"), StandardCharsets.UTF_8);
// 逐行读取大文件
try (Stream<String> lines = Files.lines(Path.of("largefile.txt"))) {
lines.forEach(System.out::println);
}
5. JavaSE中的新特性
5.1 Java 8的Lambda表达式
Lambda表达式是Java 8最重要的特性之一,它使Java具备了函数式编程的能力。
java复制// 传统方式
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
// Lambda方式
Collections.sort(list, (s1, s2) -> s1.compareTo(s2));
Lambda表达式不仅使代码更简洁,还能与Stream API配合使用,极大地提高了集合操作的表达能力。
5.2 Stream API的使用
Stream API提供了一种声明式处理集合数据的方式:
java复制List<String> filtered = list.stream()
.filter(s -> s.startsWith("A"))
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
使用Stream API时要注意:
- Stream操作分为中间操作和终端操作
- Stream是延迟执行的,只有遇到终端操作才会真正执行
- Stream只能消费一次
- 并行流(parallelStream)不一定比顺序流快,要视情况而定
5.3 模块化系统(Java 9+)
Java 9引入了模块化系统(JPMS),主要解决:
- 强封装性:明确控制哪些包可以被外部访问
- 可靠的配置:明确声明模块间的依赖关系
一个简单的模块定义(module-info.java):
java复制module com.example.myapp {
requires java.base;
requires java.sql;
exports com.example.myapp.api;
}
模块化虽然强大,但也增加了复杂性。对于小型应用,可能不需要使用模块系统。
6. JavaSE性能调优
6.1 JVM内存模型
理解JVM内存模型对性能调优至关重要。主要内存区域包括:
- 堆(Heap):对象实例存储区域
- 方法区(Method Area):类信息、常量、静态变量等
- 虚拟机栈(VM Stack):方法调用栈帧
- 本地方法栈(Native Method Stack)
- 程序计数器(Program Counter Register)
6.2 垃圾回收机制
Java的垃圾回收机制自动管理内存,但了解其工作原理有助于写出更高效的代码。
常见的垃圾回收器:
- Serial GC:单线程,适合客户端应用
- Parallel GC:多线程,吞吐量优先
- CMS GC:低延迟,已废弃
- G1 GC:平衡吞吐量和延迟,JDK9+默认
- ZGC/Shenandoah:超低延迟,实验性
可以通过JVM参数调整GC行为,例如:
code复制-XX:+UseG1GC -Xms512m -Xmx2g -XX:MaxGCPauseMillis=200
6.3 常见性能优化技巧
- 字符串操作:使用StringBuilder代替字符串拼接
- 集合初始化:预估大小避免扩容
- 自动装箱:避免不必要的装箱拆箱
- 对象复用:使用对象池或享元模式
- 缓存:适当使用缓存减少重复计算
java复制// 不好的做法
String result = "";
for (int i = 0; i < 100; i++) {
result += i; // 每次循环都创建新的StringBuilder和String
}
// 好的做法
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i);
}
String result = sb.toString();
7. JavaSE开发中的常见陷阱
7.1 equals和hashCode的契约
重写equals时必须重写hashCode,否则在使用HashMap/HashSet等集合时会出现问题。两者必须满足:
- 如果两个对象equals,那么它们的hashCode必须相同
- 如果两个对象hashCode相同,它们不一定equals
java复制@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
7.2 日期时间处理的陷阱
Java 8之前的Date/Calendar类有很多问题:
- 月份从0开始
- Date是可变的
- 线程安全问题
推荐使用Java 8的java.time包:
java复制LocalDate date = LocalDate.now();
LocalDateTime dateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = dateTime.format(formatter);
7.3 浮点数精度问题
浮点数(float/double)不适合精确计算,特别是金融相关计算:
java复制System.out.println(0.1 + 0.2); // 输出0.30000000000000004
对于精确计算,应使用BigDecimal:
java复制BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal sum = a.add(b); // 0.3
8. JavaSE学习路线建议
8.1 基础阶段
- 掌握Java基本语法
- 理解面向对象思想
- 熟悉常用类库:集合、IO、异常等
- 了解基本的多线程编程
8.2 进阶阶段
- 深入理解JVM原理
- 掌握并发编程高级特性
- 学习设计模式和编码规范
- 研究常用框架的源码
8.3 实战建议
- 多写代码,从简单项目开始
- 阅读优秀开源代码
- 参与实际项目积累经验
- 定期复习和总结
我在实际开发中发现,很多问题其实都能在JavaSE中找到答案。比如一个看似复杂的并发问题,可能只是因为对volatile的理解不够深入;一个性能问题,可能只是因为集合使用不当。因此,我建议每个Java开发者都应该定期回顾JavaSE的基础知识,随着经验的增长,每次回顾都会有新的收获。
