Java期末复习

一个简单的小复习,后面要详细再细细学习,祝Java考试大捷😎!

PPT-1

问题:

为什么Java编程语言被认为是平台独立的?

答案:

编译后的代码可以在多个平台上运行,几乎无需修改。

解释:

Java被认为是平台独立的,因为它采用了一次编写,到处运行的理念。当Java代码被编译时,它被转换成字节码(而非本机机器码),并保存在.class文件中。然后,这些字节码可以在任何安装了Java虚拟机(JVM)的平台上运行,无论底层的硬件和操作系统是什么。只要平台上有JVM,编译后的Java代码就能不做修改地运行。这就是Java平台独立性的来源。

以下是其他选项为何不正确的解释: - 不允许使用指针来操作内存:虽然Java不允许指针操作(以避免错误并增强安全性),但这并不是Java平台独立性的直接原因。 - 编译后的Java程序格式是CPU特定的代码:这是错误的。Java编译后的代码是字节码,而不是CPU特定的机器码。JVM在运行时将字节码转换为平台特定的机器码。 - 它是多线程的:虽然Java支持多线程,但这与其平台独立性没有直接关系。多线程主要涉及并发执行,而不是Java代码在多个平台上的运行能力。

问题:

The Java technology product group that is designed for developing applications for consumer devices is _______.

选项:

    1. Java SE JDK
    1. Java ES SDK
    1. Java EE SDK
    1. Java ME SDK

答案:

  1. Java ME SDK

解释:

Java ME (Micro Edition) 是为嵌入式和移动设备(例如手机、消费类电子产品等)设计的Java技术平台。Java ME SDK提供了开发、测试和部署Java应用程序的工具,专门用于低功耗设备和资源受限的环境,因此它适用于消费设备。

其他选项解释:

  • Java SE JDK (Standard Edition):这是用于开发桌面、服务器和嵌入式系统应用程序的Java开发工具包,但它不是专门为消费类设备设计的。
  • Java ES SDK (Enterprise System):这是为企业应用开发设计的Java技术平台,通常用于构建大型企业级应用系统。
  • Java EE SDK (Enterprise Edition):这是面向企业级应用开发的Java平台,主要用于构建Web应用、企业应用等,和消费设备开发无关。

Problem 1: Write a program to display "Good Morning".

Java Program:

1
2
3
4
5
public class GoodMorning {
public static void main(String[] args) {
System.out.println("Good Morning");
}
}

Explanation: This Java program creates a class GoodMorning with a main method, which is the entry point of the program. The System.out.println statement is used to display the text "Good Morning" on the console.


Problem 2: What command is used to compile a Java program?

Answer: To compile a Java program, you use the javac command.

Command:

1
javac ProgramName.java

Explanation:

  • The javac command is used to compile Java source code (.java file) into bytecode (.class file).
  • Example: javac GoodMorning.java compiles the GoodMorning.java source file and creates a GoodMorning.class file (bytecode).

Problem 3: What command is used to run a Java program?

Answer: To run a Java program, you use the java command.

Command:

1
java ProgramName

Explanation:

  • The java command is used to run the bytecode (.class file) generated after compilation.
  • Example: java GoodMorning runs the GoodMorning.class file and prints "Good Morning" to the console.

PPT-2

计算未来投资价值的 Java 程序

问题描述:

编写一个程序,输入投资金额、年利率和年数,使用以下公式计算未来的投资价值:

[ = (1 + )^{ } ]

例如,如果输入的金额为 1000,年利率为 3.25%,年数为 1年,则未来投资价值为 1032.98。

提示: 可以使用 Math.pow(a, b) 方法计算 a 的 b 次方。

样例运行:

1
2
3
4
5
样例 1:
输入投资金额:1000
输入年利率:4.25
输入年数:1
累积价值为:1043.34

程序代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.Scanner;

public class Test {
public static void main(String[] args) {
// 创建 Scanner 对象以获取用户输入
java.util.Scanner input = new java.util.Scanner(System.in);

// 输入投资金额
System.out.print("请输入投资金额,例如 120000.95:");
double investmentAmount = input.nextDouble();

// 输入年利率
System.out.print("请输入年利率,例如 8.25:");
double annualInterestRate = input.nextDouble();

// 计算月利率
double monthlyInterestRate = annualInterestRate / 1200;

// 输入年数
System.out.print("请输入投资年数,例如 5:");
int numOfYears = input.nextInt();

// 计算未来投资价值
double futureValue = investmentAmount * Math.pow(1 + monthlyInterestRate, numOfYears * 12);

// 输出未来投资价值,保留两位小数
System.out.print("未来投资价值是:" + (int)(futureValue * 100) / 100.0);
}
}

代码解释:

  1. 输入部分:
    • 使用 Scanner 获取用户输入的投资金额、年利率和年数。
  2. 计算月利率:
    • monthlyInterestRate = annualInterestRate / 1200,因为年利率是百分比,所以要除以 1200 将年利率转换为月利率。
  3. 计算未来价值:
    • 使用公式 futureValue = investmentAmount * Math.pow(1 + monthlyInterestRate, numOfYears * 12) 计算未来价值。Math.pow(a, b) 方法计算 ab 次方。
  4. 输出未来价值:
    • System.out.print("未来投资价值是:" + (int)(futureValue * 100) / 100.0); 通过乘以 100 转换为整数,再除以 100 保留两位小数。

示例输入输出:

  • 输入:
    • 投资金额:1000
    • 年利率:4.25
    • 年数:1
  • 输出:
    • 未来投资价值是:1043.34

注意事项:

  1. 程序中的 Math.pow(a, b) 用于计算 (1 + 月利率) 的年数乘以12次方
  2. 输出保留两位小数,使用 (int)(futureValue * 100) / 100.0 实现。

PPT-3

PPT-4

寻找最大数及其出现次数

问题描述:

编写一个程序,读取一组整数,找出其中的最大数,并统计其出现的次数。假设输入以数字 0 结束。

例如,输入为 3 5 2 5 5 5 0,程序会输出:

1
2
最大数是 5
最大数的出现次数是 4

提示: - 使用两个变量 maxcount: - max 存储当前的最大值。 - count 存储最大值的出现次数。 - 初始时,将第一个数字赋值给 maxcount 设为 1。 - 比较每个后续数字: - 如果数字大于 max,将其赋值给 max,并重置 count 为 1。 - 如果数字等于 max,则将 count 增加 1。

程序设计思路:

  1. 初始化:
    • 输入的第一个数字赋值给 max
    • 初始化 count 为 1,表示当前最大值第一次出现。
  2. 处理后续数字:
    • 遍历每个输入的数字:
      • 如果数字大于当前的 max,则更新 max 并重置 count 为 1。
      • 如果数字等于 max,则增加 count
  3. 结束条件:
    • 输入结束时,遇到数字 0,停止输入并输出结果。

程序代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.Scanner;

public class LargestNumber {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);

// 初始化max和count
int max = input.nextInt(); // 读取第一个数
int count = 1; // 初始化count为1,表示第一个数字的出现次数

// 输入其他数字
while (true) {
int num = input.nextInt(); // 读取下一个数
if (num == 0) { // 如果输入为0,则结束
break;
}

// 比较并更新最大值和出现次数
if (num > max) {
max = num;
count = 1; // 如果新的最大数,出现次数重置为1
} else if (num == max) {
count++; // 如果等于当前最大数,出现次数加1
}
}

