Java 第四周学习笔记:多态、接口、内部类与 String

Java 第四周学习笔记:多态、接口、内部类与 String

第四周(P114--132):从面向对象的三大支柱收尾,走到 Java 最常用的 API------String。 这篇按知识点梳理,末尾附上我自己周复盘测出来的 8 个"假会"(以为会了其实不会)。

一、多态:编译看左边,运行看右边

1.1 三个前提

多态不是随便就能用的,必须同时满足三条:

  1. 有继承或实现关系
  2. 有方法重写
  3. 父类引用指向子类对象

java

ini 复制代码
Animal a = new Cat();   // ← 第 3 条

记法:有爹、有重写、爹指儿。三条缺一不可。

1.2 调用规则

java

scss 复制代码
Animal a = new Cat();
a.eat();            // ✅ 编译过,运行调 Cat 的 eat
a.catchMouse();     // ❌ 编译不过

编译看左边 (Animal 里有没有这个方法),运行看右边 (实际执行 Cat 的重写版本)。

catchMouse() 编译不过,是因为 Animal 里根本没声明这个方法------编译期只认左边那个类型。

1.3 好处与坏处

好处:一个方法能接住一整族对象。

java

javascript 复制代码
// 不用多态:来一种新动物就得新写一个方法
void feedCat(Cat c) { c.eat(); }
void feedDog(Dog d) { d.eat(); }

// 用多态:一个方法通吃,新动物直接传进来
void feed(Animal a) { a.eat(); }

坏处 :父类引用调不了子类特有的方法 (就是上面 catchMouse() 那个问题)。

解决 :向下转型。

java

ini 复制代码
Cat c = (Cat) a;    // 向下转型(手动、有风险)
c.catchMouse();     // ✅ 现在能调了
方向 写法 安全性
向上转型 Animal a = new Cat(); 自动,安全
向下转型 Cat c = (Cat) a; 手动,类型不对会运行时报错

二、抽象类与接口

2.1 abstract 能修饰什么

类和 方法。

java

csharp 复制代码
public abstract class Animal {       // 抽象类
    public abstract void eat();      // 抽象方法
    public void sleep() {            // 普通方法也可以有
        System.out.println("睡觉");
    }
}

抽象方法的特点:

  1. 没有方法体 (eat(); 而不是 eat() { })
  2. 必须待在抽象类里
  3. 子类必须重写(除非子类也声明为 abstract)

单向规则:有抽象方法 → 必须是抽象类;但抽象类不一定要有抽象方法。

2.2 子类不想实现怎么办

子类也声明为 abstract。

java

scala 复制代码
public abstract class Cat extends Animal {
    // 不实现 eat(),那就继续挂着
}

2.3 什么时候用继承,什么时候用接口

口诀:is-a 用继承,can-do 用接口。

继承 接口
表达 is-a(是什么) can-do(能做什么)
例子 Cat extends Animal------猫是动物 Bird implements Flyable------鸟能飞
数量 只能 1 个(单继承) 可以多个(多实现)

判断步骤:

  1. 问「B 是 A 吗?」→ 是 → 继承
  2. 问「B 能做 A 这件事吗?」→ 是 → 接口
  3. 两者都成立 → 继承 + 接口一起用

典型例子 :BasketballPlayer 既是运动员(is-a,继承 Athlete),又能说英语(can-do,实现接口)。

2.4 JDK8 为什么给接口加 default 方法

为了向后兼容。

JDK8 之前,接口一旦发布就不能再改------加一个方法,所有实现类都得跟着改,否则编译不过。default 方法允许接口提供默认实现,老代码不受影响。

2.5 abstract 与 final 冲突

java

java 复制代码
public abstract final class A   // ❌ 编译报错

abstract 要求子类继承并重写,final 禁止继承------两者需求相反。


三、内部类

  • 成员内部类:定义在类的成员位置
  • 静态内部类 :加 static
  • 局部内部类:定义在方法里
  • 匿名内部类 :没有名字的类对象------一次性地创建子类/实现类的实例

匿名内部类的要点 :它本质是对象,那个"类"只是顺带定义的,用完即弃。


四、String:不可变带来的连锁反应

4.1 不可变意味着什么

String 是 final 类,不可变(immutable) ------一旦创建,内容永远不变。

这带来一个最容易踩的坑:

java

arduino 复制代码
String s = "hello";
s.replace('l', 'L');        // ❌ 这行等于白干
System.out.println(s);      // 还是 hello

为什么? 因为 replace 不会改原串 ,而是造一个新的还给你。你没接住,新串就被丢弃了。

正确写法:

java

ini 复制代码
s = s.replace('l', 'L');    // ✅ 必须赋值回去

铁律 :String 的所有"修改"方法都不改自己,只返回新串------

java

ini 复制代码
s = s.substring(1);
s = s.trim();
s = s.replace("a", "b");
s = s.toUpperCase();

判断技巧 :方法名像"动词"(改、切、换、去),前面就要有 s =。

再强调一次 := 才是接住,return 是交出去 。return s 交出去的还是老的 s,它没有"接住"的能力。

4.2 == 和 equals

java

javascript 复制代码
"abc" == "abc"                  // true
new String("abc") == "abc"      // false

两次比的都是地址 ,区别在地址是否相同:

  • "abc" 是字面量,存在常量池 ,写两次指的是同一个对象 → 地址相同 → true
  • new String("abc") 强制在堆里新建 → 地址不同 → false

