Java 基础核心总结 📚

Java 是一门面向对象的编程语言,由 Sun 公司于 1995 年发布。它具有跨平台(一次编写,到处运行)、安全性高(没有指针,垃圾自动回收)、生态丰富(开源框架众多)等特点。本文将系统总结 Java 基础知识体系,帮助大家巩固核心概念,建立完整的知识框架。


📖 目录

  1. Java 程序运行流程
  2. 数据类型
  3. 面向对象基础
  4. 集合框架
  5. 多线程基础
  6. I/O 流
  7. 泛型
  8. 反射机制

Java 程序运行流程

什么是字节码?

Java 代码最终不会直接编译成机器码,而是编译成一种中间形式的字节码(.class 文件)。这种字节码不能被任何操作系统直接执行,但可以被 Java 虚拟机(JVM)解释执行或即时编译(JIT)成机器码。

这样做的好处是:同一个 .class 文件可以在任何安装了 JVM 的操作系统上运行,这正是 Java 跨平台的核心原理。

整体执行流程

Java 程序的执行流程如下:

各阶段详细说明

阶段 组件 具体做什么
编写 开发者 编写 .java 源文件
编译 JavaC 将 .java 翻译成 .class 字节码
加载 ClassLoader 把 .class 文件加载到内存中
验证 字节码验证器 检查字节码是否符合 JVM 规范
执行 JVM 执行引擎 解释执行或 JIT 编译执行
运行 OS + Hardware 最终在具体硬件上运行

JVM 内存划分简介

JVM 在执行程序时,会把内存划分成几个区域来管理不同类型的数据:

第一个 Java 程序

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

代码解析:

  • public class HelloWorld — 定义一个公开的类,类名必须与文件名相同
  • public static void main(String[] args) — 程序入口方法,JVM 从这里开始执行
  • System.out.println(...) — 向控制台输出内容并换行

将上述代码保存为 HelloWorld.java,在命令行执行:

1
2
javac HelloWorld.java   # 编译
java HelloWorld # 运行

数据类型

为什么需要数据类型?

计算机内存中存储的是二进制数据,但不同类型的数据占用的空间不同、表示的意义也不同。数据类型就是对数据的一种分类,告诉 JVM 应该以什么方式存储和处理这些数据。

Java 中的数据类型分为两大类:基本数据类型引用数据类型。基本数据类型存储的是具体的值,而引用数据类型存储的是对象在内存中的地址(引用)。

两大类型分类

基本数据类型(8种)

Java 定义了 8 种基本数据类型,它们是 Java 语言的基础组成部分。

类型 关键字 占用空间 取值范围 默认值 示例
字节型 byte 1 字节 -128 ~ 127 0 byte b = 100;
短整型 short 2 字节 -32768 ~ 32767 0 short s = 1000;
整型 int 4 字节 -2³¹ ~ 2³¹-1(约21亿) 0 int i = 100000;
长整型 long 8 字节 -2⁶³ ~ 2⁶³-1 0L long l = 1000000L;
单精度浮点 float 4 字节 IEEE 754 标准 0.0f float f = 3.14f;
双精度浮点 double 8 字节 IEEE 754 标准 0.0d double d = 3.14159;
字符型 char 2 字节 Unicode 0 ~ 65535 ‘\u0000’ char c = 'A';
布尔型 boolean 1/4 字节 true / false false boolean flag = true;

使用建议

  • **整数类型优先使用 int**,如果数值可能超过 21 亿才用 long
  • **浮点类型优先使用 double**,因为 float 精度较低
  • long 类型数字字面量必须加 L 后缀,否则会当作 int 处理
  • float 类型数字字面量必须加 f 后缀,否则会当作 double 处理

类型转换详解

自动类型转换(隐式转换):容量小的类型可以自动转换为容量大的类型。

转换顺序如下:

强制类型转换(显式转换):容量大的类型需要强制转换,可能丢失精度。

代码示例

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
public class DataTypes {
public static void main(String[] args) {
// ============ 整型 ============
int age = 25; // 普通整数,默认 int 类型
long population = 7800000000L; // 超大整数必须加 L

// ============ 浮点型 ============
double pi = 3.1415926; // double 是 Java 浮点型的默认类型
float gravity = 9.8f; // float 必须加 f 后缀

// ============ 字符型 ============
char grade = 'A'; // 单引号包裹单个字符
char chinese = '\u4e2d'; // Unicode 编码表示中文"中"

// ============ 布尔型 ============
boolean isStudent = true; // true 或 false,不能用 0/1 代替
boolean hasJob = false;

// ============ 自动类型转换 ============
double d = age; // int 自动转换为 double
// 转换过程:int(25) -> double(25.0)
System.out.println("自动转换:int 25 转 double = " + d);

// ============ 强制类型转换 ============
int i = (int) 3.14; // double 强制转为 int,小数部分丢失
System.out.println("强制转换:double 3.14 转 int = " + i); // 输出 3

// 强制转换可能丢失精度的情况
long bigNum = 1000000L;
int smallNum = (int) bigNum; // 安全,因为 long 在 int 范围内
System.out.println("安全转换:" + smallNum);

// ============ 常见错误 ============
// float f = 3.14; // 错误!3.14 默认是 double
float f = 3.14f; // 正确写法

// long l = 1000000; // 1000000 默认是 int,可能溢出
long l = 1000000L; // 正确写法
}
}

面向对象基础

什么是面向对象?

面向对象(OOP - Object Oriented Programming) 是一种编程思想,它把现实世界中的事物抽象成对象,用对象之间的关系来描述问题。

与之对应的是面向过程编程,比如 C 语言,它更关注”一步一步怎么做”,而面向对象更关注”谁来做这件事”。

为什么要面向对象?

想象一个场景:我们需要描述一个学生管理系统。

