Java 面向对象知识要点

4 阅读14分钟

Java 面向对象

本文档整理 Java 面向对象编程的核心知识,包括类、继承、接口、多态、内部类、集合、多线程等概念。

目录

  1. 访问权限
  2. 继承与转型
  3. 多态
  4. 抽象类
  5. 接口
  6. 静态字段与方法
  7. 内部类
  8. 枚举(enum)
  9. 动态代理
  10. 注解
  11. 泛型
  12. 集合
  13. 多线程
  14. 要点总结

1. 访问权限

Java 内建的访问权限包括 publicprotectedprivatepackage(默认)四种:

修饰符同类同包子类任意位置
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 面向抽象编程的本质

  1. 上层代码只定义规范
  2. 不需要子类即可实现业务逻辑
  3. 具体业务逻辑由子类实现,调用者不关心

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.classOuter$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 文件,但不加载进 JVM
  • RUNTIME:加载进 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 遗留集合类

以下类已过时,不推荐使用:

遗留类替代类
HashtableConcurrentHashMap
VectorArrayList
StackDeque
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 使用原则

  1. 找出修改共享变量的代码块
  2. 选择一个共享实例作为锁
  3. 使用 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 实现协调