1概要

メソッドのオーバーロードとオーバーライドは、Javaプログラミング言語の重要な概念です。そのため、それらは詳細な外観に値するものです。

この記事では、これらの概念の基本を学び、それらがどのような状況で役立つ可能性があるかを見ていきます。


2メソッドのオーバーロード

  • メソッドのオーバーロードは、まとまりのあるクラスAPIを定義することを可能にする強力なメカニズムです。

2つの数字、3つの数字などを乗算するためのさまざまな方法を実装する単純なユーティリティクラスを作成したとします。


multiply2()



multiply3()

、__multiply4()のように、紛らわしい名前やあいまいな名前をメソッドに指定した場合、それはひどく設計されたクラスAPIです。ここでメソッドオーバーロードが効果を発揮します。

簡単に言うと、2つの方法でメソッドのオーバーロードを実装できます。

  • 同じ名前を持つ2つ以上の** メソッドを実装する

異なる数の引数


同じ名前を持つ2つ以上の** メソッドを実装する

異なる型の引数**


2.1. 引数の数が異なる


Multiplier

クラスは、簡単に言えば、引数の数が異なる2つの実装を定義することによって

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. 異なる型の引数

同様に、

multiply()

メソッドに異なる型の引数を受け付けるようにすることで、オーバーロードできます。

public class Multiplier {

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

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

さらに、

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;
    }
}

ただし、注意すべき点は、** 戻り型だけが異なる2つのメソッド実装を持つことはできないことです。

その理由を理解するために – 次の例を考えましょう。

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

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

この場合、

メソッド呼び出しがあいまいなため、コードは単純にコンパイルされません

– コンパイラは、

multiply()

のどの実装を呼び出すかがわかりません。


2.3. タイププロモーション

メソッドオーバーロードによって提供される優れた機能の1つは、いわゆる

typeプロモーション、a.k.a.

です。

簡単に言うと、オーバーロードされたメソッドに渡される引数のタイプと特定のメソッド実装との間に一致がない場合、あるタイプが暗黙的に別のタイプに昇格されます。

型の昇格がどのように機能するかをより明確に理解するために、

multiply()

メソッドの次の実装を検討してください。

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

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

ここで、2つの

int

引数を使用してメソッドを呼び出すと、2番目の引数が

long

に昇格されます。この場合、2つの

int

引数を使用したメソッドの実装は一致しません。

型の昇格を実証するための簡単なユニットテストを見てみましょう。

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

逆に、一致する実装を持つメソッドを呼び出した場合、型の昇格は行われません。

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

メソッドのオーバーロードに適用される型の昇格規則の概要は次のとおりです。


  • byte

    は、

    short、int、long、float、

    、または

    double

    にプロモートできます。


  • short

    は、

    int、long、float、

    、または

    double

    にプロモートできます。


  • char

    は、

    int、long、float、

    、または

    double

    にプロモートできます。


  • int

    は、

    long、float、

    、または

    double

    にプロモートできます。


  • long



    float

    または

    double

    に昇格できます


  • float



    double

    に昇格できます

** 2.4. 静的バインディング

特定のメソッド呼び出しをそのメソッドの本体に関連付ける機能は、バインディングと呼ばれています。

メソッドオーバーロードの場合、バインディングはコンパイル時に静的に実行されるため、静的バインディングと呼ばれます。

メソッドのシグネチャをチェックするだけで、コンパイラはコンパイル時に効果的にバインディングを設定できます。


3メソッドオーバーライド

  • メソッドをオーバーライドすることで、基底クラスで定義されたメソッドに対してサブクラスできめ細かい実装を提供することができます。

メソッドのオーバーライドは強力な機能ですが、それを考慮するとhttps://en.wikipedia.org/wiki/Inheritance

(object-oriented

programming)[inheritance]を使用することの論理的な結果です。 wikipedia.org/wiki/Object-oriented__programming[OOP] –

いつ、どこでそれを利用するかは、ユースケースごとに慎重に分析されるべきです

