Method Overloading and Overriding in Java – Java中的方法重载和重写

最后修改: 2018年 2月 25日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Method overloading and overriding are key concepts of the Java programming language, and as such, they deserve an in-depth look.

方法重载和重载是Java编程语言的关键概念,因此,它们值得深入研究。

In this article, we’ll learn the basics of these concepts and see in what situations they can be useful.

在这篇文章中,我们将学习这些概念的基本知识,并看看它们在什么情况下可以发挥作用。

2. Method Overloading

2.方法重载

Method overloading is a powerful mechanism that allows us to define cohesive class APIs. To better understand why method overloading is such a valuable feature, let’s see a simple example.

方法重载是一种强大的机制,它允许我们定义有凝聚力的类API。为了更好地理解为什么方法重载是一个如此有价值的功能,让我们看一个简单的例子。

Suppose that we’ve written a naive utility class that implements different methods for multiplying two numbers, three numbers, and so on.

假设我们写了一个天真的实用类,实现了两个数字、三个数字的不同乘法,等等。

If we’ve given the methods misleading or ambiguous names, such as multiply2(), multiply3(), multiply4(), then that would be a badly designed class API. Here’s where method overloading comes into play.

如果我们给这些方法起了误导性或模糊的名字,比如multiply2(), multiply3(), multiply4(), 那么这将是一个设计不良的类API。这里就是方法重载发挥作用的地方。

Simply put, we can implement method overloading in two different ways:

简单地说,我们可以通过两种不同的方式实现方法重载:

  • implementing two or more methods that have the same name but take different numbers of arguments
  • implementing two or more methods that have the same name but take arguments of different types

2.1. Different Numbers of Arguments

2.1.不同数量的论据

The Multiplier class shows, in a nutshell, how to overload the multiply() method by simply defining two implementations that take different numbers of arguments:

Multiplier类简而言之展示了如何通过简单地定义两个接受不同参数数的实现来重载multiply()方法。

public class Multiplier {
    
    public int multiply(int a, int b) {
        return a * b;
    }
    
    public int multiply(int a, int b, int c) {
        return a * b * c;
    }
}

2.2. Arguments of Different Types

2.2.不同类型的论据

Similarly, we can overload the multiply() method by making it accept arguments of different types:

同样地,我们可以通过让它接受不同类型的参数来重载 multiply()方法。

public class Multiplier {
    
    public int multiply(int a, int b) {
        return a * b;
    }
    
    public double multiply(double a, double b) {
        return a * b;
    }
}

Furthermore, it’s legitimate to define the Multiplier class with both types of method overloading:

此外,在定义Multiplier类时,使用这两种类型的方法重载是合法的。

public class Multiplier {
    
    public int multiply(int a, int b) {
        return a * b;
    }
    
    public int multiply(int a, int b, int c) {
        return a * b * c;
    }
    
    public double multiply(double a, double b) {
        return a * b;
    }
}

It’s worth noting, however, that it’s not possible to have two method implementations that differ only in their return types.

然而,值得注意的是,不可能有两个仅在返回类型上不同的方法实现

To understand why – let’s consider the following example:

为了理解原因–让我们考虑以下例子。

public int multiply(int a, int b) { 
    return a * b; 
}
 
public double multiply(int a, int b) { 
    return a * b; 
}

In this case, the code simply wouldn’t compile because of the method call ambiguity – the compiler wouldn’t know which implementation of multiply() to call.

在这种情况下,代码根本无法编译,因为方法调用不明确–编译器不知道该调用multiply()的哪个实现。

2.3. Type Promotion

2.3.类型推广

One neat feature provided by method overloading is the so-called type promotion, a.k.a. widening primitive conversion.

方法重载提供的一个整洁的功能是所谓的类型推广,也就是加宽的原始转换

In simple terms, one given type is implicitly promoted to another one when there’s no matching between the types of the arguments passed to the overloaded method and a specific method implementation.

简单地说,当传递给重载方法的参数类型与特定的方法实现之间不匹配时,一个给定的类型被隐含地提升为另一个类型。

To understand more clearly how type promotion works, consider the following implementations of the multiply() method:

为了更清楚地了解类型推广是如何工作的,请考虑以下multiply()方法的实现。

public double multiply(int a, long b) {
    return a * b;
}

public int multiply(int a, int b, int c) {
    return a * b * c;
}

