Java 入门

这是 2024 年清华大学计算机系学生科协暑期培训 Java 部分的讲义。

前置知识:Git、C/C++、OOP。

Java 简介

Java 是一种广泛使用的计算机编程语言,于 1995 年由 Sun Microsystems 公司推出。它是一种面向对象的编程语言,旨在减少编程中可能出现的错误,并易于理解和维护。

尽管近年来面临一些新兴语言的挑战,Java 由于它的跨平台性、良好的安全性、前向兼容性以及不算差的性能,仍是一门历史地位和业界地位都极其崇高的语言。庞大的 Java 社区和海量的 Java 项目,使得对任何想要接触业界的贵系同学来说,你也许可以不精通它,但至少应当对这门简单、强大、通用的语言有一些了解。

编程语言排名

发展历史

Java 发明于 20 世纪 90 年代初,由 Sun Microsystems(后来被 Oracle 收购)的工程师团队开发。最初的目标是创建一种用于家电设备的编程语言。1995 年,Java 1.0 正式发布,带来了跨平台的能力,也就是“Write Once, Run Anywhere”(一次编写,随处运行)。这一特性是通过将 Java 代码编译为中间表示形式(字节码)实现的,然后在任何支持 Java 虚拟机(JVM)的平台上运行。

随着时间的推移,Java 不仅仅成为一种用于嵌入式系统的语言,它还发展成为一种强大的服务器端、企业级应用、Web 和移动应用的开发语言。

优势与不足

优势

  1. 跨平台性:Java 采用“一次编写,到处运行”的理念,代码可以在不同操作系统(Windows、macOS、Linux 等)上运行,无需重新编写。这得益于 Java 虚拟机(Java Virtual Machine,JVM)的设计。
  2. 面向对象:Java 是一种纯粹的面向对象编程语言,支持封装、继承和多态等 OOP 特性,有利于代码的复用和维护。
  3. 安全性:Java 具有强大的安全机制,如垃圾回收机制(Garbage Collection,GC)、异常处理等,可以有效地防范程序运行过程中的安全隐患。
  4. 丰富的标准库:Java 附带了大量的标准库,涵盖了网络、图形界面、数据库等各个方面,开发者可以直接调用这些库(而无需重复造轮子),提高开发效率。
  5. 广泛的应用领域:Java 可用于开发桌面应用程序、Web 应用程序、移动应用程序、大数据处理、机器学习等各种类型的软件。

不足

  1. 性能:与编译成本地机器代码的语言(例如 C、C++)相比,Java 的性能通常较低。这是因为 Java 程序需要在 JVM 上运行,这增加了额外的抽象层。
  2. 内存消耗:Java 应用程序通常比其他编程语言的应用程序消耗更多的内存。这是由于其“面向对象”导向的设计和垃圾回收机制。
  3. 冗长的代码:与一些现代编程语言(例如 Rust)相比,Java 的代码可能显得冗长和繁琐,这也可能会使开发过程变得复杂和耗时。

尽管存在这些不足,Java 仍然是世界上最受欢迎和广泛使用的编程语言之一。它在企业级应用程序、移动应用程序(尤其是 Android)和大型系统中得到了广泛应用。

课前准备

环境配置

要运行 Java 程序,你需要先安装 Java Developer Kit(JDK)。Windows 或 Mac 用户建议直接在 Oracle 官网下载 JDK21;而 Linux 用户(这里以 Ubuntu 为例,其余平台请自行百度 / Google)则可以使用如下命令:

bash
1sudo add-apt-repository ppa:linuxuprising/java
2sudo apt update
3sudo apt install oracle-java21-installer --install-recommends

本教程将使用 JDK21 这一版本。请使用 java -version 命令来确认 JDK 是否安装完成(以下输出仅供参考):

text
1java version "21.0.3" 2024-04-16 LTS
2Java(TM) SE Runtime Environment (build 21.0.3+7-LTS-152)
3Java HotSpot(TM) 64-Bit Server VM (build 21.0.3+7-LTS-152, mixed mode, sharing)

你可以使用如下命令编译运行 Java 程序:

bash
1javac YourProgram.java	# 编译
2java YourProgram        # 运行
3java YourProgram.java   # 编译 & 运行

同时,本教程将使用,也强烈推荐使用 IntelliJ IDEA 作为 IDE 来辅助开发。如果你就读于清华大学,建议使用 <你的邮箱名>@mail.thu.edu.cn 来获取面向学生和教师的个人许可证,以获得“Ultimate Experience”(类似于 Professional 版本)。

Hello world!

在 IntelliJ IDEA 中,点击 File-New-New Project... 新建一个新的项目,项目名为 hello-world

新建一个新的项目

IDEA 会自动生成一个 demo 代码,代码内容如下:

Main.java
1public class Main {
2    public static void main(String[] args) {
3        System.out.println("Hello world!");
4    }
5}

点击右上角的“编译并运行”按钮。

编译并运行后的输出界面

如果你的程序输出:Hello world!,那么,你已经能够成功编译并运行 Java 程序了!

最后,请在 GitHub 上 fork sast-summer-training-2024/sast2024-java 至自己的账户,并使用 Git 将自己账号下的仓库 clone 至本地。

至此,课前准备环节结束。

另外,sast-summer-training-2024/sast2024-java 中的代码是使用 Gradle 构建的,并且带有单元测试。具体而言,使用 Gradle 构建的代码结构可以按照以下方式创建:

创建带有测试的代码结构

你也可以尝试运行 demo 代码。你可能会发现,输出十分冗长,但是其中应该也是有 Hello world! 的。

基础语法

Java 的基础语法与 C++ 较为类似。以下代码均包含于 src/main/java/examples/introduction 中。

输入输出

IOExample.java
 1package examples.introduction;
 2
 3import java.util.Scanner;
 4
 5public class IO {
 6    public static void main(String[] args) {
 7        // Console Output
 8        System.out.println("This is an output with a new line.");
 9        System.out.print("This is an output without a new line.");
10        System.out.println("So this sentence will appear right after the last one.");
11
12        // Console Input
13        Scanner input = new Scanner(System.in);
14        System.out.print("Enter your name: ");
15        String name = input.nextLine();
16        System.out.print("Enter your age: ");
17        int age = input.nextInt();
18        System.out.println("Hello " + name + ", you are " + age + " years old!");
19    }
20}

这个程序中展现了 Java 中基础的输入输出方式。相较于 C++ 而言,输入输出所用函数名较长,但也并不繁琐。

变量

Java 中的变量类型和 C++ 类似,但也有细微的差别。

整型数:byteshortintlong

类型大小最小值最大值
byte11 字节27=128-2^7 = -128271=1272^7-1 = 127
short22 字节215=32768-2^{15}=-327682151=327672^{15}-1=32767
int44 字节231=2147483648-2^{31} = -21474836482311=21474836472^{31}-1=2147483647
long88 字节263-2^{63}26312^{63}-1

