第 1 课 程序骨架与运行方式
对应教材第 1 章。先搞清楚"一个 Java 程序是怎么跑起来的"。
Java 程序以类为单位组织,代码写在类的花括号内。源文件先由 javac 编译成字节码(.class),
再由 JVM 解释执行——这正是 Java 平台无关的原因:同一份字节码可以在任何装有 JVM 的机器上运行。
程序的入口是固定的 main 方法,JVM 从这里开始执行第一条语句。
// 文件名必须与 public 类名完全一致:Hello.java
public class Hello {
public static void main(String[] args) { // 程序入口,签名固定
System.out.println("Hello, Java"); // 向控制台输出并换行
}
}
// 编译与运行(命令行)
// javac Hello.java → 生成 Hello.class(字节码)
// java Hello → JVM 执行(注意不带 .class 后缀)
- 一个源文件最多一个 public 类,且文件名必须与 public 类同名(大小写一致)。
- main 方法签名固定:
public static void main(String[] args)。 - Java 区分大小写;语句以分号结尾;块用花括号划分。
课堂练习(3 题)
- 执行
java Hello.class报"找不到或无法加载主类",原因是什么? 答:运行时应给出类名而不是文件名,正确写法是java Hello。 - 一个文件里写了两个 public 类,编译会怎样? 答:编译报错。一个源文件只能有一个 public 类,且文件名必须与它同名。
- 简述 Java 实现平台无关的原理。 答:源码编译为与平台无关的字节码,各平台用自己的 JVM 解释执行,因此"一次编译,到处运行"。
第 2 课 标识符与基本数据类型
对应教材第 2 章。8 种基本类型,宽度固定、与平台无关。
变量是内存中的一块存储区域,类型决定它占多大空间、能放什么值。 Java 有 8 种基本类型,它们的字节数固定不变(不像 C/C++ 那样随平台变化)。 需要区分的是:成员变量有默认值,而局部变量必须先赋值才能使用。
// 标识符:由字母、$、_ 开头,后可跟数字;区分大小写;不能是关键字 // 习惯:类名首字母大写,变量/方法名首字母小写,常量全大写 boolean flag = true; // true / false byte b = 127; // 1 字节,-128 ~ 127 short s = 32767; // 2 字节 int i = 2147483647; // 4 字节,整数的默认类型 long l = 100L; // 8 字节,字面量加后缀 L float f = 3.14F; // 4 字节,必须加 F,否则编译错误 double d = 3.1415926; // 8 字节,浮点的默认类型 char c = 'A'; // 2 字节,单引号,Unicode // 字面量写法 int hex = 0xFF, bin = 0b1010; // 十六进制 / 二进制 long big = 1_000_000L; // 下划线仅提高可读性 char nl = '\n', uni = '中'; // 转义字符与 Unicode 字符
- 成员变量默认值:int→0、double→0.0、boolean→false、引用→null;局部变量没有默认值。
- float 字面量必须写
3.14F;较大的 long 值要加L。 - char 用单引号,String 用双引号,二者不可混用。
课堂练习(3 题)
float f = 3.14;为什么编译失败? 答:3.14 默认是 double 类型,赋给 float 会损失精度,必须写成3.14F或强制转换。byte b = 200;能否通过编译? 答:不能,200 超出 byte 的取值范围(-128~127),需写成byte b = (byte) 200;(会溢出)。- 局部变量声明后直接使用会怎样? 答:编译错误(可能尚未初始化变量),必须先赋初值。
第 3 课 类型转换与字面量
对应教材 2.3。方向搞清楚,精度才不会莫名丢失。
小范围类型可以自动转换成大范围类型(不会丢精度);反过来必须强制转换,且可能丢精度或溢出。 表达式中不同类型混合运算时,整体会先提升为其中最高的类型。
// 自动转换方向:byte → short → char → int → long → float → double int i = 10; double d = i; // 自动转换,安全 // 强制转换:可能丢失精度 double x = 3.99; int n = (int) x; // 直接截断,结果是 3(不是四舍五入) byte bb = (byte) 300; // 溢出,结果是 44 // 混合运算:整体提升为最高类型 int a = 5, b = 2; System.out.println(a / b); // 2,整数除法会截断 System.out.println(a / (double)b);// 2.5,先把其中一个提升为浮点
- 强制转换直接丢弃小数部分;四舍五入要用
Math.round()。 - 整数与整数相除结果必为整数,求平均值前先转浮点。
课堂练习(2 题)
- 写出把 3.99 四舍五入为整数的表达式。
答:
(int) Math.round(3.99),结果为 4。 - 为什么
1 / 2 * 3.0的结果是 0.0? 答:先算1 / 2,两个操作数都是 int,结果为 0,再乘 3.0 得 0.0。应写成1.0 / 2 * 3。
第 4 课 运算符与表达式
对应教材 3.1。短路求值与自增是考试高频点。
运算符决定表达式如何求值。需要特别理解两点:
①短路求值——&& 左侧为 false 时右侧不再计算,|| 左侧为 true 时同理;
②自增位置——i++ 先用后加,++i 先加后用。
int a = 10, b = 3; System.out.println(a / b); // 3 整除 System.out.println(a % b); // 1 取余 System.out.println(a++); // 10 先用后加(此后 a 变为 11) System.out.println(++a); // 12 先加后用 boolean p = true, q = false; System.out.println(p && q); // false,左侧为 false 时右侧不求值 System.out.println(p || q); // true ,左侧为 true 时右侧不求值 int x = 0b1100; System.out.println(x & 0b1010); // 8 按位与 System.out.println(x | 0b1010); // 14 按位或 System.out.println(x ^ 0b1010); // 6 按位异或 System.out.println(x << 2); // 左移 2 位(相当于乘 4) int max = (a > b) ? a : b; // 三目运算符 a += 5; // 复合赋值(隐含强制转换)
&&/||短路;&/|两侧都求值(同时是位运算符)。==比较基本类型的值;比较引用类型时比较地址。- 优先级:算术 > 关系 > 逻辑 > 赋值,不确定就加括号。
课堂练习(3 题)
int i = 5; int j = i++ + ++i;求 i 与 j 的值。 答:i++ 取值 5 后 i 变 6;++i 先把 i 变 7 并取 7;j = 5 + 7 = 12,最终 i = 7。- 什么情况下必须用短路与而不是
&? 答:当右侧表达式依赖左侧成立才有意义时,例如s != null && s.length() > 0,用&会在 s 为 null 时抛异常。 - 比较两个字符串内容是否相同应该怎么写?
答:用
s1.equals(s2),不能用==(它比较引用地址)。
第 5 课 分支与循环
对应教材 3.2-3.6。程序"会判断、能重复"的关键。
顺序结构只能从上往下执行;分支让程序按条件选择路径,循环让一段代码重复执行。 switch 适合"一个变量取离散的若干值"的场景,注意每个 case 结束要写 break,否则会继续向下执行(穿透)。 for-each 是最简洁的只读遍历方式。
// 分支
if (score >= 90) {
System.out.println("优秀");
} else if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}
// switch:支持 byte/short/char/int、String、枚举
switch (grade) {
case 'A': System.out.println("90 以上"); break; // 漏写 break 会穿透
case 'B': System.out.println("80 以上"); break;
default : System.out.println("其它");
}
// while / do-while
while (n > 0) { sum += n; n--; }
do { sum += n; n--; } while (n > 0); // 至少执行一次
// for 与增强 for
for (int k = 0; k < arr.length; k++) { sum += arr[k]; }
for (int v : arr) { sum += v; } // for-each,只读遍历最方便
// break / continue
for (int k = 0; k < 10; k++) {
if (k == 3) continue; // 跳过本次循环
if (k == 8) break; // 结束整个循环
}
outer: // 标签:跳出多层循环
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
if (i * j > 2) break outer;
- switch 中漏写 break 会继续执行下一个 case。
- if / while 后不加花括号时只有第一条语句受控,建议始终写花括号。
- for-each 中的循环变量是副本,不能用来修改数组元素。
- do-while 至少执行一次,注意末尾分号。
课堂练习(3 题)
- 写出判断闰年的条件(能被 400 整除,或能被 4 整除但不能被 100 整除)。
答:
year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) - switch 中故意不写 break 会发生什么? 答:发生穿透,从匹配的 case 开始依次执行后续所有 case 的语句,直到遇到 break 或结束。
- 如何用带标签的 break 跳出两层循环?
答:在外层循环前写标签(如
outer:),内层满足条件时执行break outer;。
第 6 课 数组
对应教材 2.5。数组是引用类型,创建后长度不可变。
数组用来存放一组同类型数据。声明时用 int[] a,创建时用 new int[5] 指定长度,
创建后长度固定不变。数组变量保存的是引用,把它赋给另一个变量只是复制地址,两个变量指向同一块数据。
二维数组本质是"数组的数组",所以每行的长度可以不同。
int[] a = new int[5]; // 创建,元素默认值为 0
int[] b = {1, 2, 3, 4, 5}; // 静态初始化
int[][] m = new int[3][4]; // 3 行 4 列
int[][] m2 = {{1,2},{3,4,5}}; // 每行长度可不同
System.out.println(a.length); // 5,length 是属性不是方法
System.out.println(m.length); // 3(行数)
System.out.println(m[0].length); // 4(第一行的列数)
for (int v : b) { System.out.print(v + " "); }
System.out.println(Arrays.toString(b)); // [1, 2, 3, 4, 5]
Arrays.sort(b); // 升序排序
int[] copy = Arrays.copyOf(b, b.length); // 复制(新数组)
- 下标从 0 开始,越界抛 ArrayIndexOutOfBoundsException。
int[] c = b;只是复制引用,改 c 会影响 b;要独立副本用Arrays.copyOf。- 遍历时
arr.length是属性,不能写成arr.length()。
课堂练习(3 题)
- 如何求二维数组所有元素之和?
答:双重循环
for (int[] row : m) for (int v : row) sum += v; - 把数组 b 赋给 c 后修改 c[0],b[0] 会变化吗?
答:会。二者指向同一个数组对象,修改互相可见;需要独立副本应使用
Arrays.copyOf或 clone。 - 如何对数组降序排序?
答:基本类型数组可先
Arrays.sort(b)再倒序遍历;对象数组可用Arrays.sort(arr, Comparator.reverseOrder())。
第 7 课 输入与输出
对应教材 2.4。让程序能与用户交互。
输入用 Scanner(在 java.util 包中,需要 import),输出用 System.out。
注意 next() 读到空白符为止,nextLine() 读一整行——二者混用是初学者的经典坑:
nextInt() 之后紧跟 nextLine() 会读到空行。
import java.util.Scanner;
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
double d = sc.nextDouble();
String s = sc.next(); // 读到空白符为止
String ln = sc.nextLine(); // 读一整行
sc.close();
// 输出
System.out.print("不换行");
System.out.println("换行");
System.out.printf("%d %.2f %s%n", n, d, s); // 格式化输出
// 格式符:%d 整数 %f 浮点 %.2f 保留两位 %s 字符串 %c 字符 %b 布尔
- 使用 Scanner 前需要
import java.util.Scanner;。 - 格式符与参数必须按顺序、按类型对应,否则运行时报错。
课堂练习(2 题)
- 为什么 nextInt() 之后再调 nextLine() 读到的是空字符串? 答:nextInt() 只读数字,把换行符留在缓冲区,紧接着的 nextLine() 直接读到这个换行符并返回空串。解决办法是在中间多调用一次 nextLine() 消耗掉换行。
- 把 double 值 3.14159 按保留两位输出。
答:
System.out.printf("%.2f%n", 3.14159);输出 3.14。
第 8 课 字符串与综合练习
String 属于第 8 章,但基础阶段每段程序都会用到。
String 是不可变对象:任何"修改"都会产生新的字符串,原对象不变。
因此在循环中反复拼接字符串会产生大量临时对象,这种场景应使用 StringBuilder。
比较内容用 equals,== 只比较引用地址。
String s = "Hello";
s.length(); // 5
s.equals("Hello"); // true,比较内容用 equals
s.equalsIgnoreCase("hello"); // 忽略大小写
s.charAt(0); // 'H'
s.substring(1, 3); // "el",左闭右开
s.indexOf("ll"); // 2,未找到返回 -1
s.trim(); // 去掉首尾空白
s.toUpperCase(); // "HELLO",原串不变
String[] parts = "a,b,c".split(",");
String.valueOf(123); // "123"
StringBuilder sb = new StringBuilder(); // 频繁拼接时用
sb.append("a").append(1).reverse();
- String 不可变;循环拼接用 StringBuilder。
substring(a, b)是左闭右开区间。split的参数是正则表达式,特殊字符需转义。
综合练习(3 题)
- 读入若干整数存入数组,输出最大值、最小值与平均值(保留两位)。
答:用 Scanner 读入 n 与 n 个整数存入 int[],遍历求 sum/max/min,最后
System.out.printf("avg=%.2f%n", sum / (double) n); - 统计一行字符串中某个单词出现的次数。
答:用
split切分后遍历计数,或用indexOf(fromIndex)循环查找。 - 为什么循环里写
s += "a"效率低? 答:String 不可变,每次 += 都会新建 String 对象,n 次拼接产生 n 个临时对象;应改用 StringBuilder 的 append。
阶段自测
五道综合题,覆盖前八课的语法点。先自己写,再展开对照。
查看题目与参考答案(点击折叠)
- 读入 n 与 n 个整数,输出最大值、最小值与平均值(保留两位)。
答:用
int[] arr = new int[n]存储,遍历求 sum/max/min,输出时用System.out.printf("avg=%.2f%n", sum / (double) n); - 判断一个正整数是否为素数。
答:循环到
Math.sqrt(n),若存在能整除的因子则不是素数:for (int i = 2; i * i <= n; i++) if (n % i == 0) return false; - 去掉数组中的重复元素(保留首次出现的顺序)。
答:借助
LinkedHashSet:new ArrayList<>(new LinkedHashSet<>(Arrays.asList(arr))),或双重循环标记后复制到新数组。 - 统计一行字符串中字母、数字与其它字符的个数。
答:逐字符判断:
Character.isLetter(c)、Character.isDigit(c),其余计入其它。 - 输出九九乘法表。
答:双重循环
for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) System.out.printf("%d×%d=%d\t", j, i, i * j); System.out.println(); }