Java 语言基础
Java 语言基础
模块 2 · 课次 04 + 实验1 / 教材第 2 章(2.1 语法基础、2.2 运算符与表达式、2.3 流程控制、2.4 数组、2.5 字符串) 目标:掌握标识符与关键字;数据类型、变量与常量、类型转换与作用域;运算符与优先级;分支/循环与
break/continue/return;一维与多维数组;String/StringBuffer;并初步建立「程序 = 数据结构 + 算法」的抽象认识,为第 3 章面向对象铺垫。
〇、引入:程序 = 数据结构 + 算法
- 程序 = 数据结构 + 算法:数据结构描述「处理什么数据」,算法描述「怎么处理」。
- 数据结构是对现实世界的抽象:把现实事物的属性与行为,抽象成计算机能处理的数据组织方式。
- 由此过渡到类与对象的抽象映射(第 3 章正式展开):
| 现实世界 | 程序世界 |
|---|---|
| 具体事物的属性及行为 | 对象 |
| 同类事物共同特性(属性、行为)的抽象 | 类 |
贯穿示例:
素材/Erase-JAVA/的com.zmofun.erase.protogame.ErasGame类,就是把「擦除小游戏」这一类事物(属性:棋盘、分数;行为:擦除、计分、过关)抽象成类,再实例化为对象——下文会持续引用它的真实代码。
一、2.1 语法基础
标识符与关键字
- 标识符命名规则:字母、下划线
_、$开头,后接字母/数字/_;区分大小写(score与Score是两个变量)。 - 关键字被语言占用,不能作标识符:
class、public、static、void、int、final……
数据类型
Java 是强类型语言:每个变量都有确定的类型;各类型占用固定内存长度,与具体软硬件平台无关;每种类型有缺省值,变量取值总是确定的。
| 类别 | 类型 | 大小 | 默认值(成员变量) | 示例 |
|---|---|---|---|---|
| 字符型 | char | 2 字节 | '\u0000' | 'A'、'中' |
| 布尔型 | boolean | 1 位 | false | true / false |
| 整型 | byte | 1 字节 | 0 | (byte) 127 |
| 整型 | short | 2 字节 | 0 | (short) 30000 |
| 整型 | int | 4 字节 | 0 | 100(整型字面量默认) |
| 整型 | long | 8 字节 | 0L | 9223372036854775807L |
| 浮点型 | float | 4 字节 | 0.0f | 3.14f |
| 浮点型 | double | 8 字节 | 0.0d | 3.14159(小数默认) |
| 引用型 | String 等 | 对象头+内容 | null | "hello" |
类型转换:
- 自动提升(widening):小 → 大,如
byte → int → double,安全。 - 强制转换(narrowing):大 → 小必须显式
(int) d,可能丢精度/溢出(本章难点之一)。 char与整型可互转:char c = 65;等价于'A'。
int a = 10; // int 字面量
long b = 10L; // long 必须加 L
float f = 3.14f; // float 必须加 f
double d = 3.14; // double 默认
byte y = (byte) a; // int → byte 需强转(可能溢出)
交互实验:数据类型速查表(DataTypeTable)
点行查看每个基本类型的范围与默认值;再用下面的「转换检查器」判断两个类型之间能否自动扩宽。
| 类别 | 类型 | 大小 | 默认值(成员变量) | 范围 | 示例 |
|---|---|---|---|---|---|
| 字符型 | char | 2 字节 | '\u0000' | \u0000 (0) ~ \uFFFF (65535) | 'A' |
| 布尔型 | boolean | 1 字节 | false | — | true |
| 整型 | byte | 1 字节 | 0 | -128 ~ 127 | (byte) 127 |
| 整型 | short | 2 字节 | 0 | -32768 ~ 32767 | (short) 30000 |
| 整型 | int | 4 字节 | 0 | -2147483648 ~ 2147483647 | 100 |
| 整型 | long | 8 字节 | 0L | -9223372036854775808L ~ 9223372036854775807L | 9223372036854775807L |
| 浮点型 | float | 4 字节 | 0.0f | ≈ ±1.4E-45 ~ ≈ ±3.4E38 | 3.14f |
| 浮点型 | double | 8 字节 | 0.0d | ≈ ±4.9E-324 ~ ≈ ±1.7E308 | 3.14159 |
int:整型,4 字节;默认值 0;范围 -2147483648 ~ 2147483647
✅ byte → int 自动扩宽:范围变大,安全,编译器自动完成。
自动扩宽链:byte < short < int < long < float < double(char 可扩到 int/long/float/double);反向必须显式强转。
变量与常量、作用域
- 成员变量:定义在类体里,自动有默认值(上表),生命周期随对象。
- 局部变量:定义在方法/语句块内,没有默认值,用前必须初始化。
static:属于类而非某个对象,所有实例共享同一份;如Math.PI、Erase 里的private static final long serialVersionUID。final:一旦赋值不可再改(引用变量是「指向不可变」,对象内容仍可被修改)。
| 作用域 | 可见范围 | 例子 |
|---|---|---|
| 类作用域 | 整个类体(含所有方法) | 成员变量 score |
| 方法作用域 | 单个方法内 | 参数、方法内局部变量 |
| 语句块作用域 | { } 块内 | for (int i = 0; ...) 的 i |
class Game {
static final int MAX_ROWS = 18; // 类级常量:所有对象共享、不可改
int score = 0; // 成员变量,默认 0
void add(int n) {
int local = n; // 局部变量,必须初始化
score += local;
}
}
二、2.2 运算符与表达式
| 类别 | 运算符 | 说明 |
|---|---|---|
| 算术 | + - * / % | % 取余;结果类型随操作数提升 |
| 单目 | + - ~ ! ++ -- | 重点:++/-- 前缀与后缀的区别(见下) |
| 关系 | > < >= <= == != | 结果为 boolean |
| 逻辑 | &&、||、! | 重点:&& 与 &、|| 与 | 的区别(短路求值) |
| 位运算 | &、|、^、~、<<、>> | 以二进制位为单位,操作数与结果为整型;移位运算是难点 |
| 三目 | cond ? a : b | 条件表达式 |
| 复合赋值 | += -= *= /= %= &= |= ^= <<= >>= | a += 1 等价 a = a + 1 |
instanceof | obj instanceof Type | 判断对象是否为某类型的实例(第 3 章常用) |
++/-- 前缀 vs 后缀:
int i = 5;
int a = i++; // 先使用(a=5),再自增 → i=6
int b = ++i; // 先自增(i=8),再使用 → b=8
短路求值(&& vs &):
// && 短路:左边为 false,右边根本不求值
if (a > 0 && b(a) > 1) { ... } // a<=0 时 b(a) 不会被调用
// & 不短路:两边都求值(位运算语义)
移位运算符:<< 左移补 0(×2),>> 右移带符号(÷2),>>> 无符号右移。
优先级从高到低(同层默认左结合,拿不准就加括号):
| 级别 | 运算符 | 结合性 |
|---|---|---|
| 1 | ( )、前缀 ++ -- ! ~、后缀 ++ -- | 右 / 特殊 |
| 2 | * / % | 左结合 |
| 3 | + - | 左结合 |
| 4 | << >> & ^ | | 左结合 |
| 5 | > < >= <= instanceof | 左结合 |
| 6 | == != | 左结合 |
| 7 | && | 左结合(短路) |
| 8 | || | 左结合(短路) |
| 9 | ? : | 右结合 |
| 10 | = += -= *= /= 等赋值 | 右结合 |
注释:// 单行;/* … */ 与 /** … */(文档注释)多行。
交互实验:表达式求值台(OperatorLab)
输入算术表达式,实时看 Java 的求值顺序:* / % 先于 + -,括号最高。 点击下面的「求值步骤」可高亮对应子表达式。
注意 Java 语义:两个整数做 / 向零截断(7/2 = 3),% 符号随被除数(-7 % 3 = -1)。
三、2.3 流程控制语句
分支:
if (score >= 90) {
System.out.println("优秀");
} else if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}
switch (day) { // int / char / String 都可作 case
case 1: case 2: case 3: case 4: case 5:
System.out.println("工作日"); break; // 漏 break 会「贯穿」到下一 case
default:
System.out.println("周末");
}
循环:
int i = 0;
while (i < 5) { // 先判断后执行:可能一次都不跑
i++;
}
do { // 先执行后判断:至少跑一次
i--;
} while (i > 0);
for (int j = 0; j < 10; j++) { // 初始化; 条件; 步进
sum += j;
}
// 增强 for(foreach):遍历数组/集合,不用下标
for (String s : names) {
System.out.println(s);
}
三种循环可互相改写;选型原则:次数已知用 for,未知次数用 while,至少要执行一次用 do-while。
跳转语句:
| 语句 | 作用 |
|---|---|
break | 跳出当前循环 / switch 分支块 |
continue | 结束本轮循环,进入下一轮 |
return | 从方法返回值(同时结束方法) |
for (int n = 2; n < 20; n++) {
if (n % 2 == 0) continue; // 偶数跳过
if (n > 15) break; // 超过 15 直接结束循环
System.out.println(n); // 只打印 3,5,7,...,13
}
交互实验:控制流步进器(ControlFlowVisualizer)
逐行观察 if / while / for 的执行路径:哪条分支被进入、循环每轮的条件判断、break / continue 如何改变走向。
点「▶ 下一步」开始执行。
变量
输出
注意:条件判断每轮都会执行一次——循环退出那一步的条件结果是 false,这就是 while/for 的终止检查。
四、2.4 数组
数组是同类型元素的定长序列:声明与实例化分离,索引从 0 开始,合法下标 0 ~ length-1,越界抛 ArrayIndexOutOfBoundsException。
// 一维:声明 + 实例化
String[] names = new String[5]; // 5 个 null
names[0] = "Alice";
// 二维:new String[5][5] —— 5 行 × 5 列,每行是一个 String[]
String[][] grid = new String[5][5];
grid[2][3] = "X";
// 声明并初始化(长度由元素个数决定)
int[] keya = { 87, 83, 65, 68 }; // Erase Game.java:W/S/A/D 的键码
贯穿示例:单机版 ERASE 的
Game.java用两个数组记住键盘映射——int[] keya = { 87, 83, 65, 68 };(W/S/A/D)与int[] keyb = { 38, 40, 37, 39 };(方向键)。棋盘本身就是二维数组:行×列每个格子存一个方块编号,这正是「数据结构」的最直观形态。
交互实验:数组内存地图(ArrayMemoryMap)
数组在堆上是连续空间:a[i] 的地址 = 起始地址 + i × 元素大小。点单元格看地址公式;引用数组里每个格子装的是引用,对象另在堆上。
int[] a = {10, 20, 30};
教学简化:int 4 字节、char 2 字节、引用 8 字节;Erase 的 keya 存的是 W/S/A/D 四个字符,每个占 2 字节。
五、2.5 字符串
String 不可变:任何「修改」方法都返回新串,原串不变——这是学生最容易踩的坑(s.toUpperCase() 不接返回值就等于没改)。
| 方法 | 作用 |
|---|---|
length() | 字符个数 |
charAt(i) / substring(a, b) | 取字符 / 子串 |
startsWith(s) / endsWith(s) | 前缀/后缀判断 |
indexOf(s) / lastIndexOf(s) | 首次/末次出现位置(找不到 -1) |
equals(o) / equalsIgnoreCase(o) | 内容比较(不要用 ==) |
compareTo(o) | 字典序比较 |
toLowerCase() / toUpperCase() | 大小写转换(返回新串) |
replace(a, b) / replaceAll(r, s) | 替换(字面/正则) |
split(d) | 按分隔符切成 String[] |
频繁拼接用 StringBuffer(可变,避免每次 new 一个 String):append()、insert()、setCharAt()。
String s = "hello,world";
System.out.println(s.split(",")); // [hello, world]
System.out.println(s.equals("Hello")); // false(区分大小写)
StringBuffer sb = new StringBuffer();
sb.append("score=").append(100); // 反复追加不产生中间串
六、方法:参数传递
- 形参是方法的「局部副本」,实参是调用方传入的表达式。
- Java 只有传值——传的是「值的拷贝」:基本类型拷数值;引用类型拷引用的地址(所以能改对象内容,但不能让实参指向新对象)。
void swap(int a, int b) { // 基本类型:交换无效
int t = a; a = b; b = t;
}
int x = 1, y = 2;
swap(x, y); // x、y 仍是 1、2
class Box { int v; }
void setV(Box box, int n) { // 引用类型:能改对象内容
box.v = n;
}
Box b = new Box();
setV(b, 99); // b.v 变成 99 —— 因为传的是引用地址的拷贝
贯穿示例:Erase 里
Game、GameBox、NewShape各管一块——Game持有棋盘数组与分数(数据结构),GameBox负责画格子,键码数组驱动移动逻辑(算法)。结构化语法到 OOP 的过渡,就是「把变量、方法、控制结构装进类」。
思考题
- 结合 Erase-JAVA 项目的
ErasGame类,用自己的话说明「类是对同类事物共同特性的抽象」这句话的含义; String为什么设计成不可变?如果它是可变的,s.toUpperCase()这类方法会遇到什么问题?(提示:多个变量可能共享同一个串)- 用
for、while、do-while三种方式分别写一个「找出 2~20 之间所有奇数」的循环,比较哪种写法最清晰; - 以 ERASE 棋盘为例说明「数据结构是对现实世界的抽象」:二维数组的每个格子对应游戏里的什么?分数、关卡又该用什么数据组织?
学习小测验
学习小测验
Java 中整型常量(字面量)的默认类型是?
下列哪个不是 Java 的流程控制语句?
执行 final double PI = 3.14; 之后再写 PI = 3.14159; 会发生什么?
下列哪种循环「至少会执行一次」循环体?
String[] s = new String[5]; 下列哪个索引会抛 ArrayIndexOutOfBoundsException?
a = 0 时,表达式 a > 0 && b() 中方法 b() 会被调用几次?
方法 void f(int n) 内部执行 n = n * 2;,调用后实参的值会改变吗?