2013-07-12 17 views
10

Ich bin nicht sicher, ob dies möglich ist oder nicht, aber was will ich erreichen, ist dies:Java Generics beschränken Schnittstelle

public static <A,B extends SomeClass & A> B makeB(A thing) {...} 

Wesentlichen eine Reflexion/Generation angetrieben Verfahren, möchte ich zur Verfügung zu stellen eine Sache vom Typ B, wobei B der Klasse SomeClass angehört und die Schnittstelle A implementiert, und A wird vom Benutzer über Generics bereitgestellt.

Ich frage nicht über die Mechanismen der Generierung von B - ich habe das unter Kontrolle. Was ich suche, ist eine Möglichkeit, das generische Argument <A> auf Interfaces zu beschränken, nicht auf Klassen, so dass ich die Syntax B extends SomeClass & A für die Sicherheit vom Typ clean verwenden kann.

Ist das möglich? Kennt jemand eine alternative Herangehensweise an dieses Problem?


Edit: Ich glaube, ich habe mich nicht sehr deutlich zum Ausdruck bringen, wie es Verwirrung zu sein scheint in den Kommentaren zu verursachen:

B vorgesehen ist ein Platzhalter für einen Platzhalter zu sein, so dass der Kunde kann ein einzelnes Objekt erhalten, das sowohl ein SomeClass als auch ein A ist, ohne dass das Casting auf Vertrauensbasis erfolgen muss. Der Client hat keinen Zugriff auf den Namen der aktuellen Klasse, die SomeClass und A implementiert, da sie zur Kompilierungszeit generiert wird, daher dieses Problem hinsichtlich der Typensicherheit.

+6

Nein, ist es nicht möglich. –

+1

Das gibt mir ein trauriges Gesicht :( – torquestomp

+3

Auf der anderen Seite, wenn Sie eine Reflexion getriebene Sache wollen, könnten Sie einfach eine 'Class ' übergeben und 'Class.isInterface()' explizit nennen. –

Antwort

5

Es ist unmöglich, eine solche Kompilierungszeitbeschränkung aufzuerlegen. Generische Typparameter sind Stellvertreter für Referenztypen; Sie unterscheiden nicht zwischen Klassentypen und Schnittstellentypen. Die Tatsache, dass zusätzliche Grenzen in der Deklaration eines Typparameters Schnittstellentypen sein müssen, ist nur nebensächlich - Ihre Strategie, dies als Mittel zur Imputation eines Typs als Schnittstelle zu nutzen, war clever, aber sie unterliegt der Einschränkung, dass die Typparameter can't be used in multiple bounds.

Ihre einzigen Optionen sind für eine Laufzeitprüfung an den Anrufer mit sein mit Class.isInterface() wie Louis Wasserman pointed out, oder lassen Sie ihn abzurechnen verantwortlich, was es geht in. So oder so, stellen Sie sicher, klar zu dokumentieren, die Erwartungen des Verfahrens und Verhalten.


B sollte ein Platzhalter für einen Platzhalter sein, so dass der Kunde ein einzelnes Objekt zu bekommen, die sowohl ein SomeClass und ein A ist ohne Gießen auf Vertrauen zu tun. Der Kunde muss keinen Zugriff auf den Namen der tatsächlichen Klasse, die

Dies scheint ein Widerspruch zu mir SomeClass und A implementiert. Es hat keinen Sinn, B zu deklarieren, wenn der Aufrufer nicht wissen kann, was er auswertet. Denken Sie daran: Der Aufrufer einer generischen Methode stellt seine Typargumente zur Verfügung. So kann ein Anrufer, der B entscheidet, ohne etwas, auf dem er basiert, nur raten - und das kann nie typsicher sein.

Es scheint, wie das, was Sie möchten, dass Ihre Methode wirklich zurück eine Art ist, die sowohl eine SomeClass und ein A, aber das ist schwierig, weil sie einen gemeinsamen Supertyp nicht teilen:

public static <A> SomeClass&A makeSomeClass(A thing) {...} 

(diese ist nonsensical syntax nur für Demonstrationszwecke)

Als Workaround, alternative Möglichkeiten zur Darstellung sowohl eine SomeClass und einige Schnittstellentyp. Zum Beispiel eine gemeinsame Methode hat ein SomeClass für die Rückgabe der Kandidaten Schnittstellen könnte:

public interface IsSomeClass { 
    SomeClass asSomeClass(); 
} 

public interface Foo extends IsSomeClass { } 

Die Umsetzung asSomeClass in der Tat nur this zurückkehren würde. Dann könnten Sie tun:

public static <A extends IsSomeClass> A makeSomeClass(Class<A> type) {...} 

Und der Anrufer dieser Methode wäre in der Lage das zurückgegebene Objekt entweder als Typ zu verwenden:

final Foo foo = makeSomeClass(Foo.class); 
final SomeClass someClass = foo.asSomeClass(); 

Wenn die Schnittstellen selbst kann nicht geändert werden, dann eine andere Option ist stattdessen eine Wrapper-Klasse und Zusammensetzung zu verwenden:

final class SomeClassWrapper<A> { 

    private final SomeClass someClass; 
    private final A a; 

    //constructor and getters, etc. 
} 

und Ihre Methode stattdessen eine Wrapper-Instanz zurückkehren würde, die Umsetzung Instanz Zuweisen beidesomeClass und a:

public static <A> SomeClassWrapper<A> makeSomeClass(Class<A> type) {...} 
+1

Vielen Dank für diese Antwort. – jahroy

1

Wenn SomeClass ist immer eine Klasse, die A in <B extends SomeClass & A>kann nur eine Schnittstelle sein, denn es gibt keine Mehrfachvererbung in Java ist. Der einzige Weg & A kann erfüllt werden, wenn A eine Schnittstelle ist.

+0

Sie könnten übersehen werden, aber der Compiler beschwert sich: Der Typ A ist keine Schnittstelle; Es kann nicht als beschränkter Parameter –

+0

@Jahroy und @EJP angegeben werden Sie scheinen zu übersehen, dass 'A' ein Typparameter in' ', weshalb das nicht kompiliert. –

+0

yeah ... Sie haben Recht ... aber es gibt keine Möglichkeit, dem Compiler mitzuteilen, dass das generische Argument eine Schnittstelle sein wird? –

1

Ich denke, das Problem hier ist, dass Sie eine B von dieser Methode zurückgeben möchten.

Sie geben B als Typparameter an, aber er erscheint nirgendwo anders in der Methodensignatur.

Wie soll der Compiler den Rückgabetyp von den Argumenten ableiten?

Es gibt keine Möglichkeit für den Client-Code anzugeben, was B ist.

Es scheint, als ob Sie entweder eine SomeClass oder eine A zurückgeben sollten.

Entweder kann ein B unter der Haube sein, sollte aber als SomeClass oder A zum Client-Code erscheinen.