// 输出结果
System.out.println("最大数是 " + max);
System.out.println("最大数的出现次数是 " + count);
}
}

代码解释:

  1. 输入与初始化:
    • int max = input.nextInt(); 读取第一个输入的数字并赋值给 max
    • int count = 1; 初始化 count 为 1,表示第一个数字出现一次。
  2. 循环输入:
    • while (true) 循环持续输入直到遇到 0 为止。
    • 每次输入一个数字,将其赋值给 num
    • 如果 num == 0,则结束输入循环。
  3. 比较并更新:
    • if (num > max):如果当前输入的数字大于 max,则更新 max,并重置 count 为 1,表示当前最大值出现一次。
    • else if (num == max):如果当前数字与 max 相等,则将 count 加 1。
  4. 输出结果:
    • 最后输出 maxcount,分别表示最大数和它的出现次数。

示例输入与输出:

  • 输入:

    1
    3 5 2 5 5 5 0

  • 输出:

    1
    2
    最大数是 5
    最大数的出现次数是 4

边界情况:

  1. 没有输入任何数字:
    • 由于程序设计要求输入以 0 结束,如果没有输入其他数字,程序会直接输出:
      1
      2
      最大数是 0
      最大数的出现次数是 1
  2. 只有一个数字:
    • 如果输入的只有一个数字并且是非零的,那么该数字就是最大值,出现次数为 1。
  3. 所有输入数字相同:
    • 如果所有输入的数字相同,则程序会统计这个数字的出现次数。

PPT-5

PPT-6

计算整数各位数字和的程序

问题描述:

编写一个方法 sumDigits(long n),计算一个整数的各位数字之和。

示例: - 输入:234 - 输出:9(即:2 + 3 + 4)

提示:

  1. 使用 % 运算符来提取数字的最后一位。
    • 例如:234 % 10 提取出 4。
  2. 使用 / 运算符来移除数字的最后一位。
    • 例如:234 / 10 变成 23。

通过不断提取和移除数字,直到所有数字被处理完。

程序设计思路:

  1. sumDigits 方法:
    • 输入:一个长整型数字 n
    • 处理:
      • n 的绝对值,因为负数的每一位数字的和与正数相同。
      • 使用 %/ 运算符提取数字,并累加到 sum 变量中。
      • 使用 while 循环不断处理数字,直到所有位数被处理完。
    • 输出:数字各位数之和。
  2. Test 类:
    • 输入:提示用户输入一个整数。
    • 输出:调用 sumDigits 方法,计算并显示数字的各位和。

程序代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.Scanner;

public class Test {
public static void main(String[] args) {
// 创建扫描器对象以读取用户输入
java.util.Scanner input = new java.util.Scanner(System.in);

// 提示用户输入数字
System.out.print("Enter a number: ");
int value = input.nextInt(); // 读取整数

// 调用 sumDigits 方法并输出结果
System.out.println("The sum of digits for " + value + " is " + sumDigits(value));
}

// 计算数字各位和的方法
public static int sumDigits(long n) {
// 使用 Math.abs() 处理负数,避免负数带来的问题
int temp = (int) Math.abs(n);
int sum = 0;

// 使用循环逐位提取数字并累加
while (temp != 0) {
int remainder = temp % 10; // 提取最后一位
sum += remainder; // 将最后一位加到总和中
temp = temp / 10; // 移除最后一位
}

return sum; // 返回各位和
}
}

程序流程:

  1. 输入:
    • 程序提示用户输入一个整数 value
  2. sumDigits 方法:
    • 将输入的数字 n 取绝对值,处理负数情况。
    • 初始化 temp 为数字的绝对值,并初始化 sum 为 0。
    • 使用 while 循环:
      • 每次通过 % 10 获取当前数字的最后一位。
      • 使用 / 10 去除当前数字的最后一位。
      • 将提取的数字累加到 sum 中。
    • 循环直到所有数字处理完。
    • 返回 sum
  3. 输出:
    • 程序输出用户输入数字的各位数字之和。

示例运行:

  • 输入:

    1
    Enter a number: 234

  • 输出:

    1
    The sum of digits for 234 is 9

程序解析:

  1. sumDigits 方法:
    • 首先,方法将输入的数字取绝对值,以便处理负数的情况。
    • 接着,进入 while 循环,每次提取数字的最后一位,并加到 sum 中。
    • 每次通过除以 10 移除数字的最后一位,直到 temp 为 0,表示所有位数处理完。
  2. main 方法:
    • 通过 Scanner 类读取用户输入的整数。
    • 调用 sumDigits 方法,输出结果。

边界情况:

  1. 输入为负数:
    • 例如输入 -234,程序会自动计算其绝对值,输出 9。
  2. 输入为 0:
    • 当输入为 0 时,输出结果应为 0。
  3. 输入为单个数字:
    • 如果输入为单个数字,如 7,则输出结果为该数字本身。

PPT-7

1. 声明数组变量的语句:

  • 声明一个名为 x 的数组,类型为 double

    1
    double[] x;
    • double[] 表示数组的类型为 double 数组。
    • x 是数组的名称。

2. 创建一个大小为 40 的 int 类型数组的表达式:

1
int[] arr = new int[40];
  • int[] 表示该数组是存储 int 类型数据的数组。
  • new int[40] 表示创建一个长度为 40 的 int 数组。

3. java.util.Arrays.binarySearch 的用法:

binarySearch 方法是用于在已排序的数组中进行二分查找。该方法返回目标值的索引,如果目标值不存在,返回负值(其负值加 1 表示插入位置)。

  • java.util.Arrays.binarySearch(new int[]{1, 2, 3, 7, 14}, 7)

    • 数组 new int[]{1, 2, 3, 7, 14} 是一个已排序的数组。
    • 目标值为 7
    • 结果:binarySearch 会返回目标值 7 在数组中的索引,索引为 3(从 0 开始计数)。

    输出:

    1
    3

  • java.util.Arrays.binarySearch(new int[]{1, 2, 3, 7, 14}, 5)

    • 数组 new int[]{1, 2, 3, 7, 14} 中没有值为 5 的元素。
    • 由于 5 不在数组中,binarySearch 方法返回的是插入位置的负值加 1,即 -4
    • 这是因为 5 应该插入到索引 37 前面)的位置,所以返回的负数是 -(3 + 1),即 -4

    输出:

    1
    -4


4. 代码的输出分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {

public static void main(String[] args) {
int number = 0;
int[] numbers = new int[1];

m(number, numbers);

System.out.println("number is " + number + " and numbers[0] is " + numbers[0]);
}

public static void m(int x, int[] y) {
x = 3;
y[0] = 3;
}
}

解释:

  1. number 变量
    • main 方法中,number 被初始化为 0
    • 在调用 m(number, numbers) 时,number 被按值传递给方法 m,因此在方法内修改 xnumber 没有任何影响,number 的值保持为 0
  2. numbers 数组
    • numbers 数组被初始化为大小为 1 的数组,且初始值为 0
    • m 方法中,numbers[0] 被设置为 3,因此这个数组的值被修改为 3
    • 注意:数组是按引用传递的,所以在方法内修改 numbers[0] 会影响到 main 方法中的 numbers 数组。

程序输出:

1
number is 0 and numbers[0] is 3
  • number 的值未改变,仍为 0
  • numbers[0] 被修改为 3,所以输出为 3

5. 精华所在:

  • 声明和初始化数组: 可以通过 double[] xint[] arr 声明数组,并通过 new 关键字初始化数组。
  • 二分查找: Arrays.binarySearch 用于在已排序的数组中查找元素。返回值为目标值的索引,若不存在该值则返回负值。
  • 值传递与引用传递:
    • 值传递: 基本数据类型(如 int)是按值传递的,修改方法内的变量不会影响外部变量。
    • 引用传递: 对象(如数组)是按引用传递的,方法内对对象的修改会影响外部对象的状态。

PPT-9

1. 声明 Circle 对象的变量:

  • 声明一个 Circle 类型的变量 x
1
Circle x;
  • 这里 Circle 是对象类型,x 是对象变量,用于引用一个 Circle 类的对象。

2. 使用无参构造函数创建 Circle 对象:

1
Circle circle = new Circle();
  • Circle() 是无参数构造函数,它会创建一个默认的 Circle 对象。

3. 声明 Circle 类型的变量并初始化:

1
Circle x = new Circle(5.5);
  • 这里 Circle(5.5) 假设构造函数 Circle(double radius) 已存在,用来初始化一个半径为 5.5Circle 对象。

4. 调用 getArea() 方法返回 Circle 对象的面积:

1
double area = c.getArea();
  • 假设 c 是一个已经创建的 Circle 对象,并且 getArea() 方法存在,用来计算并返回圆的面积。

5. 创建一个 Date 对象,表示当前时间:

1
Date currentDate = new Date();
  • Date 类表示一个具体的时间点。通过 new Date() 创建一个表示当前时间的 Date 对象。

6. 代码输出分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test { 
public static void main(String[] args) {
A a1 = new A();
System.out.print(a1.j);
A a2 = new A();
System.out.print(" " + a2.j);
}
}

class A {
int i = 1;
static int j = 1;

A() {
i++;
j++;
}
}

解释:

  1. A 的成员变量:
    • i 是实例变量,属于每个 A 对象。每次创建 A 类的实例时,i 都会初始化为 1,并且在构造函数中 i 会增加 1,所以每个对象的 i 值会是 2
    • j 是静态变量,属于类 A,而不是某个实例。静态变量在类的所有实例之间共享,所以 j 只有一个值,所有对象都共享这个值。
  2. 构造函数:
    • 每次创建 A 类的实例时,构造函数会被调用,ij 都会被加 1。由于 i 是实例变量,因此每个实例的 i 都会独立增加。而 j 是静态变量,因此所有实例共享 j,它只会增加一次。
  3. 代码执行:
    • 创建 a1 对象时,i 被初始化为 1,然后在构造函数中增加 1,所以 i 变为 2。同时,静态变量 j 也被加 1,所以 j 变为 2
    • 创建 a2 对象时,i 也被初始化为 1,然后增加 1,所以 i 变为 2。但是静态变量 j 在两个对象之间共享,已经增加过一次,所以 j 变为 3

输出:

1
2 3
  • 第一个输出是 a1.j,由于 j 被加过一次,a1.j 的值为 2
  • 第二个输出是 a2.jj 被加过两次,所以 a2.j 的值为 3

7. 归纳:

  • 实例变量与静态变量:
    • 实例变量(如 i)是每个对象独有的,每次创建一个对象时,都会为实例变量分配一个新值。
    • 静态变量(如 j)是类级别的变量,所有对象共享一个静态变量,因此它的变化会影响到所有对象。
  • 构造函数
    • 在每个对象创建时,构造函数都会被调用,实例变量 i 会根据对象初始化并修改,而静态变量 j 是类级别的,因此它的值在所有对象间共享。

PPT-10

String、StringBuffer与StringBuilder的性能比较

1. 程序示例与性能测试

通过以下程序,我们比较了 StringStringBufferStringBuilder 在进行字符串拼接时的性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class StringMore {
private static int time = 50000;

public static void main(String[] args) {
testString();
testStringBuffer();
testStringBuilder();
}

// 测试 String 类型的拼接性能
public static void testString () {
String s = "";
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
s += "java"; // 字符串拼接
}
long over = System.currentTimeMillis();
System.out.println("操作" + s.getClass().getName() + "类型使用的时间为:" + (over - begin) + "毫秒");
}

// 测试 StringBuffer 类型的拼接性能
public static void testStringBuffer () {
StringBuffer sb = new StringBuffer();
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
sb.append("java"); // 使用 StringBuffer 拼接字符串
}
long over = System.currentTimeMillis();
System.out.println("操作" + sb.getClass().getName() + "类型使用的时间为:" + (over - begin) + "毫秒");
}

// 测试 StringBuilder 类型的拼接性能
public static void testStringBuilder () {
StringBuilder sb = new StringBuilder();
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++) {
sb.append("java"); // 使用 StringBuilder 拼接字符串
}
long over = System.currentTimeMillis();
System.out.println("操作" + sb.getClass().getName() + "类型使用的时间为:" + (over - begin) + "毫秒");
}
}

该程序通过测试不同的字符串拼接方式来测量执行时间。分别测试了使用 StringStringBufferStringBuilder 对同一个字符串进行拼接的性能。

2. 性能比较

  • String:每次拼接字符串时都会创建一个新的字符串对象,性能较差。因为 String 是不可变的,每次修改都会创建新的对象,因此在频繁拼接时效率较低。

  • StringBuffer:线程安全的字符串缓冲区。每次拼接时,StringBuffer 会扩展它的内部数组并追加字符,因此在多次拼接时效率较高。StringBuffer 使用了 synchronized 关键字来确保线程安全,导致它的性能较 StringBuilder 略逊一筹。

  • StringBuilder:与 StringBuffer 类似,但是它没有 synchronized 关键字,因此它比 StringBuffer 更快,适用于单线程环境。

执行效率: - StringBuilder > StringBuffer > String

3. 优化与建议

  • String拼接的优化:当直接用 + 操作符拼接字符串时,Java 编译器会对这种情况进行优化,将多个字符串拼接转换为一个 StringBuilderStringBuffer 对象的调用。例如:

    1
    String str = "my" + "hello" + "world";  // 编译器优化为 "my" + "hello" + "world"
    这样优化的效率高于手动创建 StringBuilder 对象并调用 append 方法:
    1
    StringBuilder st = new StringBuilder("my").append("hello").append("world");

  • 选择合适的类

    • String:适合拼接次数较少,或者只有少量字符串拼接的场合。
    • StringBuilder:适合在单线程环境下,进行大量字符串拼接的场合。
    • StringBuffer:适合在多线程环境下进行字符串拼接的场合,虽然它的性能稍逊于 StringBuilder

4. 成员方法对比

  • String:不可变,每次拼接都会创建新的对象。
  • StringBufferStringBuilder:都实现了可变的字符串操作,具有 appendinsert 等方法,可以高效地进行字符串拼接。
  • StringBuffer:是线程安全的,使用了 synchronized 关键字。
  • StringBuilder:不是线程安全的,性能较 StringBuffer 更优。

5. 精华

  • 当需要频繁修改或拼接字符串时,推荐使用 StringBuilder(单线程)。
  • 在多线程环境中,使用 StringBuffer(它是线程安全的)。
  • 如果拼接次数较少,使用 String 即可,因为它的实现简单且直观。

