7

私は次のことをしたいと思います:

public class ImmutableList<T> {
  public <U super T> ImmutableList<U> add(U element) { ... }
}

つまり、 の不変リストが与えられたT場合、 リストに anyUを追加して の不変リストを生成でき、 のスーパータイプでなければならないUという制約があります。例えばUT

  • サルのリストにサルを追加すると、新しいサルのリストが生成されます。
  • サルのリストに人間を追加して、ヒト科の新しいリストを作成できます (おそらく、サルと人間の上限が最小です)。
  • 人類のリストに岩を追加して、新しいリストを生成できますObject(岩と人類は他の共通の祖先を共有していないと仮定します)。

これは理論的には素晴らしいように思えますが、JLS では下限Uは合法ではありません。代わりに次のように書くこともできます:

public class ImmutableList<T> {
  public ImmutableList<T> add(T element) { ... }
  public static <U> ImmutableList<U> add(ImmutableList<? extends U> list, U element) { ... }
}

Uこのようにして、コンパイラは、リストの要素型と追加したい要素型との間の最小上限を正しく推測します。これは合法です。それはまた吸う。比較:

// if 'U super T' were legal
list.add(monkey).add(human).add(rock);

// assuming 'import static ImmutableList.add'
add(add(add(list, monkey), human), rock);

私は関数型プログラミングの大ファンですが、Java コードを Lisp 方言のように見せたくありません。だから私は3つの質問があります:

  1. なんてこと?<U super T>バウンドが合法でないのはなぜですか?この質問は実際に以前にここで尋ねられました ( 「Java 型パラメーターに下限を設定できないのはなぜですか?」 ) 。 、」私は実際には購入しません。

  2. JLS セクション 4.5.1は次のように述べています。上記の代替staticメソッド シグネチャを考えると、コンパイラは明らかに必要な上限を推測できるため、引数が壊れているように見えます。そうではないと誰かが主張できますか?

  3. 最も重要なこと:下限を持つメソッド署名の合法的な代替案を考えられる人はいますか? list.add(monkey)簡単に言えば、目標は、 Lisp ( ) ではなくJava ( ) のように見える呼び出しコードを持つことですadd(list, monkey)

4

1 に答える 1

2

あなたはほとんどそれを行うことができますが、Java の型推論は、その価値がないほどうまく機能しません。

public abstract class ImmutableList< T > {
    public interface Converter< U, V > {
        V convert( U u );
    }

    public abstract < U > ImmutableList< U > map( Converter< ? super T, ? extends U > cnv );

    public static class EmptyIL< T > extends ImmutableList< T >{
        @Override
        public < U > EmptyIL< U > map( Converter< ? super T, ? extends U > cnv ) {
            return new EmptyIL< U >();
        }
    }

    public static class NonEmptyIL< T > extends ImmutableList< T > {
        private final T tHead;
        private final ImmutableList< ? extends T > ltTail;
        public NonEmptyIL( T tHead, ImmutableList< ? extends T > ltTail ) {
            this.tHead = tHead;
            this.ltTail = ltTail;
        }
        @Override
        public < U > NonEmptyIL< U > map( Converter< ? super T, ? extends U > cnv ) {
            return new NonEmptyIL< U >( cnv.convert( tHead ), ltTail.map( cnv ) );
        }
    }

    public < U > ImmutableList< U > add( U u, final Converter< ? super T, ? extends U > cnv ) {
        return new NonEmptyIL< U >( u, map( cnv ) );
    }

    public static < V > Converter< V, V > id() {
        return new Converter< V, V >() {
            @Override public V convert( V u ) {
                return u;
            }
        };
    }

    public static < W, U extends W, V extends W > Converter< W, W > sup( U u, V v ) {
        return id();
    }

    static class Rock {}
    static class Hominid {}
    static class Human extends Hominid {}
    static class Monkey extends Hominid {}
    static class Chimpanzee extends Monkey {}

    public static void main( String[] args ) {
        Monkey monkey = new Monkey();
        Human human = new Human();
        Rock rock = new Rock();

        // id() should suffice, but doesn't
        new EmptyIL< Chimpanzee >().
            add( monkey, ImmutableList.< Monkey >id() ).
            add( human, ImmutableList.< Hominid >id() ).
            add( rock, ImmutableList.< Object >id() );

        // sup() finds the supremum of the two arguments' types and creates an identity conversion
        // but we have to remember what we last added
        new EmptyIL< Chimpanzee >().
            add( monkey, sup( monkey, monkey ) ).
            add( human, sup( monkey, human ) ). // add( human, sup( monkey, monkey ) ) also works
            add( rock, sup( human, rock ) );
    }
}

少なくとも、型間の変換可能性をコンパイル時に強制できます。追加のボーナスとして、単なるサブクラス化を超えた独自のコンバーターを定義できます。しかし、コンバーターがない場合、適切なサブクラス化コンバーターをデフォルトとして使用する必要があることを Java に理解させることはできません。これにより、元の API に到達します。

于 2012-05-30T06:27:13.050 に答える