面向过程思考方式:

  1. 定义学生的学号、姓名、成绩等变量
  2. 编写函数处理学生数据:添加学生、删除学生、查询成绩
  3. 用数组或链表存储所有学生

面向对象思考方式:

  1. 定义一个 Student 类,封装学号、姓名、成绩
  2. 定义一个 StudentManager 类,管理所有学生对象
  3. 操作变成了:manager.addStudent(s1)s1.getScore()

面向对象的优势在于:代码更易维护、复用性更高、更符合人类思维习惯

面向对象三大特性

1. 封装

封装是把属性和方法包装在一起,对外提供接口,隐藏内部实现细节。这就像一台电视,我们只需要知道怎么用遥控器,不需要知道内部的电路原理。

封装的两个核心要点:

  • 属性私有化:用 private 修饰属性,不让外部直接访问
  • 提供公共方法:通过 public 的 getter/setter 允许外部访问和修改
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
public class Person {
// ============ 私有属性 ============
// private 修饰符表示只能在 Person 类内部访问
private String name;
private int age;

// ============ 构造方法 ============
// 无参构造
public Person() {
}

// 有参构造
public Person(String name, int age) {
this.name = name;
// 通过 setAge 方法赋值,可以利用其中的校验逻辑
setAge(age);
}

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

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

public int getAge() {
return age;
}

// setAge 方法中可以加入数据校验
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数!");
}
if (age > 150) {
throw new IllegalArgumentException("年龄不合理!");
}
this.age = age;
}
}

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class EncapsulationTest {
public static void main(String[] args) {
Person person = new Person("张三", 25);
System.out.println("姓名:" + person.getName());
System.out.println("年龄:" + person.getAge());

// 尝试设置非法年龄
try {
person.setAge(-5); // 会抛出异常
} catch (IllegalArgumentException e) {
System.out.println("捕获异常:" + e.getMessage());
}

System.out.println("最终年龄:" + person.getAge()); // 仍是 25
}
}

2. 继承

继承是面向对象的核心特性之一,它允许我们创建一个新类(子类)来继承另一个类(父类),子类可以复用父类的属性和方法,还可以扩展自己的功能。

为什么要继承?

  • 代码复用:子类可以直接使用父类的代码
  • 建立类层次:体现类之间的”is-a”关系(比如”狗 is a 动物”)
  • 多态基础:没有继承就没有多态
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
// ============ 父类:动物 ============
public class Animal {
// protected 修饰符:允许子类访问,但对外隐藏
protected String name;

public Animal(String name) {
this.name = name;
}

// 动物都会吃,但具体吃什么由子类决定
public void eat() {
System.out.println(name + " 正在吃东西...");
}

// 动物都会睡觉
public void sleep() {
System.out.println(name + " 正在睡觉...");
}
}

// ============ 子类:狗 ============
public class Dog extends Animal {
// 狗类独有的属性
private String breed; // 品种

// 构造方法
public Dog(String name, String breed) {
super(name); // super() 调用父类构造,必须放在第一行
this.breed = breed;
}

// 重写父类的 eat 方法
@Override
public void eat() {
System.out.println(name + "(品种:" + breed + ")正在吃狗粮...");
}

// 狗类独有的方法
public void bark() {
System.out.println(name + " 汪汪叫!");
}

// Getter
public String getBreed() {
return breed;
}
}

// ============ 子类:猫 ============
public class Cat extends Animal {
private boolean indoorCat; // 是否是家猫

public Cat(String name, boolean indoorCat) {
super(name);
this.indoorCat = indoorCat;
}

@Override
public void eat() {
System.out.println(name + " 正在吃猫粮...");
}

// 猫独有的方法
public void meow() {
System.out.println(name + " 喵呜~");
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class InheritanceTest {
public static void main(String[] args) {
Dog dog = new Dog("旺财", "金毛");
Cat cat = new Cat("咪咪", true);

// 调用从父类继承的方法
dog.sleep();
cat.sleep();

// 调用子类重写的方法
dog.eat(); // 输出:旺财(品种:金毛)正在吃狗粮...
cat.eat(); // 输出:咪咪正在吃猫粮...

// 调用子类独有的方法
dog.bark(); // 输出:旺财汪汪叫!
cat.meow(); // 输出:咪咪喵呜~

System.out.println("\n--- 类型信息 ---");
System.out.println("dog 是 Dog 类实例:" + (dog instanceof Dog));
System.out.println("dog 是 Animal 类实例:" + (dog instanceof Animal));
System.out.println("dog 是 Cat 类实例:" + (dog instanceof Cat));
}
}

3. 多态

多态是面向对象的三大特性之一,指的是同一个方法调用在不同对象上有不同的行为。多态让程序具有更好的扩展性和灵活性。

多态的两种形式:

(1)方法重载(编译时多态)

同一个类中,方法名相同但参数列表不同,编译器根据参数决定调用哪个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MathHelper {
// 打印整数
public void print(int num) {
System.out.println("整数:" + num);
}

// 重载:打印字符串
public void print(String str) {
System.out.println("字符串:" + str);
}

// 重载:打印小数
public void print(double num) {
System.out.println("小数:" + num);
}

// 重载:打印多个整数
public void print(int... nums) {
System.out.println("多个整数:" + java.util.Arrays.toString(nums));
}
}

(2)方法重写 + 父类引用指向子类对象(运行时多态)

这是多态最典型的应用场景:父类引用可以指向子类对象,调用方法时会执行子类的实现。

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
public class PolymorphismTest {
public static void main(String[] args) {
// ============ 向上转型 ============
// 父类引用 animal 指向子类对象 dog
// 这是安全的,因为 Dog is an Animal
Animal animal = new Dog("旺财", "金毛");

// 运行时多态:调用的是 Dog 重写后的 eat()
animal.eat();
// 输出:旺财(品种:金毛)正在吃狗粮...

// animal.sleep() 从父类继承,行为不变
animal.sleep();
// 输出:旺财正在睡觉...

// 注意:animal 是 Animal 类型,不能调用 Dog 独有的 bark() 方法
// animal.bark(); // 编译错误!

// ============ 向下转型 ============
// 向下转型需要强制转换,并且要确保类型正确
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 强制转换为 Dog 类型
dog.bark(); // 现在可以调用 Dog 独有的方法了
}

// ============ 使用场景:方法参数 ============
// 这是一个典型的多态应用
feedAnimal(new Dog("旺财", "金毛")); // 传入狗
feedAnimal(new Cat("咪咪", true)); // 传入猫

// ============ 使用场景:集合 ============
List<Animal> animals = new ArrayList<>();
animals.add(new Dog("狗1", "哈士奇"));
animals.add(new Cat("猫1", false));
animals.add(new Dog("狗2", "拉布拉多"));

// 遍历时体现出多态
for (Animal a : animals) {
a.eat(); // 每种动物吃的方式不同
}
}

// 参数类型是父类,可以接受任意子类
public static void feedAnimal(Animal animal) {
System.out.println("开始喂食...");
animal.eat(); // 调用的是实际类型的方法
System.out.println("喂食完成!\n");
}
}

集合框架

为什么需要集合?

数组是我们最基本的数据结构,但它有两个明显的限制:

