Java 面向对象
本文档整理 Java 面向对象编程的核心知识,包括类、继承、接口、多态、内部类、集合、多线程等概念。
目录
1. 访问权限
Java 内建的访问权限包括 public、protected、private 和 package(默认)四种:
| 修饰符 | 同类 | 同包 | 子类 | 任意位置 |
|---|---|---|---|---|
| public | ✅ | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ✅ | ❌ |
| private | ✅ | ❌ | ❌ | ❌ |
| package | ✅ | ✅ | ❌ | ❌ |
说明:
private字段子类无法访问;protected可被子类及子类的子类访问。
子类不会继承父类的构造方法,子类默认的构造方法由编译器自动生成。
2. 继承与转型
继承是面向对象的核心特性之一,用于表达"is-a"关系。通过继承,子类可以复用父类的代码,同时可以扩展或修改父类的行为。
2.1 继承关系
- 继承是 is 关系,组合是 has 关系
- Java 只允许单继承,所有类的最终根类是
Object
2.2 向上转型
把子类型安全地变成更抽象的父类型,自动转型,无需强制转换。
2.3 向下转型
将父类类型强制转为子类类型,可能出错,需使用 instanceof 判断:
Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false
Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true
Student n = null;
System.out.println(n instanceof Student); // false
instanceof判断变量指向的实例是否是指定类型或其子类。引用为null时始终返回false。
Person p = new Student();
if (p instanceof Student) {
Student s = (Student) p; // 转型安全
}
3. 多态
多态(Polymorphism) 是面向对象的三大特性之一(封装、继承、多态)。
Java 的实例方法调用基于运行时的实际类型(动态绑定),而非变量的声明类型。
多态:针对某类型的方法调用,其真正执行的方法取决于运行时期实际类型。
3.1 final
- 修饰方法:不能被 override
- 修饰类:不能被继承
4. 抽象类
抽象类是对一类事物的抽象,被设计成只能用于被继承,强迫子类实现其定义的抽象方法,否则编译报错。它定义了子类的"规范",确保子类必须实现某些方法。
abstract class Person {
public abstract void run();
public abstract String getName();
}
- 用
abstract定义的方法是抽象方法,只有定义没有实现 - 定义了抽象方法的类必须也是抽象类
- 子类必须实现抽象方法,否则仍是抽象类
4.1 面向抽象编程的本质
- 上层代码只定义规范
- 不需要子类即可实现业务逻辑
- 具体业务逻辑由子类实现,调用者不关心
5. 接口
接口(Interface) 是比抽象类更抽象的"纯抽象接口",它只定义行为规范,不包含任何实现细节。当抽象类中没有实例字段且所有方法都是抽象方法时,可以改写为接口。
interface Person {
void run();
String getName();
}
5.1 特点
- 接口是比抽象类更抽象的纯抽象接口,连字段都不能有
- 接口方法默认是
public abstract - 一个类只能继承一个类,但可以实现多个接口
5.2 接口继承
一个接口可以继承另一个接口,相当于扩展接口方法。
5.3 继承关系设计
- 公共逻辑放在
abstract class中 - 具体逻辑放到各个子类
- 接口层次代表抽象程度,接口比抽象类更抽象
5.4 default 方法
接口新增方法时,如果定义为 default 方法,实现类不必全部修改:
interface Payment {
void pay();
default void refund() { } // 新增方法不必强制实现
}
default方法和抽象类的普通方法不同:接口没有字段,default方法无法访问实例字段,而抽象类的普通方法可以。
5.5 示例:多态应用
interface Payment {
void pay();
}
class Alipay implements Payment {
public void pay() {
System.out.println("支付宝支付");
}
}
class WechatPay implements Payment {
public void pay() {
System.out.println("微信支付");
}
}
// 向上转型后,无需区分具体子类
List<Payment> list = new ArrayList<>();
list.add(new Alipay());
list.add(new WechatPay());
6. 静态字段与方法
静态成员(字段和方法)属于类本身,而非某个实例。所有实例共享同一份静态数据。
6.1 静态字段
- 静态字段只有一个共享空间,所有实例共享
- 修改任一实例的静态字段,效果对所有实例可见
- 推荐用类名访问:
ClassName.staticField
6.2 静态方法
- 无需实例即可通过类名调用
- 静态方法内无法访问
this或实例字段,只能访问静态字段
6.3 接口的静态字段
接口可以有静态字段,但必须是 final 类型(编译器自动添加 public static final):
public interface Person {
int MALE = 1; // 相当于 public static final int MALE = 1;
int FEMALE = 2;
}
7. 包
- 包(
package)解决命名冲突 - 完整类名 =
包名.类名 - 同一个包的类可以访问包作用域(默认权限)的成员
import other.package.ClassName; // 导入其他类
8. 内部类
内部类是定义在类内部的类,它可以访问外部类的所有成员(包括 private)。Java 的内部类分为三种,各有用途。
8.1 Inner Class(成员内部类)
内部类实例不能单独存在,必须依附于外部类实例:
class Outer {
private String name;
class Inner {
void hello() {
System.out.println("Hello, " + Outer.this.name);
}
}
}
实例化方式:
Outer outer = new Outer("Nested");
Outer.Inner inner = outer.new Inner();
编译后:
Outer.class、Outer$Inner.class
特点:
- 隐含持有
Outer.this实例 - 可访问外部类的
private字段和方法
8.2 Anonymous Class(匿名内部类)
不需要在外部类中明确声明,在方法内部通过匿名类定义:
void asyncHello() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, " + Outer.this.name);
}
};
new Thread(r).start();
}
编译后:
Outer$1.class
匿名类也可继承自普通类:
HashMap<String, String> map = new HashMap<>() {
{
put("A", "1");
put("B", "2");
}
};
8.3 Static Nested Class(静态内部类)
使用 static 修饰,不依附于外部类实例,是完全独立的类:
static class StaticNested {
void hello() {
System.out.println("Hello, " + Outer.NAME);
}
}
实例化方式:new Outer.StaticNested()
特点:
- 无法引用
Outer.this - 可访问外部类的
private静态字段和静态方法 - 不能访问外部类的实例字段
8.4 三种内部类对比
| 类型 | 是否依附外部类实例 | 可访问 Outer.this | 可访问实例成员 |
|---|---|---|---|
| Inner Class | 是 | ✅ | ✅ |
| Anonymous Class | 是 | ✅ | ✅ |
| Static Nested Class | 否 | ❌ | ❌ |
8.5 典型应用:单例模式
class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
外部类加载时 Holder 不会被加载,实现延迟加载,天然线程安全。
9. 枚举(enum)
枚举(Enumeration) 用于表示固定集合的类型,比使用常量更类型安全。枚举类本质上是一个特殊的类,继承自 java.lang.Enum。
enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}
9.1 特点
- 枚举类继承自
java.lang.Enum,无法被继承 - 只能定义枚举实例,无法通过
new创建 - 每个实例全局唯一
- 适合用于
switch语句
9.2 枚举类本质
public final class Weekday extends Enum {
public static final Weekday SUN = new Weekday();
public static final Weekday MON = new Weekday();
// ...
private Weekday() {}
}
9.3 常用方法
name():获取常量定义的字符串(推荐)ordinal():返回常量定义的顺序(无实质意义)toString():不推荐使用
9.4 枚举构造
枚举类可以有构造方法、字段和方法,构造方法必须声明为 private:
public enum Color {
RED("红色"), GREEN("绿色"), BLUE("蓝色");
private final String ChineseName;
Color(String name) {
this.ChineseName = name;
}
}
10. 动态代理
动态代理是一种在运行时期动态创建代理对象的机制,无需手动编写实现类。
10.1 class 与 interface 的区别
- 可以实例化
class(非abstract) - 不能实例化
interface
所有 interface 类型的变量总是通过某个实例向上转型赋值:
CharSequence cs = new StringBuilder();
10.2 动态代理的原理
通过 JDK 提供的 Proxy.newProxyInstance() 在运行期动态创建一个接口对象,这种方式称为动态代理。
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName());
return null;
}
};
CharSequence cs = (CharSequence) Proxy.newProxyInstance(
CharSequence.class.getClassLoader(),
new Class[] { CharSequence.class },
handler
);
cs.length();
}
}
11. 注解
注解(Annotation) 是用作标注的"元数据",可以被编译器打包进 class 文件,但本身不会影响代码逻辑。
11.1 注解的作用
从 JVM 角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。
11.2 三类注解
| 类别 | 用途 | 生命周期 |
|---|---|---|
| 编译器注解 | 如 @Override、@SuppressWarnings,编译后被丢弃 | SOURCE |
| 文件处理注解 | 加载 class 时做动态修改 | CLASS |
| 运行期注解 | 加载后存在 JVM 中,可被反射读取 | RUNTIME |
11.3 注解的参数
注解参数可以是:
- 所有基本类型
- String
- 枚举类型
- 以上类型的数组
11.4 @Retention
根据 @Retention 配置:
SOURCE:编译期被丢弃CLASS:保存在 class 文件,但不加载进 JVMRUNTIME:加载进 JVM,运行期可读取
11.5 反射读取注解
"规则在注解里,代码自动运行。程序通过反射读取注解,然后自动执行逻辑。"
很多注解是给反射用的,但有些是给编译器用的。
12. 泛型
泛型是一种"代码模板",可以用一套代码适用于各种类型,避免重复编写。
public class ArrayList<T> {
private T[] array;
public void add(T e) {...}
public T get(int index) {...}
}
使用泛型时,将 <T> 替换为具体类型:
ArrayList<String> strList = new ArrayList<>();
ArrayList<Integer> intList = new ArrayList<>();
12.1 泛型的好处
- 编译器自动检查类型
- 使用时不必强制类型转换
12.2 泛型的继承关系
- 可以将
ArrayList<Integer>向上转型为List<Integer> - 不能将
ArrayList<Integer>向上转型为ArrayList<Number>(类型参数 T 不能变成父类)
List<String> list = new ArrayList<>(); // √
ArrayList<Integer> intList = new ArrayList<>();
// ArrayList<Number> numList = intList; // × 编译错误
12.3 编写泛型类
// 普通类
public class Pair {
private String first;
private String last;
}
// 泛型类
public class Pair<T> {
private T first;
private T last;
public T getFirst() { return first; }
}
12.4 静态方法与泛型
静态方法不能引用泛型类型 <T>,需要定义其他类型:
public class Pair<T> {
// 错误:静态方法不能使用 T
public static Pair<T> create(T first, T last) { ... }
// 正确:使用单独的泛型类型 K
public static <K> Pair<K> create(K first, K last) { ... }
}
12.5 多个泛型类型
public class Pair<T, K> {
private T first;
private K last;
}
12.6 泛型的局限(擦拭法)
Java 泛型由编译器在编译时实现,编译器内部将所有类型 T 视为 Object 处理。因此:
<T>不能是基本类型(如int),必须用Integer- 不能获取带泛型类型的
Class(如Pair<String>.class) - 不能判断带泛型类型的类型(如
x instanceof Pair<String>) - 不能实例化
T类型(如new T())
12.7 泛型方法
定义泛型方法时,需防止与 Object 方法冲突:
public boolean equals(T obj) { ... } // 注意:可能与 Object.equals 冲突
子类可以获取父类的泛型类型 <T>。
12.8 泛型的使用细节
- 省略编译器能自动推断出的类型:
List<String> list = new ArrayList<>() - 不指定泛型参数类型时,编译器将
<T>视为Object类型 - 可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型
13. 集合
Java 集合框架定义在 java.util 包中,主要提供三种类型的集合:
| 集合类型 | 特点 | 实现类 |
|---|---|---|
List | 有序列表,可重复 | ArrayList, LinkedList, Vector |
Set | 无序集合,不重复 | HashSet, TreeSet |
Map | 键值映射 | HashMap, TreeMap |
13.1 List
List 是有序集合,存储元素和取出元素的顺序一致,有索引概念,可重复。
List<String> list = List.of("apple", "pear", "banana");
ArrayList vs LinkedList:
| 类型 | 底层结构 | 查询 | 插入/删除 |
|---|---|---|---|
| ArrayList | 数组 | O(1) 高效 | O(n) 需移动元素 |
| LinkedList | 链表 | O(n) 需遍历 | O(1) 高效 |
一般优先使用 ArrayList,除非需要频繁插入/删除。
遍历方式:
// 方式一:索引遍历(不推荐,LinkedList 效率低)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 方式二:Iterator 遍历(推荐,永远最高效)
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
// 方式三:for each 循环
for (String s : list) {
System.out.println(s);
}
13.2 Map
Map 是键值映射表,通过 key 快速查找 value。
| 类型 | 特点 | 底层结构 |
|---|---|---|
| HashMap | 无序 | 哈希表 |
| TreeMap | 有序(key 自动排序) | 红黑树 |
HashMap 原理:
- 通过 key 的
hashCode()计算索引 - 使用开放定址法或链地址法解决哈希冲突
- 自动扩容(数组长度翻倍)
使用 HashMap 注意事项:
- 作为 key 的类必须正确覆写
equals()和hashCode()方法 - 如果两个对象相等,则
hashCode()必须相等 - 如果两个对象不相等,则
hashCode()尽量不要相等
Map<String, String> map = new HashMap<>();
map.put("name", "Alice");
System.out.println(map.get("name"));
TreeMap 特点:
- key 必须实现
Comparable接口(String、Integer 已实现) - 按 key 自然顺序自动排序
13.3 Set
Set 用于存储不重复元素,本质上是不存储 value 的 Map(只存 key)。
Set<String> set = new HashSet<>();
set.add("A");
set.add("A"); // 重复元素不会被添加
| 类型 | 特点 |
|---|---|
| HashSet | 无序 |
| TreeSet | 有序 |
13.4 Queue
Queue 是先进先出(FIFO)的有序表。
| 方法 | 作用 |
|---|---|
offer(E e) | 添加到队列末尾 |
poll() | 从队列头部取出并移除 |
peek() | 查看队列头部元素 |
Deque 是双端队列,可以从两端插入和取出元素。
13.5 遗留集合类
以下类已过时,不推荐使用:
| 遗留类 | 替代类 |
|---|---|
Hashtable | ConcurrentHashMap |
Vector | ArrayList |
Stack | Deque |
Enumeration<E> | Iterator<E> |
14. 多线程
14.1 进程与线程
进程是操作系统进行资源分配的基本单位,线程是操作系统调度的基本单位。
- 一个进程可以有多个线程,一个线程只能属于一个进程
- 进程是程序执行的最小单位,线程是程序执行的最小单位
| 模型 | 优点 | 缺点 |
|---|---|---|
| 多进程模式 | 稳定性高,一个进程崩溃不影响其他进程 | 上下文切换代价高 |
| 多线程模式 | 线程共享资源,通信方便 | 一个线程崩溃导致整个进程崩溃 |
14.2 创建线程
方式一:继承 Thread
Thread t = new MyThread();
t.start();
class MyThread extends Thread {
@Override
public void run() {
System.out.println("start new thread!");
}
}
方式二:实现 Runnable
Thread t = new Thread(new MyRunnable());
t.start();
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("start new thread!");
}
}
方式三:Lambda 简化
Thread t = new Thread(() -> System.out.println("start new thread!"));
t.start();
14.3 线程状态
线程有 6 种状态:
| 状态 | 说明 |
|---|---|
| NEW | 新建状态,尚未启动 |
| RUNNABLE | 运行中或等待 CPU |
| BLOCKED | 阻塞,等待锁 |
| WAITING | 无限期等待 |
| TIMED_WAITING | 限期等待 |
| TERMINATED | 已终止 |
14.4 线程优先级与控制
优先级:1-10,默认 5。优先级越高被调度可能性越大。
join() :等待另一个线程执行完毕
Thread t = new Thread(() -> System.out.println("hello"));
t.start();
t.join(); // 等待 t 线程结束
14.5 中断线程
方式一:interrupt() 方法
Thread t = new MyThread();
t.start();
t.interrupt(); // 请求中断
t.join();
方式二:标志位(推荐)
class HelloThread extends Thread {
public volatile boolean running = true;
@Override
public void run() {
while (running) {
System.out.println("hello!");
}
}
}
// 外部设置
HelloThread t = new HelloThread();
t.start();
t.running = false; // 终止线程
14.6 volatile
volatile 解决可见性问题:线程修改变量后,其他线程能立刻看到最新值。
原因:线程有自己的工作内存,修改后需立即回写到主内存
14.7 守护线程
守护线程在所有非守护线程结束后自动结束,用于执行系统性任务(如垃圾回收)。
Thread t = new TimerThread();
t.setDaemon(true); // 设置为守护线程
t.start();
14.8 线程同步
多线程并发读写共享变量可能发生冲突,Java 通过 synchronized 实现线程同步。
synchronized (lockObject) {
// 临界区,最多只有一个线程能执行
}
synchronized 使用原则:
- 找出修改共享变量的代码块
- 选择一个共享实例作为锁
- 使用
synchronized(lockObject) { ... }
可重入锁:同一线程可多次获取同一锁,JVM 记录获取次数。
14.9 死锁
两个或多个线程互相等待对方持有的锁,造成循环等待。
避免死锁:所有线程按固定顺序获取锁。
14.10 wait 和 notify
wait() 和 notify() 用于线程协调,必须在 synchronized 块中使用:
public synchronized void getTask() {
while (queue.isEmpty()) {
this.wait(); // 释放锁,等待
}
// 唤醒后重新获取锁
}
public synchronized void addTask(String task) {
queue.add(task);
this.notify(); // 唤醒等待的线程
}
14.11 线程池
线程池复用已创建的线程,避免频繁创建和销毁带来的性能开销。
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> {
System.out.println("task executed");
});
executor.shutdown();
15. 要点总结
- 访问权限:
public>protected>package>private - 转型:向上转型安全,向下转型需用
instanceof判断 - 多态:基于运行时实际类型的动态调用
- 抽象类 vs 接口:抽象类可包含实例字段和具体方法,接口更抽象(Java 8 后可有 default 方法)
- 内部类:Inner Class 和 Anonymous Class 必须依附外部类实例,Static Nested Class 独立存在
- 枚举:特殊类,继承自 Enum,适合用于固定集合的类型安全表示
- 动态代理:通过
Proxy.newProxyInstance()在运行期动态创建接口对象 - 注解:三类(SOURCE/CLASS/RUNTIME),通过反射读取
- 泛型:擦拭法实现,有类型擦除的局限,不能用于基本类型
- 集合:List/Set/Map 三种类型,各有实现类
- 多线程:synchronized 实现同步,wait/notify 实现协调