注释

Java 支持三种注释方式:

  • 单行注释
  • 多行注释
  • 文档注释

前两种分别是///* */,第三种被称作文档注释,它以 /**开始,以 */结束。

文档注释允许你在程序中嵌入关于程序的信息。

文档注释,使你更加方便的记录你的程序信息。

JAVA基础

关键字

被赋予特殊含义的单词

  • 字母全部小写
  • 代码编辑器一般对此都有渲染

    JAVA的数据类型

字符型char

表示通常意义上字符,用’’括起来

如:’A’,’中’等

采用unicode编码

布尔值/逻辑值

采用逻辑运算,一般用于程序流程控制

一般取值为ture或false(不能用0/1代替)

整型

有固定表数范围和字符长度,不受操作系统和硬件影响

类型 占用字节 表数范围
byte 1字节 -128~127
short 2字节 -2^15~2^15-1
int 4字节 -2^31~2^31-1
long 8字节 -2^63~2^63-1

整型默认问题

java整型变量默认int

声明长整型常量在常量后加’l’或’L’;

如:整型3、长整型3L;

注意默认特性的编程应用:

1
long k = 922336414641456314L;

Java中数值变量的声明:

二进制变量的声明以0b为前缀;
八进制变量的声明以0为前缀;
十六进制变量的声明以0x为前缀

二进制、八进制、十六进制数值在运用时候自动转为对应的十进制的值

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args)
{
int a = 0b11; //声明二进制变量
int b = 011; //声明八进制变量
int c = 11; //声明十进制变量
int d = 0x11; //声明十六进制变量
System.out.println("a:"+a); //3
System.out.println("b:"+b); //9
System.out.println("c:"+c); //11
System.out.println("d:"+d); //17
}

十进制转其它进制:
十进制转二进制:Integer.toBinaryString(i);
十进制转八进制:Integer.toOctalString(i);
十进制转十六进制:Integer.toHexString(i);

1
2
3
4
5
6
7
8
public static void main(String[] args)
{
int i = 15;
System.out.println("十进制15对应的二进制为:"+Integer.toBinaryString(i)); //1111
System.out.println("十进制15对应的八进制为:"+Integer.toOctalString(i)); //17
System.out.println(i); //15
System.out.println("十进制15对应的十六进制为:"+Integer.toHexString(i)); //f
}

4.浮点型

分类:单精度浮点、双精度浮点

类型 占用存储空间 表数范围
float 4字节 -3.403E38~3.403E38
double 8字节 -1.798E308~1.798E308

JAVA浮点型默认为double型,如要声明float,则在数字后加f或F

1
2
double d = 3.14;
float f = 3.14f;

JAVA的引用数据类型

指向一个对象在内存中的位置

  • 本质上是很强的完整性和安全性的指针(不能使用++、—运算)
  • 包括:类、接口、数组

标识符

用来表示变量名、类名、方法名、数组名和文件名等

是有效的字符序列

规则:

正常规则
  • 由字母、下划线(_)、美元符号($)组合而成
  • 以字符、下划线或美元符号开头,不能以数字开头
JAVA潜规则

要求:“见名思义”

  • stuName,area等,循环控制变量除外
  • 类名首字母大写,如String
  • 变量、方法及对象的首字母小写
  • 所有单词靠在一起,大写中间单词首字母
  • 变量大写所有字母
  • JAVA包全部小写
关键字

是指系统所保留使用的表示符

java不允许用户对关键字赋予其他的含义

常量与变量

变量

概念:在程序的运行过程中可变的量,通常用来记录运算中间结果或保存数据

特点:先声明后使用

变量的声明

变量声明是一个完整的语句,用分号结束

变量四要素:名字、类型、值、作用域

变量形式

1
int min = 10;

常量

概念:一经建立,在整个程序过程中都不会改变

形式与变量基本相同,前面加关键字final

常量形式

1
final int MIN=10; \\常量标识符一般为大写