  1. 长度固定:创建后不能改变大小
  2. 类型单一:只能存储同一种类型的数据(虽然有 Object[] 但使用不便)

为了解决这些问题,Java 提供了集合框架(Collection Framework),它是一套完善的接口和类,用于存储和操作一组对象。

集合框架继承体系

List 接口 — 有序可重复

List 是一个有序的集合(也称为序列),可以精确控制每个元素的位置,通过索引访问元素,允许重复元素。

ArrayList 原理:内部用数组实现,通过索引访问时非常快(O(1)),但是在中间插入或删除元素时需要移动后面所有元素,效率较低。

LinkedList 原理:内部用双向链表实现,插入和删除元素很快(O(1)),但随机访问需要遍历链表,效率较低。

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
public class ListDemo {
public static void main(String[] args) {
// ============ ArrayList ============
// ArrayList 是 List 接口最常用的实现类
List<String> arrayList = new ArrayList<>();

// 添加元素
arrayList.add("Apple"); // 尾部添加
arrayList.add("Banana");
arrayList.add("Orange");
arrayList.add(1, "Grape"); // 指定索引位置插入

System.out.println("ArrayList 内容:" + arrayList);
System.out.println("大小:" + arrayList.size());
System.out.println("第二个元素(索引1):" + arrayList.get(1));
System.out.println("是否包含 Banana:" + arrayList.contains("Banana"));

// 修改元素
arrayList.set(0, "RedApple");
System.out.println("修改后:" + arrayList);

// 删除元素
arrayList.remove("Banana"); // 按内容删除
arrayList.remove(0); // 按索引删除
System.out.println("删除后:" + arrayList);

System.out.println("\n--- ArrayList 适用场景 ---");
System.out.println("✅ 频繁按索引访问元素");
System.out.println("✅ 主要是遍历查找,少量增删");
System.out.println("❌ 大量在中间位置插入/删除");

// ============ LinkedList ============
// LinkedList 同时实现了 List 和 Deque 接口
LinkedList<String> linkedList = new LinkedList<>();

linkedList.add("First");
linkedList.addFirst("Zero"); // 头部插入
linkedList.addLast("Last"); // 尾部插入
linkedList.add(2, "Middle"); // 中间插入

System.out.println("\nLinkedList 内容:" + linkedList);
System.out.println("第一个:" + linkedList.getFirst());
System.out.println("最后一个:" + linkedList.getLast());

// LinkedList 作为队列使用
linkedList.poll(); // 弹出头部元素
System.out.println("poll 后:" + linkedList);

System.out.println("\n--- LinkedList 适用场景 ---");
System.out.println("✅ 频繁在头尾插入/删除");
System.out.println("✅ 作为队列、栈使用");
System.out.println("❌ 随机访问(按索引查找)");
}
}

Set 接口 — 无序不重复

Set 是一个不包含重复元素的集合,更精确地说,Set 中不会有两个相等的元素。适合用于去重和集合运算。

HashSet 原理:基于哈希表实现,元素无序,查找/插入/删除效率都很高(平均 O(1))。

TreeSet 原理:基于红黑树实现,元素自动排序(需要元素可比较)。

LinkedHashSet 原理:基于哈希表 + 链表实现,保持元素插入顺序。

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
public class SetDemo {
public static void main(String[] args) {
// ============ HashSet ============
// 最常用的 Set 实现类,无序不重复
Set<String> hashSet = new HashSet<>();

hashSet.add("Java");
hashSet.add("Python");
hashSet.add("C++");
hashSet.add("Java"); // 重复元素,不会添加成功
hashSet.add(null); // 可以存储一个 null

System.out.println("HashSet 大小:" + hashSet.size()); // 4
System.out.println("HashSet 内容:" + hashSet); // 顺序可能每次不同
System.out.println("是否包含 Java:" + hashSet.contains("Java"));

// ============ TreeSet ============
// 元素自动排序(自然顺序)
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(30);
treeSet.add(10);
treeSet.add(20);
treeSet.add(40);
treeSet.add(25);

System.out.println("\nTreeSet 内容:" + treeSet); // [10, 20, 25, 30, 40] 自动排序

// TreeSet 支持范围查询
System.out.println("小于 25 的元素:" + treeSet.headSet(25));
System.out.println("大于等于 25 的元素:" + treeSet.tailSet(25));

// ============ LinkedHashSet ============
// 保持插入顺序
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("First");
linkedHashSet.add("Second");
linkedHashSet.add("Third");
linkedHashSet.add("First"); // 重复,忽略

System.out.println("\nLinkedHashSet 内容:" + linkedHashSet); // [First, Second, Third]

// ============ 去重演示 ============
System.out.println("\n--- 去重功能演示 ---");
List<String> names = Arrays.asList("Alice", "Bob", "Alice", "Charlie", "Bob");
Set<String> uniqueNames = new HashSet<>(names);
System.out.println("原始列表:" + names);
System.out.println("去重后:" + uniqueNames);

// ============ 集合运算 ============
Set<String> set1 = new HashSet<>(Arrays.asList("A", "B", "C", "D"));
Set<String> set2 = new HashSet<>(Arrays.asList("C", "D", "E", "F"));

// 并集
Set<String> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("\n并集:" + union); // [A, B, C, D, E, F]

// 交集
Set<String> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("交集:" + intersection); // [C, D]

// 差集
Set<String> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("差集:" + difference); // [A, B]
}
}

Map 接口 — 键值对

Map 存储键值对(key-value)映射关系,其中 key 不能重复,每个 key 最多对应一个 value。Map 不是 Collection 的子接口,它是独立的接口家族。

HashMap 原理:基于哈希表实现,key 无序,查找/插入/删除效率高。

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
public class MapDemo {
public static void main(String[] args) {
// ============ HashMap ============
// 最常用的 Map 实现类
Map<String, Integer> hashMap = new HashMap<>();

// 添加键值对
hashMap.put("语文", 90);
hashMap.put("数学", 95);
hashMap.put("英语", 88);
hashMap.put("物理", 92);

// key 重复时,value 会覆盖
hashMap.put("数学", 100); // 数学成绩更新为 100

System.out.println("HashMap 内容:" + hashMap);
System.out.println("数学成绩:" + hashMap.get("数学")); // 100
System.out.println("化学成绩:" + hashMap.get("化学")); // null,不存在

// 判断操作
System.out.println("是否包含 key '数学':" + hashMap.containsKey("数学"));
System.out.println("是否包含 value 90:" + hashMap.containsValue(90));

// 删除
hashMap.remove("英语");
System.out.println("删除后:" + hashMap);

// ============ 遍历方式 ============
System.out.println("\n--- 遍历方式 ---");

// 方式1:遍历 key
System.out.println("所有学科:");
for (String subject : hashMap.keySet()) {
System.out.println(" " + subject);
}

// 方式2:遍历 value
System.out.println("所有成绩:");
for (Integer score : hashMap.values()) {
System.out.println(" " + score);
}

// 方式3:遍历键值对(最常用)
System.out.println("键值对:");
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
System.out.println(" " + entry.getKey() + " = " + entry.getValue());
}

// 方式4:Lambda 表达式(Java 8+)
System.out.println("Lambda 遍历:");
hashMap.forEach((k, v) -> System.out.println(" " + k + " -> " + v));

// ============ 其他 Map 实现 ============
// TreeMap:按键排序
Map<String, Integer> treeMap = new TreeMap<>(hashMap);
System.out.println("\nTreeMap(按键排序):" + treeMap);

// LinkedHashMap:保持插入顺序
Map<String, Integer> linkedMap = new LinkedHashMap<>();
linkedMap.put("first", 1);
linkedMap.put("second", 2);
linkedMap.put("third", 3);
System.out.println("LinkedHashMap(保序):" + linkedMap);
}
}

