Java基础

一、Java虚拟机与平台特性

1. JVM架构详解

  • 类加载器子系统

    • 启动类加载器(Bootstrap):加载rt.jar等核心库
    • 扩展类加载器(Extension):加载jre/lib/ext目录
    • 应用类加载器(Application):加载CLASSPATH路径
    • 自定义类加载器:继承ClassLoader实现热部署
  • 运行时数据区

  • 垃圾回收机制

    • 分代收集:新生代(Eden+S0+S1)和老年代
    • GC算法
      • 标记-清除:产生内存碎片
      • 复制算法:适用于新生代(90%对象存活率低)
      • 标记-整理:适用于老年代
    • 常见GC器
      • Serial:单线程,适合客户端应用
      • Parallel:多线程吞吐量优先
      • CMS:低延迟,但会产生碎片
      • G1:分区收集,预测停顿时间

2. 跨平台原理

  • 字节码文件.class):
    • 由Java编译器生成,与操作系统无关
    • 包含:常量池、字段表、方法表、属性表
  • JVM执行过程
    1
    2
    3
    4
    5
    public class Test {
    public static void main(String[] args) {
    System.out.println("Hello");
    }
    }
  • 编译后字节码(javap -c Test.class):
    1
    2
    3
    4
    5
    6
    public static void main(java.lang.String[]);
    Code:
    0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
    3: ldc #3 // String Hello
    5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8: return

二、基础语法与数据类型

1. 基本数据类型解析

基本类型 包装类 大小 默认值 取值范围 特殊说明
byte Byte 1字节 0 -128~127 适合存储小整数
short Short 2字节 0 -32768~32767
int Integer 4字节 0 -2^31~2^31-1 最常用整型
long Long 8字节 0L -2^63~2^63-1 后缀L
float Float 4字节 0.0f ±3.40282347E+38F 后缀f
double Double 8字节 0.0d ±1.79769313486231570E+308 默认浮点类型
char Character 2字节 ‘\u0000’ 0~65535 Unicode字符
boolean Boolean 1位 false true/false 无具体大小

关键说明

  1. 包装类均为引用类型,首字母大写(两个特殊情况:int 对应 Integerchar 对应 Character,其余基本类型直接首字母大写即可)
  2. 包装类主要用于需要使用对象的场景(如集合框架、反射等),可实现基本类型与包装类的自动装箱/拆箱
  • 自动类型转换规则

    1
    2
    3
    4
    int a = 10;
    double b = a; // 自动提升:int → double
    long c = 100L;
    int d = (int)c; // 显式转换,可能丢失精度
  • 装箱拆箱细节

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    //自动装箱:
    将基本类型(如 int)自动转换为包装类(如 Integer)。
    编译后等价于:Integer x = Integer.valueOf(100);
    //自动拆箱:
    将包装类(如 Integer)自动转换为基本类型(如 int)。
    编译后等价于:int y = x.intValue();

    Integer x = 100; // 自动装箱
    int y = x; // 自动拆箱

    // Integer缓存机制(-128~127)
    Integer a = 100;
    Integer b = 100;
    System.out.println(a == b); // true(引用相同)
    //Java 为 Integer 在 -128 ~ 127 范围内创建了缓存对象(通过 IntegerCache 实现)。
    //当通过 Integer.valueOf() 创建该范围内的值时,直接复用缓存对象,因此 a 和 b 指向同一个对象,== 比较返回true

    Integer c = 200;
    Integer d = 200;
    System.out.println(c == d); // false(超出缓存范围) 所以缓存范围要记住!

    //存在的意义:
    1.让基本类型“对象化”
    Java 的泛型、集合框架、反射、注解等机制只能操作对象(引用类型),不能操作基本类型。包装类(如 Integer)将基本类型“封装”为对象,解决了这一矛盾。

    2.支持 null
    基本类型(如 int)必须有默认值(int 默认是 0),无法表示“无值”状态。
    包装类可以为 null,适用于需要“可选值”的场景
    Integer age = null; // 合法:表示年龄未知
    int age = 0; // 不合法:0 可能被误解为有效值

    3.提供丰富的实用方法
    int num = Integer.parseInt("123"); // 字符串转数字
    String binary = Integer.toBinaryString(10); // 转二进制字符串
    int max = Integer.max(10, 20); // 比较大小

2. 字符串处理深度

  • String不可变性原理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    String 对象一旦创建,其内容无法修改
    效率问题:如果直接用 + 拼接多个字符串(如 a + b + c),每次操作都会创建新的 String 对象,导致大量临时对象和内存浪费。

    编译器的优化机制:
    当代码中出现字符串拼接(+)时,Java编译器会自动将其转换为 StringBuilder 的链式调用
    String s = "Hello";
    s = s + " World"; // 实际创建了新对象

    // 底层:new StringBuilder("Hello").append(" World").toString()
    1. new StringBuilder(s)
    将当前 s 的值("Hello")作为初始内容传入 StringBuilder 的构造函数。
    此时 StringBuilder 内部会复制 "Hello" 的字符数组(非引用传递)。
    2. .append(" World")
    " World" 追加到 StringBuilder 的内部缓冲区。
    内部通过 char[] 扩容和复制实现,避免频繁创建新对象。
    3. .toString()
    将 StringBuilder 的内容转换为一个新的 String 对象("Hello World")。
    这个新对象被赋值给 s,而原来的 "Hello" 对象(若无其他引用)将被垃圾回收。

  • 字符串常量池

    1
    2
    3
    4
    5
    String s1 = "Hello"; // 字面量->直接放入常量池(若不存在)
    String s2 = new String("Hello"); // 创建新对象,不在常量池,属于堆
    String s3 = "Hello"; // 字面量->引用常量池中的对象
    System.out.println(s1 == s2); // false
    System.out.println(s1 == s3); // true
  • String常用方法详解

    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
    // 1. 内容比较(区分大小写)
    "Hello".equals("hello"); // false(默认是严格对比)
    "Hello".equalsIgnoreCase("hello"); // true(不区分大小写)

    // 2. 字符串分割: split()函数的参数是正则表达式
    "a,b,c".split(","); // ["a","b","c"]
    "a..b".split("\\.\\."); // ["a","b"](转义点号)

    // 3. 字符串反转
    new StringBuilder("abc").reverse().toString(); // "cba"

    // 4. intern()方法
    String s = new String("abc").intern(); // 返回常量池中的引用

    1.new String("abc") 的创建过程:
    字面量 "abc":类加载时,"abc" 会被自动放入字符串常量池(如果不存在)。
    2.new String("abc"):
    强制在堆内存中创建新对象,内容为 "abc"。该对象与常量池中的 "abc" 是两个独立对象!此时堆中有一个新对象,常量池中有一个 "abc"
    3.调用 .intern() 的过程:
    JVM 检查常量池中是否已有内容相同的字符串("abc" 已存在),则直接返回常量池中的引用(不再创建新对象)。
    4.s 被赋值为常量池中 "abc" 的引用。

    intern() 作用的关键总结:
    如果常量池中已有该字符串,则返回池中的引用;
    如果不存在,则将当前字符串放入池中,再返回池中的引用。
  • StringBuilder vs StringBuffer

    特性 StringBuilder StringBuffer
    线程安全 ❌ 不安全 ✅ 安全(synchronized)
    性能 ⚡️ 更快 ⏳ 较慢
    使用场景 单线程环境 多线程环境
  • 用法完全一致!两者的 API 完全相同(方法名、参数、返回值、行为逻辑都一致),区别仅在于 线程安全性和性能。

三、正则表达式深度解析

正则表达式(Regular Expression)是处理字符串的强大工具,用于匹配、查找、替换符合特定规则的文本。Java中通过java.util.regex包提供正则支持,核心类为Pattern(编译正则表达式)和Matcher(执行匹配操作)。

1. 正则表达式基本语法

(1)字符匹配

