《Java 编程入门》附录 D 常见编译与运行错误

按报错关键词整理 Java 入门阶段最常见的编译与运行问题:找不到符号、类型不兼容、找不到主类、类版本不匹配、NoSuchMethodError、空指针、数组越界、并发修改、编码乱码、Maven 依赖冲突、jar 无法运行、端口占用等,每条给出典型报错、原因、排查步骤与修复示例。

怎么用这一页

先按报错的第一行关键词定位,再看「原因」和「修复」。Java 的报错通常包含三样信息:文件与行号、出错的符号或类型、期望值与实际值。读懂这三样,大多数问题不需要搜索引擎。

一个通用方法:

javac -Xdiags:verbose YourFile.java
java -XX:+ShowCodeDetailsInExceptionMessages YourApp

前一个让编译错误更详细,后一个让空指针异常告诉你「哪个变量是 null」。这个开关在 JDK 14 引入、默认关闭;JDK 15 起默认开启。本机 JDK 25 的实际默认值可以这样确认:

$ java -XX:+PrintFlagsFinal -version 2>/dev/null | grep ShowCodeDetails
     bool ShowCodeDetailsInExceptionMessages       = true  {manageable} {default}

编译错误

找不到符号:cannot find symbol

CannotFind.java:3: 错误: 找不到符号
        System.out.println(unknownVariable);
                           ^
  符号:   变量 unknownVariable
  位置: 类 CannotFind

原因:编译器在当前作用域、当前类、当前包和已导入的包里都找不到这个名字。

排查顺序:

  1. 拼写是否正确(大小写敏感)。
  2. 变量是否在另一个作用域声明。
  3. 类是否忘了 import。
  4. 依赖是否没加进 pom.xml。
  5. 方法参数类型是否写错,导致重载没匹配上。

不兼容的类型:incompatible types

Incompatible.java:3: 错误: 不兼容的类型: String无法转换为int
        int x = "hello";
                ^

原因:Java 是静态类型语言,不会自动把字符串转成整数。

修复:

int x = Integer.parseInt("42");

或者把变量类型改成 String。不要用强制转换糊弄:(int) "hello" 本身就会编译失败。

缺少返回语句:missing return statement

MissingReturn.java:4: 错误: 缺少返回语句
    }
    ^

原因:方法声明了非 void 返回值,但存在一条执行路径没有 return。

修复:补上所有分支的返回,或者把最后一个分支改成无条件 return。

static int f(int x) {
    if (x > 0) return 1;
    return 0;
}

公共类文件名不匹配

WrongName.java:1: 错误: 类 OtherName 是公共的, 应在名为 OtherName.java 的文件中声明
public class OtherName { }

原因:一个文件里只能有一个 public 类,且文件名必须与它一致。

修复:把文件重命名为 OtherName.java,或者去掉 public(如果不需要跨包访问)。

程序包不存在:package does not exist

原因:import 的包不在 classpath 上,通常是依赖没加或模块没声明。

排查:

mvn -q dependency:tree
javac -cp "lib/*" -d out $(find src -name '*.java')

Maven 项目先 mvn -q compile,看依赖是否下载成功;模块化项目检查 module-info.java 的 requires。

可能尚未初始化变量

int x;
if (condition) x = 1;
System.out.println(x); // 编译错误

原因:编译器无法证明 x 在所有路径上都被赋值。

修复:给默认值,或补全 else 分支。

不可达语句:unreachable statement

return;
System.out.println("never");

原因:return、throw、无限循环之后的代码永远不会执行。

修复:删掉不可达代码,或调整控制流。

运行错误

找不到或无法加载主类

错误: 找不到或无法加载主类 com.plume.library.LibraryApp
原因: java.lang.ClassNotFoundException: com.plume.library.LibraryApp

原因:classpath 上找不到这个全限定类名。

排查:

  1. 类名是否写全(包名 + 类名)。
  2. -cp 是否指向 class 文件所在目录,而不是 src。
  3. 包声明与目录结构是否一致(com/plume/library/LibraryApp.class)。
  4. jar 是否包含这个类:jar tf app.jar | grep LibraryApp。

修复:

javac -d out src/main/java/com/plume/library/LibraryApp.java
java -cp out com.plume.library.LibraryApp

NoClassDefFoundError

原因:编译时有这个类,运行时找不到;或者类的静态初始化失败。

