Java 基础核心总结 📚
Java 是一门面向对象的编程语言,由 Sun 公司于 1995 年发布。它具有跨平台 (一次编写,到处运行)、安全性高 (没有指针,垃圾自动回收)、生态丰富 (开源框架众多)等特点。本文将系统总结 Java 基础知识体系,帮助大家巩固核心概念,建立完整的知识框架。
📖 目录
Java 程序运行流程
数据类型
面向对象基础
集合框架
多线程基础
I/O 流
泛型
反射机制
Java 程序运行流程 什么是字节码? Java 代码最终不会直接编译成机器码,而是编译成一种中间形式的字节码(.class 文件)。这种字节码不能被任何操作系统直接执行,但可以被 Java 虚拟机(JVM)解释执行或即时编译(JIT)成机器码。
这样做的好处是:同一个 .class 文件可以在任何安装了 JVM 的操作系统上运行 ,这正是 Java 跨平台的核心原理。
整体执行流程 Java 程序的执行流程如下:
flowchart TD
A[.java 源文件] --> B[JavaC 编译]
B --> C[.class 字节码文件]
C --> D[类加载器 ClassLoader]
D --> E[字节码验证器]
E --> F[JVM 执行引擎]
F --> G[操作系统平台]
G --> H[硬件]
style A fill:#e1f5ff
style C fill:#fff3e0
style H fill:#e8f5e9
各阶段详细说明
阶段
组件
具体做什么
编写
开发者
编写 .java 源文件
编译
JavaC
将 .java 翻译成 .class 字节码
加载
ClassLoader
把 .class 文件加载到内存中
验证
字节码验证器
检查字节码是否符合 JVM 规范
执行
JVM 执行引擎
解释执行或 JIT 编译执行
运行
OS + Hardware
最终在具体硬件上运行
JVM 内存划分简介 JVM 在执行程序时,会把内存划分成几个区域来管理不同类型的数据:
flowchart TD
A[JVM 内存划分] --> B[堆 Heap]
A --> C[栈 Stack]
A --> D[方法区 Method Area]
A --> E[本地方法栈 Native Stack]
A --> F[程序计数器 PC]
B --> B1[对象实例]
B --> B2[数组]
C --> C1[方法调用]
C --> C2[局部变量]
C --> C3[操作数栈]
D --> D1[类信息]
D --> D2[常量池]
D --> D3[静态变量]
style B fill:#e8f5e9
style C fill:#e3f2fd
style D fill:#fff3e0
第一个 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 中的数据类型分为两大类:基本数据类型 和引用数据类型 。基本数据类型存储的是具体的值,而引用数据类型存储的是对象在内存中的地址(引用)。
两大类型分类
flowchart LR
subgraph 数据类型
A[数据类型] --> B[基本数据类型]
A --> C[引用数据类型]
B --> B1[整型]
B --> B2[浮点型]
B --> B3[字符型]
B --> B4[布尔型]
C --> C1[类]
C --> C2[接口]
C --> C3[数组]
end
style A fill:#fff3e0
style B fill:#e3f2fd
style C fill:#e8f5e9
基本数据类型(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 处理
类型转换详解 自动类型转换(隐式转换) :容量小的类型可以自动转换为容量大的类型。
转换顺序如下:
flowchart LR
A[byte] --> B[short]
B --> C[int]
C --> D[long]
D --> E[float]
E --> F[double]
A --> G[char]
G --> C
style A fill:#e3f2fd
style F fill:#e8f5e9
强制类型转换(显式转换) :容量大的类型需要强制转换,可能丢失精度。
代码示例 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 ; long population = 7800000000L ; double pi = 3.1415926 ; float gravity = 9.8f ; char grade = 'A' ; char chinese = '\u4e2d' ; boolean isStudent = true ; boolean hasJob = false ; double d = age; System.out.println("自动转换:int 25 转 double = " + d); int i = (int ) 3.14 ; System.out.println("强制转换:double 3.14 转 int = " + i); long bigNum = 1000000L ; int smallNum = (int ) bigNum; System.out.println("安全转换:" + smallNum); float f = 3.14f ; long l = 1000000L ; } }
面向对象基础 什么是面向对象? 面向对象(OOP - Object Oriented Programming) 是一种编程思想,它把现实世界中的事物抽象成对象,用对象之间的关系来描述问题。
与之对应的是面向过程 编程,比如 C 语言,它更关注”一步一步怎么做”,而面向对象更关注”谁来做这件事”。
为什么要面向对象? 想象一个场景:我们需要描述一个学生管理系统。
面向过程 思考方式:
定义学生的学号、姓名、成绩等变量
编写函数处理学生数据:添加学生、删除学生、查询成绩
用数组或链表存储所有学生
面向对象 思考方式:
定义一个 Student 类,封装学号、姓名、成绩
定义一个 StudentManager 类,管理所有学生对象
操作变成了:manager.addStudent(s1)、s1.getScore()
面向对象的优势在于:代码更易维护、复用性更高、更符合人类思维习惯 。
面向对象三大特性
flowchart TD
A[面向对象特性] --> B[封装 Encapsulation]
A --> C[继承 Inheritance]
A --> D[多态 Polymorphism]
B --> B1[访问修饰符]
B --> B2[getter/setter]
B --> B3[数据保护]
C --> C1[extends 关键字]
C --> C2[方法重写 @Override]
C --> C3[子类父类关系]
D --> D1[重载 Overload]
D --> D2[接口实现]
D --> D3[父类引用指向子类对象]
style A fill:#fff3e0
style B fill:#e3f2fd
style C fill:#e8f5e9
style D fill:#fce4ec
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 String name; private int age; public Person () { } public Person (String name, int age) { this .name = name; setAge(age); } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } 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()); } }
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 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); this .breed = breed; } @Override public void eat () { System.out.println(name + "(品种:" + breed + ")正在吃狗粮..." ); } public void bark () { System.out.println(name + " 汪汪叫!" ); } 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. 多态 多态 是面向对象的三大特性之一,指的是同一个方法调用在不同对象上有不同的行为。多态让程序具有更好的扩展性和灵活性。
多态的两种形式:
flowchart LR
A[多态] --> B[编译时多态 方法重载 Overload]
A --> C[运行时多态 方法重写 Override]
B --> B1[同类中方法名相同 参数列表不同]
C --> C1[父子类中方法签名相同 子类提供不同实现]
style A fill:#fff3e0
(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 animal = new Dog ("旺财" , "金毛" ); animal.eat(); animal.sleep(); if (animal instanceof Dog) { Dog dog = (Dog) animal; dog.bark(); } 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" ); } }
集合框架 为什么需要集合? 数组是我们最基本的数据结构,但它有两个明显的限制:
长度固定 :创建后不能改变大小
类型单一 :只能存储同一种类型的数据(虽然有 Object[] 但使用不便)
为了解决这些问题,Java 提供了集合框架(Collection Framework) ,它是一套完善的接口和类,用于存储和操作一组对象。
集合框架继承体系
flowchart TD
A[Collection 接口] --> B[List 接口]
A --> C[Set 接口]
A --> D[Queue 接口]
B --> B1[ArrayList 实现类]
B --> B2[LinkedList 实现类]
B --> B3[Vector 实现类]
C --> C1[HashSet 实现类]
C --> C2[LinkedHashSet 实现类]
C --> C3[TreeSet 实现类]
D --> D1[PriorityQueue 实现类]
D --> D2[LinkedList 实现类]
A --> E[Map 接口]
E --> E1[HashMap 实现类]
E --> E2[LinkedHashMap 实现类]
E --> E3[TreeMap 实现类]
E --> E4[Hashtable 实现类]
style A fill:#fff3e0
style E fill:#e8f5e9
style B fill:#e3f2fd
style C fill:#fce4ec
style D fill:#fff9c4
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) { 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<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.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) { Set<String> hashSet = new HashSet <>(); hashSet.add("Java" ); hashSet.add("Python" ); hashSet.add("C++" ); hashSet.add("Java" ); hashSet.add(null ); System.out.println("HashSet 大小:" + hashSet.size()); System.out.println("HashSet 内容:" + hashSet); System.out.println("是否包含 Java:" + hashSet.contains("Java" )); 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); System.out.println("小于 25 的元素:" + treeSet.headSet(25 )); System.out.println("大于等于 25 的元素:" + treeSet.tailSet(25 )); Set<String> linkedHashSet = new LinkedHashSet <>(); linkedHashSet.add("First" ); linkedHashSet.add("Second" ); linkedHashSet.add("Third" ); linkedHashSet.add("First" ); System.out.println("\nLinkedHashSet 内容:" + linkedHashSet); 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); Set<String> intersection = new HashSet <>(set1); intersection.retainAll(set2); System.out.println("交集:" + intersection); Set<String> difference = new HashSet <>(set1); difference.removeAll(set2); System.out.println("差集:" + difference); } }
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) { Map<String, Integer> hashMap = new HashMap <>(); hashMap.put("语文" , 90 ); hashMap.put("数学" , 95 ); hashMap.put("英语" , 88 ); hashMap.put("物理" , 92 ); hashMap.put("数学" , 100 ); System.out.println("HashMap 内容:" + hashMap); System.out.println("数学成绩:" + hashMap.get("数学" )); System.out.println("化学成绩:" + hashMap.get("化学" )); System.out.println("是否包含 key '数学':" + hashMap.containsKey("数学" )); System.out.println("是否包含 value 90:" + hashMap.containsValue(90 )); hashMap.remove("英语" ); System.out.println("删除后:" + hashMap); System.out.println("\n--- 遍历方式 ---" ); System.out.println("所有学科:" ); for (String subject : hashMap.keySet()) { System.out.println(" " + subject); } System.out.println("所有成绩:" ); for (Integer score : hashMap.values()) { System.out.println(" " + score); } System.out.println("键值对:" ); for (Map.Entry<String, Integer> entry : hashMap.entrySet()) { System.out.println(" " + entry.getKey() + " = " + entry.getValue()); } System.out.println("Lambda 遍历:" ); hashMap.forEach((k, v) -> System.out.println(" " + k + " -> " + v)); Map<String, Integer> treeMap = new TreeMap <>(hashMap); System.out.println("\nTreeMap(按键排序):" + treeMap); 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 调度的最小单位。一个进程可以包含多个线程,这些线程共享进程的内存空间。
flowchart TD
A[进程] --> B[内存空间 堆、方法区]
A --> C[线程1]
A --> D[线程2]
A --> E[线程3]
B --> F[共享数据]
C --> G[栈1]
D --> H[栈2]
E --> I[栈3]
style A fill:#fff3e0
style B fill:#e8f5e9
style C fill:#e3f2fd
style D fill:#e3f2fd
style E fill:#e3f2fd
为什么使用多线程?
提高效率 :多核 CPU 可以真正并行执行多个任务
阻塞不影响 :一个线程阻塞时,其他线程可以继续执行
提升响应 :可以将耗时操作放到后台,主线程保持响应
线程生命周期
stateDiagram-v2
[*] --> 新建: new Thread()
新建 --> 就绪: start() 被调用
就绪 --> 运行: 获得CPU执行权
运行 --> 就绪: yield() 时间片用完
运行 --> 阻塞: wait() sleep() join() I/O阻塞
阻塞 --> 就绪: notify() notifyAll() 时间到 join结束
运行 --> 死亡: run()执行完毕 未捕获异常
死亡 --> [*]
各状态说明:
状态
含义
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 class MyThread extends Thread { @Override public void run () { for (int i = 0 ; i < 5 ; i++) { System.out.println("线程1 - 第 " + i + " 次执行" ); try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } } } } 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(); } } } } 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(); Thread thread2 = new Thread (new MyRunnable ()); thread2.start(); FutureTask<Integer> futureTask = new FutureTask <>(new MyCallable ()); Thread thread3 = new Thread (futureTask); thread3.start(); 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 ; @Override public void run () { while (tickets > 0 ) { try { Thread.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 卖出第 " + tickets + " 张票" ); tickets--; } } public static void main (String[] args) { TicketSeller seller = new TicketSeller (); 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 方法时,会自动获取该对象的锁
其他线程尝试进入时会被阻塞,直到锁被释放
当方法执行完毕(正常或异常),锁会自动释放
生产者消费者问题 这是多线程经典问题:生产者生产数据,消费者消费数据,两者需要协调进行。
flowchart LR
A[生产者] -->|放入数据| B[缓冲区]
B -->|取出数据| C[消费者]
style B fill:#fff3e0
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 分类
flowchart TD
A[I/O 流] --> B[字节流]
A --> C[字符流]
B --> B1[InputStream 字节输入]
B --> B2[OutputStream 字节输出]
C --> C1[Reader 字符输入]
C --> C2[Writer 字符输出]
B1 --> B11[FileInputStream]
B1 --> B12[BufferedInputStream]
B1 --> B13[ObjectInputStream]
B2 --> B21[FileOutputStream]
B2 --> B22[BufferedOutputStream]
B2 --> B23[ObjectOutputStream]
C1 --> C11[FileReader]
C1 --> C12[BufferedReader]
C2 --> C21[FileWriter]
C2 --> C22[BufferedWriter]
style A fill:#fff3e0
何时用字节流,何时用字符流?
字节流 :用于处理二进制数据,如图片、音频、视频、压缩文件等
字符流 :用于处理文本数据,按字符读取,更适合处理文字
文件读写示例 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 (InputStream is = new FileInputStream (source); OutputStream os = new FileOutputStream (dest)) { byte [] buffer = new byte [8192 ]; int bytesRead; 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("文本文件复制完成!" ); } } public static void readByLine () throws IOException { String file = "log.txt" ; try (BufferedReader br = new BufferedReader ( new FileReader (file), 8192 )) { String line; int lineNumber = 0 ; while ((line = br.readLine()) != null ) { lineNumber++; System.out.println(lineNumber + ": " + line); } } } 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("写入完成!" ); } } 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 { private static final long serialVersionUID = 1L ; private String name; private int age; 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); } 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 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();
有泛型时:
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> { 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();
泛型的优势:
类型安全 :编译时检查类型,防止 ClassCastException
消除强制类型转换 :代码更简洁
代码复用 :一套代码可以处理多种类型
泛型体系
flowchart LR
A[泛型] --> B[泛型类 类定义时使用泛型]
A --> C[泛型接口 接口定义时使用泛型]
A --> D[泛型方法 方法定义时使用泛型]
A --> E[泛型通配符 灵活的类型限定]
style A fill:#fff3e0
泛型类与泛型接口 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 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 { 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; } public static <T extends Number > double sum (T a, T b) { return a.doubleValue() + b.doubleValue(); } 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); printArray(strArray); int index = findIndex(intArray, 3 ); System.out.println("3 在数组中的索引:" + index); Map<String, Integer> map = createMap("年龄" , 25 ); System.out.println("Map:" + map); System.out.println("求和:" + sum(1 , 2.5 )); System.out.println("最大值:" + findMax(10 , 20 )); System.out.println("最大值:" + findMax("apple" , "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 { public static void printNumbers (List<? extends Number> list) { for (Number num : list) { System.out.println(num + " " ); } } public static void addNumbers (List<? super Integer> list) { list.add(1 ); list.add(2 ); list.add(Integer.valueOf(3 )); Object obj = list.get(0 ); } public static void printAnyList (List<?> list) { for (Object obj : list) { System.out.println(obj); } list.add(null ); 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" ); System.out.println("=== printNumbers ===" ); printNumbers(intList); printNumbers(doubleList); 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)
flowchart LR
A[泛型通配符] --> B[extends T 上限 只读]
A --> C[super T 下限 只写]
A --> D[无界 ? 读写 Object]
style A fill:#fff3e0
反射机制 什么是反射? 反射(Reflection) 是 Java 的一个强大特性,它允许程序在运行时 动态地获取类的信息、创建对象、调用方法和访问属性。
正常情况下,我们在编译时就知道要使用哪个类:
1 2 String str = new String ("hello" ); str.substring(0 , 2 );
但有些场景下,我们在编译时不知道要操作哪个类 ,比如:
开发框架(Spring、Hibernate)需要在运行时加载类
注解处理器需要在运行时读取注解信息
动态代理、RPC 框架等
反射就是在程序运行时自我探索和操作的能力。
反射核心 API
flowchart LR
A[反射 API] --> B[Class 类本身]
A --> C[Field 属性]
A --> D[Method 方法]
A --> E[Constructor 构造方法]
B --> B1[getName]
B --> B2[newInstance]
B --> B3[getDeclaredFields]
C --> C1[get]
C --> C2[set]
C --> C3[setAccessible]
D --> D1[invoke]
D --> D2[getParameterTypes]
style A fill:#fff3e0
获取 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" ; Class<?> c1 = str.getClass(); System.out.println("方式一获取:" + c1.getName()); Class<?> c2 = Class.forName("java.lang.String" ); System.out.println("方式二获取:" + c2.getName()); Class<?> c3 = String.class; System.out.println("方式三获取:" + c3.getName()); Class<?> c4 = Integer.TYPE; System.out.println("Integer.TYPE:" + c4); System.out.println("\n=== 验证 Class 唯一性 ===" ); System.out.println("c1 == c2:" + (c1 == c2)); System.out.println("c2 == c3:" + (c2 == c3)); } }
操作类的结构 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=== 属性信息 ===" ); for (Field field : clazz.getDeclaredFields()) { String modifier = Modifier.toString(field.getModifiers()); String typeName = field.getType().getSimpleName(); System.out.println(modifier + " " + typeName + " " + field.getName()); } 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); 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 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 基础的核心知识点:
知识框架图
mindmap
root((Java 基础))
Java 程序运行流程
JavaC 编译
ClassLoader 加载
JVM 执行
字节码跨平台
数据类型
基本类型 8 种
引用类型
类型转换
面向对象
封装
继承
多态
集合框架
List
Set
Map
多线程
线程创建
线程同步
生产者消费者
I/O 流
字节流
字符流
对象序列化
泛型
泛型类
泛型方法
通配符
反射
获取 Class
操作属性
调用方法
核心要点回顾
知识点
核心概念
关键点
数据类型
基本类型 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 日