《Java编程思想》第7章「复用类」反常识+新手高频易错点

本章核心是组合、继承、构造器初始化、重写、向上转型、清理、代理,下面按模块整理所有违背直觉、极易踩坑的点,全部是书中原文重点坑。

五、向上转型:看似简单,隐藏反常识逻辑

1. 向上转型自动安全,向下转型强制强转且有风险

误区:子类对象转父类会丢失功能

真相:向上转型只是缩小接口视图,对象本体还是子类实例,调用重写方法依然走子类;只是父类不存在的方法无法直接调用,要强转回来。

2. 向上转型是继承的核心价值,不是“附加功能”

新手以为继承只是复用代码,书中强调:继承最重要的作用是向上转型实现多态,代码复用只是附带收益。

六、清理与垃圾回收:颠覆C/C++思维的反常识点

1. Java无析构函数,finalize不可靠,不能依赖GC自动释放资源

新手直觉:对象不用了GC自动清理资源(文件、流、连接)
书中结论:
  • GC只回收堆内存,不处理IO、网络、文件句柄等系统资源;
  • finalize()执行时机不确定,甚至永远不执行;
  • 必须手动提供dispose()/close()清理方法,搭配try-finally保证一定执行。

2. 垃圾回收只和内存有关,和对象生命周期业务清理完全无关

很多新手混淆内存回收与资源释放,导致文件句柄泄漏、连接占满。

一、核心一句话

GC只管堆内存里的对象空间,不关文件流、数据库连接、Socket、句柄这类系统资源;GC触发时机不可控,千万别靠GC帮你关资源。

二、错误示范(依靠GC释放资源,必泄漏)
import java.io.FileReader;
import java.io.IOException;

public class GcResourceTest {
    // 方法执行完毕,reader变成局部垃圾对象
    public void readFile() throws IOException {
        FileReader reader = new FileReader("test.txt");
        char[] buf = new char[1024];
        reader.read(buf);
        // 故意不写 reader.close(),指望GC回收reader时自动关闭文件
    }

    public static void main(String[] args) throws Exception {
        GcResourceTest test = new GcResourceTest();
        for (int i = 0; i < 1000; i++) {
            test.readFile();
        }
        System.gc(); // 手动建议GC,依旧不靠谱
        Thread.sleep(2000);
    }
}
问题说明
  1. reader 对象在方法结束后成为垃圾,堆内存迟早会被GC清空;
  2. 但是文件句柄属于操作系统内核资源,GC回收对象不会主动调用close关闭文件
  3. 循环上千次打开文件不关闭,最终直接抛出:打开的文件过多 系统异常,造成句柄泄漏。
