JAVA语言程序设计 偏难点梳理
一、 面向对象三大特性深度理解
(1)继承与多态机制
(i)方法重写(Override)核心要点
方法重写发生在继承关系中,是子类为满足自身独特需求,重新实现从父类继承来的方法。其核心是“不变方法声明,变其内部实现”。
1. 必须遵守的语法规则
- 方法签名一致
- 重写的方法与被重写的父类方法,其方法名、参数列表(类型、顺序、数量)必须完全相同。
- 访问权限不能更严格
- 子类重写方法的访问修饰符权限不能低于父类原方法。
- 常见顺序:
public>protected> 默认(包私有) >private - 例如:父类方法为
protected,子类可改为public或保持protected,但不能改为默认或private。
- 返回类型协变
- 基本类型:返回类型必须完全相同。
- 引用类型:子类方法的返回类型可以是父类方法返回类型的子类(协变返回类型)。
- 异常声明限制
- 子类重写方法可以抛出更少、更具体或不抛出检查异常(Checked Exception)。
- 不能抛出比父类方法更新、更宽或更多的检查异常。
2. 关键特性与注意事项
- 使用
@Override注解(强推)- 用于显式声明,让编译器检查重写是否正确,避免因笔误等导致意外创建新方法。
- 调用父类实现 (
super)- 在子类重写方法中,可通过
super.父类方法名()调用父类的原始实现,这是扩展功能而非完全替换的常见做法。
- 在子类重写方法中,可通过
- 不可重写的情况
final方法:禁止被重写。static方法:属于类,可被“隐藏”而非“重写”,无多态性。private方法:对子类不可见,因此无法重写。- 构造器:不能重写。
3. 与重载(Overload)的本质区别
| 特性 | 重写 (Override) | 重载 (Overload) |
|---|---|---|
| 发生位置 | 继承关系的父子类之间 | 同一个类内部(或父子类间) |
| 方法签名 | 必须完全相同 | 必须不同(参数类型、顺序、数量至少一项不同) |
| 返回类型 | 必须相同或是协变类型 | 可以不同 |
| 访问修饰符 | 不能比父类更严格 | 可以不同 |
| 异常声明 | 可以减少或细化,不能扩大 | 可以不同 |
| 核心目的 | 实现运行时多态(动态绑定) | 提供同一功能的多种调用方式,是编译时多态 |
4. 核心目的与总结
- 核心目的:实现运行时多态,让程序架构更灵活、可扩展。这是面向对象编程的基石之一。
- 一句话总结:遵守规则(签名、权限、异常),善用注解(
@Override)和父类调用(super),理解其与重载的根本区别,最终服务于多态。
(ii)静态块和构造代码块与构造函数执行顺序
当创建子类对象时,类加载和实例化的完整流程是固定的。关键在于理解两个阶段:类加载阶段(一次性的)和对象实例化阶段(每次new时发生)。
阶段1:类加载阶段(只执行一次)
当程序第一次主动使用某个类时(如创建实例、访问静态成员等),JVM会加载该类。如果它有父类,则会先递归加载父类。
- 父类静态成员初始化
- 按在代码中出现的书写顺序,依次执行父类的静态变量显式赋值和静态代码块(
static {})。
- 按在代码中出现的书写顺序,依次执行父类的静态变量显式赋值和静态代码块(
- 子类静态成员初始化
- 父类加载完成后,按书写顺序,依次执行子类的静态变量显式赋值和静态代码块。
重要:类加载阶段在整个程序运行期间通常只发生一次。后续再创建该类的对象,将直接从阶段2(实例化)开始。
阶段2:对象实例化阶段(每次new都执行)
当执行 new SubClass() 时,开始创建对象实例。
- 父类实例成员初始化
- 为父类分配内存空间。
- 按书写顺序,依次执行父类的实例变量显式赋值和构造代码块(非静态代码块
{})。
- 父类构造方法执行
- 执行父类构造方法中的剩余语句。
- 注意:任何构造方法的第一行(无论是显式书写还是隐式存在),都是
super(...)或this(...)。默认情况下,子类构造方法会隐式调用父类的无参构造super()。
- 子类实例成员初始化
- 父类部分初始化完成后,开始子类部分。
- 按书写顺序,依次执行子类的实例变量显式赋值和构造代码块(非静态代码块
{})。
- 子类构造方法执行
- 最后执行子类构造方法中的剩余语句。
关键点与注意事项
- 静态代码块与类绑定,与对象无关:它只在类加载时执行一次,用于初始化静态资源(如加载驱动、初始化静态配置)。
- 构造代码块(非静态代码块)与每个对象绑定:每次创建对象时都会执行,且在构造方法主体之前执行,用于执行对象创建时的通用初始化逻辑。它比构造方法先执行,但晚于实例变量的显式赋值(按书写顺序穿插)。
- 构造方法调用链:创建子类对象时,一定会调用到父类的构造方法(至少到
Object类)。如果父类没有无参构造,子类构造方法必须通过super(...)显式指定调用父类的哪个有参构造。 - 变量、构造代码块与代码执行顺序:在同一个类中,无论是静态还是非静态,变量初始化和代码块都严格按照它们在源代码中出现的顺序执行。例如,如果构造代码块写在实例变量之前,则先执行构造代码块,反之亦然。
- 构造代码块的典型应用场景:当多个构造方法都需要执行相同的初始化逻辑时,可将公共代码提取到构造代码块中,避免代码重复。
(2)封装的深层应用与访问控制符
封装是面向对象三大特性之一,其核心是将数据(属性)和对数据的操作(方法)捆绑在一起,并隐藏内部实现细节,仅对外暴露可控的访问接口。访问控制符是实现封装的关键语法工具。
(i) 访问控制符作用域详解
下表清晰地展示了Java中四种访问控制符的可见范围:
| 访问控制符 | 当前类 | 同包(package) | 不同包子类 | 不同包非子类 | 核心设计意图 |
|---|---|---|---|---|---|
private |
✅ | ❌ | ❌ | ❌ | 彻底隐藏,仅限类内部使用。 |
default |
✅ | ✅ | ❌ | ❌ | 包内可见,提供包级别的模块化封装。 |
protected |
✅ | ✅ | ✅ | ❌ | 主要服务于继承,允许子类访问,无论是否同包。 |
public |
✅ | ✅ | ✅ | ✅ | 完全公开,定义对外承诺的稳定接口。 |
关键说明:
default(默认/包私有):不使用任何关键字修饰时即为default。它是包(package) 这一逻辑单元的“内部门禁”。protected:其“不同包子类可访问”的特性,清晰地体现了继承关系优先于包结构的面向对象设计思想。
二、 异常处理机制
finally 与 return 的复杂交互
核心规则:finally块中的代码几乎总是会执行,并且它的return或修改返回值的行为会覆盖try或catch中的操作。
场景1: finally 中有 return 语句
finally中的return会“覆盖”try或catch中的返回值,并吞掉其中未捕获的异常!
场景2: finally 修改引用类型或基本类型变量的值
- 基本类型:
finally中对返回变量的修改无效(因为返回时保存的是值的副本)。 - 引用类型:
finally中修改对象的状态有效,但让引用指向新对象无效(原理同基本类型)。
三、 I/O流体系
| 流类型 | 方向 | 抽象基类 | 常用实现类 | 功能说明 | 使用频率 |
|---|---|---|---|---|---|
| 字节流 | 输入 | InputStream |
FileInputStream |
从文件读取字节数据 | ⭐⭐⭐ |
BufferedInputStream |
带缓冲的字节输入流(包装类) | ⭐⭐⭐⭐⭐ | |||
ObjectInputStream |
对象反序列化 | ⭐⭐⭐⭐ | |||
DataInputStream |
读取Java基本数据类型 | ⭐⭐⭐ | |||
ByteArrayInputStream |
从内存字节数组读取 | ⭐⭐ | |||
| 输出 | OutputStream |
FileOutputStream |
向文件写入字节数据 | ⭐⭐⭐ | |
BufferedOutputStream |
带缓冲的字节输出流(包装类) | ⭐⭐⭐⭐⭐ | |||
ObjectOutputStream |
对象序列化 | ⭐⭐⭐⭐ | |||
DataOutputStream |
写入Java基本数据类型 | ⭐⭐⭐ | |||
ByteArrayOutputStream |
向内存字节数组写入 | ⭐⭐ | |||
| 字符流 | 输入 | Reader |
FileReader |
从文件读取字符(默认编码) | ⭐⭐⭐ |
BufferedReader |
带缓冲的字符输入流,支持readLine() |
⭐⭐⭐⭐⭐ | |||
InputStreamReader |
字节流→字符流桥梁(可指定编码) | ⭐⭐⭐⭐⭐ | |||
CharArrayReader |
从内存字符数组读取 | ⭐⭐ | |||
| 输出 | Writer |
FileWriter |
向文件写入字符(默认编码) | ⭐⭐⭐ | |
BufferedWriter |
带缓冲的字符输出流,支持newLine() |
⭐⭐⭐⭐⭐ | |||
OutputStreamWriter |
字符流→字节流桥梁(可指定编码) | ⭐⭐⭐⭐⭐ | |||
CharArrayWriter |
向内存字符数组写入 | ⭐⭐ |
1.常见文件操作类总结
| 操作需求 | 推荐使用的类 | 说明 |
|---|---|---|
| 读取二进制文件 | FileInputStream + BufferedInputStream |
非文本文件的标准读取方式 |
| 写入二进制文件 | FileOutputStream + BufferedOutputStream |
非文本文件的标准写入方式 |
| 复制任意文件 | 字节流 + 缓冲流 | 通用文件复制模板 |
| 读取文本文件 | BufferedReader(new FileReader(...)) 或 BufferedReader(new InputStreamReader(...)) |
后者可指定编码,更推荐 |
| 写入文本文件 | BufferedWriter(new FileWriter(...)) 或 BufferedWriter(new OutputStreamWriter(...)) |
后者可指定编码,更推荐 |
| 按行读取文本 | BufferedReader.readLine() |
最便捷的文本行读取方法 |
| 读写Java对象 | ObjectInputStream / ObjectOutputStream |
对象必须实现Serializable接口 |
| 读写基本数据类型 | DataInputStream / DataOutputStream |
用于读写int、double等基本类型 |
| 内存操作 | ByteArrayInputStream/OutputStream CharArrayReader/Writer |
在内存中读写数据 |
| 标准输入/输出 | System.in (InputStream) System.out (PrintStream) |
控制台输入输出 |
2.最佳实践与要点
- 总是使用缓冲流:对于文件I/O,几乎总是应该用
BufferedXxx包装底层流,可极大提升性能。 - 总是使用try-with-resources:Java 7+的
try-with-resources语句可自动关闭流,避免资源泄漏。 - 处理文本务必指定编码:
FileReader/FileWriter使用平台默认编码,可能导致跨平台乱码。最佳实践是使用InputStreamReader和OutputStreamWriter并明确指定编码(如StandardCharsets.UTF_8)。 - 正确关闭流:关闭顺序应是“后打开的先关闭”,但使用
try-with-resources时无需担心。 - 对象序列化:
- 实现
Serializable接口的类应显式声明serialVersionUID,否则JVM会自动生成,类结构变化会导致反序列化失败。 transient关键字用于避免敏感字段被序列化。- 静态变量不会被序列化(属于类,不属于对象状态)。
- 实现
NIO(New I/O)
Buffer、Channel、Selector的概念- NIO与传统IO的区别
4. Java内存管理
- 堆(Heap)与栈(Stack)的区别
- 垃圾回收机制(GC)基本原理
- 强引用、软引用、弱引用、虚引用
- 内存泄漏的常见场景
5. 设计模式在Java中的应用
- 单例模式的多种实现方式(特别是线程安全的实现)
- 工厂模式、建造者模式
- 观察者模式、装饰器模式
- 策略模式的实际应用