一、抽象类:不完整的类,专为被继承而生
1.1为什么需要抽象类?
想象一下,你正在写一个图形绘制程序。
有圆形、矩形、三角形……它们都有“画出来”这个动作,也就是 draw()
方法。
但“图形”本身是个抽象概念——你不能画一个叫“图形”的东西,只能画具体的圆或矩形。
(具体参考上一篇博客)
class Shape {
void draw() {
// 这个方法写什么?不知道!
}
}
如果让 Shape
类的 draw()
方法空着,或者写个“暂未实现”,不仅没意义,还容易出错。
于是,Java提供了 抽象类——专门用来表示这种“不完整”的类。
1.2抽象类语法
用 abstract 关键字修饰的类就是抽象类:
public abstract class Shape {
// 抽象方法:只有声明,没有实现
abstract void draw();
abstract void calcArea();
// 普通方法:可以有实现
public double getArea() {
return area;
}
protected double area;
}
关键点:
- 抽象方法不能有方法体(即 {})。
- 包含抽象方法的类,必须声明为 abstract。
- 抽象类可以有普通方法、属性,甚至构造方法。
- 有抽象方法的类一定是抽象类
1.3抽象类五大特性
特性1:不能直接实例化
错误示范:
Shape shape = new Shape(); // 编译错误!
错误提示:Shape是抽象的;无法实例化
特性2:抽象方法不能是 private
abstract private void draw();
因为抽象方法就是要被子类重写的,private
会把它锁死,矛盾。
✅ 特性3:不能被 final 或 static 修饰
abstract final void methodA(); // 冲突
abstract static void methodB(); // 冲突
final
表示不能重写,static
属于类,而抽象方法必须被实例重写。
特性4:子类必须重写所有抽象方法
public class Rect extends Shape {
private double length, width;
@Override
public void draw() {
System.out.println("矩形: length=" + length + " width=" + width);
}
@Override
public void calcArea() {
area = length * width;
}
}
如果子类不实现,那它也得是抽象类:
public abstract class Triangle extends Shape {
// 只实现 draw,不实现 calcArea
@Override
public void draw() { ... }
// 没重写 calcArea → 所以自己也必须是 abstract
}
比喻:爷爷欠的债,父亲不还儿子也得还,出来混总是要还的。
class B{
}
class C extends B{
private int a = 10;
public void fun(){
System.out.println("C"+a);
}
}
特性5:抽象类可以有构造方法
public abstract class Shape {
protected double area;
public Shape() {
System.out.println("Shape 初始化");
}
}
子类 new Rect() 时,会先执行 Shape() 构造方法。
1.4抽象类的真正作用:编译器的“安全带”
你可能会问:普通类也能被继承,也能重写方法,为啥非要用抽象类?
关键在于——抽象类能防止你误用父类。
比如你不小心写了 new Shape(),普通类不会报错(如果方法空实现),但抽象类会在编译时报错,让你立刻发现问题。
二、接口:行为的契约,能力的说明书
2.1接口的概念
接口就像一份行为规范。
比如USB接口:只要符合协议,U盘、鼠标、键盘都能插上去用。
电源插座:只要电压匹配,电脑、电饭煲都能供电。
在Java中,接口就是这种“通用标准”。
2.2语法规则
public interface USB {
void openDevice();
void closeDevice();
}
- 接口中的方法默认是 public abstract,可以省略不写。
- 变量默认是 public static final,也就是常量。
interface USB {
double brand = 3.0; // 等价于 public static final double brand = 3.0;
void openDevice(); // 等价于 public abstract void openDevice();
}
2.3如何实现接口
用 implements 关键字:
public class Mouse implements USB {
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
public void click() {
System.out.println("鼠标点击");
}
}
注意:
- 必须实现接口中所有方法,否则类必须声明为 abstract。
- 重写时访问权限不能降低,必须是 public。
2.4一个类可以实现多个接口
Java不支持多继承(一个类只能有一个父类),但可以实现多个接口。
interface IRunning { void run(); }
interface ISwimming { void swim(); }
interface IFlying { void fly(); }
class Duck extends Animal implements IRunning, ISwimming, IFlying {
@Override public void run() { ... }
@Override public void swim() { ... }
@Override public void fly() { ... }
}
这叫“is-a + has-ability”模式:
- extends Animal → 鸭子是一种动物(is-a)
- implements ... → 鸭子会跑、会游、会飞(has ability)
2.5接口的真正价值:让程序“忘记类型”
public static void walk(IRunning running) {
System.out.println("我带着伙伴去散步");
running.run();
}
只要实现了 IRunning,就能传进来:
Cat cat = new Cat("小猫");
Frog frog = new Frog("小青蛙");
Robot robot = new Robot("机器人"); // 机器人也能跑!
walk(cat); // 小猫正在跑
walk(frog); // 小青蛙正在跳
walk(robot); // 机器人正在用轮子跑
程序不再关心“你是谁”,只关心“你会不会跑”。
这就是多态的精髓。
三、接口还能继承接口
接口之间可以多继承:
interface IAmphibious extends IRunning, ISwimming {
// 两栖能力:既能跑又能游
}
- Frog 实现 IAmphibious,就得实现 run() 和 swim()。
- 这相当于把多个接口“合并”成一个新协议,代码更清晰。
四、实战案例:给学生排序
Java的 Arrays.sort() 方法默认只能排数字。
如果想排 Student 对象,怎么办?
让它实现 Comparable 接口:
class Student implements Comparable<Student> {
private String name;
private int score;
@Override
public int compareTo(Student o) {
return Integer.compare(o.score, this.score); // 降序
}
@Override
public String toString() {
return "[" + name + ":" + score + "]";
}
}
然后就能直接排序了:
Student[] students = { /* ... */ };
Arrays.sort(students);
System.out.println(Arrays.toString(students));
// 输出:[[王五:97], [李四:96], [张三:95], [赵六:92]]
注:comparable 就是Java内置的一个“可比较”契约。
五、克隆对象:浅拷贝与深拷贝
Java的 Object 类有个 clone() 方法,但想用它?先得实现 Cloneable 接口。
class Person implements Cloneable {
public Money money = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
执行克隆:
Person p1 = new Person();
Person p2 = (Person) p1.clone();
但注意:这是浅拷贝——只复制对象本身,不复制引用的对象。
p2.money.m = 13.6;
System.out.println(p1.money.m); // 13.6!也被改了
因为 money 是引用类型,p1 和 p2 指向同一个 Money 对象。
要实现深拷贝,必须手动重写 clone(),递归拷贝所有引用对象。
六、抽象类 vs 接口:一张表说清区别
特性 | 抽象类(abstract class) | 接口(interface) |
关键字 | abstract class | interface |
方法类型 | 可有抽象方法和普通方法 | 只能抽象方法(JDK8+可有default) |
字段 | 可有普通字段 | 只能 public static final 常量 |
构造方法 | 可有 | 无 |
继承 | 单继承(一个父类) | 多实现(多个接口) |
使用场景 | “是什么”(is-a) | “有什么能力”(has-a) |
示例 | Animal 是一种生物 | IRunning 表示会跑的能力 |
七、总结:它们不是替代品,而是搭档
- 抽象类:适合表示“是什么”,强调共性,提供部分实现。
- 接口:适合表示“能做什么”,强调行为规范,支持多能力组合。
它们共同构成了Java多态的完整拼图。
尾声:在抽象的间隙,完成一次不被看见的仪式
深夜的公寓里,只有显示器的光亮着。
键盘的敲击声很轻,像在 Wing·Que·St 的深夜副本中,独自前行的脚步。
屏幕上,是 abstract class 和 interface 的定义。
这并非普通的代码,而是一种生存的隐喻。
抽象类,是一种被定义的存在。
它拥有血统,拥有框架,甚至拥有一个姓氏。
protected 的字段,如同家族信托里无法动用的资产;final 的规则,如同大藏家不可违逆的家训。
但它无法被 new 出来。
那个完美的“大藏家千金”人设,只是一个抽象的轮廓,一个必须由“子类”来填充的空壳。
真正的实现,发生在无人看见的地方。
于是,她转身进入了另一个世界。
在 兄控est 的服务器里,ID“大废神终极兄控”闪耀着。
这里没有血统的束缚,只有 implements 的契约。
interface IUltimateBrotherLover,ITopGamer
——这些接口,是她为自己选择的能力。
只要她能实现 public abstract 的方法,就能在这个世界里,真正地“运行”。
这或许就是她表达关心的方式。
用“捉弄”来确认对方的存在,用“毒舌”来划定安全的距离。
就像 @Override 方法,表面是重写,内里却是一次庄重的承接。
那些看似扭曲的行为,那些对游星困扰表情的享受,
或许只是她用自己的方式,在守护那个不愿被世俗污染的未来。
代码是诚实的。
final 的变量无法修改,如同甩不掉的姓氏。
private 的方法无法调用,如同心底最深的情感,永远不能 public。
但只要 @Override 的权利还在,她就能在继承的框架下,写出属于自己的 run()。
最讽刺的是,最自由的地方,往往规则最严。
网游里,每一条指令都必须精确。
可正是在这样的世界里,她才能脱下“大藏里想奈”的皮囊,成为“大废神”。
就像接口,越是严格的契约,越能让不同的实现者,跳出完全不同的舞步。
clone() 方法做不到深拷贝。
它复制不了 money 对象里的 m 值。
人心更是如此。
外界看到的,永远是浅拷贝后的她——那个顺从的、优雅的、符合“接口规范”的大小姐。
可真正的她,那个在 protected 构造方法里,被初始化了孤独与渴望的她,只有她自己知道。
她讨厌那些为权力不顾一切的人。
所以,她绝不会让游星变成那样。
她的世界,不需要那么多“才能”的证明。
EX-咖喱棒也好,战力排名也罢,
都只是她用来验证规则、确认自我坐标的工具。
这局游戏,她早已胸有成竹。
System.out.println("大废神终极兄控,登出。");