Helloworld案例的编写

1
2
3
4
5
public class HelloWorld{  //这里的类名称和文件名称保持一致
public static void main(String[] args){
System.out.println("HelloWorld");
}
}

注意:

  • 一个 .java 源文件里面可以写很多个类,但最多只能有一个 public 类。
  • 编译拆分:当代码被编译后,每一个类都会生成一个独立的 .class 文件(即使它们原本写在同一个文件里)。如果你代码里有隐藏的内部类,还会生成类似 MainActivity$1.class 这种带美元符号的文件(你在之前的截图中其实已经见过了)。

运算符

  • 运算符:对常量或者变量进行操作的符号
  • 表达式:用运算符把常量或者变量连接起来符合java语法的式子 就可称为表达式

    算数运算符

符号 作用
+
-
*
/
% 取余

赋值运算符

符号 作用
= 赋值

自增自减运算符

符号 作用
++ 自增
自减

关系运算符

符号 说明
== 判断a和b的值是否相等,相等为true,不等为false
!=
>
>=
<
<=

逻辑运算符

基本逻辑运算符

符号 说明
& 逻辑与
\ 逻辑或
^ 逻辑异或
! 逻辑非

短路逻辑运算符

符号 作用 说明
&& 短路与
\ \ 短路或

区别

  • 短路与左边为true,一定为true;短路或左边为flase,一定为flase

三元运算符

  • 格式:关系表达式?表达式1:表达式2;
  • 范例:a>b?true:false

数据输入

Scanner使用的基本步骤

  1. 导包import java.util.Scanner;
  2. 创建对象 Scanner sc=new Scanner(System.in);
  3. 接受数据 int i = sc.nextInt();

JAVA分支语句

流程控制

分为顺序结构、分支结构、循环结构

顺序结构

是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,一次执行,程序中大多数的代码都是这样执行的

分支结构

if语句

格式1

1
2
3
if(关系表达式){
语句体;
}

执行流程

  1. 受限计算关系表达式的值
  2. 为true就执行语句体
  3. 为false就不执行

    格式2

    1
    2
    3
    4
    5
    if(关系表达式){
    语句体1;
    }else{
    语句体2
    }

    循环语句

JAVA数组

是用于存储多个相同类型数据的存储模型
一次性声明大量的用于存储数据的变量。同时要存储的数据都是同类型数据

格式

有两种格式可以使用

1
int[] arr

或者
1
int arr[]

动态初始化

初始化时只指定数组长度,由系统为数组分配初始值

1
int[] arr = new int[3]

方法

概述

方法是将具有独立功能的代码块组成一个整体,使其具有特殊功能的代码集

方法必须属于某个类,不能像 C / Python 那样独立存在。

注意

  • 方法必须先创建才可以使用
  • 发布该法创建后不是直接运行的,需要手动使用执行,称为方法调用

使用

格式

1
2
3
public static void 方法名(){
//方法体
}

调用

JAVA的方法调用不用关心顺序,因为 Java 编译时会先把整个类结构读完,知道这个类里有哪些方法,然后再检查方法调用。

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
sayHello(); // 方法定义在后面,但可以先调用
}

public static void sayHello() {
System.out.println("Hello");
}
}

带参

定义

1
2
3
public static void 方法名(参数){
....
}

比如说

1
2
3
pubilc static void getMax(int number1,int number2){
....
}

返回值

1
2
3
public static boolean isEvenNumber(int number){
return true;
}

方法重载

简单说就是:

同一个类里,方法名相同,但参数列表不同。

比如你想写一个 add 方法,可以加两个整数,也可以加三个整数,也可以加小数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static int add(int a, int b) {
return a + b;
}

public static int add(int a, int b, int c) {
return a + b + c;
}

public static double add(double a, double b) {
return a + b;
}

public static void main(String[] args) {
System.out.println(add(1, 2)); // 调用第一个
System.out.println(add(1, 2, 3)); // 调用第二个
System.out.println(add(1.5, 2.5)); // 调用第三个
}
}