PPT-11

1. Superclasses and Subclasses (父类与子类)

在面向对象编程中,类继承是构建类关系的一种方式。类与类之间可以形成父类(superclass)与子类(subclass)的关系。继承机制使得子类可以继承父类的属性和方法,从而避免重复代码。

  • 父类(Superclass):是一个被其他类继承的类,通常包含一些通用的属性和方法。
  • 子类(Subclass):继承父类的类。子类可以继承父类的所有非私有属性和方法,并可以根据需要重写或扩展这些属性和方法。
  • extends 关键字:用于表示继承关系,子类通过 extends 关键字继承父类。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal {  // 父类
void sound() {
System.out.println("Animal makes a sound");
}
}

class Dog extends Animal { // 子类继承父类Animal
void sound() {
System.out.println("Dog barks");
}
}

public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.sound(); // 输出:Dog barks
}
}

关键点:

  • 子类通过继承父类,可以访问父类的公有属性和方法。
  • 子类可以通过方法重写(Override)父类的方法。
  • 子类可以使用父类的构造方法,但父类的构造方法必须通过 super() 显式调用。

2. The Object Class and Its Methods (Object类及其方法)

Object是 Java 中的根类,所有的类(包括数组)都直接或间接继承自 Object 类。Object 类中定义了许多常用的方法,这些方法可以被所有类所使用。

常用的 Object 类方法:

  • toString()

    :返回对象的字符串表示。

    • 默认返回对象的类名和内存地址,可以被子类重写以提供更有意义的输出。
    1
    2
    3
    4
    @Override
    public String toString() {
    return "Dog";
    }
  • equals(Object obj)

    :比较两个对象的相等性。

    • 默认通过内存地址比较两个对象,但通常会重写该方法来比较对象的内容。
  • hashCode()

    :返回对象的哈希码。

    • 通常与 equals() 方法一起重写,确保当两个对象相等时,它们的哈希码也相等。
  • getClass()

    :返回对象的类类型。

    • 可以用来获取对象的实际类。

示例:

1
2
3
4
5
6
7
8
9
10
11
public class Animal {
@Override
public String toString() {
return "Animal Class";
}

@Override
public boolean equals(Object obj) {
return this == obj; // 仅检查内存地址是否相同
}
}

3. Polymorphism, Dynamic Binding and Generic Programming (多态、动态绑定和泛型编程)

3.1 Polymorphism (多态)

多态是面向对象编程的核心概念之一,它允许使用统一的接口来操作不同类型的对象。多态使得程序具有更强的灵活性和可扩展性。

  • 编译时多态(方法重载):方法名相同,但参数列表不同。
  • 运行时多态(方法重写):子类重写父类的方法。

3.2 Dynamic Binding (动态绑定)

动态绑定是指程序在运行时决定方法调用的具体类型(方法的实现)。例如,当子类重写父类的方法时,Java 会根据对象的实际类型来决定调用哪个方法。

  • 动态绑定通常发生在多态的情况下,即方法重写。
  • 方法重载是编译时绑定。

3.3 Generic Programming (泛型编程)

泛型是 Java 提供的一种支持类型参数化的机制。通过泛型,可以在编译时检查类型错误,而不是运行时。

  • 泛型使得代码更加通用和可重用。
  • 泛型使用尖括号 <> 来指定类型。

示例:

1
2
3
4
5
6
// 泛型方法
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
}

4. The ArrayList Class (ArrayList类)

ArrayList 是 Java 中的一个可动态扩展的数组实现,它是 List 接口的实现类之一。与普通数组不同,ArrayList 可以自动调整大小。

主要特点:

  • 动态扩展:当添加元素超过容量时,ArrayList 会自动扩展。
  • 顺序存储:它保持元素的插入顺序,并且允许重复的元素。
  • 元素访问:通过索引访问元素,时间复杂度为 O(1)。

常用方法:

  • add(E e):添加元素。
  • get(int index):获取指定索引的元素。
  • size():返回元素的数量。
  • remove(int index):删除指定索引的元素。

示例:

1
2
3
4
5
6
7
8
9
10
11
import java.util.ArrayList;

public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");
System.out.println(list.get(1)); // 输出:Python
}
}

5. The protected / final Modifier (protected / final 修饰符)

5.1 protected 修饰符

  • protected 允许类中的成员被当前类、同一包中的其他类以及所有继承该类的子类访问。
  • protected 主要用于继承机制中的属性和方法共享。

示例:

1
2
3
4
5
6
7
8
9
class Animal {
protected String name;
}

class Dog extends Animal {
public void setName(String name) {
this.name = name;
}
}

5.2 final 修饰符

  • final

    用于变量、方法和类:

    • final 变量:值不可更改,一旦赋值就不能修改。
    • final 方法:不能被子类重写。
    • final:不能被继承。

示例:

1
2
3
4
5
6
7
final class FinalClass {
final int value = 10;

final void display() {
System.out.println("This is a final method.");
}
}

精华:

  • Superclasses and Subclasses:通过继承机制,子类能够继承父类的属性和方法,并可以重写和扩展。
  • The Object ClassObject 类是所有 Java 类的根类,提供了如 toString()equals() 等常用方法。
  • Polymorphism and Dynamic Binding:通过多态性,可以让不同类型的对象调用相同的方法,且方法调用的实际实现通过动态绑定决定。
  • The ArrayList ClassArrayList 是一个动态扩展的数组实现,适用于需要频繁增加和删除元素的场景。
  • The protected/final Modifierprotected 提供了访问权限控制,而 final 用于限制变量、方法和类的修改和继承。

PPT-12

1. Exception (异常)

异常(Exception) 是程序运行过程中发生的意外事件,它通常导致程序的正常执行流中断。Java 使用异常处理机制来处理这些错误和异常情况。异常可以是运行时错误或逻辑错误,并且可以通过异常处理机制(如 try-catch)来捕获和处理。

主要特点:

  • 异常是对象,可以使用 throw 语句抛出。
  • 异常通常会导致程序终止,但可以通过适当的异常处理机制恢复程序的执行。

2. Exception Advantages (异常的优势)

异常处理机制提供了以下几个主要优势:

  • 提高程序的可维护性:通过异常处理,程序能够明确处理不同的错误情况,而不会让错误影响到其他部分的逻辑。
  • 减少错误:通过集中处理错误,程序能够避免出现未处理的错误。
  • 增强代码的健壮性:通过捕获和处理异常,程序能够在出现问题时优雅地失败或恢复,而不是直接崩溃。
  • 代码清晰:异常处理机制使得错误处理与正常逻辑分开,增强了代码的可读性。

3. Exception Types (异常类型)

在 Java 中,异常类型可以分为以下几类:

  • Checked Exception(已检查异常)
    • 这些异常在编译时就能被发现,必须被显式捕获或声明。
    • 常见的 Checked Exception 包括 IOExceptionSQLException 等。
    • 这些异常通常是由外部因素引起的,如文件不存在、数据库连接失败等。
  • Unchecked Exception(未检查异常)
    • 这些异常是程序错误引起的,通常在运行时发生。
    • 常见的 Unchecked Exception 包括 NullPointerExceptionArrayIndexOutOfBoundsExceptionArithmeticException 等。
    • 未检查异常不需要显式地处理或声明。
  • Error(错误)
    • 错误通常是由系统级问题引起的,例如 OutOfMemoryErrorStackOverflowError,这些通常无法通过代码处理。

