どのような地獄は、デフォルトメソッドのインタフェースであります

インタフェースは、インタフェースが、それは実現しなかったということですが、文になってきました。しかし、その後、すべて変更し、Javaはデフォルトの方法が行われている、デフォルトの方法、C#が登場しました。伝統的な意味でのインタフェースは、概念は、抽象クラス、純粋な抽象的なもの、実体の突然の出現に向かって移動し始めた無邪気に言わないで始めたインタフェースではありません。

世界は変化しているが、彼はどのようにそれを変更し始めていますか?

1.起源

この記事は、Javaを述べたが、近年では、私は主にC#のプログラムを書きますが、それは言語を明示的に命名されていないことより、いくつかの更新のC#の仕様に傾いなります。

一度、私たちは、定義IStringListリストを宣言するインターフェイスを:

これは、追加の技術的思想の導入を避けるために、一般的な例のない使用がない、単なる一例です。

interface IStringList {
    void Add(string o); // 添加元素
    void Remove(int i); // 删除元素
    string Get(int i);  // 获取元素
    int Length { get; } // 获取列表长度
}

とにかく、このリストはすでにトラバーサルなどの基本的な追加、削除、変更、検索機能を、持っている、あなたが書くことができます

IStringList list = createList();
for (var i = 0; i < list.Length; i++) {
    string o = list.Get(i);
    // Do something with o
}

このIStringListベースクラスライブラリインタフェースは、このインタフェースを使用して、プログラマーの多数、リストの各種の束、等の解除後StringArrayListLinkedStringList、、...StringQueue 抽象クラスが存在し、拡張インターフェースが存在するが、また様々な実装クラス。要するに、蓄積の長い期間の後、世界中の子供や孫。StringStackSortedStringListIStringList

そして、IStringList技術の急速な発展のスーツの開発者へのより多くの方法のリストを定義することを決定した発明者は、IStringListそう、使いやすさを必要とします

interface IStringList {
    int IndexOf(string o);          // 查找元素的索引,未找到返回 -1
    void Insert(string o, int i);   // 在指定位置插入元素

    // ------------------------------
    void Add(string o); // 添加元素
    void Remove(int i); // 删除元素
    string Get(int i);  // 获取元素
    int Length { get; } // 获取列表长度
}

もちろん、抽象基本クラスライブラリは、そうでない場合、コンパイラは文句を言うだろう、それのすべてを実装しなければならないインタフェースの実装クラスに加えて、変更AbstractStringListの上に新しいインターフェイスが増加を実装しました。財務省完璧なコンパイルの全体の基礎は、バージョン2.0をリリースしました。

しかし、現実は非常に残酷です!

基本ライブラリのユーザー(開発者)は、彼らはあまりにも多くあるので、文句を言う大きな音を出しますが、コードがコンパイルされます!

すべてのユーザーが直接継承しますはい、いないAbstractStringList、多くのユーザーが直接実現しますIStringListそこに多くのユーザーがいるとでも拡大しIStringList、それらは定義されていませんint IndexOf(string o)が、定義されましたint Find(string o)基礎となるライブラリのインターフェースなのでIStringList変更は、ユーザーが実装するコードに多くの時間を費やす必要がIStringList新しいメソッドの定義を。

記載されているこの例ではIStringList、2つのだけの方法を追加しました。が、これは、ユーザによって引き起こさノートラブルに少しを持っていますが、ワークロードはまだ許容。しかし、私は、ユーザーがのみ使用できることを怖い、JDKおよび.NET Framework /コア膨大なライブラリの基盤を考える「崩壊」を記述するために!

2.対策

確かに、ユーザーは、この問題を解決するための方法を見つける必要性を崩壊することはできませ。だから、JavaとC#は、2つのプログラムが登場しています

  • Javaはデフォルトの実装は、インターフェイスに追加され、デフォルトの方法を提示し
  • C#の拡張メソッド、すなわち静的メソッド呼び出しの形を変更することによってそれをふりをし、提案されたコールを受けます

