1. 面试必考但总说不清的问题:继承和多态到底什么关系
做了这么多年Java,被问到最多、也最容易被问住的问题之一,就是“多态和继承有什么区别”。这问题看着简单,却有两种典型表现:一类人张口就来“继承是一个类继承另一个类的属性方法,多态就是一个对象多种形态”,说完自己都觉得空;另一类人抛开八股文去讲JVM虚方法表,结果面试官反手一句“那没有继承能有多态吗”,直接卡壳。
先说结论:继承和多态根本不是一个维度的东西。继承是一套代码组织与复用机制,解决的是“类之间如何建立关系、子类如何复用父类结构”;多态是一套行为分发机制,解决的是“同一个调用动作,在不同对象上如何表现出不同行为”。两者在Java里经常搭配出现,所以容易被当成一回事,但它们的职责边界、应用场景、背后原理完全不同。这篇文章我就把这个话题彻底拆开,从原理、代码、JVM机制到面试回答策略全部过一遍,希望帮你建立一套能真正讲清楚这个问题的心智模型。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 继承的本质:不只是语法糖,而是一套代码复用契约
2.1 继承到底解决了什么问题
先想一个问题:没有继承的时候,我们写代码会遇到什么麻烦?
最直接的麻烦是复用困难。假设你要写一个订单系统,里面有不同的订单类型:普通订单、秒杀订单、跨境订单。它们有大量公共字段——订单号、用户ID、金额、状态、创建时间;也有一批公共方法——创建订单、取消订单、查询状态。如果没有继承,最朴素的做法是每个订单类把这些字段和方法都抄一遍,或者搞一个Util工具类把公共逻辑抽出来。字段可以复制,但每个订单类里的相同逻辑一旦要改,就得改三处,漏改一处就等着线上出bug。
继承的出现,就是让你把公共的部分沉淀到一个父类里,由子类自动获得这些结构。普通订单、秒杀订单、跨境订单都继承同一个BaseOrder,那么订单号、用户ID这些字段和创建订单这些方法,子类天然具备。从代码组织的角度讲,继承建立的是一种is-a关系:子类首先是一个父类,然后才是它自己。
2.2 继承建立的依赖关系远比你看到的复杂
但很多人在学到继承的时候,只记住了“子类能复用父类的代码”,却没意识到继承同时建立了非常强的编译期依赖关系。
看这个例子:
java复制public class BaseOrder {
protected String orderNo;
protected BigDecimal amount;
public void create() {
validate();
save();
notifyUser();
}
protected void validate() {
// 基础校验:订单号非空、金额大于0
}
private void save() {
// 写入数据库
}
protected void notifyUser() {
// 发送通知
}
}
public class SeckillOrder extends BaseOrder {
@Override
protected void validate() {
// 秒杀订单额外校验:是否在秒杀时间内、是否限购
}
}
这个例子中,子类可以覆写validate方法,但父类create方法里“先校验、再保存、后通知”的流程骨架,子类是不能改变的——除非你把create也覆写掉。这种设计在模板方法模式里很常见,父类定义算法骨架,子类填充可变细节。
2.3 JVM眼中的继承:字段布局与方法查找
从JVM视角看,继承关系一旦确定,内存布局也基本确定了。子类对象的内存空间包含父类的字段和子类自己的字段。JVM在分配对象空间时,会把父类字段排在前面,子类字段排在后面,这就是为什么子类对象可以直接访问父类的protected、public字段——它们在物理地址上是连续的、确定归属的一块区域。
方法调用则要走虚方法表(vtable)。每个类加载后都会生成一张方法表,表里记录了该类所有虚方法的实际入口地址。子类继承了父类的方法,如果子类没有覆写,那虚方法表里这个位置指向父类的实现;如果子类覆写了,就指向子类的实现。这就是“覆写”这个行为的底层机制。说句题外话,理解了虚方法表,也就理解了为什么@Override注解拦不住你写一个签名拼写错误的“假覆写”——签名不匹配时,编译器认为你在新增方法,虚方法表里父类方法仍然指向父类实现。
3. 多态的本质:编译期的静态承诺与运行期的动态抉择
3.1 多态的三个必要条件缺一不可
教科书上写的好听:多态是同一个行为具有多个不同表现形式的能力。但在Java里,多态要真正发生,必须同时满足三个条件:
- 继承或实现:一个类继承另一个类,或一个类实现某个接口;
- 方法覆写:子类对父类的方法进行覆写,或实现类对接口方法进行实现;
- 父类引用指向子类对象:声明类型是父类(或接口),实际对象类型是子类。
这三个条件缺一不可。我常常在面试中遇到候选人说“重载也是多态”,然后被我追问一句“这个重载发生在编译期还是运行期”,就开始支支吾吾。后面我专门有一节讲重载和多态的关系,这里先不展开。
3.2 动态绑定:一个方法调用怎么找到真正的执行入口
多态机制的核心是“方法调用在编译期不能被确定到具体实现类”。来看这段代码:
java复制public class DiscountCalculator {
public BigDecimal calculate(BaseOrder order) {
return order.calculateDiscount();
}
}
public class BaseOrder {
public BigDecimal calculateDiscount() {
return BigDecimal.ZERO;
}
}
public class SeckillOrder extends BaseOrder {
@Override
public BigDecimal calculateDiscount() {
return this.amount.multiply(new BigDecimal("0.5"));
}
}
public class CrossBorderOrder extends BaseOrder {
@Override
public BigDecimal calculateDiscount() {
return this.amount.multiply(new BigDecimal("0.8"));
}
}
DiscountCalculator的calculate方法,参数类型是BaseOrder。编译期,编译器只能确认order变量具备BaseOrder这个类型,所以它检查的是BaseOrder里有没有calculateDiscount方法。至于具体调用SeckillOrder还是CrossBorderOrder的实现,编译器压根不关心。
到了运行期,order变量指向的实际对象如果是SeckillOrder,JVM就通过SeckillOrder类的虚方法表,找到覆写后的calculateDiscount入口;如果是CrossBorderOrder,就走另一条路径。这个依据运行期实际类型来寻找方法入口的过程,就叫动态绑定,也叫后期绑定。
代码里体现的类型约定是静态承诺,运行期的对象分发是动态抉择。多态的核心,就是这两个时间的分离。
3.3 一个对象可能真的有多种类型身份
“一个对象多种形态”这句话不是口号,是可以用代码验证的:
java复制BaseOrder order1 = new SeckillOrder();
BaseOrder order2 = new CrossBorderOrder();
SeckillOrder order3 = new SeckillOrder();
System.out.println(order1 instanceof BaseOrder); // true
System.out.println(order1 instanceof SeckillOrder); // true
System.out.println(order2 instanceof CrossBorderOrder); // true
System.out.println(order3 instanceof SeckillOrder); // true
一个SeckillOrder对象,既有SeckillOrder的类型身份,也有BaseOrder的类型身份。这跟现实中“一个人同时是程序员、是员工、是孩子的父亲”是同一个逻辑。Java里把这种多重类型身份用继承链串了起来,运行期JVM可以在这个链上判断类型归属。
4. 多态与继承的核心区别:一个管代码归属,一个管行为分发
4.1 两句话先立住框架
用最简单的话来讲:
- 继承解决“代码属于谁”:子类拥有父类的字段和方法,这是一种静态的、编译期已经确定的关系。
- 多态解决“行为怎么做”:同一方法调用在不同对象上有不同表现,这是一种动态的、运行期才确定的行为分发。
继承是静态关系——A类和B类之间的结构关系在编译期就锁死了;多态是动态行为——调用哪个实现方法是在运行期才发生的决策。
所以“多态跟继承的区别”这个问题,本质是在问:代码的组织方式和运行时的行为决策机制之间的区别。
4.2 从代码层面看两者的因果关系
为了更直观地看到区别,可以设计一个不依赖继承的多态场景。在Java里,多态可以通过接口实现,接口不涉及继承的字段复用,但同样能产生多态效果:
java复制public interface Payable {
void pay();
}
public class WeChatPay implements Payable {
@Override
public void pay() {
System.out.println("微信支付");
}
}
public class Alipay implements Payable {
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
public class PaymentService {
public void doPay(Payable payable) {
payable.pay();
}
}
PaymentService.doPay方法,接收一个Payable接口,但我们永远不会知道运行期传进来的是WeChatPay还是Alipay。这里没有继承,只有接口实现,但多态效果照样成立——这就是为什么说继承不是多态的必要条件,接口才是更纯粹的多态载体。
继承在这里起什么作用?如果你用继承来实现上述场景,写法上最明显的差异是:子类可以直接获得父类的字段和方法,比如支付服务里需要有商户号、支付密钥这些公共配置,放父类或公共基类里,子类直接继承拿来用。这就是“代码归属”层面的收益。
4.3 继承关心的是结构,多态关心的是契约
在设计层面,继承跟多态的关注点很不一样。
继承更多是一种“实现层面的复用手段”。基类里有现成的字段、方法、流程,子类拿过来直接用,想改就覆写。父类为子类提供了一套可复用的实现细节。这种关系在类层次上非常具体,层级越深,耦合越大,改父类可能引发不可预知的连锁反应。
多态强调的则更像一种“契约层面的抽象”。调用方只关心“这个对象能不能执行某个行为”,而不关心它是谁、怎么实现的。它的底层支撑是接口、抽象方法、虚方法表这些机制,保证“只要你能被当成Payable用,我就敢调用你的pay方法,具体怎么付我不管”。
4.4 一张表把区别彻底讲清楚
来做个系统性的对比,后面面试可以直接照着这个思路组织语言:
| 维度 | 继承 | 多态 |
|---|---|---|
| 核心焦点 | 代码的归属和复用 | 行为的分发和表现 |
| 关系类型 | 静态的编译期关系(is-a) | 动态的运行期行为决策 |
| 作用对象 | 类(class)与类之间 | 方法(method)与对象之间 |
| 载体 | 父类/子类继承链 | 方法覆写、接口实现、父类引用指向子类对象 |
| 发生在哪个阶段 | 编译期 | 运行期(动态绑定) |
| 依赖关系 | 子类强依赖父类 | 调用方依赖抽象,不依赖具体实现 |
| 违反时的后果 | 代码耦合、继承链爆炸 | 无法实现面向接口编程,行为无法扩展 |
| 代码中的体现 | extends关键字 | @Override + 父类引用指向子类对象 |
| 可以独立存在吗 | 可以,但只有继承没有多态时,设计非常僵硬 | 可以,通过接口实现多态,无需继承 |
5. 多态的三种实现路径:重写、接口与重载的真正边界
5.1 覆写才是运行时多态的第一功臣
Java里多态最基础的实现路径就是方法覆写。只有子类真正覆写了父类的方法,动态绑定才有意义。如果子类没有覆写,那调用的是父类实现,这不算多态——只是普通的方法继承。
写代码时有个小细节容易踩坑:@Override注解本身不参与方法匹配,但它能在编译期帮你检查是否真正覆写了某个父类方法。如果你写了一个方法,签名跟父类不一致,编译器会立刻报错,这能在源头避免“我以为覆写了,其实没有”的情况。
还有一个经典坑位:覆写方法的访问权限不能比父类更严格。父类是public,子类覆写时绝不能是protected或private。因为多态成立的前提是“父类引用指向子类对象”,如果子类把访问权限缩小了,外部通过父类引用调方法时,就可能出现“声明的是public,实际执行的是private”,这在编译期无法保证安全性。Java的设计很简单粗暴:禁止这种访问权限缩小。
5.2 接口抽象:比继承更优美的多态载体
前面已经提到,接口可以不依赖继承实现多态。Java 8之后接口还能写默认方法,这让接口既有抽象约束能力,也有一定的实现复用能力。
从设计角度看,接口是比继承更推荐的多态载体。经典的主从模式或者策略模式里,你定义一个有handle()方法的接口,然后写几十个实现类,调用方只依赖这个接口,新加一个实现类不需要改调用方代码——这就是典型的开闭原则实践,也是“面向接口编程”而非“面向实现编程”的体现。
在大型项目里,我倾向于让模块与模块之间的交互走接口,业务实体的层级复用才用继承。为什么?因为接口天然是天然的边界,一个模块对另一个模块只暴露一个契约,内部怎么实现可以随便换;继承则把父类的内部实现细节直接暴露给了子类,外部对内部结构的依赖无法避免,稳定性和灵活性都会打折扣。
5.3 重载是不是多态?这个问题背后有讲究
很多教科书把重载称为编译期多态或静态多态,但严谨来说,Java里的“多态”一词通常不加限定地指运行时多态。重载(Overload)发生在编译期,编译器根据参数的静态类型和方法签名组合,在编译时直接决定调用哪一个重载版本,不涉及运行期的动态绑定。
看个例子:
java复制public class Printer {
public void print(String s) {
System.out.println("print(String): " + s);
}
public void print(Integer i) {
System.out.println("print(Integer): " + i);
}
public void print(Object o) {
System.out.println("print(Object): " + o);
}
}
public class Main {
public static void main(String[] args) {
Printer p = new Printer();
Object obj = "hello";
p.print(obj); // 编译期就确定调用 print(Object)
}
}
打印结果是print(Object): hello,而不是print(String): hello。这就是编译期根据静态类型做出的决策——虽然obj的实际类型是String,但编译期变量obj的声明类型是Object,所以选了print(Object)。重载是方法名相同、参数列表不同的多版本文案,编译期就定死了。
所以关于重载的归属问题,我的明确结论是:如果你面试时跟面试官讨论多态的常见考题,可以体现你的严谨——**Java里常说的多态特指运行时多态,重载是编译期的静态决策,两者不能混为一谈。**时间紧的话记住这个表述就行。
6. 继承的坑位盘点:菱形继承、破坏封装与“组合优于继承”
6.1 为什么Java不允许多继承,C++却可以
网上关于“菱形继承”的讨论很多,但放到Java语境里,它更像一个设计决策的注脚:Java直接禁止了类的多继承,所以不会出现C++那套复杂的菱形继承问题。
菱形继承的场景是:B和C都继承A,D同时继承B和C,那D里从A继承下来的那份成员,到底是从B路径来还是从C路径来?两份拷贝的字段如何初始化?这直接导致代码在运行期的状态没法一条线捋清楚。C++用虚继承来解决,但虚继承又带来额外的复杂性和性能开销。
Java选择弃疗,直接不让你类多继承。如果确实需要多个父类的能力,可以通过接口多实现来实现。类只能单继承,但接口可以多继承接口、类可以实现多个接口——这个设计就是特意避开菱形继承的坑,同时保留了部分多继承的表达能力。
6.2 继承最容易翻车的三个场景
第一,继承破坏封装。子类一旦继承父类,父类的protected成员就对子类可见,子类可以直接修改父类的内部状态。如果父类设计时没有做好不可变性控制,子类就可能在某个覆盖方法里把父类的核心字段改坏了,而且排查起来极其困难。
第二,继承链过深导致职责混乱。很多人追求“公共代码尽可能往上提”,结果四五层继承之后,每个层级都塞了一堆隐含假设。到了最底层,子类能用的方法有一半是根本不该暴露给它的。我自己见过一个项目,一个用户类从BaseEntity继承,BaseEntity又从BaseDomainObject继承,网上还到处是这种例子。这种链子上加一个字段,改动波及的范围足以让整个改版延期。
第三,覆写方法时没有遵循父类的设计契约。Java类库设计常常用protected方法加注释的方式表达“这个方法是给人覆写的”,但业务代码里往往缺乏类似的约定。当你覆写一个父类方法时,未必清楚父类方法内部还做了其他事情,覆盖后新的行为可能与父类原有逻辑不一致,最终结果就变得非预期。
6.3 什么场景值得用继承,什么场景应该改用组合
先声明一个行业共识:组合优于继承。这不是说继承没用,而是说大多数场景下,组合能获得继承的复用效果,同时避免继承的耦合。
组合的典型写法:
java复制public class OrderService {
private final OrderRepository orderRepository;
private final DiscountCalculator discountCalculator;
public OrderService(OrderRepository orderRepository, DiscountCalculator discountCalculator) {
this.orderRepository = orderRepository;
this.discountCalculator = discountCalculator;
}
}
OrderService不继承任何东西,但它拥有了OrderRepository的持久化能力和DiscountCalculator的折扣计算能力——这是通过持有对象的引用完成的。换掉DiscountCalculator的实现在这里很容易,构造时传入新实例就行,不需要动OrderService的代码。如果用继承,这个替换往往意味着创建一个新的OrderService子类,类数量就会指数级膨胀。
继承适合什么场景?核心只有一个:当你要表达的是真正稳定的is-a关系,并且父类提供了模板方法骨架,子类只变动少量细节时。比如前述BaseOrder和SeckillOrder的关系。除此之外,优先考虑接口+组合的做法,代码会更灵活,也更可测。
7. 从面试官角度拆解:这道题到底在考什么
7.1 三个递进的回答层级
这道题之所以高频出现在八股文里,不是因为它本身有多难,而是它是一个极好的“递进考察点”。面试官可以通过候选人的回答,快速判断他的Java基础到了哪个层级。
第一层:能背出定义。继承是一个类获得另一个类的属性和方法;多态是同一个方法调用表现不同。这个层级只能证明你看过课本,过不了几轮追问。
第二层:能结合代码讲。知道覆写、接口、父类引用指向子类对象这三点,能从静态类型和动态类型的维度解释多态,知道动态绑定发生在运行期。这个层级说明你有一定的动手经验。
第三层:能从设计角度谈。知道继承是静态关系、多态是动态行为;知道继承侧重代码复用、多态侧重行为契约;知道组合优于继承背后的耦合考量;还能说出重载和覆写的区别、虚方法表是怎么回事。这个层级意味着你不仅会写代码,还能在系统设计里合理运用这些语言特性。
我个人给团队做面试时,遇到的候选人大多卡在第二层和第三层之间。能讲清楚“继承是一种静态关系,多态是一种动态关系”的,本身已经是基本功非常扎实的信号。
7.2 面试官会追问的几个坑
围绕这个问题,面试官通常还会变着法子问下面这些,你可以提前准备一下:
- 父类引用指向子类对象,能访问子类中独有的方法吗?答:不能。编译期静态类型决定可访问的成员,子类独有的方法必须強转成子类类型后才能调用。
- 静态方法能被覆写吗?答:不能。静态方法是类级别的,通过父类引用调用静态方法时,走的是编译期的静态绑定,跟对象实际类型无关。经典例子是“隐藏”而不是“覆写”。
- 私有方法和构造器能被覆写吗?答:不能。private方法对子类不可见,构造器不是普通方法,不走动态绑定。
- final类能被继承吗?final方法能被覆写吗?答:都不能。final关键字从编译期阻塞继承与覆写,这也是一种保护不变量和管理继承面大小的手段。
准备这些问题时,别只背结论,自己动手写个小demo验证,印象会深得多。
7.3 我建议的标准回答框架
如果在面试中被问到“Java中多态跟继承的区别”,我会按这个顺序组织答案,既清晰又分层次:
- 一句话定性:继承是一种静态的代码组织关系,多态是一种动态的运行时行为决策机制。
- 说继承:继承解决的是类的复用与层级归属,子类获得父类成员,是一种编译期的is-a关系,这种关系一旦建立基本就固定了。
- 说多态:多态解决的是行为的按需分发,依赖方法覆写、接口实现和父类引用指向子类对象,真正的落脚点是运行期的动态绑定。
- 说关联:继承可以为多态提供实现基础,但不是多态的必要条件——接口同样能做到多态,甚至在很多场景下是更合适的选择。
- 收尾拔高:理解了继承和多态的区别,背后真正体现的是面向对象设计中“关注变化与隔离变化”的思想。继承关注稳定结构的复用,多态关注可变行为的扩展。
这套框架既覆盖了基本概念、原理和代码层内容,又能延展到设计思想层面,属于比较标准的“加分回答”。
8. 实战中如何用好继承和多态:一个完整的项目演化例子
8.1 从最朴素的实现到多态化改造
理论说了这么多,还是看一个实际的演化过程。假设你要做一个消息推送系统,支持短信、邮件、站内信三种推送方式。
第一版,你可能会写出这样的代码:
java复制public class NotifyService {
public void send(int type, String userId, String content) {
if (type == 1) {
System.out.println("发送短信: " + userId + " - " + content);
} else if (type == 2) {
System.out.println("发送邮件: " + userId + " - " + content);
} else if (type == 3) {
System.out.println("发送站内信: " + userId + " - " + content);
}
}
}
这段代码的问题是显而易见的:新增一种推送渠道,就得往这里加一个else if,NotifyService越来越胖。这是典型的面向过程式写法,继承和多态都没有发挥作用,代码僵化、难以测试、难以扩展。
第二版,我们引入接口和多态:
java复制public interface Notifier {
void send(String userId, String content);
}
public class SmsNotifier implements Notifier {
@Override
public void send(String userId, String content) {
System.out.println("发送短信: " + userId + " - " + content);
}
}
public class EmailNotifier implements Notifier {
@Override
public void send(String userId, String content) {
System.out.println("发送邮件: " + userId + " - " + content);
}
}
public class InAppNotifier implements Notifier {
@Override
public void send(String userId, String content) {
System.out.println("发送站内信: " + userId + " - " + content);
}
}
public class NotifyService {
private final Notifier notifier;
public NotifyService(Notifier notifier) {
this.notifier = notifier;
}
public void send(String userId, String content) {
notifier.send(userId, content);
}
}
改造后,NotifyService只依赖Notifier接口,新增渠道只需要写一个新实现类,然后组合给NotifyService就行,NotifyService本身一行都不用改。这就是多态带来的扩展力。
第三版,如果渠道间有公共逻辑——比如发送前要校验用户是否有接收权限、发送后要记录日志,就可以用继承来沉淀这部分公共逻辑:
java复制public abstract class AbstractNotifier implements Notifier {
@Override
public void send(String userId, String content) {
checkPermission(userId);
doSend(userId, content);
recordLog(userId, content);
}
protected abstract void doSend(String userId, String content);
private void checkPermission(String userId) {
// 校验用户是否允许接收通知
}
private void recordLog(String userId, String content) {
// 记录发送日志
}
}
public class SmsNotifier extends AbstractNotifier {
@Override
protected void doSend(String userId, String content) {
System.out.println("发送短信: " + userId + " - " + content);
}
}
这个例子里,接口负责多态,让NotifyService面向抽象编程;抽象类负责代码复用,把公共流程固定下来,子类只实现自己独特的部分。两者协作,各司其职——继承负责复用骨架,多态负责行为分发。这是我认为最标准的“继承+多态搭配使用”的方式。
8.2 选型清单:什么时候上继承,什么时候上接口
在实战里做设计决策时,我给自己定了一套判断清单,你可以直接抄:
- 希望复用公共实现代码,且多个子类有大量相同逻辑 -> 考虑继承抽象类。
- 希望定义行为契约,允许实现方式千差万别 -> 选接口。
- 某个能力将来可能要替换成完全不同的实现 -> 选接口+组合。
- 类的层级关系稳定,几乎不随需求变化 -> 继承是安全的。
- 类的层级关系不稳定,今天可能是子类明天就不是了 -> 绝对别用继承,用组合。
8.3 实测下来需要避开的操作细节
这套方案里最容易被忽视的有两个细节。第一,抽象类的构造器。抽象类不能实例化,但子类实例化时一定会调用抽象类的构造器。如果抽象类的构造器依赖某个实例方法,而实例方法被子类覆写了,就可能在子类尚未完成初始化时调用子类逻辑,产生空指针。保险做法:抽象类构造器只做最简单、最确定的初始化,不要在构造器里调用可被覆写的方法。
第二,父类里的protected成员可见性问题。虽然子类和父类在不同包时,子类可以通过继承访问父类protected成员,但这种访问是“白名单制”,一旦把protected权限扩大到public,就打破了封装边界。我一般约定:父类的protected方法都必须加注释标明用途和覆写前提,确保子类覆写时清楚行为契约。
9. 彻底搞懂这题之后,你还能带走什么
这个问题走到这里已经比较深了,但我想说的是,面试问“多态和继承的区别”,真正的意图往往不在问题的字面答案上,而在于考察你对面向对象编程的理解是否足够体系化。
如果你能顺着多态讲出静态绑定、动态绑定、虚方法表;能顺着继承讲出单继承、接口多实现、组合优于继承;还能回到设计层面分析什么时候该用继承、什么时候该用接口——那这道题你已经完全拿下了。
最近在带团队做代码评审的时候,我特别留意大家有没有下意识地过度使用继承。看到有人动不动就extends一个不相关的业务类,我会先问一句:你是想要它的“代码”,还是想要它的“能力”?要代码,看看能不能抽个公共组件;要能力,看看能不能通过接口实现多态。这样一番对照下来,继承和多态的区别就从一个面经问题,变成了真正能指导你日常设计编码的思维方式。
