开发环境与第一个 Java 程序
学习目标
- 区分 JDK、JRE、JVM,以及
javac、java、jar各自负责的工作。 - 能在命令行编译和运行带包名的程序,并解释字节码和
classpath的作用。 - 根据常见错误信息定位 Java 版本、类名、包名或入口方法问题。
核心知识点
专业术语
- JVM(Java Virtual Machine):加载并执行
.class字节码的虚拟机,负责类加载、执行和运行时内存管理。 - JRE(Java Runtime Environment):运行 Java 程序所需的 JVM 与标准类库;现代 JDK 通常直接提供完整运行时,不再单独发布传统 JRE 安装包。
- JDK(Java Development Kit):开发工具包,包含编译器、运行器、打包和诊断工具。
- 字节码(bytecode):
javac生成的、与操作系统无关的中间指令,通常存放在.class文件中。 - classpath:JVM 和编译器查找类文件与依赖的路径集合;包名对应 classpath 下的目录层级。
| 名称 | 主要职责 | 边界 |
|---|---|---|
| JVM | 加载、校验并执行字节码 | 不能把源码直接当作已编译项目管理 |
| JRE | 提供 JVM 与运行所需的标准类库 | 传统独立 JRE 安装包并不是现代 JDK 的必需分发形式 |
| JDK | 提供编译、运行、打包和诊断工具 | 开发者仍需正确配置版本和 classpath |
白话解释与边界
源码到字节码
可以把源码到运行理解为“先翻译,再执行”:javac 把 .java 翻译成字节码,java 启动 JVM 找到入口类,JVM 再解释或即时编译字节码。JDK 20 是本课程基准;编译器版本和运行时版本不匹配时,可能出现 UnsupportedClassVersionError。classpath 必须指向包目录的根,而不是包目录本身;命令行能运行并不代表 IDE 的项目配置正确。
javac 编译
javac 只负责把源码编译为字节码,并通过 -d 指定输出根目录;编译成功不代表程序已经运行。
java 运行
java 负责启动 JVM,按全限定类名和 classpath 查找入口;JDK 11 起可以用 java Hello.java 直接运行单文件源码,但这不会取代正式项目对编译输出和依赖的管理。
jar 打包
jar 负责创建、查看或更新归档;它不会自动执行程序,运行 JAR 仍需要正确的 classpath 或入口配置。
常见启动错误
常见错误可以按阶段判断:找不到 javac 通常是 JDK 或 PATH 问题,Could not find or load main class 多半是包名或 classpath 根错误,UnsupportedClassVersionError 则说明编译目标高于运行时。先确认 java 与 javac 的实际路径,再检查输出目录层级,通常比反复修改源码更快。
常用用法
javac 与 java:编译并运行类
先用 javac CompileRunDemo.java 生成字节码,再用 java CompileRunDemo 启动;两步分开更容易定位编译错误和运行时错误。
public class CompileRunDemo {
// 作用:先用 javac CompileRunDemo.java 生成字节码,再用 java CompileRunDemo 启动;两步分开更容易定位编译错误和运行时错误。
// 初始状态:javac 编译源文件 CompileRunDemo.java,java 再以类名 CompileRunDemo 启动 main。
public static void main(String[] args) {
System.out.println("compiled and running");
// 输出:compiled and running
}
}jar:把编译结果打成归档
jar 只负责归档,真正运行仍由 java 启动;正式项目还要配置入口类和依赖 classpath。
// 语义:jar 只负责归档,真正运行仍由 java 启动。
public class JarEntryDemo {
// 作用:jar 只负责归档,真正运行仍由 java 启动;正式项目还要配置入口类和依赖 classpath。
public static void main(String[] args) {
System.out.println("jar entry");
// 输出:jar entry
// 作用:通过 jar 把编译结果打成归档。
}
}不常用但需要知道
java Source.java:直接运行单文件源码
单文件启动适合教学和一次性脚本;多文件项目仍应显式编译并管理输出目录。
// 作用:通过 java Source.java 直接运行单文件源码。
public class SingleFileDemo {
// 作用:单文件启动适合教学和一次性脚本;多文件项目仍应显式编译并管理输出目录。
public static void main(String[] args) {
System.out.println("single file");
// 输出:single file
}
}classpath:从包根查找类
运行 demo.ClasspathDemo 时,classpath 要指向包含 demo 目录的根,而不是 demo 目录本身。
package demo;
public class ClasspathDemo {
// 作用:通过 classpath 从包根查找类。
public static void main(String[] args) {
System.out.println("package root");
// 输出:package root
}
}简单案例
先创建 src/com/example/HelloJava.java,再把编译产物放到 out:
package com.example;
public class HelloJava {
public static void main(String[] args) {
String name = args.length == 0 ? "Java" : args[0];
System.out.println("Hello, " + name + "!");
// 输出:Hello, 小明!
System.out.println("class: " + HelloJava.class.getName());
// 输出:class: com.example.HelloJava
}
}javac -d out src/com/example/HelloJava.java
java -cp out com.example.HelloJava 小明
jar --create --file hello.jar -C out .
java -cp hello.jar com.example.HelloJava 小明out/com/example/HelloJava.class 就是编译生成的字节码。运行时写 com.example.HelloJava 而不是 HelloJava,因为 classpath 根是 out。
易混点
- JDK 是开发工具包,JVM 是运行字节码的程序;安装 JDK 不等于可以忽略运行时版本。
- 包名不是文件夹的装饰,而是类的全限定名组成部分;
-cp out后才能从out找到com.example.HelloJava。 javac成功只表示源码编译成功,java仍可能因入口签名、类路径或版本错误而启动失败。.class是字节码而不是某个平台的机器码;JVM 会在目标平台完成后续执行。
课后小问
为什么运行带包名的
HelloJava时要写com.example.HelloJava? 答案:因为包名和类名共同组成全限定类名,JVM 按它在 classpath 根下定位.class文件。 解析:-cp out指向的是目录根,实际文件位于out/com/example/HelloJava.class;只写短类名会让 JVM 查找错误路径。javac、java和jar之间有什么区别? 答案:javac编译源码,java启动 JVM 运行类,jar创建或查看 JAR 归档。 解析:三者处于不同阶段;编译没有运行程序,打包也不会自动执行程序,仍需用 classpath 或 JAR 启动命令指定入口。
本节小结
- JDK 提供开发工具,JVM 负责加载和执行字节码,传统 JRE 只是运行时概念。
javac生成.class,java按全限定类名和 classpath 找到入口并启动。- 包名决定类的路径关系,
-d和-cp分别控制输出根与查找根。 - 版本、入口方法和 classpath 是命令行运行失败的三类常见边界。
快速回顾
- 能说明 JDK、JVM、字节码三者在运行链路中的位置。
- 能用
javac -d编译带包名的类并用java -cp启动。 - 能解释
HelloJava.class为什么位于包目录层级下。 - 能区分找不到命令、找不到主类和版本不兼容错误。
阿源的知识库