各类集合对比

集合类型 线程安全 底层数据结构 元素是否有序 适用场景
ArrayList ❌ 否 数组 按索引 随机访问多,增删少
LinkedList ❌ 否 双向链表 按插入顺序 增删操作频繁
HashSet ❌ 否 哈希表 无序 去重,不关心顺序
LinkedHashSet ❌ 否 哈希表+链表 插入顺序 去重且需保持顺序
TreeSet ❌ 否 红黑树 自然排序 需要排序的去重
HashMap ❌ 否 哈希表 无序 键值对存储(最常用)
LinkedHashMap ❌ 否 哈希表+链表 插入顺序 需要按插入顺序遍历
TreeMap ❌ 否 红黑树 按 key 排序 需要按键排序的映射
Hashtable ✅ 是 哈希表 无序 旧版本,现已被 ConcurrentHashMap 取代
ConcurrentHashMap ✅ 是 分段锁哈希表 无序 高并发场景

多线程基础

什么是线程?

进程(Process) 是程序的一次执行过程,是系统分配资源的基本单位。每个进程都有独立的内存空间。

线程(Thread) 是进程中的一个执行单元,是 CPU 调度的最小单位。一个进程可以包含多个线程,这些线程共享进程的内存空间。

为什么使用多线程?

  • 提高效率:多核 CPU 可以真正并行执行多个任务
  • 阻塞不影响:一个线程阻塞时,其他线程可以继续执行
  • 提升响应:可以将耗时操作放到后台,主线程保持响应

线程生命周期

各状态说明:

状态 含义
New(新建) 创建了线程对象,但还没调用 start()
Runnable(就绪) 调用了 start(),等待 CPU 分配时间片
Running(运行) 获得了 CPU 执行权,正在执行 run()
Blocked(阻塞) 等待获取锁、sleep、wait 等
Dead(死亡) run() 执行完毕或抛出未捕获异常