それでは、単純な継承ベース( “is-a”)の関係を作成してメソッドのオーバーライドを使用する方法を見てみましょう。

これが基本クラスです。

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.";
    }
}

そして、これが人造のサブクラスです

public class Car extends Vehicle {

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

上の階層では、サブタイプ

Car.

のより洗練された実装を提供するために

accelerate()

メソッドをオーバーライドしました

ここで、アプリケーションが

Vehicle

クラスのインスタンスを使用する場合、

accelerate()

メソッドの両方の実装が同じシグネチャと同じ戻り型を持つため、

Car

のインスタンスでも動作できることがわかります。


Vehicle

クラスと

Car

クラスをチェックするための単体テストをいくつか書いてみましょう。

@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.");
}

ここで、オーバーライドされていない

run()

メソッドと

stop()

メソッドが

Car



Vehicle

の両方に等しい値を返す方法を示す単体テストをいくつか見てみましょう。

@Test
public void givenVehicleCarInstances__whenCalledRun__thenEqual() {
    assertThat(vehicle.run()).isEqualTo(car.run());
}

@Test
public void givenVehicleCarInstances__whenCalledStop__thenEqual() {
   assertThat(vehicle.stop()).isEqualTo(car.stop());
}

私たちの場合は、両方のクラスのソースコードにアクセスできるので、

Vehicle

のベースインスタンスで

accelerate()

メソッドを呼び出し、

Car

のインスタンスで

accelerate()

を呼び出すと、同じクラスの値が異なることがわかります引数。

したがって、次のテストは、上書きされたメソッドが

Car

のインスタンスに対して呼び出されることを示しています。

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

** 3.1. タイプ置換可能性

**

OOPの基本原則は型代入可能性の原則であり、これはhttps://en.wikipedia.org/wiki/Liskov

substitution

principle[Liskov Substitution Principle(LSP)]と密接に関連しています。

簡単に言えば、LSPは、アプリケーションが特定の基本型で動作する場合、そのサブタイプのいずれでも動作するはずであると述べています。このようにして、型代入性は適切に維持されます。

  • メソッドオーバーライドの最大の問題は、派生クラスの特定のメソッド実装がLSPに完全に準拠していない可能性があるため、型の代用性を維持できないことです。

もちろん、オーバーライドされたメソッドを使用してさまざまなタイプの引数を受け取り、さまざまなタイプを返すこともできますが、これらの規則を完全に遵守してください。

  • 基底クラス内のメソッドが与えられた型の引数を取る場合、

オーバーライドされたメソッドは、同じ型またはスーパータイプ(別名.k.a)を取ります。


contravariant

メソッドの引数)
** 基本クラスのメソッドが

void

を返す場合、オーバーライドされたメソッド


void

を返さなければなりません
** 基本クラスのメソッドがプリミティブを返すと、オーバーライドされます。

メソッドは同じプリミティブを返すべきです
** 基本クラスのメソッドが特定の型を返す場合は、オーバーライドされます。

メソッドは同じ型かサブタイプを返すべきです(別名

covariant

返品タイプ)
** 基本クラスのメソッドが例外をスローした場合は、オーバーライドされます。

メソッドは、同じ例外または基本クラス例外のサブタイプをスローする必要があります


3.2. 動的バインディング

メソッドのオーバーライドは、基本型とサブタイプの階層がある場合に継承でしか実装できないことを考慮すると、基本クラスとサブクラスの両方で定義するため、コンパイラはコンパイル時にどのメソッドを呼び出すかを決定できません同じ方法です。

結果として、コンパイラはどのメソッドを呼び出すべきかを知るためにオブジェクトのタイプをチェックする必要があります。

このチェックは実行時に行われるので、メソッドのオーバーライドは動的バインディングの典型的な例です。


4結論

このチュートリアルでは、メソッドのオーバーロードとオーバーライドを実装する方法を学び、それらが役に立つ典型的な状況をいくつか調べました。

いつものように、この記事に示すすべてのコードサンプルはhttps://github.com/eugenp/tutorials/tree/master/core-java-lang-oop[GitHubで利用可能]です。