3

私がこのインターフェースを持っているとしましょう:

interface Foo<T extends Foo<T>> { // ... }

そして、それを実装する次の 3 つのクラス:

class TrueFoo  implements Foo<TrueFoo > { // ... }

class FalseFoo implements Foo<FalseFoo> { // ... }

class WrongFoo implements Foo<FalseFoo> { // ... }
//                            ^^^^^^^^ this here is wrong, only 
//                                     Foo<WrongFoo> should be allowed

次のメソッドのメソッド シグネチャは、TrueFoo または FalseFoo 型のオブジェクトを返すことができるが、WrongFoo 型のオブジェクトを返さないことを強制するには、どのようなものである必要がありますか?

public ???? getFoo(boolean which) {
    return which ? new TrueFoo() : new FalseFoo();
}

以下は機能しません。

  • これにより、新しい WrongFoo() を返すこともできます。

    public Foo<?> getFoo(boolean which) {
        return which ? new TrueFoo() : new FalseFoo();
    }
    
  • これにより、何も返すことができません(説明は次のとおりです)

    public <FooType extends Foo<FooType>> FooType getFoo(boolean which) {
        return which ? new TrueFoo() : new FalseFoo();
    }
    

    問題は、FooType の型が、getFoo()実際に返そうとするオブジェクトではなく、関数を呼び出すコンテキストによって決定されることです。

ちなみに、メソッドを呼び出すときにこの署名を強制するのは簡単です。

public <FooType extends Foo<FooType>> void test(FooType foo) {
    // Do something with foo
}

これを WrongFoo のインスタンスで呼び出そうとすると、バインドされた不一致エラーが発生します。

メソッドの戻り値の型にもこれを行う方法があるはずだと思います。

または、Foo のインターフェイス定義に既にこの署名を強制する方法はありますか?

編集:

このインターフェイスが何をするべきかを視覚化するために、たとえば次のバージョンについて考えることができます。

interface CanCopy<Type extends CanCopy<Type>> {
    public Type copy();
}

明らかにクラス likeFoo implements CanCopy<Bar>は意味をなさず、Foo implements CanCopy<Foo>orだけBar implements CanCopy<Bar>です。

編集2:

ソリューションが存在するという概念実証と同様に、次のヘルパー クラスを自分で定義できます。

class FooResult {
    private final Foo<?> foo;

    public <FooType extends Foo<FooType>> FooResult(FooType foo) {
        this.foo = foo;
    }

    @SuppressWarnings("unchecked")
    public <FooType extends Foo<FooType>> FooType getFoo() {
        return (FooType) foo;
    }
}

次に、 getFoo メソッドを次のタイプにするように要求できます。

public FooResult getFoo(boolean which) {
    return which ? new FooResult(new TrueFoo()) : new FooResult(new WrongFoo());
}

そうすれば、get メソッドから WrongFoo を返すことができなくなります。

しかし、これは明らかに、Javaジェネリックが通常コードを作成する傾向があるのと同じくらいエレガントであるには少し複雑すぎます(私の経験では)...では、これを何とか短縮できますか?

編集3:

次のように定義することで、インターフェイスを実装している人にチェックを提供する別の方法を見つけました。

interface Foo<FooType extends Foo<FooType>> { 
    // Other interface definitions

    /**
     * Please implement with just this line in it:<br/><br/>
     * &nbsp;&nbsp;&nbsp;&nbsp;<code>return this;</code>
     * @return <code>this</code>
     */
    public FooType returnThis();
}

ここで、誰かが上記のように WrongFoo クラスを実装しようとすると、メソッドを提供する必要があり、doctype で要求FalseFoo returnThis()されているとおりに単に実装するとreturn this、その行でエラーがスローされます。

これは保証ではありませんが、クラスの不注意なコピーによるミスに対するかなり良いダミーチェックです...そして、とにかくこの署名付きのメッセージが必要な場合には、それは素晴らしい解決策になるでしょう.

他にアイデアはありますか?

4

1 に答える 1

3

ジェネリックで発生している問題は、問題ではなく症状です。具体的な実装に依存するようにインターフェイスを要求しています。getFoo インターフェイス メソッドが WrongFoo ではないものを返すようにしたい場合は、TrueFoo または FalseFoo であるが WrongFoo ではないものを返さなければなりません。クラス階層には、この特定の間接性はありません。

追加するには、次のようにする必要があります。

class TrueFoo extends SpecialFoo {...}

class FalseFoo extends SpecialFoo {...}

class WrongFoo implements Foo<WrongFoo> {...}

class SpecialFoo implements Foo<SpecialFoo> {  
  SpecialFoo getFoo() {...}
}

Java は共変の戻り値の型をサポートしていることを思い出してください。そのため、SpecialFoo を返すことで、Foo インターフェイスで作成されたコントラクトを実装できます。Foo getFoo() {...}

TrueFoo と FalseFoo に固有の動作が必要な場合は、さらに SpecialFoo などをパラメーター化することができます

編集:

あなたの編集を読んだ後、あなたが達成しようとしていることが言語によってサポートされているとは思えません。具体的な実装に基づいてインターフェイスを制約したいようです。これにより、定義により、インターフェイスではなくなります。次の例は、探しているものに最も近いと思います。

interface CanCopy<T extends CanCopy<T>> {
       T copy();
}

interface FooCanCopy extends CanCopy<Foo> {
}

interface BarCanCopy extends CanCopy<Bar> {
}

class Foo implements FooCanCopy {

    public Foo copy() {
        return null;
    }
}

class Bar implements BarCanCopy {

    public Bar copy() {
        return null;
    }
}

なぜこのアプローチが機能しないのかが明確になったことを願っています。ここでも、誰かがそのようなことをするのを防ぐことはできませんclass Baz implements FooCanCopy。なぜあなたがこれをやろうとしているのか知りたいですか?開発者が間違いを犯すのを防ぐためであれば、単体テストのイントロスペクションやパッケージの変更など、他のオプションがあるかもしれません。

于 2012-11-30T02:24:01.840 に答える