浮点数:floatdouble

在 Java 中,浮点数字面量可以写为 123.45.5,或 321.4f 等。其中以 f 结尾的浮点数字面量是 float 类型,其余均为 double 类型。因此,以下的语句是不合法的:

java
1float doubleVariable = 3.1415;

布尔型变量:boolean

在 Java 中,布尔型变量用 boolean 来定义,而非像 C++ 一样使用关键字 bool

字符:char

在 Java 中,char 类型用于表示 Unicode 字符,而不是 ASCII。每个 char 类型的变量可以存储一个 Unicode 码点,其数值范围从 006553565535(即 0xFFFF)。这意味着 Java 的 char 类型甚至能够表示汉字。

数组

在 Java 中,推荐将数组的定义([])写在类型后面,例如 int[] arrString[][] array_2d 等,而非 double xs[]

代码示例

VariablesExample.java
 1package examples.introduction;
 2
 3public class Variables {
 4    public static void main(String[] args) {
 5        // Integers
 6        byte byteVariable = 123;
 7        short shortVariable = 12345;
 8        int intVariable = 1234567890;
 9        long longVariable = 1234567890123456789L;
10
11        // Floating-point Numbers
12        float floatVariable = 3.14f;
13        double doubleVariable = 3.14159265358979;
14
15        // Character
16        char charVariable = 'A';
17
18        // Boolean
19        boolean booleanVariable = true;
20
21        // Printing the values
22        System.out.println("byte: " + byteVariable);
23        System.out.println("short: " + shortVariable);
24        System.out.println("int: " + intVariable);
25        System.out.println("long: " + longVariable);
26        System.out.println("float: " + floatVariable);
27        System.out.println("double: " + doubleVariable);
28        System.out.println("char: " + charVariable);
29        System.out.println("boolean: " + booleanVariable);
30
31        // Integer array
32        int[] intArray = {1, 2, 3, 4, 5};
33
34        // Accessing array elements
35        System.out.println("First element: " + intArray[0]);
36        System.out.println("Last element: " + intArray[intArray.length - 1]);
37
38        // Iterating through the array
39        System.out.println("Iterating through the array:");
40        for (int i = 0; i < intArray.length; i++) {
41            System.out.println(intArray[i]);
42        }
43
44        // Character array
45        char[] charArray = {'a', 'b', 'c', 'd', 'e'};
46
47        // Iterating through the character array
48        System.out.println("Iterating through the character array:");
49        for (char c : charArray) {
50            System.out.println(c);
51        }
52    }
53}

运算符

运算符中可能仅有 >>> 不常见。>>> 是专门针对有符号数设计的“逻辑右移”。

>>(算术右移)不同的是,逻辑右移在最高位补 0;而算术右移补的是符号位(即,符号位为 1 则补 1,符号位为 0 则补 0)。

例如,int x = -4;,即 11111111 11111111 11111111 11111100,则:

  • x >>> 101111111 11111111 11111111 11111110,即 21474836462147483646
  • x >> 111111111 11111111 11111111 11111110,即 2-2
OperatorsExample.java
 1package examples.introduction;
 2
 3public class Operators {
 4    public static void main(String[] args) {
 5        int a = 114;
 6        int b = 514;
 7        System.out.println("a + b = " + (a + b));
 8        System.out.println("a - b = " + (a - b));
 9        System.out.println("a * b = " + (a * b));
10
11        // int / int = int
12        System.out.println("a / b = " + (a / b));
13        // int / double = double
14        System.out.println("a / b = " + (a / (double) b));
15
16        System.out.println("a % b = " + (a % b));
17        System.out.println("a++ = " + (a++));
18        System.out.println("++a = " + (++a));
19        System.out.println("b += 100 = " + (b += 100));
20        System.out.println("a == b = " + (a == b));
21        System.out.println("a != b = " + (a != b));
22        System.out.println("a > b = " + (a > b));
23        System.out.println("a >= b = " + (a >= b));
24        System.out.println("a & b = " + (a & b));
25        System.out.println("a | b = " + (a | b));
26        System.out.println("a ^ b = " + (a ^ b));
27        System.out.println("~a = " + (~a));
28        System.out.println("a << 2 = " + (a << 2));
29
30        int x = -4;
31        // Arithmetic shift
32        System.out.println("x >> 2 = " + (x >> 1));
33        // Logical shift
34        System.out.println("x >>> 2 = " + (x >>> 1));
35
36        boolean c = true;
37        boolean d = false;
38        System.out.println("c && d = " + (c && d));
39        System.out.println("c || d = " + (c || d));
40        System.out.println("!c = " + (!c));
41        System.out.println("c ? 'T' : 'F' = " + (c ? 'T' : 'F'));
42    }
43}

控制流语句

Java 和 C++ 的控制流语句大同小异,可以结合以下例子进行学习。

ControlFlowExample.java
 1package examples.introduction;
 2
 3public class ControlFlow {
 4    public static void main(String[] args) {
 5        // if-else example
 6        int age = 18;
 7        if (age >= 18) {
 8            System.out.println("You are an adult.");
 9        } else {
10            System.out.println("You are a minor.");
11        }
12
13        // switch example
14        int month = 3;
15        String monthName;
16        switch (month) {
17            case 1:
18                monthName = "January";
19                break;
20            case 2:
21                monthName = "February";
22                break;
23            case 3:
24                monthName = "March";
25                break;
26            default:
27                monthName = "Invalid month";
28        }
29        System.out.println("The month is " + monthName);
30
31        // for loop example
32        System.out.println("Counting from 1 to 5:");
33        for (int i = 1; i <= 5; i++) {
34            System.out.println(i);
35        }
36
37        // while loop example
38        int counter = 0;
39        while (counter < 3) {
40            System.out.println("Counter value: " + counter);
41            counter++;
42        }
43
44        // do-while loop example
45        int number = 5;
46        do {
47            System.out.println("Number value: " + number);
48            number--;
49        } while (number > 0);
50
51        // break example
52        for (int i = 0; i < 10; i++) {
53            if (i == 5) {
54                System.out.println("Breaking the loop at i = 5");
55                break;
56            }
57            System.out.println("i = " + i);
58        }
59
60        // continue example
61        for (int i = 0; i < 10; i++) {
62            if (i % 2 == 0) {
63                continue;
64            }
65            System.out.println("Odd number: " + i);
66        }
67    }
68}

异常处理

Java 将异常分为两大类:

受检异常(Checked Exceptions)

  • 编译器会检查这些异常是否被处理或声明抛出。
  • 如果方法可能抛出受检异常,调用者必须要么捕获该异常,要么在方法签名中声明抛出该异常。
  • 常见的受检异常有 IOExceptionSQLExceptionClassNotFoundException 等。

