'javac' is not recognized as an internal or external command, operable program or batch file.
Windows 找不到 javac。按顺序检查三个原因。第一,窗口是在安装完成之前打开的:关掉它,再开一个新的。第二,Java 没有被加进 PATH:打开“设置”,进入“应用”,找到 Eclipse Temurin JDK,选“修改”,把“Add to PATH”打开。第三,你装的是 JRE,它能运行程序但没有 javac:请安装 JDK。PowerShell 的说法不同:The term 'javac' is not recognized as the name of a cmdlet, function, script file, or operable program。意思是一样的。
javac: command not found
终端找不到 javac。macOS 的 zsh 会写成 zsh: command not found: javac,macOS 也可能改说 Unable to locate a Java Runtime。请按上面的步骤安装 JDK。如果刚装完,先关掉终端再开一个新的。如果 java -version 能用而 javac 不能用,说明你装成了 JRE 而不是 JDK。在 Linux 上请装 temurin-25-jdk,不要装 temurin-25-jre。Ubuntu 可能会打印 Command 'javac' not found, but can be installed with: 和一串软件包。你可以不理那一串,改按上面的 Temurin 步骤来做。
javac: file not found: Main.java
javac 在终端当前所在的文件夹里看不到这个名字的文件。用 dir(Windows)或 ls(macOS 和 Linux)列出文件,再用 cd 进入存放文件的文件夹,例如 cd Desktop。仔细核对文件名。Windows 可能把扩展名隐藏起来,所以你命名为 Main.java 的文件,实际上可能是 Main.java.txt。
error: class Hello is public, should be declared in a file named Hello.java
public 的类必须放在同名文件里,大小写也要一样。这里的类叫 Hello,所以文件必须叫 Hello.java。要么给文件改名,要么把类名改成和文件一致。在设置为中文的电脑上,这条信息可能显示为:错误: 类Hello是公共的, 应在名为 Hello.java 的文件中声明。
publicclassMain{publicstaticvoidmain(String[]args){System.out.println("Hello, Java!");System.out.println("My name is Leon.");System.out.println(2+3);}}
先预测输出,再看结果
预期输出
Hello, Java!
My name is Leon.
5
逐步讲解
public class Main { 开始定义类。它叫 Main,所以文件必须叫 Main.java。这个花括号打开类,文件的最后一行把它关上。
文本编辑器可能会在文件名后面加上 .txt,而 Windows 又会隐藏扩展名,所以你看不到它。这时 javac 会报告:javac: file not found: Main.java。在文件资源管理器里,打开“查看”菜单,勾选“文件扩展名”,就能看到真实的文件名。在记事本的“另存为”窗口里,先把“保存类型”改成“所有文件”,再输入 Main.java。在 Mac 上,“文本编辑”默认保存为富文本:保存之前先选“格式”菜单里的“制作纯文本”,或者改用 VS Code 之类的编辑器。
正确写法
Main.java
错误写法
java Main (typed after editing Main.java, without compiling again)
for 循环让一个代码块重复运行。循环就是让代码块一遍又一遍运行的语句。for 的括号里有三部分,用分号隔开,例如 for (int i = 1; i <= 5; i++)。起点 int i = 1 创建一个叫 i 的计数器并设为 1。测试 i <= 5 在每一轮开始前检查,变成 false 时循环结束。步进 i++ 在每一轮结束后让 i 加 1,它是 i = i + 1 的简写。同样,i-- 表示减 1。
publicclassMain{publicstaticvoidmain(String[]args){inttotal=0;for(inti=1;i<=5;i++){total=total+i;if(i%2==0){System.out.println(i+" is even, total "+total);}else{System.out.println(i+" is odd, total "+total);}}System.out.println("final total "+total);}}
先预测输出,再看结果
预期输出
1 is odd, total 1
2 is even, total 3
3 is odd, total 6
4 is even, total 10
5 is odd, total 15
final total 15
逐步讲解
int total = 0; 在循环开始之前创建累计的总和。
for (int i = 1; i <= 5; i++) 开始循环。i 从 1 开始,只要 i <= 5,代码块就继续运行。
第 1 轮:i 是 1,所以 total 变成 0 + 1 = 1。余数 1 % 2 是 1,不是 0,所以条件 i % 2 == 0 为 false,运行 else 代码块,打印 1 is odd, total 1。
第 2 轮:i 是 2,total 变成 3。2 % 2 是 0,条件为 true,运行第一个代码块,打印 2 is even, total 3。
在方法里创建的变量(包括形参)都是局部变量。它只在方法运行期间存在,别的方法看不到它。因为实参是被复制过去的,所以在方法里修改形参,不会改变调用它的代码(称为调用者)里的变量。例子里用 n 演示了这一点。
新词
方法和调用
方法是有名字的一段代码。调用它,就是写出名字和括号来运行它。
形参和实参
形参是方法头里接收输入的变量;实参是调用时传进去的值。
返回值
方法用 return 送回的值。它的种类是返回类型;void 表示什么也不返回。
局部变量
只在某个方法运行期间、只在这个方法里面存在的变量。
完整例子
Java 8+
publicclassMain{staticvoidgreet(Stringname){System.out.println("Hello, "+name+"!");}staticintsquare(intn){returnn*n;}staticintaddOne(intn){n=n+1;returnn;}publicstaticvoidmain(String[]args){greet("Iris");intresult=square(6);System.out.println("6 squared is "+result);System.out.println(square(square(2)));intn=5;intbigger=addOne(n);System.out.println("n is "+n+", bigger is "+bigger);}}
先预测输出,再看结果
预期输出
Hello, Iris!
6 squared is 36
16
n is 5, bigger is 6
逐步讲解
greet 是 void 方法。它只打印,不返回任何东西。它的形参 name 接收传给它的文字。
square 接收一个 int,返回一个 int。return n * n; 把乘积送回给调用者。
addOne 修改的是它自己的那份 n,然后把它返回。
在 main 里,greet("Iris") 把实参 "Iris" 复制给 name,于是打印出 Hello, Iris!。
要访问每一个元素,可以按下标循环:for (int i = 0; i < scores.length; i++)。还有一种更短的写法,叫 for-each 循环:for (int s : scores) { ... } 读作“对 scores 里的每个 int s”。变量 s 依次取到每一个值。不需要下标的时候用它。
第一个循环用 i 作下标,从 0 到 2。i 变成 3 时,测试 i < scores.length 为 false,所以下标 3 永远不会被碰到。它把 80、100、70 加起来,得到 250。
for-each 循环按顺序访问这三个值,s 每次拿到其中一个。
new int[3] 创建三个元素,它们一开始都是 0。
常见错误
错误写法
for (int i = 0; i <= scores.length; i++) {
System.out.println(scores[i]);
}
数组有 3 个元素,最后一个下标是 2,而 i 会取到 3。程序先打印出三个分数,然后停下来,报:Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3。它下面一行会写出你的文件名和行号。Java 8 在冒号后只会打印数字 3。和 length 比较时请用 <。
正确写法
for (int i = 0; i < scores.length; i++) {
System.out.println(scores[i]);
}
publicclassMain{publicstaticvoidmain(String[]args){Stringword="hello";Stringtyped=newString("hello");// pretend this text came from the keyboardSystem.out.println("same text? "+word.equals(typed));System.out.println("same object? "+(word==typed));Stringshout=word.toUpperCase();System.out.println(shout+" "+word);System.out.println(word.length());System.out.println(word.charAt(1));System.out.println(word.substring(1,3));System.out.println(word.indexOf("l"));System.out.println("ignoring case? "+word.equalsIgnoreCase("HELLO"));}}
先预测输出,再看结果
预期输出
same text? true
same object? false
HELLO hello
5
e
el
2
ignoring case? true
逐步讲解
word 是一个字面量。new String("hello") 另外创建了一个独立的对象,里面的字母一样。我们假装这段文字来自键盘。
不带 static 的方法是针对某一个具体的对象工作的。对 Leon 的对象调用 addPoints(5),改变的是 Leon 的分数,不会影响别人。在类的内部,这样的方法直接用名字使用字段。
构造方法是一种特殊的方法,用 new 创建对象时它会运行。它的名字和类名相同,没有返回类型,连 void 也不写。它的工作是给字段赋上最初的值。new Player("Leon") 会创建对象,以 "Leon" 作为实参运行构造方法,并把指向新对象的引用交给你。
在构造方法里,形参和字段可能都叫 name。写 this.name 表示“这个对象的 name 字段”,单独写 name 表示形参。所以 this.name = name; 的作用是把实参存进字段。
类放在哪里呢?在真正的项目里,每个类都有自己的文件,文件以类命名。为了让这个例子留在同一个文件里,多出来的类要写在 public class Main 的上方。在这种写法下,只有叫 Main 的类可以是 public 的。
新词
类和对象
类是蓝图;对象是用 new 按蓝图造出来的一个具体的东西。
字段
属于某个对象的变量,声明在类里面、所有方法外面。
构造方法
一种特殊的方法,名字与类相同,没有返回类型。new 创建对象时它会运行,负责设置字段。
this
指代正在运行其方法的那个对象。this.name 就是这个对象的 name 字段。
private
只有同一个类里面的代码才能使用它。
完整例子
Java 8+
classPlayer{privateStringname;privateintscore;Player(Stringname){this.name=name;this.score=0;}voidaddPoints(intpoints){score=score+points;}Stringdescribe(){returnname+" has "+score+" points";}}publicclassMain{publicstaticvoidmain(String[]args){Playerleon=newPlayer("Leon");Playermia=newPlayer("Mia");leon.addPoints(5);leon.addPoints(3);mia.addPoints(10);System.out.println(leon.describe());System.out.println(mia.describe());}}
先预测输出,再看结果
预期输出
Leon has 8 points
Mia has 10 points
逐步讲解
class Player { 开始定义这张蓝图。它不是 public 的,所以同一个文件里还可以放 public class Main。
private String name; 和 private int score; 是字段。每个 Player 对象都有自己的一对。
加上返回类型(哪怕是 void),它就变成了一个碰巧叫 Player 的普通方法。这样类里就没有接收 String 的构造方法了,new Player("Leon") 会失败:error: constructor Player in class Player cannot be applied to given types;,并附带 required: no arguments 和 found: String。
正确写法
Player(String name) {
this.name = name;
}
错误写法
Player(String name) {
name = name;
this.score = 0;
}
两边都是形参,所以这是把形参赋给它自己。字段 name 从来没有被设置,保持默认值 null。不会出现任何报错,describe() 打印出来的是 null has 0 points。要在字段前面写上 this.。
有三个习惯很有帮助。先修第一个错误,因为后面的错误常常是它引起的,少一个花括号就可能带来十几条报错。每改一处就重新编译一次。其次,报出的行号是 Java 发现问题的地方,真正的错误可能在上面一行。最后,把信息当作提示,而不是诊断结论。cannot find symbol 只说明 Java 不认识某个名字。
异常没有被处理时,就会出现堆栈跟踪。异常是一种事件,表示程序运行时出了问题,例如除以零,或者使用了数组范围之外的下标。程序会在那一刻停止。堆栈跟踪的第一行写着异常的名字和一条消息。接下来是若干以 at 开头的行,每一行是一个当时正在运行的方法,括号里是文件名和行号。最上面的 at 行是异常发生的地方,它下面一行是调用它的方法,依次往下,一直到 main。找到第一行写着你自己文件名(例如 Main.java)的 at 行,打开那个文件,跳到那一行。提到 java.base 或 java.util 的行是 Java 自己的代码。原因几乎总是在你自己的代码里。
publicclassMain{staticintreadAge(Stringtext){returnInteger.parseInt(text);}publicstaticvoidmain(String[]args){System.out.println("start");try{System.out.println("age is "+readAge("12"));System.out.println("age is "+readAge("twelve"));System.out.println("this line is skipped");}catch(NumberFormatExceptione){System.out.println("not a whole number");}System.out.println("done");}}
public class Main {
public static void main(String[] args) {
int total = 5;
System.out.println(total);
}
}
错误写法
public class Main {
static int average(int total, int count) {
return total / count;
}
public static void main(String[] args) {
System.out.println(average(10, 0));
}
}
这段代码能通过编译,但运行时 Java 会打印: Exception in thread "main" java.lang.ArithmeticException: / by zero at Main.average(Main.java:3) at Main.main(Main.java:7) 从最上面读起。第一行说明发生了什么:一个整数被零除。第一条 at 行说明在哪里:average 方法,Main.java 的第 3 行,也就是 return total / count; 这一行。下一条 at 行说明是谁调用的:main,在第 7 行。所以除法在第 3 行,而那个 0 来自第 7 行的调用。要决定修哪一处。这里应该让方法检查 count 是不是零。
正确写法
static int average(int total, int count) {
if (count == 0) {
return 0;
}
return total / count;
}
一个程序停下来时的输出是:Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3,然后是 at Main.last(Main.java:9),再是 at Main.main(Main.java:15)。哪个方法出了问题,在哪一行?这条消息告诉了你什么?