私はC#の拡張メソッドが非常にスマートであることを言っているが、すべて本当にC#8で、インターフェイス上で展開するも、インターフェイスの拡張によって引き起こされる問題を解決するためのデフォルトの方法を参加していない後にそれがあります。

インターフェース拡張方法は提案された後、それはデフォルトの実装の問題を解決するが、新たな問題を持ち出し。

  • デフォルトのインターフェイスメソッドは、クラスが実装インタフェースは、それを達成するために必要な?あなたは何が起こるか理解していない場合は?
  • かどうかは、JavaやC#クラスは多重継承を許可されていませんが、インターフェースが可能。デフォルトのインターフェイスの実装が行う方法を、多重継承の問題が生じる同様のクラスをもたらしますか?
  • 複合実装と継承関係で、最終的にこれは、実行の最終的な方法になりますか?

3.関係の問題、メソッドやクラスのデフォルトの実装

上記の無視IStringListを追加するインターフェイスInsert(Object, int)のメソッドを、私たちは、に焦点を当てるIndexOf(Object)に。JavaとC#は、同様の構文:

3.1。デフォルトのメソッドの構文で見てみましょう

  • Javaのバージョン
interface StringList {
    void add(Object s);
    void remove(int i);
    Object get(int i);
    int getLength();

    default int indexOf(Object s) {
        for (int i = 0; i < getLength(); i++) {
            if (get(i) == s) { return i; }
        }
        return -1;
    }
}
  • C#バージョン
interface IStringList
{
    public void Add(string s);
    void Remove(int i);
    string Get(int i);
    int Length { get; }
    int IndexOf(string s)
    {
        for (var i = 0; i < Length; i++)
        {
            if (Get(i) == s) { return i; }
        }
        return -1;
    }
}

ここではC#とJavaインタフェースは、引数と命名規則の両方がわずかに異なっている主な理由は、ダウン書かれました。ローカルに似た研究C#とJavaの行動は、その後に行わ、それは主にC#のAに焦点を当てています。

サンプルC#やJavaの例を区別する方法?コードの仕様を見て、パスカル、Javaのメソッドの命名規則を使用ラクダで最も有名なC#の命名方法。もちろん、同じ矢印ラムダはありません。

次の実現、唯一のC#の例:

class MyList : IStringList
{
    List<string> list = new List<string>();  // 偷懒用现成的

    public int Length => list.Count;
    public void Add(string o) => list.Add(o);
    public string Get(int i) => list[i];
    public void Remove(int i) => list.RemoveAt(i);
}

MyList実装されていませんIndexOfが、それらを使用すると、何の問題もありません

class Program
{
    static void Main(string[] args)
    {
        IStringList myList = new MyList();
        myList.Add("First");
        myList.Add("Second");
        myList.Add("Third");

        Console.WriteLine(myList.IndexOf("Third"));  // 输出 2
        Console.WriteLine(myList.IndexOf("first"));  // 输出 -1,注意 first 大小写
    }
}

3.2。IndexOfメソッドをマイリストに達成

さて、MyList追加するIndexOf検索文字列の大文字と小文字を区別しないを実現するために、:

// 这里用 partial class 表示是部分实现,
// 对不住 javaer,Java 没有部分类语法
partial class MyList
{
    public int IndexOf(string s)
    {
        return list.FindIndex(el =>
        {
            return el == s
                || (el != null && el.Equals(s, StringComparison.OrdinalIgnoreCase));
        });
    }
}

その後、Main出力の内容は、関数となります

Console.WriteLine(myList.IndexOf("Third")); // 还是返回 2
Console.WriteLine(myList.IndexOf("first")); // 返回 0,不是 -1

明らかに、このコールMyList.IndexOf()

3.3結論、ならびにJavaやC#の違い

上記主にC#の例としては、実際には、Javaは同じです。上記の例では、インターフェイスタイプによって呼び出されるIndexOf方法。最初の呼び出しは、IStringList.IndexOfその時点であるため、デフォルトの実装でMyList具体化しなかったIndexOf。2回目の呼び出しをされてMyList.IndexOf実現しました。私は同様のコード、まったく同じ振る舞いを記述するためにJavaを使用しています。