与 ClassNotFoundException 的区别:后者是「从来没找到」,前者是「找过但加载失败/缺失」。

排查:

  1. 依赖是否打进产物(mvn dependency:tree、jar tf)。
  2. 静态块是否抛异常(看 Caused by)。
  3. 是否有版本冲突导致同一个类被替换。

UnsupportedClassVersionError

Exception in thread "main" java.lang.UnsupportedClassVersionError:
LibraryApp has been compiled by a more recent version of the Java Runtime
(class file version 69.0), this version of the Java Runtime only recognizes
class file versions up to 52.0

原因:class 文件版本高于 JVM 支持版本。69 对应 JDK 25,52 对应 JDK 8。

修复:

javac --release 21 -d out src/main/java/com/plume/library/LibraryApp.java

或者升级运行环境的 JDK。不要用 -source/-target 代替 --release。

NoSuchMethodError

原因:编译时链接了高版本 API,运行时加载的是低版本类。常见于依赖冲突。

排查:

mvn -q dependency:tree -Dincludes=com.example:some-lib

修复:在 pom.xml 里显式声明正确版本,或用 dependencyManagement 统一版本。

NullPointerException

Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.length()" because "<local1>" is null
	at Npe.main(Npe.java:4)

原因:对 null 调用方法、访问字段、拆箱、数组长度。

修复:

  • 用 Objects.requireNonNull 在边界处快速失败。
  • 用 Optional 表达「可能没有值」,但不要用它替代所有判空。
  • 检查集合查找结果:map.get(key) 可能返回 null。
  • 自动拆箱时尤其小心:Integer 为 null 会抛 NPE。

ArrayIndexOutOfBoundsException

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException:
Index 5 out of bounds for length 3

原因:索引超出 0..length-1。

修复:检查循环边界(i < array.length),检查 subList/substring 的结束索引是否越界。

ClassCastException

Exception in thread "main" java.lang.ClassCastException:
class java.lang.String cannot be cast to class java.lang.Integer

原因:强转的类型与实际类型不兼容。

修复:先用 instanceof 判断,或用模式匹配:

if (obj instanceof Integer i) {
    System.out.println(i + 1);
}

IllegalArgumentException

原因:参数值不合法(负数、空字符串、越界范围)。

修复:在方法入口校验参数,抛出带具体信息的异常;调用方捕获后给用户可读提示。

ConcurrentModificationException

Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1096)

原因:用增强 for 遍历集合时,直接调用集合的 remove/add。

修复:

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("a")) it.remove();
}

或使用 removeIf:

list.removeIf(s -> s.equals("a"));

或使用 CopyOnWriteArrayList(读多写少场景)。

ExceptionInInitializerError

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.ArithmeticException: / by zero
	at InitErr.<clinit>(InitErr.java:2)

原因:静态初始化块或静态字段初始化抛异常。

修复:看 Caused by 找到真正的异常;把可能失败的静态初始化改成惰性初始化或显式方法。

OutOfMemoryError

类型常见原因排查
Java heap space对象太多/太大、泄漏堆转储、jmap、MAT
Metaspace动态生成类太多检查反射/代理/字节码生成
Direct buffer memoryNIO 直接内存未释放检查 ByteBuffer.allocateDirect
unable to create native thread线程数过多检查线程池、ulimit

卷一只需知道:OOM 不是「加内存」就完事,要先看是哪块内存。卷四会讲堆转储和 NMT。

StackOverflowError

原因:递归太深或无限递归。

修复:加终止条件,或改成迭代。默认栈大小可以用 -Xss 调整,但通常先改代码。

编码与文件

中文乱码

原因:写入和读取用了不同编码,或终端/文件/数据库编码不一致。

排查:

file -I books.csv
xxd -l 16 books.csv

修复:读写都显式指定 UTF-8:

Files.writeString(path, text, StandardCharsets.UTF_8);
String text = Files.readString(path, StandardCharsets.UTF_8);

如果文件来自 Excel,可能带 BOM。读取时用 BufferedReader 并跳过 ,或先用工具去掉 BOM。

路径找不到

原因:相对路径的基准是「启动 JVM 的当前目录」,不是源码目录,也不是 jar 所在目录。

修复:用 Path.of(...).toAbsolutePath() 打印实际路径;生产环境用配置指定数据目录,不要依赖当前目录。

WatchService 收不到事件

原因:监听的是目录,不是文件;某些编辑器保存时是「写临时文件 + 重命名」,事件类型不是 ENTRY_MODIFY。