非受检异常(Unchecked Exceptions)

  • 这些异常不会被编译器检查。
  • 包括 RuntimeException 及其子类,如 NullPointerExceptionArrayIndexOutOfBoundsExceptionIllegalArgumentException 等。
  • 非受检异常可以不被显式捕获,也可以在方法签名中不声明抛出。

对于非受检异常,Java 并不要求开发者必须显式捕获或声明抛出。这是因为非受检异常通常是由于程序逻辑错误或输入数据错误导致的,应该在代码设计和编写时就尽量避免这类异常的发生。

相比之下,受检异常通常是由于外部原因(如 I/O 错误、网络异常等)导致的,开发者无法完全控制。因此,Java 要求开发者必须处理这些异常,以确保程序的健壮性和可靠性。

ExceptionHandlingExample.java
 1package examples.introduction;
 2
 3import java.util.InputMismatchException;
 4import java.util.Scanner;
 5
 6public class ExceptionHandling {
 7    public static void main(String[] args) {
 8        Scanner sc = new Scanner(System.in);
 9        System.out.println("Enter a non-negative integer n, and I will try to calculate 100 / n: ");
10        try {
11            int n = sc.nextInt();
12            int result = 100 / n; // if n = 0 then `ArithmeticException` will be thrown
13            if (n < 0)
14                throw new IllegalArgumentException("Number is negative."); // custom exception
15            System.out.println("Result: " + result);
16        } catch (ArithmeticException e) { // will be executed if n = 0
17            System.out.println("Number is zero.");
18        } catch (InputMismatchException e) {
19            System.out.println("Number is not an integer.");
20        } catch (Exception e) { // will be executed if other exception occurs
21            e.printStackTrace();
22        } finally { // will be executed always
23            System.out.println("Finally block is always executed.");
24            sc.close();
25        }
26    }
27}

不过,在程序开发过程中,即使出现了未捕获的异常导致程序发生 Runtime Error,也可以通过查看调用栈中的调试信息,来找到问题出错的根源。

标准库

正如 C++ 中的 STL 一样,Java 也有很多标准库。以下代码均包含于 src/main/java/examples/datastructures 中。

BigIntegerBigDecimal

BigIntegerBigDecimal 是 Java 中用于处理大数和高精度计算的两个类。它们属于 java.math 包。

  • BigInteger 类用于表示整数值,它不受 Java 内置的 intlong 类型所受的固定大小限制。BigInteger 可以处理任意精度的整数,包括非常大的数值,例如在示例代码中展示的 123456789012345678901234567890

  • BigDecimal 类用于表示具有精确小数位的小数值,它提供了对小数点后任意位数的精确控制。BigDecimal 常用于需要高精度计算的金融领域。

示例代码展示了 BigIntegerBigDecimal 的基本使用,包括创建实例、执行基本的算术操作(加、减、乘、除),以及展示 BigDecimal 的精度控制。

BigIntegerAndBigDecimalExample.java
 1package examples.datastructures;
 2
 3import java.math.BigDecimal;
 4import java.math.BigInteger;
 5
 6public class BigIntegerAndBigDecimalExample {
 7    public static void main(String[] args) {
 8        // Example usage of BigInteger
 9        BigInteger bigInt = new BigInteger("123456789012345678901234567890");
10        System.out.println("BigInteger: " + bigInt);
11
12        // Arithmetic operations with BigInteger
13        BigInteger bigInt1 = new BigInteger("987654321098765432109876543210");
14        BigInteger bigInt2 = new BigInteger("100");
15        BigInteger sum = bigInt1.add(bigInt2);
16        BigInteger difference = bigInt1.subtract(bigInt2);
17        BigInteger product = bigInt1.multiply(bigInt2);
18        BigInteger quotient = bigInt1.divide(bigInt2);
19
20        System.out.println("Sum: " + sum);
21        System.out.println("Difference: " + difference);
22        System.out.println("Product: " + product);
23        System.out.println("Quotient: " + quotient);
24
25        // Example usage of BigDecimal
26        BigDecimal bigDec = new BigDecimal("1234567890.12345678901234567890");
27        System.out.println("BigDecimal: " + bigDec);
28
29        // Arithmetic operations with BigDecimal
30        BigDecimal bigDec1 = new BigDecimal("987654321.098765432109876543210");
31        BigDecimal bigDec2 = new BigDecimal("0.0012345");
32        BigDecimal sumBD = bigDec1.add(bigDec2);
33        BigDecimal differenceBD = bigDec1.subtract(bigDec2);
34        BigDecimal productBD = bigDec1.multiply(bigDec2);
35        BigDecimal quotientBD = bigDec1.divide(bigDec2, 10, BigDecimal.ROUND_HALF_UP);
36
37        System.out.println("BigDecimal Sum: " + sumBD);
38        System.out.println("BigDecimal Difference: " + differenceBD);
39        System.out.println("BigDecimal Product: " + productBD);
40        System.out.println("BigDecimal Quotient (rounded to 10 scale): " + quotientBD);
41
42        // Demonstrate precision of BigDecimal
43        BigDecimal pi = new BigDecimal(Math.PI);
44        System.out.println("Pi (to 10 scale): " + pi.setScale(10, BigDecimal.ROUND_HALF_UP));
45    }
46}

StringStringBufferStringBuilder

Java 中的 String 类表示不可变字符串,这意味着对 String 对象的任何修改都会生成一个新的 String 对象StringBufferStringBuilder 都是可变的字符串缓冲区,允许在不创建新对象的情况下修改字符串。

  • String 类的示例代码展示了字符串的不可变性。每次使用 + 操作符连接字符串时,都会创建一个新的 String 对象。

  • StringBuffer 是线程安全的,可以在多线程环境中使用。示例代码展示了如何使用 appendinsert 和其他方法来修改 StringBuffer 的内容。

  • StringBuilderStringBuffer 类似,但它不是线程安全的,因此在单线程环境中使用时通常比 StringBuffer 更快。示例代码演示了 StringBuilder 的使用,包括清空内容、追加字符串、插入字符串等操作。