4. Declaring, Throwing, and Catching Exceptions (声明、抛出和捕获异常)

4.1 声明异常(Declaring Exceptions)

如果方法中可能会抛出 Checked Exception,那么必须在方法签名中使用 throws 关键字声明该异常。

1
2
3
public void readFile() throws IOException {
// 代码可能会抛出 IOException
}

4.2 抛出异常(Throwing Exceptions)

通过 throw 语句可以在程序中显式抛出异常。

1
throw new ArithmeticException("Cannot divide by zero");

4.3 捕获异常(Catching Exceptions)

通过 try-catch 语句来捕获和处理异常。如果发生异常,程序会跳转到对应的 catch 块中。

1
2
3
4
5
try {
int result = 10 / 0; // 可能会抛出 ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Error: " + e.getMessage());
}

4.4 多重捕获(Multiple Catch Blocks)

可以有多个 catch 块来捕获不同类型的异常。

1
2
3
4
5
6
7
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Arithmetic Error");
} catch (Exception e) {
System.out.println("General Error");
}

4.5 finally

finally 块用于执行清理代码(如释放资源),无论是否发生异常,它始终都会被执行。

1
2
3
4
5
6
7
try {
// 代码块
} catch (Exception e) {
// 异常处理
} finally {
// 清理操作
}

5. Text I/O (文本输入输出)

Java 提供了多种方式来处理文本的输入输出。常用的类包括 FileReaderBufferedReaderPrintWriter

5.1 FileReaderBufferedReader

FileReader 用于读取字符文件,但它的效率较低,因此通常配合 BufferedReader 使用来提高效率。

1
2
3
4
5
6
7
FileReader fr = new FileReader("file.txt");
BufferedReader br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();

5.2 PrintWriter

PrintWriter 是一种便捷的类,可以用于输出文本数据,支持自动刷新和格式化输出。

1
2
3
PrintWriter writer = new PrintWriter("output.txt");
writer.println("Hello, World!");
writer.close();

6. File (文件处理)

在 Java 中,文件处理可以通过 File 类来实现,File 类提供了文件和目录的创建、删除、重命名等操作。

1
2
3
4
5
6
7
8
9
10
11
12
File file = new File("example.txt");

// 创建文件
file.createNewFile();

// 检查文件是否存在
if (file.exists()) {
System.out.println("File exists");
}

// 获取文件路径
System.out.println(file.getAbsolutePath());

常用方法:

  • createNewFile():创建新文件。
  • delete():删除文件。
  • exists():检查文件或目录是否存在。
  • getAbsolutePath():返回文件的绝对路径。

7. PrintWriter (PrintWriter类)

PrintWriter 是 Java 中用于写入字符数据到文件或控制台的类。它具有更高效的输出能力,并且支持自动刷新功能。

1
2
3
PrintWriter writer = new PrintWriter(new FileWriter("output.txt"));
writer.println("Hello, World!");
writer.close();
  • flush():刷新流,确保数据写入。
  • close():关闭流,释放资源。

8. Scanner (Scanner类)

Scanner 类是用于从控制台或文件中读取输入的工具。它支持读取各种类型的数据,包括整数、浮点数、字符串等。

示例:

1
2
3
4
5
6
Scanner sc = new Scanner(System.in);
System.out.print("Enter your name: ");
String name = sc.nextLine();

System.out.print("Enter your age: ");
int age = sc.nextInt();

常用方法:

  • nextLine():读取一行文本。
  • nextInt():读取一个整数。
  • nextDouble():读取一个浮点数。
  • close():关闭 Scanner 对象。

精华:

  • 异常是 Java 中重要的概念,通过 try-catch 机制来捕获和处理运行时错误。
  • 异常的种类包括 Checked ExceptionUnchecked Exception,每种类型的异常有不同的处理方式。
  • Text I/O 涉及读取和写入文本文件,常用的类有 FileReaderBufferedReaderPrintWriter 等。
  • 文件操作可以通过 File 类来进行文件的创建、删除、检查等。
  • PrintWriter 提供了便捷的文本输出功能,Scanner 用于读取输入。

PPT-13

深拷贝 (Deep Copy) 和浅拷贝 (Shallow Copy)

在对象复制的过程中,常常遇到浅拷贝和深拷贝的问题。它们之间的区别在于是否对对象内部的引用类型字段进行了复制。

  • 浅拷贝 (Shallow Copy):只复制对象的引用,而不复制对象内部的引用类型字段。即复制后的对象与原对象共享相同的引用数据。

  • 深拷贝 (Deep Copy):不仅复制对象本身,还会复制对象内部引用的数据,使得复制后的对象与原对象完全独立,所有字段都有自己的副本。

问题描述:在 Course1 类中实现 clone() 方法并对 students 字段进行深拷贝

Course1 类中有一个 students 字段,表示课程中的学生数组。需要实现 clone() 方法,确保在克隆 Course1 对象时,students 数组进行深拷贝。

Course1 类设计

假设 Course1 类的设计如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class Course1 implements Cloneable {
private String courseName;
private String[] students = new String[100];
private int numberOfStudents;

// 构造函数
public Course1(String courseName) {
this.courseName = courseName;
this.numberOfStudents = 0;
}

// 添加学生
public void addStudent(String student) {
if (numberOfStudents < 100) {
students[numberOfStudents] = student;
numberOfStudents++;
}
}

// 获取学生列表
public String[] getStudents() {
return students;
}

// 获取学生人数
public int getNumberOfStudents() {
return numberOfStudents;
}

// 获取课程名称
public String getCourseName() {
return courseName;
}

// 删除学生
public void dropStudent(String student) {
for (int i = 0; i < numberOfStudents; i++) {
if (students[i].equals(student)) {
// 移动数组元素
for (int j = i; j < numberOfStudents - 1; j++) {
students[j] = students[j + 1];
}
numberOfStudents--;
break;
}
}
}

// 实现克隆方法进行深拷贝
@Override
public Object clone() throws CloneNotSupportedException {
// 调用父类的 clone() 方法进行浅拷贝
Course1 c = (Course1) super.clone();

// 对 students 数组进行深拷贝
c.students = new String[100];
System.arraycopy(this.students, 0, c.students, 0, 100);

// 或者使用循环进行深拷贝:
/*
for (int i = 0; i < numberOfStudents; i++) {
c.students[i] = this.students[i];
}
*/
return c;
}
}

代码详解

  1. Course1 类字段
    • courseName:课程名称,类型为 String
    • students:一个长度为 100 的 String 数组,表示学生的姓名。
    • numberOfStudents:记录学生的数量。
  2. 构造方法 (Course1(String courseName))
    • 用于初始化课程名称和学生数量。
  3. addStudent(String student) 方法
    • 用于添加学生到 students 数组中,numberOfStudents 会自增。
  4. getStudents() 方法
    • 返回学生数组。
  5. getNumberOfStudents() 方法
    • 返回当前学生的数量。
  6. getCourseName() 方法
    • 返回课程名称。
  7. dropStudent(String student) 方法
    • 用于从学生数组中删除指定学生。删除后数组会左移,numberOfStudents 减 1。
  8. clone() 方法
    • clone() 方法用于克隆对象。在 Java 中,Object 类提供了一个 clone() 方法,但它是浅拷贝。为了实现深拷贝,我们在 clone() 方法中手动拷贝 students 数组。
    • 首先,调用 super.clone() 完成浅拷贝,复制 Course1 对象的基本属性(如 courseNamenumberOfStudents)。
    • 然后,使用 System.arraycopy() 或者循环手动拷贝 students 数组,以确保新的 Course1 对象拥有一个独立的 students 数组副本。