语法 描述 示例
普通字符 匹配自身 abc 匹配 “abc”
. 匹配任意单个字符(除换行符) a.c 匹配 “abc”、”a1c”
[abc] 匹配括号内任意一个字符 [abc] 匹配 “a”、”b”、”c”
[^abc] 匹配不在括号内的任意字符 [^abc] 匹配 “d”、”1”
[a-z] 匹配指定范围的字符 [0-9a-zA-Z] 匹配数字或字母
\d 等价于 [0-9](数字) \d{3} 匹配 “123”
\D 等价于 [^0-9](非数字) \D 匹配 “a”、”!”
\w 等价于 [a-zA-Z0-9_](单词字符) \w+ 匹配 “user123”
\W 等价于 [^a-zA-Z0-9_](非单词字符) \W 匹配 “@”、”#”
\s 匹配空白字符(空格、制表符等) a\sb 匹配 “a b”、”a\tb”
\S 匹配非空白字符 a\Sb 匹配 “a1b”、”a@b”

(2)量词(控制匹配次数)

语法 描述 示例
* 匹配前一个元素0次或多次(贪婪模式) a* 匹配 “”、”a”、”aa”
+ 匹配前一个元素1次或多次(贪婪模式) a+ 匹配 “a”、”aa”
? 匹配前一个元素0次或1次(贪婪模式) a? 匹配 “”、”a”
{n} 匹配前一个元素恰好n次 a{3} 匹配 “aaa”
{n,} 匹配前一个元素至少n次 a{2,} 匹配 “aa”、”aaa”
{n,m} 匹配前一个元素n到m次 a{1,3} 匹配 “a”、”aa”、”aaa”
*?/+?/?? 非贪婪模式(尽可能少匹配) "a.*?b" 匹配 “aab” 中的 “aab”(而非贪婪的 “aab…b”)

(3)边界匹配

语法 描述 示例
^ 匹配字符串开始位置 ^abc 匹配 “abc123”(不匹配 “xabc”)
$ 匹配字符串结束位置 abc$ 匹配 “123abc”(不匹配 “abcx”)
\b 匹配单词边界(单词与非单词字符之间) \bcat\b 匹配 “cat”(不匹配 “category” 中的 “cat”)
\B 匹配非单词边界 \Bcat\B 匹配 “category” 中的 “cat”

(4)分组与捕获

语法 描述 示例
(pattern) 分组,将pattern视为一个整体,可捕获匹配结果 (ab)+ 匹配 “abab”
\n 引用第n个分组的匹配结果(n为1-9) (a)\1 匹配 “aa”
(?:pattern) 非捕获分组(仅分组不捕获结果) (?:ab)+ 匹配 “abab” 但不保存分组
(?<name>pattern) 命名分组(通过名称引用) (?<id>\d+) 匹配数字并命名为”id”

(5)逻辑运算符

