news 2026/5/10 1:56:28

Java——继承的细节

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java——继承的细节

继承的细节

    • 1、构造方法
      • 1.1、父类无默认构造
      • 1.2、父类构造调用可被重载的方法
    • 2、重名与静态绑定
      • 2.1、重名
    • 3、重载和重写
    • 4、父子类型转换
    • 5、继承访问权限protected
    • 6、可见性重写
    • 7、防止继承final

1、构造方法

1.1、父类无默认构造

子类可以通过super调用父类的构造方法,如果子类没有通过super调用,则会自动调动父类的默认构造方法,那如果父类没有默认构造方法呢?如下所示:

publicclassBase{privateStringmember;publicBase(Stringmember){this.member=member;}}

这个类只有一个带参数的构造方法,没有默认构造方法。这个时候,它的任何子类都必须在构造方法中通过super调用Base的带参数构造方法,如下所示,否则,Java会提示编译错误。

publicclassChildextendsBase{publicChild(Stringmember){super(member);}}

1.2、父类构造调用可被重载的方法

另外需要注意的是,如果在父类构造方法中调用了可被重写的方法,则可能会出现意想不到的结果。我们来看个例子,下面是基类代码:

publicclassBase{publicBase(){test();}publicvoidtest(){}}

构造方法调用了test()方法。这是子类代码:

publicclassChildextendsBase{privateinta=123;publicChild(){}publicvoidtest(){System.out.println(a);}}

子类有一个实例变量a,初始赋值为123,重写了test()方法,输出a的值。看下使用的代码:

publicstaticvoidmain(String[]args){Childc=newChild();c.test();}

输出结果是:

0 123

第一次输出为0,第二次输出为123。第一行为什么是0呢?第一次输出是在new过程中输出的,在new过程中,首先是初始化父类,父类构造方法调用test()方法,test()方法被子类重写了,就会调用子类的test()方法,子类方法访问子类实例变量a,而这个时候子类的实例变量的赋值语句和构造方法还没有执行,所以输出的是其默认值0。

像这样,在父类构造方法中调用可被子类重写的方法,是一种不好的实践,容易引起混淆,应该只调用private的方法。

2、重名与静态绑定

2.1、重名

子类可以重写父类非private的方法,当调用的时候,会动态绑定,执行子类的方法。那实例变量、静态方法和静态变量呢?它们可以重名吗?如果重名,访问的是哪一个呢?

重名是可以的,重名后实际上有两个变量或方法。

private变量和方法只能在类内访问,访问的也永远是当前类的,即:

  • 在子类中访问的是子类的;
  • 在父类中访问的是父类的,它们只是碰巧名字一样而已,没有任何关系。

public变量和方法,则要看如何访问它。

  • 在类内,访问的是当前类的,但子类可以通过super.明确指定访问父类的。
  • 在类外,则要看访问变量的静态类型:
    • 静态类型是父类,则访问父类的变量和方法;
    • 静态类型是子类,则访问的是子类的变量和方法。

我们来看个例子,这是基类代码:

publicclassBase{publicstaticStrings="static_base";publicStringm="base";publicstaticvoidstaticTest(){System.out.println("base static: "+s);}}

定义了一个public静态变量s,一个public实例变量m,一个静态方法staticTest。这是子类代码:

publicclassChildextendsBase{publicstaticStrings="child_base";publicStringm="child";publicstaticvoidstaticTest(){System.out.println("child static: "+s);}}

子类定义了和父类重名的变量和方法。对于一个子类对象,它就有了两份变量和方法,在子类内部访问的时候,访问的是子类的,或者说,子类变量和方法隐藏了父类对应的变量和方法,下面看一下外部访问的代码:

publicstaticvoidmain(String[]args){Childc=newChild();Baseb=c;System.out.println(b.s);System.out.println(b.m);b.staticTest();System.out.println(c.s);System.out.println(c.m);c.staticTest();}

以上代码创建了一个子类对象,然后将对象分别赋值给了子类引用变量c和父类引用变量b,然后通过b和c分别引用变量和方法。这里需要说明的是,静态变量和静态方法一般通过类名直接访问,但也可以通过类的对象访问。程序输出为:

static_base base basestatic:static_base child_base child childstatic:child_base