だから、デフォルトの方法のために、それがされます優先呼クラスを実装するだけで、デフォルトのインターフェイスのメソッドを呼び出すために、クラスのデフォルトの方法でインターフェースを実装し、そうでない場合。

しかし!それのインスタンスを参照するクラスタイプのインスタンスによって置き換えられた場合、前の例では、インターフェイスに使用リファレンス実装のタイプは?

場合MyList実現しIndexOf、結果は違いはありません。ただし、MyList中に実装されていないIndexOf時、JavaとC#は、治療の違いで持っています。

C#のを見てみましょうMain機能が、コンパイラ(コンパイラエラーCS1929をするので、)MyList定義されていませんIndexOf

どのような地獄は、デフォルトメソッドのインタフェースであります

Javaはありますか?結果を実行している、いつものように、渡されました!

どのような地獄は、デフォルトメソッドのインタフェースであります

観点から、C#が、MyList私たちはそこにあることを知っているので、IndexOfインターフェイス、それを実現する必要がありますが、知らないふりをすることはできません。しかしによるならばIStringList呼び出しにIndexOf、考えることができるMyListのに気づいていないIndexOfため、許可がデフォルトのインターフェイスを呼び出し、インターフェース。インターフェイスまたはインターフェイスは、気づいていなかった、新しいインタフェースのメソッドを知らないあなたを責めないでください。しかし、あなたは気付いていない知っていた、そしてそれはあなたの権利です。

しかし、ジャワの観点から、MyList消費者は必ずしもではないMyListプロデューサー。消費者の視点からは、MyList達成するためStringListのインタフェースを、およびインタフェース定義がありindexOf、消費者の呼び出しがための方法は、myList.indexOf合理的です。

Javaの動作は、限り、あなたは、あなたが使用して実現するよう、比較的緩い達成されるかを制御しません。

より厳格なC#の動作消費者が使用することは、簡単に、コンパイラ自身の使用することによって理解することができたときに(どのくらいの使用を知りませんでしたが)それはクラスが実装、またはデフォルトのインターフェイスの実装です。クラスの内部に実装されていない場合は実際には、インタフェースの文書がポップアップ表示されませんスマートプロンプトエディタとのインタフェースを書かれていません。本当に書きたい、それがコールへのインタフェースに表示することができます。

Console.WriteLine(((IStringList)myList).IndexOf("Third"));

そして、上記の試験結果によると、将来的にMyList達成するためにIndexOf次のことを、そのようなコールは、呼び出し元に直接切り替えられたMyList実装、意味がないの問題。

4.質問2、多重継承の問題

かどうかは、JavaやC#クラスは多重継承を許可されていませんが、インターフェースが可能。デフォルトのインターフェイスの実装が行う方法を、多重継承の問題が生じる同様のクラスをもたらしますか?

例えば、人が歩くことができる、あなたがそこに取得する方法「雲の君主」、その後、鳥を取ることができますか?

4.1。C#の初めて目に

デフォルトの実装クラスのインターフェイスません:

interface IPerson
{
    void Walk() => Console.WriteLine("IPerson.Walk()");
}

interface IBird
{
    void Walk() => Console.WriteLine("IBird.Walk()");
}

class BirdPerson : IPerson, IBird { }

呼び出しの結果:

BirdPerson birdPerson = new BirdPerson();
// birdPerson.Walk();           // CS1061,没有实现 Walk
((IPerson)birdPerson).Walk();   // 输出 IPerson.Walk()
((IBird)birdPerson).Walk();     // 输出 IBird.Walk()

直接使用することはできませんbirdPerson.Walk()真実の前では話されました。しかし、インターフェースの異なる種類によってコールに、動作は、一貫性のない完全にデフォルトの方式インタフェースによって決定されます。クラスは、開発者が指定されたインターフェイスのデフォルトの動作を使用することを示し、どのような参照インタフェース、その後はない独自の実装を、ないので、これは、理解しやすいです。

