インタビューで、私は多型が継承なしで達成できるかどうか尋ねられました。これは可能ですか?
6 に答える
私が今まで読んだ主題についての最も良い説明は、有名なタイプの理論家であるルカ・カーデリによる記事です。この記事の名前は、「タイプの理解、データの抽象化、およびポリモーフィズム」です。
ポリモーフィズムの種類
Cardelliは、この記事でいくつかのタイプのポリモーフィズムを定義しています。
- ユニバーサル
- パラメトリック
- インクルージョン
- このために
- オーバーロード
- 強制
継承に関連するポリモーフィズムの種類は、包含ポリモーフィズムまたはサブタイプポリモーフィズムに分類されます。
ウィキペディアは良い定義を提供します:
オブジェクト指向プログラミングでは、サブタイプポリモーフィズムまたは包含ポリモーフィズムは型理論の概念であり、いくつかの共通のスーパークラスによって関連付けられている限り、名前は多くの異なるクラスのインスタンスを表すことができます。包含多態性は、一般にサブタイピングによってサポートされます。つまり、異なるタイプのオブジェクトは、別のタイプのオブジェクト(それらの基本タイプ)を完全に置き換えることができるため、共通のインターフェースを介して処理できます。あるいは、包含多型は、型キャストとしても知られる型強制によって達成することができます。
オブジェクト指向プログラミングにおけるポリモーフィズムと呼ばれる別のウィキペディアの記事も、あなたの質問に答えているようです。
Javaの場合
Javaのこのサブタイピング機能は、とりわけ、クラスとインターフェースの継承によって実現されます。ただし、Javaのサブタイピング機能は、継承の観点からは常に明らかではない場合があります。たとえば、ジェネリックとの共変性と反変性の場合を考えてみましょう。また、配列はシリアライズ可能でクローン可能ですが、これはタイプ階層のどこにも明らかではありません。また、プリミティブな拡張変換により、Javaの数値演算子はポリモーフィックであり、場合によってはまったく関係のないオペランド(つまり、文字列と数値の連結、または文字列とその他のオブジェクトの連結)を受け入れることさえあります。プリミティブのボックス化とボックス化解除の場合も考慮してください。後者のポリモーフィズム(強制とオーバーロード)のケースは、継承とはまったく関係ありません。
例
インクルージョン
List<Integer> myInts = new ArrayList<Integer>();
これは、あなたの質問が参照しているように見えるケースです。つまり、ArrayListがListを実装する場合のように、型間に継承または実装の関係がある場合です。
ただし、前述したように、Javaジェネリックを導入すると、サブタイピングのルールがあいまいになることがあります。
List<? super Number> myObjs = new ArrayList<Object>();
List<? extends Number> myNumbers = new LinkedList<Integer>();
そして他の場合では、関係はAPIでさえ明白ではありません
Cloneable clone = new int[10];
Serializable obj = new Object[10]
それでも、Cardelliによれば、これらはすべて普遍的なポリモーフィズムの形態です。
パラメトリック
public <T> List<T> filter(Predicate<T> predicate, List<T> source) {
List<T> result = new ArrayList<>();
for(T item : source) {
if(predicate.evaluate(item)){
result.add(item);
}
return result;
}
}
同じアルゴリズムを使用して、考えられるすべてのタイプのリストに対して1行のコードを繰り返すことなく、すべての種類の述語ですべての種類のリストをフィルタリングできます。実際のリストのタイプと述部のタイプはパラメトリックです。JDK 8プレビューで使用可能なラムダ式を使用したこの例を参照してください(述語の実装を簡潔にするため)。
filter(x -> x % 2 == 0, asList(1,2,3,4,5,6)); //filters even integers
filter(x -> x % 2 != 0, asList(1L,2L,3L,4L,5L,6L)); //filters odd longs
filter(x -> x >= 0.0, asList(-1.0, 1.0)); //filters positive doubles
Cardelliによると、これは普遍的なポリモーフィズムの一形態です。
強制
double sum = 1 + 2.0;
整数演算と浮動小数点演算はまったく異なります。ここで異なるタイプの2つのオペランドにプラス演算子を適用することは、何らかの強制がなければ不可能です。
この例では、integer型とdouble型は、明示的なキャストなしで自動的にdouble型に強制変換(変換)されます。表現全体が2倍になります。これは、Javaにはプリミティブな拡張変換があるためです。
Cardelliによると、この形式の自動強制は、plus演算子に提供されるアドホック多相の形式です。
明示的なキャストなしでは整数と浮動小数点数を合計することさえできない言語があります(つまり、AFAIK、SML、ちなみに、パラメトリック多態性はこの種の問題を克服するための鍵です)。
オーバーロード
double sum = 2.0 + 3.0;
String text = "The sum is" + sum;
ここでのプラス演算子は、使用される引数に応じて2つの異なる意味を持ちます。明らかに、オペレーターは過負荷になっています。これは、オペランドのタイプに応じて実装が異なることを意味します。Cardelliによると、これはプラス演算子に提供されるアドホック多相の形式です。
もちろん、これはクラスでのメソッドのオーバーロードの形式にも当てはまります(つまり、java.lang.Mathメソッドのminメソッドとmaxメソッドは、さまざまなプリミティブ型をサポートするためにオーバーロードされます)。
他の言語で
継承がこれらの形式のポリモーフィズムのいくつかの実装で重要な役割を果たす場合でも、確かにそれが唯一の方法ではありません。オブジェクト指向ではない他の言語は、他の形式のポリモーフィズムを提供します。たとえば、Pythonのような動的言語、Goのような静的に型付けされた言語、SML、Ocaml、Scalaのような言語の代数データ型、 Haskellのような言語の型クラス、Clojureのマルチメソッドでのダックタイピングの場合を考えてみましょう。 、JavaScriptでの典型的な継承など。
アドホック多相性>演算子のオーバーロード>継承なし
アドホック多相性>メソッドのオーバーロード>継承なし
アドホック多相性>メソッドのオーバーライド>継承あり
パラメトリックポリモーフィズム>ジェネリックス>継承なし
サブタイプポリモーフィズムまたは包含ポリモーフィズム>ポリモーフィック割り当て>継承あり
サブタイプポリモーフィズムまたは包含ポリモーフィズム>ポリモーフィックリターンタイプ>継承あり
サブタイプポリモーフィズムまたは包含ポリモーフィズム>ポリモーフィック引数タイプ>継承あり
強制ポリモーフィズム>拡大>継承の有無
強制ポリモーフィズム>自動ボクシングとアンボクシング>継承なし
強制ポリモーフィズム>変数引数>継承なし
強制ポリモーフィズム>型キャスト>継承なし
もちろん。Javaでは、2つのクラスに同じインターフェースを実装させることができ、それらの結果はポリモーフィックです。機能は継承されません。
public interface Foo {
public int a();
}
public class A implements Foo {
public int a() {
return 5;
}
}
public class B implements Foo {
public int a() {
return 6;
}
}
その後、他の場所:
Foo x = new A();
System.out.println(x.a())
Foo y = new B();
System.out.println(y.a())
x
とは両方ともy
sFoo
ですが、を呼び出すと結果が異なりますa()
。
はい、おそらく彼らはインターフェースによるポリモーフィズムについて聞きたかったと思います。したがって、同じインターフェースから実装する2つのクラスがある場合、そのような相互作用を持つオブジェクトを検査するすべての場所で使用できます。ウィキペディアのコードを参照してください。
// from file Animal.java
public interface Animal {
public String talk();
}
// from file Cat.java
public class Cat implements Animal {
@Override
public String talk() {
return "Cat says Meow!";
}
}
// from file Dog.java
public class Dog implements Animal {
@Override
public String talk() {
return "Dog says Woof! Woof!";
}
}
// from file PolymorphismExample.java
public class PolymorphismExample {
public static void main(String[] args) {
Collection<Animal> animals = new ArrayList<Animal>();
animals.add(new Cat());
animals.add(new Dog());
for (Animal a : animals) {
System.out.println(a.talk());
}
}
}
関数のオーバーロードは、継承なしで実現できるポリモーフィズムの1つです(実際のポリモーフィズムの意味ではありませんが)。
例えば
class Foo {
public void Arrest( Animal A){
/*code...*/
}
public void Arrest( Terrorist T ) {
/*code...*/
}
}
from main :
Foo f= new Foo();
f.Arrest( new Lion() );
f.Arrest(new Terrorist());
逮捕メソッドは2回呼び出されますが、コードの実行パスが異なります。
*繰り返しますが、これはポリモーフィズムの真の形ではありません。一般的な実際のポリモーフィズムは、継承なしでは達成できません。
静的タイプ
オーバーロード-これは、同じ名前で異なる署名を持つ複数のメソッドを意味し、オーバーライドせずに可能です
class StaticPolyExample
{
void print(int s)
{
//print s
}
void print(String s)
{
//print s
}
}
ダイナミックタイプ
オーバーライド-これは、スーパークラスのメソッドが継承を必要とするサブクラスで再定義されることを意味します
class Printer
{
void print(String s)
{
// prints String
}
}
class diffPrinter extends Printer
{
void print(String s)
{
// prints String differently
}
}