这是我参与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)