単刀直入に言えば、あなたは雲の君主として見られている、法律を取るだろう彼の雇用主、あなたは鳥のように見える雲の君主を入れて、それが鳥の散歩です。

クラスが達成した場合は、状況が異なります。

class BirdPerson : IPerson, IBird
{
    // 注意这里的 public 可不能少
    public void Walk() => Console.WriteLine("BirdPerson.Walk()");
}
BirdPerson birdPerson = new BirdPerson();
birdPerson.Test();              // 输出 BirdPerson.Walk()
((IPerson)birdPerson).Walk();   // 输出 BirdPerson.Walk()
((IBird)birdPerson).Walk();     // 输出 BirdPerson.Walk()

それはクラス内に存在しない場合とまったく同じ出力、インタフェースで定義されたデフォルトの動作は、達成するための時間があります!

雲の人格のモナーク:あなたはどのように見えるかに関係なく、私はちょうど残しておきます。

ここれる唯一の注意点BirdPerson実装がWalk()宣言されなければならないpublic、またはC#は、内部クラスの振る舞いではなく、インタフェースの実装の振る舞いとして使用します。このC#とインターフェイスメソッドの実装のための要件は同じです:するインターフェイスメンバが宣言されなければならない実装しますpublic

4.2。その後、異なるJavaを見て

Java側に移動し、状況が異なっている、あまりにもコンパイラを聞かせて決して

interface Person {
    default void walk() {
        out.println("IPerson.walk()");
    }
}

interface Bird {
    default void walk() {
        out.println("Bird.walk()");
    }
}

// Duplicate default methods named walk with the parameters () and ()
// are inherited from the types Bird and Person
class BirdPerson implements Person, Bird { }

、この手段PersonBirdすべて同じ署名があるwalkデフォルト現在のメソッドが定義されては、コンパイラが知らないBirdPersonそれを行う方法を最後に。だから、一つだけの場合はwalk、それを達成するためのデフォルトを持っていますか?

interface Person {
    default void walk() {
        out.println("IPerson.walk()");
    }
}

interface Bird {
    void walk();
}

// The default method walk() inherited from Person conflicts
// with another method inherited from Bird
class BirdPerson implements Person, Bird { }

この手段は、二つのインタフェースが一貫性のない動作であることを、コンパイラは、まだ対処する方法がわかりませんBirdPerson

要するに、とにかく、それがすることであるBirdPerson自分自身を実装する必要がwalk()以来BirdPersonの内部実装walk()、その呼び出しの動作は非常にサスペンスを持っていません。

BirdPerson birdPerson = new BirdPerson();
birdPerson.walk();              // 输出 BirdPerson.walk()
((Person) birdPerson).walk();   // 输出 BirdPerson.walk()
((Bird) birdPerson).walk();     // 输出 BirdPerson.walk()

4.3おわりに、多重継承は問題ありません

クラスが実装する場合、複数のインターフェイスが同じメソッドシグネチャではなく、当然のデフォルト実現することになり、、問題なくによって定義されます。

クラスはメソッドのシグネチャで実装され、そしてどのような場合には、このメソッドを呼び出しているという場合は、問題ありません。

しかし、インターフェイスにデフォルトの実装がありますが、クラスがに対処するため、実際の行動に状況、C#の参照型を実装していませんが、Javaは直接に対処するための開発者に与えられています。私はずっと、すべての後に、心のデフォルトの方法は、メソッドのインタフェースの増加激論を処理するために、開発者を強制しないことです、C#の練習を好みます。

5.質問3、より複雑な例は、どのように分析します

より複雑なケースでは、ほとんどの時間や呼び出し方法を推測することができ、すべての後に、そこでの基本的な原則があります。

5.1。クラスの優先順位を実装

例えば、WalkBase定義Walk()方法は、しかし、任意のインターフェースを実装していなかったBirdPersonからWalkBase、継承実装するIPersonインタフェースを、しかし実現しなかったWalk()の実装次に、方法をWalkそれ?

実行しますWalkBase.Walk()-どのような状況に関係なく、好ましい方法などを