修复:监听目录,处理 ENTRY_CREATE、ENTRY_DELETE、ENTRY_MODIFY 三类事件;对重命名做去抖。

Maven 与构建

mvn -version 的 Java 版本不对

原因:Maven 用 JAVA_HOME 指向的 JDK,而不是 PATH 里的 java。

修复:

echo $JAVA_HOME
which -a java
mvn -version

把 JAVA_HOME 指向 JDK 25,重新打开终端。

依赖冲突

症状:NoSuchMethodError、ClassNotFoundException、行为与文档不一致。

排查:

mvn -q dependency:tree
mvn -q dependency:tree -Dincludes=com.fasterxml.jackson.core

修复:用 dependencyManagement 统一版本,或排除传递依赖:

<dependency>
  <groupId>com.example</groupId>
  <artifactId>some-lib</artifactId>
  <version>1.2.3</version>
  <exclusions>
    <exclusion>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
    </exclusion>
  </exclusions>
</dependency>

测试没跑

原因:测试类命名不符合 Surefire 默认规则(*Test、Test*、*Tests、*TestCase),或测试目录不在 src/test/java。

修复:重命名测试类,或配置 Surefire 的 includes。

mvn package 后 jar 不能运行

症状:

hello.jar中没有主清单属性

原因:jar 的 META-INF/MANIFEST.MF 没有 Main-Class。

修复:用 maven-jar-plugin 配置 mainClass,或用 maven-shade-plugin 打可执行 fat jar。第 17 章有完整配置。

构建结果不可复现

原因:时间戳、文件顺序、依赖版本范围、本地缓存。

修复:固定依赖版本,使用 maven-enforcer-plugin 锁定版本,开启 project.build.outputTimestamp,在干净环境验证。

HTTP 与服务

Address already in use

原因:端口被占用。

排查:

lsof -i :8080

修复:换端口,或结束占用进程。不要在代码里硬编码端口,用配置或环境变量。

Connection refused

原因:目标服务没启动、端口写错、防火墙拦截。

排查:先用 curl 或 nc 确认服务可达,再查客户端代码。

HTTP 请求超时

原因:没有设置连接/读取超时,默认可能无限等待。

修复:

HttpClient client = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(3))
    .build();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://example.com/api"))
    .timeout(Duration.ofSeconds(5))
    .GET()
    .build();

调试流程

遇到问题时,按这个顺序缩小范围:

  1. 读第一行报错:类型、文件名、行号。
  2. 读 Caused by:真正的根因通常在最后一层。
  3. 最小复现:把可疑代码复制到单独文件,用 javac/jshell 跑。
  4. 打印中间值:System.out.println 不丢人,先确认假设。
  5. 检查环境:JDK 版本、classpath、编码、时区、当前目录。
  6. 查官方文档:Javadoc 和 Release Notes 比博客可靠。
  7. 再改代码:一次只改一个变量,改完立刻验证。

常用诊断命令

# 版本与环境
java -version
javac -version
mvn -version
echo $JAVA_HOME

# 类与依赖
jar tf app.jar | head
jdeps --multi-release 21 -summary out
mvn -q dependency:tree

# 运行时
jps -l
jcmd <pid> Thread.print
jcmd <pid> GC.heap_info
jstack <pid>
jmap -dump:format=b,file=heap.hprof <pid>

# 端口
lsof -i :8080

jps、jcmd、jstack、jmap 都在 JDK 的 bin 下。卷四会系统讲这些工具。

一页记忆

  • 编译错误看「符号、类型、行号」;运行错误看「异常类、消息、Caused by」。
  • 找不到类:先查 classpath 和包名,再查依赖。
  • 版本错误:用 --release 统一编译目标。
  • 空指针:在边界用 Objects.requireNonNull,拆箱前判空。
  • 并发修改:用 Iterator.remove 或 removeIf。
  • 乱码:读写都显式 UTF-8,检查 BOM。
  • Maven 冲突:dependency:tree + dependencyManagement。
  • jar 不能跑:检查 Main-Class 和依赖是否打进去。
  • OOM:先分清堆、Metaspace、直接内存、线程,再谈加内存。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Netty 高性能网络编程》15.3 linkd-v2 验收与复盘
  2. 《Netty 高性能网络编程》15.2 限流、超时与降级
  3. 《Netty 高性能网络编程》15.1 网关路由与转发