深拷贝的实现方式

clone() 方法中实现深拷贝有两种常见方式:

  1. 使用 System.arraycopy()
    • System.arraycopy(this.students, 0, c.students, 0, 100); 直接将原数组的内容拷贝到新数组。这样可以有效地复制 students 数组,但两者依然是独立的数组副本。
  2. 使用循环手动拷贝
    • 使用 for 循环遍历 students 数组,将每个学生的引用逐一复制到新数组中。
    1
    2
    3
    for (int i = 0; i < numberOfStudents; i++) {
    c.students[i] = this.students[i];
    }

为什么要使用 clone() 方法?

clone() 方法提供了一种创建对象副本的简便方式。通过实现深拷贝,我们确保了两个 Course1 对象之间不会互相影响。特别是在涉及到 students 数组这样的引用类型字段时,深拷贝非常重要。

  • 浅拷贝:如果不进行深拷贝,克隆后的 Course1 对象和原对象会共享 students 数组中的数据。对其中一个对象的修改可能会影响到另一个对象的状态。
  • 深拷贝:通过深拷贝,我们确保了原对象和克隆对象的 students 数组是完全独立的,互不干扰。

注意事项

  • Cloneable 接口:要使用 clone() 方法,类必须实现 Cloneable 接口,否则会抛出 CloneNotSupportedException 异常。
  • super.clone()super.clone() 会调用 Object 类的 clone() 方法,这会返回一个浅拷贝的副本,之后可以在此基础上进行深拷贝操作。

精华

Course1 类中,通过实现 clone() 方法并对 students 数组进行深拷贝,确保了克隆的 Course1 对象拥有独立的学生数组,从而避免了原对象和克隆对象之间的相互影响。这种深拷贝的方式是对对象内部引用字段进行复制的标准做法,适用于具有复杂属性的类。

PPT-14

JavaFX vs Swing and AWT

1. Swing: - Swing 是 Java 中的一个 GUI(图形用户界面)工具包,它基于 AWT(抽象窗口工具包)进行扩展。 - Swing 提供了丰富的控件,如按钮、标签、文本框等,使用 Java 编写而不是依赖于本地操作系统的窗口组件,因此界面更加一致。 - Swing 中的组件是“轻量级”的,意味着它们不依赖于操作系统的窗口,而是由 Java 绘制的。

2. AWT (Abstract Window Toolkit): - AWT 是 Java 的基础 GUI 库,用于构建图形界面。 - 与 Swing 不同,AWT 组件通常是“重量级”的,即它们依赖于操作系统提供的本地组件,因此它们的外观和行为可能因平台而异。 - AWT 是 Java GUI 的最初实现,但功能较为简单,不支持高级图形处理。

3. JavaFX: - JavaFX 是 Java 8 引入的新 GUI 库,用于开发丰富的桌面应用程序。 - 相比于 Swing 和 AWT,JavaFX 提供了更强大的功能,如硬件加速的图形处理、动画、3D 图形、样式表支持(CSS)、更现代的布局管理和易于使用的事件处理。 - JavaFX 基于 Scene Graph(场景图)模型,它比 Swing 和 AWT 更灵活、强大,支持多种控件和图形元素。

4. 比较总结: - Swing vs JavaFX:Swing 的设计比较旧,适合传统的桌面应用,而 JavaFX 适合构建现代的、具有多媒体功能的桌面应用。 - AWT vs JavaFX:AWT 提供基础功能,JavaFX 提供更现代和丰富的功能,支持硬件加速和复杂的界面设计。

SceneBuilder

SceneBuilder 是一种 JavaFX 可视化布局工具,用于创建 JavaFX 界面的 FXML 文件。FXML 是一种类似于 HTML 的标记语言,用于描述 JavaFX 应用的用户界面。SceneBuilder 提供了一个图形化的界面,用户可以通过拖放组件来设计 JavaFX 界面,然后生成相应的 FXML 文件。

特点: - 图形化界面:通过拖放界面控件,快速构建界面。 - 生成 FXML 文件:自动生成 FXML 文件,用户可以进一步在 Java 代码中引用。 - 与代码分离:JavaFX 与 FXML 分离,代码更加清晰,易于维护。

优势: - 提高开发效率:无需手动编写 UI 布局代码,便于快速构建应用。 - 支持预览:可以实时预览界面设计效果。 - 可与 Java 代码无缝集成:FXML 与 Java 代码结合,支持动态控制界面元素。

Basic Structure of JavaFX

JavaFX 的基本结构包括:

  1. Application 类
    • 所有 JavaFX 程序都必须继承 javafx.application.Application 类,并重写 start() 方法。
    • start() 方法接受一个 Stage 参数,表示应用的主窗口。
  2. Stage(舞台)
    • Stage 是应用的顶层容器,它表示一个窗口。
    • 每个 JavaFX 程序至少有一个 Stage 对象,应用程序的主界面是 Stage 上的 Scene
  3. Scene(场景)
    • Scene 表示一个容器,包含所有界面元素(控件、布局等)。
    • 场景是添加到舞台上的,舞台可以持有多个场景。
  4. Node(节点)
    • Node 是 JavaFX 中所有可视化元素的基类,包括图形、控件、容器等。每个节点都可以是 StageScene 的一部分。
    • 常见的节点有 ButtonTextFieldImageViewShape 等。

Stage/Scene/Pane/Node

  • Stage
    • JavaFX 的 Stage 类是舞台,它表示一个窗口。
    • 每个 JavaFX 应用程序必须至少有一个 Stage,它是界面的容器。
  • Scene
    • Scene 是舞台中的内容,包含所有的 UI 元素,和事件处理。
    • Scene 可以包含多个控件和布局元素。
  • Pane
    • Pane 是 JavaFX 中的容器类,主要用于布局和组织其他节点。
    • 常见的 Pane 类型包括 StackPaneGridPaneFlowPane 等,它们有不同的布局策略。
  • Node
    • Node 是所有 JavaFX 可视元素的基类,可以是 UI 控件(如按钮、标签)或图形元素(如矩形、圆形)。

Binding Property (属性绑定)

属性绑定是 JavaFX 中的强大功能,它允许控件或对象的属性相互关联,并自动更新。当一个属性的值发生变化时,绑定到该属性的其他属性也会自动更新。

常见的属性绑定方式: - 双向绑定:两个属性互相绑定,互相更新。 - 单向绑定:一个属性绑定到另一个属性,源属性变化时目标属性自动更新。

示例

1
2
3
TextField textField = new TextField();
Label label = new Label();
label.textProperty().bind(textField.textProperty()); // 双向绑定

在此示例中,label 的文本会始终与 textField 的文本内容同步。

Common Properties and Methods for Nodes