StringAndStringBufferAndStringBuilderExample.java
 1package examples.datastructures;
 2
 3public class StringAndStringBufferAndStringBuilderExample {
 4    public static void main(String[] args) {
 5        // String array
 6        String[] stringArray = {"apple", "banana", "cherry"};
 7
 8        // Iterating through the string array
 9        System.out.println("Iterating through the string array:");
10        for (String s : stringArray) {
11            System.out.println(s);
12        }
13
14        // Demonstrating String immutability
15        String str = "Hello";
16        System.out.println("Original String: " + str);
17        str += " World"; // New String object is created
18        System.out.println("After concatenation: " + str);
19
20        // Demonstrating StringBuffer usage
21        StringBuffer stringBuffer = new StringBuffer("Initial");
22        stringBuffer.append(" String"); // Appending to StringBuffer
23        stringBuffer.insert(0, "Another "); // Inserting into StringBuffer
24        System.out.println("StringBuffer: " + stringBuffer);
25
26        // Demonstrating StringBuilder usage
27        StringBuilder stringBuilder = new StringBuilder("Initial");
28        stringBuilder.append(" String"); // Appending to StringBuilder
29        stringBuilder.insert(0, "Another "); // Inserting into StringBuilder
30        System.out.println("StringBuilder: " + stringBuilder);
31
32        // StringBuilder is generally faster than StringBuffer
33        // because it is not synchronized, making it ideal for single-threaded scenarios.
34
35        // Example of using StringBuilder for complex string manipulations
36        System.out.println("Complex StringBuilder operations:");
37        stringBuilder.setLength(0); // Clear the StringBuilder content
38        stringBuilder.append("Looping and ");
39        stringBuilder.append("building a long ");
40        stringBuilder.append("string in a ");
41        stringBuilder.append("StringBuilder.");
42        System.out.println(stringBuilder);
43
44        // Convert StringBuilder to String
45        String finalString = stringBuilder.toString();
46        System.out.println("StringBuilder converted to String: " + finalString);
47    }
48}

ArraysArrayList

Arrays 类提供了一系列静态方法来操作数组,而 ArrayList 是一个基于数组实现的可调整大小的集合。

  • Arrays 类的示例代码展示了如何使用 Arrays 类来打印数组内容、获取数组长度、执行二分查找、复制数组、排序以及填充数组。

  • ArrayListjava.util 包中的一个类,提供了动态数组的功能。示例代码展示了如何向 ArrayList 中添加元素、检查元素是否存在、获取和设置元素、移除元素以及清空列表。

ArraysAndArrayListExample.java
 1package examples.datastructures;
 2
 3import java.util.ArrayList;
 4import java.util.Arrays;
 5
 6public class ArraysAndArrayListExample {
 7    public static void main(String[] args) {
 8        // Example usage of Arrays
 9        int[] numbersArray = {1, 1, 4, 5, 1, 4};
10
11        // Print the length of the array
12        System.out.println("numbersArray.length = " + numbersArray.length);
13
14        // Print the first element of the array
15        System.out.println("numbersArray[0] = " + numbersArray[0]);
16
17        // Use Arrays.toString to print the contents of the array
18        System.out.println("Arrays.toString(numbersArray) = " + Arrays.toString(numbersArray));
19
20        // Check if the array is equal to itself
21        System.out.println("Arrays.equals(numbersArray, numbersArray) = " + Arrays.equals(numbersArray, numbersArray));
22
23        // Use Arrays.binarySearch to perform binary search for the element 4
24        System.out.println("Arrays.binarySearch(numbersArray, 4) = " + Arrays.binarySearch(numbersArray, 4));
25
26        // Use Arrays.copyOf to create a new array containing the first 3 elements of the original array
27        System.out.println("Arrays.copyOf(numbersArray, 3) = " + Arrays.toString(Arrays.copyOf(numbersArray, 3)));
28
29        // Use Arrays.copyOfRange to create a subarray from index 1 to 3
30        System.out.println("Arrays.copyOfRange(numbersArray, 1, 3) = " + Arrays.toString(Arrays.copyOfRange(numbersArray, 1, 3)));
31
32        // Use Arrays.sort to sort the array
33        Arrays.sort(numbersArray);
34        System.out.println("Arrays.sort(numbersArray) = " + Arrays.toString(numbersArray));
35
36        // Use Arrays.fill to fill the array with zeros
37        Arrays.fill(numbersArray, 0);
38        System.out.println("Arrays.fill(numbersArray, 0) = " + Arrays.toString(numbersArray));
39
40        // Print the hash code of the array after filling with zeros
41        System.out.println("Arrays.hashCode(numbersArray) = " + Arrays.hashCode(numbersArray));
42
43        // Example usage of ArrayList
44        ArrayList<String> sitesList = new ArrayList<>();
45
46        // Add elements to the ArrayList
47        sitesList.add("Google");
48        sitesList.add("Runoob");
49        sitesList.add("Taobao");
50        sitesList.add("Weibo");
51
52        // Print all elements in the ArrayList
53        System.out.println("ArrayList<String> sitesList = " + sitesList);
54
55        // Example of other ArrayList operations
56        // Check if the ArrayList contains a specific element
57        System.out.println("sitesList.contains(\"Runoob\") = " + sitesList.contains("Runoob"));
58
59        // Get the size of the ArrayList
60        System.out.println("sitesList.size() = " + sitesList.size());
61
62        // Remove an element from the ArrayList
63        sitesList.remove("Taobao");
64        System.out.println("sitesList after removing \"Taobao\" = " + sitesList);
65
66        // Get an element at a specific index
67        System.out.println("sitesList.get(1) = " + sitesList.get(1));
68
69        // Set an element at a specific index
70        sitesList.set(1, "Baidu");
71        System.out.println("sitesList after setting index 1 to \"Baidu\" = " + sitesList);
72
73        // Clear all elements in the ArrayList
74        sitesList.clear();
75        System.out.println("sitesList after clear() = " + sitesList);
76    }
77}

LinkedList

LinkedList 是 Java 中的一个双向链表实现,允许对列表中的元素进行高效的插入、删除操作。

示例代码展示了如何创建 LinkedList 实例、添加和删除元素、访问首尾元素、以及遍历链表。

LinkedListExample.java
 1package examples.datastructures;
 2
 3import java.util.LinkedList;
 4import java.util.List;
 5
 6public class LinkedListExample {
 7    public static void main(String[] args) {
 8        // Create a new LinkedList instance
 9        List<String> linkedList = new LinkedList<>();
10
11        // Add elements to the LinkedList
12        linkedList.add("Element 1");
13        linkedList.add("Element 2");
14        linkedList.add("Element 3");
15
16        // Print the LinkedList
17        System.out.println("Initial LinkedList: " + linkedList);
18
19        // Access the first element
20        String firstElement = linkedList.get(0);
21        System.out.println("First Element: " + firstElement);
22
23        // Access the last element
24        String lastElement = linkedList.get(linkedList.size() - 1);
25        System.out.println("Last Element: " + lastElement);
26
27        // Remove the first occurrence of an element
28        linkedList.remove("Element 2");
29        System.out.println("LinkedList after removing 'Element 2': " + linkedList);
30
31        // Add an element at the beginning
32        linkedList.addFirst("New First Element");
33        System.out.println("LinkedList after adding new first element: " + linkedList);
34
35        // Add an element at the end
36        linkedList.addLast("New Last Element");
37        System.out.println("LinkedList after adding new last element: " + linkedList);
38
39        // Remove the first element
40        linkedList.removeFirst();
41        System.out.println("LinkedList after removing the first element: " + linkedList);
42
43        // Remove the last element
44        linkedList.removeLast();
45        System.out.println("LinkedList after removing the last element: " + linkedList);
46
47        // Get the size of the LinkedList
48        int size = linkedList.size();
49        System.out.println("Size of the LinkedList: " + size);
50
51        // Check if the LinkedList is empty
52        boolean isEmpty = linkedList.isEmpty();
53        System.out.println("Is the LinkedList empty? " + isEmpty);
54
55        // Clear all elements from the LinkedList
56        linkedList.clear();
57        System.out.println("LinkedList after clear(): " + linkedList);
58
59        // Demonstrate iteration over LinkedList elements
60        System.out.println("Iterating over LinkedList elements:");
61        for (String element : linkedList) {
62            System.out.println(element);
63        }
64    }
65}

