Java 基础 01:平台、JDK、字节码与可维护的最小项目骨架
写给初级工程师:JVM/JRE/JDK 分工、源码到字节码的路径、classpath 与模块的基础心智;用 Maven 或 Gradle 搭一个最小可测试骨架。
- Java
- JVM
- 基础
- 工程化
本篇目标:你能脱离 IDE「黑盒运行」,说清楚「编译期 vs 运行期」错误;用 Maven 或 Gradle 搭出带测试的最小工程;遇到
ClassNotFoundException/NoClassDefFoundError知道往哪查。
1. 知识点:JVM / JRE / JDK#
| 名词 | 职责 | 你该记住的一句话 |
|---|---|---|
| JVM | 执行字节码、线程、GC、JIT | 跨平台跑的是「字节码 + JVM 实现」 |
| JRE | JVM + 运行类库 | 服务器只有运行环境时可能没有 javac |
| JDK | JRE + javac、诊断工具 | 开发机永远装 JDK |
学以致用:终端执行 javac -version 与 java -version,确认版本一致(避免「编译用 21、运行用 17」)。
2. 知识点:从 .java 到运行的链路#
- 编译:
javac -d out src/com/example/App.java→ 生成out/com/example/App.class。 - 运行:
java -cp out com.example.App(注意包名对应目录)。 - 类加载:加载(读字节码)→链接(校验、准备、解析)→初始化(执行 static 块)。
学以致用:给 App 加一个 static 块打印日志,观察「首次主动使用类」时才初始化(对比只引用常量不一定初始化——了解即可)。
3. 知识点:CLASSPATH 与模块(JPMS)#
- classpath:告诉 JVM 去哪找 已编译 的类与 jar。
- 常见错误:少 jar →
NoClassDefFoundError(链接阶段);错类名/错包 →ClassNotFoundException。 - JPMS(Java 9+):
module-info.java控制导出包;大型项目逐步模块化。初级先会用构建工具管理依赖,少手写超长-cp。
对比表(排障用)
| 异常 | 常见触发 | 第一反应 |
|---|---|---|
ClassNotFoundException | Class.forName、ClassLoader 找不到 | 检查坐标、包名、shade 规则 |
NoClassDefFoundError | 编译有,运行时缺传递依赖 | 查依赖树 mvn dependency:tree |
4. 动手:Maven 最小骨架(核心片段)#
在空目录:
mvn -B archetype:generate -DgroupId=com.example -DartifactId=demo -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5重点看 pom.xml:
groupId/artifactId/version:坐标,定位 jar。dependencies:junit-jupiterscopetest。maven.compiler.release:锁语言级别(如 17)。
学以致用:把以下纯函数放进 src/main/java 并写测试(边界:负数、零、100% 边界)。
public final class DiscountCalculator {
private DiscountCalculator() {}
/** 返回折后价(分),四舍五入;费率 [0,1]。 */
public static long applyPercent(long priceFen, double rate) {
if (priceFen < 0) throw new IllegalArgumentException("price");
if (rate < 0 || rate > 1) throw new IllegalArgumentException("rate");
return Math.round(priceFen * (1 - rate));
}
}import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class DiscountCalculatorTest {
@Test
void roundsHalfUp() {
assertEquals(901L, DiscountCalculator.applyPercent(1000L, 0.099)); // 四舍五入
}
}5. 知识点:java.time(务必替换 Date/Calendar)#
| API | 用途 |
|---|---|
Instant | UTC 时间轴上的点,适合存库/日志 |
ZonedDateTime | 带时区的墙上时间,适合展示 |
Duration | 两个 Instant 的间隔 |
DateTimeFormatter | 解析/格式化(线程安全) |
学以致用:把「用户时区展示」与「数据库存 UTC」拆清;永远不要用手动 + 8 * 3600 * 1000。
import java.time.*;
Instant now = Instant.now();
ZonedDateTime shanghai = now.atZone(ZoneId.of("Asia/Shanghai"));
String label = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(shanghai);6. 知识点:字符集(线上乱码首位嫌疑)#
读写文件必须显式 charset:
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
String text = Files.readString(Path.of("note.txt"), StandardCharsets.UTF_8);
Files.writeString(Path.of("out.txt"), text, StandardCharsets.UTF_8);7. 自测清单(须能口述或演示)#
- 用命令行完成「编译 + 运行」你的
App,并解释-cp指向哪里。 - 故意删掉一个依赖,复现
NoClassDefFoundError,再用dependency:tree解释原因。 DiscountCalculator三条测试:正常、非法费率、边界四舍五入。- 写出「为何要用
Instant存库」的一句话答案。
下一篇:java-fundamentals-02-oop-collections-generics,把集合从「背 API」变成「按复杂度与语义选型」。
相关阅读
基于内容相似度 + 标签/系列加权
- 2 分钟Java 从基础到精通(初级 · 高级 · 专家路线)
Java 基础 02:面向对象、集合框架与泛型(正确用上接口与算法复杂度)
初级必读:类/接口/抽象的分工、equals 与 hashCode 契约、List/Set/Map 选型、迭代与并发修改;泛型如何避免强制转换地狱。
- Java
- 集合
- 泛型
- 1 分钟Java 从基础到精通(初级 · 高级 · 专家路线)
Java 进阶 02:JVM 运行时、GC 与调参入门(从现象到证据)
堆/栈/元空间、对象分配与 TLAB、常见 GC 算法对比、GC 日志与 jcmd/jstack 入门;Stop-The-World 与延迟取舍,避免迷信参数大全。
- Java
- JVM
- GC
- 2 分钟Java 从基础到精通(初级 · 高级 · 专家路线)
Java 基础 03:异常体系、try-with-resources、IO/NIO.2 入门与并发初探
初级收尾篇:受检/非受检异常取舍、异常链与日志;现代 IO 用法;线程与 synchronized 的最小正确用法,为进阶 JUC 铺路。
- Java
- 异常
- IO
- 2 分钟Java 从基础到精通(初级 · 高级 · 专家路线)
Java 路线 00:从初级到专家的三层能力模型、学习顺序与全系列索引
面向初级、高级与资深/专家 Java 工程师的分层路线:每层该学什么、如何验收、与本系列各篇的对应关系;附统一练习模板与进阶节奏。
- Java
- 学习路线
- 方法论