什么叫“参数列表不同”?

参数列表不同,主要看这几个:

参数个数不同

1
2
public static void test(int a) {}
public static void test(int a, int b) {}

这个可以重载。


参数类型不同

1
2
public static void test(int a) {}
public static void test(String a) {}

这个也可以重载。


参数顺序不同

1
2
public static void test(int a, String b) {}
public static void test(String a, int b) {}

这个也可以重载。

因为调用时可以区分:

1
2
test(1, "abc");
test("abc", 1);

类和对象

对象的概念

万物皆对象,客观存在的事物皆为对象

类的概念

类是指现实生活中一类具有共同属性行为的事物的抽象
特点

  • 是对象的数据类型
  • 是具有相同属性和行为的一组对象的集合

类的基本写法

1
2
3
4
5
6
7
8
public class Person {
String name;
int age;

public void sayHello() {
System.out.println("我叫 " + name + ",今年 " + age + " 岁");
}
}

这里:

1
2
3
Person 是类名
name、age 是成员变量,也叫属性、字段
sayHello() 是方法

可以理解为:

1
2
属性:对象有什么
方法:对象能干什么

创建对象

类只是模板,不能直接代表某个具体的人。

要创建对象,需要使用 new

1
Person p = new Person();

含义:

1
2
3
Person p        声明一个 Person 类型的变量
new Person() 创建一个 Person 对象
= 把对象的地址/引用交给变量 p

然后可以访问对象的属性和方法:

1
2
3
4
p.name = "Tom";
p.age = 18;

p.sayHello();

完整例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
public static void main(String[] args) {
Person p = new Person();

p.name = "Tom";
p.age = 18;

p.sayHello();
}
}

class Person {
String name;
int age;

public void sayHello() {
System.out.println("我叫 " + name + ",今年 " + age + " 岁");
}
}

输出:

1
我叫 Tom,今年 18 岁

类和对象的关系

一个类可以创建多个对象。

1
2
3
4
5
6
7
Person p1 = new Person();
p1.name = "Tom";
p1.age = 18;

Person p2 = new Person();
p2.name = "Jerry";
p2.age = 20;

p1p2 都是 Person 类型,但它们是两个不同对象。

1
2
3
Person 类:模板
p1 对象:Tom,18 岁
p2 对象:Jerry,20 岁

每个对象都有自己独立的成员变量。


成员变量和局部变量

成员变量

写在类里面、方法外面的变量叫成员变量。

1
2
3
4
class Person {
String name;
int age;
}

成员变量属于对象。

如果没有手动赋值,会有默认值:

1
2
3
4
5
int       默认 0
double 默认 0.0
boolean 默认 false
char 默认 '\u0000'
引用类型 默认 null

例如:

1
2
3
Person p = new Person();
System.out.println(p.age); // 0
System.out.println(p.name); // null

局部变量

写在方法里面的变量叫局部变量。

1
2
3
public void test() {
int x = 10;
}

局部变量必须先赋值才能使用。

这个不行:

1
2
3
4
public void test() {
int x;
System.out.println(x); // 报错
}

所以要记住:

1
2
成员变量有默认值
局部变量没有默认值,必须手动初始化

构造方法

构造方法用于创建对象时初始化数据。

构造方法特点:

1
2
3
方法名和类名一样
没有返回值,连 void 都不能写
创建对象时自动调用

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
String name;
int age;