HashSet

HashSet 是 Java 中的一个不允许重复元素的集合实现。它基于 HashMap 实现,因此具有快速的查找速度。

HashSetExample.java
 1package examples.datastructures;
 2
 3import java.util.HashSet;
 4import java.util.Set;
 5
 6public class HashSetExample {
 7    public static void main(String[] args) {
 8        // Create a new HashSet instance
 9        Set<String> set = new HashSet<>();
10
11        // Add elements to the HashSet
12        set.add("Apple");
13        set.add("Banana");
14        set.add("Cherry");
15
16        // Print the HashSet
17        System.out.println("Initial HashSet: " + set);
18
19        // Check if the HashSet contains an element
20        boolean contains = set.contains("Banana");
21        System.out.println("Does the HashSet contain 'Banana'? " + contains);
22
23        // Remove an element from the HashSet
24        set.remove("Apple");
25        System.out.println("HashSet after removing 'Apple': " + set);
26
27        // Get the size of the HashSet
28        int size = set.size();
29        System.out.println("Size of the HashSet: " + size);
30
31        // Check if the HashSet is empty
32        boolean isEmpty = set.isEmpty();
33        System.out.println("Is the HashSet empty? " + isEmpty);
34
35        // Clear all elements from the HashSet
36        set.clear();
37        System.out.println("HashSet after clear(): " + set);
38
39        // Demonstrate iteration over HashSet elements
40        System.out.println("Iterating over HashSet elements:");
41        for (String fruit : set) {
42            System.out.println(fruit);
43        }
44
45        // Add elements back to the HashSet and demonstrate uniqueness
46        set.add("Apple"); // Duplicate element
47        set.add("Apple"); // Attempt to add duplicate
48        System.out.println("HashSet with duplicate 'Apple': " + set);
49    }
50}

HashMap

HashMap 是 Java 中的一个键值对集合,它允许存储唯一的键和对应的值。HashMap 不保证元素的顺序,并且不是线程安全的。

示例代码展示了如何创建 HashMap 实例、添加键值对、获取与键关联的值、检查键是否存在、移除键值对、获取 HashMap 的大小、检查 HashMap 是否为空以及清空 HashMap

HashMapExample.java
 1package examples.datastructures;
 2
 3import java.util.HashMap;
 4import java.util.Map;
 5
 6public class HashMapExample {
 7    public static void main(String[] args) {
 8        // Create a new HashMap instance
 9        Map<String, Integer> map = new HashMap<>();
10
11        // Put key-value pairs into the HashMap
12        map.put("One", 1);
13        map.put("Two", 2);
14        map.put("Three", 3);
15
16        // Print the HashMap
17        System.out.println("Initial HashMap: " + map);
18
19        // Get the value associated with a key
20        Integer value = map.get("Two");
21        System.out.println("Value for key 'Two': " + value);
22
23        // Check if a key exists in the HashMap
24        boolean containsKey = map.containsKey("Three");
25        System.out.println("Does the key 'Three' exist? " + containsKey);
26
27        // Remove a key-value pair from the HashMap
28        map.remove("One");
29        System.out.println("HashMap after removing 'One': " + map);
30
31        // Get the size of the HashMap
32        int size = map.size();
33        System.out.println("Size of the HashMap: " + size);
34
35        // Check if the HashMap is empty
36        boolean isEmpty = map.isEmpty();
37        System.out.println("Is the HashMap empty? " + isEmpty);
38
39        // Clear all key-value pairs from the HashMap
40        map.clear();
41        System.out.println("HashMap after clear(): " + map);
42
43        // Demonstrate iteration over HashMap entries
44        System.out.println("Iterating over HashMap entries:");
45        for (Map.Entry<String, Integer> entry : map.entrySet()) {
46            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
47        }
48    }
49}

面向对象程序设计

Java 最初的设计目标之一就是成为一种纯粹的面向对象语言。所有的代码都必须包含在类(Class)中,基本上所有元素都是对象(基本数据类型除外)。它支持封装、继承和多态等面向对象的核心概念,并鼓励开发者使用这些概念构建模块化和可重用的代码。具体地说,所有的 Java 代码都需要封装在类里,每一个 .java 文件恰有一个与其同名的 public

面向对象编程的基本流程为:

  1. 设计类 class Car { /* ... */ }
  2. 创建/实例化对象 Car myCar = new Car();
  3. 向对象发送消息 myCar.move();

Java 使用垃圾回收(Garbage Collection,GC)机制进行内存管理。开发者不需要显式地分配和释放内存,这减轻了开发负担并减少了内存泄漏和悬挂指针等常见的错误。也就是说,Java 没有指针的概念。函数传参只有传值没有传引用

Java 程序员只需要关心何时 new 一个对象,而不需要考虑何时 delete 它。

Java 通过封装的概念将数据和操作封装在对象中。对象可以隐藏其内部状态和实现细节,只暴露出对外提供的接口。这种封装性可以保护数据的安全性和一致性,并提供更好的模块化和代码组织。

以下代码均包含于 src/main/java/examples/oop 中。

package

Java 通过包(package)来组织代码。

  • 包名使用 . 建立层次结构,应当与文件目录结构相同。
  • 包名通常全小写且单词之间无分隔。
package1/package2/PackageExample.java
1package examples.oop.package1.package2;
2
3public class PackageExample {
4    public static void main(String[] args) {
5        System.out.println("This is a package example.");
6    }
7}

以上代码在目录中的组织结构为:

text
1examples
23└───oop
45    └───package1
67        └───package2
89            └───PackageExample.java

class

Java 中的 class 和 C++ 中的 class 比较类似。

类修饰符:publicprotectedprivate

与 C++ 中相同,Java 中也有 publicprotectedprivate 修饰符。

构造函数

Java 中构造函数的声明与 C++ 相同。

java
1class Test {
2    private int variable_1;
3    private String variable_2;
4    
5    public Test(int variable_1, String variable) { // 构造函数
6        this.variable_1 = variable_1; // 与参数同名,则需要显式写出 this
7        variable_2 = variable; // 不同名,则不需要显式写出 this
8    }
9}

