一、简介

📢 公司:由 SUN 公司 詹姆斯-高斯林 开发,后被 甲骨文(Oracle)收购。
知名作品:我的世界、淘宝网、Android 操作系统。

Java 容器:很多繁琐又重复的工作我们可以提前做好,然后调用,但谁来做呢,有个组织出来定义了接口,谁家都可以造轮子,用户想用哪家的都可以,各家自己造的轮子(如Tomcat、GlassFish、IBM WebSphere)就叫做 Java 容器。随着越来越多的企业加入到这个阵营,官方给出的规范组件并不是最受欢迎的,反而一些企业的组件在实际开发中更让人喜欢。

Java SE,EE,ME 三者的区别

1、开发桌面应用的 Java SE(Java Platform,Standard Edition)
2、开发 Web 应用的 Java EE(Java Platform,Enterprise Edition)
3、Android 开发移动应用 和 嵌入式 的 Java ME(Java Platform,Micro Edition)
4、参考连接: Java SE EE ME具体区别

JAR: Java 类库的 class 文件。

JDK,JRE,JVM 三者的区别

总的来说,JDK 中包含 JRE,因为开发总要运行嘛,而 JRE 又包含 JVM。具体可以打开我们下载的JDK文件夹,里面又包含了一个JRE文件夹。参考CSDN
1、JDK:JDK(Java Development Kit)称为 Java 开发包 或 Java 开发工具,是一个编写 Java 的 Applet 小程序和应用程序的程序开发环境。JDK 是整个 Java 的核心,包括了 Java 运行环境 JRE(Java Runtime Envirnment),JVM 和 Java 的核心类库(Java API)。 JDK 不同版本特性
2、JRE: JRE(Java Runtime Envirnment)运行java程序的环境,JRE里面只有client运行环境,安装过程中,会自动添加PATH。
3、JVM:Java 虚拟机(Java Virtual Machine)

Java和C++的区别

  1. Java是解释型语言,所谓的解释型语言,就是源码会先经过一次编译,成为中间码,中间码再被解释器解释成机器码。对于Java而言,中间码就是字节码(.class),而解释器在JVM中内置了。
  2. C++是编译型语言,所谓编译型语言,就是源码一次编译,直接在编译的过程中链接了,形成了机器码。
  3. C++比Java执行速度快,但是Java可以利用JVM跨平台。
  4. Java是纯面向对象的语言,所有代码(包括函数、变量)都必须在类中定义。而C++中还有面向过程的东西,比如是全局变量和全局函数。
  5. C++中有指针,Java中没有,但是有引用。
  6. C++支持多继承,Java中类都是单继承的。但是继承都有传递性,同时Java中的接口是多继承,类对接口的实现也是多实现。
  7. C++中,开发需要自己去管理内存,但是Java中JVM有自己的GC机制,虽然有自己的GC机制,但是也会出现OOM和内存泄漏的问题。C++中有析构函数,Java中Object的finalize方法
  8. C++运算符可以重载,但是Java中不可以。同时C++中支持强制自动转型,Java中不行,会出现ClassCastException(类型不匹配)。

Linux、Windows与MAC OS下的换行符

我使用的是 Windows,提交上来的文档总是不能够换行,导致格式很糟糕,于是就去查了一下,是不是提交上来的换行符有问题。参考连接

历史:为了保证打字机换行时消耗的时间内不会有其它字符进来,主动添加了两个无效字符(回车 换行)。
计算机出现后,开始讨论加一个还是两个的问题,注意了 Typora 的软换行在 GitHub 中是无效的,在 Typora 中按下 Shift + Enter 是软换行,按下此组合键后,可以看到换了一行,但是推送到 GitHub 上后,你会发现换行是无效的。要解决这问题,你就要搞清楚空格、软换行、硬换行、换段的在 Typora 中的概念。

Typora 在空格与换行部分主要是使用 CommonMark 作为标注规范。与前文提到的 GFM 一样,CommonMark 也是比较流行的 Markdown 语言规范(解析器)之一。

  • 空格:在输入连续的空格后,Typora 会在编辑器视图里为你保留这些空格,但当你打印或导出时,这些空格会被省略成一个。
    你可以在源代码模式下,为每个空格前加一个 \ 转义符,或者直接使用 HTML 风格的 &nbps; 来保持连续的空格。
  • 软换行: 需要说明的是,在 Markdown 语法中,换行(line break)与换段是不同的。且换行分为软换行和硬换行。在 Typora 中,你可以通过 Shift + Enter 完成一次软换行。软换行只在编辑界面可见,当文档被导出时换行会被省略。
  • 硬换行: 你可以通过 空格 + 空格 + Shift + Enter 完成一次硬换行,而这也是许多 Markdown 编辑器所原生支持的。硬换行在文档被导出时将被保留,且没有换段的段后距。
  • 换段: 你可以通过 Enter 完成一次换段。Typora 会自动帮你完成两次 Shift + Enter 的软换行,从而完成一次换段。这也意味着在 Markdown 语法下,换段是通过在段与段之间加入空行来实现的。
  • Windows 风格(CR+LF)与 Unix 风格(CR)的换行符: CR 表示回车 \r ,即回到一行的开头,而 LF 表示换行 \n ,即另起一行。
    所以 Windows 风格的换行符本质是「回车 + 换行」,而 Unix 风格的换行符是「换行」。这也是为什么 Unix / Mac 系统下的文件,如果在 Windows 系统直接打开会全部在同一行内。 你可以在 文件 - 偏好设置 - 编辑器 - 默认换行符 中对此进行切换。

二、从0-1配置Java

1. 安装 Java 开发环境

1.1 安装 JDK(Java Development Kit)

JDK 是 Java 开发的核心工具包,包含编译器(javac)和 JRE(Java 运行环境)。

  • 下载:访问 Oracle JDK 或 OpenJDK,根据系统选择版本(如 Windows 64 位)。
  • 安装:运行安装程序,按默认路径安装(如C:\Program Files\Java\jdk-17)。
  • 配置环境变量
    • JAVA_HOME:指向 JDK 安装路径(如C:\Program Files\Java\jdk-17)。
    • PATH:添加%JAVA_HOME%\bin到系统路径。
    • 验证:打开命令行输入java -versionjavac -version,显示版本号即成功。

1.2 安装集成开发环境(IDE)

推荐使用 IntelliJ IDEA Community Edition(免费)或 Eclipse

常用快捷键:

快捷键 作用
main/psvmsout、… 快速键入相关代码
Ctrl + D 复制当前行数据到下一行
Ctrl + Y 删除所在行,建议用Ctrl + X
Ctrl + ALT + L 格式化代码
ALT + SHIFT + ⬆ALT + SHIFT + ⬇ 上下移动当前行代码
Ctrl + /Ctrl + SHIFT +/ 对代码进行注释

2. 编写第一个 Java 程序

2.1 创建项目

以 IntelliJ IDEA 为例:

  1. 打开 IDE,选择File → New → Project
  2. 选择Java,SDK 选择已安装的 JDK,点击Create

2.2 编写 Hello World

src目录下创建Main.java文件,输入以下代码:

java

public class Main { public static void main(String[] args) { System.out.println("Hello, World!"); } }

image

  • 代码解释
    • public class Main:定义一个名为Main的公共类。
    • public static void main(String[] args):程序入口方法,Java 虚拟机(JVM)从这里开始执行。
    • System.out.println():向控制台输出内容。

2.3 运行程序

  • 在代码编辑器右键点击 → Run 'Main.main()'

  • 或使用命令行:

    bash

    `# 编译Java文件
    javac Main.java

    运行编译后的类

    java Main`

    image

  • 输出:Hello, World!

三、Java基础语法

1. 命名与基本方法

  • 类名:UpperCamelCase,首字母全部大写
  • 方法名:lowerCamelCase,第一个首字母小写
  • 源文件名:Java 文件名必须匹配类名。请使用类名保存文件并添加扩展名 “.java”。(如果文件名和类名不相同则会导致编译错误)。
  • 关键字与保留字:Java 保留字是指现在 Java 版本尚未使用,但以后版本可能会作为关键字使用。所以注意 false、true、null 等都是保留字。
  • Java 标识符:字母、数字、下划线、美元符($)。数字不能作为首位。
  • 主方法入口:所有的 Java 程序由 public static void main(String[] args) 方法开始执行。

2. 输入输出方法

使用标准输入输出流。

