01方法课后作业
Java 第二讲《方法》:动手动脑问题与课后实验整理
目录
1. 动手动脑 1:手写线性同余随机数发生器
题目
课件给出了一种完全 "手写代码" 实现随机数的算法 —— 线性同余法(LCG),参数如下:
-
Modulus(模数):M = 2³¹ − 1 =
Integer.MAX_VALUE= 2147483647 -
Multiplier(乘数):A = 7⁵ = 16807
-
Increment(增量):C = 0
要求:编写一个方法,使用以上算法生成指定数目(比如 1000 个)的随机整数。
原理
线性同余公式:
next = (A × seed + C) mod M
每次以上一次的结果作为新的种子(seed)代入,即可产生一个伪随机序列。该序列要经过约 2³¹−1 个数之后才会重复。
代码
public class LCGRandom {
private long seed;
private static final long M = 2147483647L; // 2^31 - 1
private static final long A = 16807L; // 7^5
private static final long C = 0L;
public LCGRandom(long seed) {
this.seed = seed;
}
/** 生成下一个伪随机整数 */
public int nextInt() {
seed = (A * seed + C) % M;
return (int) seed;
}
/** 一次生成 count 个随机整数 */
public int[] generate(int count) {
int[] arr = new int[count];
for (int i = 0; i < count; i++) {
arr[i] = nextInt();
}
return arr;
}
public static void main(String[] args) {
// 以当前时间为种子,避免每次运行结果相同
LCGRandom rand = new LCGRandom(System.currentTimeMillis());
int[] data = rand.generate(10); // 试生成 10 个
for (int x : data) {
System.out.print(x + " ");
}
}
}
2. 动手动脑 2:MethodOverLoad 到底特殊在哪?(方法重载)
代码
public class MethodOverLoad {
// 三个方法都叫 add,但参数列表不同
public static int add(int a, int b) {
System.out.println("调用的是 add(int, int)");
return a + b;
}
public static double add(double a, double b) {
System.out.println("调用的是 add(double, double)");
return a + b;
}
public static int add(int a, int b, int c) {
System.out.println("调用的是 add(int, int, int)");
return a + b + c;
}
public static void main(String[] args) {
System.out.println(add(1, 2)); // 两个 int
System.out.println(add(1.5, 2.5)); // 两个 double
System.out.println(add(1, 2, 3)); // 三个 int
}
}
特殊之处在于:方法名相同,但参数列表不同,这就是 Java 的方法重载(Overload)。
方法重载的判断要点
两个或多个方法构成重载,必须同时满足:
-
方法名相同;
-
参数列表不同:参数类型不同、参数个数不同,或者参数类型的顺序不同。
注意:
方法的返回值类型不作为重载的判断条件
仅返回值不同而参数列表相同,编译器会直接报错。
调用时编译器会根据你传入的实参类型和个数,自动匹配到最合适的那个重载版本。
3. 课堂练习:观察 System.out.println ()
题目
查看一下 JDK 中 System.out.println() 方法,你发现了什么?
答案
打开 java.io.PrintStream 的源码就会发现,println 被重载了很多次,例如:
public void println() // 只换行
public void println(int x)
public void println(long x)
public void println(double x)
public void println(float x)
public void println(boolean x)
public void println(char x)
public void println(char[] x)
public void println(String x)
public void println(Object x)
也就是说,无论你传给它的是整数、浮点数、字符串还是一个对象,都能正确打印 —— 这正是方法重载在 JDK 中的典型应用:用同一个方法名,适配不同类型的参数。
4. 思考:n! 的阶乘结果怎么会是负数?(int 溢出)
现象
课件演示 CalculateN 用递归求 n!,结果阶乘算着算着竟然出现了负数:
public class CalculateN {
public static int f(int n) {
if (n == 1) return 1;
return n * f(n - 1);
}
public static void main(String[] args) {
for (int i = 1; i <= 21; i++) {
System.out.println(i + "! = " + f(i));
}
}
}
原因
Java 中 int 占 32 位,是有符号整数:
-
Integer.MAX_VALUE = 2³¹ − 1 = 2,147,483,647 -
Integer.MIN_VALUE = −2³¹ = −2,147,483,648
阶乘增长极快:13! = 6,227,020,800 已经超过了 int 的上限。计算机用固定位数保存数值,超出范围时会自动截断高位二进制,从补码角度看,符号位被翻成 1,结果就变成了负数。
这就是课件说的:"由于计算机使用固定的位数来保存数值,能处理的数值大小是有限的,超出范围就会得到错误结果。"
解决:用 BigInteger
对大整数运算,应使用 java.math.BigInteger:
import java.math.BigInteger;
public class BigFactorial {
public static BigInteger calculateN(int n) {
if (n == 1 || n == 0) {
return BigInteger.valueOf(1);
}
return BigInteger.valueOf(n).multiply(calculateN(n - 1));
}
public static void main(String[] args) {
System.out.println("50! = " + calculateN(50));
}
}
BigInteger 支持任意精度整数的加减乘除,不再受 32 位限制。
5. 思考:为什么 i == j 输出 true?(浮点数比较)
现象
double i = 0.0001;
double j = 0.00010000000000000001;
System.out.println(i == j); // 输出:true
肉眼看这两个数明显不同,但 == 却判断它们相等。
原因
计算机遵循 IEEE 754 标准存储浮点数,double 只有 53 位有效二进制精度。在这个精度下,0.0001 和 0.00010000000000000001 的差别已经落在 "最后几位" 之外,被舍入成了完全相同的二进制表示,所以 == 返回 true。
结论:不能用 == 直接比较两个浮点数是否相等。
正确做法
比较它们差的绝对值是否落在一个可接受的误差范围(epsilon)内:
double i = 0.0001;
double j = 0.00010000000000000001;
if (Math.abs(i - j) < 1e-10) {
System.out.println("true(在允许误差范围内相等)");
} else {
System.out.println("false");
}
具体容差取多少,要根据业务对精度的要求来定。
6. 课后实验:自动四则运算出题程序
6.1 背景与阶段 1
故事:二柱的小孩上二年级,老师要求家长每天出 30 道四则运算题。写个小程序自动生成。
阶段 1(课堂测试一):花约 20 分钟写一个能自动生成 30 道小学四则运算题的 "软件"。
6.2 阶段 2(课堂测试二)要求
-
题目避免重复;
-
减法结果不允许出现负数;
-
乘法结果不允许出现四位数;
-
除法必须整除,不允许出现小数;
-
实现在线实时答题,交卷后统计错题数和正确率;
-
增加倒计时功能,指定时间内完成,超时未做完的题算错;
-
上述内容要求用方法(method)实现。
6.3 实现
import java.util.*;
public class ArithmeticQuiz {
private static final int TOTAL = 30; // 题目总数
private static final int TIME_LIMIT_SEC = 300; // 限时 5 分钟
private static Set<String> used = new HashSet<>(); // 题目去重
private static Random rand = new Random();
/** 一道题:题干 + 正确答案 */
static class Question {
String text;
int answer;
Question(String t, int a) { text = t; answer = a; }
}
/** 按约束随机生成一道不重复的题目 */
private static Question generateQuestion() {
String text;
int answer;
int a, b;
do {
int op = rand.nextInt(4); // 0:+ 1:- 2:× 3:÷
switch (op) {
case 0: // 加法
a = rand.nextInt(100) + 1;
b = rand.nextInt(100) + 1;
text = a + " + " + b;
answer = a + b;
break;
case 1: // 减法:保证不出现负数
a = rand.nextInt(100) + 1;
b = rand.nextInt(100) + 1;
if (a < b) { int t = a; a = b; b = t; }
text = a + " - " + b;
answer = a - b;
break;
case 2: // 乘法:结果不超过三位数
a = rand.nextInt(9) + 1;
b = rand.nextInt(9) + 1;
text = a + " × " + b;
answer = a * b;
break;
default: // 除法:保证整除
b = rand.nextInt(9) + 1;
answer = rand.nextInt(9) + 1;
a = b * answer;
text = a + " ÷ " + b;
}
} while (used.contains(text)); // 去重
used.add(text);
return new Question(text, answer);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int correct = 0;
long start = System.currentTimeMillis();
System.out.println("开始答题,共 " + TOTAL + " 题,限时 "
+ TIME_LIMIT_SEC + " 秒!");
for (int i = 1; i <= TOTAL; i++) {
// 倒计时:检查是否超时
long elapsed = (System.currentTimeMillis() - start) / 1000;
if (elapsed >= TIME_LIMIT_SEC) {
System.out.println("\n时间到!剩余题目按错题计。");
break;
}
Question q = generateQuestion();
System.out.printf("[剩余 %d 秒] 第 %d 题:%s = ",
TIME_LIMIT_SEC - elapsed, i, q.text);
int userAns = sc.nextInt();
if (userAns == q.answer) {
correct++;
}
}
int wrong = TOTAL - correct;
double rate = correct * 100.0 / TOTAL;
System.out.println("===== 答题结束 =====");
System.out.println("答对:" + correct + " 题");
System.out.println("答错:" + wrong + " 题");
System.out.printf("正确率:%.2f%%%n", rate);
}
}
核心点:出题、去重、判分、统计都各自封装成方法,正体现了本讲 "方法" 的主题。阶段 1 的需求(自动出 30 道题)只需要 generateQuestion() 部分即可完成。
小结
| 知识点 | 对应问题 |
|---|---|
| 手写随机数 / 伪随机算法 | 动手动脑 1(线性同余法) |
| 方法重载(Overload) | 动手动脑 2、课堂练习 println |
| 整数溢出、BigInteger | n! 算出负数 |
| 浮点数精度、epsilon 比较 | i == j 为何为 true |
| 方法封装、流程设计 | 自动四则运算出题程序 |
通过这几个问题可以看到:"方法" 本质上就是把一段重复出现、有独立功能的代码封装起来,起名字、给参数、给返回值,从而让程序更清晰、更好维护—— 也就是课件开头说的 "是懒人造就了方法"。

浙公网安备 33010602011771号