析构函数

Java 语言本身并没有像 C++ 中那样明确定义的析构函数。Java 采用的是垃圾回收机制(Garbage Collection,GC)来自动管理内存,因此不需要程序员手动释放对象占用的内存。

虽然 Java 中也有类似于析构函数的 finalize() 语句,但它并不被推荐使用,具体的原因也不在这里多做介绍。感兴趣的同学可以查找 finalize() 方法、try-with-resource 语句和 AutoClosable 接口等相关资料。

static

static 是 Java 编程语言中的一个关键字,用于表示某个成员(变量或方法)属于类本身,而不是类的某个特定对象(实例)。这意味着使用 static 修饰的成员可以在不创建类实例的情况下被访问和使用。

主要特点

  1. 类级别的属性static 变量是类的所有实例共享的,称为类变量或静态变量。
  2. 无需实例化:可以直接通过类名访问 static 方法和变量,无需创建类的实例。
  3. 存储位置static 变量存储在 Java 堆内存的方法区,而非某个具体对象的内存空间。
  4. 生命周期static 变量和方法的生命周期与类加载和卸载的过程相关联。
  5. 不变性static 方法不能访问类的非静态成员变量和方法,因为它们与任何具体对象的状态无关。

代码示例

在下面这份代码中,在 Car 这个类里,counter 是一个静态变量。

  • 静态变量作为计数器counter 作为静态变量,用于跟踪创建的 Car 实例数量。每次创建 Car 实例时,counter 都会递增。这说明,静态变量在实例化过程中具有“共享”的特性。
  • 无需实例化访问:直接使用类名 Car,可以访问静态变量 counter。即使没有创建类的实例,也可以访问和操作静态成员。
StaticExample.java
 1package examples.oop;
 2
 3public class StaticExample {
 4    static class Car {
 5        static int counter = 0;
 6        String name;
 7
 8        Car(String name) {
 9            this.name = name;
10            counter++;
11        }
12        @Override
13        public String toString() {
14            return "Car [name=" + name + "]";
15        }
16    }
17    public static void main(String[] args) {
18        Car a = new Car("Benz");
19        System.out.println("First car: " + a + ". Total car count: " + Car.counter);
20        Car b = new Car("Honda");
21        System.out.println("Second car: " + b + ". Total car count: " + Car.counter);
22        Car c = new Car("Volkswagen");
23        System.out.println("Third car: " + c + ". Total car count: " + Car.counter);
24    }
25}

实例初始化块

在类中直接使用 {} 扩起来的部分称为实例初始化块,它会在任何构造器运行之前运行。

java
1class Test {
2    { System.out.println("This is an Instance Initialization Block."); }
3    public Test() {
4        System.out.println("This is a constructor.");
5    }
6}

当实例化一个 Test 类型的对象时,运行结果将会是

text
1This is an Instance Initialization Block.
2This is a constructor.

final

final 修饰的成员变量是常量,因此除了声明赋值、构造函数、实例初始化块之外,不允许对其进行其他修改。

通常使用 static final 来定义静态常量。一般使用 SCREAMING_SNAKE_CASE(全字母大写,用下划线分隔)来命名。例如:

java
1public class Wordle {
2    static final int ALPHABET_SIZE = 26;            // The size of the alphabet
3    static final int WORD_LENGTH = 5;               // The length of words
4    static final int TOTAL_CHANCES = 6;             // The chances in total
5    // ...
6}

组合与继承

组合

  • 概念:组合是一种对象包含其他对象的关系,展示了“has-a”关系。例如,一个汽车对象可能包含引擎和轮胎对象。
  • 实现:通过在类中创建其他类的实例作为成员变量来实现。
  • 优点:提供了更大的灵活性,因为组合的对象可以独立于组合类存在,并且可以在运行时动态地替换或修改。

代码示例

在这份代码中,一个汽车对象包含了一个引擎对象和一个轮胎对象。每个类(EngineTyreCar)都封装了自己的属性和行为,提供了一个清晰的接口来与外部世界交互。

CompositionExample.java
 1package examples.oop;
 2
 3public class CompositionExample {
 4    static class Engine {
 5        int typeCode;
 6
 7        Engine(int typeCode) {
 8            this.typeCode = typeCode;
 9        }
10        @Override
11        public String toString() {
12            return "Engine [typeCode=" + typeCode + "]";
13        }
14    }
15    static class Tyre {
16        int radius;
17
18        Tyre(int radius) {
19            this.radius = radius;
20        }
21        @Override
22        public String toString() {
23            return "Tyre [radius=" + radius + "]";
24        }
25    }
26    static class Car {
27        Engine engine;
28        Tyre tyre;
29
30        Car(int engineTypeCode, int tyreRadius) {
31            this.engine = new Engine(engineTypeCode);
32            this.tyre = new Tyre(tyreRadius);
33        }
34        @Override
35        public String toString() {
36            return "Car {" + engine + ", " + tyre + "}";
37        }
38    }
39    public static void main(String[] args) {
40        Car car = new Car(3, 5);
41        System.out.println(car);
42    }
43}

继承

  • 概念:继承是一种类与类之间的层级关系,展示了“is-a”关系。例如,狗类和猫类可以继承动物类的特性。
  • 实现:通过使用 extends 关键字来实现基类(父类)的继承。
  • 优点:支持代码复用,允许新类自动拥有基类的属性和方法。

代码示例

以下代码展示了 Dog 类和 Cat 类对于 Animal 类的继承关系。

  1. Animal:这是一个父类,代表动物,有两个属性 ageweight,以及一个 makeSound() 方法,该方法在被调用时打印 "Make sound"。
  2. Dog:这个类继承自 Animal 类,是子类。它添加了一个新属性 color,并在其构造函数中调用了父类的构造函数 super(age, weight) 来初始化继承的属性。它还重写了 makeSound() 方法,以打印 "Bark bark."。
  3. Cat:与 Dog 类似,Cat 类也继承自 Animal 类,添加了新属性 name。它同样调用了父类的构造函数来初始化继承的属性,并重写了 makeSound() 方法,以打印 "Meow meow."。
InheritanceExample.java
 1package examples.oop;
 2
 3public class InheritanceExample {
 4    static class Animal {
 5        int age, weight;
 6
 7        public Animal(int age, int weight) {
 8            this.age = age;
 9            this.weight = weight;
10        }
11        void makeSound() {
12            System.out.println("Make sound");
13        }
14    }
15    static class Dog extends Animal {
16        String color;
17
18        Dog(int age, int weight, String color) {
19            super(age, weight);
20            this.color = color;
21        }
22        @Override
23        void makeSound() {
24            System.out.println("Bark bark.");
25        }
26    }
27    static class Cat extends Animal {
28        String name;
29
30        Cat(int age, int weight, String name) {
31            super(age, weight);
32            this.name = name;
33        }
34        @Override
35        void makeSound() {
36            System.out.println("Meow meow.");
37        }
38    }
39    public static void main(String[] args) {
40        Dog dog = new Dog(3, 25, "White");
41        Cat cat = new Cat(4, 4, "Kitty");
42        dog.makeSound();
43        cat.makeSound();
44    }
45}

