第一篇文章已经运行了 HealthController,其中出现了变量、字符串、方法调用和 return。先运行一个结果再回头拆解语法,可以知道这些符号最终解决什么问题;但在继续学习类与 Spring 之前,还需要把 Java 程序最基本的执行规则补齐。
本章使用独立的 JavaSyntaxDemo 练习,不修改已经能够运行的心跳接口。代码只依赖 JDK,可以直接使用 javac 编译并通过 java 运行。
1. 语句与代码块
Java 程序由类型、方法和语句组成。下面的 main 方法是程序入口,JVM 从方法体的第一条语句开始顺序执行:
1package cn.usehook.spring.basics;23public class JavaSyntaxDemo {45public static void main(String[] args) {6String service = "spring";7System.out.println(service);8}9}
这里先把 package、class 和 main 当作运行代码所需的外围结构,只观察 main 花括号中的两条语句。类与对象、包和访问控制会在后面两篇逐项展开,不需要现在一次记住全部声明。
大多数语句以分号结束,花括号表示一个代码块。缩进不会改变 Java 语义,但统一使用四个空格可以直接看出代码所属范围。// 后面是单行注释,/* ... */ 是多行注释;注释应该解释必要的原因,而不是重复代码本身。
变量只在声明它的代码块及其子代码块中可见:
1if (service != null) {2String message = "checking " + service;3System.out.println(message);4}56// 这里不能访问 message
这就是局部作用域。缩小变量的作用域可以减少无关代码修改它的机会,也让读者更容易判断一个值在哪里有效。
2. 变量与数据类型
变量声明由类型、名称和可选初始值组成:
1String service = "spring";2int attempts = 3;3boolean available = true;
Java 是静态类型语言。attempts 被声明为 int 后,只能接收兼容的整数值,不能在运行过程中变成字符串。编译器会在程序运行前检查大部分类型错误。
Java 有八种基本类型:
| 类型 | 表达的数据 | 示例 |
|---|---|---|
byte、short、int、long | 不同范围的整数 | int attempts = 3 |
float、double | 浮点数 | double elapsed = 12.5 |
char | 单个 UTF-16 代码单元 | char level = 'A' |
boolean | 真或假 | boolean available = true |
整数业务值通常先使用 int,需要更大范围时使用 long。浮点数存在二进制表示误差,不适合直接表示要求精确的小数金额;这类数据通常使用 BigDecimal。char 使用单引号,String 使用双引号,而且 String 是引用类型,不是基本类型。
每种基本类型都有包装类型,例如 int 对应 Integer,boolean 对应 Boolean。泛型集合不能写 List<int>,需要使用 List<Integer>;自动装箱可以在很多场景下完成二者转换,但包装类型还可能是 null。
引用类型变量保存的是对象引用。引用可以暂时没有指向对象,此时值是 null:
1String service = null;23if (service != null) {4System.out.println(service.toUpperCase());5}
对 null 调用方法会抛出 NullPointerException。不要把 null 当作所有缺省值的通用表达;字段是否允许缺失应该在方法契约、校验规则或后面学习的 Optional 中明确说明。
局部变量的类型足够明显时可以使用 var:
1var service = "spring";
var 仍然会在编译期推断出固定的 String 类型,不会把 Java 变成动态类型语言。它只能用于有初始值的局部变量,不能用于字段、方法参数或返回类型。
3. 表达式与运算符
表达式计算出一个值。赋值号右侧的 attempts + 1 先计算,再把结果写回变量:
1int attempts = 2;2attempts = attempts + 1;34boolean retryable = attempts < 5;5boolean healthy = retryable && attempts > 0;
常用运算符可以分为几类:
| 类型 | 运算符 | 作用 |
|---|---|---|
| 算术 | +、-、*、/、% | 数值计算与求余 |
| 比较 | ==、!=、>、>=、<、<= | 得到 boolean 结果 |
| 逻辑 | &&、` | |
| 赋值 | =、+=、-= | 保存计算结果 |
&& 和 || 使用短路计算。left && right 在左侧为 false 时不会计算右侧,因此下面的空值检查是安全的:
1boolean usable = service != null && !service.isBlank();
基本数值可以使用 == 比较值,引用类型的 == 比较是否指向同一个对象。字符串内容应该使用 equals(),或者把已知非空常量放在前面:
1boolean up = "UP".equals(status);
String 的 + 可以连接文本,但复杂日志不要提前拼接字符串;日志章节会使用参数化日志,让框架按日志级别决定是否格式化内容。
4. 条件分支
if 根据布尔条件决定是否执行代码块:
1if (service == null || service.isBlank()) {2System.out.println("service is required");3} else {4System.out.println("checking " + service);5}
条件较多时可以继续使用 else if,但嵌套层级过深通常意味着一个方法承担了太多判断。能够提前结束的方法可以先处理非法条件,再执行正常路径。
已知选项之间的分支适合使用 switch。Java 21 的 switch 表达式可以直接产生值:
1String message = switch (status) {2case "UP" -> "service is available";3case "DOWN" -> "service is unavailable";4default -> "service status is unknown";5};
箭头右侧是对应分支的结果,default 处理没有明确列出的值。后面的分页文章会先把外部排序选项转换成枚举,再使用 switch 选择预先写好的 SQL 排序片段。
5. 循环与数组
数组保存一组相同类型、长度固定的元素:
1String[] services = {"spring", "postgresql", "redis"};
数组下标从 0 开始,services[0] 是第一个元素。访问小于 0 或大于等于数组长度的下标会抛出 ArrayIndexOutOfBoundsException。
只需要依次读取元素时,增强 for 循环最清楚:
1for (String current : services) {2System.out.println(current);3}
需要下标时可以使用普通 for:
1for (int index = 0; index < services.length; index++) {2System.out.println(index + ": " + services[index]);3}
while 会在每次执行前判断条件,适合循环次数不能提前确定的场景:
1int attempts = 0;23while (attempts < 3) {4attempts++;5System.out.println("attempt " + attempts);6}
循环必须让结束条件最终能够改变,否则会形成无限循环。业务数据经常需要动态增加和删除,后面的集合章节会使用 List 和 Map 替代固定长度数组。
6. 方法
方法把一段操作命名,并通过参数接收输入、通过返回值交付结果:
01package cn.usehook.spring.basics;0203public class JavaSyntaxDemo {0405public static void main(String[] args) {06String result = normalizeService(" PostgreSQL ");07System.out.println(result);08}0910static String normalizeService(String service) {11if (service == null || service.isBlank()) {12throw new IllegalArgumentException("service is required");13}1415return service.trim();16}17}
normalizeService 的返回类型是 String,参数也是 String。执行到 return 后,当前方法结束并把结果交给调用方。返回类型为 void 的方法不返回结果;它仍然可以使用单独的 return; 提前结束。
同一个类可以声明名称相同但参数列表不同的方法,这叫方法重载:
1static String healthMessage() {2return healthMessage("spring");3}45static String healthMessage(String service) {6return service + " is UP";7}
编译器根据参数数量和类型选择方法。仅改变返回类型不能形成重载,因为调用位置可能无法据此判断应该选择哪个方法。
编译并运行练习类:
1./mvnw compile2java -cp target/classes cn.usehook.spring.basics.JavaSyntaxDemo
变量让程序保存值,分支和循环控制语句执行顺序,方法把操作组织成可以复用的单元。但这些局部数据会在方法结束后离开作用域。下一篇通过类与对象把状态和行为组合成自己的类型,再讨论对象怎样创建、保存和比较。