Now, calling the method with two int arguments will result in the second argument being promoted to long, as in this case there’s not a matching implementation of the method with two int arguments.

现在,用两个int参数调用该方法将导致第二个参数被提升为long,因为在这种情况下,没有一个匹配的有两个int参数的方法实现。

Let’s see a quick unit test to demonstrate type promotion:

让我们看看一个快速的单元测试来演示类型推广。

@Test
public void whenCalledMultiplyAndNoMatching_thenTypePromotion() {
    assertThat(multiplier.multiply(10, 10)).isEqualTo(100.0);
}

Conversely, if we call the method with a matching implementation, type promotion just doesn’t take place:

相反,如果我们用一个匹配的实现来调用这个方法,类型推广就不会发生。

@Test
public void whenCalledMultiplyAndMatching_thenNoTypePromotion() {
    assertThat(multiplier.multiply(10, 10, 10)).isEqualTo(1000);
}

Here’s a summary of the type promotion rules that apply for method overloading:

下面是一个适用于方法重载的类型推广规则的总结。

  • byte can be promoted to short, int, long, float, or double
  • short can be promoted to int, long, float, or double
  • char can be promoted to int, long, float, or double
  • int can be promoted to long, float, or double
  • long can be promoted to float or double
  • float can be promoted to double

2.4. Static Binding

2.4.静态绑定

The ability to associate a specific method call to the method’s body is known as binding.

将特定的方法调用与方法的主体联系起来的能力被称为绑定。

In the case of method overloading, the binding is performed statically at compile time, hence it’s called static binding.

在方法重载的情况下,绑定是在编译时静态进行的,因此它被称为静态绑定。

The compiler can effectively set the binding at compile time by simply checking the methods’ signatures.

编译器可以通过简单地检查方法的签名,在编译时有效地设置绑定。

3. Method Overriding

3.方法重写

Method overriding allows us to provide fine-grained implementations in subclasses for methods defined in a base class.

方法覆盖允许我们在子类中为基类中定义的方法提供精细化的实现。

While method overriding is a powerful feature – considering that is a logical consequence of using inheritance, one of the biggest pillars of OOPwhen and where to utilize it should be analyzed carefully, on a per-use-case basis.

虽然方法覆盖是一个强大的功能–考虑到这是使用继承的逻辑结果,是OOP>的最大支柱之一–何时何地利用它,应根据每个使用情况仔细分析

Let’s see now how to use method overriding by creating a simple, inheritance-based (“is-a”) relationship.

现在让我们看看如何通过创建一个简单的、基于继承的(”is-a”)关系来使用方法重写。

Here’s the base class:

这里是基类。

public class Vehicle {
    
    public String accelerate(long mph) {
        return "The vehicle accelerates at : " + mph + " MPH.";
    }
    
    public String stop() {
        return "The vehicle has stopped.";
    }
    
    public String run() {
        return "The vehicle is running.";
    }
}

And here’s a contrived subclass:

而这里有一个臆造的子类。

public class Car extends Vehicle {

    @Override
    public String accelerate(long mph) {
        return "The car accelerates at : " + mph + " MPH.";
    }
}

In the hierarchy above, we’ve simply overridden the accelerate() method in order to provide a more refined implementation for the subtype Car.

在上面的层次结构中,我们简单地重载了accelerate()方法,以便为子类型Car.提供一个更精细的实现。

Here, it’s clear to see that if an application uses instances of the Vehicle class, then it can work with instances of Car as well, as both implementations of the accelerate() method have the same signature and the same return type.

在这里,我们可以清楚地看到,如果一个应用程序使用Vehicle类的实例,那么它也可以与Car的实例一起工作,因为accelerate()方法的两种实现具有相同的签名和相同的返回类型。

Let’s write a few unit tests to check the Vehicle and Car classes:

让我们写几个单元测试来检查VehicleCar类。

@Test
public void whenCalledAccelerate_thenOneAssertion() {
    assertThat(vehicle.accelerate(100))
      .isEqualTo("The vehicle accelerates at : 100 MPH.");
}
    
@Test
public void whenCalledRun_thenOneAssertion() {
    assertThat(vehicle.run())
      .isEqualTo("The vehicle is running.");
}
    
@Test
public void whenCalledStop_thenOneAssertion() {
    assertThat(vehicle.stop())
      .isEqualTo("The vehicle has stopped.");
}

