web-dev-qa-db-ja.com

ジェネリック型パラメーターを強制的にインターフェイスにする方法は?

Javaを指定する方法はありますか?ジェネリッククラスのタイプパラメータは、(それを拡張するだけではなく)インターフェイスでなければなりません!)

私がしたいことは次のとおりです:

public class MyClass<X extends SomeInterface, Y extends SomeOtherClass & X>

YはSomeOtherClassのサブクラスであり、かつXを実装する必要があることを意味します。現在コンパイラーが取得しているのは

タイプXはインターフェースではありません。境界パラメータとして指定することはできません

では、Xは常にインターフェイスでなければならないことをコンパイラに伝えるにはどうすればよいでしょうか。

編集:
OK、問題を少し単純化しすぎたと思います。実際のアプリケーションドメインを使用して、より明確にしましょう。

図を表すためのAPIがあります。 A DiagramにはNodeおよびEdgeオブジェクトが含まれます。これら3つのクラスはすべてShapeインターフェースを実装しています。図形には、子図形と親図形があり、図に属している場合があります。

問題は、このAPIの2つのバージョンを作成する必要があることです。1つは基本機能のみを備えたオープンソース、もう1つはより多くの機能を備えた拡張バージョンです。ただし、拡張APIは、拡張タイプ(ExtendedDiagramExtendedNodeExtendedEdge)を返すメソッドのみを提供する必要があります(ここで問題が発生します)- ExtendedShape)。
だから私はこのようなものを持っています:

/* BASIC CLASSES */
public interface Shape<X extends Shape<X,Y>, Y extends Diagram<X,Y>>{
    public List<X> getChildShapes();
    public X getParent();
    public Y getDiagram();
    ...
}

public class Diagram<X extends Shape<X,Y>, Y extends Diagram<X,Y>> implements Shape<X,Y>{...}
public class Edge<X extends Shape<X,Y>, Y extends Diagram<X,Y>> implements Shape<X,Y>{...}
...

/* EXTENDED CLASSES */
public interface ExtendedShape extends Shape<ExtendedShape,ExtendedDiagram> { ... }

public class ExtendedDiagram extends Diagram<ExtendedShape,ExtenedDiagram> implements ExtendedShape { ... }
public class ExtendedEdge extends Edge<ExtendedShape,ExtenedDiagram> implements ExtendedShape { ... }
...

拡張APIは正常に機能し、基本的なAPIコードはいくつかの警告を出しますが、基本的なAPIを使用すると主な問題が発生します。

public class SomeImporter<X extends Shape<X,Y>, Y extends Diagram<X,Y>, E extends Edge<X,Y>>{
    private Y diagram;

    public void addNewEdge(E newEdge){
        diagram.addChildShape(newEdge);
    ...

最後の行で次の警告が表示されます。

タイプDiagramのメソッドaddChildShape(X)は、引数(E)には適用できません

だから今、私はEもXを実装する必要があることを指定したいのですが、すべてうまくいきます-私は願っています;)

それはすべて意味がありますか?君たちはそれをする方法を知っていますか?または、上記の制限付きの拡張APIを取得するためのより良い方法はありますか?
私に固執してくれてありがとう、どんな助けでも大歓迎です!

45
Philipp Maschke

以下を使用できます。

class Foo<T extends Number & Comparable> {...}

1つの型パラメーターTを持つクラスFoo。Fooは、Numberのサブタイプであり、Comparableを実装する型でインスタンス化する必要があります。

15
idichekop

ジェネリックのコンテキストでは、<Type extends IInterface>は拡張と実装の両方を処理します。次に例を示します。

public class GenericsTest<S extends Runnable> {
    public static void main(String[] args) {
        GenericsTest<GT> t = new GenericsTest<GT>();
        GenericsTest<GT2> t2 = new GenericsTest<GT>();
    }
}

class GT implements Runnable{
    public void run() {

    }
}

class GT2 {

}

GenericsTestは、Runnableを実装しているため、GTを受け入れます。 GT2はそうではないので、その2番目のGenericsTestインスタンス化をコンパイルしようとすると失敗します。

3
Mike Thomsen

モデルを少し単純化できるかもしれません。ジェネリックが多すぎるとすぐに読みやすさの点で本当の痛みになり、パブリックAPIを定義する場合、これはかなりの問題になります。通常、括弧の中に何があるべきかもう理解できない場合、あなたはあなたの必要性のために行き過ぎています-そしてあなたはユーザーがあなたよりよくそれを理解することを期待することはできません...

とにかく、コードをコンパイルするには、Shapeタイプで次のようなものを定義してみてください。

public <S extends Shape<?,?>> void addChildShape(S shape);

それでうまくいくはずです。

HTH

1
Vincent

あなたは以下を書きました:

public interface Shape<X extends Shape<X,Y>, Y extends Diagram<X,Y>>{
    public List<X> getChildShapes();
    public X getParent();
    public Y getDiagram();
    ...
}

少なくとも、次のようにX型変数を取り除くことをお勧めします。

public interface Shape<Y>{
    public List<Shape<Y>> getChildShapes();
    public Shape<Y> getParent();
    public Diagram<Y> getDiagram();
    ...
}

その理由は、あなたが最初に書いたものは、型パラメータの潜在的に無制限の再帰的なネストに苦しんでいるからです。シェイプは親シェイプ内にネストできます。親シェイプは別のシェイプ内にネストできます。これらはすべて型シグネチャで説明する必要があります...読みやすくするための良いレシピではありません。ええと、あなたの例ではそうはいきません、あなたが "Shape <Shape <X >>"の代わりに "Shape <X>"を宣言するということですが、あなたが望むなら、それはあなたが進んでいる方向です実際にShapeを単独で使用します...

同様の理由で、さらに1歩進んでY変数を取り除くことをお勧めします。 Javaジェネリックスはこの種の構成にうまく対応していません。ジェネリックスを介してこのタイプのモデリングに静的タイプを強制しようとすると、型システムが機能しなくなると、後で物事を拡張し始めます。

これは典型的な動物/犬の問題です...動物にはgetChildren()がありますが、犬の子供も犬でなければなりません... Javaはこれにうまく対応しませんScalaのような言語のように抽象型が不足しているためですが、急いでScala)を使用する必要があると言っているわけではありません。型変数はあらゆる種類で宣言し始める必要があります彼らが実際に属していない場所の。

