前言
大家好,欢迎来到我的博客!作为一名即将参加 Java 期末考试的学生,我决定通过写博客的方式来复习和巩固知识。今天分享的是 2024 年 Java 面向对象程序设计卷的完整解析,包含 7 道编程题的详细代码和注释。希望通过这篇博客,能够帮助到同样在备考的同学们,也欢迎大家多多交流,一起进步!
一、用Java实现一个简单的猜数字游戏
规则如下:
程序随机生成一个1到100之间的整数作为目标数字。
用户有4次猜测机会,每次猜测时程序会提示用户猜测的数字是偏大还是偏小,或者正确。
如果用户在4次机会内猜对了数字,则显示“恭喜你,猜对了!”;
否则,显示“很遗憾,你没有猜对,正确答案是X(目标数字)”。
(一)使用for循环控制游戏流程,完整代码
package question1;
// 1.导入必要的类库
import java.util.Random;// 导入生成随机数所需的类,利用该类可以生成伪随机数序列
import java.util.Scanner;// 导入获取用户输入所需的类,通过该类可以从控制台读取用户输入的数据
public class GuessNumber1 {
public static void main(String[] args) {
// 2.程序随机生成一个1到100之间的整数作为目标数字
Random random = new Random();// 创建Random类的实例对象:生成伪随机数序列
int targetNumber = random.nextInt(100) + 1;// nextInt(100)返回0-99的整数,+1调整范围为1-100
// 3.初始化游戏参数
int attempts = 4;// 用户有4次猜测机会
Scanner scanner = new Scanner(System.in);// 创建Scanner类的实例对象:读取控制台输入
boolean hasGuessedCorrectly = false;// 标记用户是否猜对了数字,初始值为false表示还未猜对
//4,游戏开始提示
System.out.println("欢迎来到猜数字游戏!你需要在1到100之间猜一个数字,你有4次猜测机会!");
//5.使用for循环控制游戏流程
for(int i=1;i<=attempts;i++) {// 使用for循环控制猜测次数,i从1开始,当i小于等于猜测次数时执行循环体
System.out.println("请输入第" + i + "次猜测的数字");// 提示用户输入猜测的数字
int guessNumber = scanner.nextInt();// 读取用户输入的整数,将其存储在guessNumber变量中
// 检查用户输入的数字是否在1到100的有效范围内
if (guessNumber < 0 || guessNumber > 100) {
System.out.println("请输入1到100之间的数字,请重新输入!");
i--;// 本次猜测无效,不减少机会
continue;// 跳过本次循环剩余部分,重新开始循环
}
// 用户有4次猜测机会,每次猜测时程序会提示用户猜测的数字是偏大还是偏小,或者正确
if (guessNumber < targetNumber) {
System.out.println("猜小了,再大一点!");
} else if (guessNumber > targetNumber) {
System.out.println("猜大了,再小一点!");
} else {
hasGuessedCorrectly = true;
break; // 猜对后跳出循环
}
// 如果还没到最后一次猜测,提示用户剩余猜测次数
if (i < attempts) {
System.out.println("你还剩下" + (attempts - i) + "次猜测机会");
}
}
if (hasGuessedCorrectly) {// 如果用户在4次机会内猜对了数字,则显示“恭喜你,猜对了!”
System.out.println("恭喜你,猜对了!");
} else {// 否则,显示“很遗憾,你没有猜对,正确答案是X(目标数字)”。
System.out.println("很遗憾,你没有猜对,正确答案是 " + targetNumber);
}
scanner.close();// 关闭Scanner资源,防止内存泄漏
}
}
(二)使用while循环控制游戏流程,完整代码
package question1;
// 1.导入必要的类库
import java.util.Random;// 导入生成随机数所需的类,利用该类可以生成伪随机数序列
import java.util.Scanner;// 导入获取用户输入所需的类,通过该类可以从控制台读取用户输入的数据
public class GuessNumber2 {
public static void main(String[] args) {
// 2.程序随机生成一个1到100之间的整数作为目标数字
Random random = new Random();// 创建Random类的实例对象:生成伪随机数序列
int targetNumber = random.nextInt(100) + 1;// nextInt(100)返回0-99的整数,+1调整范围为1-100
// 3.初始化游戏参数
int attempts = 4;// 用户有4次猜测机会
Scanner scanner = new Scanner(System.in);// 创建Scanner类的实例对象:读取控制台输入
boolean hasGuessedCorrectly = false;// 标记用户是否猜对了数字,初始值为false表示还未猜对
//4,游戏开始提示
System.out.println("欢迎来到猜数字游戏!你需要在1到100之间猜一个数字,你有4次猜测机会!");
//5.使用while循环控制游戏流程
int currentAttempts=1;
while (attempts > 0) {// 当剩余猜测次数大于0时,执行循环体
// 提示用户第几次猜测
System.out.println("请输入第" + currentAttempts+ "次猜测的数字");// 提示用户输入猜测的数字
int guessNumber= scanner.nextInt();// 读取用户输入的整数,将其存储在guessNumber变量中
// 检查用户输入的数字是否在1到100的有效范围内
if (guessNumber < 0 || guessNumber > 100) {
System.out.println("请输入1到100之间的数字,请重新输入!");
currentAttempts++;// 本次猜测无效,不减少机会
continue;// 跳过本次循环剩余部分,重新开始循环
}
if (guessNumber < targetNumber) {
System.out.println("猜小了!再大一点");
} else if (guessNumber > targetNumber){
System.out.println("猜大了!再小一点");
} else{hasGuessedCorrectly = true;
break;// 猜对后跳出循环
}
// 如果还没到最后一次猜测,提示用户剩余猜测次数
attempts--;
System.out.println("你还剩下 " + attempts + " 次猜测机会。");
}
if (hasGuessedCorrectly) {// 如果用户在4次机会内猜对了数字,则显示“恭喜你,猜对了!”
System.out.println("恭喜你,猜对了!");
} else {// 否则,显示“很遗憾,你没有猜对,正确答案是X(目标数字)”。
System.out.println("很遗憾,你没有猜对,正确答案是 " + targetNumber);
}
scanner.close();// 关闭Scanner资源,防止内存泄漏
}
}
(三)该题涉及的知识点总结
1. 随机数生成
在 Java 里,java.util.Random 类或者 java.util.concurrent.ThreadLocalRandom 类可用于生成随机数。在猜数字游戏中,要生成 1 到 100 之间的随机整数作为目标数字。
2. 输入处理
java.util.Scanner 类能从标准输入(控制台)读取用户输入的信息,在游戏里借助该类获取用户猜测的数字。
3. 循环结构
for 循环或者 while 循环可用来控制用户猜测的次数,保证用户仅有 4 次猜测机会。
4. 条件判断
if-else 语句用于判断用户猜测的数字和目标数字的大小关系,并且给出相应的提示信息。
5. 程序流程控制
利用 break 语句在用户猜对数字时提前结束循环,提高程序的执行效率。
6.资源管理
及时关闭Scanner资源,避免内存泄漏。
二、用Java实现不同员工薪资计算(继承与方法重写的应用)
定义一个父类Employee,包含属性name(姓名)、salary(月薪),
并定义一个方法getAnnualSalary用于计算员工的年薪。
定义子类Manager和Staff,其中Manager类还有一个bonus用于表示经理的年度奖金,Staff类没有
额外的属性。要求如下:
1)父类包含带参数构造方法,子类继承父类构造方法。
2)创建Manager、Staff对象,调用getSalary方法计算输出各自的年薪。
(一)完整代码
package question2;
// 定义父类 Employee,包含员工基本信息和年薪计算方法
class Employee {
private String name;
private double salary;
// 带参数的构造方法,初始化员工姓名和月薪
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
// 计算员工的年薪
public double getAnnualSalary() {
return salary * 12;
}
// 获取员工姓名
public String getName() {
return name;
}
// 获取员工月薪
public double getSalary() {
return salary;
}
}
// 定义 Manager 类,继承自 Employee 类
class Manager extends Employee {
// 定义私有属性 bonus,用于存储经理的年度奖金
private double bonus;
// 子类构造方法,调用父类构造方法初始化员工基本信息,并初始化奖金
public Manager(String name, double salary, double bonus) {
// 调用父类 Employee 的构造方法,初始化员工姓名和月薪
super(name, salary);
// 初始化当前 Manager 对象的年度奖金
this.bonus = bonus;
}
// 重写父类的 getAnnualSalary 方法,计算年薪时加上年度奖金
@Override
public double getAnnualSalary() {
// 调用父类的 getAnnualSalary 方法获取基本年薪,再加上年度奖金
return super.getAnnualSalary() + bonus;
}
}
// 定义 Staff 类,继承自 Employee 类
class Staff extends Employee {
// 子类构造方法,调用父类构造方法初始化员工基本信息
public Staff(String name, double salary) {
super(name, salary);
}
// 无额外属性,直接继承父类的 getAnnualSalary 方法,无需重写
}
// 主类,程序入口
public class Main {
public static void main(String[] args) {
// 创建 Manager 对象,月薪 10000,年度奖金 50000
Manager manager = new Manager("张三", 10000, 50000);
// 创建 Staff 对象,月薪 5000
Staff staff = new Staff("李四", 5000);
// 输出各自的年薪
System.out.println(manager.getName() + "的年薪是:" + manager.getAnnualSalary() + "元");
System.out.println(staff.getName() + "的年薪是:" + staff.getAnnualSalary() + "元");
}
}
(二)该题涉及的知识点总结
1. 类与对象
类:类是对现实世界中具有相同特征和行为事物的抽象描述。本题里,Employee 作为父类,代表员工这一抽象概念;Manager 和 Staff 是子类,分别对应经理和普通员工。
对象:对象是类的实例。在主程序里,借助 new 关键字创建 Manager 和 Staff 类的对象,让抽象的类有了具体的实例。
2. 封装
封装指将数据和操作数据的方法绑定在一起,隐藏对象内部实现细节,仅对外提供公共访问方式。Employee 类把 name 和 salary 属性声明为 private,再通过 getter 方法供外部访问,保证了数据的安全性和可控性。
3. 继承
继承允许一个类继承另一个类的属性和方法,提高代码复用性。Manager 和 Staff 类通过 extends 关键字继承 Employee 类的属性和方法,这样无需重复编写父类已有的代码,减少了代码冗余。
4. 构造方法
构造方法用于创建对象时初始化对象属性。Employee 类定义了带参数的构造方法,Manager 和 Staff 类则通过 super 关键字调用父类构造方法,对从父类继承的属性进行初始化,确保对象属性在创建时就有合适的值。
5. 方法重写
方法重写即子类可以重写父类方法以实现特定功能。Manager 类重写了 Employee 类的 getAnnualSalary 方法,在计算年薪时加上年度奖金,体现了子类对父类方法的扩展和定制。
6. 多态(潜在应用)
本题代码虽未显式体现多态,但基于继承和方法重写具备实现多态的基础。多态能让父类引用指向子类对象,调用重写后的方法,增强代码的灵活性和可扩展性
三、用Java实现正方体和球体表面积与体积计算(接口的应用)
定义IShape接口,接口中包含方法area()(计算表面积), volumn()(计算体积)。
定义类 Cuble(正方体),Sphere(球体)实现IShape接口。要求如下:
1)类中定义带参数构造方法。
2)创建两个类对象,调用area()及volumn(),计算输出各自表面积及体积。
(一)完整代码
package question3;
// 定义 IShape 接口,包含计算表面积和体积的抽象方法
interface IShape {
// 计算表面积
double area();
// 计算体积
double volume();
}
// 定义正方体类,实现 IShape 接口
class Cuble implements IShape {
// 正方体边长
double side;
// 带参数的构造方法,初始化边长
public Cuble(double side) {
this.side = side;
}
// 重写接口的 area 方法,计算正方体表面积
@Override
public double area() {
return side * side * 6;
}
// 重写接口的 volume 方法,计算正方体体积
@Override
public double volume() {
return side * side * side;
}
}
// 定义球体类,实现 IShape 接口
class Sphere implements IShape {
// 球体半径
double radius;
// 带参数的构造方法,初始化半径
public Sphere(double radius) {
this.radius = radius;
}
// 重写接口的 area 方法,计算球体表面积
@Override
public double area() {
return 4 * Math.PI * radius * radius;
}
// 重写接口的 volume 方法,计算球体体积
@Override
public double volume() {
return (4.0 / 3.0) * Math.PI * radius * radius * radius;
}
}
// 主类,程序入口
public class Main {
public static void main(String[] args) {
// 创建正方体对象,边长为 5
Cuble cube = new Cuble(5);
// 创建球体对象,半径为 3
Sphere sphere = new Sphere(3);
// 输出正方体的表面积和体积
System.out.println("正方体的表面积是:" + cube.area());
System.out.println("正方体的体积是:" + cube.volume());
// 输出球体的表面积和体积
System.out.println("球体的表面积是:" + sphere.area());
System.out.println("球体的体积是:" + sphere.volume());
}
}
(二)该题涉及的知识点总结
1. 接口(Interface)
概念:接口是一种抽象类型,它定义了一组方法的签名,但不包含方法的具体实现。IShape 接口定义了 area() 和 volume() 方法,作为不同形状类需要实现的规范。
作用:接口提供了一种标准和契约,使得不同的类可以遵循相同的规则,实现多态性,增强代码的可扩展性和可维护性。
2. 类的实现(Implements)
概念:类使用 implements 关键字来实现接口,实现类必须实现接口中定义的所有抽象方法。Cuble 类和 Sphere 类通过实现 IShape 接口,分别实现了计算表面积和体积的具体逻辑。
作用:实现接口能让类具备接口定义的行为,同时根据自身特性实现不同的功能,体现了面向对象编程的多态性。
3. 构造方法
概念:构造方法是类中的特殊方法,用于在创建对象时初始化对象的属性。Cuble 类和 Sphere 类的构造方法分别接收边长和半径作为参数,对对象的属性进行初始化。
作用:确保对象在创建时就拥有必要的初始状态,提高代码的健壮性。
4. 方法重写(Override)
概念:子类或实现类对父类或接口中已有的方法进行重新定义,方法名、参数列表和返回值类型需与被重写的方法一致。Cuble 类和 Sphere 类重写了 IShape 接口的 area() 和 volume() 方法,根据各自的几何特性实现不同的计算逻辑。
作用:实现多态,允许不同的对象对同一消息做出不同的响应,增强代码的灵活性。
5. 多态
概念:多态是指同一个方法调用,由于对象不同可能会有不同的行为。虽然在 main 方法中没有显式使用接口引用调用方法,但代码具备多态的基础,例如可以用 IShape 接口引用指向 Cuble 或 Sphere 对象。
作用:提高代码的可扩展性和可维护性,使代码可以处理不同类型的对象,而无需关心对象的具体类型。
6. 数学计算
概念:在计算表面积和体积时,需要运用数学公式。代码中使用了正方体和球体的表面积、体积公式,还借助 Math.PI 常量来完成球体相关计算。
作用:实现具体的业务逻辑,得出正确的计算结果。
四、用Java实现圆半径合法性校验(自定义异常处理的应用)
对圆的半径进行异常处理,
首先定义InvalidRadiusException类,用于创建自定义异常类,在该类中,使用构造函数中对半径进行赋值。
再者,定义CircleWithCustomException类,在该类中对于非法赋值的半径进行抛出异常,(注意对属性radius进行赋值的方法有两种,一种是构造函数,另一种是setRadius()函数)
最后,定义TestCircleWithCustomException类,在该类中创建main函数用于处理异常。
(一)完整代码
package question4;
// 自定义异常类,用于处理圆半径非法的情况
class InvalidRadiusException extends Exception {
// 存储非法的半径值
private double radius;
// 构造函数,接收非法半径值并初始化异常信息
public InvalidRadiusException(double radius) {
super("非法的半径值: " + radius);
this.radius = radius;
}
// 获取非法半径值
public double getRadius() {
return radius;
}
}
// 圆类,对圆的半径进行异常处理
class CircleWithCustomException {
// 圆的半径,使用 private 保证封装性
private double radius;
// 无参构造函数,默认半径为 1.0,可能抛出异常
public CircleWithCustomException() throws InvalidRadiusException {
this(1.0);
}
// 带参构造函数,接收半径值,可能抛出异常
public CircleWithCustomException(double radius) throws InvalidRadiusException {
// 调用 setRadius 方法设置半径,该方法会检查半径合法性
setRadius(radius);
}
// 获取圆的半径
public double getRadius() {
return radius;
}
// 设置圆的半径,对非法半径抛出异常
public void setRadius(double radius) throws InvalidRadiusException {
if (radius < 0) {
throw new InvalidRadiusException(radius);
}
this.radius = radius;
}
// 计算圆的周长
public double getPerimeter() {
return 2 * Math.PI * radius;
}
// 计算圆的面积
public double getArea() {
return Math.PI * radius * radius;
}
}
// 测试类,用于测试圆半径的异常处理
public class TestCircleWithCustomException {
public static void main(String[] args) {
try {
// 创建半径为 5.0 的圆对象
CircleWithCustomException circle1 = new CircleWithCustomException(5.0);
System.out.println("圆 1 的周长: " + circle1.getPerimeter());
System.out.println("圆 1 的面积: " + circle1.getArea());
// 尝试创建半径为 -2.0 的圆对象,会抛出异常
CircleWithCustomException circle2 = new CircleWithCustomException(-2.0);
System.out.println("圆 2 的周长: " + circle2.getPerimeter());
System.out.println("圆 2 的面积: " + circle2.getArea());
} catch (InvalidRadiusException e) {
// 捕获并处理异常,输出异常信息
System.out.println("捕获到异常: " + e.getMessage());
}
}
}
(二)该题涉及的知识点总结
1. 自定义异常类
概念:Java 允许开发者创建自定义异常类,以满足特定业务需求。本题中 InvalidRadiusException 继承自 Exception,用于处理圆半径为负数这种非法情况。
作用:通过自定义异常,能更精准地描述程序中出现的问题,让异常处理逻辑更具针对性,提高代码的可读性和可维护性。
2. 异常抛出
概念:在方法中,若遇到不符合业务规则的情况,可使用 throw 关键字抛出异常对象。CircleWithCustomException 类的构造函数和 setRadius 方法在半径值为负数时,抛出 InvalidRadiusException 异常。
作用:将异常情况从正常业务逻辑中分离出来,让调用者处理异常,使代码逻辑更清晰。
3. 异常声明
概念:当一个方法可能抛出异常时,需使用 throws 关键字在方法签名中声明。CircleWithCustomException 类的构造函数和 setRadius 方法声明了可能抛出 InvalidRadiusException 异常。
作用:告知调用者该方法可能会抛出的异常类型,提醒调用者进行异常处理。
4. 异常捕获与处理
概念:使用 try-catch 语句捕获并处理异常。在 TestCircleWithCustomException 类的 main 方法中,使用 try-catch 块捕获 InvalidRadiusException 异常,并输出异常信息。
作用:避免程序因异常而崩溃,增强程序的健壮性,同时能对异常情况进行相应处理。
5. 封装
概念:将类的属性私有化,通过公共方法访问和修改。CircleWithCustomException 类的 radius 属性被声明为 private,并通过 getRadius 和 setRadius 方法进行访问和修改。
作用:隐藏类的内部实现细节,保护数据的安全性,同时提供统一的访问接口,便于代码的维护和扩展。
6. 构造函数
概念:构造函数用于创建对象时初始化对象的属性。CircleWithCustomException 类提供了无参和带参构造函数,方便不同场景下创建对象。
作用:确保对象在创建时就具备合适的初始状态,提高代码的健壮性。
五、用Java实现随机字母生成与指定元素统计(集合的应用)
创建集合用于存放随机生成的40个小写字母;
并且编写方法alphaCount(Collection list, String str)统计集合中指定元素(a、m、z)出现的次数。
(一)完整代码
package question5;
// 导入 java.util 包下的 ArrayList 类,它是一个实现了 List 接口的动态数组类,
// 可用于存储和操作一组对象,这里用来存储随机生成的字符
import java.util.ArrayList;
// 导入 java.util 包下的 Collection 接口,它是集合框架的根接口,
// 定义了集合的基本操作,如添加、删除、遍历等,作为方法参数类型增加代码的通用性
import java.util.Collection;
// 导入 java.util 包下的 Random 类,用于生成伪随机数,
// 这里利用它生成随机数来获取随机小写字母对应的 ASCII 码值
import java.util.Random;
// 定义 RandomLetterCounter 类,用于生成随机字母集合并统计指定字母出现次数
public class RandomLetterCounter {
public static void main(String[] args) {
// 调用 generateRandomLetters 方法,生成包含 40 个随机小写字母的集合
Collection<Character> letters = generateRandomLetters(40);
// 提示用户下面将输出生成的随机字母集合
System.out.println("生成的随机字母集合:");
// 输出生成的随机字母集合
System.out.println(letters);
// 提示用户下面将输出统计结果
System.out.println("\n统计结果:");
// 调用 alphaCount 方法,统计字母 'a' 在集合中出现的次数并输出
System.out.println("字母 'a' 出现的次数:" + alphaCount(letters, "a"));
// 调用 alphaCount 方法,统计字母 'm' 在集合中出现的次数并输出
System.out.println("字母 'm' 出现的次数:" + alphaCount(letters, "m"));
// 调用 alphaCount 方法,统计字母 'z' 在集合中出现的次数并输出
System.out.println("字母 'z' 出现的次数:" + alphaCount(letters, "z"));
}
// 编写一个方法:生成指定数量的随机小写字母集合
public static Collection<Character> generateRandomLetters(int count) {
// 创建一个 ArrayList 实例,用于存储随机生成的小写字母
Collection<Character> letters=new ArrayList<>();
// 创建 Random 类的实例,用于生成随机数
Random random=new Random();
// 循环指定次数,生成对应数量的随机小写字母
for(int i=0;i<count;i++){
// 调用 Random 类的 nextInt 方法,生成 0 到 25 之间的随机整数,
// 加上 97 得到 97 到 122 之间的整数,对应小写字母的 ASCII 码
char c=(char)(random.nextInt(26)+97);
// 将生成的随机字符添加进集合中
letters.add(c);
}
// 返回存储随机小写字母的集合
return letters;
}
// 编写一个方法:统计集合中指定元素出现的次数
public static int alphaCount(Collection<Character> list, String str){
// 检查传入的字符串是否为 null 或者长度不为 1,如果是则直接返回 0
if(str==null||str.length()!=1){
return 0;
}
// 从传入的字符串中提取目标字符
char target=str.charAt(0);
// 初始化计数器,用于记录目标字符出现的次数
int count=0;
// 遍历集合中的每个元素
for(Character c : list)
// 检查当前元素不为 null 且等于目标字符
if (c != null && c == target) {
// 若满足条件,计数器加 1
count++;
}
// 返回目标字符在集合中出现的次数
return count;
}
}
(二)该题涉及的知识点总结
1. Java 类库的使用
java.util.ArrayList:ArrayList 是 Java 集合框架中的动态数组实现类,继承自 AbstractList 并实现了 List 接口。在代码里用它来存储随机生成的小写字母。它的优势在于可以根据元素数量自动扩容,提供了方便的元素添加、访问和操作方法。
java.util.Collection:Collection 是 Java 集合框架的根接口,定义了集合的基本操作,如添加、删除、遍历等。代码中把 Collection 作为方法参数类型,提高了代码的通用性,使得 generateRandomLetters 和 alphaCount 方法可以处理不同类型的集合实现。
java.util.Random:Random 类用于生成伪随机数。在生成随机小写字母时,借助 Random 类的 nextInt 方法生成 0 到 25 之间的随机整数,再加上 97 得到对应小写字母的 ASCII 码值,从而生成随机小写字母。
2. 方法的定义与调用
方法定义:代码中定义了 generateRandomLetters 和 alphaCount 两个方法。generateRandomLetters 方法接收一个整数参数,用于生成指定数量的随机小写字母并存储在集合中;alphaCount 方法接收一个集合和一个字符串,用于统计集合中指定字符出现的次数。方法的定义让代码模块化,提高了代码的复用性和可维护性。
方法调用:在 main 方法中调用 generateRandomLetters 生成随机字母集合,再调用 alphaCount 方法统计指定字母(a、m、z)出现的次数,最后将结果输出。
3. 循环结构
for 循环:在 generateRandomLetters 方法中,使用 for 循环控制随机字母的生成次数,循环指定次数,每次生成一个随机小写字母并添加到集合中。在 alphaCount 方法中,使用增强 for 循环遍历集合中的元素,对符合条件的元素进行计数。
4. 条件判断
if 语句:在 alphaCount 方法中,使用 if 语句检查传入的字符串是否为 null 或者长度是否不为 1,如果是则直接返回 0。在遍历集合元素时,也使用 if 语句判断当前元素是否等于目标字符,若是则计数器加 1。条件判断能让程序根据不同情况执行不同的逻辑。
5. 字符与 ASCII 码的转换
代码里通过 (char)(random.nextInt(26) + 97) 将 97 到 122 之间的整数转换为对应的小写字母。因为在 ASCII 码表中,97 对应小写字母 a,122 对应小写字母 z,利用这种转换可以生成随机小写字母。
六、用Java实现火车时刻表文件读写操作(文件的应用)
有一个火车时刻表文件(文件名及存放路径自定),内容如左下图,
请编写程序读出该文件内容并显示在屏幕上,
然后用键盘输入一个南昌到你家乡的火车时刻信息(数据自定)
并添加在该文件的后面如右下图最后一行。
(一)完整代码
package question6;
// 导入 Java 输入输出流相关的类,用于文件的读写操作
// FileReader 用于从文件中读取字符流
import java.io.FileReader;
// BufferedReader 用于缓冲字符输入流,提高读取效率
import java.io.BufferedReader;
// FileWriter 用于向文件中写入字符流
import java.io.FileWriter;
// BufferedWriter 用于缓冲字符输出流,提高写入效率
import java.io.BufferedWriter;
// IOException 是输入输出操作可能抛出的异常类
import java.io.IOException;
// 导入 Scanner 类,用于从标准输入(键盘)读取用户输入
import java.util.Scanner;
// 火车时刻表文件操作类,实现火车时刻表文件的读取和追加功能
public class TrainScheduleFileOperation {
public static void main(String[] args) {
// 定义文件路径,根据实际文件存放位置修改
// 注意:Windows 系统中路径分隔符需要使用双反斜杠转义
String filePath = "D:\\Software Engineering\\2.Java study\\Bit-Java-2028\\1.JavaSE_Code\\JavaExamination2024\\src\\question6\\TrainTime_Table";
// 1. 读取并显示现有时刻表
readTrainSchedule(filePath);
// 2. 添加新的火车时刻表信息
addNewTrainSchedule(filePath);
}
// 读取火车时刻表文件内容并显示
public static void readTrainSchedule(String filePath) {
// 使用 try-with-resources 语句,自动关闭资源(BufferedReader)
// 创建 BufferedReader 对象,用于从文件中读取内容
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
// 逐行读取文件直到文件末尾(readLine 返回 null)
while ((line = br.readLine()) != null) {
// 输出每行内容
System.out.println(line);
}
} catch (IOException e) {
// 处理文件读取异常(如文件不存在、权限不足等)
e.printStackTrace();
}
}
// 添加新的火车时刻信息到文件末尾
public static void addNewTrainSchedule(String filePath) {
// 创建 Scanner 对象,用于从标准输入读取用户输入
Scanner scanner = new Scanner(System.in);
// 提示用户输入新的火车时刻表信息(按指定格式)
System.out.println("请输入南昌到你家乡的火车时刻信息(格式:出发地 目的地 车次 出发时间 到达时间):");
// 读取用户输入的一行内容
String newSchedule = scanner.nextLine();
// 使用 try-with-resources 自动关闭资源(BufferedWriter)
// FileWriter 第二个参数 true 表示以追加模式打开文件
try (BufferedWriter bw = new BufferedWriter(new FileWriter(filePath, true))) {
// 添加换行符,确保新内容从新行开始
bw.newLine();
// 写入用户输入的新时刻表信息
bw.write(newSchedule);
} catch (IOException e) {
// 处理文件写入异常
e.printStackTrace();
} finally {
// 确保 Scanner 资源被关闭
scanner.close();
}
}
}
(二)该题涉及的知识点总结
1. Java 输入输出流
字符输入流:
FileReader:FileReader 是 Java 中用于从文件读取字符流的类,它继承自 InputStreamReader。在本题中,FileReader 作为基础的文件读取工具,为 BufferedReader 提供数据源,用于读取火车时刻表文件的内容。
BufferedReader:BufferedReader 是一个缓冲字符输入流,它从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。通过 BufferedReader 的 readLine() 方法可以逐行读取文件内容,提高了文件读取的效率。
字符输出流:
FileWriter:FileWriter 用于向文件中写入字符流,继承自 OutputStreamWriter。在本题中,FileWriter 以追加模式(第二个参数 true)打开文件,使得新的火车时刻表信息可以添加到文件末尾。
BufferedWriter:BufferedWriter 是缓冲字符输出流,它将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。通过 BufferedWriter 的 write() 方法将用户输入的新时刻表信息写入文件,newLine() 方法用于添加换行符。
2. 异常处理
IOException:输入输出操作可能会因为多种原因失败,如文件不存在、权限不足等,这些异常都继承自 IOException。在本题中,使用 try-catch 块捕获 IOException,并通过 e.printStackTrace() 打印异常堆栈信息,方便调试和定位问题。
try-with-resources 语句:这是 Java 7 引入的语法糖,用于自动关闭实现了 AutoCloseable 接口的资源。在本题中,BufferedReader 和 BufferedWriter 都实现了 AutoCloseable 接口,使用 try-with-resources 语句可以确保在操作完成后或发生异常时,这些资源会被自动关闭,避免资源泄漏。
3. 用户输入处理
Scanner 类:Scanner 类是 Java 中用于获取用户输入的工具类,它可以从多种输入源读取数据,本题中从标准输入(键盘)读取用户输入的新火车时刻表信息。通过 scanner.nextLine() 方法读取用户输入的一整行内容。使用完 Scanner 后,需要调用 close() 方法关闭资源,避免资源泄漏,本题在 finally 块中确保 Scanner 被关闭。
4. 文件操作
文件路径:在 Windows 系统中,文件路径使用双反斜杠 \\ 进行转义,因为反斜杠在 Java 字符串中有转义的作用。本题中明确指定了火车时刻表文件的路径,开发时可根据实际情况修改。
文件追加:通过 FileWriter 的构造函数第二个参数 true 实现文件的追加模式,使得新的火车时刻表信息可以添加到文件末尾,而不会覆盖原有的内容。
5. 方法封装
本题将文件读取和文件追加操作分别封装在 readTrainSchedule 和 addNewTrainSchedule 方法中,提高了代码的模块化程度和可维护性。main 方法作为程序的入口,调用这两个方法完成整个火车时刻表文件的读取和新信息添加的操作。
七、用Java多线程实现 1 - 100 奇数偶数求和(多线程的应用)
编写多线程程序,在主线程main中启动两个线程,要求如下:
1)线程1计算1-00之间的奇数之和
2)线程2计算1-100之间的偶数之和
3)主线程main中打印出上述两个线程计算的结果之和。
(一)完整代码
package question7;
// 导入 Thread 类,Java 中用于创建和操作线程的基础类
import java.lang.Thread;
// 定义主类,程序的入口点
public class OddEvenSumThread {
public static void main(String[] args) throws InterruptedException {
// 创建并启动奇数计算线程
// 实例化 SumThread 类,传入线程名 "奇数线程" 和标记是否计算奇数的 true
SumThread oddThread = new SumThread("奇数线程", true);
// 创建并启动偶数计算线程
// 实例化 SumThread 类,传入线程名 "偶数线程" 和标记是否计算奇数的 false
SumThread evenThread = new SumThread("偶数线程", false);
// 调用 start 方法启动奇数线程,使其进入就绪状态,等待 CPU 调度执行
oddThread.start();
// 调用 start 方法启动偶数线程,使其进入就绪状态,等待 CPU 调度执行
evenThread.start();
// 等待奇数线程执行完毕,主线程会阻塞直到 oddThread 线程执行结束
oddThread.join();
// 等待偶数线程执行完毕,主线程会阻塞直到 evenThread 线程执行结束
evenThread.join();
// 计算并输出结果
// 调用 getSum 方法获取奇数线程的计算结果,并与偶数线程的计算结果相加
int totalSum = oddThread.getSum() + evenThread.getSum();
// 打印奇数和偶数的总和
System.out.println("奇数和偶数的总和为: " + totalSum);
}
// 自定义线程类,继承自 Thread 类,用于计算奇数或偶数的和
static class SumThread extends Thread {
// 标记是否计算奇数,若为 true 则计算奇数和,否则计算偶数和
private final boolean isOdd;
// 存储计算结果的变量
private int sum;
// 构造函数,接收线程名和是否计算奇数的标记
public SumThread(String name, boolean isOdd) {
// 调用父类 Thread 的构造函数,设置线程名
super(name);
// 初始化是否计算奇数的标记
this.isOdd = isOdd;
}
// 重写 run 方法,线程启动后会执行此方法中的逻辑
@Override
public void run() {
// 初始化计算结果为 0
sum = 0;
// 根据 isOdd 标记确定起始值,若计算奇数从 1 开始,若计算偶数从 2 开始
int start = isOdd ? 1 : 2;
// 循环从起始值开始,每次递增 2,遍历 1 到 100 之间的奇数或偶数
for (int i = start; i <= 100; i += 2) {
// 将当前奇数或偶数累加到 sum 中
sum += i;
}
// 打印当前线程计算完成的信息以及计算结果
System.out.println(getName() + "计算完成,结果为: " + sum);
}
// 获取计算结果的方法
public int getSum() {
// 返回计算得到的奇数或偶数的和
return sum;
}
}
}
(二)该题涉及的知识点总结
1. 多线程基础
线程创建:通过继承 Thread 类创建自定义线程类 SumThread。在 Java 中,创建线程有两种常见方式,继承 Thread 类和实现 Runnable 接口,本题采用继承 Thread 类的方式,重写 run 方法来定义线程要执行的任务。
线程启动:在 main 方法中,调用 Thread 对象的 start() 方法启动线程。start() 方法会让线程进入就绪状态,等待操作系统调度执行,而不是直接调用 run() 方法,直接调用 run() 方法不会启动新线程,只是普通的方法调用。
2. 线程同步与等待
join() 方法:main 方法中使用 oddThread.join() 和 evenThread.join() 让主线程等待两个子线程执行完毕。join() 方法会使当前线程阻塞,直到调用该方法的线程执行结束,确保主线程在获取子线程计算结果之前,子线程已经完成计算任务。
3. 线程执行逻辑
任务划分:通过 isOdd 布尔变量区分两个线程的任务,一个线程计算 1 - 100 之间的奇数和,另一个线程计算 1 - 100 之间的偶数和。在 run() 方法中,根据 isOdd 的值确定起始值,使用 for 循环遍历并累加相应的数字。
4. 类的设计与封装
自定义线程类:SumThread 类封装了线程的执行逻辑和计算结果。使用 private 修饰 isOdd 和 sum 变量,保证数据的封装性,通过构造函数初始化线程名称和计算任务类型,提供 getSum() 方法供外部获取计算结果。
5. 异常处理
InterruptedException:main 方法声明抛出 InterruptedException 异常。join() 方法可能会被其他线程中断,当线程在等待过程中被中断时,会抛出 InterruptedException 异常,调用者需要处理该异常,本题选择将异常抛出给调用者处理。
6. 代码输出与结果汇总
结果汇总:在 main 方法中,等待两个子线程执行完毕后,通过调用 getSum() 方法获取两个线程的计算结果并相加,最后打印出奇数和偶数的总和,实现了多线程计算结果的汇总。
结语
如果你觉得这篇博客对你有帮助,别忘了点赞、评论和关注哦!你们的支持是我持续创作的动力,后续还会分享更多关于 Java 和其他编程语言的学习资料和考试经验。祝大家期末考试顺利,一起加油!