15

「API の設計はセックスのようなものです。1 つの間違いを犯すと、残りの人生をサポートすることができます」 ( Twitter の Josh Bloch )

Java ライブラリには多くの設計ミスがあります。Stack extends Vector(ディスカッション)、破損を引き起こさずにそれを修正することはできません。廃止しようとすることはできますがInteger.getInteger(議論)、おそらく永遠に残るでしょう。

それにもかかわらず、特定の種類のレトロフィットは、破損することなく行うことができます。

効果的な Java 2nd Edition、項目 18: 抽象クラスよりもインターフェイスを優先する: 既存のクラスは、新しいインターフェイスを実装するために簡単に改造できます。

例: String implements CharSequenceVector implements Listなど

有効な Java 2nd Edition、項目 42: varargs を慎重に使用する: 既存のクライアントに影響を与えずに、最終パラメーターとして配列を受け取る既存のメソッドを改造して、代わりに varags を使用できます。

(悪名高い) 有名な例はArrays.asList、混乱 (議論) を引き起こしましたが、破損は引き起こしませんでした。

この質問は、別の種類の改造に関するものです。

void既存のコードを壊さずに何かを返すようにメソッドを改良できますか?

私の最初の予感はイエスです。

  • 戻り値の型は、コンパイル時に選択されるメソッドには影響しません
    • 参照: JLS 15.12.2.11 - 戻り型は考慮されない
    • したがって、戻り値の型を変更しても、コンパイラによって選択されるメソッドは変更されません。
    • を改造しvoidて何かを返すことは合法です (ただし、その逆はできません!)
  • リフレクションを使ってもClass.getMethod返り値の型が区別されないなど

ただし、Java/API 設計の経験が豊富な他の人による、より徹底的な分析を聞きたいと思っています。


付録: 動機

タイトルで示唆されているように、1 つの動機は流暢なインターフェイススタイルのプログラミングを容易にすることです。

シャッフルされた名前のリストを出力するこの単純なスニペットを考えてみましょう:

    List<String> names = Arrays.asList("Eenie", "Meenie", "Miny", "Moe");
    Collections.shuffle(names);
    System.out.println(names);
    // prints e.g. [Miny, Moe, Meenie, Eenie]

入力リストを返すCollections.shuffle(List)ように宣言されていた場合、次のように記述できます。

    System.out.println(
        Collections.shuffle(Arrays.asList("Eenie", "Meenie", "Miny", "Moe"))
    );

の代わりに、などCollectionsの入力リストを返すと、はるかに快適に使用できるメソッドが他にもあります。実際、とreturnを持つことは特に不幸です。このように:voidreverse(List)sort(List)Collections.sortArrays.sortvoid

// DOES NOT COMPILE!!!
//     unless Arrays.sort is retrofitted to return the input array

static boolean isAnagram(String s1, String s2) {
    return Arrays.equals(
        Arrays.sort(s1.toCharArray()),
        Arrays.sort(s2.toCharArray())
    );
}

もちろん、流暢さを妨げているこのvoid戻り値の型は、これらのユーティリティ メソッドだけに限定されているわけではありません。メソッドは、流暢さを促進するために(alaおよび) をjava.util.BitSet返すように作成することもできます。thisStringBufferStringBuilder

// we can write this:
    StringBuilder sb = new StringBuilder();
    sb.append("this");
    sb.append("that");
    sb.insert(4, " & ");
    System.out.println(sb); // this & that

// but we also have the option to write this:
    System.out.println(
        new StringBuilder()
            .append("this")
            .append("that")
            .insert(4, " & ")
    ); // this & that

// we can write this:
    BitSet bs1 = new BitSet();
    bs1.set(1);
    bs1.set(3);
    BitSet bs2 = new BitSet();
    bs2.flip(5, 8);
    bs1.or(bs2);
    System.out.println(bs1); // {1, 3, 5, 6, 7}

// but we can't write like this!
//  System.out.println(
//      new BitSet().set(1).set(3).or(
//          new BitSet().flip(5, 8)
//      )
//  );

残念ながら、StringBuilder/とは異なりStringBufferすべてBitSetミューテーターは を返しvoidます。

関連トピック

4

3 に答える 3

14
于 2010-08-28T07:54:13.613 に答える
1

ソースの互換性のみに対処する必要がある場合は、先に進んでください。void から戻り値の型に変更しても壊れません。

しかし、実際にやりたいことに対処するには、流暢なインターフェースの問題は、行がかなり長くなる傾向があり、フォーマット後に多少読みにくいことだと思います。ビルダーにとっては問題なく動作しますが、それ以外にはおそらく使用しないでしょう。

これは、それで遊ぶためですか、それともこれが本当に素晴らしいとわかったからですか?

于 2010-08-28T09:41:49.707 に答える
1

改造できない場合でも、同じメソッドを使用するが正しい戻り値 (MyClassFluent) を持つ新しいクラスにクラスをラップできます。または、新しいメソッドを追加できますが、名前は異なりますが、代わりArrays.sort()Arrays.getSorted().

解決策は、強制することではなく、対処することだと思います。

編集:「voidメソッドのレトロフィット」の質問には答えていないことは知っていますが、あなたの答えはすでに本当に明らかです。

于 2010-08-28T08:45:34.307 に答える