1
nbryant

私はここでベースから外れているかもしれませんが、ジェネリックの私の理解は少し異なります。

私が間違っている場合は誰かに修正してもらいます。

IMO-

これは非常にわかりにくい構造です。無限に参照されているShapeのサブクラスがあります。

ShapeインターフェースはHashMapと同じように使用されますが、HashMapがあなたがやろうとしていることを実行するのを見たことがないので、最終的にXをShapeのクラスにする必要があります。それ以外の場合は、HashMapを実行しています

Xをインターフェイスに対する「IS A」の関係にしたい場合は、発生しません。それはジェネリック医薬品の目的ではありません。ジェネリックスは、複数のオブジェクトにメソッドを適用するために使用され、インターフェイスをオブジェクトにすることはできません。インターフェイスは、クライアントとクラス間のコントラクトを定義します。メソッドで実行できるのは、Runnableインターフェースメソッドを使用するためにすべてまたは一部のメソッドが必要であるため、Runnableを実装する任意のオブジェクトを受け入れることです。それ以外の場合、指定せずにとして定義すると、クラスとクライアントとの間の規約により予期しない動作が発生し、誤った戻り値または例外がスローされる可能性があります。

例えば:

public interface Animal {
    void eat();

    void speak();
}

public interface Dog extends Animal {
    void scratch();

    void sniff();
}

public interface Cat extends Animal {
    void sleep();

    void stretch();
}

public GoldenRetriever implements Dog {
    public GoldenRetriever() { }

    void eat() {
        System.out.println("I like my Kibbles!");
    }

    void speak() {
        System.out.println("Rufff!");
    }

    void scratch() {
        System.out.println("I hate this collar.");
    }

    void sniff() {
        System.out.println("Ummmm?");
    }
}

public Tiger implements Cat {
    public Tiger() { }

    void eat() {
        System.out.println("This meat is tasty.");
    }

    void speak() {
        System.out.println("Roar!");
    }

    void sleep() {
        System.out.println("Yawn.");
    }

    void stretch() {
        System.out.println("Mmmmmm.");
    }
}

これでこのクラスを実行した場合、常に 'speak()'& 'sniff()'を呼び出すことができると期待できます。

public class Kingdom<X extends Dog> {
    public Kingdom(X dog) {
        dog.toString();
        dog.speak();
        dog.sniff();
    }
}

ただし、これを行った場合、常に「speak()」と「sniff()」を呼び出すことはできません

public class Kingdom<X> {
    public Kingdom(X object) {
        object.toString();
        object.speak();
        object.sniff();
    }
}

結論:

ジェネリックスを使用すると、インターフェイスではなく、さまざまなオブジェクトでメソッドを利用できます。ジェネリックへの最後のエントリは、オブジェクトのタイプである必要があります。

0
bdparrish

予約語は、タイプパラメータTとともに「拡張」して境界を指定します。

「…この文脈では、extendsは一般的な意味で使用されて(クラスのように)「拡張」または(インターフェースのように)「実装」のいずれかを意味します。 '[ https://docs.Oracle.com/ javase/tutorial/Java/generics/bounded.html ]

つまり、「extends」は、一部のクラス型パラメーターTの境界(クラスまたはインターフェース)を指定するためにのみ使用でき、インターフェース型パラメーターTは指定できません。

パブリッククラスMyClass <XはSomeInterfaceを拡張し、YはSomeOtherClass&Xを拡張します>

コンパイラはXをクラスとして解決します。 Xが2番目に出現し、型パラメーターY(明らかにクラスである必要がある)の場合、Xがインターフェースである必要があります。 Xがクラスであると既に解決しているため、Xの2番目の発生のエラーを通知します。

タイプXはインターフェースではありません。

さらに、Xが最初のオカレンスで無制限パラメーターとして指定されていた場合、コンパイラーはそれをクラスまたはインターフェースのいずれかに解決し、Xの2番目のオカレンスを可能なインターフェースと見なしてコンパイルを許可します。そうではなかったので、コンパイラは明確にし、

境界パラメータとして指定することはできません

0
rps

プリプロセッサーを使用して、コードの「縮小」バージョンを生成します。 aptと注釈を使用するのが良い方法かもしれません。

0
alex