比内容要用 equals()。

4.3 常用方法

方法 说明
substring(begin, end) 截取,含头不含尾 (substring(2,5) 取 2、3、4)
substring(begin) 从 begin 截到末尾
replace(旧, 新) 替换,返回新 String(有两个重载:char / String)
split(分隔符) 按分隔符切成数组
trim() 只剪两端空格,中间不动
charAt(i) 取指定位置的字符

split 的坑:

java

bash 复制代码
"192.168.1.1".split(".")     // ❌ 返回空数组!"." 在正则里是"任意字符"
"192.168.1.1".split("\.")   // ✅ 要转义
"a,b,,c".split(",")          // {"a","b","","c"} 长度 4,空段保留
"a,b,,".split(",")           // {"a","b"} 长度 2,尾部空串默认被丢

4.4 StringBuilder

为什么需要它 :String 不可变,循环拼接会产生大量垃圾对象。

java

ini 复制代码
String s = "";
for (int i = 0; i < 1000; i++) {
    s += i;              // ❌ 每次循环都 new 一个新 String,造 1000 个垃圾对象
}

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);        // ✅ 在同一个对象上改
}

要点:

  • 内部是 char[] 数组

  • append() 返回 this (自己),所以可以链式调用 :sb.append("a").append("b")

    • 判断依据:看到方法能链式调用,就知道它返回的是自己
  • reverse() 是 StringBuilder 特有,String 没有(因为改不了内容)

  • 互转:new StringBuilder(str) / sb.toString()


五、我的 8 个"假会"(周复盘测出来的)

回顾这周,闭卷自测暴露的问题比想象中多。所谓"假会"就是:以为自己会了,一测才发现不会。

① 多态三前提,我只说得出 1 条

只记得"有继承关系",漏了方法重写 和父类引用指向子类对象。

② 多态的好处坏处,会用不会说

代码写得对(编译/运行规则全对),但让我用术语描述"多态有什么用"就卡住了。

③ is-a / can-do 口诀会背,换个问法接不上

"抽象类 vs 接口的区别"这道题,我答得含糊------明明背过口诀,却没跟这个问法对上。

④ abstract 只能修饰方法?

错,能修饰类和 方法。

⑤ 抽象方法带方法体(第 12 题答错)

java

csharp 复制代码
abstract void f() { }    // ❌ 有花括号 = 有方法体,报错
abstract void f();       // ✅ 分号结尾

⑥ 匿名内部类是"没有名字的类"?

不准确 。应该是没有名字的类对象------本质是对象,类只是顺带定义的。

⑦ "abc" == "abc" 结果对,理由说反了

我答"比较的是数值",错 。两次比的都是地址,区别在地址是否相同(常量池复用 vs new 强制新建)。

⑧ 最严重的:以为 return 能"接住"

java

arduino 复制代码
String s = "hello";
s.replace('l', 'L');
return s;              // 我以为 s 已经更新了

这个错误我犯了三次:学的时候、当天快检、周复盘自测。

根因是没建立正确的心智模型:

代码块

复制代码
方法返回新值 → 必须用 = 把它绑到变量上 → 变量才更新

return 是交出去,= 才是接住。


六、一条认知突破

循环体内对数组做原地修改时,下一轮看到的不再是原数组。

以 "abc" 打乱为例:

轮次 我以为在操作的 实际在操作的
i=0 [a,b,c] [a,b,c] ← 只有第一轮是原值
i=1 [a,b,c] ❌ [c,b,a] ✅
i=2 [a,b,c] ❌ [b,c,a] ✅

推广 :冒泡排序、插入排序、双指针交换、快排分区、数组去重全是这个道理 。分析循环要跟着状态走,不能盯着初始值。


小结

这一周最大的收获不是"学会了 String 有哪些方法",而是发现"以为自己会"和"真的会"之间有多大差距。

8 个假会里,最扎心的是第 ⑧ 条------同一个错误犯了三次。但换个角度想:正因为周复盘把它挖出来了,它现在才真的被记住了。

知识不是看会的,是测出来的。


本系列按周更新,记录 Java 后端学习路线上的每个阶段。

相关推荐
彩雲演文1 小时前
51单片机万年历Proteus仿真跑通实录:LCD12864B+DS1302+DHT11,带农历和闹钟(效果图+接线表+全套源码)
后端
ThinkerQAQ_1 小时前
并发编程(十):BlockingQueue 与 Channel 的实现——从 Runtime 到 CPU
后端
霂鳯1 小时前
新手勇闯网络安全 | PHP基础(四)
后端
海岳云舟1 小时前
程序常驻后台运行的原理和方法
后端·全栈
彩雲演文1 小时前
Proteus仿真LCD12864B不显示/黄屏?别急着改接线,真凶是BusyCheck死循环(51单片机实战排查)
后端
Rain的Java大神之路2 小时前
Java 三层架构,90% 的人只答对一半:Controller 不是瘦,Service 不是透传,Dao 不是只写 SQL
java·后端·面试
小羊没烦恼!2 小时前
浅谈SQL Server中的事务日志(二)----事务日志在修改数据时的角色
前端·数据库·后端·python·web安全·oracle
IT_陈寒3 小时前
一文搞懂Vite的核心概念
前端·人工智能·后端
一个简单的TTS开发者3 小时前
中文TTS工具有哪些-8款主流方案横向对比
人工智能·后端·python·indextts