Java开发教程

一、简介

📢 公司:由 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 -version和javac -version,显示版本号即成功。

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

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

常用快捷键:

快捷键

作用

main/psvm、sout、…

快速键入相关代码

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文件,输入以下代码:

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

    • public class Main:定义一个名为Main的公共类。

    • public static void main(String[] args):程序入口方法,Java 虚拟机(JVM)从这里开始执行。

    • System.out.println():向控制台输出内容。

2.3 运行程序

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

  • 或使用命令行:

    # 编译java文件
    javac Main.java
    # 运行编译后的类
    java Main
  • 输出: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();
        }
    }
}

实际开发中,更常用Scanner或BufferedReader包装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. 数据类型

数据类型分为两组:

  • 原始数据类型 - 包括 byte、short、int、long、float、double、boolean 和 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),没有小数。有效类型为 byte、short、int 和 long。您应该使用哪种类型取决于数值。

  • 浮点类型 表示带有小数部分的数字,包含一位或多位小数。有两种类型:float 和 double。float 的精度只有六位或七位小数,而 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 \|= 3​

x = x \| 3​

\^\=

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​

||

逻辑或

如果其中一条语句为真,则返回 true。

x < 5 \|\| x < 4​

!

逻辑否

反转结果。如果结果为真,则返回 false。

!(x < 5 && x < 10)​

8. 字符串

  • String是不可变对象,所有修改操作(如replace、substring)都会返回新的字符串,原字符串不变。

  • 频繁拼接字符串时,建议使用StringBuilder或StringBuffer以提高性能。

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|float|int|long

acos(​x​)

返回x的反余弦值,以弧度为单位。

double

addExact(​x​,​y​)

返回x和y的和。

int|long

asin(​x​)

返回x的反正弦,以弧度为单位。

double

atan(​x​)

返回x的反正切值,以 -PI/2 和 PI/2 弧度之间的数值。

double

atan2(​y​,​x​)

返回从直角坐标 (​x,​y) 转换为极坐标 (r, theta) 的角度 theta。

double

cbrt( ​x​)

返回x的立方根。

double

ceil(​x​)

返回x的值向上舍入到最接近的整数。

double

copySign(​x​,​y​)

返回第二个浮点y的符号和第一个浮点​x。

double

cos(​x​)

返回x的余弦值(x以弧度为单位)。

double

cosh(​x​)

返回 double 值的双曲余弦值。

double

decrementExact(​x​)

返回​x-1。

int|long

exp(​x​)

返回 Ex 的值。

double

expm1(​x​)

返回 ex -1。

double

floor(​x​)

返回向下舍入到最接近的整数的x的值。

double

floorDiv(​x​,​y​)

返回x除以y向下取整的结果。

int|long

floorMod(​x​,​y​)

返回x除以y的余数,其中除法结果已向下取整。

int|long

getExponent(​x​)

返回x中使用的无偏指数(unbiased exponent)。

int

hypot(​x​,​y​)

返回没有中间溢出或下溢的 sqrt(x2 +y2) 。

double

IEEEremainder(​x​,​y​)

规定计算x和y的余数运算,按照 IEEE 754 标准。

double

incrementExact(​x​)

返回​x+1

int|double

log(​x​)

返回x的自然对数(以 E 为底)。

double

log10(​x​)

返回x的以 10 为底的对数。

double

log1p(​x​)

返回x和 1 之和的自然对数(以 E 为底)。

double

max( ​x​, ​y​)

返回有最高值的数字。

double|float|int|long

min( ​x​, ​y​)

返回有最小值的数字。

double|float|int|long

multiplyExact(​x​,​y​)

返回x与y的乘积结果。

int|long

negateExact(​x​)

返回x的相反数。

int|long

nextAfter(​x​,​y​)

返回y方向与x相邻的浮点数。

double|float

nextDown(​x​)

返回在负方向上与x相邻的浮点值。

double|float

nextUp(​x​)

返回正无穷大方向上与x相邻的浮点值。

double|float

pow( ​x​, ​y​)

返回x的y次方的值。

double

random()

返回 0 到 1 之间的随机数。

double

rint(​x​)

返回最接近x且等于某个数学整数的双精度值。

double

round(​x​)

返回舍入到最接近整数的x的值。

int

scalb(​x​,​y​)

返回x乘以 2 的y次幂的结果。

double|float

signum(​x​)

返回x的符号。

double

sin(​x​)

返回x的正弦值(x以弧度为单位)。

double

sinh(​x​)

返回双精度值的双曲正弦值。

double

sqrt( ​x​)

返回x的平方根。

double

subtractExact(​x​,​y​)

返回x减去y的结果。

int|long

tan(​x​)

返回角度的正切。

double

tanh(​x​)

返回 double 值的双曲正切值。

double

toDegrees(​x​)

将以弧度为单位的角度转换为近似值。以度为单位的等效角度。

double

toIntExact(​x​)

将长整型值转换为整型。

int

toRadians(​x​)

将以度为单位的角度转换为近似值。以弧度为单位的角度。

double

ulp(​x​)

返回x的最小精度单位 (ulp) 的大小。

double|float

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
        }
    }
  • 静态方法(类方法)

    • 可直接通过类名调用,无需创建对象

    • 静态方法中不能使用this和super关键字

    • 静态方法只能访问静态成员,不能访问非静态成员

    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表示成员变量
        }
    }
  2. 调用当前类的其他构造方法

    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;
        }
    }
  3. 返回当前对象

    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

  • 接口属性默认为 public、static 和 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 一样拥有属性和方法。唯一的区别是枚举常量是 public、static 和 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 种基本类型:int、byte、short、long、float、double、char、boolean。特点:直接存储值(而非地址),不继承Object类,不能调用方法,存储在栈内存中。

  • 引用数据类型(Reference Types) 包括:类(如String、Integer)、接口、数组、枚举等。特点:存储的是对象的地址(而非值本身),继承Object类,可调用方法,对象实体存储在堆内存中。

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

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

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

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

以int为例,它是基本类型,而 Java 的集合框架(如List、Set)只能存储引用类型,不能直接存储基本类型。因此需要用对应的 “包装类”(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|void

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|T

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|void

addAll()

向列表中添加一个集合的所有元素。

boolean

addFirst()

在列表开头添加一个元素。

void

addLast()

在列表末尾添加一个元素。

void

clear()

从列表中移除所有元素。

void

clone()

创建 LinkedList 的副本。

contains()

检查列表中是否包含某个元素。

boolean

descendingIterator()

返回一个迭代器,以逆序遍历列表中的元素。

Iterator<T>

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|T

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 的一个字符。

元字符

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

元字符

描述

|

查找由 | 分隔的任何模式之一,如:cat|dog|fish。

.

查找任何字符的一个实例。

\^

在字符串的开头查找匹配,如:\^Hello。

$

在字符串的结尾查找匹配,如: World$。

\d

查找数字。

\s

查找空白字符。

\b

在单词开头查找匹配,比如:\bWORD,或在单词结尾,比如:WORD\b。

\uxxxx

查找十六进制数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

‍