关于Java的多态(详解)
在面向对象程序设计中,多态是继数据抽象和继承之后第三种基本特征
多态通过分离做什么和怎么做,从另一种角度将接口与实现分离开来
多态可以消除类型之间的耦合关系。继承允许将对象是为它自己本身的类型或者其基类型来处理,这就使得继承允许将多种由同一基类导出的类型视为一种类型来处理,从而使一份代码可以毫无差别的运行在不同类型之上
向上转型 {
对象既可以作为自己本身的类型来使用,也可以作为基类型来使用
把某个对象的引用视为其基类类型的引用的做法称之为向上转型 {
这里的转换场合有很多 {
方法的形参列表 {
在传递参数的时候,形参为基类型,实参为导出类型,编译器会自动进行向上转型 {
class Super {
private void g() {
System.out.println(this.toString);
}
public void f(Super s) {
s.g();
}
}
public class Sub extends Super {
Super sup;
public static void main(String[] args) {
sup = new Sub();
}
}
这样做的好处在于不必为每一个继承自Super类的对象编写f(),g()方法;这两个方法可以接受任意继承自Super类型引用作为参数,也就是说,它们可以处理所有该类型的消息
}
}
成员变量 {
可以对成员变量进行向上转型 {
class Super {}
public class Sub extends Super {
Super s = new Sub();
}
}
}
局部变量 {
class Super {}
public class Sub extends Super {
public static void main(String[] args) {
Super s = new Sub();
}
}
}
}
}
}
方法调用绑定 {
将一个方法调用同一个方法主体关联起来被称为绑定。
若程序执行前进行绑定(由编译器和链接程序实现),叫做前期绑定,这种绑定方式是面向过程的语言中的默认绑定方式
在运行时根据对象的类型进行绑定叫做后期绑定,也称之为动态绑定和运行时绑定。编译器在编译代码的时候不知道对象的类型,但是方法调用机制却可以找到正确的方法,并加以调用
在java中,除了static 方法和final方法,都是后期绑定的
}
产生正确的行为 {
由于java中方法都是通过动态绑定实现多态的,所以我们可以编写只与基类打交道的代码了,这些代码对于所有导出类都可以正确的运行。或者说,发送某个消息给对象,让对象判定应该做什么事。
}
可扩展性 {
由于有多态的机制,我们可以根据自己所需对系统添加任意多的新类型,而不必修改方法。在一个良好的面向对象程序中,大多数方法都会遵循基类方法的模型,而且只与基类接口通信。这样的程序是可扩展的,因为以通用的基类继承出新的数据类型,从而添加一些新的功能。那些操作基类接口的方法不需要任何改动就可以应用于新类。
}
覆盖私有方法 {
私有方法被自动认为是final形式的,所以会对导出类屏蔽。因此,当我们在导出类中定义了一个与父类中同名的方法时,是不会产生多态的行为的,因为我们相当于直接在导出类中新定义了一个方法,与父类无任何关系,而且编译器不会报错。所以在到处类中,对于基类的private方法,最好不要使用相同的名字。
}
域与静态方法 {
在多态机制中,只有普通方法是可以多态发生的。例如,当你直接访问某个域,这个访问就在编译时进行解析。当我们使用父类引用指向子类对象时,使用父类引用访问成员变量实际上是访问父类的成员变量。
如果方法是静态的,它就不具有多态性。静态方法是与类,而并非某个对象相关联的。
}
构造器与多态 {
构造器并不具有多态性,因为构造器是static方法,只不过该static的声明是隐形的。
基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次向上链接,以使得每个基类的构造器都被调用。
构造器检查对象是否被正确地构造。导出类只能访问自己的成员,不能访问基类成员(基类成员通常是private类型)。只有基类的构造器才有恰当的知识和权限对自己的元素进行初始化。
构造器的调用顺序是很重要的。当进行继承时,我们已经知道了基类的一切,并且可以访问基类中的public和protected的成员。这意味着在导出类中,必须确保基类的所有成员都是有效的(都可以访问并且使用)。一种标准方法是,构造动作一经发生,那么对象所有部分的全体成员都会得到构建。然而,在构造器内部,我们必须确保所要使用的的成员都已经构建完成。为了确保这一目的,唯一的办法就是首先调用基类的构造器。那么在进入导出类的构造器时,在基类中可供我们访问的成员都已初始化。
}
继承与清理 {
通过组合与继承的方式创建新类时,永远不必担心对象的清理问题,子类对象通常会留给垃圾回收器进行清理。如果确实遇到清理问题,可以在类中创建清理方法,导出类应该覆盖基类的清理方法,同时在该方法中调用基类的清理方法,否则,基类的清理动作就不会发生。
在销毁对象时,应该先销毁导出类对象,因为万一某个子类对象依赖与其他对象,销毁的顺序应该与初始化顺序相反。对于字段,则意味着与声明的顺序相反(因为字段的初始化是按照声明的顺序进行的)
}
构造器内部的多态方法的行为 {
构造器调用的层次结构带来了一个有趣的两难问题。如果在一个构造器的内部调用正在构造对象的某个动态绑定方法,那么会发生什么情况呢?
如果我们在基类的构造器中调用具有多态性的方法,那么编译器就会调用导出类中的方法,而此时导出类还没有被初始化,所以当中的所有成员都被设置为0,所以该方法所操作的成员数据只有初始默认值。
因此,编写构造器时有一条准则:“用尽可能简单的方式是对象进入正常状态;如果可以的话,不要调用其它方法。”在构造器内部唯一会被安全调用的方法就是final和private修饰的方法。
}
协变与逆变 {
协变返回类型表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类。
Java作为面向对象的典型语言,在类的继承派生中,是符合Liskov(里氏替换)替换原则(LSP)的。LSP总结起来,就一句话:
所有引用基类的地方必须能够透明地使用其子类对象。
LSP包含四层含义:
① 子类完全拥有父类的方法,且具体子类必须实现父类的抽象方法;
② 子类中可以增加自己的方法;
③ 当子类覆盖或实现父类的方法时,方法的形参要比父类方法的更加宽松;
④ 当子类覆盖或实现父类的方法时,方法的返回值要比父类方法的更加严格。
针对LSP四层含义的③④条,就引出了协变(Covariance)和逆变(Contravariance)的概念:
协变,简言之,就是父类型到子类型,变得越来越具体,在Java中体现在返回值类型不变或更加具体(异常类型也是如此)等。
逆变,简言之,就是父类型到子类型,变得越来越具体,但是方法的形参却变得更加抽象或不变(注意:这里在Java中本质为方法重载,而不是覆盖,当添加@Override标签将会报错!)
}
使用继承进行设计 {
当我们使用现成的类设计新类时,应该首先考虑组合。组合不会强制我们的程序进入继承设计的结构中去。而且,组合更加灵活,因为它可以动态地选择类型,相反,继承在编译时就需要知道确切的类型。
纯继承与拓展 {
采取“纯粹”的方式来创建继承层次结构似乎是最好的方式,也就是说,只有在基类中经建立的方法才可以在导出类中被覆盖。
这被称作是纯粹"is-a"的关系,因为一个类的接口已经确定了它因该是什么。继承可以确保所有的导出类具有基类的接口,而且绝对不会少。也就是说,基类可以接受任何发送给导出类对象的消息,因为二者具有完全相同的接口。我们只需要从导出类向上转型,永远不需要知道正在处理对象的确切类型。所有这一切,都是通过多态实现的。
然而,在实际开发中,我们有时会不可避免地拓展接口,这可以称为"is-like-a"的关系,因为导出类具有和基类相同的基本接口,但是它还有其它的接口。
这导致导出类中拓展的接口无法通过基类访问,因此,一旦我们向上转型,就不能调用那些新方法。
}
向下转型与运行时类型识别 {
由于向上转型会丢失具体类的类型信息,所以我们就想,通过向下转型因该能够获取类型信息。
通过向下转型(在继承层次中向下移动)可以获取具体的类型信息;但是,我们无法确知一个“几何形状”是一个圆,还是一个三角形
在Java中,所有转型都会得到检查;即使只是进行一次普通的加括弧形式的类型转换,在进入运行时仍然会对其进行检查,以保证它的确是我们希望的那种类型;如果不是,就会返回一个ClassCastException(类转型异常);这种在运行期间对类型进行检查的行为称作“运行时类型识别”(RTTI)
关于javainstanceof运算符 {
instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。
instanceof是Java中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则,返回false。
instanceof左边显式声明的类型与右边操作元必须是同种类或存在继承关系,也就是说需要位于同一个继承树,否则会编译错误。
左边的对象实例不能是基础数据类型 。
null用instanceof跟任何类型比较时都是false。
instanceof一般用于对象类型强制转换。
}
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战