抽象类

事实上,在“继承”的代码示例中,我们会发现,Animal 类的 makeSound() 方法并没有被调用;这个方法被所有继承 Animal 类的子类都重载了。我们还会发现一个逻辑上的问题——世界上不存在一个仅仅是“动物”的对象。它可以是“狗”、可以是“猫”,但它不能仅仅是“动物”。

因此,我们应该将 Animal 类实现为一个抽象类。抽象类是一种不能被实例化的类,它通常被用作其他类的基类。抽象类可以包含抽象方法,这些方法没有具体的实现,就像 C++ 中的纯虚函数一样。

Animal 类中的 makeSound() 方法就是一个抽象方法。它只需要被声明,而不需要被定义。

更改后的代码如下:

AbstractClassExample.java
 1package examples.oop;
 2
 3public class AbstractClassExample {
 4    abstract static class Animal {
 5        int age, weight;
 6
 7        public Animal(int age, int weight) {
 8            this.age = age;
 9            this.weight = weight;
10        }
11        abstract void makeSound();
12    }
13    static class Dog extends Animal {
14        String color;
15
16        Dog(int age, int weight, String color) {
17            super(age, weight);
18            this.color = color;
19        }
20        @Override
21        void makeSound() {
22            System.out.println("Bark bark.");
23        }
24    }
25    static class Cat extends Animal {
26        String name;
27
28        Cat(int age, int weight, String name) {
29            super(age, weight);
30            this.name = name;
31        }
32        @Override
33        void makeSound() {
34            System.out.println("Meow meow.");
35        }
36    }
37    public static void main(String[] args) {
38        Dog dog = new Dog(3, 25, "White");
39        Cat cat = new Cat(4, 4, "Kitty");
40        dog.makeSound();
41        cat.makeSound();
42        // Animal animal = new Animal(1, 2); // This is prohibited
43    }
44}

多重继承与接口

多重继承

多重继承是指一个类可以同时继承多个父类。然而,在 Java 中,类的多重继承是不被支持的,这意味着 Java 类不能直接从多个类继承。这是因为多重继承可能导致所谓的“菱形问题”(Diamond Problem)。

假设其中继承的两个父类可能都定义了相同的方法,那么应该从哪个父类中继承该方法呢?这没有办法确定。

接口

由于 Java 不支持类的多重继承,接口提供了一种替代方案来实现类似多重继承的特性。接口是一组抽象方法的集合,它可以被任何类实现,而不受这个类继承体系的限制。

如果你学过 Rust,那么你会发现,这类似于 Rust 里的 trait

InterfaceExample.java
 1package examples.oop;
 2
 3public class InterfaceExample {
 4    interface Flyable {
 5        void fly();
 6    }
 7
 8    interface Drivable {
 9        void drive();
10    }
11    
12    static class Vehicle implements Flyable, Drivable {
13        @Override
14        public void fly() {
15            System.out.println("Flying");
16        }
17
18        @Override
19        public void drive() {
20            System.out.println("Driving");
21        }
22    }
23    public static void main(String[] args) {
24        Vehicle vehicle = new Vehicle();
25        vehicle.fly();
26        vehicle.drive();
27    }
28}

java.lang.Object

所有对象都是它的对象。

在 Java 中,java.lang.Object 是所有类的基类,它位于类继承层次结构的顶端。每个 Java 类(除了 Object 本身)都隐式地继承自 Object 类。Object 类提供了一些基本方法,这些方法必须在子类中正确实现,以确保正确处理“判断两个对象是否相等”这一问题。

对象的字符串描述:toString()

  • toString() 方法默认返回 <类型名>@<哈希码的十六进制表示> 的形式,例如 Student@68be2bc2。这种默认实现通常没有实际用途。

  • 为了提供更有用的字符串描述,通常需要重写 toString() 方法。例如

    java
    1@Override
    2
    3public String toString() {
    4
    5    return "Person [name=" + name + ", age=" + age + "]";
    6
    7}
    

比较二者是否相等:equals()

equals() 方法用于比较两个对象的内容是否相等,而不是它们的内存地址。

  • 自反性x.equals(x) 应返回 true
  • 对称性x.equals(y) 应与 y.equals(x) 相等。
  • 传递性:如果 x.equals(y)y.equals(z) 都返回 true,则 x.equals(z) 也应返回 true
  • 一致性:多次调用 equals() 方法的结果应保持一致。
  • 非空性x.equals(null) 应返回 false

推荐的 equals() 的重载方法如下:

java
 1@Override
 2public boolean equals(Object o) {
 3    if (this == o) return true; // 如果两个对象引用相同,那内容一定相同
 4    if (o == null || getClass() != o.getClass()) return false; // 如果不是这个类的对象,那么一定不相同
 5    Student student = (Student) o; // 强制转换为此类对象
 6    if (studentID != student.studentID) return false; // 依次比较各个域
 7    if (age != student.age) return false; // 依次比较各个域
 8    // ...
 9    // 依次比较各个域
10    return true;
11}

哈希码:hashCode()

哈希码是用于哈希表(如 HashMapHashSet)中快速查找对象的关键。正确实现哈希码对于这些数据结构的性能至关重要。

  • 一致性:如果两个对象通过 equals() 方法比较相等,则它们的哈希码也必须相等。
  • 不同对象的哈希码应尽可能不同:这有助于减少哈希冲突。

为了减轻程序员设计哈希函数的负担,Objects.hash() 已经提供了一种哈希函数:

java
1@Override
2public int hashCode() {
3    return Objects.hash( /* 填入各个成员域 */ );
4}

代码示例