语法 描述 示例
` ` 逻辑或,匹配左边或右边的表达式
() 结合逻辑或 `(ab

2. Java正则核心类(Pattern与Matcher)

(1)Pattern类(编译正则表达式)

  • 作用:预编译正则表达式为Pattern对象(线程安全,可复用)。
  • 常用方法:
    • Pattern.compile(String regex):编译正则表达式。
    • Pattern.matches(String regex, CharSequence input):快速匹配(等价于编译后直接调用matches())。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class MatcherDemo {
public static void main(String[] args) {
// 1. 先通过Pattern编译正则表达式(匹配3位数字)
Pattern pattern = Pattern.compile("\\d{3}");

// 2. 传入输入字符串,通过Pattern的matcher()方法获取Matcher对象
String input = "abc123def456ghi78";
Matcher matcher = pattern.matcher(input);

// 此时matcher已绑定正则(\\d{3})和输入字符串(abc123def456ghi78),可执行匹配操作
}
}

(2)Matcher类(执行匹配操作)

  • 作用:通过Pattern对象创建,用于对输入字符串执行匹配操作。
  • 常用方法:
    • matches():整个字符串是否完全匹配正则。
    • find():查找字符串中是否有匹配的子序列(可多次调用,每次找下一个)。
    • group():获取匹配的子序列(结合分组使用)。
    • start()/end():获取匹配子序列的起始/结束索引。
    • replaceAll(String replacement):替换所有匹配的子序列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class MatcherMethodWithResultDemo {
public static void main(String[] args) {
// 1. 预编译正则表达式:\\d+ 匹配1个及以上的数字(整数)
Pattern pattern = Pattern.compile("\\d+");
// 待匹配的输入字符串
String inputStr = "abc123def456ghi7890";
// 2. 通过Pattern创建Matcher对象(绑定正则和输入字符串)
Matcher matcher = pattern.matcher(inputStr);

// ========== 1. 演示 matches() 方法:全量匹配 ==========
System.out.println("===== matches() 全量匹配 =====");
// 判断整个输入字符串是否完全符合正则(\\d+ 要求全是数字,此处有字母,返回false)
boolean isFullMatch = matcher.matches();
System.out.println("整个字符串是否完全匹配数字:" + isFullMatch);
// 运行结果注释:输出 -> 整个字符串是否完全匹配数字:false

// 注意:调用matches()后,Matcher的匹配状态会改变,如需后续操作,先重置
matcher.reset();

// ========== 2. 演示 find() + group() + start()/end() 方法:查找+提取+获取位置 ==========
System.out.println("\n===== find() + group() + start()/end() 查找与提取 =====");
// while循环迭代:find() 每次调用查找下一个匹配的子串,无匹配时返回false
while (matcher.find()) {
// group():获取当前匹配到的完整子串(等价于group(0))
String matchContent = matcher.group();
// start():获取匹配子串在输入字符串中的起始索引(从0开始,左闭)
int startIndex = matcher.start();
// end():获取匹配子串在输入字符串中的结束索引(左闭右开,即最后一个字符的索引+1)
int endIndex = matcher.end();

// 打印结果
System.out.printf("匹配到数字:%s,起始索引:%d,结束索引:%d,对应区间:[%d, %d)%n",
matchContent, startIndex, endIndex, startIndex, endIndex);
// 循环内运行结果注释(依次输出):
// 匹配到数字:123,起始索引:3,结束索引:6,对应区间:[3, 6)
// 匹配到数字:456,起始索引:9,结束索引:12,对应区间:[9, 12)
// 匹配到数字:7890,起始索引:15,结束索引:19,对应区间:[15, 19)
}

// ========== 3. 演示 group() 分组功能:正则中用()表示分组 ==========
System.out.println("\n===== group() 分组提取 =====");
// 重新编译带分组的正则:(\\d{2})(\\d+) 表示把数字分为2组:前2位 + 剩余位数
Pattern groupPattern = Pattern.compile("(\\d{2})(\\d+)");
Matcher groupMatcher = groupPattern.matcher(inputStr);
// 查找并提取分组内容
while (groupMatcher.find()) {
// 1. 获取当前匹配项的完整子串(group(0) 固定对应完整匹配内容)
String fullMatch = groupMatcher.group(0);
// 匹配123时:fullMatch = "123";匹配456时:fullMatch = "456";匹配7890时:fullMatch = "7890"

// 2. 获取第1个分组匹配的内容(对应第一个括号 (\\d{2}):匹配2位数字)
String group1 = groupMatcher.group(1);
// 匹配123时:group1 = "12"(前2位数字);匹配456时:group1 = "45";匹配7890时:group1 = "78"

// 3. 获取第2个分组匹配的内容(对应第二个括号 (\\d+):匹配1位及以上剩余数字)
String group2 = groupMatcher.group(2);
// 匹配123时:group2 = "3"(剩余1位数字);匹配456时:group2 = "6";匹配7890时:group2 = "90"

// 4. 格式化打印完整内容和分组内容
System.out.printf("完整匹配:%s,分组1(前2位):%s,分组2(剩余):%s%n",
fullMatch, group1, group2);
// 按格式拼接字符串并输出,对应示例中的三行打印结果
// 循环内运行结果注释(依次输出):
// 完整匹配:123,分组1(前2位):12,分组2(剩余):3
// 完整匹配:456,分组1(前2位):45,分组2(剩余):6
// 完整匹配:7890,分组1(前2位):78,分组2(剩余):90
}

// ========== 4. 演示 replaceAll() 和 replaceFirst() 替换方法 ==========
System.out.println("\n===== replaceAll() + replaceFirst() 替换 =====");
// 重置Matcher状态,重新绑定输入字符串
matcher.reset();
// replaceAll():替换所有匹配的子串为指定内容
String replaceAllResult = matcher.replaceAll("【数字】");
System.out.println("替换所有数字后的结果:" + replaceAllResult);
// 运行结果注释:输出 -> 替换所有数字后的结果:abc【数字】def【数字】ghi【数字】

// 再次重置Matcher
matcher.reset();
// replaceFirst():只替换第一个匹配的子串
String replaceFirstResult = matcher.replaceFirst("【首个数字】");
System.out.println("只替换第一个数字后的结果:" + replaceFirstResult);
// 运行结果注释:输出 -> 只替换第一个数字后的结果:abc【首个数字】def456ghi7890
}
}

3. 常用场景示例

(1)数据验证

1
2
3
4
5
6
7
// 验证邮箱(简单规则)
String emailRegex = "^[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)+$";
boolean isEmail = "user@example.com".matches(emailRegex); // true

// 验证手机号(中国大陆)
String phoneRegex = "^1[3-9]\\d{9}$";
boolean isPhone = "13812345678".matches(phoneRegex); // true

(2)文本提取

1
2
3
4
5
6
7
8
9
10
11
// 从HTML中提取所有链接(<a href="xxx">)
String html = "<a href='https://a.com'>链接1</a><a href='https://b.com'>链接2</a>";
Pattern linkPattern = Pattern.compile("href=['\"](.*?)['\"]");
Matcher matcher = linkPattern.matcher(html);

while (matcher.find()) {
System.out.println("链接:" + matcher.group(1)); // 提取分组1的内容
}
// 输出:
// 链接:https://a.com
// 链接:https://b.com

(3)文本替换

1
2
3
4
// 替换字符串中的所有数字为"*"
String text = "密码:123456,验证码:789";
String replaced = text.replaceAll("\\d", "*");
System.out.println(replaced); // 输出:密码:******,验证码:***

四、数组与集合框架

1. 数组底层实现

  • 内存布局
    1
    2
    3
    int[] arr = new int[3];
    // 内存结构:
    // [arr对象头][length=3][0][0][0]
  • 多维数组
    1
    2
    3
    4
    5
    int[][] matrix = new int[2][3];
    // 内存结构:
    // [matrix对象头][length=2][array1][array2]
    // array1: [length=3][0][0][0]
    // array2: [length=3][0][0][0]

2. 集合框架深度解析

(1) List实现类

类型 底层结构 扩容机制 特点 适用场景
ArrayList 动态数组 1.5倍扩容 随机访问快
插入删除慢
频繁查询
较少修改
LinkedList 双向链表 无扩容 插入删除快
随机访问慢
频繁增删
较少查询
  • ArrayList扩容细节

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // JDK1.8源码
    private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍
    if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
    }
  • ArrayList常用方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    import java.util.ArrayList;
    import java.util.List;

    public class ArrayListSimpleDemo {
    public static void main(String[] args) {
    // 1. 创建ArrayList对象,存储字符串类型元素
    List<String> arrayList = new ArrayList<>();

    // 2. 向集合中添加元素(尾部添加,简单高效)
    arrayList.add("Java");
    arrayList.add("Python");
    arrayList.add("C++");
    arrayList.add("Go");
    System.out.println("初始ArrayList:" + arrayList);
    // 运行结果:初始ArrayList:[Java, Python, C++, Go]

    // 3. 核心优势:随机访问(通过索引快速获取元素,对应表格中“随机访问快”)
    // 索引从0开始,直接获取第2个元素(Python)、第3个元素(C++)
    String element1 = arrayList.get(1);
    String element2 = arrayList.get(2);
    System.out.println("索引1的元素:" + element1 + ",索引2的元素:" + element2);
    // 运行结果:索引1的元素:Python,索引2的元素:C++

    // 4. 修改元素(通过索引快速修改)
    arrayList.set(3, "Rust"); // 将索引3的元素(Go)改为Rust
    System.out.println("修改后ArrayList:" + arrayList);
    // 运行结果:修改后ArrayList:[Java, Python, C++, Rust]

    // 5. 删除元素(尾部删除高效,中间删除效率低,对应表格“插入删除慢”)
    arrayList.remove(2); // 删除索引2的元素(C++)
    System.out.println("删除索引2元素后:" + arrayList);
    // 运行结果:删除索引2元素后:[Java, Python, Rust]

    // 6. 遍历集合(普通for循环,利用索引快速遍历,适配ArrayList特性)
    System.out.println("普通for循环遍历(适配ArrayList随机访问特性):");
    for (int i = 0; i < arrayList.size(); i++) {
    System.out.println("索引" + i + ":" + arrayList.get(i));
    }
    // 运行结果:
    // 索引0:Java
    // 索引1:Python
    // 索引2:Rust
    }
    }
  • LinkedList常用方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    import java.util.LinkedList;

    public class LinkedListSimpleDemo {
    public static void main(String[] args) {
    // 1. 创建LinkedList对象,存储整数类型元素
    LinkedList<Integer> linkedList = new LinkedList<>();

    // 2. 向集合中添加元素(支持尾部、头部添加,高效)
    linkedList.add(10); // 尾部添加(普通添加方法)
    linkedList.add(20);
    linkedList.addFirst(5); // 特有方法:头部添加(高效,对应“插入删除快”)
    linkedList.addLast(25); // 特有方法:尾部添加(高效)
    System.out.println("初始LinkedList:" + linkedList);
    // 运行结果:初始LinkedList:[5, 10, 20, 25]

    // 3. 核心优势:频繁增删(头部、尾部、指定位置,效率高于ArrayList)
    linkedList.add(2, 15); // 在索引2的位置插入15(高效)
    System.out.println("索引2插入15后:" + linkedList);
    // 运行结果:索引2插入15后:[5, 10, 15, 20, 25]

    linkedList.removeFirst(); // 特有方法:删除头部元素(高效)
    linkedList.removeLast(); // 特有方法:删除尾部元素(高效)
    System.out.println("删除头尾元素后:" + linkedList);
    // 运行结果:删除头尾元素后:[10, 15, 20]

    // 4. 查询元素(通过索引查询,效率低,对应表格“随机访问慢”,不推荐频繁使用)
    Integer element = linkedList.get(1);
    System.out.println("索引1的元素:" + element);
    // 运行结果:索引1的元素:15

    // 5. 遍历集合(推荐增强for循环,避免索引查询,适配LinkedList特性)
    System.out.println("增强for循环遍历(适配LinkedList增删特性):");
    for (Integer num : linkedList) {
    System.out.println(num);
    }
    // 运行结果:
    // 10
    // 15
    // 20
    }
    }

(2) Set实现类

类型 底层结构 排序特性 线程安全 特点
HashSet HashMap 无序 通过hashCode()快速查找
LinkedHashSet LinkedHashMap 插入顺序 保持插入顺序
TreeSet TreeMap 自然排序 通过红黑树实现
  • HashSet:无序、去重(底层 HashMap)

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

    public class HashSetDemo {
    public static void main(String[] args) {
    // 1. 创建HashSet对象,存储字符串类型
    Set<String> hashSet = new HashSet<>();

    // 2. 添加元素(包含重复元素,演示去重特性)
    hashSet.add("Java");
    hashSet.add("Python");
    hashSet.add("Java"); // 重复元素,添加失败
    hashSet.add("C++");
    hashSet.add("Go");

    // 3. 打印集合(无序,且无重复元素“Java”)
    System.out.println("HashSet元素(无序+去重):" + hashSet);
    // 运行结果(顺序不固定,示例参考):HashSet元素(无序+去重):[Java, Python, C++, Go]

    // 4. 常用操作:判断元素是否存在、删除元素
    boolean hasPython = hashSet.contains("Python");
    hashSet.remove("C++");
    System.out.println("是否包含Python:" + hasPython);
    System.out.println("删除C++后HashSet:" + hashSet);
    // 运行结果:
    // 是否包含Python:true
    // 删除C++后HashSet:[Java, Python, Go]
    }
    }
  • LinkedHashSet:保持插入顺序、去重(底层 LinkedHashMap)

    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
    import java.util.LinkedHashSet;
    import java.util.Set;

    public class LinkedHashSetDemo {
    public static void main(String[] args) {
    // 1. 创建LinkedHashSet对象
    Set<String> linkedHashSet = new LinkedHashSet<>();

    // 2. 按顺序添加元素(包含重复元素)
    linkedHashSet.add("Java");
    linkedHashSet.add("Python");
    linkedHashSet.add("Java"); // 重复元素,添加失败
    linkedHashSet.add("C++");
    linkedHashSet.add("Go");

    // 3. 打印集合(与插入顺序完全一致,且去重)
    System.out.println("LinkedHashSet元素(插入顺序+去重):" + linkedHashSet);
    // 运行结果(顺序固定):LinkedHashSet元素(插入顺序+去重):[Java, Python, C++, Go]

    // 4. 遍历集合(顺序与插入一致)
    System.out.println("遍历LinkedHashSet(保持插入顺序):");
    for (String str : linkedHashSet) {
    System.out.println(str);
    }
    // 运行结果:Java -> Python -> C++ -> Go(与插入顺序一致)
    }
    }
  • TreeSet:自然排序、去重(底层 TreeMap,红黑树)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import java.util.TreeSet;
    import java.util.Set;

    public class TreeSetDemo {
    public static void main(String[] args) {
    // 示例1:存储整数,自然排序(升序)
    Set<Integer> numTreeSet = new TreeSet<>();
    numTreeSet.add(30);
    numTreeSet.add(10);
    numTreeSet.add(20);
    numTreeSet.add(30); // 重复元素,添加失败
    System.out.println("TreeSet整数(自然升序+去重):" + numTreeSet);
    // 运行结果(固定升序):TreeSet整数(自然升序+去重):[10, 20, 30]

    // 示例2:存储字符串,自然排序(字典序)
    Set<String> strTreeSet = new TreeSet<>();
    strTreeSet.add("Java");
    strTreeSet.add("Python");
    strTreeSet.add("C++");
    strTreeSet.add("Go");
    System.out.println("TreeSet字符串(字典序+去重):" + strTreeSet);
    // 运行结果(固定字典序):TreeSet字符串(字典序+去重):[C++, Go, Java, Python]
    }
    }

(3) Map实现类

类型 底层结构 扩容机制 线程安全 特点
HashMap 数组+链表+红黑树 2倍扩容 最常用Map
Hashtable 数组+链表 2倍扩容 同步,已过时
ConcurrentHashMap 分段锁(JDK7)
CAS+synchronized(JDK8)
2倍扩容 高并发场景
TreeMap 红黑树 按键排序
  • HashMap:最常用、无序、非线程安全(底层数组 + 链表 + 红黑树)

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

    public class HashMapDemo {
    public static void main(String[] args) {
    // 1. 创建HashMap对象,键:字符串(姓名),值:整数(年龄)
    Map<String, Integer> hashMap = new HashMap<>();

    // 2. 添加键值对(增)
    hashMap.put("张三", 25);
    hashMap.put("李四", 30);
    hashMap.put("王五", 28);
    hashMap.put("张三", 26); // 重复键,覆盖原有值

    // 3. 获取值(查)
    int zhangSanAge = hashMap.get("张三");
    System.out.println("张三的年龄:" + zhangSanAge);
    // 运行结果:张三的年龄:26(已覆盖原有值25)

    // 4. 打印Map(无序存储)
    System.out.println("HashMap键值对(无序):" + hashMap);
    // 运行结果(顺序不固定):HashMap键值对(无序):{张三=26, 李四=30, 王五=28}

    // 5. 修改值(改)
    hashMap.put("李四", 31);
    // 6. 删除键值对(删)
    hashMap.remove("王五");
    System.out.println("修改+删除后HashMap:" + hashMap);
    // 运行结果:修改+删除后HashMap:{张三=26, 李四=31}

    // 7. 遍历Map
    System.out.println("遍历HashMap:");
    for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
    System.out.println("键:" + entry.getKey() + ",值:" + entry.getValue());
    }
    }
    }
  • Hashtable:线程安全、已过时(底层数组 + 链表)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import java.util.Hashtable;
    import java.util.Map;

    public class HashtableDemo {
    public static void main(String[] args) {
    // 1. 创建Hashtable对象
    Map<String, Integer> hashtable = new Hashtable<>();

    // 2. 添加键值对(用法与HashMap类似)
    hashtable.put("张三", 25);
    hashtable.put("李四", 30);
    hashtable.put("王五", 28);
    hashtable.put("张三", 26); // 重复键,覆盖值

    // 3. 打印Hashtable(无序)
    System.out.println("Hashtable键值对(线程安全+无序):" + hashtable);
    // 运行结果:Hashtable键值对(线程安全+无序):{李四=30, 张三=26, 王五=28}

    // 4. 关键提醒:Hashtable已过时,不推荐使用,性能远低于ConcurrentHashMap
    System.out.println("注意:Hashtable已过时,优先使用ConcurrentHashMap实现线程安全Map");
    }
    }
  • ConcurrentHashMap:高并发、线程安全(JDK8:CAS+synchronized)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
      import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;

    public class ConcurrentHashMapDemo {
    public static void main(String[] args) throws InterruptedException {
    // 1. 创建ConcurrentHashMap对象
    Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();

    // 2. 演示多线程写入(体现高并发安全特性)
    Runnable task = () -> {
    for (int i = 0; i < 1000; i++) {
    // 并发写入,不会出现线程安全问题(如数据丢失、异常)
    concurrentHashMap.put(Thread.currentThread().getName() + "-" + i, i);
    }
    };

    // 3. 启动2个线程并发写入
    Thread t1 = new Thread(task, "线程1");
    Thread t2 = new Thread(task, "线程2");
    t1.start();
    t2.start();
    t1.join();
    t2.join();

    // 4. 打印元素个数(应为2000个,无数据丢失)
    System.out.println("ConcurrentHashMap元素个数(并发写入无丢失):" + concurrentHashMap.size());
    // 运行结果:ConcurrentHashMap元素个数(并发写入无丢失):2000

    // 5. 普通增删改查(用法与HashMap一致)
    concurrentHashMap.put("张三", 25);
    System.out.println("张三的年龄:" + concurrentHashMap.get("张三"));
    // 运行结果:张三的年龄:25
    }
    }
    ```

    - **TreeMap:按键自然排序、非线程安全(底层红黑树)**
    ```java
    import java.util.TreeMap;
    import java.util.Map;

    public class TreeMapDemo {
    public static void main(String[] args) {
    // 1. 创建TreeMap对象,键:字符串(姓名),值:整数(年龄)
    Map<String, Integer> treeMap = new TreeMap<>();

    // 2. 添加键值对(键为字符串,按字典序排序)
    treeMap.put("张三", 25);
    treeMap.put("李四", 30);
    treeMap.put("王五", 28);
    treeMap.put("赵六", 35);

    // 3. 打印TreeMap(按键自然排序:字典序)
    System.out.println("TreeMap键值对(按键字典序排序):" + treeMap);
    // 运行结果(固定字典序):TreeMap键值对(按键字典序排序):{李四=30, 王五=28, 张三=25, 赵六=35}

    // 4. 遍历TreeMap(按键升序遍历)
    System.out.println("遍历TreeMap(按键排序):");
    for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
    System.out.println("键:" + entry.getKey() + ",值:" + entry.getValue());
    }

    // 示例2:键为整数,按升序排序
    Map<Integer, String> numTreeMap = new TreeMap<>();
    numTreeMap.put(3, "C++");
    numTreeMap.put(1, "Java");
    numTreeMap.put(2, "Python");
    System.out.println("TreeMap(键为整数,升序排序):" + numTreeMap);
    // 运行结果:TreeMap(键为整数,升序排序):{1=Java, 2=Python, 3=C++}
    }
    }

(5)总结

实现类 核心特性体现 适用场景
HashSet 无序、去重 无需有序存储,仅需去重
LinkedHashSet 插入顺序、去重 需要保留插入顺序且去重
TreeSet 自然排序、去重 需要有序存储且去重
HashMap 高效、无序、非线程安全 普通场景(单线程/无并发)的键值对存储
Hashtable 线程安全、性能差、已过时 兼容旧系统,不推荐新开发使用
ConcurrentHashMap 高并发、线程安全、性能优异 多线程并发写入/读取的场景
TreeMap 按键自然排序、非线程安全 需要按键盘序遍历的键值对存储

五、面向对象深度解析

1. 继承与多态

  • 多态实现原理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Animal { 
    void sound() {
    System.out.println("Animal");
    }
    }
    class Dog extends Animal {
    @Override
    void sound() {
    System.out.println("Woof");
    }
    }

    Animal a = new Dog();
    a.sound(); // 输出"Woof"(动态绑定)
    • JVM通过虚方法表(vtable) 实现
    • 每个类有虚方法表,指向实际方法地址
  • super关键字详解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Parent {
    Parent(String name) { /* ... */ }
    }

    class Child extends Parent {
    Child() {
    super("Child"); // 必须在第一行调用父类构造器
    }
    }
  • Override与super关键字

    • Override:子类重写父类方法,必须与父类方法签名一致(返回值类型、方法名、参数列表)

    • super:调用父类方法或构造器,必须在子类方法第一行(构造器中)

    • 示例代码

      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
      // 父类:定义基础方法
      class Person {
      // 父类的问候方法
      public void sayHello() {
      System.out.println("父类(Person):你好!");
      }
      }

      // 子类:继承父类,并重写方法
      class Student extends Person {
      // 1. 使用 @Override 注解标注此方法
      @Override
      public void sayHello() {
      // 2. 使用 super 关键字调用父类的 sayHello() 方法
      super.sayHello();
      // 子类自己的扩展逻辑
      System.out.println("子类(Student):我是一名学生!");
      }
      }

      // 测试类
      public class OverrideAndSuperDemo {
      public static void main(String[] args) {
      Student student = new Student();
      student.sayHello(); // 调用子类重写后的方法
      }
      }

      实际上是可以删除 @Override 注解的,因为它不是强制要求的。

2. 抽象类

  • 抽象类定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public abstract class Animal {
    // 抽象方法(无实现)
    public abstract void sound();

    // 具体方法(有实现)
    public void eat() {
    System.out.println("Animal is eating");
    }
    }

    public class Dog extends Animal {
    @Override
    public void sound() {
    System.out.println("Woof");
    }
    }
  • 知识点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1.抽象类不能直接实例化(不能用 new AbstractClass() 创建对象),只能作为父类被子类继承。
    补充:可通过“子类实例化+向上转型”的方式使用抽象类(如 AbstractAnimal animal = new Dog())。
    2.成员组成:
    抽象方法:用 abstract 修饰,无方法体(以分号结尾),表示“待子类实现的方法”,抽象类中可以有 0 个或多个抽象方法;不能被 private、final、static 修饰(会限制子类重写)。
    普通方法:有完整方法体,供子类直接继承复用,无需子类重写(子类可按需重写,重写时遵循方法重写通用规则,访问权限不能比父类更严格)。
    成员变量:支持普通成员变量(可修改,属于对象实例)、静态变量(static,属于抽象类本身,可通过类名直接访问),有默认初始化值。
    构造器:存在构造器(用于子类初始化时,调用父类构造器初始化抽象类的成员变量),但不能用于实例化抽象类本身;子类构造器会默认隐式调用抽象类无参构造器(super()),若抽象类只有有参构造器,子类必须显式调用(super(参数))且放在第一行。
    3.继承规则:
    类继承抽象类时,要么自身也声明为抽象类(可遗留抽象方法,未实现的方法向下传递),要么必须实现抽象类的所有抽象方法。
    Java 是单继承机制,一个子类只能继承一个抽象类(该规则适用于所有Java类,一个类仅有一个直接父类)。

3. 接口

  • 接口定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public interface Animal {
    // 抽象方法(无实现)
    void sound();

    // 默认方法(有实现)
    default void eat() {
    System.out.println("Animal is eating");
    }
    }

    public class Dog implements Animal {
    @Override
    public void sound() {
    System.out.println("Woof");
    }
    }
    //接口的使用通过类实例使用
    Animal myPet = new Dog(); // ✅ 正确!myPet 是 Animal 接口类型,实际对象是 Dog
    myPet.sound(); // 输出: Woof!
  • 知识点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1. 接口是一种引用类型,用于定义行为规范(抽象方法)。
    2. 接口不能直接实例化(不能用 new Interface() 创建对象),只能作为类型被实现或被其他接口继承。
    3. java8之前:
    接口可以包含常量(默认 public static final)和抽象方法(默认 public abstract)。
    4. java8及以后:
    在3的基础上,接口可以包含默认方法(default 修饰,有方法体)和静态方法(static 修饰,有方法体)。
    注意:默认方法和静态方法不能使用 public abstract 修饰符(二者有方法体,与抽象方法特性冲突);接口的抽象方法仍默认携带 public abstract 修饰符,可省略不写。
    5. 类实现接口时,必须实现接口中的所有抽象方法,否则类必须声明为抽象类。
    6. 接口可以继承其他接口,形成接口的层次结构。

4. 抽象类与接口的联系总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1.  抽象类可以没有抽象方法(但通常会有普通方法);即使无抽象方法,抽象类依然无法直接实例化。
2. 抽象方法不能被 private/final/static 修饰(冲突特性);核心原因:这三个修饰符会限制子类重写抽象方法(private不可访问、final不可重写、static不属于实例无法重写)。
3. 抽象类的构造器不能私有化(否则无法被继承)。
4. 抽象类可作为方法参数 / 返回值(体现多态);实际传入/返回的是抽象类的子类实例(向上转型)。
5. 抽象类可以继承抽象类:
子抽象类可选择性实现父抽象类的抽象方法(实现部分、实现全部、一个都不实现都可以),未实现的抽象方法会向下传递,由最终的非抽象子类统一实现。
从中也可以反映:抽象类可以有非抽象方法。
6. 抽象类可以实现接口的部分方法:
抽象类可以实现接口的部分方法(即实现接口的抽象方法),甚至可以一个都不实现;未实现的方法可以在抽象类中保持抽象状态,继续向下传递。
7. 普通类必须实现接口的所有抽象方法,否则类必须声明为抽象类。
8. 普通类继承抽象类时,必须实现抽象类的所有抽象方法,否则类必须声明为抽象类;此处的“所有抽象方法”包括抽象类自身定义的、继承父抽象类遗留的、实现接口传递下来的所有未完成抽象方法。
9. 类(普通类,内部类,抽象类)只能单继承,但可以实现多个接口。
10. 接口与抽象类的抽象方法:
接口:
1. 抽象方法默认修饰符是 public abstract,可省略不写(三种写法等价:void method(); / public void method(); / public abstract void method();)。
2. 有且仅有 public abstract 这一种修饰符组合,不允许使用 private、protected 等其他任何修饰符,否则编译报错。
3. 子类重写该抽象方法时,必须使用 public 修饰符(权限不能更严格)。
抽象类:
1. abstract 修饰符必须手动指定(不写则不是抽象方法),无默认的 public abstract/protected abstract 组合。
2. 访问修饰符支持 2 种:public、protected(可手动指定);若不指定任何访问修饰符,默认是「包访问权限(default 权限,无关键字)」,非 protected。
3. 不支持的修饰符:private、final、static(与 abstract 互斥,编译报错)。
4. 子类重写该抽象方法时,访问权限不能比父类更严格(如父类 protected → 子类 protected/public;父类 public → 子类只能 public)。

六、异常处理解析

1. 异常体系结构

  • Checked Exception vs Unchecked Exception
    类型 特点 举例 处理要求
    Checked Exception 编译时检查 IOException, SQLException 必须捕获或声明抛出
    Unchecked Exception 运行时异常 NullPointerException, IllegalArgumentException 可选处理

2. 异常处理最佳实践

  • try-with-resources(JDK7+)

    1
    2
    3
    4
    5
    try (FileInputStream fis = new FileInputStream("file.txt")) {
    // 自动关闭资源
    } catch (IOException e) {
    e.printStackTrace();
    }
  • 异常链式处理

    1
    2
    3
    4
    5
    try {
    // 可能抛出异常的代码
    } catch (IOException e) {
    throw new RuntimeException("处理失败", e); // 保留原始异常
    }
  • 自定义异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class CustomException extends Exception {
    public CustomException(String message) {
    super(message);
    }
    }

    // 使用
    if (invalidData) {
    throw new CustomException("数据无效");
    }

七、内部类解析

1. 四种内部类详解

类型 定义位置 访问外部类成员 实例化方式 特点
成员内部类 类中方法外 ✅ 可直接访问 Outer.Inner in = new Outer().new Inner(); 需外部类实例
静态内部类 类中方法外+static ❌ 只能访问静态成员 Outer.StaticInner si = new Outer.StaticInner(); 独立于外部类
局部内部类 方法内 ✅ 可访问final局部变量 new Inner();(仅在方法内) 作用域仅限方法
匿名内部类 无名,直接实例化 new Interface() { ... } 适用于单次使用
  • 成员内部类示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Outer {
    private int outerField = 10;

    public class Inner {
    public void display() {
    System.out.println(outerField); // 直接访问外部类私有成员
    }
    }
    }

    // 使用
    Outer outer = new Outer();
    Outer.Inner inner = outer.new Inner();
    inner.display();
  • 匿名内部类典型应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 创建线程
    new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("Running");
    }
    }).start();

    // Java 8+ Lambda写法
    new Thread(() -> System.out.println("Running")).start();

八、文件I/O与NIO.2

1. 传统IO流体系

类型 抽象类 具体实现 用途
字节流 InputStream/OutputStream FileInputStream/FileOutputStream 二进制数据
字符流 Reader/Writer FileReader/FileWriter 文本数据
缓冲流 BufferedInputStream/BufferedOutputStream BufferedInputStream/BufferedWriter 提高读写效率
转换流 InputStreamReader/OutputStreamWriter InputStreamReader/OutputStreamWriter 字节→字符转换
  • 文件读写示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 读取文本文件
    try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
    System.out.println(line);
    }
    }

    // 写入文本文件(追加模式)
    try (BufferedWriter bw = new BufferedWriter(new FileWriter("file.txt", true))) {
    bw.write("New line");
    }

2. NIO.2核心特性

  • Path与Files工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Path path = Paths.get("file.txt");

    // 读取所有行
    List<String> lines = Files.readAllLines(path);

    // 写入文件
    Files.write(path, "New content".getBytes(), StandardOpenOption.CREATE);

    // 复制文件
    Files.copy(Paths.get("source.txt"), Paths.get("dest.txt"), StandardCopyOption.REPLACE_EXISTING);
  • 异步文件操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("file.txt"), StandardOpenOption.READ);
    ByteBuffer buffer = ByteBuffer.allocate(1024);

    channel.read(buffer, 0, null, new CompletionHandler<Integer, Object>() {
    @Override
    public void completed(Integer result, Object attachment) {
    System.out.println("Read " + result + " bytes");
    }
    @Override
    public void failed(Throwable exc, Object attachment) {
    exc.printStackTrace();
    }
    });

九、泛型

类型擦除原理

1
2
3
4
5
6
7
8
9
10
11
12
List<String> list = new ArrayList<>();
list.add("Hello");

// 编译后实际为:
List list = new ArrayList();
list.add("Hello"); // 类型信息被擦除


List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true
// 因为擦除后,两者都是 ArrayList 类型,JVM 无法区分它们的泛型参数
  • 泛型基础
    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
    // 无标签的盒子:什么都能装
    List box = new ArrayList();
    box.add("苹果"); // ✅ 水果
    box.add(123); // ✅ 编译通过!但这是数字,不是水果!
    box.add(new Car()); // ✅ 编译通过!但这是汽车,不是水果!

    // 取出时需要手动类型转换
    String fruit = (String) box.get(0); // ✅ 正常
    int num = (int) box.get(1); // ❌ 运行时错误!int 不能直接转 Object
    int num = (Integer) box.get(1); // 编译通过,但运行时可能崩溃!所以还是要特定的泛型存储特定的类型
    Car car = (Car) box.get(2); // ✅ 正常


    // 带“水果标签”的盒子:只能装 String 类型
    List<String> fruitBox = new ArrayList<>();
    fruitBox.add("苹果"); // ✅ 符合标签:String 类型
    fruitBox.add(123); // ❌ 编译直接报错!123 不是 String 类型
    fruitBox.add(new Car());// ❌ 编译直接报错!Car 不是 String 类型
    String fruit = fruitBox.get(0); // 取出时无需强制转换


    // 用泛型定义“只能装 Integer 的盒子”
    List<Integer> numberBox = new ArrayList<>();
    numberBox.add(123); // ✅ 编译通过(123 自动装箱为 Integer)
    // numberBox.add("abc"); // ❌ 编译报错!类型不匹配
    int num = numberBox.get(0); // // 取出时直接得到 int(自动拆箱)


绕过类型擦除获取泛型类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TypeReference<T> {
private final Type type;

protected TypeReference() {
Type superclass = getClass().getGenericSuperclass();
type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}

public Type getType() {
return type;
}
}

// 使用
TypeReference<List<String>> ref = new TypeReference<>() {};
System.out.println(ref.getType()); // 输出:java.util.List<java.lang.String>

十、Lambda表达式(java 8+)

Lambda 表达式是 Java 8 引入的函数式编程特性,用于简化匿名内部类的写法。它的核心本质是:

将“行为”作为参数传递,让代码更简洁、更易读。

Lambda 表达式通用语法

1
(参数列表) -> { 代码块 }
  • (参数列表):函数的输入参数(可省略类型,由编译器自动推断)
  • ->:箭头操作符(固定写法)
  • { 代码块 }:函数体(单行表达式可省略 {}return

语法简化规则

1️⃣ 完整版(带类型和代码块)

1
2
3
4
// 完整写法:参数类型明确 + 代码块 + return
(int a, int b) -> {
return a + b;
}

2️⃣ 省略参数类型(编译器自动推断)

1
2
3
4
// 省略参数类型:a, b 的类型由上下文推断为 int
(a, b) -> {
return a + b;
}

3️⃣ 单参数省略括号

1
2
// 单参数时可省略括号
x -> x * 2 // 等价于 (x) -> { return x * 2; }

4️⃣ 单行表达式省略 return 和 {}

1
2
// 单行表达式可省略 return 和 {}
x -> x * 2 // 等价于 x -> { return x * 2; }

5️⃣ 无参数时用空括号

1
2
() -> System.out.println("Hello") 
// 等价于 new Runnable() { public void run() { System.out.println("Hello"); } }

经典场景示例

场景 1:函数式接口实现

1
2
3
4
5
6
7
8
9
10
11
12
// 定义一个函数式接口(只有一个抽象方法)
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}

// Lambda 实现
MathOperation add = (a, b) -> a + b; // 简化版
MathOperation multiply = (a, b) -> a * b; // 单行表达式

System.out.println(add.operate(2, 3)); // 5
System.out.println(multiply.operate(2, 3)); // 6

场景 2:Stream API 过滤

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
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 过滤偶数:x -> x % 2 == 0
List<Integer> evenNumbers = numbers.stream()
.filter(x -> x % 2 == 0) // 单参数 + 单行表达式
.collect(Collectors.toList());

System.out.println(evenNumbers); // [2, 4]

--------------


List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 过滤偶数,平方,求和
int sum = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.reduce(0, Integer::sum);

// 并行流处理(大数据量加速)
int parallelSum = numbers.parallelStream()
.mapToInt(n -> n * n)
.sum();


场景 3:多参数 + 代码块

1
2
3
4
5
6
7
8
9
// 需要多行逻辑时用代码块
Comparator<Integer> comparator = (a, b) -> {
if (a > b) return 1;
else if (a < b) return -1;
else return 0;
};

// 用法
System.out.println(comparator.compare(3, 5)); // -1

场景 4:无参数(如 Runnable)

1
2
3
4
5
6
7
8
9
10
// 创建线程
new Thread(() -> System.out.println("Running")).start();

// 等价于传统匿名内部类:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Running");
}
}).start();

关键注意事项

规则 说明 示例
必须是函数式接口 Lambda 只能用于 只有一个抽象方法的接口(用 @FunctionalInterface 标注) Runnable, Comparator, Predicate
访问外部变量必须是 final 或 effectively final Lambda 内部不能修改外部变量(只能读取) int x = 10;
() -> System.out.println(x); // ✅
x = 20; // ❌ 编译错误
不能声明新变量 Lambda 表达式中不能声明新变量(如 int y = 5; x -> { int y = x; return y; } // ❌ 编译错误
方法引用是 Lambda 的简写 :: 操作符引用已有方法 System.out::println 等价于 x -> System.out.println(x)

Lambda语法总结

1
(参数列表) -> { 表达式或代码块 }
  • 参数列表:可省略类型,单参数省略括号
  • 箭头 ->:固定符号
  • 函数体:单行表达式省略 {}return

十一、Optional类使用

Optional 类深度解析:彻底告别 NPE(空指针异常)

核心理念
Optional 是 Java 8 引入的「容器类」,用于显式表达「值可能存在也可能不存在」,强制开发者处理空值场景,从根源上杜绝 NullPointerException


为什么需要 Optional

传统空值处理的痛点

1
2
3
4
5
6
7
8
9
// 传统方式:到处写 null 检查,代码臃肿且易遗漏
String name = null;
if (user != null) {
if (user.getAddress() != null) {
if (user.getAddress().getCity() != null) {
name = user.getAddress().getCity().getName();
}
}
}
  • 问题
    • 代码冗长、可读性差
    • 容易遗漏检查(导致运行时 NPE)
    • null 语义不明确:无法区分「故意为 null」还是「忘记处理」

Optional 的解决方案

1
2
3
4
5
6
// 使用 Optional 链式调用,语义清晰且安全
String name = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.map(City::getName)
.orElse("未知城市");
  • 优势
    • 强制处理空值:编译器要求你显式处理 null 场景
    • 链式调用:避免多层嵌套 if
    • 语义明确Optional 本身表示「可能为空」,代码自解释

Optional 核心方法

1️⃣ 创建 Optional 实例

方法 说明 示例 适用场景
Optional.of(T value) 包装非 null 值 Optional.of("Hello") 值一定不为 null(否则抛 NullPointerException
Optional.ofNullable(T value) 包装可能为 null 的值 Optional.ofNullable(user) 值可能为 null(安全创建)
Optional.empty() 创建空 Optional Optional.empty() 显式表示「无值」

2️⃣ 安全获取值

方法 说明 示例 风险
T get() 直接获取值 optional.get() ⚠️ 若为空则抛 NoSuchElementException(不推荐!)
T orElse(T other) 有值返回值,无值返回默认值 optional.orElse("default") 安全(推荐)
T orElseGet(Supplier supplier) 有值返回值,无值执行 Supplier 生成默认值 optional.orElseGet(() -> generateDefault()) 延迟计算(默认值创建成本高时用)
T orElseThrow(Supplier exceptionSupplier) 有值返回值,无值抛异常 optional.orElseThrow(() -> new CustomException()) 需显式处理异常

3️⃣ 转换值(避免嵌套)

方法 说明 示例 作用
Optional<U> map(Function mapper) 对值应用函数(返回新 Optional) optional.map(String::toUpperCase) 链式调用(如 user.getAddress().getCity()
Optional<U> flatMap(Function mapper) 对值应用返回 Optional 的函数 optional.flatMap(User::getAddress) 值本身是 Optional 时使用

4️⃣ 条件处理(无值/有值时操作)

方法 说明 示例 作用
void ifPresent(Consumer consumer) 值存在时执行操作 optional.ifPresent(name -> System.out.println(name)) 仅需处理非空场景
void ifPresentOrElse(Consumer, Runnable) 有值执行 Consumer,无值执行 Runnable optional.ifPresentOrElse(System.out::println, () -> System.out.println("空值")) 完整处理空/非空场景
Optional<T> filter(Predicate predicate) 过滤值(不符合条件则变空) optional.filter(name -> name.length() > 3) 验证值是否符合规则

传统 vs Optional

场景 1:嵌套对象取值

1
2
3
4
5
6
7
8
9
10
11
12
// 传统方式(易遗漏 null 检查)
String city = "未知";
if (user != null && user.getAddress() != null && user.getAddress().getCity() != null) {
city = user.getAddress().getCity().getName();
}

// Optional 方式(编译器强制处理,安全简洁)
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.map(City::getName)
.orElse("未知");

场景 2:从 Map 中安全取值

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

// 传统方式(需两次 null 检查)
String value = null;
if (config != null && config.get("timeout") != null) {
value = config.get("timeout");
}

// Optional 方式(一行代码解决)
String value = Optional.ofNullable(config)
.map(map -> map.get("timeout"))
.orElse("30s");

场景 3:处理数据库查询结果(可能为空)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 传统方式(冗长且易错)
User user = userRepository.findById(100);
if (user != null) {
// 处理 user
} else {
// 处理空
}

// Optional 方式(语义清晰)
Optional<User> userOpt = userRepository.findById(100);
userOpt.ifPresent(user -> {
// 处理 user
});
userOpt.orElseThrow(() -> new UserNotFoundException("ID 100 不存在"));

Optional 使用规则

误区 正确做法 原因
❌ 用 Optional 作为类的成员变量 仅用于方法返回值 Optional 不可序列化,且违背设计初衷(字段应明确表示是否可空)
❌ 使用 optional.get() 永远用 orElse/orElseGet/orElseThrow get() 会抛异常,和直接用 null 无区别
❌ 用 Optional 包装集合 集合返回空集合(Collections.emptyList() 集合的空值语义应由空集合表示,而非 Optional
❌ 用 Optional 替代基础类型判断 用基本类型特化版本(OptionalInt/OptionalDouble 基本类型包装会损失性能(OptionalInt 无装箱开销)

💡 关键原则
Optional 是方法返回值的「语义增强器」,不是 null 的替代品!

  • 方法返回 Optional<T> → 明确告诉调用者:「这个值可能为空」
  • 方法返回 T → 明确告诉调用者:「这个值一定不为空」

使用事例

正确使用场景

1
2
3
4
5
6
7
8
9
10
11
// 方法返回 Optional(明确表示可能为空)
public Optional<User> findUserById(int id) {
User user = userRepository.findById(id);
return Optional.ofNullable(user); // 显式表示可能为空
}

// 调用方安全处理
Optional<User> userOpt = findUserById(100);
userOpt.ifPresent(user -> {
// 处理 user
});

链式调用示例

1
2
3
4
5
6
7
8
9
10
// 从配置中获取用户默认语言(可能为空)
String lang = Optional.ofNullable(config)
.map(c -> c.get("language"))
.filter(s -> !s.isEmpty())
.orElse("zh-CN");

// 从订单中获取最后修改时间(可能为空)
LocalDateTime time = Optional.ofNullable(order)
.map(Order::getModifiedTime)
.orElse(LocalDateTime.now());

避免过度使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 错误示范:用 Optional 包装非空字段
public class User {
private Optional<String> name; // 不要这样!
}

// 正确做法:字段用 null 表示空,方法返回 Optional
public class User {
private String name; // 允许为 null

public Optional<String> getName() {
return Optional.ofNullable(name);
}
}

总结

传统代码 Optional 代码 本质差异
String name = user.getName(); String name = Optional.ofNullable(user).map(User::getName).orElse("Unknown"); 强制显式处理空值:编译器要求你写 orElse/ifPresent 等,否则无法编译通过
if (user != null) { ... } userOpt.ifPresent(u -> { ... }); 代码结构明确ifPresent 语义清晰,避免嵌套 if

十二、Java 8+ 新特性详解

1. 日期时间API(java.time)

  • 核心类

    用途 示例
    LocalDate 日期(年月日) LocalDate.now()
    LocalTime 时间(时分秒) LocalTime.of(10, 30)
    LocalDateTime 日期+时间 LocalDateTime.now()
    ZonedDateTime 带时区的日期时间 ZonedDateTime.now(ZoneId.of("Asia/Shanghai"))
    Duration 时间段(秒/纳秒) Duration.between(start, end)
    Period 日期段(年月日) Period.between(date1, date2)
  • 日期时间格式化

    1
    2
    3
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime now = LocalDateTime.now();
    String formatted = now.format(formatter);

2. 并发工具类

  • CompletableFuture

    1
    2
    3
    4
    CompletableFuture.supplyAsync(() -> {
    return "Result";
    }).thenApply(result -> result.toUpperCase())
    .thenAccept(System.out::println);
  • StampedLock(读写锁优化):

    1
    2
    3
    4
    5
    6
    7
    StampedLock lock = new StampedLock();
    long stamp = lock.readLock();
    try {
    // 读操作
    } finally {
    lock.unlockRead(stamp);
    }

十三、JVM性能调优关键点

1. 内存参数配置

参数 说明 示例
-Xms 初始堆大小 -Xms512m
-Xmx 最大堆大小 -Xmx2g
-XX:NewRatio 新生代/老年代比例 -XX:NewRatio=2
-XX:MaxMetaspaceSize 元空间最大大小 -XX:MaxMetaspaceSize=256m
-XX:+UseG1GC 启用G1垃圾回收器 -XX:+UseG1GC

2. 常见内存问题诊断

  • OutOfMemoryError

    • 堆内存溢出java.lang.OutOfMemoryError: Java heap space
      • 解决:增大-Xmx,检查内存泄漏
    • 元空间溢出java.lang.OutOfMemoryError: Metaspace
      • 解决:增大-XX:MaxMetaspaceSize
    • 栈溢出java.lang.StackOverflowError
      • 解决:增大-Xss
  • 内存泄漏诊断工具

    • jmap -heap <pid>:查看堆内存使用
    • jstat -gc <pid> 1000:实时监控GC情况
    • jvisualvm:图形化分析内存快照

十四、设计模式的应用

1. 单例模式(双重检查锁)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static volatile Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

2. 工厂模式(简单工厂)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Shape {
void draw();
}

public class Circle implements Shape {
public void draw() { System.out.println("Circle"); }
}

public class ShapeFactory {
public Shape getShape(String type) {
if ("circle".equals(type)) return new Circle();
if ("rectangle".equals(type)) return new Rectangle();
return null;
}
}

3. 观察者模式(Java内置实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Observable;
import java.util.Observer;

class Subject extends Observable {
private int state;

public void setState(int state) {
this.state = state;
setChanged(); // 标记已更改
notifyObservers(state); // 通知所有观察者
}
}

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

十五、反射机制深度解析

1. 反射核心API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 获取Class对象
Class<?> clazz = Class.forName("com.example.MyClass");
// 或
Class<?> clazz = MyClass.class;

// 创建实例
Object obj = clazz.getDeclaredConstructor().newInstance();

// 调用方法
Method method = clazz.getMethod("myMethod", String.class);
method.invoke(obj, "param");

// 访问私有字段
Field field = clazz.getDeclaredField("privateField");
field.setAccessible(true);
field.set(obj, "value");

2. 反射应用场景

  • 框架开发:Spring IOC依赖注入
  • 动态代理:JDK动态代理
  • 序列化/反序列化:JSON库(如Jackson)
  • 单元测试:访问私有成员

3. 反射性能优化

1
2
3
4
5
6
7
8
9
10
// 1. 缓存Method对象
private static final Method METHOD = MyClass.class.getMethod("method");

// 2. 使用MethodHandle(JDK7+)
MethodHandle handle = MethodHandles.lookup().findVirtual(MyClass.class, "method", MethodType.methodType(void.class));
handle.invokeExact(obj);

// 3. 使用VarHandle(JDK9+)进行字段访问
VarHandle handle = MethodHandles.lookup().findVarHandle(MyClass.class, "field", int.class);
handle.set(obj, 10);

十六、JVM内存模型(JMM)

1. 主内存与工作内存

2. 关键内存屏障

指令 作用 使用场景
LoadLoad 确保加载指令顺序 读取共享变量前
StoreStore 确保存储指令顺序 写入共享变量后
LoadStore 确保加载在存储前完成 读写混合操作
StoreLoad 最强屏障,防止重排序 volatile写后读

3. volatile关键字原理

  • 保证可见性:写入时刷新到主内存,读取时从主内存加载
  • 禁止指令重排序:通过内存屏障实现
  • 不保证原子性:如i++操作(包含读-改-写三步)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 正确使用volatile
private volatile boolean initialized = false;

// 线程1
void init() {
// 初始化操作
initialized = true; // 写入volatile变量
}

// 线程2
void use() {
if (initialized) { // 读取volatile变量
// 可以安全使用初始化数据
}
}

十七、Java并发编程核心

1. synchronized底层原理

  • 对象头结构
    1
    [Mark Word][Class Pointer][Lock Record]
  • 锁升级过程
    1
    无锁 → 偏向锁 → 轻量级锁 → 重量级锁
  • 锁消除(JIT优化):
    1
    2
    3
    4
    5
    6
    // 以下代码会被优化为无锁
    public void method() {
    synchronized(new Object()) {
    // 无共享数据
    }
    }

2. Lock接口与AQS

  • ReentrantLock实现
    1
    2
    3
    4
    5
    6
    7
    Lock lock = new ReentrantLock();
    lock.lock();
    try {
    // 临界区
    } finally {
    lock.unlock();
    }
  • AQS(AbstractQueuedSynchronizer)核心
    • 维护一个CLH队列(FIFO)
    • 通过CAS操作修改state字段
    • 支持公平锁/非公平锁

3. 并发工具类详解

用途 示例
CountDownLatch 计数器,等待多个线程完成 new CountDownLatch(3)
CyclicBarrier 循环屏障,等待固定数量线程 new CyclicBarrier(3)
Semaphore 信号量,控制并发数 new Semaphore(5)
Exchanger 线程间数据交换 exchanger.exchange(data)
Phaser 动态注册的屏障 phaser.register()