Day 01:Java 程序结构、方法与基础控制流程
Day 01:Java 程序结构、方法与基础控制流程
| 项目 | 内容 |
|---|---|
| 分类 | Java 基础 |
| 系列 | Java 记忆恢复 |
| 整理日期 | 2026-08-06 |
| 适用环境 | JDK 17 |
| 文档用途 | 知识复习、发布与打印 |
状态说明:阅读和理解本文不等于已经独立掌握,掌握程度仍需以后通过复述、代码阅读和间隔练习验证。
学习目标
完成本篇复习后,应当能够逐步理解:
- 普通 Java 应用为什么使用
main作为入口。 public、private、static分别解决什么问题。- 方法如何接收参数、返回结果,以及调用时如何执行。
int与long的区别,以及整数溢出为什么会产生错误结果。for、while、do...while的基本流程。break、continue、return分别会结束什么。- 如何使用
Scanner读取控制台输入。 nextLine()、trim()、Integer.parseInt()如何配合。try/catch如何处理NumberFormatException。- 局部变量、成员变量和作用域的基本区别。
- 自动类型转换、强制类型转换和表达式类型提升的基本规则。
第一部分:已讲知识整理
1. Java 程序为什么从 main 方法开始
1.1 main 是普通 Java 应用的标准入口
在 JDK 17 中,运行一个普通 Java 应用时,通常会执行:
java HelloJavaJava 启动器需要知道从哪个位置把程序控制权交给我们编写的代码。为此,Java 约定使用下面的方法作为标准入口:
public static void main(String[] args) { System.out.println("程序开始");}大致执行过程如下:
- Java 启动器创建 JVM。
- JVM 加载命令中指定的类。
- JVM完成必要的类初始化。
- Java 启动器找到符合规定签名的
main方法。 main被调用,程序开始执行我们编写的流程。
Java 不会在所有类中随意寻找一个看起来能运行的方法。统一的入口约定可以让启动过程明确、稳定。
1.2 main 方法各部分的含义
public static void main(String[] args)| 部分 | 含义 |
|---|---|
public | Java 启动器可以从类的外部访问这个方法 |
static | 不需要先创建当前类的对象就能调用 |
void | 方法不向调用者返回一个普通 Java 值 |
main | Java 约定的入口方法名 |
String[] args | 用来接收命令行参数的字符串数组 |
参数名不一定必须叫 args,下面的写法同样有效:
public static void main(String[] values) {}也可以写成可变参数形式:
public static void main(String... args) {}1.3 main 不一定是 JVM 执行的第一段代码
“程序从 main 开始”是一种便于理解的说法。更准确地说,main 是普通 Java 应用把控制权交给业务代码时使用的标准入口。
类的静态初始化可能先于 main 执行:
public class Demo { static { System.out.println("静态初始化"); }
public static void main(String[] args) { System.out.println("main 方法"); }}输出顺序是:
静态初始化main 方法1.4 常见错误
缺少 static:
public void main(String[] args) {}返回类型错误:
public static int main(String[] args) { return 0;}方法名大小写错误:
public static void Main(String[] args) {}Java 区分大小写,Main 和 main 是两个不同的方法名。
一句话记忆
main不是 JVM 启动时执行的全部代码中的第一行,而是普通 Java 应用交给我们控制时使用的标准入口。
2. static:属于类,而不是属于某个对象
2.1 类成员与对象成员
static 表示成员属于类本身。没有 static 的实例成员通常属于具体对象。
public class Student { static String school = "第一中学"; String name;}创建两个对象:
Student student1 = new Student();student1.name = "小明";
Student student2 = new Student();student2.name = "小红";这里:
name属于具体对象,两个学生可以保存不同姓名。school属于Student类,所有Student对象共享同一份数据。
访问静态成员时,推荐通过类名访问:
System.out.println(Student.school);2.2 静态方法
静态方法不依赖某个具体对象,可以直接通过类调用:
public class Calculator { public static int add(int a, int b) { return a + b; }}调用:
int result = Calculator.add(10, 20);Java 标准库中的常见静态方法包括:
Math.max(10, 20);Integer.parseInt("123");这些方法只依赖传入的数据,不需要保存某个对象的个人状态。
2.3 为什么 main 使用 static
程序刚启动时,Java 启动器还没有创建当前类的对象。如果入口是实例方法,启动器就需要额外决定:
- 应该调用哪个构造方法。
- 构造方法需要传入什么参数。
- 应该创建几个对象。
把 main 声明为 static 后,可以直接通过类调用,不需要先创建对象。
2.4 静态方法的限制
静态方法不能直接访问实例成员:
public class Student { String name;
public static void showName() { System.out.println(name); // 编译错误 }}原因是调用 showName() 时,不一定存在某个具体 Student 对象。即使存在多个对象,Java 也不知道这里需要读取哪一个对象的 name。
可以显式传入对象:
public static void showName(Student student) { System.out.println(student.name);}静态方法中也不能直接使用 this,因为 this 表示“当前对象”,而静态方法没有固定的当前对象。
2.5 适用场景
适合使用 static:
- 程序入口,例如
main。 - 不依赖对象状态的工具方法。
- 所有对象需要共享的数据。
- 配合
final定义类级别常量。
public static final int MAX_SCORE = 100;不适合仅仅为了省略创建对象,就把所有字段和方法都改成 static。姓名、年龄、余额等属于具体对象的数据通常不应共享。
2.6 常见误区
static不表示数据不能修改;防止重新赋值通常还需要final。static不等于“永远不会释放”或“永远存在”,它仍由 JVM 的类加载和生命周期管理。- 虽然可以通过对象访问静态成员,但这样容易让人误以为它属于对象,推荐使用类名访问。
- 可修改的静态字段会被所有对象共享,使用不当可能造成相互影响。
一句话记忆
普通成员属于具体对象,
static成员属于类本身。
3. private:限制访问范围
3.1 private 解决什么问题
private 表示成员只能在声明它的类内部直接访问。
public class Student { private int score;}其他类不能直接修改:
Student student = new Student();student.score = -100; // 编译错误这样可以避免外部代码绕过类自己的规则,随意写入不合理数据。
3.2 private 与封装
更合适的做法是通过方法控制数据如何修改:
public class Student { private int score;
public void setScore(int score) { if (score >= 0 && score <= 100) { this.score = score; } }
public int getScore() { return score; }}外部代码只能通过类允许的方式操作数据:
Student student = new Student();student.setScore(80);System.out.println(student.getScore());这种做法体现了封装:
- 数据和操作数据的方法放在同一个类中。
- 不应由外部直接操作的细节隐藏在类内部。
- 对外只提供经过控制的使用方式。
3.3 私有方法
私有方法通常用于隐藏类内部的辅助逻辑:
private boolean isValidScore(int score) { return score >= 0 && score <= 100;}public void setScore(int score) { if (isValidScore(score)) { this.score = score; }}外部使用者只需要知道 setScore(),不需要依赖内部如何判断分数是否合法。
3.4 private 和 static 的区别
| 修饰符 | 回答的问题 |
|---|---|
static | 这个成员属于类,还是属于具体对象? |
private | 哪些代码可以直接访问这个成员? |
它们可以组合使用:
private static int count;含义是:
static:count属于类,所有对象共享。private:只有当前类内部可以直接访问。
3.5 常见访问范围
| 写法 | 基本访问范围 |
|---|---|
private | 当前类内部 |
| 不写访问修饰符 | 同一个包内 |
protected | 同一个包内,以及符合条件的子类 |
public | 所有能够引用这个类的位置 |
初学阶段可以先采用一个实用原则:字段通常优先使用 private,需要对外提供的能力再通过方法开放。
但不要机械地为所有字段生成 getter 和 setter。如果某个数据不应由外部修改,就不应该随意提供 setter。
3.6 常见误区
private不表示变量不能修改,只表示类外不能直接访问。private的范围是整个类,不是当前方法。private不是数据加密或安全存储机制,它主要是代码层面的访问控制。- 顶层类不能声明为
private;顶层类通常是public或不写访问修饰符。
一句话记忆
private把实现细节留在类内部,外部只能通过类允许的方式使用它。
4. 方法、参数与返回值
4.1 方法是什么
方法是一段具有名称、可以调用和复用的代码。一个方法通常负责完成一个明确的小任务。
基本结构:
访问修饰符 static 返回类型 方法名(参数列表) { 方法体 return 返回值;}例如:
private static int add(int a, int b) { return a + b;}| 部分 | 含义 |
|---|---|
private | 只能在当前类内部直接调用 |
static | 不需要创建对象即可调用 |
int | 方法最终返回一个整数 |
add | 方法名 |
int a, int b | 方法需要接收的参数 |
return a + b | 把计算结果交还给调用者 |
4.2 形参与实参
方法声明中的参数叫形参:
private static int add(int a, int b)这里的 a 和 b 是形参。
调用时传入的数据叫实参:
int result = add(10, 20);这里的 10 和 20 是实参。
可以先把调用过程理解为:
a 接收到 10b 接收到 20计算 a + b把 30 返回给调用位置参数让同一个方法可以处理不同数据:
add(10, 20);add(5, 8);add(100, 200);4.3 返回值
返回值是方法执行完毕后交给调用者的结果:
private static boolean isPassed(int score) { return score >= 60;}调用:
boolean passed = isPassed(80);执行过程:
- 把实参
80交给形参score。 - 计算
score >= 60。 - 得到
true。 - 把
true返回给调用位置。 - 将结果保存到变量
passed。
返回值类型必须与声明的返回类型兼容:
private static int getNumber() { return 10;}下面的写法类型不匹配:
private static int getNumber() { return "10"; // 编译错误}4.4 void
void 表示方法执行后不返回一个结果值:
private static void printMessage() { System.out.println("你好");}调用时直接执行:
printMessage();不能把 void 方法赋值给变量:
String message = printMessage(); // 编译错误void 方法可以使用单独的 return 提前结束:
private static void printScore(int score) { if (score < 0) { return; }
System.out.println(score);}这里的 return 只结束方法,不返回数据。
4.5 方法调用过程
public class ScoreService { public static void main(String[] args) { int score = 80; boolean passed = isPassed(score); printResult(passed); }
private static boolean isPassed(int score) { return score >= 60; }
private static void printResult(boolean passed) { System.out.println(passed); }}执行顺序:
- Java 启动器调用
main。 - 创建局部变量
score,值为80。 main暂停在调用位置,进入isPassed(80)。isPassed返回true。- 回到
main,把结果保存到passed。 - 调用
printResult(true)。 - 输出
true。 main执行结束。
4.6 打印和返回不是一回事
只打印结果:
private static void add(int a, int b) { System.out.println(a + b);}返回结果:
private static int add(int a, int b) { return a + b;}返回结果通常更灵活:
int result = add(10, 20);System.out.println(result);int doubled = result * 2;一般可以让计算方法负责返回结果,让显示代码决定如何输出。这样更方便复用和测试。
4.7 常见错误
- 调用时参数数量不正确。
- 实参类型与形参类型不兼容。
- 声明了非
void返回类型,却没有覆盖所有必要的返回路径。 - 方法已经返回结果,但调用者没有保存或使用它。
- 把局部变量误认为方法结束后仍然可以访问。
一句话记忆
参数负责把数据传进方法,
return负责把结果带出方法,返回类型规定带出的数据类型。
5. int、long 与整数溢出
5.1 基本范围
int 和 long 都用于保存整数,主要区别是可表示范围不同。
| 类型 | 位数 | 取值范围 |
|---|---|---|
int | 32 位 | -2,147,483,648 ~ 2,147,483,647 |
long | 64 位 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 |
普通整数通常使用 int:
int age = 18;int score = 100;可能超过 int 范围时需要考虑 long:
long population = 8_000_000_000L;数字中的下划线只用于提高可读性,不影响数值。
5.2 L 后缀
整数常量通常先按照 int 处理。超出 int 范围的整数常量需要添加 L:
long number = 3_000_000_000L;推荐使用大写 L,因为小写 l 容易看成数字 1。
long sum = 0L;0L 表示这是一个 long 类型的零。
5.3 整数溢出
当计算结果超出类型能够表示的范围时,会发生溢出:
int number = 2_147_483_647;number = number + 1;
System.out.println(number);输出:
-2147483648Java 的普通整数运算默认不会因为溢出而自动抛出异常。程序可能继续运行,但结果已经错误。
5.4 为什么返回 long 仍然可能出错
private static long calculateSum(int number) { int sum = 0;
for (int i = 1; i <= number; i++) { sum += i; }
return sum;}虽然方法返回类型是 long,但累加过程使用的是 int。如果 sum 已经溢出,最后只是把错误的 int 结果转换成 long。
正确方向是让中间结果也使用 long:
private static long calculateSum(int number) { long sum = 0L;
for (int i = 1; i <= number; i++) { sum += i; }
return sum;}5.5 表达式可能在赋值前溢出
long result = 100_000 * 100_000;右侧两个操作数都是 int,所以会先执行 int 乘法。乘法可能已经溢出,然后才把错误结果赋给 long。
正确写法:
long result = 100_000L * 100_000;其中一个操作数是 long 后,整个表达式会使用 long 运算。
int + int → intlong + int → longlong + long → long5.6 主动检测溢出
希望溢出时直接抛出异常,可以使用:
int sum = Math.addExact(a, b);long product = Math.multiplyExact(x, y);如果整数可能超过 long,可以进一步了解 BigInteger。基础练习中,先学会根据输入范围判断中间结果和最终结果需要什么类型。
5.7 常见错误
- 方法返回
long,但累加变量仍然是int。 - 最终变量是
long,但右侧表达式先以int完成计算。 - 认为强制类型转换一定安全。
- 认为使用
long后永远不会溢出。 - 只根据测试样例选择类型,没有考虑允许输入的最大范围。
一句话记忆
最终变量是
long还不够;参与计算的中间变量和表达式也必须在溢出前变成long。
6. 循环与流程控制
6.1 for 循环
for 通常适合循环次数或计数范围较明确的场景:
for (int i = 1; i <= 5; i++) { System.out.println(i);}执行顺序:
- 执行
int i = 1,只执行一次。 - 判断
i <= 5。 - 条件成立,执行循环体。
- 执行
i++。 - 回到条件判断。
6.2 while 循环
while 会先判断条件,再决定是否执行循环体:
int i = 1;
while (i <= 5) { System.out.println(i); i++;}如果第一次判断时条件就不成立,循环体一次也不会执行。
6.3 do...while 循环
do...while 先执行循环体,再判断是否继续,因此至少执行一次:
int i = 1;
do { System.out.println(i); i++;} while (i <= 5);6.4 while (true)
while (true) { // 重复执行}条件永远是 true,因此循环本身没有结束条件。它适合“不知道需要尝试多少次”的场景,例如不断读取输入,直到获得合法数据。
while (true) 内部应存在清晰、可达到的退出路径,否则程序会一直运行。
6.5 break
break 立即结束距离它最近的一层循环:
while (true) { String input = scanner.nextLine();
if (!input.isEmpty()) { break; }}
System.out.println("循环已经结束");执行 break 后,从循环后面的代码继续执行。
6.6 continue
continue 跳过当前这一轮剩余代码,直接进入下一轮:
for (int i = 1; i <= 5; i++) { if (i == 3) { continue; }
System.out.println(i);}输出:
12456.7 return
return 结束的是整个当前方法,而不只是循环:
private static int readPositiveNumber(Scanner scanner) { while (true) { int number = Integer.parseInt(scanner.nextLine());
if (number > 0) { return number; } }}执行 return number 后:
while结束。- 当前方法结束。
number被交给调用者。
6.8 三者对比
| 语句 | 作用 |
|---|---|
continue | 跳过本轮剩余代码,进入下一轮循环 |
break | 结束当前循环,从循环后继续 |
return | 结束整个当前方法,回到调用位置 |
6.9 常见错误
忘记更新循环条件:
int i = 1;
while (i <= 5) { System.out.println(i); // 忘记 i++,循环无法结束}更新之前执行 continue:
int i = 1;
while (i <= 5) { if (i == 3) { continue; // i 会一直等于 3 }
i++;}循环条件后误加分号:
while (i <= 5); { System.out.println(i);}这里的分号形成了一个空循环,后面的代码块并不属于该循环。
普通流程中也不要用 System.exit(0) 代替 break 或 return。System.exit(0) 会终止整个 JVM,而不只是结束循环或方法。
一句话记忆
continue跳过本轮,break跳出循环,return离开整个方法。
第二部分:Day 01 补全知识
7. 使用 Scanner 读取控制台输入
7.1 Scanner 是什么
Scanner 是 Java 标准库提供的输入读取工具。使用前需要导入:
import java.util.Scanner;创建读取控制台输入的对象:
Scanner scanner = new Scanner(System.in);这里:
System.in表示标准输入流,普通控制台程序中通常对应键盘输入。new Scanner(System.in)创建一个能够解析输入内容的Scanner对象。scanner是保存这个对象引用的变量。
7.2 使用 nextLine() 读取一整行
System.out.print("请输入年龄:");String input = scanner.nextLine();nextLine() 会读取用户输入的一整行文本,并返回 String。
即使用户输入:
18程序最初得到的仍然是字符串 "18",不是整数 18。
7.3 nextInt() 与 nextLine() 的选择
也可以直接读取整数:
int age = scanner.nextInt();但是把 nextInt() 与 nextLine() 混合使用时,容易遇到换行符尚未被读取的问题:
int age = scanner.nextInt();String name = scanner.nextLine();此时 name 可能立即得到一个空字符串,因为 nextLine() 读取了前一次整数输入后留下的换行。
在需要统一校验输入的基础程序中,可以采用一种更容易控制的方式:
- 始终使用
nextLine()读取整行字符串。 - 清理字符串。
- 使用解析方法转换类型。
- 转换失败时统一处理异常。
7.4 关闭 Scanner
可以使用 try-with-resources:
try (Scanner scanner = new Scanner(System.in)) { String input = scanner.nextLine(); System.out.println(input);}代码块结束后,Scanner 会被自动关闭。
关闭包装 System.in 的 Scanner 也会关闭底层标准输入。因此在较大的程序中,不要在多个方法中反复创建并关闭不同的控制台 Scanner。通常在入口创建一次,再把它作为参数传给需要读取输入的方法。
7.5 常见错误
- 忘记导入
java.util.Scanner。 - 认为
nextLine()返回的是整数。 - 混用
nextInt()和nextLine(),却没有理解换行处理。 - 在多个方法中反复创建和关闭控制台
Scanner。 - 把输入、转换、业务计算和输出全部塞进
main,导致流程难以理解。
一句话记忆
Scanner负责读取输入;nextLine()读取到的是一整行字符串。
8. trim() 与 Integer.parseInt()
8.1 清理字符串
用户可能在数字前后输入空格:
[空格][空格][空格]18[空格][空格][空格]可以使用 trim() 去除字符串首尾的常见空白字符:
String input = scanner.nextLine().trim();如果需要更完整地处理 Unicode 空白字符,JDK 11 及以上还可以使用:
String input = scanner.nextLine().strip();基础控制台程序中,trim() 已经足以说明“读取后先清理首尾空白”的过程。
trim() 不会修改原来的字符串,而是返回一个新字符串:
String original = " 18 ";String cleaned = original.trim();8.2 把字符串转换为整数
String input = "18";int age = Integer.parseInt(input);Integer.parseInt() 的工作是把符合整数格式的字符串解析为 int。
它不会把任意字符串都转换成整数:
Integer.parseInt("abc");Integer.parseInt("12.5");Integer.parseInt("");这些输入都不是合法的 int 文本,会产生 NumberFormatException。
8.3 parseInt() 与强制类型转换不是一回事
解析字符串:
int number = Integer.parseInt("123");数值类型转换:
double value = 123.9;int number = (int) value;前者把文本解释成数字,后者在已有数值类型之间转换。它们解决的是不同问题。
8.4 读取更大的整数
如果输入本身可能超过 int 范围,可以使用:
long number = Long.parseLong(input);选择 Integer.parseInt() 还是 Long.parseLong(),应当根据允许的输入范围决定。
8.5 常见错误
- 忘记接收
trim()的返回值,以为原字符串会被原地修改。 - 输入带有无法识别的内容,却认为
parseInt()会自动返回默认值。 - 把
12.5当作合法整数文本。 - 只把最终变量改成
long,输入仍然使用只能解析int的方法,却没有明确这是有意限制输入范围。
一句话记忆
nextLine()得到文本,trim()清理首尾空白,parseInt()再把合法的整数字符串解析为int。
9. try/catch 与 NumberFormatException
9.1 异常是什么
异常表示程序执行过程中出现了需要处理的非正常情况。
例如:
int number = Integer.parseInt("abc");"abc" 无法解析为整数,因此会产生 NumberFormatException。
如果没有处理,当前方法的正常流程会被打断,异常继续向调用者传播,普通控制台程序最终可能直接结束并打印错误信息。
9.2 使用 try/catch
try { int number = Integer.parseInt(input); System.out.println(number);} catch (NumberFormatException e) { System.out.println("输入不是合法整数");}执行逻辑:
try中没有异常:继续执行try后面的正常流程,跳过对应的catch。try中产生匹配的NumberFormatException:立即停止try中剩余代码,进入catch。catch执行结束后:从整个try/catch后面继续。
9.3 与循环组合
while (true) { String input = scanner.nextLine().trim();
try { int number = Integer.parseInt(input);
if (number <= 0) { System.out.println("请输入正整数"); continue; }
return number; } catch (NumberFormatException e) { System.out.println("输入不是合法整数"); }}不同输入对应不同流程:
| 输入情况 | 程序行为 |
|---|---|
| 不能解析为整数 | 进入 catch,提示后开始下一轮 |
能解析但小于等于 0 | 执行 continue,开始下一轮 |
| 合法正整数 | 执行 return,返回数据并结束方法 |
9.4 为什么不要随意捕获所有异常
下面的写法范围过大:
try { int number = Integer.parseInt(input);} catch (Exception e) { System.out.println("发生错误");}它会把许多不同问题都归为同一种情况,容易隐藏真正的程序错误。
已经明确知道需要处理的是整数格式问题时,更适合捕获具体异常:
catch (NumberFormatException e)9.5 catch 参数
catch (NumberFormatException e)其中:
NumberFormatException是需要捕获的异常类型。e是保存本次异常对象的局部变量。
如果确实需要诊断,可以读取异常信息。但面向普通用户时,通常应该输出清晰的业务提示,而不是直接展示完整堆栈。
9.6 常见错误
- 把正常的条件判断全部写成异常处理。
- 捕获异常后什么也不做,导致问题被静默忽略。
- 使用过于宽泛的
catch (Exception e)隐藏真正错误。 - 认为进入
catch后会自动重新执行try;只有外层循环才会让程序再次尝试。 - 把
try范围写得过大,导致难以判断究竟哪一步可能失败。
一句话记忆
try放可能失败的操作,catch处理指定异常;是否重新尝试由循环决定,不是由catch自动决定。
10. 局部变量、成员变量与作用域
10.1 作用域是什么
作用域表示一个变量名在哪一段代码中可以被访问。
局部变量只在声明它的代码块内部有效:
public static void main(String[] args) { int score = 80; System.out.println(score);}离开 main 后,其他方法不能直接使用这里的 score。
10.2 代码块作用域
if (true) { int number = 10; System.out.println(number);}
System.out.println(number); // 编译错误number 只存在于 if 的代码块中。
循环变量也有自己的作用域:
for (int i = 0; i < 5; i++) { System.out.println(i);}
System.out.println(i); // 编译错误10.3 成员变量
声明在类中、方法外的变量叫字段或成员变量:
public class Student { private int score;}实例字段通常跟随对象保存状态。静态字段属于类:
public class Student { private static int count; private int score;}10.4 默认值与初始化
字段会获得类型对应的默认值,例如:
- 整数类型默认是
0。 boolean默认是false。- 引用类型默认是
null。
局部变量不会自动获得可直接使用的默认值:
int number;System.out.println(number); // 编译错误:可能尚未初始化需要先明确赋值:
int number = 0;System.out.println(number);10.5 同名变量与 this
public class Student { private int score;
public void setScore(int score) { this.score = score; }}这里:
this.score表示当前对象的字段。- 右侧
score表示方法参数。
参数名遮蔽了同名字段,因此需要用 this 明确指定对象字段。
10.6 参数与字段如何选择
- 数据只在一次方法调用中临时使用:优先使用参数或局部变量。
- 数据需要成为对象持续保存的状态:考虑实例字段。
- 数据需要由整个类共享:谨慎考虑静态字段。
不要为了让多个方法都能访问某个临时数据,就随意把局部变量改成静态字段。这样会扩大状态范围,增加代码之间的相互影响。
一句话记忆
变量只能在自己的作用域内使用;临时数据用局部变量,需要保存的对象状态才使用字段。
11. 基本类型转换与表达式类型
11.1 自动扩大转换
范围较小的整数类型放入范围较大的类型时,通常可以自动转换:
int small = 100;long large = small;这个方向不会因为整数范围缩小而丢失数据。
常见的扩大方向可以先简单理解为:
byte → short → int → long → float → double这只是帮助记忆的基础顺序。不同运算还会受到具体类型提升规则影响。
11.2 强制缩小转换
把范围较大的类型放入范围较小的类型,需要显式强制转换:
long large = 3_000_000_000L;int small = (int) large;这段代码能够编译,但结果会失真,因为原值超出了 int 范围。
强制转换表达的是“允许执行转换”,不代表转换后的结果一定安全。
11.3 浮点数转整数
double price = 19.99;int whole = (int) price;结果是:
19强制转换会直接舍弃小数部分,不会自动四舍五入。需要四舍五入时,应根据需求使用 Math.round() 等明确方法。
11.4 表达式类型
int a = 100_000;int b = 100_000;long result = a * b;a * b 会先按 int 运算。即使接收变量是 long,也不能改变已经完成的表达式类型。
可以先把一个操作数变成 long:
long result = (long) a * b;或者使用 long 字面量:
long result = 100_000L * 100_000;11.5 整数除法
两个整数相除,结果仍然是整数:
int result = 5 / 2;结果是 2,小数部分被舍弃。
希望得到小数,需要让至少一个操作数成为浮点类型:
double result = 5.0 / 2;结果是 2.5。
11.6 常见错误
- 认为接收变量的类型会提前改变右侧表达式的计算类型。
- 认为强制类型转换一定不会丢失数据。
- 认为整数除法会自动保留小数。
- 为了消除编译错误随意添加强制转换,却没有检查允许的数值范围。
一句话记忆
类型转换不仅要看最终变量,还要看参与运算的每个操作数以及表达式在哪个类型中完成。
第三部分:综合代码阅读示例
12. 正整数输入与累加
下面的完整示例用于串联本篇知识点。它是阅读材料,不是必须立即完成的作业。
import java.util.Scanner;
public class PositiveSumCalculator {
public static void main(String[] args) { try (Scanner scanner = new Scanner(System.in)) { int upperBound = readPositiveInt(scanner); long sum = calculateSum(upperBound);
System.out.println("1 到 " + upperBound + " 的整数和为:" + sum); } }
private static int readPositiveInt(Scanner scanner) { while (true) { System.out.print("请输入一个正整数:"); String input = scanner.nextLine().trim();
try { int number = Integer.parseInt(input);
if (number <= 0) { System.out.println("输入必须大于 0,请重新输入。"); continue; }
return number; } catch (NumberFormatException e) { System.out.println("输入不是合法整数,请重新输入。"); } } }
private static long calculateSum(int upperBound) { long sum = 0L;
for (long current = 1L; current <= upperBound; current++) { sum += current; }
return sum; }}12.1 程序结构
main是入口。readPositiveInt()负责读取和校验输入。calculateSum()负责计算。main负责组织调用并输出最终结果。
每个方法只承担相对明确的职责,输入、计算和显示没有全部堆在同一个代码块中。
12.2 输入流程
读取整行字符串→ 清理首尾空白→ 尝试解析为 int→ 检查是否大于 0→ 合法时返回→ 不合法时提示并继续循环12.3 为什么累加器和循环变量使用 long
upperBound 的输入范围被限制为 int,但从 1 累加到较大的 int 时,结果可能超过 int。
因此:
long sum = 0L;累加过程在 long 中完成。
循环变量也使用 long:
for (long current = 1L; current <= upperBound; current++)这样即使 upperBound 接近 Integer.MAX_VALUE,循环变量递增时也不会先发生 int 回绕。实际执行如此巨大的循环会非常耗时,但类型边界仍然保持正确。
12.4 异常后的流程
当 Integer.parseInt(input) 失败时:
try中剩余代码停止执行。- 进入
catch (NumberFormatException e)。 - 输出提示。
catch执行结束。- 到达
while循环体末尾。 - 开始下一轮输入。
不是 catch 自动重试,而是外层 while (true) 让程序重新执行。
第四部分:打印速查
13. 核心概念速查表
| 知识点 | 核心含义 |
|---|---|
main | 普通 Java 应用的标准入口 |
static | 成员属于类,不依赖某个具体对象 |
private | 成员只能在当前类内部直接访问 |
| 参数 | 调用者传入方法的数据 |
| 返回值 | 方法处理后交还给调用者的数据 |
void | 方法不返回一个结果值 |
int | 32 位有符号整数 |
long | 64 位有符号整数 |
0L | long 类型的整数零 |
| 溢出 | 结果超过类型范围后产生错误回绕 |
break | 结束当前循环 |
continue | 跳过本轮剩余代码 |
return | 结束整个当前方法 |
nextLine() | 读取一整行字符串 |
trim() | 返回去除首尾常见空白后的新字符串 |
parseInt() | 把合法整数字符串解析为 int |
try/catch | 尝试执行可能失败的操作并处理指定异常 |
| 作用域 | 变量名能够被访问的代码范围 |
14. 常见错误检查表
-
main是否写成public static void main(String[] args)? - 静态方法是否错误地直接访问了实例字段或
this? - 字段是否因为图方便而被随意声明成
public? - 方法的参数数量、类型和返回类型是否匹配?
- 需要后续使用的计算结果是否真正通过
return返回? - 累加器和中间表达式是否可能先以
int溢出? -
while循环是否存在明确的退出路径? -
continue是否跳过了循环变量更新? -
nextLine()得到的字符串是否需要清理和解析? -
parseInt()失败时是否只捕获了需要处理的具体异常? - 局部变量是否在使用前明确初始化?
- 强制类型转换是否可能丢失数据?
- 整数除法是否错误地期待得到小数结果?
15. 关键记忆句
main是普通 Java 应用的标准入口。static回答“成员属于类还是对象”。private回答“哪些代码可以直接访问成员”。- 参数把数据传入方法,
return把结果带出方法。 - 返回类型是
long,不代表中间计算自动使用long。 continue跳过本轮,break跳出循环,return离开方法。nextLine()读取文本,trim()清理文本,parseInt()解析文本。catch负责处理异常,循环负责决定是否重新尝试。- 临时数据使用局部变量,需要保存的对象状态才考虑字段。
- 类型安全要同时检查变量、操作数和整个表达式。
第五部分:下一阶段预告
Day 02 将继续恢复以下知识:
- 数组的声明、创建和初始化。
- 数组下标为什么从
0开始。 array.length与数组遍历。- 数组越界异常。
String的基本特点。- 字符串为什么使用
.equals()比较内容。 ==比较基本类型与引用类型时分别意味着什么。
这些内容将在下一篇中完整展开,本篇只保留学习路线,不提前混入更多细节。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!