Java 三大基础特性の多态

361 阅读3分钟

这是我参与8月更文挑战的第 19 天,活动详情查看:8月更文挑战

定义

子类对父类的多种表现形态

定义一个父类,通过继承的方式,得到多个不同的子类。虽然我们一直是对父类的引用进行操作,但引用实际指向的是某个子类,我们调用父类引用的方法是,会被动态绑定到子类对应的方法上(如果子类有重写这个方法的话)

代码讲解

先定义 2 个 class,具备有继承关系

class Super{
  int a = 1;
  void f(){
    System.out.print("Super.f()");
  }
}
class Sub extends Super{
  int a = 2;
  void f(){
    System.out.print("Sub.f()");
  }
  void g(){
    System.out.print("Sub.g()");
  }
}

最常见的编码方式是,实例化一个子类,用对应的类型去接收这个实例

Sub sub = new Sub();

或者,我们使用这个类型的父类类型来接受这个实例,如下,这也称之为向上转型

Super super = new Sub();

细节

// ---------------- 1 -------------------
Super sup = new Super();
sup.a;	// 1
sup.f();	// Super.f()
Sub sub = new Sub();
// ---------------- 2 -------------------
sub.a;	// 2
sub.f();	// Sub.f();
sub.g();	// Sub.g();
// ---------------- 3 -------------------
Super sup2 = new Sub();
sup2.a;	// 1
sup2.f();	// Sup.f();
Sub sub2 = (Sub)sup2;
sub2.a;	// 2
sub2.g();	// Sup.g()

第 1,2 部分

实例化一个对象,并使用自己对应的类型来接收,正常使用

第 3 部分

因为是使用 Super 来接收 Sub 的实例,正常的一个对变量、方法的访问,应该都是 Super 中的相关量的访问。但是,由于java的动态绑定机制,JVM在运行的时候,发现我们实际传入的居然是一个 Sub ??然后,我们对 sup.f() 的调用,JVM 帮我们处理为对 sub.f() 的调用,这里说的也就是子类对父类的多种表现形态

因为我们使用 Super 来接收了一个实例,所以我们可以使用 Super 上的任意方法;我们确认这个实例是 Sub,并想使用 Sub 上的特有的方法,或者是 Sub 对应的变量,我们就可以强转 Sub sub = (Sub)sup;

模版模式

abstract class Template{
  public void func(){
    f();
    g();
  }
  abstract void f();
  abstract void g();
}
class A extends Template{
  void f(){};
  void g(){};
}
class B extends Template{
  void f(){};
  void g(){};
}
Template t = new A();
t.func();

我们定义一个抽象类 Template,然后在其中编写了一个公共的处理方法 func(),同时,将在公共处理逻辑中无法处理的特定的细节,定义为抽象的方法,交给子类去单独实现,或者描述为,交给不同的子类去实现对应的需求

当我们以多态的思路来编写代码时,实例化一个 A,用 Template 类型来接收,然后调用 func(),JVM 会帮助我们去动态绑定到 A 类型的对应的方法上(f(), g()

附录

向上转型

因为继承的原因,向上转型总有着 is a 的关系,并且,将类型向上转换为自己的父类,意味着丢失了属于子类特有的方法和成员,这种由宽范围转化为窄范围在 java 中是被支持的

在 java 的基础类型中,我们使用更高精度的类型去接收当前的数据,是默认允许的。如果我们对高精度的数据使用较小精度的类型去接收,就会有精度丢失的问题,这个时候就需要显示的强制表示我们已经知道该操作的后果

精度从下到大: char -> byte -> short -> int -> long -> float -> double

向下转型

与向上转型刚好相反,我们对一个父类引用的实例,期望转化为某个具体的子类,并且使用那个子类特定的方法,这存在一个很大的问题,通常一个父类会有着无数个子类,JVM 并不知道这个实例的实际类型,如果我们需要,也可以强转,操作如下

Super sup = new Sub();
Sub sub = (Sub)sup;
// Sub2 sub2 = (Sub2)sup;  // ClassCastException !!

当然,我们需要谨慎的操作,否则,就会见到经典的异常之一 ClassCastException(另一个更加重量级的应该就是 NPE -> NullPointExpection)