ObjectExample.java
 1package examples.oop;
 2
 3import java.util.Objects;
 4
 5public class ObjectExample {
 6    static abstract class Person {
 7        String name;
 8        int age;
 9
10        public Person(String name, int age) {
11            this.name = name;
12            this.age = age;
13        }
14        @Override
15        public String toString() {
16            return "Person [name=" + name + ", age=" + age + "]";
17        }
18        @Override
19        public boolean equals(Object o) {
20            if (this == o) return true;
21            if (o == null || getClass() != o.getClass()) return false;
22            Person person = (Person) o;
23            if (age != person.age) return false;
24            return Objects.equals(name, person.name);
25        }
26        @Override
27        public int hashCode() {
28            return Objects.hash(name, age);
29        }
30    }
31    static class Student extends Person {
32        int studentID;
33
34        public Student(String name, int age, int studentID) {
35            super(name, age);
36            this.studentID = studentID;
37        }
38        @Override
39        public String toString() {
40            return "Student [name=" + name + ", age=" + age + ", studentID=" + studentID + "]";
41        }
42        @Override
43        public boolean equals(Object o) {
44            if (this == o) return true;
45            if (o == null || getClass() != o.getClass()) return false;
46            Student student = (Student) o;
47            if (studentID != student.studentID) return false;
48            if (age != student.age) return false;
49            return Objects.equals(name, student.name);
50        }
51        @Override
52        public int hashCode() {
53            return Objects.hash(name, age, studentID);
54        }
55    }
56    static class Teacher extends Person {
57        int teacherID;
58
59        public Teacher(String name, int age, int teacherID) {
60            super(name, age);
61            this.teacherID = teacherID;
62        }
63        @Override
64        public String toString() {
65            return "Teacher [name=" + name + ", age=" + age + ", teacherID=" + teacherID + "]";
66        }
67        @Override
68        public boolean equals(Object o) {
69            if (this == o) return true;
70            if (o == null || getClass() != o.getClass()) return false;
71            Teacher teacher = (Teacher) o;
72            if (teacherID != teacher.teacherID) return false;
73            if (age != teacher.age) return false;
74            return Objects.equals(name, teacher.name);
75        }
76    }
77    public static void main(String[] args) {
78        Student student_1 = new Student("Alice", 18, 2023000000);
79        Teacher teacher_1 = new Teacher("Bob", 50, 2000000000);
80        Teacher teacher_2 = new Teacher("Carol", 45, 2000000001);
81        Teacher teacher_3 = new Teacher("Bob", 50, 2000000000);
82        System.out.println("student_1 = " + student_1);
83        System.out.println("teacher_1 = " + teacher_1);
84        System.out.println("teacher_2 = " + teacher_2);
85        System.out.println("student_1 == teacher_1 ? " + student_1.equals(teacher_1));
86        System.out.println("teacher_1 == teacher_2 ? " + teacher_1.equals(teacher_2));
87        System.out.println("teacher_1 == teacher_3 ? " + teacher_1.equals(teacher_3));
88    }
89}

包装类

在 Java 中,基本数据类型是预定义的,它们是原始类型,直接存储在内存中,而不是对象。Java 提供了一种特殊的类,称为包装类(Wrapper Class),用于将基本类型包装为对象。

以下是 Java 中的基本类型及其对应的包装类:

基本类型包装类
intInteger
doubleDouble
floatFloat
charCharacter
byteByte
shortShort
longLong
booleanBoolean

基本类型不能直接参与面向对象的编程,但包装类可以。这些包装类允许基本类型参与到面向对象的编程中,例如在集合框架中使用。这意味着,HashSet<int> 其实是不合法的,你应写 HashSet<Integer>

举例:Integer

Integer 类是 int 类型的包装类。以下是一些常用的 Integer 类方法:

  • parseInt(String s):将字符串解析为整数。
  • toString(int i):将整数转换为字符串。
  • valueOf(String s):将字符串转换为 Integer 对象。
  • compare(int x, int y):比较两个整数的大小。
  • max(int a, int b)min(int a, int b):返回两个整数中的最大值或最小值。

其他包装类

其他包装类如 DoubleFloatCharacter 等也提供了类似的功能。例如:

  • Double.parseDouble(String s):将字符串解析为双精度浮点数。
  • Character.isDigit(char ch):检查字符是否为数字。
  • Byte.parseByte(String s, int radix):将字符串解析为 byte 类型的整数。

自动装箱和拆箱

Java 5 引入了自动装箱(Autoboxing)和拆箱(Unboxing)的概念,使得基本类型和其对应的包装类之间的转换更加方便:

  • 自动装箱:将基本类型自动转换为相应的包装类对象。

    java
    1int i = 5;
    2
    3Integer iObject = i; // 自动装箱
    
  • 自动拆箱:将包装类对象自动转换为基本类型。

    java
    1Integer iObject = new Integer(5);
    2
    3int i = iObject; // 自动拆箱
    

虽然自动装箱和拆箱使得代码更简洁,但过度使用可能会导致性能问题,尤其是在循环中。此外,包装类也有缓存机制,例如 Integer.valueOf(int i) 方法在 -128127 范围内的值会被缓存。

枚举类

Java 中的枚举类型是一种特殊的类,它允许定义一组固定的常量。枚举类型提供了一种方式来定义一个类型安全的枚举,并且可以包含字段、方法和构造函数。

常见方法

枚举类中有以下常见方法:

  • values():返回枚举类型的所有枚举常量的数组。
  • toString():返回枚举常量的字符串表示形式。默认情况下,它返回枚举常量的名称。
  • hashCode():返回枚举常量的哈希码。
  • ordinal():返回枚举常量在枚举声明中的顺序,从零开始。

对于枚举类中更多的方法,详情请见 Enum (Java SE 21 & JDK 21)

特点

枚举类型在需要一组固定的常量时非常有用,例如星期、月份、颜色、方向等,枚举类能使得代码更加清晰和易于维护。

  • 枚举类型是单例的,每个枚举项都是唯一的实例。
  • 枚举可以包含字段、方法和构造函数,但构造函数不能是公开的
  • 枚举类型默认继承自 java.lang.Enum 类。

代码示例

EnumExample.java
 1package examples.oop;
 2
 3public class EnumExample {
 4    public enum Color {
 5        RED, GREEN, YELLOW, BLACK, WHITE
 6    }
 7    public enum ColorWithAbbr {
 8        RED("R"), GREEN("G"), YELLOW("Y"), BLACK("B"), WHITE("W");
 9
10        private final String abbr;
11        ColorWithAbbr(String abbr) {
12            this.abbr = abbr;
13        }
14        public String getAbbr() {
15            return abbr;
16        }
17    }
18    public static void main(String[] args) {
19        Color[] colors = Color.values();
20        for (Color color : colors) {
21            System.out.println(color + " toString() = " + color.toString());
22            System.out.println(color + " name() = " + color.name());
23            System.out.println(color + " hashCode() = " + color.hashCode());
24            System.out.println(color + " ordinal() = " + color.ordinal());
25        }
26
27        ColorWithAbbr[] colorsWithAbbr = ColorWithAbbr.values();
28        for (ColorWithAbbr color : colorsWithAbbr) {
29            System.out.println(color + " getAbbr() = " + color.getAbbr());
30            System.out.println(color + " toString() = " + color);
31            System.out.println(color + " name() = " + color.name());
32            System.out.println(color + " hashCode() = " + color.hashCode());
33            System.out.println(color + " ordinal() = " + color.ordinal());
34        }
35    }
36}

参考资料

课后作业

详情请见 sast-summer-training-2024/sast2024-java