创建线程的方式

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
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
// ============ 方式一:继承 Thread 类 ============
// 缺点:Java 是单继承,继承了 Thread 就不能再继承其他类
class MyThread extends Thread {
@Override
public void run() {
// 线程要执行的代码
for (int i = 0; i < 5; i++) {
System.out.println("线程1 - 第 " + i + " 次执行");
try {
Thread.sleep(100); // 休眠 100 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// ============ 方式二:实现 Runnable 接口(推荐) ============
// 优点:只是实现了接口,还可以继承其他类
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("线程2 - 第 " + i + " 次执行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// ============ 方式三:实现 Callable 接口 + FutureTask ============
// 优点:有返回值,可以抛出异常,可以取消任务
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
// 模拟耗时操作
Thread.sleep(500);
return sum; // 返回结果
}
}

// ============ 测试类 ============
public class ThreadCreateDemo {
public static void main(String[] args) throws Exception {
// 方式一
MyThread thread1 = new MyThread();
thread1.start(); // 注意:必须调用 start(),不是 run()

// 方式二
Thread thread2 = new Thread(new MyRunnable());
thread2.start();

// 方式三
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
Thread thread3 = new Thread(futureTask);
thread3.start();

// 获取 Callable 的返回值
Integer result = futureTask.get(); // 会阻塞等待结果
System.out.println("Callable 计算结果:" + result);

// 等待所有线程结束
thread1.join();
thread2.join();
thread3.join();

System.out.println("所有线程执行完毕!");
}
}

线程同步

多线程带来了效率的提升,但也带来了新的问题:线程安全问题

当多个线程同时访问同一个资源(变量、文件、数据库等)时,可能会出现数据不一致的问题。

经典的线程不安全问题示例:

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
// 模拟抢票系统
public class TicketSeller implements Runnable {
private int tickets = 100; // 100 张票

@Override
public void run() {
while (tickets > 0) {
// 模拟出票耗时
try {
Thread.sleep(1); // 休眠 1 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}

// 关键问题:多个线程可能同时读到 tickets > 0
// 导致卖出同一张票,甚至卖出负数票
System.out.println(Thread.currentThread().getName()
+ " 卖出第 " + tickets + " 张票");
tickets--;
}
}

public static void main(String[] args) {
TicketSeller seller = new TicketSeller();
// 创建 10 个线程同时卖票
for (int i = 0; i < 10; i++) {
new Thread(seller, "窗口" + (i + 1)).start();
}
}
}

解决方案:使用 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
public class SafeTicketSeller implements Runnable {
private int tickets = 100;

// 同步方法:整个方法体被加锁
public synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 卖出第 " + tickets + " 张票");
tickets--;
}
}

@Override
public void run() {
while (tickets > 0) {
sellTicket(); // 调用同步方法
}
}

public static void main(String[] args) {
SafeTicketSeller seller = new SafeTicketSeller();
for (int i = 0; i < 10; i++) {
new Thread(seller, "窗口" + (i + 1)).start();
}
}
}

synchronized 同步原理:

  • 每个对象都有一把内部锁(Monitor Lock)
  • 当线程进入 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
72
73
74
75
76
77
78
public class ProducerConsumer {
private static final int CAPACITY = 10; // 缓冲区容量
private Queue<Integer> buffer = new LinkedList<>();
private int count = 0;

// 生产者方法
public synchronized void produce() throws InterruptedException {
while (buffer.size() >= CAPACITY) {
// 缓冲区满了,生产者等待
System.out.println("缓冲区已满,生产者等待...");
wait();
}

count++;
buffer.offer(count);
System.out.println("生产者放入第 " + count + " 个产品"
+ ",缓冲区大小:" + buffer.size());

// 唤醒消费者
notifyAll();
}

// 消费者方法
public synchronized void consume() throws InterruptedException {
while (buffer.isEmpty()) {
// 缓冲区空,消费者等待
System.out.println("缓冲区为空,消费者等待...");
wait();
}

int product = buffer.poll();
System.out.println("消费者取出第 " + product + " 个产品"
+ ",缓冲区大小:" + buffer.size());

// 唤醒生产者
notifyAll();
}

public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();

// 生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 20; i++) {
pc.produce();
Thread.sleep((long) (Math.random() * 500));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者");

// 消费者线程
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 20; i++) {
pc.consume();
Thread.sleep((long) (Math.random() * 800));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者");

producer.start();
consumer.start();

try {
producer.join();
consumer.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("所有产品已处理完毕!");
}
}

I/O 流

什么是 I/O?

I/O 是 Input/Output 的缩写,即输入输出。在 Java 中,I/O 用于程序与外部数据源之间的数据交换,包括:

  • 读取文件(Input)
  • 写入文件(Output)
  • 网络通信
  • 标准输入输出

I/O 分类

何时用字节流,何时用字符流?

  • 字节流:用于处理二进制数据,如图片、音频、视频、压缩文件等
  • 字符流:用于处理文本数据,按字符读取,更适合处理文字

文件读写示例

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
public class FileIODemo {

// ============ 字节流:复制图片 ============
// 适用于二进制文件(图片、音视频等)
public static void copyImage() throws IOException {
String source = "source.jpg";
String dest = "dest.jpg";

long start = System.currentTimeMillis();

// try-with-resources:自动关闭流
try (InputStream is = new FileInputStream(source);
OutputStream os = new FileOutputStream(dest)) {

// 使用缓冲数组提高效率
byte[] buffer = new byte[8192]; // 8KB 缓冲区
int bytesRead;

// 循环读取直到文件结束(read 返回 -1 表示读完)
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead); // 只写入实际读取的字节数
}

System.out.println("图片复制完成!");
}

long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
}

// ============ 字符流:读写文本文件 ============
// 适用于纯文本文件
public static void readWriteText() throws IOException {
String inputFile = "input.txt";
String outputFile = "output.txt";

// 基本字符流
try (Reader reader = new FileReader(inputFile);
Writer writer = new FileWriter(outputFile)) {

char[] buffer = new char[1024];
int length;

while ((length = reader.read(buffer)) != -1) {
writer.write(buffer, 0, length);
}

System.out.println("文本文件复制完成!");
}
}

// ============ BufferedReader:按行读取(推荐) ============
// BufferedReader 提供了 readLine() 方法,按行读取更方便
public static void readByLine() throws IOException {
String file = "log.txt";

try (BufferedReader br = new BufferedReader(
new FileReader(file), 8192)) { // 指定缓冲区大小

String line;
int lineNumber = 0;

// readLine() 返回 null 表示文件结束
while ((line = br.readLine()) != null) {
lineNumber++;
System.out.println(lineNumber + ": " + line);
}
}
}

// ============ BufferedWriter:带缓冲写入 ============
public static void bufferedWrite() throws IOException {
String file = "output.txt";

try (BufferedWriter bw = new BufferedWriter(
new FileWriter(file))) {

bw.write("第一行内容");
bw.newLine(); // 写入换行符

bw.write("第二行内容");
bw.newLine();

bw.write("第三行内容");

bw.flush(); // 刷新缓冲区,将数据立即写入文件
System.out.println("写入完成!");
}
}

// ============ Scanner:简单场景首选 ============
// 适合小文件的简单读取
public static void scannerRead() {
String file = "data.txt";

try (Scanner scanner = new Scanner(new File(file))) {
scanner.useDelimiter(","); // 指定分隔符

while (scanner.hasNext()) {
if (scanner.hasNextInt()) {
System.out.println("整数:" + scanner.nextInt());
} else if (scanner.hasNextDouble()) {
System.out.println("小数:" + scanner.nextDouble());
} else {
System.out.println("字符串:" + scanner.next());
}
}
} catch (FileNotFoundException e) {
System.out.println("文件不存在:" + e.getMessage());
}
}
}