@Test
public void whenCalledAccelerate_thenOneAssertion() {
    assertThat(car.accelerate(80))
      .isEqualTo("The car accelerates at : 80 MPH.");
}
    
@Test
public void whenCalledRun_thenOneAssertion() {
    assertThat(car.run())
      .isEqualTo("The vehicle is running.");
}
    
@Test
public void whenCalledStop_thenOneAssertion() {
    assertThat(car.stop())
      .isEqualTo("The vehicle has stopped.");
}

Now, let’s see some unit tests that show how the run() and stop() methods, which aren’t overridden, return equal values for both Car and Vehicle:

现在,让我们看看一些单元测试,看看没有被重载的run()stop()方法如何为CarVehicle返回相等的值。

@Test
public void givenVehicleCarInstances_whenCalledRun_thenEqual() {
    assertThat(vehicle.run()).isEqualTo(car.run());
}
 
@Test
public void givenVehicleCarInstances_whenCalledStop_thenEqual() {
   assertThat(vehicle.stop()).isEqualTo(car.stop());
}

In our case, we have access to the source code for both classes, so we can clearly see that calling the accelerate() method on a base Vehicle instance and calling accelerate() on a Car instance will return different values for the same argument.

在我们的案例中,我们可以访问两个类的源代码,所以我们可以清楚地看到,在一个基本的Vehicle实例上调用accelerate()方法和在一个Car实例上调用accelerate()将为同一个参数返回不同的值。

Therefore, the following test demonstrates that the overridden method is invoked for an instance of Car:

因此,下面的测试表明,对于Car的一个实例,重载方法被调用。

@Test
public void whenCalledAccelerateWithSameArgument_thenNotEqual() {
    assertThat(vehicle.accelerate(100))
      .isNotEqualTo(car.accelerate(100));
}

3.1. Type Substitutability

3.1.类型可替代性

A core principle in OOP is that of type substitutability, which is closely associated with the Liskov Substitution Principle (LSP).

OOP的一个核心原则是类型可替代性,它与Liskov替代原则(LSP)密切相关。

Simply put, the LSP states that if an application works with a given base type, then it should also work with any of its subtypes. That way, type substitutability is properly preserved.

简单地说,LSP规定,如果一个应用程序可以与给定的基本类型一起使用,那么它也应该与它的任何子类型一起使用。这样一来,类型的可替代性就得到了适当的保留。

The biggest problem with method overriding is that some specific method implementations in the derived classes might not fully adhere to the LSP and therefore fail to preserve type substitutability.

方法覆盖的最大问题是,派生类中的一些特定方法实现可能不完全遵守LSP,因此无法保留类型可替代性。

Of course, it’s valid to make an overridden method to accept arguments of different types and return a different type as well, but with full adherence to these rules:

当然,让一个重载方法接受不同类型的参数并返回不同的类型也是有效的,但是要完全遵守这些规则。

  • If a method in the base class takes argument(s) of a given type, the overridden method should take the same type or a supertype (a.k.a. contravariant method arguments)
  • If a method in the base class returns void, the overridden method should return void
  • If a method in the base class returns a primitive, the overridden method should return the same primitive
  • If a method in the base class returns a certain type, the overridden method should return the same type or a subtype (a.k.a. covariant return type)
  • If a method in the base class throws an exception, the overridden method must throw the same exception or a subtype of the base class exception

3.2. Dynamic Binding

3.2.动态绑定

Considering that method overriding can be only implemented with inheritance, where there is a hierarchy of a base type and subtype(s), the compiler can’t determine at compile time what method to call, as both the base class and the subclasses define the same methods.

考虑到方法覆盖只能通过继承来实现,其中有一个基类型和子类型的层次结构,编译器不能在编译时确定要调用什么方法,因为基类和子类都定义了相同的方法。

As a consequence, the compiler needs to check the type of object to know what method should be invoked.

因此,编译器需要检查对象的类型以知道应该调用什么方法。

As this checking happens at runtime, method overriding is a typical example of dynamic binding.

由于这种检查发生在运行时,方法重写是动态绑定的一个典型例子。

4. Conclusion

4.结论

In this tutorial, we learned how to implement method overloading and method overriding, and we explored some typical situations where they’re useful.

在本教程中,我们学习了如何实现方法重载和方法重写,并探讨了它们有用的一些典型情况。

As usual, all the code samples shown in this article are available over on GitHub.

像往常一样,本文中显示的所有代码样本都可以在GitHub上找到