第一单元 · 终端、变量、输入 · 第 1 / 12 节
在 SSH 里写下第一个 Java 程序
从 Linux shell 提示符开始,建 lesson01 工作目录,写 Hello.java,分清提示符/源码/输入/输出,用 javac -encoding UTF-8 编译、java 运行。
把网页放在左边,SSH 终端放在右边:网页负责讲清楚“做什么、为什么”,右边负责真正敲命令、看结果。本节的每个命令都可以从网页复制,但请先读一遍再粘贴,别把整页一次性倒进终端。
开始本节前,你已经通过 SSH 登录学习服务器。SSH 是一种加密的远程登录方式:你在自己电脑上输入命令,真正执行命令的是服务器。登录后屏幕上有一个 Linux shell 提示符;shell 是终端里读命令、把命令交给 Linux 的程序。提示符的样子随账号和配置变化,可能是 ubuntu@host:~$,也可能是 root@host:~#;它不是命令的一部分,复制时不要带上。如果这是新开的终端,先按 setup.html 的说明加载课程环境,通常是:
$ source ~/java-practice/env.sh
加载后 java -version 和 javac -version 都应是 17.x。看不到 17.x 就先回 setup 页排查,不要继续往下走,也不要在服务器上自己安装 JDK。
先把屏幕上四种东西分开,后面每一节都按这个规则看:
| 你看到的东西 | 它是什么 | 怎么处理 |
|---|---|---|
root@host:~# 或 ubuntu@host:~$ | SSH 的 Linux shell 提示符,表示终端在等你输入命令 | 不复制 |
public class Hello { ... } | Java 源码,属于某个 .java 文件 | 复制进文件 |
javac -encoding UTF-8 Hello.java | 给 shell 的编译命令 | 复制到终端执行 |
你好,Java! | 程序运行后写到标准输出(stdout)的文字 | 不复制,只用来对照 |
Java 的第一个硬规则:**一个 .java 文件里最多有一个 public 类,而且这个类的名字必须和文件名完全一致,大小写也不能差。** 下面的类叫 Hello,所以文件必须叫 Hello.java,不能叫 hello.java、Hello.Java 或 Test.java。Linux 区分大小写,这一点和 Windows 不一样。
第二个硬规则:程序从 main 方法开始执行。public static void main(String[] args) 这一行先当成“入口的固定写法”,一个字符都不要少;每个单词的含义后面会逐步展开。
本节没有键盘输入,网页上不会出现 input 块;input 块从 lesson03 开始才有。现在只要分清 shell 命令、Java 源码和程序输出。
$ 提示符;输出和输入示例没有复制按钮。已完成 0 / 7 步
第 1 步:认提示符,确认 JDK 17
先确认自己在哪里、工具是不是 17。source 这一行让当前 shell 临时使用课程提供的 JDK;它只影响这个终端窗口,不会改系统默认设置。
$ source ~/java-practice/env.sh
$ java -version
$ javac -version
$ pwd
java -version 和 javac -version 都应出现 17.x(例如 17.0.20.1)。java -version 把版本写到标准错误(stderr),这是正常现象,不是报错。pwd 显示当前工作目录,刚登录时通常是 /root 或 /home/你的用户名。
如果 source 提示找不到文件,说明课程环境还没准备好;回 setup.html 按顺序做,不要用 apt install 之类的方法自己补 JDK。服务器上还有别的服务,装错版本会影响别人。
第 2 步:建 lesson01 目录,先保护旧文件
每个小节用独立目录,避免上一节的 Hello.class 和这一节混在一起。下面的 mkdir -p 表示“目录不存在就建,已经存在也不报错”;cd 进入目录;pwd 再确认一次。
$ mkdir -p ~/java-practice/lesson01
$ cd ~/java-practice/lesson01
$ pwd
$ ls -la
$ if [ -e Hello.java ]; then mv Hello.java "Hello.java.bak.$(date +%s)"; fi
最后一行是保护动作,拆开看:
[ -e Hello.java ]:测试Hello.java是否存在;mv Hello.java "Hello.java.bak.$(date +%s)":存在就改名,$(date +%s)会插入当前时间的秒数,所以备份名不会重复;- 双引号把文件名包住,避免空格或特殊字符出问题;
- 文件不存在时,
if什么也不做。
这里出现的是 **shell 的 if**,不是 Java 的 if;本单元不教 Java 分支,看到它不用紧张。ls -la 现在应该只看到 .、..,或者你以前留下的备份;如果看到 Hello.java 已经被改名成 Hello.java.bak.数字,说明保护生效了,旧内容还在。
第 3 步:用 nano 写下 Hello.java
nano 是 Linux 上最简单的文本编辑器。它必须单独占一个命令块:如果把 nano 和后面的 javac 放在同一块里复制,编辑器会“吞掉”后面的命令,看起来像卡住。
$ nano Hello.java
打开后,把下面这一整份内容粘贴进去。粘贴完按 Ctrl+O,屏幕底部会问文件名,直接按 Enter 确认;再按 Ctrl+X 退出。记不住就念一遍:Ctrl+O、Enter、Ctrl+X。如果服务器没有 nano,可以用 vi Hello.java:按 i 进入插入模式,粘贴后按 Esc,输入 :wq 再回车。
public class Hello {
public static void main(String[] args) {
System.out.println("你好,Java!");
}
}
逐行认识这份源码:
public class Hello:声明一个叫Hello的类。class是 Java 的“类”关键字,现在把它当成程序的外壳;public表示这个类可以被外部启动。- 两个花括号
{和}:包住类的内容。花括号必须成对,少一个就会编译失败。 public static void main(String[] args):程序的入口方法。main这个名字和参数写法是 Java 规定的,先照写。System.out.println("你好,Java!"):把双引号里的文字打印到屏幕,并换一行。System是 Java 自带的系统类,out是输出通道,println是“打印一行”。- 行末的分号
;:Java 语句的结束标记,不能省。 - 缩进只是为了人看,Java 不靠缩进判断层次;但保持缩进能让错误更容易发现。
源码里的标点必须是英文半角:();{}" 这些字符。中文引号 “”、中文分号 ; 会让编译器看不懂。字符串里面的中文是内容,可以正常写。
第 4 步:检查文件,编译成 Hello.class
先看文件在不在、编码是不是 UTF-8,再编译。javac 是 Java 编译器,它读 .java 源文件,生成 .class 字节码文件;字节码是给 Java 虚拟机(JVM)执行的中间格式,不是给人直接读的。
$ ls -l Hello.java
$ file Hello.java
$ javac -encoding UTF-8 Hello.java
$ ls -l Hello.class
file Hello.java 一般会显示 UTF-8 text 或 Unicode text 之类;如果显示别的编码,先回编辑器确认保存格式。javac 成功时不会打印“成功”两个字,这是命令行工具的常见习惯:没有输出通常就是成功。ls -l Hello.class 能看到文件,才说明编译真的产出了结果。
-encoding UTF-8 明确告诉编译器按 UTF-8 读源码。UTF-8 是一种把字符存成字节的编码;源文件里有中文时建议每次都写,换一台机器或换一个 locale,少了它就可能出现乱码或“不可映射字符”。
第 5 步:运行,分清类名和文件名
编译成功后,用 java 启动类。注意这里写的是类名 Hello,不是文件名 Hello.java,也不是 Hello.class。
$ java Hello
屏幕上应该出现:
你好,Java!
java Hello 会在当前目录(默认 classpath)找 Hello.class,然后从 main 方法开始执行。classpath 是 JVM 查找 .class 的路径列表,默认包含当前目录。System.out.println 写出的内容就是标准输出(stdout),也就是上面 output 块里的那一行。网页把 output 标成只读,是因为它是程序结果,不是你要输入的命令。
如果 java Hello 报“找不到或无法加载主类”,先别改代码,按顺序检查:pwd 是不是 ~/java-practice/lesson01、ls -l Hello.class 在不在、javac 那一步有没有报错。运行和编译是两层,不要把两类错误混在一起。
第 6 步:故意错一次,看见“类名=文件名”
现在做一个安全的实验:复制一份 Hello.java 叫 WrongName.java,但里面的 public class 仍然是 Hello。这会让编译器发现“public 类名和文件名不一致”。
$ if [ -e WrongName.java ]; then mv WrongName.java "WrongName.java.bak.$(date +%s)"; fi
$ cp Hello.java WrongName.java
$ javac -encoding UTF-8 WrongName.java
$ rm -f WrongName.java
cp 只复制,不改 Hello.java;rm -f WrongName.java 只删掉这次实验用的副本,不会碰你的 Hello.java 和 Hello.class。最后一行在 javac 报错后仍会执行,因为 shell 默认会继续跑后面的命令;如果你是一行一行手敲,看到错误后再敲 rm 也一样。
注意 预期会看到编译错误
英文环境通常会看到类似 class Hello is public, should be declared in a file named Hello.java 的片段;中文环境会显示对应的翻译,但同样会指出 Hello.java。不同 JDK 补丁和 locale 的完整措辞可能不同,不要逐字背报错,只要抓住“public 类名”和“文件名”这两个关键词。
这个实验证明:类名和文件名一致不是“建议”,而是 public 类的硬性要求。实验完 ls -l 应仍能看到 Hello.java 和 Hello.class;如果 WrongName.java 还在,说明 rm 那行没执行,手动删掉这个临时副本即可。
第 7 步:改一行,输出两行,再跑一次
最后改一次文件:把 Hello.java 的内容整份替换成下面这份,而不是只在末尾追加。整份替换可以避免花括号或分号漏掉。
$ nano Hello.java
打开后先删掉旧内容,再粘贴下面整份源码;保存方式仍是 Ctrl+O、Enter、Ctrl+X。
public class Hello {
public static void main(String[] args) {
System.out.println("你好,Java!");
System.out.println("我的第一个类叫 Hello。");
}
}
然后用单独的命令块重新编译、运行:
$ javac -encoding UTF-8 Hello.java
$ java Hello
输出应该是两行:
你好,Java!
我的第一个类叫 Hello。
每改一次源码都要重新 javac,再 java;只运行旧的 .class 不会看到新文字。反过来,如果只改文字不改类名,Hello.class 会被新编译结果覆盖,这是正常的构建产物更新。
说明 重复执行时备份会怎样
如果你再次执行第 2 步最后那行保护命令,Hello.java 会被改名成 Hello.java.bak.<秒数>,然后第 3 步的 nano Hello.java 会新建一个同名文件。旧内容没有丢,只是在备份文件里。用 ls -lt 可以按时间查看这些备份;不需要它们时可以只删除自己确认过的 .bak 文件,不要动别人的目录。
常见错误
- **现象:
class Hello is public, should be declared in a file named Hello.java。原因:public class后面的类名和文件名不一致,或大小写不同。先检查什么:ls -l看文件名,head -n 3 Hello.java看类名,两边逐字对照。** - **现象:
javac: file not found: Hello.java(中文“找不到文件”)。原因:当前目录不是~/java-practice/lesson01,或文件没保存成功。先检查什么:pwd、ls -l;不在目录里就cd ~/java-practice/lesson01,文件不在就回第 3 步重写。** - **现象:
Error: Could not find or load main class Hello(中文“找不到或无法加载主类”)。原因:没有Hello.class,或java在别的目录运行,或把类名写成了文件名。先检查什么:ls -l Hello.class,确认javac成功,并在同一目录执行java Hello。** - **现象:
error: unmappable character (0x...) for encoding,或中文变成问号/乱码。原因:源文件不是 UTF-8,或终端 locale 与文件编码不一致(locale 是系统的语言/编码环境)。先检查什么:file Hello.java、locale;本课程统一 UTF-8,编译时带-encoding UTF-8。** - **现象:
javac: command not found或java: command not found。原因:当前 shell 没有加载课程 JDK,PATH 里找不到命令。先检查什么:source ~/java-practice/env.sh,再跑java -version和javac -version;仍不是 17.x 就停下报告环境问题,不要自己装 JDK。** - **现象:
reached end of file while parsing。原因:花括号或分号漏了,常见于只粘贴了方法的一部分。先检查什么:从public class到最后一个}数一遍花括号,确认每条语句末尾有分号。**
练习
- 把第二行文字改成
我在 lesson01 目录里。,重新编译运行,写出两行输出。 - 先预测:如果把文件另存为全小写的
hello.java,里面的public class Hello不变,执行javac -encoding UTF-8 hello.java会成功吗?为什么?预测后再实际试一次,最后把文件名改回Hello.java。
先自己改、先写预测,再展开答案。
提示 卡住时看这里
第 1 题只改 println 双引号里的文字,类名和文件名都保持 Hello。第 2 题看 public class 后面的名字和文件名是否逐字一致;Linux 区分大小写,hello.java 和 Hello.java 是两个不同的文件名。
参考答案 对答案
第 1 题改好的整份源码:
public class Hello {
public static void main(String[] args) {
System.out.println("你好,Java!");
System.out.println("我在 lesson01 目录里。");
}
}
运行后是两行:
你好,Java!
我在 lesson01 目录里。
第 2 题:不会成功。文件叫 hello.java,里面的 public class 叫 Hello,大小写不一致,编译器仍会报“public 类应声明在 Hello.java 中”。把文件名改回 Hello.java(或把类名改成 hello 并同步改文件名)才能通过。这个练习也说明:在 Linux 上,文件名大小写不是小事。
做到这里
做到这里今天可以停。你已经会:确认 JDK、建独立目录、用 nano 新建文件、用 javac -encoding UTF-8 编译、用 java 类名 运行,也能在报错时先判断是“文件名/类名”“目录”“编译”还是“运行”哪一层的问题。
本节不需要提前学 if、循环、数组或自定义方法。下一节 lesson02 才引入变量和类型;本节写的 Hello 只是程序外壳,不是第四单元那种“自己设计的类”。
来源 来源与改编说明(非官方)
主题参考:黑马程序员 Java 入门到起飞(BV17F411T7Ao,2022-04-27)P001–P016 的 Java 入门、JDK、HelloWorld 主题。本站为独立原创讲解,非官方站点/教材,未获授权,不逐字复刻视频或讲义;示例、错误清单与练习为原创补充。详见 source/SOURCE.md。
公开核对入口:
- 课程页:https://www.bilibili.com/video/BV17F411T7Ao/
- Oracle JDK 17
javac:https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html - Oracle JDK 17
java:https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html
说明:本站是个人学习整理,不是官方教材;不包含原课程的书本、视频或受版权保护的整本内容。原课程与商标归其权利人。