对象序列化

序列化是将对象转换为字节序列的过程,用于将对象保存到文件或在网络上传输。反序列化是序列化的逆过程。

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
// ============ 可序列化类 ============
class Person implements Serializable {
// serialVersionUID:版本号,序列化/反序列化时需要匹配
private static final long serialVersionUID = 1L;

private String name;
private int age;

// transient 修饰的字段不会被序列化
// 常用于敏感信息(密码等)或不需要持久化的数据
private transient String password;

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

@Override
public String toString() {
return "Person{name='" + name + "', age=" + age
+ ", password='" + password + "'}";
}
}

// ============ 序列化与反序列化 ============
public class SerializationDemo {
private static final String FILE = "person.dat";

public static void main(String[] args) {
// 序列化:对象 -> 文件
Person person = new Person("张三", 25, "123456");

try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(FILE))) {
oos.writeObject(person);
System.out.println("序列化成功!对象:" + person);
} catch (IOException e) {
e.printStackTrace();
}

// 反序列化:文件 -> 对象
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(FILE))) {
Person p = (Person) ois.readObject();
System.out.println("反序列化成功!对象:" + p);
// 注意:password 会是 null,因为被 transient 修饰
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

泛型

什么是泛型?

泛型是 Java 5 引入的一个新特性,它的本质是参数化类型。我们可以把泛型理解成一种”类型的占位符”,在编写代码时不指定具体类型,而在使用时再确定类型。

为什么需要泛型?

举一个实际例子:假设我们需要一个容器来装东西。

没有泛型时(使用 Object):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用 Object 类型的容器
public class ObjectBox {
private Object content;

public void set(Object content) {
this.content = content;
}

public Object get() {
return content;
}
}

// 使用时需要强制类型转换,容易出错
ObjectBox box = new ObjectBox();
box.set("Hello");
String str = (String) box.get(); // 必须强制转换

box.set(123); // 可以放入任何类型
Integer num = (Integer) box.get(); // 如果之前放的是 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
// 使用泛型的容器
public class GenericBox<T> { // T 是类型参数
private T content;

public void set(T content) {
this.content = content;
}

public T get() {
return content; // 无需强制转换
}
}

// 使用时指定具体类型
GenericBox<String> stringBox = new GenericBox<>();
stringBox.set("Hello");
String str = stringBox.get(); // 无需强制转换,类型安全

GenericBox<Integer> intBox = new GenericBox<>();
intBox.set(123);
Integer num = intBox.get(); // 无需强制转换

// 编译阶段就会报错,无法放入错误类型
// stringBox.set(123); // 编译错误!

泛型的优势:

  1. 类型安全:编译时检查类型,防止 ClassCastException
  2. 消除强制类型转换:代码更简洁
  3. 代码复用:一套代码可以处理多种类型

泛型体系

泛型类与泛型接口

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
89
90
91
92
93
94
95
96
97
// ============ 泛型类 ============
// Box<T> 中的 T 被称为类型参数(Type Parameter)
public class Box<T> {
private T content;

public void set(T content) {
this.content = content;
}

public T get() {
return content;
}
}

// 多个类型参数
public class Pair<K, V> {
private K key;
private V value;

public Pair(K key, V value) {
this.key = key;
this.value = value;
}

public K getKey() { return key; }
public V getValue() { return value; }
}

// ============ 泛型接口 ============
public interface Container<K, V> {
void put(K key, V value);
V get(K key);
boolean contains(K key);
}

// 实现泛型接口时,可以指定具体类型
class StringIntegerContainer implements Container<String, Integer> {
private Map<String, Integer> map = new HashMap<>();

@Override
public void put(String key, Integer value) {
map.put(key, value);
}

@Override
public Integer get(String key) {
return map.get(key);
}

@Override
public boolean contains(String key) {
return map.containsKey(key);
}
}

// 也可以继续使用泛型
class GenericContainer<K, V> implements Container<K, V> {
private Map<K, V> map = new HashMap<>();

@Override
public void put(K key, V value) {
map.put(key, value);
}

@Override
public V get(K key) {
return map.get(key);
}

@Override
public boolean contains(K key) {
return map.containsKey(key);
}
}

// ============ 使用示例 ============
public class GenericDemo {
public static void main(String[] args) {
// 泛型类使用
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
System.out.println("Box 内容:" + stringBox.get());

Box<Integer> intBox = new Box<>();
intBox.set(123);
System.out.println("Box 内容:" + intBox.get());

// 键值对使用
Pair<String, Integer> pair = new Pair<>("语文", 90);
System.out.println("键值对:" + pair.getKey() + " = " + pair.getValue());

// 泛型接口使用
Container<String, Integer> container = new StringIntegerContainer();
container.put("数学", 95);
System.out.println("容器内容:" + container.get("数学"));
}
}

泛型方法

泛型方法可以在普通类中定义,不需要类本身是泛型的。

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
public class GenericMethodDemo {

// ============ 基本泛型方法 ============
// <T> 表示这是一个泛型方法,T 是类型参数
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}

// ============ 返回类型也是泛型 ============
public static <T> int findIndex(T[] array, T target) {
for (int i = 0; i < array.length; i++) {
if (array[i].equals(target)) {
return i;
}
}
return -1;
}

// ============ 多个类型参数 ============
public static <K, V> Map<K, V> createMap(K key, V value) {
Map<K, V> map = new HashMap<>();
map.put(key, value);
return map;
}

// ============ 限制类型上限 ============
// <T extends Number> 表示 T 必须是 Number 或其子类
public static <T extends Number> double sum(T a, T b) {
return a.doubleValue() + b.doubleValue();
}

// ============ 比较器示例 ============
// <T extends Comparable<T>> 确保 T 可以比较
public static <T extends Comparable<T>> T findMax(T a, T b) {
if (a.compareTo(b) > 0) {
return a;
}
return b;
}

public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] strArray = {"A", "B", "C"};