class WalkBase
{
    public void Walk() => Console.WriteLine("WalkBase.Walk()");
}

class BirdPerson : WalkBase, IPerson { }

static void Main(string[] args)
{
    BirdPerson birdPerson = new BirdPerson();
    birdPerson.Walk();              // 输出 WalkBase.Walk()
    ((IPerson)birdPerson).Walk();   // 输出 WalkBase.Walk()
}

親クラスが達成するために達成するためのサブクラスではなく、「ヘビーデューティー」のサブクラスではなく、「カバー」を持っている場合は、次のような、参照型に応じて、最も近いクラスに来る必要があります

class WalkBase : IBird  // <== 注意这里实现了 IBird
{
    public void Walk() => Console.WriteLine("WalkBase.Walk()");
}

class BirdPerson : WalkBase, IPerson  // <== 这里_没有_实现 IBird
{
    // 注意:这里是 new,而不是 override
    public new void Walk() => Console.WriteLine("BirdPerson.Walk()");
}

static void Main(string[] args)
{
    BirdPerson birdPerson = new BirdPerson();
    birdPerson.Walk();              // 输出 BirdPerson.Walk()
    ((WalkBase)birdPerson).Walk();  // 输出 WalkBase.Walk()
    ((IPerson)birdPerson).Walk();   // 输出 BirdPerson.Walk()
    ((IBird)birdPerson).Walk();     // 输出 WalkBase.Walk()
}

もしWalkBaseするvirtual定義Walk()、およびBirdPersonするためにoverride定義されWalk()ないサスペンス出力がすべて存在しないこと、BirdPerson.Walk()

class WalkBase : IBird
{
    public virtual void Walk() => Console.WriteLine("WalkBase.Walk()");
}

class BirdPerson : WalkBase, IPerson
{
    public override void Walk() => Console.WriteLine("BirdPerson.Walk()");
}

static void Main(string[] args)
{
    BirdPerson birdPerson = new BirdPerson();
    birdPerson.Walk();              // 输出 BirdPerson.Walk()
    ((WalkBase)birdPerson).Walk();  // 输出 BirdPerson.Walk()
    ((IPerson)birdPerson).Walk();   // 输出 BirdPerson.Walk()
    ((IBird)birdPerson).Walk();     // 输出 BirdPerson.Walk()
}

上記の例では、最後の文候補出力を通じてIBird.Walk()検索するWalkBase.Walk()と、WalkBase.Walk()仮想リンクを介しての方法を発見したBirdPerson.Walk()、まだ出力BirdPerson.Walk()C ++の学生は、この時間は非常に感じかもしれない学びました!

Java用として、すべてのメソッドは仮想メソッドです。それはできますがfinal、それは本当作るが、Javaの場合は容易になりますので、ない同じメソッドシグネチャは、サブクラスで定義されています。

5.2。ノークラスの実装は、参照の種類に応じて最寄りのデフォルトの実装を見つけます

または取るWalkBaseBirdPerson達成されたIBirdIPerson、例

class WalkBase : IBird { }
class BirdPerson : WalkBase, IPerson { }

((IPerson)birdPerson).Walk();   // 输出 IPerson.Walk()
((IBird)birdPerson).Walk();     // 输出 IBird.Walk()

コンパイラは、要件を実装しなければならないのでああ、もちろん、Javaで存在していませんBirdPerson.Walk()

5.3。どのようにだけでなく、より複雑なケース

本当に私は今、テストを行うことを提案、より複雑な状況があれば、真話します!

6.注意してデフォルトの方法

デフォルトの方法は、新しいライブラリの設計では、その歴史的な理由を持って現れ、それが途中で、この問題のデフォルトの方法を考慮することが最善ではありません。達成すべきデフォルト動作がある場合、いくつかのために、まだ抽象基底クラスより適切かもしれません。

しかし、関係は確かに非常に複雑で設計した場合のクラスおよびインタフェースは、複数であっても、類似または継承関係を必要とし、適切なデフォルトの方法も悪い考えではないことを検討してください。

おすすめ

転載: blog.51cto.com/jamesfancy/2476640