当通过b(静态类型Base)访问时,访问的是Base的变量和方法,当通过c(静态类型Child)访问时,访问的是Child的变量和方法,这称之为静态绑定,即访问绑定到变量的静态类型。静态绑定在程序编译阶段即可决定,而动态绑定则要等到程序运行时。*实例变量、静态变量、静态方法、private方法,都是静态绑定的。

3、重载和重写

重载是指方法名称相同但参数签名不同(参数个数、类型或顺序不同)​,重写是指子类重写与父类相同参数签名的方法。对一个函数调用而言,可能有多个匹配的方法,有时候选择哪一个并不是那么明显。我们来看个例子,这是基类代码:

publicclassBase{publicintsum(inta,intb){System.out.println("base_int_int");returna+b;}}

它定义了方法sum,下面是子类代码:

publicclassChildextendsBase{publiclongsum(longa,longb){System.out.println("child_long_long");returna+b;}}

以下是调用的代码:

publicstaticvoidmain(String[]args){Childc=newChild();inta=2;intb=3;c.sum(a,b);}

Child和Base都定义了sum方法,这里调用的是哪个sum方法呢?子类的sum方法参数类型虽然不完全匹配但是是兼容的,父类的sum方法参数类型是完全匹配的。程序输出为:

base_int_int

父类类型完全匹配的方法被调用了。如果父类代码改成下面这样呢?

publicclassBase{publiclongsum(inta,longb){System.out.println("base_int_long");returna+b;}}

父类方法类型也不完全匹配了。程序输出为:

base_int_long

调用的还是父类的方法。父类和子类的两个方法的类型都不完全匹配,为什么调用父类的呢?因为父类的更匹配一些。现在修改一下子类代码,更改为:

publicclassChildextendsBase{publiclongsum(inta,longb){System.out.println("child_int_long");returna+b;}}

程序输出变为了:

child_int_long

终于调用了子类的方法。可以看出,当有多个重名函数的时候,在决定要调用哪个函数的过程中,首先是按照参数类型进行匹配的,换句话说,寻找在所有重载版本中最匹配的,然后才看变量的动态类型,进行动态绑定。

4、父子类型转换

子类型的对象可以赋值给父类型的引用变量,这叫向上转型,那父类型的变量可以赋值给子类型的变量吗?或者说可以向下转型吗?语法上可以进行强制类型转换,但不一定能转换成功。我们以前面的例子来看:

Baseb=newChild();Childc=(Child)b;

Child c = (Child)b就是将变量b的类型强制转换为Child并赋值为c,这是没有问题的,因为b的动态类型就是Child,但下面的代码是不行的:

Baseb=newBase();Childc=(Child)b;

语法上Java不会报错,但运行时会抛出错误,错误为类型转换异常。

一个父类的变量能不能转换为一个子类的变量,取决于这个父类变量的动态类型(即引用的对象类型)是不是这个子类或这个子类的子类。

给定一个父类的变量能不能知道它到底是不是某个子类的对象,从而安全地进行类型转换呢?答案是可以,通过instanceof关键字,看下面代码:

publicbooleancanCast(Baseb){returnbinstanceofChild;}

这个函数返回Base类型变量是否可以转换为Child类型,instanceof前面是变量,后面是类,返回值是boolean值,表示变量引用的对象是不是该类或其子类的对象。

5、继承访问权限protected

变量和函数有public/private修饰符,public表示外部可以访问,private表示只能内部使用,还有一种可见性介于中间的修饰符protected,表示虽然不能被外部任意访问,但可被子类访问。另外,protected还表示可被同一个包中的其他类访问,不管其他类是不是该类的子类。我们来看个例子,这是基类代码:

publicclassBase{protectedintcurrentStep;protectedvoidstep1(){}protectedvoidstep2(){}publicvoidaction(){this.currentStep=1;step1();this.currentStep=2;step2();}}

action表示对外提供的行为,内部有两个步骤step1()和step2(),使用currentStep变量表示当前进行到了哪个步骤,step1()、step2()和currentStep是protected的,子类一般不重写action,而只重写step1和step2,同时,子类可以直接访问currentStep查看进行到了哪一步。子类的代码是:

publicclassChildextendsBase{protectedvoidstep1(){System.out.println("child step "+this.currentStep);}protectedvoidstep2(){System.out.println("child step "+this.currentStep);}}

使用Child的代码是:

publicstaticvoidmain(String[]args){Childc=newChild();c.action();}

输出为:

child step1child step2

基类定义了表示对外行为的方法action,并定义了可以被子类重写的两个步骤step1()和step2(),以及被子类查看的变量currentStep,子类通过重写protected方法step1()和step2()来修改对外的行为。

这种思路和设计是一种设计模式,称之为模板方法。action方法就是一个模板方法,它定义了实现的模板,而具体实现则由子类提供。模板方法在很多框架中有广泛的应用,这是使用protected的一种常见场景。

6、可见性重写

重写方法时,一般并不会修改方法的可见性。但我们还是要说明一点,重写时,子类方法不能降低父类方法的可见性。不能降低是指,父类如果是public,则子类也必须是public,父类如果是protected,子类可以是protected,也可以是public,即子类可以升级父类方法的可见性但不能降低。看个例子,基类代码为:

publicclassBase{protectedvoidprotect(){}publicvoidopen(){}}

子类代码为:

publicclassChildextendsBase{//以下是不允许的,会有编译错误//private void protect(){//}//以下是不允许的,会有编译错误//protected void open(){//}publicvoidprotect(){}}

为什么要这样规定呢?继承反映的是“is-a”的关系,即子类对象也属于父类,子类必须支持父类所有对外的行为,将可见性降低就会减少子类对外的行为,从而破坏“is-a”的关系,但子类可以增加父类的行为,所以提升可见性是没有问题的。

7、防止继承final

继承是把双刃剑,带来的影响就是,有的时候我们不希望父类方法被子类重写,有的时候甚至不希望类被继承,可以通过final关键字实现。final关键字可以修饰变量,而这是final的另一种用法。一个Java类,默认情况下都是可以被继承的,但加了final关键字之后就不能被继承了,如下所示:

publicfinalclassBase{//主体代码}

一个非final的类,其中的public/protected实例方法默认情况下都是可以被重写的,但加了final关键字后就不能被重写了,如下所示:

publicclassBase{publicfinalvoidtest(){System.out.println("不能被重写");}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/10 1:52:57

基于MCP协议构建AI智能体工具服务器:原理、安全与实践

1. 项目概述:一个为AI智能体赋能的MCP服务器最近在折腾AI智能体(Agent)的开发,发现一个挺有意思的痛点:如何让这些智能体稳定、安全地访问外部工具和资源?比如,你想让一个智能体帮你分析GitHub仓…

作者头像 李华
网站建设 2026/5/10 1:51:31

传统PM转型AI产品经理:我踩过的3个坑

本文是一位传统产品经理转型AI产品经理的心路历程。作者从对AI一无所知,到通过实践学习如何与AI协作,总结出三条转型路径:从“用”AI提效开始建立感知;完整主导一个AI功能从0到1;有选择地补充AI基础知识。转型关键在于…

作者头像 李华
网站建设 2026/5/10 1:50:41

在Hermes Agent中配置自定义供应商指向Taotoken的详细流程

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 在Hermes Agent中配置自定义供应商指向Taotoken的详细流程 Hermes Agent 是一款功能强大的AI代理开发框架,支持通过自定…

作者头像 李华
网站建设 2026/5/10 1:48:30

vcs后仿(+sdf)踩坑记录 外围协议接口 双端握手异步

vip信号在没有drv delay情况下,监测到握手后发生信号变化,若此时dut内部握手尚未完成则会导致test fail,且在接口层面难以发现如何无视vip与dut的clk相位差,依靠为vip drv信号增加一个合适的delay以保证dut握手成功问题示例&#…

作者头像 李华
网站建设 2026/5/10 1:47:31

大语言模型多智能体系统:架构、应用与挑战

1. 从单兵作战到团队协作:大语言模型多智能体系统的演进与核心架构如果你在过去一年里深度使用过ChatGPT、Claude或者国内的文心一言、通义千问等大模型,你可能会有一个直观的感受:单个大模型在回答具体问题、生成文本或代码片段时已经相当出…

作者头像 李华
网站建设 2026/5/10 1:46:49

UseAI:基于MCP协议的本地AI编码分析工具,量化人机协作效率

1. 项目概述:为你的AI编码之旅装上一个“本地黑匣子”如果你和我一样,每天花大量时间与Claude Code、Cursor、GitHub Copilot这些AI编程助手打交道,那你可能也好奇过:我到底用了多久?哪种工具效率最高?我的…

作者头像 李华