public Person() {
System.out.println("无参构造方法");
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

使用:

1
2
3
Person p1 = new Person();

Person p2 = new Person("Tom", 18);

this 关键字

this 表示当前对象。

最常见用途:区分成员变量和局部变量。

1
2
3
4
5
6
7
8
9
class Person {
String name;
int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

这里:

1
2
this.name 是成员变量
name 是构造方法参数

如果不写 this

1
name = name;

就会变成参数自己给自己赋值,成员变量没有被正确赋值。

所以构造方法里经常写:

1
2
this.name = name;
this.age = age;

对象变量保存的是引用

Java 里的对象变量保存的不是对象本体,而是对象的引用。

例如:

1
2
3
4
5
6
7
Person p1 = new Person();
p1.name = "Tom";

Person p2 = p1;
p2.name = "Jerry";

System.out.println(p1.name);

输出:

1
Jerry

原因是:

1
2
3
p1 和 p2 指向同一个对象
通过 p2 修改对象
p1 看到的也是修改后的结果

可以理解成:

1
2
变量里存的是对象地址
不是对象的完整拷贝

null 空引用

对象变量可以是 null

1
Person p = null;

意思是:

1
p 没有指向任何对象

如果直接调用:

1
p.sayHello();

会报错:

1
NullPointerException

也就是空指针异常。

所以使用对象前要确保它已经指向真实对象:

1
2
Person p = new Person();
p.sayHello();

封装

实际开发中,一般不建议直接暴露成员变量。

不推荐:

1
2
3
4
class Person {
public String name;
public int age;
}

更常见写法是使用 private 修饰属性,再提供 getter 和 setter 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person {
private String name;
private int age;

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

public String getName() {
return name;
}

public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
}
}

public int getAge() {
return age;
}
}

使用:

1
2
3
4
5
6
Person p = new Person();
p.setName("Tom");
p.setAge(18);

System.out.println(p.getName());
System.out.println(p.getAge());

封装的作用:

  • 隐藏内部数据
  • 控制外部访问
  • 可以在 setter 里做校验
  • 提高代码安全性和可维护性

访问修饰符

常见访问修饰符:

1
2
3
4
public     所有地方都能访问
protected 同包或子类能访问
默认 同一个包能访问
private 只有当前类内部能访问

初学先记住:

1
2
public:公开
private:私有

属性一般用 private,方法根据需要用 public


一个文件里多个类

Java 一个 .java 文件里可以写多个类,但有规则:

1
2
一个文件最多只能有一个 public 类
public 类名必须和文件名一致

例如文件名是 Main.java

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
Person p = new Person();
}
}

class Person {
}

这是可以的。

但如果这样:

1
2
public class Person {
}

文件名就必须叫:

1
Person.java

类和对象的完整例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Tom", 18);
Person p2 = new Person("Jerry", 20);

p1.sayHello();
p2.sayHello();
}
}

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("我叫 " + name + ",今年 " + age + " 岁");
}

public String getName() {
return name;
}

public int getAge() {
return age;
}
}

输出:

1
2
我叫 Tom,今年 18 岁
我叫 Jerry,今年 20 岁

API

概念

Java 官方或第三方提供好的类和方法,可以直接调用,不需要自己从零实现。

Java 官方 API 指 JDK 自带的类和方法,比如 String、Scanner、ArrayList、Math、System 等。
第三方 API 一般需要额外导入 jar 包或 Maven 依赖。

几个常见的API:

String:
字符串类,属于引用类型,不是基本类型。
特点是不可变,适合保存和少量处理字符串。

StringBuilder:
可变字符串类,适合频繁拼接和修改字符串。
常用方法是 append()、toString()、reverse()、insert()、delete()。

String 和 StringBuilder 的区别:
String 不可变,每次拼接可能产生新对象。
StringBuilder 可变,适合循环拼接,效率更高。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void main(String[] args) {
String name = "Tom";

StringBuilder sb = new StringBuilder();
sb.append("Hello, ");
sb.append(name);
sb.append("!");

String result = sb.toString();

System.out.println(result);
}
}

集合

概述

编程的时候如果要存储多个数据,使用长度固定的数组存储格式,不一定满足要求,这时候就需要集合

集合类有很多,比如常见的ArrayList,主要分为两大类,Collection(单列集合)和Map(双列集合)

集合提供了一种存储空间可变的方法

双列集合

