语言基础
Java 语言基础
本篇是《Java 从入门到精通》第 02 篇,覆盖 Java 的数据类型、运算符、流程控制、数组、字符串。Java 是静态强类型语言,类型系统是它的根基,务必吃透。
1. 基本语法要素
1.1 程序结构
Java 一切代码都在类中,程序入口是 main 方法:
public class Demo {
public static void main(String[] args) {
System.out.println("hi");
}
}
- 每条语句以
;结尾。 - 代码块用
{}包裹。 - Java 区分大小写,
Main与main是不同的标识符。
1.2 注释
// 单行注释
/*
多行注释
可跨行
*/
/**
* 文档注释(Javadoc)
* 可被 javadoc 工具提取生成 API 文档
* @param args 命令行参数
*/
public static void main(String[] args) {}
1.3 标识符与关键字
标识符命名规则:字母、数字、下划线、$,不能以数字开头,不能是关键字。约定:
- 类名:PascalCase(
HelloWorld)。 - 方法/变量名:camelCase(
firstName)。 - 常量:UPPER_SNAKE_CASE(
MAX_VALUE)。 - 包名:全小写(
com.lfange.util)。
Java 有 50 个关键字(含保留字),如 class、public、static、void、if、new、return 等。注意 true/false/null 是字面量不是关键字,但仍不能作标识符。
2. 数据类型
Java 类型分两大类:基本类型(primitive) 和 引用类型(reference)。
2.1 八种基本类型
| 类型 | 大小 | 默认值 | 范围 | 用途 |
|---|---|---|---|---|
byte | 1 字节 | 0 | -128 ~ 127 | 节省内存、二进制流 |
short | 2 字节 | 0 | -32768 ~ 32767 | 罕用 |
int | 4 字节 | 0 | -2³¹ ~ 2³¹-1 | 最常用整数 |
long | 8 字节 | 0L | -2⁶³ ~ 2⁶³-1 | 大整数、时间戳 |
float | 4 字节 | 0.0f | IEEE 754 单精度 | 罕用(精度不够) |
double | 8 字节 | 0.0d | IEEE 754 双精度 | 最常用浮点 |
char | 2 字节 | '�' | 0 ~ 65535 | 单个 Unicode 字符 |
boolean | 1 位(JVM 实现) | false | true / false | 逻辑 |
int a = 100;
long b = 100L; // long 字面量加 L
double pi = 3.14159;
float f = 3.14f; // float 字面量加 f
char c = 'A'; // 单引号,单个字符
boolean flag = true;
// 数字字面量增强:下划线分隔(编译期忽略)
int million = 1_000_000;
long nanos = 1_000_000_000L;
int binary = 0b1010; // 二进制
int hex = 0xFF; // 十六进制
浮点数精度陷阱
double/float 是 IEEE 754 浮点,无法精确表示十进制小数:
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3); // false
金额计算必须用 java.math.BigDecimal(第 05 篇会详述)。
2.2 引用类型
除了 8 种基本类型,其余都是引用类型:类(String)、接口、数组、枚举等。引用类型变量存的是对象的地址,而非对象本身。
String s1 = "hello"; // s1 存的是字符串对象的引用
String s2 = s1; // s2 与 s1 指向同一对象
int[] arr = {1, 2, 3}; // arr 存的是数组对象的引用
基本类型 vs 引用类型
- 基本类型变量直接存值,赋值是值拷贝;作为方法参数是值传递。
- 引用类型变量存地址,赋值拷贝的是地址(共享对象);方法参数传的也是地址的副本,但副本指向同一对象,所以方法内可修改对象内部状态。
2.3 包装类与自动装箱
每种基本类型都有对应的包装类(位于 java.lang):Byte/Short/Integer/Long/Float/Double/Character/Boolean。
Integer i1 = 100; // 自动装箱:int -> Integer(实际 Integer.valueOf(100))
int i2 = i1; // 自动拆箱:Integer -> int(实际 i1.intValue())
// 集合只能装对象,所以需要包装类
List<Integer> nums = List.of(1, 2, 3); // 装箱自动发生
Integer 缓存陷阱
Integer 对 -128 ~ 127 有缓存(IntegerCache),此范围内相同值返回同一对象:
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true -- 缓存命中,同一对象
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false -- 超出缓存,不同对象
System.out.println(c.equals(d)); // true -- 比较值
比较包装类始终用 .equals(),绝不用 ==。
自动装箱的性能
自动装箱在循环中会创建大量临时对象:
Integer sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i; // 每次都拆箱+装箱,产生约 100 万个临时 Integer
}
性能敏感场景用基本类型 int。
3. 变量、常量与类型转换
3.1 局部变量类型推断 var(Java 10+)
var 让编译器推断局部变量类型,仍是静态类型(编译期确定,非 JS 的 var):
var name = "Alice"; // 推断为 String
var count = 10; // 推断为 int
var list = new ArrayList<String>(); // 推断为 ArrayList<String>
// var 只能用于局部变量,不能用于字段、方法参数、返回值
何时用 var
类型明显时用 var 提升可读性(var stream = Files.lines(path));类型不明显时显式写出(var result = process(); 不如 OrderResult result = process();)。
3.2 常量 final
final int MAX_RETRY = 3; // 基本类型常量,值不可变
final List<String> NAMES = new ArrayList<>();
NAMES.add("Tom"); // 合法:引用不变,内容可变
// NAMES = new ArrayList<>(); // 编译错误:引用本身不可重新赋值
final 修饰引用类型时,锁定的是引用(不能再指向别的对象),对象内部仍可修改。类级常量通常加 static final。
3.3 类型转换
自动转换(小 -> 大,无精度损失):
int i = 100;
long l = i; // int -> long,自动
double d = l; // long -> double,自动
转换方向:byte -> short -> int -> long -> float -> double,char -> int。
强制转换(大 -> 小,可能丢精度,需显式):
double d = 3.99;
int i = (int) d; // 3,直接截断小数部分
long l = 100000L;
int j = (int) l; // 100000,但超出 int 范围会溢出得到错误结果
强转溢出
int x = (int) 3000000000L; // 3000000000 超出 int 范围,结果为 -1294967296
大转小前应检查范围。
4. 运算符
4.1 算术运算符
int a = 10, b = 3;
a + b // 13
a - b // 7
a * b // 30
a / b // 3 整数除法,截断小数
a % b // 1 取余
a++ // 后置自增
++a // 前置自增
整数除法
System.out.println(10 / 3); // 3,不是 3.333
System.out.println(10.0 / 3); // 3.333...,有一个操作数是浮点才按浮点算
4.2 关系与逻辑运算符
// 关系:== != > < >= <=
// 逻辑:&&(短路与) ||(短路或) !(非)
int age = 20;
if (age >= 18 && age <= 60) { ... } // 短路:age<18 时不再算右边
// 短路示例:避免空指针
String s = null;
if (s != null && s.length() > 0) { ... } // s 为 null 时不会调用 s.length()
&& 和 || 是短路的:左边能确定结果就不算右边。& 和 | 是非短路版(左右都算),也用于位运算。
4.3 位运算符
int a = 0b1100; // 12
int b = 0b1010; // 10
a & b // 0b1000 = 8 按位与
a | b // 0b1110 = 14 按位或
a ^ b // 0b0110 = 6 按位异或
~a // 按位取反
a << 2 // 48 左移(乘 2^n)
a >> 1 // 6 算术右移(带符号)
a >>> 1 // 无符号右移
位运算常用于权限位标志、状态掩码、高性能计算。
4.4 三元运算符
int a = 10, b = 20;
int max = a > b ? a : b; // 20
4.5 instanceof 与模式匹配
Object obj = "hello";
if (obj instanceof String s) { // Java 16+:模式匹配,同时声明变量 s
System.out.println(s.length()); // 5
}
// 等价于旧写法:
// if (obj instanceof String) {
// String s = (String) obj;
// ...
// }
5. 流程控制
5.1 if-else
int score = 85;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else if (score >= 60) {
System.out.println("C");
} else {
System.out.println("D");
}
5.2 switch 语句与表达式
传统 switch 语句(易忘 break,存在穿透问题):
int day = 3;
switch (day) {
case 1:
System.out.println("周一");
break;
case 2:
System.out.println("周二");
break;
case 3:
System.out.println("周三");
break;
default:
System.out.println("其他");
}
switch 表达式(Java 14+,推荐):用 -> 箭头,无穿透,可直接返回值:
int day = 3;
String name = switch (day) {
case 1 -> "周一";
case 2 -> "周二";
case 3 -> "周三";
case 4, 5 -> "工作日";
default -> "其他";
};
System.out.println(name); // 周三
多行 case 用 yield 返回:
String type = switch (day) {
case 1, 2, 3, 4, 5 -> {
System.out.println("处理工作日");
yield "工作日";
}
default -> "周末";
};
5.3 for 循环
// 传统 for
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
// 增强 for(for-each),遍历数组/集合
int[] nums = {1, 2, 3, 4, 5};
for (int n : nums) {
System.out.println(n);
}
5.4 while 与 do-while
int i = 0;
while (i < 5) { // 先判断后执行
System.out.println(i);
i++;
}
int j = 0;
do { // 先执行后判断,至少执行一次
System.out.println(j);
j++;
} while (j < 5);
5.5 break / continue / 标签
// break:跳出循环
for (int i = 0; i < 10; i++) {
if (i == 5) break;
System.out.println(i); // 0~4
}
// continue:跳过本次,进入下次
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue;
System.out.println(i); // 1,3,5,7,9
}
// 带标签的 break:跳出多层循环
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) break outer;
System.out.println(i + "," + j);
}
}
6. 数组
数组是固定长度、同类型元素的容器,本质是对象。
6.1 声明与初始化
int[] arr1 = {1, 2, 3, 4, 5}; // 静态初始化
int[] arr2 = new int[5]; // 动态初始化,默认值 0
String[] arr3 = new String[3]; // 默认值 null
int[] arr4 = new int[]{10, 20, 30}; // 完整写法
6.2 访问与长度
int[] arr = {10, 20, 30, 40, 50};
System.out.println(arr[0]); // 10
System.out.println(arr.length); // 5(length 是属性,非方法)
arr[0] = 99;
// arr[5] = 1; // ArrayIndexOutOfBoundsException
6.3 遍历
int[] arr = {1, 2, 3};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
for (int n : arr) { // for-each
System.out.println(n);
}
6.4 多维数组
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
System.out.println(matrix[1][2]); // 6
// 不规则数组(每行长度不同)
int[][] ragged = new int[3][];
ragged[0] = new int[]{1, 2};
ragged[1] = new int[]{3, 4, 5};
ragged[2] = new int[]{6};
6.5 数组工具类 Arrays
java.util.Arrays 提供常用操作:
import java.util.Arrays;
int[] arr = {5, 2, 8, 1, 9};
Arrays.sort(arr); // 排序:[1, 2, 5, 8, 9]
System.out.println(Arrays.toString(arr)); // [1, 2, 5, 8, 9]
int[] copy = Arrays.copyOf(arr, 3); // 拷贝前 3 个:[1, 2, 5]
int[] range = Arrays.copyOfRange(arr, 1, 4); // 拷贝 [1,4):[2, 5, 8]
int idx = Arrays.binarySearch(arr, 5); // 二分查找(需先排序),返回索引
Arrays.fill(arr, 0); // 全部填 0
// 比较
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(a == b); // false,比较引用
System.out.println(Arrays.equals(a, b)); // true,比较内容
数组 vs 集合
数组长度固定、类型协变、性能高但功能少。需要动态增删、丰富 API 时用集合(ArrayList 等,见第 05 篇)。
7. 字符串
String 是 Java 最常用的类,理解它的不可变性至关重要。
7.1 不可变性
String 对象不可变——一旦创建,内容不能改变。所有"修改"操作都返回新对象:
String s = "hello";
s.concat(" world"); // 返回新对象 "hello world",s 不变
System.out.println(s); // hello
s = s.toUpperCase(); // s 重新指向新对象 "HELLO"
String 不可变的设计原因
- 线程安全:不可变对象天生线程安全,可自由共享。
- hashCode 缓存:值不变,哈希可缓存,作 HashMap 键更高效。
- 字符串常量池:不可变才能安全共享同一字面量。
- 安全性:类加载器、网络连接的字符串参数不会被意外篡改。
7.2 字符串常量池
String s1 = "hello"; // 字面量,存于常量池
String s2 = "hello"; // 复用常量池同一对象
String s3 = new String("hello"); // 堆中新对象
String s4 = s3.intern(); // 放入/复用常量池
System.out.println(s1 == s2); // true -- 同一常量池对象
System.out.println(s1 == s3); // false -- s3 是堆中新对象
System.out.println(s1 == s4); // true -- intern 后指向常量池
System.out.println(s1.equals(s3)); // true -- 内容相同
比较字符串用 equals
== 比较引用地址,.equals() 比较内容。绝大多数业务场景要比较内容,永远用 .equals()。
7.3 常用方法
String s = "Hello, World";
s.length(); // 12
s.charAt(0); // 'H'
s.substring(7); // "World"
s.substring(0, 5); // "Hello"
s.indexOf("World"); // 7
s.toLowerCase(); // "hello, world"
s.toUpperCase(); // "HELLO, WORLD"
s.trim(); // 去首尾空白
s.replace("World", "Java"); // "Hello, Java"
s.split(", "); // ["Hello", "World"]
s.startsWith("Hello"); // true
s.contains("World"); // true
s.isEmpty(); // false
String.join("-", "a", "b", "c"); // "a-b-c"
7.4 StringBuilder(可变字符串)
字符串频繁拼接时,+ 会产生大量临时对象。StringBuilder 是可变序列,高效拼接:
// 低效:循环中用 +,每次新建 String
String s = "";
for (int i = 0; i < 1000; i++) {
s += i; // 每次编译后等价于 s = new StringBuilder(s).append(i).toString();
}
// 高效:用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String result = sb.toString();
StringBuilder 方法:append()、insert()、delete()、reverse()、length()、charAt()、setCharAt()。
StringBuffer vs StringBuilder
StringBuilder:单线程,不保证线程安全,性能更好(推荐)。StringBuffer:线程安全(方法加synchronized),性能较差,基本已被StringBuilder取代。
7.5 文本块(Java 15+)
多行字符串用 """ 包裹,避免转义地狱:
// 旧写法
String json = "{\n" +
" \"name\": \"Tom\",\n" +
" \"age\": 18\n" +
"}";
// 文本块
String json = """
{
"name": "Tom",
"age": 18
}
""";
8. 输入与输出
8.1 输出
System.out.println("带换行");
System.out.print("不带换行");
System.out.printf("格式化:%s 年龄 %d,身高 %.2f%n", "Tom", 18, 1.75);
// 格式化:Tom 年龄 18,身高 1.75
// 常用格式符:%s 字符串 %d 整数 %f 浮点 %.nf 保留n位 %n 换行 %x 十六进制
8.2 输入:Scanner
import java.util.Scanner;
Scanner scanner = new Scanner(System.in);
System.out.print("输入姓名:");
String name = scanner.nextLine(); // 读一行
System.out.print("输入年龄:");
int age = scanner.nextInt(); // 读整数
System.out.println("你好," + name + "," + age + " 岁");
scanner.close();
8.3 命令行参数
通过 main 的 args 接收(见 01 篇)。
9. 综合案例:简易计算器
综合运用流程控制、方法、Scanner:
import java.util.Scanner;
public class Calc {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("输入表达式(如 3 + 4),输入 q 退出:");
String line = sc.nextLine().trim();
if (line.equals("q")) {
System.out.println("再见");
break;
}
String[] parts = line.split("\\s+");
if (parts.length != 3) {
System.out.println("格式错误,请重新输入");
continue;
}
try {
double a = Double.parseDouble(parts[0]);
double b = Double.parseDouble(parts[2]);
String op = parts[1];
double result = switch (op) {
case "+" -> a + b;
case "-" -> a - b;
case "*" -> a * b;
case "/" -> b == 0 ? Double.NaN : a / b;
default -> throw new IllegalArgumentException("未知运算符: " + op);
};
System.out.printf("= %.4f%n", result);
} catch (NumberFormatException e) {
System.out.println("数字格式错误");
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
sc.close();
}
}
运行示例:
java Calc.java
# 输入表达式(如 3 + 4),输入 q 退出:3 + 4
# = 4.0000
# 输入表达式(如 3 + 4),输入 q 退出:10 / 4
# = 2.5000
# 输入表达式(如 3 + 4),输入 q 退出:q
# 再见
语法预告
这里用到了 try-catch 异常处理和 switch 表达式。异常处理将在第 06 篇系统讲解。
小结
| 主题 | 关键点 |
|---|---|
| 数据类型 | 8 种基本类型 + 引用类型;基本类型值传递,引用类型传地址副本 |
| 包装类 | 自动装箱/拆箱;Integer 缓存 -128~127;比较用 equals |
| 变量 | var 类型推断;final 常量;类型转换(自动/强制) |
| 运算符 | 算术、关系、逻辑(短路)、位、三元、instanceof 模式匹配 |
| 流程控制 | if-else、switch 表达式(->、yield)、for、while、break/continue |
| 数组 | 固定长度对象;length 属性;Arrays 工具类 |
| 字符串 | String 不可变;常量池;用 equals 比较;StringBuilder 高效拼接;文本块 |
| IO | System.out.printf、Scanner、命令行 args |
下一篇我们将进入 Java 的灵魂——面向对象编程:类、对象、封装、构造方法、this/static/final。
下一篇预告
《03 - 面向对象基础》:从"万物皆对象"出发,讲清类与对象、封装、构造方法、this/static/final、包与访问控制,为继承多态打基础。