常见的 JavaFX 节点属性和方法包括:

  1. 属性
    • xy:定义节点的位置。
    • widthheight:定义节点的尺寸。
    • opacity:设置节点的透明度。
    • style:设置节点的 CSS 样式。
    • visible:设置节点是否可见。
    • rotate:设置节点的旋转角度。
  2. 方法
    • setLayoutX()setLayoutY():设置节点的位置。
    • setWidth()setHeight():设置节点的宽度和高度。
    • setStyle():设置节点的 CSS 样式。

Style / Rotate

  • Style

    • JavaFX 使用 CSS 来控制界面元素的样式。通过 setStyle() 方法可以动态改变节点的样式。
    • 可以设置属性,如 background-colorfont-sizecolor 等。

    示例:

    1
    button.setStyle("-fx-background-color: blue;");

  • Rotate

    • rotate 属性用于旋转节点,单位是度(°)。
    • 旋转是相对于节点的原点进行的,可以通过 setRotate() 方法设置旋转角度。

    示例:

    1
    rectangle.setRotate(45); // 旋转 45 度

Color / Font / Layout Panes / Image / Shape

  1. Color

    • JavaFX 提供了 Color 类用于设置颜色。可以使用预定义的颜色常量,或通过 RGB 值自定义颜色。

    示例:

    1
    2
    Color color = Color.RED; // 使用预定义颜色
    Color customColor = Color.rgb(255, 0, 0); // 使用 RGB 值

  2. Font

    • Font 类用于设置文本的字体、大小等属性。

    示例:

    1
    Font font = Font.font("Arial", 20); // 设置 Arial 字体,大小为 20

  3. Layout Panes

    • JavaFX 提供了多种布局容器类(Pane),如 VBoxHBoxGridPaneFlowPane,用于自动排列子节点。

    示例:

    1
    2
    VBox vbox = new VBox();
    vbox.getChildren().add(button); // 将按钮添加到垂直布局

  4. Image

    • Image 类用于处理图像。可以加载本地或远程图片并显示。

    示例:

    1
    2
    Image image = new Image("file:picture.jpg");
    ImageView imageView = new ImageView(image);

  5. Shape

    • JavaFX 提供了多种形状类,如 RectangleCircleEllipseLine,可以用来绘制图形。

    示例:

    1
    Rectangle rect = new Rectangle(100, 100, 200, 150); // 创建一个矩形

PPT-15

Java Event Delegation Model

Java 中的 事件委托模型(Event Delegation Model)是事件处理的核心。它是处理 GUI 事件(如按钮点击、鼠标移动等)的机制,并基于分离关注的设计理念。事件的发生与事件的响应(处理)是分离的,事件源和事件处理器(监听器)是解耦的。

基本概念:

  • 事件源(Event Source):事件源是触发事件的对象。在 Java 中,通常是 GUI 组件(如按钮、文本框等)。
  • 事件(Event):事件表示用户与应用程序的交互行为(如点击、按键、鼠标移动等)。Java 中的事件通常继承自 java.util.EventObject 类。
  • 事件监听器(Event Listener):事件监听器是对特定事件做出响应的对象。它会在事件发生时被触发,并执行相应的处理逻辑。

事件处理的过程:

  1. 用户在界面上与某个组件交互(如点击按钮)。
  2. 该组件会生成一个事件(例如 ActionEvent)。
  3. 事件通过事件源传播到注册的监听器(事件处理器)。
  4. 监听器接收事件并根据事件的类型执行对应的处理方法。

事件委托模型的优势:

  • 松散耦合:事件源和监听器是解耦的,事件源不需要知道监听器的具体实现,监听器通过接口与事件源进行交互。
  • 灵活性:多个监听器可以监听同一个事件源,允许事件的灵活处理。

Event / Event Source / Event Handler (Listener)

  1. Event(事件)
    • 事件是用户与 GUI 组件交互时产生的一个对象,表示某种动作或状态变化。
    • Java 提供了多种不同的事件类,如 ActionEvent(按钮点击事件),KeyEvent(键盘事件),MouseEvent(鼠标事件)等。
  2. Event Source(事件源)
    • 事件源是产生事件的对象。在 Java 中,常见的事件源是 GUI 组件,如 ButtonTextFieldLabel 等。
    • 事件源通过 addXXXListener() 方法将事件监听器注册到自身。当事件发生时,事件源会通知所有已注册的监听器。
  3. Event Handler(事件处理器,Listener)
    • 事件监听器是实现特定事件处理逻辑的对象。监听器实现特定的事件监听接口,并定义响应事件的方法。
    • 每个事件类型都有对应的监听接口,例如:ActionListener(用于按钮点击),MouseListener(用于鼠标事件),KeyListener(用于键盘事件)等。

Inner Class(内部类)

内部类是定义在另一个类中的类。Java 中有四种内部类类型:

  1. 成员内部类:定义在外部类的成员位置,且可以访问外部类的成员。
  2. 静态内部类:定义为 static 的内部类,它不依赖于外部类的实例,因此不能访问外部类的实例成员。
  3. 局部内部类:定义在方法中,作用范围限定在方法内。
  4. 匿名内部类:没有名字的类,它是对某个类的快速实现,通常用于简化代码,特别是在事件处理时。

内部类的应用

  • 内部类可以访问外部类的成员和方法,因此它可以直接访问外部类的非静态变量和方法。
  • 内部类能有效实现封装和逻辑分离,特别适合事件处理和回调等场景。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class OuterClass {
private String name = "Outer";

class InnerClass {
void printName() {
System.out.println(name); // 访问外部类的成员
}
}

void createInnerClass() {
InnerClass inner = new InnerClass();
inner.printName();
}
}

Anonymous Inner Class (匿名内部类) / Lambda Expression

1. Anonymous Inner Class (匿名内部类)

  • 匿名内部类是没有名字的内部类,它通常用来简化事件监听器的实现。匿名内部类可以在创建的同时初始化并实现接口或继承类。
  • 常用在事件监听中,如按钮的点击事件监听。

示例:

1
2
3
4
5
6
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});

匿名内部类无需单独定义一个实现类,而是直接在创建监听器时实现事件处理方法。

2. Lambda Expression (Lambda 表达式)

  • 从 Java 8 开始,Java 引入了 Lambda 表达式,它是一种简洁的方式来表示函数式接口的实现,特别是对于事件监听器的处理更加简洁和清晰。
  • Lambda 表达式为事件监听提供了简化的语法,避免了冗长的匿名内部类定义。

示例:

1
button.addActionListener(e -> System.out.println("Button clicked!"));

Lambda 表达式简化了事件处理代码,使得事件处理变得更加直观和易于维护。

Listeners for Observable Objects

在 Java 中,某些对象支持监听其状态的变化,这些对象称为 可观察对象(Observable Objects)。Java 提供了 Observable 类和 Observer 接口来实现对象状态变化的监听。

  • Observable 类Observable 是 Java 中一个抽象类,表示可被观察的对象。它维护了一些观察者(Observer)的列表,当其状态变化时,通知所有注册的观察者。
  • Observer 接口Observer 接口表示观察者,它定义了一个 update() 方法,用于接收被观察对象的状态变化通知。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.*;

class Subject extends Observable {
private String state;

public void setState(String state) {
this.state = state;
setChanged();
notifyObservers(state); // 通知观察者
}

public String getState() {
return state;
}
}