三、正确写法:手动释放资源(try-with-resources 最优)
public void readFileCorrect() {
    // 实现AutoCloseable的资源,代码块结束自动close
    try (FileReader reader = new FileReader("test.txt")) {
        char[] buf = new char[1024];
        reader.read(buf);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
四、配套直白总结
  1. GC职责:归还JVM堆内存;
  2. 业务资源(文件、连接、端口):必须开发者手动主动释放;
  3. finalize() 曾用来收尾资源,现已被官方废弃,完全不可信赖。

七、protected访问修饰符:极易理解偏差

错误认知:protected=子类任意访问

真实规则(双重权限)

  1. 同包内所有类均可访问;
  2. 不同包,只有子类能访问;
    反常识:跨包无关类哪怕名字相近,也不能碰protected成员。

八、静态方法:不存在重写,只有遮蔽

大坑:静态方法绑定引用类型,不绑定实际对象

class Parent {
    public static void test() { System.out.println("父静态"); }
}
class Son extends Parent {
    public static void test() { System.out.println("子静态"); }
}
Parent p = new Son();
p.test(); // 输出父静态,静态方法无多态,看引用类型Parent

新手误以为静态方法可以重写、支持多态,完全错误,这叫静态遮蔽

汇总:新手最容易踩的Top5致命反常识坑

  1. 子类同名父private方法,以为重写,多态完全失效;
  2. 父构造器中调用重写方法,子类变量未初始化,数据错乱;
  3. 无脑用继承复用代码,忽略优先组合的设计原则;
  4. 混淆变量遮蔽与方法重写,以为变量有多态;
  5. 依赖GC/finalize释放文件、网络资源,造成资源泄漏。

知识回炉重温

向上转型 进一步解释

寓言:戏台演员(完美对应向上转型)

人物

父类身份:【普通演员挂牌】
子类本体:【武生演员】
武生自带两套本事:所有演员都会的登台唱戏(重写方法)、自己独有的翻跟头(子类独有方法)

故事剧情

剧场规定:入场登记只允许挂「普通演员」牌子进场。
武生没办法,挂上普通演员的牌子走入后台:普通演员挂牌 = 真实武生本人 → 这就是向上转型

  1. 本体从没变化
    人从头到尾就是武生,翻跟头、唱戏的本事全部都在身上,半点没丢掉。

  2. 被牌子限制了对外能力

  • 剧场喊所有人上台唱戏:武生上台表演的是武生版唱腔动作(执行子类重写方法)
  • 你想让他当众翻跟头:拿着“普通演员”这个牌子,剧场系统不认翻跟头这项技能,直接不让执行,不是武生不会,是牌子权限不够。
  1. 想要翻跟头怎么办
    把挂牌换回「武生」身份(向下强制转型),才能解锁专属技能。
对应继承的本质

剧团设计不同演员分类(继承体系),首要目的是:剧场只用“普通演员”一个入口,就能收纳武生、花旦、老生所有演员入场管理(多态)。
至于武生不用重新学习基础唱戏流程、共用舞台规则(代码复用),只是顺带的便利而已。

一句话收尾

向上转型=本人没变,只是证件降级,通用行为照旧,专属技能封存

成员对象初始化:编译器只管父类构造,不管组合成员 --不理解

寓言:开一家连锁酒馆,看懂「继承父类构造」和「组合成员变量」的编译器区别

人物设定
  1. 酒馆总店 = 父类:开分店必须先建好总店的地基、消防、执照,总店的硬性规则没人能跳过
  2. 分店门店 = 子类(继承):你想开一家分店,属于总店体系下的门店
  3. 酒馆里的调酒师 = 组合成员对象:分店自己聘请的员工,是分店单独持有的一个外部对象
第一部分:继承(分店→总店),编译器=市场监管局,强制先走父类构造

市场监管局(编译器)有死规定:任何一家挂靠总店的分店,开业之前,总店本身必须是合法完工状态

  1. 总店建造(父类构造方法):铺设下水、办理消防证件、搭建房屋主体,这套流程是法定必填项。
  2. 你修建分店的时候,哪怕你完全不管总店,监管系统会自动强制调用总店的施工流程(隐式super());
  3. 如果你故意删掉总店施工流程、或者写了错误的构造调用,监管直接不给你下发开业许可(代码编译报错)。

一句话:继承是上下级从属关系,上层的基础建设由法律(Java语法)强制兜底、强制执行,不做就不让编译。

第二部分:组合(分店→调酒师),编译器只管登记姓名,不管真人上岗

分店内部需要一名调酒师干活,你在分店图纸里写下「本店配置一名调酒师」(也就是代码里写 Bartender bartender; 成员变量)。
监管局(编译器)只会做一件事:给这个岗位默认填上“人员空缺(null)”
监管完全不会:

  1. 不会强制要求你开业前必须招聘到位、办理入职;
  2. 不会主动帮你去找调酒师、完成实例化 new Bartender()
  3. 不会校验你这家店有没有调酒师。
  • 你要是勤快:开店前在图纸上直接写明招聘好人、或者开店装修(构造方法)时招好人,营业调酒正常运行;
  • 你要是偷懒:只标注了岗位,全程不去招人,门店开张后客人点酒,你喊调酒师干活,当场没人在岗(运行时空指针NPE崩溃)。

监管局的逻辑很简单:总店是体系根基,影响整个门店合法性,必须强制校验;调酒师只是门店的一个私有工具人,属于门店自己的业务细节,官方不插手你的人事管理,出事自己负责。

对应原文四条知识点一一对照
  1. 继承:强制递归初始化基类,不写super直接报错
    开分店必须逐层把总店、总店的上级总部全部施工完毕,super省略编译器自动补,手动写错super参数直接施工审批驳回(编译报错)。

  2. 组合成员:编译器不会强制初始化,默认null,极易空指针
    只登记岗位名称,默认状态就是空缺null,什么时候招人完全由店家自己决定,营业调用岗位功能就会出问题。

  3. 成员四种初始化方式(店家四种招人时机)

  • 定义处初始化:图纸上直接写明「本店自带调酒师张三」Bartender b = new Bartender();
  • 构造器初始化:门店装修施工的时候同步完成招聘
  • 实例代码块初始化:门店动工阶段自动执行招人流程
  • 懒加载:客人第一次点酒的时候,现场临时招人上岗
    这四种方式全是店家自主行为,监管全程不会插手兜底校验。
核心本质总结

继承 = 层级血缘根基,语言层面强制安全校验
组合 = 自己持有的外部资源,语法只分配内存引用,业务正确性完全交给开发者自己管控

习题巩固

第1题(向上转型知识点)
单选:关于Java向上转型说法正确的是()
A. 向上转型后子类独有的成员方法会被销毁删除
B. 父类 引用 = new 子类() 属于向上转型,编译自动通过
C. 继承设计的首要目的是复用父类代码,多态只是附加效果
D. 向上转型调用重写方法,执行的是父类里面的方法实现
正确答案:B
解析:
A错误:子类方法本体依旧存在,只是父类引用无法直接访问,没有被销毁;
B正确:父类引用 = new 子类实例 就是标准向上转型,Java自动隐式转换,无需强制转型;
C错误:继承核心价值是依托向上转型实现多态,代码复用只是附带收益;
D错误:向上转型执行重写方法,实际运行的是子类重写后的代码。

第2题(GC与资源释放)
单选:针对Java GC与系统资源释放,描述正确的是()
A. 对象变成垃圾后,GC回收内存时会自动关闭文件流、数据库连接
B. finalize方法执行时机可控,可用它放心关闭各类系统资源
C. FileReader这类资源,推荐使用try-with-resources语法自动关闭
D. 调用System.gc(),程序就会立刻执行垃圾回收动作
正确答案:C
解析:
A错误:GC只回收堆内存,不会主动释放文件、连接这类系统资源,会造成句柄泄漏;
B错误:finalize() 执行时间不确定,甚至不会执行,官方早已废弃,不能用来释放资源;
C正确:所有实现AutoCloseable的流资源,使用try-with-resources,代码块结束自动关闭,是标准最优写法;
D错误:System.gc() 只是向JVM发起回收建议,无法强制、立刻触发GC。

第3题(protected修饰符)
单选:protected修饰的成员变量/方法,访问权限准确描述是()
A. 只有本类和不同包下的子类能够访问
B. 同包所有类 + 不同包的子类可以访问
C. 只要是子类,不管是否同包都能随意访问
D. 仅本类内部可以访问
正确答案:B
解析:
A错误:漏掉了同包普通类也能访问,表述不全;
B正确:精准匹配规则:同一包全部类可访问;跨包仅子类有权访问;
C错误:跨包无关类就算名称相似也无法访问;
D错误:该描述是private的权限范围。

第4题(静态方法遮蔽)
单选:执行以下代码打印结果是

class Parent {
    public static void test() { System.out.println("父静态"); }
}
class Son extends Parent {
    public static void test() { System.out.println("子静态"); }
}
Parent p = new Son();
p.test();

A. 父静态
B. 子静态
C. 编译报错
D. 运行异常
正确答案:A
解析:
静态方法不存在重写,属于静态遮蔽。静态方法的调用绑定左侧引用类型(此处引用是Parent类型),和右边实际new出来的Son对象无关,因此执行父类的静态方法,打印「父静态」。

链接:

《Java编程思想》重点章节+踩坑学习规划

Logo

纵情码海钱塘涌,杭州开发者创新动! 属于杭州的开发者社区!致力于为杭州地区的开发者提供学习、合作和成长的机会;同时也为企业交流招聘提供舞台!

更多推荐