Javaでのメソッドのオーバーロードとオーバーライド
1. 概要
メソッドのオーバーロードとオーバーライドは、Javaプログラミング言語の重要な概念であり、そのため、詳細な調査に値します。
この記事では、これらの概念の基本を学び、どのような状況で役立つかを確認します。
2. メソッドのオーバーロード
Method overloading is a powerful mechanism that allows us to define cohesive class APIs.メソッドのオーバーロードがこのような価値のある機能である理由をよりよく理解するために、簡単な例を見てみましょう。
2つの数値、3つの数値などを乗算するためのさまざまなメソッドを実装する素朴なユーティリティクラスを作成したとします。
multiply2()、multiply3()、multiply4(),など、誤解を招くような名前やあいまいな名前をメソッドに付けた場合、それは不適切に設計されたクラスAPIになります。 ここで、メソッドのオーバーロードが役立ちます。
簡単に言えば、2つの異なる方法でメソッドのオーバーロードを実装できます。
-
2つ以上のmethods that have the same name but take different numbers of argumentsを実装する
-
2つ以上のmethods that have the same name but take arguments of different typesを実装する
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;
}
}
ただし、it’s not possible to have two method implementations that differ only in their return typesであることは注目に値します。
理由を理解するために、次の例を考えてみましょう。
public int multiply(int a, int b) {
return a * b;
}
public double multiply(int a, int b) {
return a * b;
}
この場合、the code simply wouldn’t compile because of the method call ambiguity –コンパイラはmultiply()のどの実装を呼び出すかを知りません。
2.3. タイププロモーション
メソッドのオーバーロードによって提供される優れた機能の1つは、いわゆるtype promotion, a.k.a. widening primitive conversionです。
簡単に言うと、オーバーロードされたメソッドに渡される引数のタイプと特定のメソッド実装との間に一致がない場合、特定のタイプが暗黙的に別のタイプにプロモートされます。
タイププロモーションがどのように機能するかをより明確に理解するには、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. メソッドのオーバーライド
メソッドのオーバーライドにより、基本クラスで定義されたメソッドのサブクラスにきめ細かい実装を提供できます。
メソッドのオーバーライドは強力な機能ですが、これはOOPの最大の柱の1つであるinheritanceを使用することの論理的帰結であることを考えると、when and where to utilize it should be analyzed carefully, on a per-use-case basisです。
単純な継承ベース(「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()メソッドを単純にオーバーライドしました。
ここでは、accelerate()メソッドの両方の実装が同じ署名と同じ戻り値の型を持っているため、if an application uses instances of the Vehicle class, then it can work with instances of Car as wellを確認できます。
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()を呼び出すことがはっきりとわかります。 )sインスタンスは、同じ引数に対して異なる値を返します。
したがって、次のテストは、オーバーライドされたメソッドがCarのインスタンスに対して呼び出されることを示しています。
@Test
public void whenCalledAccelerateWithSameArgument_thenNotEqual() {
assertThat(vehicle.accelerate(100))
.isNotEqualTo(car.accelerate(100));
}
3.1. タイプの代替可能性
OOPのコア原則は、Liskov Substitution Principle (LSP)と密接に関連するタイプの代替可能性の原則です。
簡単に言えば、LSPはif an application works with a given base type, then it should also work with any of its subtypesと述べています。 これにより、型の代替性が適切に保持されます。
メソッドのオーバーライドに関する最大の問題は、派生クラスの特定のメソッド実装がLSPに完全に準拠していない可能性があるため、型の置換可能性を維持できないことです。
もちろん、オーバーライドされたメソッドを作成して、異なるタイプの引数を受け入れ、異なるタイプを返すことも有効ですが、これらのルールを完全に順守します。
-
基本クラスのメソッドが特定の型の引数をとる場合、オーバーライドされるメソッドは同じ型またはスーパータイプ(別名:a.k.a. contravariantメソッド引数)
-
基本クラスのメソッドがvoidを返す場合、オーバーライドされたメソッドはvoidを返す必要があります
-
基本クラスのメソッドがプリミティブを返す場合、オーバーライドされたメソッドは同じプリミティブを返す必要があります
-
基本クラスのメソッドが特定の型を返す場合、オーバーライドされたメソッドは同じ型またはサブタイプ(別名:a.k.a. covariantの戻り値の型)
-
基本クラスのメソッドが例外をスローする場合、オーバーライドされたメソッドは、同じ例外または基本クラス例外のサブタイプをスローする必要があります
3.2. 動的バインディング
メソッドのオーバーライドは、基本タイプとサブタイプの階層が存在する継承でのみ実装できることを考慮すると、基本クラスとサブクラスの両方が定義するため、コンパイラはコンパイル時にどのメソッドを呼び出すかを決定できません。同じ方法。
結果として、コンパイラはオブジェクトのタイプをチェックして、どのメソッドを呼び出す必要があるかを知る必要があります。
このチェックは実行時に行われるため、メソッドのオーバーライドは動的バインディングの典型的な例です。
4. 結論
このチュートリアルでは、メソッドのオーバーロードとメソッドのオーバーライドを実装する方法を学び、それらが役立ついくつかの典型的な状況を調査しました。
いつものように、この記事に示されているすべてのコードサンプルは利用可能なover on GitHubです。