class ObserverImpl implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("State updated: " + arg);
}
}

public class Main {
public static void main(String[] args) {
Subject subject = new Subject();
Observer observer = new ObserverImpl();

subject.addObserver(observer);

subject.setState("New state"); // 状态变化,观察者收到通知
}
}

精华

  • Java 事件委托模型:通过事件源、事件和事件监听器解耦了事件的发生和处理,使得代码更加灵活和易于维护。
  • 事件源与监听器:事件源产生事件,监听器接收并处理事件。通过 addXXXListener() 方法将监听器注册到事件源。
  • 内部类:内部类能够访问外部类的成员,常用于事件处理。匿名内部类简化了事件处理器的定义。
  • Lambda 表达式:从 Java 8 开始,Lambda 表达式简化了函数式接口(如事件监听器)的实现。
  • 可观察对象Observable 类和 Observer 接口为对象的状态变化提供了通知机制,允许多个观察者监听对象的状态变化。

PPT-16

PPT-17

Text I/O 与 Binary I/O

在 Java 中,输入和输出操作分为两种主要类型:文本输入/输出(Text I/O)二进制输入/输出(Binary I/O)。两者主要区别在于数据的处理方式,文本 I/O 处理的是字符数据,而二进制 I/O 处理的是原始字节数据。


1. Text I/O 与 Binary I/O 的区别

  • Text I/O(文本输入/输出):
    • 处理的是字符数据。文本文件通常是由字符(例如 ASCII 或 UTF-8 编码的字符)组成的。
    • Java 提供了字符流类,例如 FileReaderFileWriterBufferedReaderBufferedWriter 等,主要用于字符的读取与写入。
    • 文本流会自动进行字符编码与解码(例如从字节转换为字符)。
  • Binary I/O(二进制输入/输出):
    • 处理的是字节数据。二进制文件可以包含任何类型的数据(如图片、音频、视频、程序文件等)。
    • Java 提供了字节流类,如 InputStreamOutputStreamFileInputStreamFileOutputStreamDataInputStreamDataOutputStream 等,用于处理字节数据。
    • 二进制流不会进行编码或解码操作,直接读取或写入字节数据。

2. InputStream 和 OutputStream

  • InputStream
    • InputStream 是所有字节输入流的父类,用于表示字节输入流的基类。它定义了从输入流中读取字节的基本方法。
    • 常用方法:
      • read():读取一个字节并返回(返回 -1 表示流结束)。
      • read(byte[] b):读取一定数量的字节并存储到数组中。
      • close():关闭流。
  • OutputStream
    • OutputStream 是所有字节输出流的父类,用于表示字节输出流的基类。它定义了向输出流写入字节的基本方法。
    • 常用方法:
      • write(int b):写入一个字节。
      • write(byte[] b):写入字节数组。
      • flush():刷新输出流,确保所有数据都被写入。
      • close():关闭流。

3. FileInputStream 和 FileOutputStream

  • FileInputStream

    • 用于从文件中读取字节数据,继承自 InputStream 类。
    • 读取文件的字节流,并将内容传递给程序。

    示例:

    1
    2
    3
    4
    5
    6
    FileInputStream fis = new FileInputStream("file.txt");
    int byteData;
    while ((byteData = fis.read()) != -1) {
    System.out.print((char) byteData); // 将字节转换为字符
    }
    fis.close();

  • FileOutputStream

    • 用于向文件中写入字节数据,继承自 OutputStream 类。
    • 向指定的文件中写入字节数据。

    示例:

    1
    2
    3
    4
    FileOutputStream fos = new FileOutputStream("file.txt");
    String content = "Hello, Java!";
    fos.write(content.getBytes()); // 将字符串转换为字节并写入文件
    fos.close();


4. DataInputStream 和 DataOutputStream

  • DataInputStream

    • DataInputStreamInputStream 的子类,用于以机器无关的方式读取原始数据类型(如 intfloatdoublelongboolean)以及 UTF-8 编码的字符串。
    • 它提供了许多方法,可以直接读取 Java 中的基本数据类型。

    常用方法:

    • readInt():读取一个 int
    • readFloat():读取一个 float
    • readUTF():读取一个 UTF-8 编码的字符串。

    示例:

    1
    2
    3
    4
    DataInputStream dis = new DataInputStream(new FileInputStream("data.dat"));
    int number = dis.readInt();
    String name = dis.readUTF();
    dis.close();

  • DataOutputStream

    • DataOutputStreamOutputStream 的子类,用于以机器无关的方式写入原始数据类型。
    • 它提供了写入 Java 中基本数据类型的方法。

    常用方法:

    • writeInt(int v):写入一个 int
    • writeFloat(float v):写入一个 float
    • writeUTF(String str):写入一个 UTF-8 编码的字符串。

    示例:

    1
    2
    3
    4
    DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.dat"));
    dos.writeInt(100);
    dos.writeUTF("Hello");
    dos.close();


5. BufferedInputStream 和 BufferedOutputStream

  • BufferedInputStream

    • BufferedInputStreamInputStream 的子类,使用一个缓冲区来提高读取数据的效率。它通过减少磁盘操作来提升性能。
    • 一般在需要频繁读取数据时使用 BufferedInputStream

    示例:

    1
    2
    3
    4
    5
    6
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("file.txt"));
    int byteData;
    while ((byteData = bis.read()) != -1) {
    System.out.print((char) byteData); // 将字节转换为字符
    }
    bis.close();

  • BufferedOutputStream

    • BufferedOutputStreamOutputStream 的子类,使用缓冲区来提高写入数据的效率。
    • 它将数据先写入缓冲区,再一次性写入磁盘,从而减少磁盘操作,提高性能。

    示例:

    1
    2
    3
    4
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("file.txt"));
    String content = "Buffered Output!";
    bos.write(content.getBytes());
    bos.close();


6. RandomAccessFile

  • RandomAccessFile
    • RandomAccessFile 类允许随机访问文件内容,可以在文件的任意位置读写数据。它既可以用于读取数据,也可以用于写入数据。
    • RandomAccessFile 与其他流类的不同之处在于,它支持读写文件中的任意位置,而不必按顺序读取或写入。
    常用方法:
    • seek(long pos):设置文件指针的位置,可以随机访问文件中的任意位置。
    • read():读取一个字节。
    • write(byte[] b):写入字节数组。
    示例:
    1
    2
    3
    4
    RandomAccessFile raf = new RandomAccessFile("file.txt", "rw");
    raf.seek(5); // 设置文件指针到第5个字节位置
    raf.write("Hello".getBytes()); // 写入数据
    raf.close();

精华:

  • Text I/OBinary I/O 主要的区别在于数据的处理方式:Text I/O 处理字符数据,Binary I/O 处理字节数据。
  • 字节流InputStreamOutputStream)适用于读取和写入所有类型的数据,包括文本和二进制数据。
  • 字符流(如 FileReaderFileWriter)专门用于处理文本文件。
  • DataInputStreamDataOutputStream 提供了读取和写入 Java 原始数据类型的方法。
  • BufferedInputStreamBufferedOutputStream 提供了带缓冲的字节流,提升了 I/O 操作的效率。
  • RandomAccessFile 允许随机访问文件中的任意位置,是一种灵活的文件操作方式。

PPT-18

PPT-19

PPT-20

PPT-21