// 泛型方法调用
printArray(intArray); // 输出:1 2 3 4 5
printArray(strArray); // 输出:A B C

// 查找索引
int index = findIndex(intArray, 3);
System.out.println("3 在数组中的索引:" + index); // 2

// 创建 Map
Map<String, Integer> map = createMap("年龄", 25);
System.out.println("Map:" + map);

// 类型限制
System.out.println("求和:" + sum(1, 2.5)); // 3.5

// 最大值
System.out.println("最大值:" + findMax(10, 20)); // 20
System.out.println("最大值:" + findMax("apple", "banana")); // banana
}
}

泛型通配符

泛型通配符用于泛型方法的参数中,提供更灵活的类型处理。

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
public class WildcardDemo {

// ============ <? extends T> 上限通配符 ============
// 表示未知类型是 T 或 T 的子类,只能读取(作为生产者)
public static void printNumbers(List<? extends Number> list) {
// 可以读取,但类型未知
for (Number num : list) {
System.out.println(num + " ");
}

// 不能写入任何东西!因为不知道具体是 Integer 还是 Double
// list.add(1); // 编译错误!
// list.add(1.0); // 编译错误!
}

// ============ <? super T> 下限通配符 ============
// 表示未知类型是 T 或 T 的父类,只能写入(作为消费者)
public static void addNumbers(List<? super Integer> list) {
// 可以写入 Integer 或其子类
list.add(1);
list.add(2);
list.add(Integer.valueOf(3));

// 读取时只能当 Object 处理
// Number num = list.get(0); // 编译错误!
Object obj = list.get(0); // 只能当 Object 处理
}

// ============ <?> 无界通配符 ============
// 表示未知类型,可以读写,但类型不安全需要自己注意
public static void printAnyList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}

// 可以写入 null
// list.add("string"); // 编译错误!
list.add(null);

// 读取时只能当 Object 处理
Object element = list.get(0);
}

public static void main(String[] args) {
// 准备测试数据
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
List<Number> numberList = Arrays.asList(1, 2.0, 3L);
List<Object> objectList = Arrays.asList("a", "b", "c");

// <? extends Number> 可以接受 Integer、Double、Number
System.out.println("=== printNumbers ===");
printNumbers(intList);
printNumbers(doubleList);

// <? super Integer> 可以接受 Integer、Number、Object
System.out.println("\n=== addNumbers ===");
addNumbers(intList);
addNumbers(numberList);
System.out.println("添加后的 Integer 列表:" + intList);

// <?> 可以接受任何类型
System.out.println("\n=== printAnyList ===");
printAnyList(intList);
printAnyList(objectList);
}
}

通配符记忆口诀:

  • <? extends T> — “我是 T 的消费者,只能读,不能写”(Producer Extends)
  • <? super T> — “我是 T 的生产者,只能写,不能读”(Consumer Super)

反射机制

什么是反射?

反射(Reflection) 是 Java 的一个强大特性,它允许程序在运行时动态地获取类的信息、创建对象、调用方法和访问属性。

正常情况下,我们在编译时就知道要使用哪个类:

1
2
String str = new String("hello");  // 编译时就知道是 String
str.substring(0, 2); // 编译时就知道有 substring 方法

但有些场景下,我们在编译时不知道要操作哪个类,比如:

  • 开发框架(Spring、Hibernate)需要在运行时加载类
  • 注解处理器需要在运行时读取注解信息
  • 动态代理、RPC 框架等

反射就是在程序运行时自我探索和操作的能力。

反射核心 API

获取 Class 对象

Class 对象是反射的入口,每个类在 JVM 中都只有一个 Class 对象。

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
public class ReflectDemo {
public static void main(String[] args) throws Exception {
String str = "Hello";

// 方式一:使用 getClass() 方法
// 任何对象都有 getClass() 方法
Class<?> c1 = str.getClass();
System.out.println("方式一获取:" + c1.getName());

// 方式二:使用 Class.forName()
// 最常用,可以动态指定类名
Class<?> c2 = Class.forName("java.lang.String");
System.out.println("方式二获取:" + c2.getName());

// 方式三:使用 .class 属性
// 最简单直接,适用于已知类型的情况
Class<?> c3 = String.class;
System.out.println("方式三获取:" + c3.getName());

// 方式四:基本类型的 TYPE
// 每个基本类型都有一个包装类,包装类有 TYPE 属性
Class<?> c4 = Integer.TYPE;
System.out.println("Integer.TYPE:" + c4); // int

// 验证:同一个类的 Class 对象是同一个
System.out.println("\n=== 验证 Class 唯一性 ===");
System.out.println("c1 == c2:" + (c1 == c2)); // true
System.out.println("c2 == c3:" + (c2 == c3)); // true
}
}