2.1 标准输入流(System.in

  • 类型:InputStream(字节流)
  • 作用:从控制台(键盘)读取输入数据
  • 通常需要包装为字符流使用,更方便处理文本

示例:使用**System.in**读取输入

import java.io.IOException;
import java.io.InputStream;

public class StdinExample {
    public static void main(String[] args) {
        System.out.print("请输入内容: ");
        
        try (InputStream in = System.in) {
            byte[] buffer = new byte[1024];
            int bytesRead = in.read(buffer); // 读取字节
            String input = new String(buffer, 0, bytesRead).trim();
            System.out.println("你输入了: " + input);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

实际开发中,更常用ScannerBufferedReader包装System.in

  • BufferedReader:主要用于高效读取字符流,特别适合读取大文件或需要缓冲的场景,只能读取字符串类型数据,使用 readLine() 读取整行.
  • Scanner:专门设计用于解析和读取各种类型的数据,包括基本数据类型,可以直接读取多种数据类型(int, double, String 等), 提供多种读取方法如 nextLine(), nextInt(),nextDouble() 等.
// 使用BufferedReader包装(推荐)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();
System.out.print(line);

// 使用Scanner(简单场景)
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
System.out.print(input);

2.2 标准输出流(System.out

  • 类型:PrintStream(字节流的子类,提供方便的打印方法)
  • 作用:向控制台输出数据
  • 常用方法:print()println()printf()
方法 描述
print() 将文本或值打印到控制台。
printf() 将格式化文本或值打印到控制台。
println() 将文本或值打印到控制台,并在其后添加新行。

示例:使用**System.out**输出

public class StdoutExample {
    public static void main(String[] args) {
        System.out.print("这是print,"); // 不自动换行
        System.out.println("这是println,会自动换行");
        
        // 格式化输出
        String name = "Java";
        int version = 17;
        System.out.printf("编程语言: %s, 版本: %d%n", name, version);
    }
}

2.3 标准错误流(System.err

  • 类型:PrintStream
  • 作用:向控制台输出错误信息
  • 特点:通常显示为红色(取决于终端),用于区分正常输出和错误信息
public class StderrExample {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;
            System.out.println("结果: " + result);
        } catch (ArithmeticException e) {
            // 使用err输出错误信息
            System.err.println("发生错误: " + e.getMessage());
        }
    }
}

2.4 重定向标准流

可以通过System类的方法重定向标准流,将输入输出指向文件或其他流:

import java.io.FileOutputStream;
import java.io.PrintStream;

public class RedirectStreams {
    public static void main(String[] args) throws Exception {
        // 保存原始输出流
        PrintStream originalOut = System.out;
        
        // 重定向标准输出到文件
        System.setOut(new PrintStream(new FileOutputStream("output.txt")));
        System.out.println("这句话会被写入文件");
        
        // 恢复原始输出流
        System.setOut(originalOut);
        System.out.println("这句话会显示在控制台");
    }
}

3. 注释

  • // :单行注释
  • /* */ :多行注释

4. 变量

  • String - 存储文本,例如 “Hello”。字符串值用双引号括起来
  • int - 存储整数(whole numbers),没有小数,例如 123 或 -123
  • float - 存储带小数的浮点数,例如 19.99 或 -19.99
  • char - 存储单个字符,例如 ‘a’ 或 ‘B’。 char 值用单引号括起来
  • boolean - 以两种状态存储值:true 或 false

声明(创建)变量

如需创建变量,必须指定类型并为其赋值:

typevariable =value;

其中 type 是 Java 的一种类型(比如 int 或者 String),variableName 是变量的名称(比如 x 或者 name)。等号用于为变量赋值。

Final变量

final 关键字(这会将变量声明为 “final” 或 “constant”,表示不可更改和只读):

final int myNum = 15;
myNum = 20;  // 将产生错误:无法为 final 变量赋值

5. 数据类型

数据类型分为两组:

  • 原始数据类型 - 包括 byteshortintlongfloatdoubleboolean 和 char
  • 非原始数据类型 - 例如 字符串、数组 和 类

5.1 原始数据类型

原始数据类型指定变量值的大小和类型,并且不拥有额外的方法。

Java 中有八种原始数据类型:

数据类型 大小 描述
byte 1 字节 存储从 -128 到 127 的整数。
short 2 字节 存储从 -32,768 到 32,767 的整数。
int 4 字节 存储从 -2,147,483,648 到 2,147,483,647 的整数。
long 8 字节 存储从 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 的整数。
float 4 字节 存储小数。足以存储 6 到 7 位十进制数字。
double 8 字节 存储小数。足以存储 15 位十进制数字。
boolean 1 比特 存储 true 值或 false 值。
char 2 字节 存储单个字符/字母或 ASCII 值

5.2 数字

原始数字类型分为两组:

  • 整数类型 存储整数,正数或负数(例如 123 或 -456),没有小数。有效类型为 byteshortint 和 long。您应该使用哪种类型取决于数值。
  • 浮点类型 表示带有小数部分的数字,包含一位或多位小数。有两种类型:float 和 doublefloat 的精度只有六位或七位小数,而 double 变量的精度约为 15 位。因此,对于大多数计算来说,使用 double 更安全。

**! 提示:**尽管 Java 中有许多数字类型,但最常用于数字的是 int(整数)和 double(浮点数)。

// 示例
byte byte_Num = 100;
short short_Num = 5000;
int int_Num = 100000;
long long_Num = 15000000000L;
float float_Num = 5.75f;
double double_Num = 19.99d;
boolean isJavaFun = true;
char myGrade = 'B';
String greeting = "Hello World";

5.3 非原始数据类型

非原始数据类型称为_引用类型_,因为它们引用对象。

_原始数据类型和非原始_数据类型之间的主要区别是:

  • Java 中预定义(已经定义了)原始类型。非原始类型是由程序员创建的,不是由 Java 定义的(String 除外)。
  • 非原始类型可用于调用方法来执行某些操作,而原始类型则不能。
  • 原始类型总是有值,而非原始类型可以为 null
  • 原始类型以小写字母开头,而非原始类型以大写字母开头。

6. 类型转换

类型转换指的是将一种原始数据类型的值赋给另一种类型。

在 Java 中,有两种类型的转换:

  • 放大转换(Widening Casting)(自动) - 将较小的类型转换为较大的类型byte -> short -> char -> int -> long -> float -> double
  • 缩小转换(Narrowing Casting)(手动) - 将较大的类型转换为较小的类型double -> float -> long -> int -> char -> short -> byte
// 放大转换自动完成
int myInt = 9;
double myDouble = myInt; // 自动转换:int 到 double

// 缩小转换必须再值前面的括号中指明转换类型
double myDouble = 9.78d;
int myInt = (int) myDouble; // 手动转换:double 转换为 int

7. 运算符

7.1 算数运算符

运算符 名称 描述 例子
+ 将两个值相加 x + y
- 从另一个值中减去一个值 x - y
* 两个值相乘 x * y
/ 将一个值除以另一个值 x / y
% 取余 返回除法余数 x % y
++ 递增 将变量的值加 1 ++x
递减 将变量的值减 1 --x

7.2 赋值运算符

运算符 例子 等同于
= x = 5 x = 5
+= x += 3 x = x + 3
-= x -= 3 x = x - 3
*= x *= 3 x = x * 3
/= x /= 3 x = x / 3
%= x %= 3 x = x % 3
&= x &= 3 x = x & 3
= `x
^= x ^= 3 x = x ^ 3
>>= x >>= 3 x = x >> 3
<<= x <<= 3 x = x << 3

7.3 比较运算符

运算符 名称 等同于
== 等于 x == y
!= 不相等 x != y
> 大于 x > y
< 小于 x < y
>= 大于或等于 x >= y
<= 小于或等于 x <= y

7.4 逻辑运算符

运算符 名称 描述 例子
&& 逻辑和 如果两条语句都为真,则返回 true。 x < 5 && x < 10
逻辑或
! 逻辑否 反转结果。如果结果为真,则返回 false。 !(x < 5 && x < 10)

8. 字符串

  • String是不可变对象,所有修改操作(如replacesubstring)都会返回新的字符串,原字符串不变。
  • 频繁拼接字符串时,建议使用StringBuilderStringBuffer以提高性能。

8.1 常用字符串方法

  • length():返回字符串长度
String str = "Hello";
int len = str.length(); // 结果:5
  • isEmpty():判断字符串是否为空(长度为 0)
"".isEmpty(); // true
"a".isEmpty(); // false
  • isBlank()(Java 11+):判断字符串是否为空或仅包含空白字符
"   ".isBlank(); // true
" a ".isBlank(); // false
  • toUpperCase() :将字符串全部转换为大写字母
String txt = "Hello World";
System.out.println(txt.toUpperCase());   // 输出 "HELLO WORLD"
  • toLowerCase():将字符串全部转换为小写字母
String txt = "Hello World";
System.out.println(txt.toLowerCase());   // 输出 "hello world"
  • trim():去除首尾空白字符(不包括中间)
"  Hello  ".trim(); // "Hello"
  • strip()(Java 11+):去除首尾空白字符(支持 Unicode 空白)
" Hello ".strip(); // "Hello"(全角空格也会被去除)
  • replace(char oldChar, char newChar):替换所有指定字符
"Hello".replace('l', 'x'); // "Hexxo"
  • replace(CharSequence target, CharSequence replacement):替换所有指定子串
"Hello World".replace("World", "Java"); // "Hello Java"
  • replaceAll(String regex, String replacement):按正则表达式替换
"a1b2c3".replaceAll("\\d", ""); // "abc"(移除所有数字)
  • toCharArray():转换为字符数组
char[] arr = "abc".toCharArray(); // ['a','b','c']
  • valueOf(...):静态方法,将其他类型转换为字符串
String.valueOf(123); // "123"
String.valueOf(true); // "true"
  • join(CharSequence delimiter, CharSequence... elements):拼接多个字符串并添加分隔符
String.join("-", "2023", "10", "01"); // "2023-10-01"

8.2 字符串的截取与拆分

  • substring(int beginIndex):从指定索引截取到末尾
"Hello".substring(2); // "llo"
  • substring(int beginIndex, int endIndex):截取 [begin, end) 区间的子串
"Hello".substring(1, 4); // "ell"
  • split(String regex):按正则表达式拆分字符串为数组
"a,b,c".split(","); // ["a", "b", "c"]

8.3 字符串的查找与判断

  • contains(CharSequence s):判断是否包含指定字符序列
"Hello World".contains("World"); // true
  • startsWith(String prefix):判断是否以指定前缀开头
"Hello".startsWith("He"); // true
  • endsWith(String suffix):判断是否以指定后缀结尾
"file.txt".endsWith(".txt"); // true
  • indexOf(String str):返回指定子串首次出现的索引(未找到返回 - 1)
"abcabc".indexOf("ab"); // 0
  • lastIndexOf(String str):返回指定子串最后出现的索引
"abcabc".lastIndexOf("ab"); // 3
  • charAt(int index):返回指定索引的字符
"Hello".charAt(1); // 'e'

8.4 字符串的拼接与比较

  • concat(String str):拼接字符串(等价于 + 运算符)
"Hello".concat(" World"); // "Hello World"
// 直接使用+也可以
String str = "Hello" + "World";
  • equals(Object anObject):判断内容是否相等(区分大小写)
"Hello".equals("hello"); // false
  • equalsIgnoreCase(String anotherString):忽略大小写比较内容
"Hello".equalsIgnoreCase("hello"); // true
  • compareTo(String anotherString):按字典顺序比较(返回整数)
"a".compareTo("b"); // -1(a在b前)
"b".compareTo("a"); // 1(b在a后)
"a".compareTo("a"); // 0(相等)

8.5 字符串中的转义字符

转义字符 结果 描述
' 单引号
" " 双引号
\ \ 反斜杠

9. 数学方法

注意:所有数学方法都是 static(静态的)。

方法 描述 返回类型
abs(x) 返回 x 的绝对值。 double
acos(x) 返回 x 的反余弦值,以弧度为单位。 double
addExact(xy) 返回 x 和 y 的和。 int
asin(x) 返回 x 的反正弦,以弧度为单位。 double
atan(x) 返回 x 的反正切值,以 -PI/2 和 PI/2 弧度之间的数值。 double
atan2(y,x) 返回从直角坐标 (xy) 转换为极坐标 (r, theta) 的角度 theta。 double
cbrt(x) 返回 x 的立方根。 double
ceil(x) 返回 x 的值向上舍入到最接近的整数。 double
copySign(xy) 返回第二个浮点 y 的符号和第一个浮点 x double
cos(x) 返回 x 的余弦值(x 以弧度为单位)。 double
cosh(x) 返回 double 值的双曲余弦值。 double
decrementExact(x) 返回 x-1。 int
exp(x) 返回 Ex 的值。 double
expm1(x) 返回 ex -1。 double
floor(x) 返回向下舍入到最接近的整数的 x 的值。 double
floorDiv(xy) 返回 x 除以 y 向下取整的结果。 int
floorMod(xy) 返回 x 除以 y 的余数,其中除法结果已向下取整。 int
getExponent(x) 返回 x 中使用的无偏指数(unbiased exponent)。 int
hypot(xy) 返回没有中间溢出或下溢的 sqrt(_x_2 + _y_2) 。 double
IEEEremainder(xy) 规定计算 x 和 y 的余数运算,按照 IEEE 754 标准。 double
incrementExact(x) 返回 x+1 int
log(x) 返回 x 的自然对数(以 E 为底)。 double
log10(x) 返回 x 的以 10 为底的对数。 double
log1p(x) 返回 x 和 1 之和的自然对数(以 E 为底)。 double
max(x, y) 返回有最高值的数字。 double
min(x, y) 返回有最小值的数字。 double
multiplyExact(xy) 返回 x 与 y 的乘积结果。 int
negateExact(x) 返回 x 的相反数。 int
nextAfter(xy) 返回 y 方向与 x 相邻的浮点数。 double
nextDown(x) 返回在负方向上与 x 相邻的浮点值。 double
nextUp(x) 返回正无穷大方向上与 x 相邻的浮点值。 double
pow(x, y) 返回 x 的 y 次方的值。 double
random() 返回 0 到 1 之间的随机数。 double
rint(x) 返回最接近 x 且等于某个数学整数的双精度值。 double
round(x) 返回舍入到最接近整数的 x 的值。 int
scalb(xy) 返回 x 乘以 2 的 y 次幂的结果。 double
signum(x) 返回 x 的符号。 double
sin(x) 返回 x 的正弦值(x 以弧度为单位)。 double
sinh(x) 返回双精度值的双曲正弦值。 double
sqrt(x) 返回 x 的平方根。 double
subtractExact(xy) 返回 x 减去 y 的结果。 int
tan(x) 返回角度的正切。 double
tanh(x) 返回 double 值的双曲正切值。 double
toDegrees(x) 将以弧度为单位的角度转换为近似值。以度为单位的等效角度。 double
toIntExact(x) 将长整型值转换为整型。 int
toRadians(x) 将以度为单位的角度转换为近似值。以弧度为单位的角度。 double
ulp(x) 返回 x 的最小精度单位 (ulp) 的大小。 double

10. if…else… 判断

eg:

int myNum = 10; // 这是一个正数还是负数?
 
if (myNum > 0) {
  System.out.println("该值为正数。");
} else if (myNum < 0) {
  System.out.println("该值为负数。");
} else {
  System.out.println("该值为 0。");
}

note: 可以用三元运算符代替if…else…

int time = 20;
if (time < 18) {
  System.out.println("早安");
} else {
  System.out.println("晚安");
}
// 可以写为
int time = 20;
String result = (time < 18) ? "早安" : "晚安";
System.out.println(result);

11. Switch 判断

eg:

int day = 4;
switch (day) {
  case 6:
    System.out.println("Today is Saturday");
    break;
  case 7:
    System.out.println("Today is Sunday");
    break;
  default:
    System.out.println("Today is WorkDay");
}
// 输出 "Today is WorkDay"

12. While循环

12.1 while

eg:

int i = 0;
while (i < 5) {
  System.out.println(i);
  i++;
}

12.2 do…while…

eg:

int i = 0;
do {
  System.out.println(i);
  i++;
}
while (i < 5);

13. For循环

eg:

for (int i = 0; i <= 10; i = i + 2) {
  System.out.println(i);
}

还有一个 “for-each” 循环,专门用于遍历_数组_中的元素:

String[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
for (String i : cars) {
  System.out.println(i);
}

Note: break 语句可用于跳出_循环_。continue 语句会中断一次迭代(在循环中),并继续循环中的下一次迭代。

14. 数组

14.1 声明数组

声明数组,请使用_方括号_定义变量类型:

String[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
int[] myNum = {10, 20, 30, 40};

14.2 数组的访问与修改

  • 访问元素:通过索引(从 0 开始)访问
int first = nums[0]; // 获取第一个元素
String second = names[1]; // 获取第二个元
  • 修改元素:通过索引重新赋值
nums[2] = 100; // 将第三个元素改为100
names[0] = "Anna"; // 修改第一个元素
// 注意:访问超出数组长度的索引会抛出ArrayIndexOutOfBoundsException

14.3 数组排序

  • 使用java.util.Arrays工具类的sort()方法:
import java.util.Arrays;

int[] arr = {3, 1, 4, 2};
Arrays.sort(arr); // 升序排序,结果:[1, 2, 3, 4]

// 对字符串数组排序(按字典顺序)
String[] strs = {"banana", "apple", "cherry"};
Arrays.sort(strs); // 结果:["apple", "banana", "cherry"]
  • 自定义排序(对象数组):
// 定义学生类
class Student {
    String name;
    int age;
    // 添加构造函数
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 可选:添加toString方法便于输出
    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + "}";
    }
}

// 对学生数组按年龄排序(在main方法中使用)
Student[] students = {new Student("Bob", 20), new Student("Alice", 18)};
Arrays.sort(students, (s1, s2) -> s1.age - s2.age);

14.4 数组的查找

  • 线性查找:遍历数组逐个比较
public static int findIndex(int[] arr, int target) {
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] == target) {
            return i; // 找到返回索引
        }
    }
    return -1; // 未找到返回-1
}
  • 二分查找:需先排序,效率更高(使用Arrays.binarySearch()
int[] sortedArr = {1, 2, 3, 4, 5};
int index = Arrays.binarySearch(sortedArr, 3); // 返回2(找到)
int notFound = Arrays.binarySearch(sortedArr, 6); // 返回负数(未找到

14.5 数组的复制

  • Arrays.copyOf():复制指定长度的数组
int[] original = {1, 2, 3};
int[] copy = Arrays.copyOf(original, 5); // 复制并扩容到5个元素,结果:[1,2,3,0,0]
  • System.arraycopy():指定源数组、起始位置、目标数组、目标起始位置和复制长度
int[] src = {10, 20, 30};
int[] dest = new int[5];
System.arraycopy(src, 1, dest, 2, 2); // 从src[1]复制2个元素到dest[2],结果:[0,0,20,30,0]

14.6 数组的转换

  • 数组转字符串:使用Arrays.toString()
int[] arr = {1, 2, 3};
String str = Arrays.toString(arr); // 结果:"[1, 2, 3]"
  • 数组转集合:使用Arrays.asList()(注意:返回的是固定大小的集合)
String[] strs = {"a", "b", "c"};
List<String> list = Arrays.asList(strs);

14.7 多维数组

// 初始化二维数组
int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 遍历二维数组
for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        System.out.print(matrix[i][j] + " ");
    }
    System.out.println();
}

四、Java方法

1. Java 方法

_方法_是仅在被调用时运行的代码块。

将数据(称为参数)传递到方法中。

方法用于执行特定的操作,也被称为_函数_。

为什么要使用方法?重用代码:定义一次代码,多次使用。

📢 这玩意其实就是函数,Java中称之为方法,非要叫的这么高大上。

方法必须在类中声明。它用方法名定义,后跟括号 ()。 Java 提供了一些预定义的方法,例如 System.out.println(),但也可以创建自己的方法来执行某些操作:

如需在 Java 中调用方法,请写方法名称,后跟两个括号 () 和一个分号;

在下例中,myMethod() 用于在调用时打印文本(操作):

public class Main {
  static void myMethod() {
    System.out.println("Hello world!");
  }

  public static void main(String[] args) {
    myMethod();
  }
}

// 输出 "Hello world!"

2. 方法重载(函数重载)

可以使得多个方法可以拥有相同的名称和不同的参数:

**注意:**只要参数的数量和/或类型不同,多个方法就可以拥有相同的名称。

static int plusMethod(int x, int y) {
  return x + y;
}

static double plusMethod(double x, double y) {
  return x + y;
}

public static void main(String[] args) {
  int myNum1 = plusMethod(8, 5);
  double myNum2 = plusMethod(4.3, 6.26);
  System.out.println("int: " + myNum1);
  System.out.println("double: " + myNum2);
}

3. 作用域

在 Java 中,只能在变量被创建的区域内对其访问。这称为_作用域_。

3.1 方法作用域

直接在方法内部声明的变量,可在方法中声明它们的代码行之后的任何位置使用:

public class Main {
  public static void main(String[] args) {

    // 此处的代码不能使用 x

    int x = 100;

    // 此处的代码能够使用 x
    System.out.println(x);
  }
}

3.2 块作用域

代码块是指大括号 {} 之间的所有代码。在代码块内声明的变量只能由大括号之间的代码访问,且这些代码位于声明变量的行之后:

public class Main {
  public static void main(String[] args) {

    // 此处的代码不能使用 x

    { // 这是代码块

      // 此处的代码不能使用 x

      int x = 100;

      // 此处的代码能够使用 x
      System.out.println(x);

   } // 代码块在此处结束

  // 此处的代码不能使用 x

  }
}

4. Java 递归

递归(Recursion)是使函数调用其本身。

**eg:**使用递归将 5 到 10 之间的所有数字相加

public class Main {
  public static void main(String[] args) {
    int result = sum(5, 10);
    System.out.println(result);
  }
  public static int sum(int start, int end) {
    if (end > start) {
      return end + sum(start, end - 1);
    } else {
      return end;
    }
  }
}

Note:递归必须有停止条件,避免递归陷入死循环。

五、Java 类

1. Java 类和对象

  • 类是对现实世界中事物的抽象(比如 “人” 可以抽象为Person类,包含 “姓名、年龄” 等属性和 “吃饭、睡觉” 等方法)。
  • 所有的 Java 代码(变量、方法、逻辑)都必须定义在类中,没有 “类外的代码”。
  • 程序的运行也是以类为单位:通过类创建对象(new 类名()),通过对象调用方法来完成功能。
  • 类名使用大驼峰命名法(每个单词首字母大写)

1.1 类的定义语法与示例

[修饰符] class 类名 [extends 父类名] [implements 接口名列表] {
    // 成员变量(属性)
    // 成员方法(行为)
    // 构造方法
    // 内部类
}

示例:

// 定义一个Person类
public class Person {
    // 成员变量(属性)
    private String name;
    private int age;
    
    // 构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 成员方法(行为)
    public void sayHello() {
        System.out.println("Hello, my name is " + name);
    }
    
    // getter和setter方法
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

1.2 类定义中的关键字

**访问控制修饰符**

用于控制类、成员变量、方法的访问权限。

修饰符 类内部 同一包 子类 其他包 可修饰对象 说明
public 类、成员变量、方法 所有类均可访问该代码。
protected × 成员变量、方法(不能修饰外部类) 代码可以在同一个包和子类中访问。
默认(无修饰符) × × 类、成员变量、方法 该代码只能在同一个包中访问。在您未指定修饰符时使用。
private × × × 成员变量、方法(不能修饰外部类) 代码只能在声明的类中访问。
// public类:可以被所有类访问
public class PublicClass {
    public String publicField;         // 所有类可访问
    protected String protectedField;   // 本类、同包类、子类可访问
    String defaultField;               // 本类、同包类可访问
    private String privateField;       // 仅本类可访问
    
    public void publicMethod() {}      // 所有类可访问
    protected void protectedMethod() {}// 本类、同包类、子类可访问
    void defaultMethod() {}            // 本类、同包类可访问
    private void privateMethod() {}    // 仅本类可访问
}
**static 关键字**

用于修饰类的成员(变量、方法、内部类),表示属于类本身而非实例。

  • 静态变量(类变量)
public class Counter {
    // 静态变量:所有对象共享同一个值
    private static int count = 0;
    // 实例变量:每个对象有独立的值
    private int id;
    
    public Counter() {
        count++;
        id = count;
    }
    
    public static int getCount() {
        return count;
    }
    
    public int getId() {
        return id;
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        System.out.println(Counter.getCount());  // 输出:2(通过类名访问)
        System.out.println(c1.getId());          // 输出:1
        System.out.println(c2.getId());          // 输出:2
    }
}
  • 静态方法(类方法)
    • 可直接通过类名调用,无需创建对象
    • 静态方法中不能使用thissuper关键字
    • 静态方法只能访问静态成员,不能访问非静态成员
public class MathUtil {
    // 静态方法:工具类常用
    public static int add(int a, int b) {
        return a + b;
    }
    
    public static int multiply(int a, int b) {
        return a * b;
    }
}

// 调用静态方法
int sum = MathUtil.add(3, 5);  // 直接通过类名调用
  • 静态内部类
public class OuterClass {
    private int outerField = 10;
    private static int staticOuterField = 20;
    
    // 静态内部类
    public static class StaticInnerClass {
        public void print() {
            // 可以访问外部类的静态成员
            System.out.println("静态外部变量: " + staticOuterField);
            
            // 不能直接访问外部类的非静态成员
            // System.out.println(outerField);  // 编译错误
        }
    }
    
    // 非静态内部类
    public class NonStaticInnerClass {
        public void print() {
            // 可以访问外部类的所有成员
            System.out.println("非静态外部变量: " + outerField);
            System.out.println("静态外部变量: " + staticOuterField);
        }
    }
}
**final 关键字**

表示 “不可改变”,可修饰类、方法、变量。

  • 修饰类

final修饰的类不能被继承。

// final类不能有子类
public final class FinalClass {
    // ...
}

// 编译错误:不能继承final类
// public class SubClass extends FinalClass {}
  • 修饰方法

final修饰的方法不能被重写。

public class Parent {
    // final方法不能被重写
    public final void finalMethod() {
        System.out.println("这是final方法");
    }
}

public class Child extends Parent {
    // 编译错误:不能重写final方法
    // public void finalMethod() {}
}
  • 修饰变量

final修饰的变量成为常量,一旦赋值就不能改变。

public class FinalVariableExample {
    // 声明时赋值
    public final int CONSTANT1 = 100;
    
    // 空白final变量,必须在构造方法中赋值
    public final int CONSTANT2;
    
    public FinalVariableExample() {
        CONSTANT2 = 200;  // 构造方法中赋值
    }
    
    public void method() {
        final int localConstant = 300;
        // localConstant = 400;  // 编译错误:不能修改final变量
    }
}
**abstract 关键字**

用于声明抽象类和抽象方法。

  • 抽象类
    • 不能被实例化
    • 可以包含抽象方法和非抽象方法
    • 抽象类的子类必须实现所有抽象方法(除非子类也是抽象类)
// 抽象类
public abstract class Animal {
    // 非抽象方法
    public void breathe() {
        System.out.println("呼吸空气");
    }
    
    // 抽象方法:只有声明,没有实现
    public abstract void makeSound();
}

// 继承抽象类并实现抽象方法
public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("汪汪汪");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("喵喵喵");
    }
}
**this 关键字**

表示当前对象的引用,用于:

  1. 区分成员变量和局部变量
public class Person {
    private String name;
    
    public void setName(String name) {
        this.name = name;  // this.name表示成员变量
    }
}
  1. 调用当前类的其他构造方法
public class Person {
    private String name;
    private int age;
    
    public Person() {
        this("未知", 0);  // 调用带参数的构造方法
    }
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
  1. 返回当前对象
public class Person {
    private int age;
    
    public Person setAge(int age) {
        this.age = age;
        return this;  // 返回当前对象,支持方法链调用
    }
}

// 方法链调用
Person p = new Person();
p.setAge(20).setName("张三");  // 假设还有setName方法
**super 关键字**

用于访问父类的成员,主要用途:

  • 调用父类的构造方法
public class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
}

public class Dog extends Animal {
    private String breed;
    
    public Dog(String name, String breed) {
        super(name);  // 调用父类的构造方法
        this.breed = breed;
    }
}
  • 调用父类的方法
public class Parent {
    public void print() {
        System.out.println("父类的print方法");
    }
}

public class Child extends Parent {
    @Override
    public void print() {
        super.print();  // 调用父类的print方法
        System.out.println("子类的print方法");
    }
}
**其他相关关键字** - **extends 关键字**

用于表示类的继承关系,Java 中类只能单继承。

public class Animal {
    // ...
}

// Dog类继承Animal类
public class Dog extends Animal {
    // 继承了Animal类的属性和方法
    // 可以添加自己特有的属性和方法
}
  • implements 关键字

用于表示类实现接口,一个类可以实现多个接口。

public interface Runnable {
    void run();
}

public interface Swimmable {
    void swim();
}

// 实现多个接口
public class Duck implements Runnable, Swimmable {
    @Override
    public void run() {
        System.out.println("鸭子在跑");
    }
    
    @Override
    public void swim() {
        System.out.println("鸭子在游泳");
    }
}
  • transient 关键字

用于修饰成员变量,表示该变量不会被序列化。

import java.io.Serializable;

public class User implements Serializable {
    private String username;
    private transient String password;  // 不会被序列化
    
    // getter和setter方法
}
  • volatile 关键字

用于修饰成员变量,确保多线程环境下变量的可见性。

public class VolatileExample {
    private volatile boolean flag = false;
    
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    
    public void checkFlag() {
        while (!flag) {
            // 循环等待,直到flag变为true
        }
        System.out.println("Flag已变为true");
    }
}
  • synchronized 关键字

方法一次只能被一个线程访问。

1.3 创建对象

在 Java 中,对象是从类创建的。我们已经创建了名为 Main 的类,所以现在我们可以使用它来创建对象。

如需创建 Main 对象,请指定类名,后跟对象名,并使用关键字 new

public class Main {
  int x = 5;

  public static void main(String[] args) {
    Main myObj1 = new Main();  // 对象 1
    Main myObj2 = new Main();  // 对象 2
    System.out.println(myObj1.x);
    System.out.println(myObj2.x);
  }
}

1.4 使用多个类

还可以创建一个类的对象并在另一个类中访问它。这通常用于更好地组织类(一个类拥有所有属性和方法,而另一个类拥有 main() 方法(要执行的代码))。

请记住,java 的文件名应与类名相匹配。在这个例子中,我们在同一个目录/文件夹中创建了两个文件:

  • Main.java
  • Second.java

Main.java

public class Main {
  int x = 5;
}

Second.java

class Second {
  public static void main(String[] args) {
    Main myObj = new Main();
    System.out.println(myObj.x);
  }
}

当两个文件都编译完成后:

C:\Users\Your Name>javac Main.java
C:\Users\Your Name>javac Second.java

运行 Second.java 文件:

C:\Users\Your Name>java Second

将会输出5.

2. Java 类方法

方法是在类中声明的,并且它们用于执行某些操作:

示例:

在 Main 中创建名为 myMethod() 的方法:

public class Main {
  static void myMethod() {
    System.out.println("Hello World!");
  }

  public static void main(String[] args) {
    myMethod();
  }
}
// 输出 "Hello World!"

静态与非静态

经常会看到具有 static 或 public 属性和方法的 Java 程序。
static 方法和 public _方法_之间差异的例子:

public class Main {
  // 静态方法:
  static void myStaticMethod() {
    System.out.println("无需创建对象即可调用静态方法");
  }

  // 公共方法:
  public void myPublicMethod() {
    System.out.println("公共方法必须通过创建对象来调用");
  }

  // 主方法:
  public static void main(String[] args) {
    myStaticMethod(); // 调用静态方法
    // myPublicMethod(); 这会编译错误

    Main myObj = new Main(); // 创建 Main 的对象
    myObj.myPublicMethod(); // 调用对象的公共方法
  }
}

3. Java 构造函数

Java 中的构造函数是一种用于初始化对象的_特殊方法_。创建类的对象时,会调用构造函数。它可用于设置对象属性的初始值:

请注意,构造函数名称必须_与类名称匹配,并且不能有返回类型_(比如 void)。

public class Main {
  int modelYear;
  String modelName;

  public Main(int year, String name) {
    modelYear = year;
    modelName = name;
  }

  public static void main(String[] args) {
    Main myCar = new Main(1969, "Mustang");
    System.out.println(myCar.modelYear + " " + myCar.modelName);
  }
}

// 输出 1969 Mustang

4. Java 封装

_封装(Encapsulation)_的含义,是确保“敏感”数据对用户隐藏。要实现这一目标,您必须:

  • 将类变量/属性声明为 private
  • 提供公共 get 和 set 方法来访问和更新 private 变量的值
public class Person {
  private String name; // private = 禁止进入

  // Getter
  public String getName() {
    return name;
  }

  // Setter
  public void setName(String newName) {
    this.name = newName;
  }
}

public class Main {
  public static void main(String[] args) {
    Person myObj = new Person();
    myObj.name = "Bill";  // 错误
    System.out.println(myObj.name); // 错误 
  }
}

get 方法返回变量名的值。

set 方法接受一个参数 (newName) 并将其分配给 name 变量。this 关键字用于引用当前对象。

但是,由于 name 变量被声明为 private,我们_无法_从此类外部访问它:

正确访问应该如下:

public class Main {
  public static void main(String[] args) {
    Person myObj = new Person();
    myObj.setName("Bill"); // 将 name 变量的值设置为 "Bill"
    System.out.println(myObj.getName());
  }
}

// 输出 "Bill"

为什么要封装?

  • 更好地控制类属性和方法
  • 类属性可以设为_只读(如果只使用 get 方法)或只写_(如果只使用 set 方法)
  • 灵活:程序员可以改变一部分代码而不影响其他部分
  • 提高数据安全性

5. Java 包和 API

Java 中的包用于对相关类进行分组。可将其视为_文件目录中的文件夹_。我们使用包来避免名称冲突,并编写更好的可维护代码。

包分为两类:

  • 内置包(来自 Java API 的包)
  • 用户定义的包(创建您自己的包)

5.1 内置软件包

Java API 是一个预先编写的类库,可以免费使用,包含在 Java 开发环境中。

该库包含用于管理输入、数据库编程等的组件。完整列表可以在 Oracles 网站上找到:

https://docs.oracle.com/javase/8/docs/api/

库分为__。这意味着您可以导入单个类(及其方法和属性),也可以导入包含属于指定包的所有类的整个包。

如需使用库中的类或包,您需要使用 import 关键字:

语法:

import package.name.Class;   // 导入单个类
import package.name.*;   // 导入整个包

5.2 导入类

如果导入需要使用的类,例如_用于获取用户输入_的 Scanner 类,请编写以下代码:
实例

import java.util.Scanner;

class MyClass {
  public static void main(String[] args) {
    Scanner myObj = new Scanner(System.in);
    System.out.println("Enter username");

    String userName = myObj.nextLine();
    System.out.println("Username is: " + userName);
  }
}

在上面的例子中,java.util 是一个包,而 Scanner 是 java.util 包的一个类。使用时需要创建该类的对象并使用 Scanner 类文档中的任何可用方法。上面的例子中,使用 nextLine() 方法,该方法用于读取完整行。

5.3 导入包

有很多包可供选择。在前面的实例中,我们使用了 java.util 包中的 Scanner 类。这个包还包含日期和时间工具、随机数生成器和其他实用程序类。

如需导入整个包,请以星号 (*) 结束句子。下例将导入 java.util 包中的所有类:

import java.util.*;

5.4 用户定义的包

如需创建自己的包,您需要了解:Java 使用文件系统目录来存储它们。就像计算机上的文件夹一样:

└── root
  └── mypack
    └── MyPackageClass.java

如需创建包,请使用 package 关键字:
MyPackageClass.java

package mypack;
class MyPackageClass {
  public static void main(String[] args) {
    System.out.println("这是我的包!");
  }
}

将文件保存为 MyPackageClass.java,并编译它:

C:\Users\Your Name>javac MyPackageClass.java

然后编译包:

C:\Users\Your Name>javac -d . MyPackageClass.java

这会强制编译器创建 “mypack” 包。

  • d 关键字指定保存类文件的目的地。您可以使用任何目录名称,例如 c:/user (windows),或者,如果您想将包保存在同一目录中,您可以使用点号 “.”,如上例所示。

**注意:**包名要小写,以免与类名冲突。

当我们编译上例中的包时,会创建名为 “mypack” 的新文件夹。

如需运行 MyPackageClass.java 文件,请键入以下内容:

C:\Users\Your Name>java mypack.MyPackageClass

// 输出:这是我的包!

6. Java 继承(子类和超类)

在 Java 中,可以从一个类继承属性和方法到另一个类。将“继承”这个概念分为两类:

  • subclass (子类) - 从另一个类继承的类
  • superclass(父类) - 被继承的类

如需从类继承,请使用 extends 关键字。

在下例中,Car 类(子类)继承了 Vehicle 类(超类)的属性和方法:

class Vehicle {
  protected String brand = "Ford";        // Vehicle 属性
  public void honk() {                    // Vehicle 方法
    System.out.println("Tuut, tuut!");
  }
}

class Car extends Vehicle {
  private String modelName = "Mustang";    // Car 属性
  public static void main(String[] args) {

    // 创建 myCar 对象
    Car myCar = new Car();

    // 调用 myCar 对象上的 honk() 方法(来自 Vehicle 类)
    myCar.honk();

    // 显示 brand 属性的值(来自 Vehicle 类)和来自 Car 类的 modelName 的值
    System.out.println(myCar.brand + " " + myCar.modelName);
  }
}

是否注意到 Vehicle 中的 protected 修饰符?

将 Vehicle 中的 brand 属性设置为 protected 访问修饰符。如果将其设置为 private,则 Car 类将无法访问它。

为什么以及何时使用“继承”?

  • 它对于代码可重用性很有用:请在创建新类时重用现有类的属性和方法。

final 关键词

如果不希望其他类从某个类继承,使用 final 关键字:

如果尝试访问 final 类,Java 将生成错误:

final class Vehicle {
  ...
}

class Car extends Vehicle {
  ...
}

输出将是这样的:

Main.java:9: error: cannot inherit from final Vehicle
class Main extends Vehicle {
                  ^
1 error)

7. Java 多态

多态(Polymorphism)的意思是“多种形式”,它发生在我们有许多通过继承相互关联的类时。_继承让我们从另一个类继承属性和方法。多态_使用这些方法来执行不同的任务。这允许我们以不同的方式执行单一操作。
例如:名为 Animal 的超类,它有一个名为 animalSound() 的方法。Animals 的子类可以是 Pigs、Cats、Dogs、Birds - 而且它们也有自己的动物声音实现(猪的 oinks 和猫的 meows 等):

class Animal {
  public void animalSound() {
    System.out.println("The animal makes a sound");
  }
}

class Pig extends Animal {
  public void animalSound() {
    System.out.println("The pig says: wee wee");
  }
}

class Dog extends Animal {
  public void animalSound() {
    System.out.println("The dog says: bow wow");
  }
}


class Main {
  public static void main(String[] args) {
    Animal myAnimal = new Animal();  // 创建 Animal 对象
    Animal myPig = new Pig();  // 创建 Pig 对象
    Animal myDog = new Dog();  // 创建 Dog 对象
    myAnimal.animalSound();
    myPig.animalSound();
    myDog.animalSound();
  }
}

8. Java 内部类

8.1 Java 内部类

在 Java 中,也可以嵌套类(类中的类)。嵌套类的目的是将属于一起的类进行分组,这会使您的代码更具可读性和可维护性。

如需访问内部类,先创建外部类的对象,然后再创建内部类的对象:

class OuterClass {
  int x = 10;

  class InnerClass {
    int y = 5;
  }
}

public class Main {
  public static void main(String[] args) {
    OuterClass myOuter = new OuterClass();
    OuterClass.InnerClass myInner = myOuter.new InnerClass();
    System.out.println(myInner.y + myOuter.x);
  }
}

// 输出 15 (5 + 10)

8.2 私有内部类

与“常规”类不同,内部类可以是 private 或 protected。如果您不希望外部对象访问内部类,请将该类声明为 private

class OuterClass {
  int x = 10;

  private class InnerClass {
    int y = 5;
  }
}

public class Main {
  public static void main(String[] args) {
    OuterClass myOuter = new OuterClass();
    OuterClass.InnerClass myInner = myOuter.new InnerClass();
    System.out.println(myInner.y + myOuter.x);
  }
}

从外部访问私有内部类会发生错误:

Main.java:13: error: OuterClass.InnerClass has private access in OuterClass
    OuterClass.InnerClass myInner = myOuter.new InnerClass();

8.3 静态内部类

内部类也可以是 static,这意味着您可以在不创建外部类的对象的情况下访问它:

class OuterClass {
  int x = 10;

  static class InnerClass {
    int y = 5;
  }
}

public class Main {
  public static void main(String[] args) {
    OuterClass.InnerClass myInner = new OuterClass.InnerClass();
    System.out.println(myInner.y);
  }
}

// 输出 5

8.4 从内部类访问外部类

内部类的优点之一是它们可以访问外部类的属性和方法:

class OuterClass {
  int x = 10;

  class InnerClass {
    public int myInnerMethod() {
      return x;
    }
  }
}

public class Main {
  public static void main(String[] args) {
    OuterClass myOuter = new OuterClass();
    OuterClass.InnerClass myInner = myOuter.new InnerClass();
    System.out.println(myInner.myInnerMethod()); // 直接调用内部类的方法返回了外部类的属性
  }
}

// 输出 10

9. Java 抽象

数据_抽象_是隐藏某些细节并仅向用户显示基本信息的过程。

抽象可以通过_抽象类_或接口来实现

abstract 关键字是一个非访问修饰符,用于类和方法:

  • 抽象类:是一种受限制的类,不能用于创建对象(要访问它,它必须从另一个类继承)。
  • 抽象方法:只能在抽象类中使用,并且没有主体。主体由子类提供(继承自)。

抽象类可以同时拥有抽象方法和常规方法:

abstract class Animal {
  public abstract void animalSound();
  public void sleep() {
    System.out.println("Zzz");
  }
}

在上例中,无法创建 Animal 类的对象:

Animal myObj = new Animal(); // 会产生错误

如需访问抽象类,它必须从另一个类继承。

// 抽象类:
abstract class Animal {
  // 抽象方法(没有主体):
  public abstract void animalSound();
  // 常规方法:
  public void sleep() {
    System.out.println("Zzz");
  }
}

// 子类(继承自 Animal):
class Pig extends Animal {
  public void animalSound() {
    // 此处提供了 animalSound() 的主体
    System.out.println("The pig says: wee wee");
  }
}

class Main {
  public static void main(String[] args) {
    Pig myPig = new Pig(); // 创建 Pig 对象
    myPig.animalSound(); // 打印: The pig says: wee wee
    myPig.sleep(); // 打印: Zzz
  }
}

10. Java 接口

在 Java 中实现抽象的另一种方法是使用接口。

interface 是一种完全_“抽象类”_,用于组合相关方法与空主体:

// 接口:
interface Animal {
  public void animalSound(); // 接口方法(没有主体)
  public void run(); // 接口方法(没有主体)
}

如需访问接口方法,接口必须由另一个类使用 implements 关键字(而不是 extends)来“实现”(有点像继承)。

// 接口:
interface Animal {
  public void animalSound(); // 接口方法(没有主体)
  public void sleep(); // 接口方法(没有主体)
}

// Pig “实现”了 Animal 接口:
class Pig implements Animal {
  public void animalSound() {
    // 此处提供了 animalSound() 的主体
    System.out.println("The pig says: wee wee");
  }
  public void sleep() {
    // 此处提供了 sleep() 的主体
    System.out.println("Zzz");
  }
}

class Main {
  public static void main(String[] args) {
    Pig myPig = new Pig();  // 创建 Pig 对象
    myPig.animalSound();
    myPig.sleep();
  }
}

接口注意事项:

  • 与_抽象类一样,接口不能_用于创建对象(在上面的例子中,不可能在 MyMainClass 中创建 “Animal” 对象)
  • 接口方法没有主体 - 主体由 “implement” 类提供
  • 在实现接口时,您必须覆盖其所有方法
  • 接口方法默认是 abstract 和 public
  • 接口属性默认为 publicstatic 和 final
  • 接口不能包含构造函数(因为它不能用于创建对象)

为什么以及何时使用接口?

1)为了实现安全 - 隐藏某些细节,只显示一个对象(接口)的重要细节。

2)Java 不支持“多重继承”(一个类只能从一个超类继承)。但是,它可以通过接口来实现,因为类可以_实现_ 多个接口。

**注释:**如需实现多个接口,用逗号分隔它们(请见下面的例子)。

多个接口

要实现多个接口,请用逗号分隔它们:

interface FirstInterface {
  public void myMethod(); // 接口方法
}

interface SecondInterface {
  public void myOtherMethod(); // 接口方法
}

class DemoClass implements FirstInterface, SecondInterface {
  public void myMethod() {
    System.out.println("Some text..");
  }
  public void myOtherMethod() {
    System.out.println("Some other text...");
  }
}

class Main {
  public static void main(String[] args) {
    DemoClass myObj = new DemoClass();
    myObj.myMethod();
    myObj.myOtherMethod();
  }
}

11. Java 枚举

11.1 枚举

枚举(enum)是一种特殊的“类”,代表一组_常量_(不可更改的变量,如 final 变量)。

如需创建 enum,请使用 enum 关键字(而不是类或接口),并用逗号分隔常量。请注意,它们应为大写字母:

enum Level {
  LOW,
  MEDIUM,
  HIGH
}

使用_点语法_访问 enum 常量:

Level myVar = Level.MEDIUM;

11.2 类中的枚举

public class Main {
  enum Level {
    LOW,
    MEDIUM,
    HIGH
  }

  public static void main(String[] args) {
    Level myVar = Level.MEDIUM; 
    System.out.println(myVar);
  }
}

// 输出:MEDIUM

11.3 Switch 语句中的枚举

枚举通常用于 switch 语句中,以检查相应的值:

enum Level {
  LOW,
  MEDIUM,
  HIGH
}

public class Main {
  public static void main(String[] args) {
    Level myVar = Level.MEDIUM;

    switch(myVar) {
      case LOW:
        System.out.println("低级");
        break;
      case MEDIUM:
        System.out.println("中级");
        break;
      case HIGH:
        System.out.println("高级");
        break;
    }
  }
}
// 输出:中级

11.4 遍历枚举

枚举类型有一个 values() 方法,它返回包含所有枚举常量的数组。当您想遍历枚举的常量时,此方法很有用:

for (Level myVar : Level.values()) {
  System.out.println(myVar);
}
// 输出:LOW
//       MEDIUM
//       HIGH

枚举和类的区别

enum 可以像 class 一样拥有属性和方法。唯一的区别是枚举常量是 publicstatic 和 final(不可更改 - 不能被覆盖)。

enum 不能用于创建对象,也不能扩展其他类(但它可以实现接口)。

为什么以及何时使用枚举?

当您知道某些值不会改变时请使用枚举,例如月、日、颜色、一副牌等。

12. Java 用户输入 (Scanner)

Scanner 类用于获取用户输入,它位于 java.util 包中。如需使用 Scanner 类,请创建该类的对象并使用 Scanner 类文档中的任何可用方法。输入类型表如下:

方法 描述
nextBoolean() 读取来自用户的 boolean 值。
nextByte() 读取来自用户的 byte 值。
nextDouble() 读取来自用户的 double 值。
nextFloat() 读取来自用户的 float 值。
nextInt() 读取来自用户的 int 值。
nextLine() 读取来自用户的 String 值。
nextLong() 读取来自用户的 long 值。
nextShort() 读取来自用户的 short 值。
import java.util.Scanner;

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

    System.out.println("请输入名字、年龄、薪资:");

    // 字符串输入
    String name = myObj.nextLine();

    // 数值输入
    int age = myObj.nextInt();
    double salary = myObj.nextDouble();

    // 输出用户的输入
    System.out.println("名字:" + name);
    System.out.println("年龄:" + age);
    System.out.println("薪资:" + salary);
  }
}

13. Java 日期和时间

Java 没有内置的 Date 类,但我们可以导入 java.time 包来使用日期和时间 API。该软件包囊括了许多日期和时间类。例如:

描述
LocalDate 表示日期(年、月、日(yyyy-MM-dd))。
LocalTime 表示时间(小时、分钟、秒和纳秒 (HH-mm-ss-ns))。
LocalDateTime 表示日期和时间 (yyyy-MM-dd-HH-mm-ss-ns)。
DateTimeFormatter 用于显示和解析日期时间对象的格式化程序。

13.1 显示当前日期

如需显示当前日期,请导入 java.time.LocalDate 类,并使用其 now() 方法:

import java.time.LocalDate; // 导入 LocalDate 类

public class Main {
  public static void main(String[] args) {
    LocalDate myObj = LocalDate.now(); // 创建日期对象
    System.out.println(myObj); // 显示当前日期
  }
}
// 输出: 2025-07-27

13.2 显示当前时间

如需显示当前时间(小时、分钟、秒和纳秒),请导入 java.time.LocalTime 类,并使用其 now() 方法:

import java.time.LocalTime; // 导入 LocalTime 类

public class Main {
  public static void main(String[] args) {
    LocalTime myObj = LocalTime.now();
    System.out.println(myObj);
  }
}
// 输出: 18:51:44.890974600

13.3 显示当前日期和时间

如需显示当前日期和时间,请导入 java.time.LocalDateTime 类,并使用其 now() 方法:

import java.time.LocalDateTime; // 导入 LocalDateTime 类

public class Main {
  public static void main(String[] args) {
    LocalDateTime myObj = LocalDateTime.now();
    System.out.println(myObj);
  }
}
// 输出: 2025-07-27T18:52:39.207533500

13.4 格式化日期和时间

上例中的 “T” 用于将日期与时间分开。您可以使用 DateTimeFormatter 类和同一包中的 ofPattern() 方法来格式化或解析日期时间对象。下例将从日期时间中删除 “T” 和纳秒:

import java.time.LocalDateTime; // 导入 LocalDateTime 类
import java.time.format.DateTimeFormatter; // 导入 DateTimeFormatter 类

public class Main {
  public static void main(String[] args) {
    LocalDateTime myDateObj = LocalDateTime.now();
    System.out.println("格式化之前:" + myDateObj);
    DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");

    String formattedDate = myDateObj.format(myFormatObj);
    System.out.println("格式化之后:" + formattedDate);
  }
}
// 格式化之前:2025-07-27T18:53:41.060494900
// 格式化之后:27-07-2025 18:53:41

ofPattern() 方法接受各种值,如果您想以不同的格式显示日期和时间。例如:

例子
yyyy-MM-dd “1988-09-29”
dd/MM/yyyy “29/09/1988”
dd-MMM-yyyy “29-Sep-1988”
E, MMM dd yyyy “Thu, Sep 29 1988”

六、Java 数据结构

1. 包装类

1.1 先明确两个关键概念

  • **基本数据类型(Primitive Types)**Java 有 8 种基本类型:intbyteshortlongfloatdoublecharboolean。特点:直接存储值(而非地址),不继承Object类,不能调用方法,存储在栈内存中。
  • **引用数据类型(Reference Types)**包括:类(如StringInteger)、接口、数组、枚举等。特点:存储的是对象的地址(而非值本身),继承Object类,可调用方法,对象实体存储在堆内存中。

Java中的集合框架(如ListSetMap等)对存储类型的支持规则不同,但共同遵循一个原则:引用类型可以直接存储,基本类型需特殊处理

1.2 为什么**String**可以直接使用?

String引用类型(本质是String类的对象),无论在数组还是列表中,都符合容器对 “存储引用类型” 的要求。

1.3 为什么其他基本类型(如**int)需要用Integer****?**

int为例,它是基本类型,而 Java 的集合框架(如****ListSet****)只能存储引用类型,不能直接存储基本类型。因此需要用对应的 “包装类”(Integer)来包装基本类型,使其成为引用类型。

1.4 包装类的作用:连接基本类型和引用类型

在使用非 String 类型时,必须指定等效的包装类Integer

原始数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character

2. ArrayList(数组)

ArrayList 类是一种可调整大小的数组,可以在 java.util 包中找到。

Java 中的内置数组和 ArrayList 的区别在于,数组的大小不能修改(如果要向数组添加元素或从数组中删除元素,则必须创建一个新数组)。虽然可以随时在 ArrayList 中添加和删除元素。语法也略有不同。

以下表格列出了 ArrayList 的所有方法。

部分方法使用 ArrayList 中元素的类型作为参数或返回值。在表格中,这种类型将被表示为 T。

方法 描述 返回类型
add() 向列表中添加一个元素。 boolean
addAll() 向列表中添加一个集合的所有元素。 boolean
clear() 从列表中移除所有元素。 void
clone() 创建 ArrayList 的一个副本。 Object
contains() 检查列表中是否包含某个元素。 boolean
ensureCapacity() 增加列表的容量,以便能够容纳指定数量的元素。 void
forEach() 对列表中的每个元素执行一个操作。 void
get() 返回列表中指定位置的元素。 T
indexOf() 返回列表中首次出现某个元素的索引。 int
isEmpty() 检查列表是否为空。 boolean
iterator() 为 ArrayList 返回 Iterator 对象。 Iterator
lastIndexOf() 返回列表中最后一次出现某个元素的索引。 int
listIterator() 为 ArrayList 返回 ListIterator 对象。 ListIterator
remove() 从列表中移除一个元素。 boolean
removeAll() 从列表中移除一个集合的所有元素。 boolean
removeIf() 移除列表中满足指定条件的所有元素。 boolean
replaceAll() 将列表中的每个元素替换为对该元素执行操作后的结果。 void
retainAll() 从列表中移除所有不属于指定集合的元素。 boolean
set() 替换列表中指定位置的元素。 T
size() 返回列表中的元素数量。 int
sort() 对列表进行排序。 void
spliterator() 为 ArrayList 返回 Spliterator 对象。 Spliterator
subList() 返回列表的子列表,该子列表提供了对原列表中一定范围内元素的访问。 List
toArray() 返回包含列表中所有元素的数组。 Object[]
trimToSize() 根据需要减小列表的容量以匹配元素的数量。 void

示例:

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> cars = new ArrayList<String>();
        cars.add("Volvo"); // 添加元素
        cars.add(0, "Mazda"); // 在列表开头(索引0)插入元素

        String temp_car = cars.get(0); // 访问元素
        cars.set(1, "BWM"); // 修改指定索引的元素
        cars.remove(0); // 删除指定索引的元素
        cars.clear(); // 删除所有元素
        int size = cars.size(); // 获取数组长度

        // for 循环遍历数组
        for (int i = 0; i < cars.size(); i++) {
            System.out.println(cars.get(i));
        }
        // for-each 循环遍历数组
        for (String i : cars) {
            System.out.println(i);
        }

        System.out.println(size);
        System.out.println(temp_car);
        System.out.println(cars);
    }
}

对数组进行排序

java.util 包中另一个有用的类是 Collections 类,它包括 sort() 方法,用于按字母或数字对列表进行排序:

import java.util.ArrayList;
import java.util.Collections;  // 导入 Collections 类
 
public class Main {
  public static void main(String[] args) {
    ArrayList<Integer> myNumbers = new ArrayList<Integer>(); //注意非String对象的数据类型必须为Integer
    myNumbers.add(33);
    myNumbers.add(15);
    myNumbers.add(20);
    myNumbers.add(34);
    myNumbers.add(8);
    myNumbers.add(12);
 
    Collections.sort(myNumbers);  // 对 myNumbers 进行升序排序
    // Collections.sort(myNumbers, Collections.reverseOrder());  // 对 myNumbers 进行逆序排序
 
    for (int number : myNumbers) {
      System.out.println(number);
    }
  }
}

3. LinkedList(链表)

以下表格列出了 LinkedList 的所有方法。

部分方法使用 LinkedList 中元素的类型作为参数或返回值。在表格中,这种类型将被表示为 T。

方法 描述 返回类型
add() 向列表中添加一个元素。 boolean
addAll() 向列表中添加一个集合的所有元素。 boolean
addFirst() 在列表开头添加一个元素。 void
addLast() 在列表末尾添加一个元素。 void
clear() 从列表中移除所有元素。 void
clone() 创建 LinkedList 的副本。
contains() 检查列表中是否包含某个元素。 boolean
descendingIterator() 返回一个迭代器,以逆序遍历列表中的元素。 Iterator
element() 检索列表中的第一个元素。与 getFirst() 类似。 T
forEach() 对列表中的每个元素执行一个操作。 void
get() 返回列表中指定位置的元素。 T
getFirst() 返回列表中的第一个元素。 T
getLast() 返回列表中的最后一个元素。 T
indexOf() 返回列表中首次出现某个元素的索引。 int
isEmpty() 检查列表是否为空。 boolean
iterator() 为 LinkedList 返回一个 Iterator 对象。 Iterator
lastIndexOf() 返回列表中最后一次出现某个元素的索引。 int
listIterator() 为 LinkedList 返回一个 ListIterator 对象。 ListIterator
offer() 在列表末尾添加一个元素。
offerFirst() 在列表开头添加一个元素。 boolean
offerLast() 在列表末尾添加一个元素。 boolean
peek() 检索但不移除列表中的第一个元素。与 getFirst() 类似。 T
peekFirst() 检索但不移除列表中的第一个元素。与 peek() 类似。 T
peekLast() 检索但不移除列表中的最后一个元素。 T
poll() 检索并移除列表中的第一个元素。与 removeFirst() 类似 T
pollFirst() 检索并移除列表中的第一个元素。与 poll() 类似。 T
pollLast() 检索并移除列表中的最后一个元素。 T
pop() 返回并移除列表中的第一个元素。与 removeFirst() 类似 T
push() 在列表开头添加一个元素。与 addFirst() 类似。 void
remove() 从列表中移除一个元素。 boolean
removeAll() 从列表中移除一个集合的所有元素。 boolean
removeFirst() 移除列表中的第一个元素。 T
removeFirstOccurrence() 移除列表中第一次出现的指定元素。
removeIf() 移除列表中满足指定条件的所有元素。 boolean
removeLast() 移除列表中的最后一个元素。 T
removeLastOccurrence() 移除列表中最后一次出现的指定元素。 boolean
replaceAll() 将列表中的每个元素替换为对该元素执行操作后的结果。 void
retainAll() 从列表中移除所有不属于指定集合的元素。 boolean
set() 替换列表中指定位置的元素。 T
size() 返回列表中的元素数量。 int
sort() 对列表进行排序。 void
spliterator() 为 LinkedList 返回 Spliterator 对象。 Spliterator
subList() 返回列表的子列表,该子列表提供了对原列表中一定范围内元素的访问。 List
toArray() 返回包含列表中所有元素的数组。 Object[]

示例:

// 导入 LinkedList 类
import java.util.LinkedList;

public class Main {
  public static void main(String[] args) {
    LinkedList<String> cars = new LinkedList<String>();
    cars.add("Volvo");
    cars.add("BMW");
    cars.add("Ford");
    cars.add("Mazda");
    System.out.println(cars);
  }
}

ArrayList 与 LinkedList

LinkedList 类是一种集合,它可以包含许多相同类型的对象,就像 ArrayList 一样。

LinkedList 类拥有与 ArrayList 类相同的所有方法,因为它们都实现了 List 接口。这意味着您可以以相同的方式添加项目、更改项目、删除项目和清除列表。

然而,虽然 ArrayList 类和 LinkedList 类能够以相同的方式使用,但它们的构建方式却大不相同。

ArrayList 的工作原理

ArrayList 类内部有一个常规数组。添加元素时,将其放入数组中。如果数组不够大,则会创建一个新的更大的数组来替换旧数组,然后删除旧数组。

LinkedList 的工作原理

LinkedList 将其项目存储在“容器”中。该列表有一个指向第一个容器的链接,每个容器都有一个指向列表中下一个容器的链接。要将元素添加到列表中,需要将该元素放入一个新容器中,并将该容器链接到列表中的其他容器之一。

何时使用

请使用 ArrayList 存储和访问数据,使用 LinkedList 操作数据。

4. HashMap

HashMap 用来存储键值对。它可以存储不同的类型:String 键和 Integer 值,或相同的类型,如:String 键和 String 值:

4.1 创建 HashMap

创建名为 capitalCities 的 HashMap 对象,它将存储 String __和 String 

import java.util.HashMap; // 导入 HashMap 类

HashMap<String, String> capitalCities = new HashMap<String, String>();

4.2 添加项目

向其中添加项目,使用 put() 方法:

// 导入 HashMap 类
import java.util.HashMap;

public class Main {
  public static void main(String[] args) {
    // 创建名为 capitalCities 的 HashMap 对象
    HashMap<String, String> capitalCities = new HashMap<String, String>();

    // Add keys and values (Country, City)
    capitalCities.put("China", "Beijing");
    capitalCities.put("England", "London");
    capitalCities.put("Germany", "Berlin");
    capitalCities.put("USA", "Washington DC");
    System.out.println(capitalCities);
  }
}

4.3 访问项目

如需访问 HashMap 中的值,使用 get() 方法并引用其键:

capitalCities.get("England")

4.4 删除项目

如需删除项目,使用 remove() 方法并引用键:

capitalCities.remove("England")

如需删除所有项目,使用 clear() 方法:

capitalCities.clear();

4.5 哈希图大小

如需找出有多少项目,使用 size() 方法:

capitalCities.size();

4.6 循环遍历 HashMap

使用 for-each 循环遍历 HashMap 的项目。

**注释:**如果您只需要键,请使用 keySet() 方法,如果您只需要值,请使用 values() 方法:

// 打印键
for (String i : capitalCities.keySet()) {
  System.out.println(i);
}
// 打印值
for (String i : capitalCities.values()) {
  System.out.println(i);
}
// 打印键和值
for (String i : capitalCities.keySet()) {
  System.out.println("key: " + i + " value: " + capitalCities.get(i));
}

5. HashSet

HashSet 是项目的集合,其中每个项目都是唯一的,可以在 java.util 包中找到:

5.1 创建HashSet

创建名为 cars 的 HashSet 对象,用于存储字符串:

import java.util.HashSet; // 导入 HashSet 类

HashSet<String> cars = new HashSet<String>();

5.3 添加项目

要向其中添加项目,使用 add() 方法:

// 导入 HashSet 类
import java.util.HashSet;

public class Main {
  public static void main(String[] args) {
    HashSet<String> cars = new HashSet<String>();
    cars.add("Volvo");
    cars.add("BMW");
    cars.add("Ford");
    cars.add("BMW");
    cars.add("Mazda");
    System.out.println(cars);
  }
}

5.4 检查项目是否存在

如需检查 HashSet 中是否存在项目,使用 contains() 方法:

cars.contains("Mazda");

5.5 删除项目

如需删除项目,使用 remove() 方法:

cars.remove("Volvo");

如需删除所有项目,使用 clear() 方法:

cars.clear()

5.6 HashSet 大小

如需找出有多少项,使用 size 方法:

cars.size()

5.7 循环遍历 HashSet

for (String i : cars) {
  System.out.println(i);
}

6. Iterator 迭代器

迭代器(Iterator)是一种对象,可用于循环遍历集合,比如 ArrayList 和 HashSet。
要使用迭代器,必须从 java.util 包中导入它。

6.1 获取迭代器

iterator() 方法可用于获取任何集合的 Iterator

// 导入 ArrayList 类和 Iterator 类:
import java.util.ArrayList;
import java.util.Iterator;

public class Main {
  public static void main(String[] args) {

    // 生成集合:
    ArrayList<String> cars = new ArrayList<String>();
    cars.add("Volvo");
    cars.add("BMW");
    cars.add("Ford");
    cars.add("Mazda");

    // 获取迭代器:
    Iterator<String> it = cars.iterator(); //此时迭代器的初始状态是指向集合中第一个元素的 “前面”

    // 打印首个项目:
    System.out.println(it.next());
  }
}

6.2 循环遍历集合

如需循环遍历集合,使用 Iterator 的 hasNext() 和 next() 方法:

while(it.hasNext()) {
  System.out.println(it.next());
}

6.3 从集合中删除项目

迭代器旨在轻松更改它们循环遍历的集合。remove() 方法可以在循环时从集合中删除项目。

示例:使用迭代器从集合中删除小于 10 的数字:

import java.util.ArrayList;
import java.util.Iterator;

public class Main {
  public static void main(String[] args) {
    ArrayList<Integer> numbers = new ArrayList<Integer>();
    numbers.add(12);
    numbers.add(8);
    numbers.add(2);
    numbers.add(23);
    Iterator<Integer> it = numbers.iterator();
    while(it.hasNext()) { // it.hasNext()用于检查集合中是否还有下一个元素。
      Integer i = it.next();
      if(i < 10) {
        it.remove();
      }
    }
    System.out.println(numbers);
  }
}

**注意:**尝试使用 _for 循环_或 _for-each 循环_删除项目将无法正常工作,因为在代码尝试循环的同时,集合正在更改大小。

七、Java 高级

1. 异常

在执行 Java 代码时,可能会发生各种错误:程序员的编码错误、错误输入导致的异常或其他不可预见的情况。当发生错误时,Java 通常会停止并生成错误消息。用技术术语讲就是:Java 将抛出_异常_(抛出错误)。

1.1 Try…Catch

try 语句允许您定义要在执行时测试错误的代码块。
如果try块中发生错误,catch 语句允许您定义要执行的代码块。
try 和 catch 关键字成对出现:
语法:

try {
  //  要尝试的代码块
}
catch(Exception e) {
  //  处理错误的代码块
}

示例:

public class Main {
  public static void main(String[ ] args) {
    try {
      int[] myNumbers = {1, 2, 3};
      System.out.println(myNumbers[10]);
    } catch (Exception e) {
      System.out.println("出错了。");
    }
  }
}
// 输出: 出错了

1.2 Finally

finally 语句让你在 try...catch 之后执行代码,不管结果如何:

public class Main {
  public static void main(String[] args) {
    try {
      int[] myNumbers = {1, 2, 3};
      System.out.println(myNumbers[10]);
    } catch (Exception e) {
      System.out.println("出错了。");
    } finally {
      System.out.println("'try catch' 已结束。");
    }
  }
}
// 输出:
// 出错了。
// 'try catch' 已结束。

1.3 throw 关键字

throw 语句允许您创建自定义错误。

throw 语句可与异常类型一同使用。Java 中有许多可用的_异常类型_:

  • ArithmeticException :算术异常,当出现数学运算错误时抛出。最常见的情况是整数除以零(int a = 5 / 0;),因为这在数学上是无意义的操作。
  • FileNotFoundException :文件未找到异常,属于 IO 异常的一种。当程序试图打开一个不存在的文件(如通过FileInputStream读取文件),且该操作未被正确捕获处理时抛出。
  • ArrayIndexOutOfBoundsException :数组索引越界异常。当访问数组时使用了超出其有效范围的索引(如数组长度为 3,却访问索引3-1)时抛出,因为数组索引从0开始,最大索引为 “长度 - 1”。
  • SecurityException :安全异常。当程序尝试执行一个被安全管理器(SecurityManager)禁止的操作时抛出,例如访问受限资源、修改系统配置等,常见于有安全限制的环境(如 Applet)。

实例

如果_年龄_低于 18 岁,则抛出异常(打印 “拒绝访问”)。如果年龄为 18 岁或以上,则打印 “允许访问”:

public class Main {
  static void checkAge(int age) {
    if (age < 18) {
      throw new ArithmeticException("拒绝访问 - 您需要至少满 18 岁。");
    }
    else {
      System.out.println("允许访问 - 您的年纪够大!");
    }
  }

  public static void main(String[] args) {
    checkAge(15); // 将年龄设置为 15(低于 18 ...)
  }
}
//  输出:
Exception in thread "main" java.lang.ArithmeticException: 拒绝访问 - 您需要至少满 18 岁。
        at Main.checkAge(Main.java:4)
        at Main.main(Main.java:12)
如果年龄是 20 岁,则不会出现异常:

2. 正则表达式 - RegEx

正则表达式是形成搜索模式的字符序列。在文本中搜索数据时,您可以使用搜索模式来描述您要搜索的内容。

正则表达式可以是单个字符,也可以是更复杂的模式。

正则表达式可用于执行所有类型的_文本搜索文本替换_操作。

Java 没有内置的正则表达式类,但我们可以导入 java.util.regex 包来处理正则表达式。该包提供以下类:

  • Pattern 类 - 定义模式(用于搜索)
  • Matcher 类 - 用于搜索模式
  • PatternSyntaxException 类 - 指示正则表达式模式中的语法错误

实例

找出句子中是否出现 “joey” 一词:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
  public static void main(String[] args) {
    Pattern pattern = Pattern.compile("joey", Pattern.CASE_INSENSITIVE);
    Matcher matcher = pattern.matcher("Visit joey!");
    boolean matchFound = matcher.find();
    if(matchFound) {
      System.out.println("找到匹配");
    } else {
      System.out.println("未找到匹配");
    }
  }
}
// 输出:找到匹配

解释:此例在句子中搜索单词 “joey”。

首先,使用 Pattern.compile() 方法创建模式。第一个参数规定要搜索的模式,第二个参数是一个标志,规定搜索应该不区分大小写。第二个参数是可选的。

matcher() 方法用于在字符串中搜索模式。它返回 Matcher 对象,其中包含有关已执行搜索的信息。

如果在字符串中找到模式,则 find() 方法返回 true,如果未找到,则返回 false。

标志

compile() 方法中的标志改变了搜索的执行方式。这是其中几个:

  • Pattern.CASE_INSENSITIVE - 执行搜索时将忽略字母的大小写。
  • Pattern.LITERAL - 模式中的特殊字符没有任何特殊含义,在执行搜索时将被视为普通字符。
  • Pattern.UNICODE_CASE - 将它与 CASE_INSENSITIVE 标志一起使用也可以忽略英文字母表之外的字母的大小写

正则表达式模式

Pattern.compile() 方法的第一个参数是模式。它描述了正在搜索的内容。

括号用于查找一系列字符:

表达式 描述
[abc] 查找括号之间的一个字符。
[^abc] 查找不在括号之间的一个字符。
[0-9] 查找范围为 0 到 9 的一个字符。

元字符

元字符是拥有特殊含义的字符:

元字符 描述
. 查找任何字符的一个实例。
^ 在字符串的开头查找匹配,如:^Hello。
$ 在字符串的结尾查找匹配,如: World$。
\d 查找数字。
\s 查找空白字符。
\b 在单词开头查找匹配,比如:\bWORD,或在单词结尾,比如:WORD\b。
\u_xxxx_ 查找十六进制数 xxxx 指定的 Unicode 字符。

量词

量词定义数量:

量词 描述
n+ 匹配包含至少一个 n 的任意字符串。
n* 匹配包含零个或多个 n 的任意字符串。
n? 匹配包含零个或一个 n 的任意字符串。
n{x} 匹配包含 x 个 n 的序列的任意字符串。
n{x,y} 匹配包含从 x 到 y 个 n 的序列的任意字符串。
n{x,} 匹配包含至少 x 个 n 的序列的任意字符串。

**注意:**如果您的表达式需要搜索特殊字符之一,可以使用反斜杠 ( \ ) 将其转义。在 Java 中,字符串中的反斜杠需要自己转义,因此需要两个反斜杠来转义特殊字符。例如,要搜索一个或多个问号,您可以使用以下表达式:“\?”

3. 线程

3.1 创建线程

创建线程有两种方法。

可通过扩展 Thread 类并覆盖其 run() 方法来创建:

扩展语法

public class Main extends Thread {
  public void run() {
    System.out.println("这段代码在一个线程中运行");
  }
}

另一种创建线程的方法是实现 Runnable 接口:

实现语法

public class Main implements Runnable {
  public void run() {
    System.out.println("这段代码在一个线程中运行");
  }
}

3.2 运行线程

如果类扩展了 Thread 类,则可以通过创建该类的实例并调用其 start() 方法来运行该线程:

扩展实例

public class Main extends Thread {
  public static void main(String[] args) {
    Main thread = new Main();
    thread.start();
    System.out.println("这段代码在线程之外");
  }
  public void run() {
    System.out.println("这段代码在一个线程中运行");
  }
}

如果类实现了 Runnable 接口,则可以通过将类的实例传递给 Thread 对象的构造函数,然后调用线程的 start() 方法来运行线程:

实现实例

public class Main implements Runnable {
  public static void main(String[] args) {
    Main obj = new Main();
    Thread thread = new Thread(obj);
    thread.start();
    System.out.println("这段代码在线程之外");
  }
  public void run() {
    System.out.println("这段代码在一个线程中运行");
  }
}

“扩展”和“实现”线程之间的差异

主要区别在于,当类扩展 Thread 类时,您不能扩展任何其他类,但通过实现 Runnable 接口,也可以从另一个类扩展,例如:类 MyClass extends OtherClass implements Runnable

3.3 并发问题

由于线程与程序的其他部分同时运行,因此无法获知代码将以何种顺序运行。当线程和主程序读取和写入相同的变量时,值是不可预测的。由此产生的问题称为并发问题。

在下面的例子中,变量 amount 是不可预测的:

public class Main extends Thread {
  public static int amount = 0;

  public static void main(String[] args) {
    Main thread = new Main();
    thread.start();
    System.out.println(amount);
    amount++;
    System.out.println(amount);
  }

  public void run() {
    amount++;
  }
}

为了避免并发问题,最好在线程之间共享尽可能少的属性。如果需要共享属性,一种可能的解决方案是使用线程的 isAlive() 方法在使用线程可以更改的任何属性之前检查线程是否已完成运行。

使用 isAlive() 来防止并发问题:

public class Main extends Thread {
  public static int amount = 0;

  public static void main(String[] args) {
    Main thread = new Main();
    thread.start();
    // Wait for the thread to finish
    while(thread.isAlive()) {
    System.out.println("Waiting...");
  }
  // Update amount and print its value
  System.out.println("Main: " + amount);
  amount++;
  System.out.println("Main: " + amount);
  }
  public void run() {
    amount++;
  }
}

4. Lambda 表达式

Lambda 表达式是在 Java 8 中添加的。

lambda 表达式是一小段代码,它接受参数并返回值。 Lambda 表达式类似于方法,但不需要名称,并可直接在方法主体中实现。

语法

最简单的 lambda 表达式包含一个参数和一个表达式:

parameter -> expression

如需使用多个参数,请将它们括在括号中:

(parameter1, parameter2) -> expression

表达式(expression)是有限制的。它们必须立即返回值,且不能包含变量、赋值或诸如 if 或 for 之类的语句。

为了进行更复杂的操作,可以在花括号内使用代码块:

(parameter1, parameter2) -> { code block }

如果 lambda 表达式需要返回值,那么代码块内需要有 return 语句。

示例:在 ArrayList 的 forEach() 方法中使用 Lamba 表达式来打印列表中的每个项目:

import java.util.ArrayList;

public class Main {
  public static void main(String[] args) {
    ArrayList<Integer> numbers = new ArrayList<Integer>();
    numbers.add(5);
    numbers.add(9);
    numbers.add(8);
    numbers.add(1);
    numbers.forEach( (n) -> { System.out.println(n); } );
  }
}

5. 高级排序(Comparator 和 Comparable)

在“列表排序”章节中,您学习了如何按字母顺序和数字顺序对列表进行排序,但如果列表中包含对象呢?

为了对对象进行排序,您需要指定一个规则来决定对象的排序方式。例如,如果您有一个汽车列表,你可能想按年份排序,规则可以是年份较早的汽车排在前面。

Comparator 和 Comparable 接口允许你指定用于排序对象的规则。

能够指定排序规则还允许您改变字符串和数字的排序方式。

5.1 Comparator(比较器)

实现了 Comparator 接口的对象被称为比较器。

Comparator 接口允许你创建一个包含 compare() 方法的类,该方法比较两个对象以决定哪个在列表中应排在前面。

compare() 方法应返回一个数字,该数字:

  • 为负数时,表示第一个对象应在列表中排在前面。
  • 为正数时,表示第二个对象应在列表中排在前面。
  • 为零时,表示顺序无关紧要。

实现 Comparator 接口的类可能如下所示:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

// 定义一个 Car 类
class Car {
  public String brand; // 品牌
  public String model; // 型号
  public int year; // 年份
  
  // 构造函数
  public Car(String b, String m, int y) {
    brand = b;
    model = m;
    year = y;
  }
}

// 创建一个比较器
class SortByYear implements Comparator {
  // 实现 compare 方法
  public int compare(Object obj1, Object obj2) {
    // 确保传入的对象是 Car 类型
    Car a = (Car) obj1;
    Car b = (Car) obj2;
    
    // 比较两辆车的年份
    if (a.year < b.year) return -1; // 第一辆车年份更小
    if (a.year > b.year) return 1;  // 第一辆车年份更大
    return 0; // 两辆车年份相同
  }
}

public class Main { 
  public static void main(String[] args) { 
    // 创建一个汽车列表
    ArrayList<Car> myCars = new ArrayList<Car>();    
    myCars.add(new Car("BMW", "X5", 1999));
    myCars.add(new Car("Honda", "Accord", 2006));
    myCars.add(new Car("Ford", "Mustang", 1970));

    // 使用比较器对汽车进行排序
    Comparator myComparator = new SortByYear();
    Collections.sort(myCars, myComparator);

    // 显示排序后的汽车
    for (Car c : myCars) {
      System.out.println(c.brand + " " + c.model + " " + c.year);
    }
  } 
}

使用 Lambda 表达式

为了简化代码,比较器可以被替换为具有与 compare() 方法相同参数和返回值的 Lambda 表达式:

Collections.sort(myCars, (obj1, obj2) -> {
  Car a = (Car) obj1;
  Car b = (Car) obj2;
  if (a.year < b.year) return -1;
  if (a.year > b.year) return 1;
  return 0;
});

5.2 Comparable 接口

Comparable 接口允许对象通过 compareTo() 方法指定其自身的排序规则。

compareTo() 方法接受一个对象作为参数,并将可比较对象与该参数进行比较,以决定哪个在列表中应排在前面。

与比较器类似,compareTo() 方法返回一个数字,该数字:

  • 为负数时,表示可比较对象应在列表中排在前面。
  • 为正数时,表示另一个对象应在列表中排在前面。
  • 为零时,表示顺序无关紧要。

许多原生 Java 类(如 String 和 Integer)都实现了 Comparable 接口。

这就是为什么字符串和数字不需要比较器就可以进行排序的原因。

示例:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

// 定义一个可比较的 Car 类
class Car implements Comparable<Car> {
  public String brand; // 品牌
  public String model; // 型号
  public int year;     // 年份
  
  // 构造方法,用于初始化 Car 对象
  public Car(String b, String m, int y) {
    brand = b;
    model = m;
    year = y;
  }
  
  // 实现 compareTo 方法,决定此对象如何与其他 Car 对象进行比较
  @Override
  public int compareTo(Car other) {
    if (year < other.year) return -1; // 如果此车的年份小于另一辆车的年份,则返回 -1,表示此车较小
    if (year > other.year) return 1;  // 如果此车的年份大于另一辆车的年份,则返回 1,表示此车较大
    return 0; // 如果两车的年份相同,则返回 0,表示两车相等(在年份上)
  }
}

public class Main { 
  public static void main(String[] args) { 
    // 创建一个汽车列表
    ArrayList<Car> myCars = new ArrayList<Car>();    
    myCars.add(new Car("BMW", "X5", 1999));
    myCars.add(new Car("Honda", "Accord", 2006));
    myCars.add(new Car("Ford", "Mustang", 1970));

    // 对汽车进行排序
    Collections.sort(myCars);

    // 显示汽车列表
    for (Car c : myCars) {
      System.out.println(c.brand + " " + c.model + " " + c.year);
    }
  } 
}

5.3 Comparator vs. Comparable

比较器(comparator)是一个具有一个方法的对象,用于比较两个不同的对象。

可比较对象(comparable)是一个可以与其他对象进行比较的对象。

在可能的情况下,使用 Comparable 接口更容易,但 Comparator 接口更强大,因为它允许你对任何类型的对象进行排序,即使你不能更改其代码。

八、文件处理

1. 文件

java.io 包中的File 类允许我们处理文件。

如需使用File 类,请创建该类的对象,并指定文件名或目录名。

示例:

import java.io.File;  // 导入 File 类

File myObj = new File("filename.txt"); // 规定文件名

File 类中有许多方法来创建文件并且获取有关文件的信息:

方法 类型 描述
canRead() Boolean 测试文件是否可读。
canWrite() Boolean 测试文件是否可写。
createNewFile() Boolean 创建空文件。
delete() Boolean 删除文件。
exists() Boolean 检查文件是否存在。
getName() String 返回文件名。
getAbsolutePath() String 返回文件的绝对路径名。
length() Long 返回文件的大小,以字节为单位。
list() String[] 返回目录中文件的数组。
mkdir() Boolean 创建目录。

2. 创建/写入文件

2.1 创建文件

createNewFile() 方法。此方法返回一个布尔值:如果文件已成功创建,则为 true,如果文件已存在,则为 false。请注意,该方法包含在 try...catch 块中。这是很必要的,因为如果发生错误(如果由于某种原因无法创建文件),则会抛出 IOException

import java.io.File;  // 导入 File 类
import java.io.IOException;  // 导入 IOException 类来处理错误

public class CreateFile {
  public static void main(String[] args) {
    try {
      File myObj = new File("filename.txt"); // 指定文件路径
      if (myObj.createNewFile()) {
        System.out.println("文件已创建:" + myObj.getName());
      } else {
        System.out.println("文件已存在。");
      }
    } catch (IOException e) {
      System.out.println("发生错误。");
      e.printStackTrace();
    }
  }
}
// 输出将是:
// 文件已创建:filename.txt

2.2 写入文件

使用 FileWriter 类及其 write() 方法将文本写入文件。请注意,当完成写入文件时,应使用 close() 方法关闭它:

import java.io.FileWriter;   // 导入 FileWriter 类
import java.io.IOException;  // 导入 IOException 类来处理错误

public class WriteToFile {
  public static void main(String[] args) {
    try {
      FileWriter myWriter = new FileWriter("filename.txt");
      myWriter.write("Java 中的文件可能很棘手,但它很有趣!");
      myWriter.close();
      System.out.println("成功写入文件。");
    } catch (IOException e) {
      System.out.println("发生错误。");
      e.printStackTrace();
    }
  }
}
// 输出将是:
// 成功写入文件。

3. 读取文件

3.1 读取文件内容

使用 Scanner 类来读取文本文件的内容:

import java.io.File;  // 导入 File 类
import java.io.FileNotFoundException;  // 导入这个类来处理错误
import java.util.Scanner; // 导入 Scanner 类以读取文本文件

public class ReadFile {
  public static void main(String[] args) {
    try {
      File myObj = new File("filename.txt");
      Scanner myReader = new Scanner(myObj);
      while (myReader.hasNextLine()) {
        String data = myReader.nextLine();
        System.out.println(data);
      }
      myReader.close();
    } catch (FileNotFoundException e) {
      System.out.println("发生错误。");
      e.printStackTrace();
    }
  }
}

3.2 获取文件信息

如需获取有关文件的更多信息,使用任意 File 方法:

import java.io.File;  // 导入 File 类

public class GetFileInfo { 
  public static void main(String[] args) {
    File myObj = new File("filename.txt");
    if (myObj.exists()) {
      System.out.println("文件名称:" + myObj.getName());
      System.out.println("绝对路径:" + myObj.getAbsolutePath());
      System.out.println("是否可写:" + myObj.canWrite());
      System.out.println("是否可读:" + myObj.canRead());
      System.out.println("文件大小:" + myObj.length());
    } else {
      System.out.println("文件不存在。");
    }
  }
}

4. 删除文件

在 Java 中删除文件,使用 delete() 方法:

import java.io.File;  // 导入 File 类

public class DeleteFile {
  public static void main(String[] args) { 
    File myObj = new File("filename.txt"); 
    if (myObj.delete()) { 
      System.out.println("已删除文件:" + myObj.getName());
    } else {
      System.out.println("删除文件失败。");
    } 
  } 
}
// 输出将是:
// 已删除文件:filename.txt

删除文件夹:(文件夹必须为空)

import java.io.File; 

public class DeleteFolder {
  public static void main(String[] args) { 
    File myObj = new File("C:\\Users\\MyName\\Test"); 
    if (myObj.delete()) { 
      System.out.println("已删除文件夹:" + myObj.getName());
    } else {
      System.out.println("删除文件夹失败。");
    } 
  } 
}
// 输出将是:
// 已删除文件夹:Test