每次添加添加一堆元素,即键值对
image.png

  • 双列集合一次需要存储一对数据
  • 键不能重复,值可以重复
  • 键和值是一一对应的
  • 在java中称为“Entry对象”

HashMap

概念

HashMap 是 Java 中常用的双列集合,用来存储键值对数据。

格式:

1
HashMap<键的类型, 值的类型> map = new HashMap<>();

例如:

1
HashMap<String, Integer> map = new HashMap<>();

表示:

1
2
键是 String 类型
值是 Integer 类型

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.HashMap;

public class Main {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();

map.put("张三", 18);
map.put("李四", 20);
map.put("王五", 22);

System.out.println(map);
System.out.println(map.get("张三"));
}
}

输出:

1
2
{李四=20, 张三=18, 王五=22}
18

注意:HashMap 的输出顺序不一定和添加顺序一致。


HashMap 的特点

1
2
3
4
5
1. HashMap 存储的是键值对,也就是 key-value。
2. key 不能重复。
3. value 可以重复。
4. 一个 key 对应一个 value。
5. HashMap 的数据没有固定顺序。

例如:

1
2
3
4
5
6
HashMap<String, Integer> map = new HashMap<>();

map.put("张三", 18);
map.put("张三", 20);

System.out.println(map);

结果:

1
{张三=20}

原因是:

1
2
key 不能重复。
如果 key 已经存在,再次 put 相同 key,会覆盖原来的 value。

HashMap 常用方法

1
2
3
4
5
6
7
8
put(key, value)       添加或修改键值对
get(key) 根据 key 获取 value
remove(key) 根据 key 删除键值对
containsKey(key) 判断是否包含某个 key
containsValue(value) 判断是否包含某个 value
size() 获取键值对数量
isEmpty() 判断集合是否为空
clear() 清空集合

例子:

1
2
3
4
5
6
7
8
9
10
11
12
HashMap<String, Integer> map = new HashMap<>();

map.put("Tom", 18);
map.put("Jerry", 20);

System.out.println(map.get("Tom")); // 18
System.out.println(map.containsKey("Tom")); // true
System.out.println(map.size()); // 2

map.remove("Tom");

System.out.println(map); // {Jerry=20}

HashMap 的遍历

方式一:遍历 key,再通过 key 获取 value
1
2
3
4
5
6
7
8
9
10
HashMap<String, Integer> map = new HashMap<>();

map.put("Tom", 18);
map.put("Jerry", 20);
map.put("Jack", 22);

for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}

理解:

1
2
keySet() 可以拿到所有 key。
然后通过 get(key) 获取对应的 value。

方式二:遍历 Entry 对象
1
2
3
4
5
6
7
8
9
10
11
12
HashMap<String, Integer> map = new HashMap<>();

map.put("Tom", 18);
map.put("Jerry", 20);
map.put("Jack", 22);

for (HashMap.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();

System.out.println(key + " = " + value);
}

理解:

1
2
3
Entry 对象表示一对键值对。
一个 Entry 里面包含一个 key 和一个 value。
entrySet() 可以拿到所有键值对对象。

也就是说:

1
2
3
4
HashMap
├── Entry(key, value)
├── Entry(key, value)
└── Entry(key, value)

HashSet

概念

HashSet 是单列集合,用来存储不重复的数据。

1
HashSet<String> set = new HashSet<>();

例如:

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

public class Main {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();

set.add("Tom");
set.add("Jerry");
set.add("Tom");

System.out.println(set);
}
}

输出中只会有一个 Tom


HashSet 的特点

1
2
3
4
1. 只能存储单个元素。
2. 元素不能重复。
3. 没有固定顺序。
4. 底层和 HashMap 有关。

HashSet 表面上是单列集合,但底层可以理解成借助 HashMap 实现。

所以 HashSet 判断元素是否重复时,也会用到:

1
2
hashCode()
equals()

HashSet 和反序列化的关系

因为 HashSet 底层和 HashMap 有关,所以反序列化时也可能间接触发:

1
2
hashCode()
equals()

因此:

1
HashSet → 底层 HashMap → hashCode / equals

反序列化学习中,看到 HashSet,也要想到它可能和 HashMap 的触发逻辑有关。


PriorityQueue

概念

PriorityQueue 是优先队列。

普通队列一般是先进先出,而优先队列会按照优先级进行排序。

1
PriorityQueue<Integer> queue = new PriorityQueue<>();

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.PriorityQueue;

public class Main {
public static void main(String[] args) {
PriorityQueue<Integer> queue = new PriorityQueue<>();

queue.add(30);
queue.add(10);
queue.add(20);

System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}

输出:

1
2
3
10
20
30

因为默认按照从小到大的顺序取出。


PriorityQueue 的特点

1
2
3
4
1. PriorityQueue 是队列。
2. 它会根据优先级排序。
3. 排序时需要比较元素。
4. 比较时可能调用 compare() 或 compareTo()。

Comparator 和 Comparable

PriorityQueue 排序时有两种常见方式:

1
2
3
4
5
Comparable:
对象自己定义比较规则。

Comparator:
额外传入一个比较器,专门负责比较两个对象。

例如使用 Comparator

1
2
3
4
5
6
7
PriorityQueue<Integer> queue = new PriorityQueue<>((a, b) -> b - a);

queue.add(10);
queue.add(30);
queue.add(20);

System.out.println(queue.poll()); // 30

这里 (a, b) -> b - a 就是自定义比较规则。


PriorityQueue 和反序列化的关系

PriorityQueue 在反序列化恢复数据时,需要重新维护队列的排序结构。

排序时会触发比较逻辑,也就是:

1
2
compare()
compareTo()

所以反序列化中经常关注:

1
2
PriorityQueue → Comparator.compare()
PriorityQueue → 元素.compareTo()

大致流程:

1
2
3
4
5
6
7
8
9
反序列化 PriorityQueue

恢复队列中的元素

重新排序

调用 compare() / compareTo()

触发后续 gadget 链

所以看到 PriorityQueue,要想到:

1
它可能通过排序逻辑自动触发 compare()。

TreeMap

概念

TreeMap 是一种有序的双列集合。

它也是存储 key-value,但是会按照 key 的顺序进行排序。

1
TreeMap<String, Integer> map = new TreeMap<>();

例子:

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

public class Main {
public static void main(String[] args) {
TreeMap<String, Integer> map = new TreeMap<>();

map.put("c", 3);
map.put("a", 1);
map.put("b", 2);

System.out.println(map);
}
}

输出:

1
{a=1, b=2, c=3}

因为 TreeMap 会按照 key 排序。


TreeMap 的特点

1
2
3
4
1. TreeMap 是双列集合。
2. key 不能重复,value 可以重复。
3. TreeMap 会按照 key 排序。
4. 排序时会调用 compare() 或 compareTo()。

TreeMap 和 HashMap 的区别

1
2
3
4
5
HashMap:
无序,重点关注 hashCode() 和 equals()。

TreeMap:
有序,重点关注 compare() 和 compareTo()。

所以可以这样记:

1
2
HashMap 靠 hash 存数据。
TreeMap 靠排序存数据。

TreeSet

概念

TreeSet 是一种有序的单列集合。

1
TreeSet<Integer> set = new TreeSet<>();

例子:

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

public class Main {
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>();

set.add(30);
set.add(10);
set.add(20);

System.out.println(set);
}
}

输出:

1
[10, 20, 30]

TreeSet 的特点

1
2
3
4
1. TreeSet 是单列集合。
2. 元素不能重复。
3. 元素会自动排序。
4. 排序时会调用 compare() 或 compareTo()。

TreeSetTreeMap 的关系类似于:

1
2
HashSet 底层和 HashMap 有关。
TreeSet 底层和 TreeMap 有关。

所以:

1
TreeSet → TreeMap → compare / compareTo