操作类的结构

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
// 示例类
class Person {
public String name;
protected int age;
private String password;

public Person() {}

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

public void sayHello() {
System.out.println("你好,我是" + name);
}

private void privateMethod() {
System.out.println("这是私有方法");
}
}

public class ReflectStructure {
public static void main(String[] args) throws Exception {
Class<?> clazz = Person.class;

// ============ 获取类信息 ============
System.out.println("=== 类信息 ===");
System.out.println("完整类名:" + clazz.getName()); // 包名.类名
System.out.println("简单类名:" + clazz.getSimpleName()); // 只有类名
System.out.println("包名:" + clazz.getPackage()); // 所在包

// ============ 获取属性 ============
System.out.println("\n=== 属性信息 ===");
// getDeclaredFields() 获取所有声明的属性(包括私有)
for (Field field : clazz.getDeclaredFields()) {
// 获取修饰符(public、private 等)
String modifier = Modifier.toString(field.getModifiers());
// 获取类型名
String typeName = field.getType().getSimpleName();
System.out.println(modifier + " " + typeName + " " + field.getName());
}
// 输出:
// public String name
// protected int age
// private String password

// ============ 获取方法 ============
System.out.println("\n=== 方法信息 ===");
for (Method method : clazz.getDeclaredMethods()) {
String modifier = Modifier.toString(method.getModifiers());
String returnType = method.getReturnType().getSimpleName();
String methodName = method.getName();

// 获取参数类型
Class<?>[] paramTypes = method.getParameterTypes();
String params = Arrays.stream(paramTypes)
.map(Class::getSimpleName)
.collect(Collectors.joining(", "));

System.out.println(modifier + " " + returnType + " "
+ methodName + "(" + params + ")");
}

// ============ 获取构造方法 ============
System.out.println("\n=== 构造方法 ===");
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
String modifier = Modifier.toString(constructor.getModifiers());
String constructorName = constructor.getName();

Class<?>[] paramTypes = constructor.getParameterTypes();
String params = Arrays.stream(paramTypes)
.map(Class::getSimpleName)
.collect(Collectors.joining(", "));

System.out.println(modifier + " " + constructorName + "(" + params + ")");
}
}
}

反射创建对象与调用方法

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
public class ReflectInvoke {
public static void main(String[] args) throws Exception {
Class<?> clazz = Person.class;

// ============ 创建对象 ============
System.out.println("=== 创建对象 ===");

// 方式一:使用无参构造创建对象
Object obj1 = clazz.getDeclaredConstructor().newInstance();
System.out.println("无参构造创建:" + obj1);

// 方式二:使用有参构造创建对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj2 = constructor.newInstance("张三", 25);
System.out.println("有参构造创建:" + obj2);

// ============ 操作属性 ============
System.out.println("\n=== 操作属性 ===");

// 操作公共属性
Field nameField = clazz.getField("name");
nameField.set(obj2, "李四"); // 设置属性值
System.out.println("修改后的 name:" + nameField.get(obj2));

// 操作私有属性
Field passwordField = clazz.getDeclaredField("password");
passwordField.setAccessible(true); // 必须设置可访问,否则会抛异常
passwordField.set(obj2, "newPassword");
System.out.println("私有 password:" + passwordField.get(obj2));

// ============ 调用方法 ============
System.out.println("\n=== 调用方法 ===");

// 调用公共方法
Method sayHello = clazz.getMethod("sayHello");
sayHello.invoke(obj2); // 输出:你好,我是李四

// 调用重载方法:需要指定参数类型
// 假设 Person 有一个 sayHello(String greeting) 方法
// Method greetMethod = clazz.getMethod("sayHello", String.class);
// greetMethod.invoke(obj2, "早上好"); // 输出:早上好,我是李四

// 调用私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true); // 设置可访问
privateMethod.invoke(obj2); // 输出:这是私有方法

// ============ 动态调用示例 ============
System.out.println("\n=== 动态调用 ===");
// 假设我们要根据配置调用不同的方法
String methodName = "sayHello";
Method dynamicMethod = clazz.getMethod(methodName);
dynamicMethod.invoke(obj2);
}
}

反射优缺点

方面 说明
优点 动态加载类、框架基石(Spring/Hibernate)、运行时操作、极大灵活性
缺点 性能开销大(比直接调用慢数十倍)、破坏封装性、存在安全风险
使用建议 日常业务代码少用,框架和工具类中常用

反射的实际应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 场景一:Spring 依赖注入
// Spring 容器在启动时通过反射创建 Bean 并注入属性

// 场景二:JSON 序列化(Fastjson、Gson)
// 通过反射读取对象的属性,将它们转换为 JSON

// 场景三:注解处理器
// 通过反射读取类、方法、属性上的注解,做相应处理

// 场景四:通用对象克隆
public static <T> T deepClone(T obj) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
@SuppressWarnings("unchecked")
T clone = (T) ois.readObject();
return clone;
}

📌 总结

本文系统地总结了 Java 基础的核心知识点:

知识框架图

核心要点回顾

知识点 核心概念 关键点
数据类型 基本类型 vs 引用类型 自动转型、强制转型、精度丢失
面向对象 封装、继承、多态 private/protected/public、extends、override/overload
集合框架 List/Set/Map ArrayList/HashSet/HashMap 最常用
多线程 生命周期、同步机制 synchronized、wait/notify、线程安全
I/O 流 字节流、字符流、对象流 try-with-resources、自动关闭
泛型 类型参数化、安全检查 <? extends T>、<? super T>
反射 运行时动态操作 Class.forName、Method.invoke

纸上得来终觉浅,绝知此事要躬行 🚀

学习 Java 最好的方式就是多敲代码,遇到问题多思考。阅读源码(如 JDK 源码、Spring 源码)是提升内功的有效途径。


📅 本文首次发布于 2026 年 5 月 20 日