02_oo_09_override_equivalent

第二十二章 函数签名相等性(Override-Equivalent Signatures)

1 简介

判断两个函数是否具有相互重载的关系或者一个覆盖另一个的关系取决于这两个函数签名是否相同。那么,判断两个函数签名是否相同的核心问题是判断这两个函数是否是Override-Equivalent。所以,本章重点讲解Override-Equivalent概念。

2 函数重载(Method Overloading)与函数覆盖(Method Overriding)

函数重载是指给定两个函数,这两个函数可能声明于同一个类,也可能声明于一个继承结构中的两个类,当这两个函数具有相同的函数名,但它们不是Override-Equivalent时,这两个函数互为重载函数。

If two methods of a class (whether both declared in the same class, or both inherited by a class, or one declared and one inherited) have the same name but signatures that are not override-equivalent, then the method name is said to be overloaded. -- Section 8.4.9 Overloading in The Java Language Specification, Java SE 13 Edition.

函数覆盖是指当子类的一个函数与基类的一个函数重名时,且子类的函数是基类函数的Sub-Signature,那么,子类的函数覆盖基类的函数(基类的函数是public或者protected的)。

其中,Sub-Signature是单向的Override-Equivalent。换句话说,如果两个函数互为对方的Sub-Signature,即这两个函数互为Override-Equivalent。那么,什么是Sub-Signature呢?

3 Sub-Signature

我们将通过基本场景和泛型场景来解释Sub-Signature的概念。

3.1 基本场景

在基本场景下,即非泛型编程场景下,Sub-Signature表示两个函数的函数签名相同。例如,在如下代码中,Vehicle.setBrand()和Car.setBrand()两个成员方法签名相同。所以,它们互为对方的Sub-Signature,也就是说,它们互为Override-Equivalent。(提示:Vehicle.setBrand()是一个抽象(abstract)的成员方法。抽象的方法是指在类中可以声明方法的签名,但不需要提供实现。其具体的内容可以在子类中实现。)

public class Vehicle {
    public abstract void setBrand(String brandName);
}

public class Car extends Vehicle {
    private String brand = null;
    public void setBrand(String brandName) {
        this.brand = brandName;
    }
}

3.2 泛型场景

在泛型场景下,我们将需要用到上一章讲到的Type Erasure的概念。我们首先回顾一下Type Erasure过程中的对象类型转换的几种场景,如表一所示。

  1. 场景1:当泛型参数为T时(未设定上界),即T的上界是类Object,则在Type Erasure处理后,T被转换为类Object。

  2. 场景2:当泛型参数为T时,设定上界为类S,即T继承自S,则在Type Erasure处理后,T被转换为类S。

  3. 场景3:当泛型参数为数组T[]时,则在Type Erasure处理后,T[]被转换为Object[]。

  4. 场景4:当泛型参数T为类G的类型参数时,在Type Erasure处理后,G<T>被转换为G。

     

表一 Type Erasure过程前后对象类型的变化

场景Type Erasure之前的类型Type Erasure之后的类型
1TObject
2T extends SS
3T[]Object[]
4G<T>G

 

所以,在泛型场景下,如果方法M1的函数签名与方法M2经过Type Erasure处理之后的方法签名相同的话,则M1是M2的Sub-Signature。例如:在如下代码中,类Vehicle有一个成员方法setTires(),其入参是一个包含多个轮胎的链表。该链表的泛型类型参数为Tire。而在类Car中,setTires()方法的参数为List,未设定泛型参数类型。因此,Car.setTires()是Vehicle.setTires()的Sub-Signature,因为,根据表一中的场景4,经过Type Erasure处理之后,List<Tire>会被转换为List,进而Vehicle.setTires()和Car.setTires()有着相同的函数签名。所以,Car.setTires()覆盖Vehicle.setTires()。

import java.util.List;

// 轮胎类
public class Tire {}

public class Vehicle {
    public abstract void setTires(List<Tire> tires);
}

public class Car extends Vehicle {
    private List<Tire> tires = null;
    @Override
    public void setTires(List fourTires) {
        this.tires = fourTires
    }
}

但是,如果我们将setTires方法反过来,即Vehicle.setTires(List)和Car.setTires(List<Tire>),则会造成编译错误,因为Car.setTires(List<Tire>)不是Vehicle.setTires(List)的Sub-Signature。因为,经Type Erasure处理后,Vehicle.setTires()的参数类型还是List,而Car.setTires()的参数类型是List<Tire>,这两个是不同的类型。

import java.util.List;

// 轮胎类
public class Tire {}

public class Vehicle {
    public abstract void setTires(List tires);
}

public class Car extends Vehicle {
    private List<Tire> tires = null;
    @Override  // 编译错误,Car.setTire 不能覆盖 Vehicle.setTires
    public void setTires(List<Tire> fourTires) {
        this.tires = fourTires
    }
}

最后,我们用一个问题来结束本章的内容。既然Car.setTires(List<Tire>)不能覆盖Vehicle.setTires(List),因为前者不是后者的Sub-Signature,那么,方法setTires(List<Tire>)和setTires<List>不是Override-Equivalent。它们能构成相互重载关系吗?如下面的代码所示。

import java.util.List;

// 轮胎类
public class Tire {}

public class Vechile {
    public void setTires(List tires) {
    }
    
    public void setTires(List<Tire> tires) {
        // 编译错误,在Type Erasure处理后,两个setTires方法有着相同的函数签名。
    }
}

答案是:不能。从Java语言给出的函数重载定义来看,它们是可以构成重构关系的。但是,Java语言附加了一个额外规则,限制了这种情况的使用。在一个类中不能存在两个方法,它们有着相同的名字,且在Type Erasure处理后,它们有着相同的函数签名。所以,上述代码会出现编译错误。

These restrictions are necessary because generics are implemented via erasure. The rule above implies that methods declared in the same class with the same name must have different erasures. It also implies that a type declaration cannot implement or extend two distinct invocations of the same generic interface. -- The Java Language Specification (Java SE 13 Edition)

4 结语

函数重载和函数覆盖是面向对象程序设计中的两个重要概念。它们的定义非常清晰,易于理解。然而,当Java语言引入了泛型编程之后,普通函数与泛型函数之间的重载关系和覆盖关系变得非常复杂。本章着重讲解了Override-Equivalent和Sub-Signature的概念;它们是理解Java函数重载和覆盖的两个非常重要的概念。在开发过程中,小水滴建议尽量避免混合使用泛型编程和函数重载/覆盖,尽量使用不同的函数名称以避免触发函数重载和覆盖。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.