2017-03-13 2 views
1

Ich versuche, etwas zu tun, und es möglicherweise nicht der beste Weg, um darüber zu gehen, oder vielleicht ich nur etwas in der Sprache Design fehlt. Grundsätzlich habe ich einen Dienst, der wie folgt aussieht:Verwenden Sie Variable für generische Art beim Erstellen neuer Instanz

public class MyService<T extends MyAbstractClass>{} 

Als Konfiguration auf meinen Server, möchte ich diktieren, welche Erweiterung von MyAbstractClass ich als Umgebungsvariable verwenden. In meinem speziellen Anwendungsfall ist es im Grunde eine spezifische Datenquelle für das, was ich mache, aber das ist hier nicht so wichtig.

Ich dachte, ich könnte etwas in der Art machen, aber es gibt mir Syntaxfehler, also ist es natürlich nicht erlaubt. Ich bin mir nicht sicher, ob ich das tun kann, was ich erreichen möchte.

@Bean(name="myService") 
public MyService<? extends MyAbstractClass> getMyService(){ 
    String packageName = "com.my.project." + ConfigUtils.get(env, "mypackage") + ""; 
    Class<? extends MyAbstractClass> clazz = (Class<? extends MyAbstractClass>) Class.forName(packageName); 
    return new MyService<clazz>(); 
} 

Ich habe sehe sich um eine Antwort, aber ich bin nicht einmal sicher, wie Begriff dieses ordnungsgemäß in einer Suchmaschine zu finden. Wie soll ich das in Java erreichen?

+0

Sind Sie bequem mit der Idee, dass MyService und MyService sind zur Laufzeit nicht unterscheidbar? –

+0

@OliverCharlesworth Ja, das bin ich, aber es ist eine Beschwerde, der ich ständig mit Java begegne, also lehnt es ein Teil von mir ab und zu ab, es zu akzeptieren. Ich versuche, alles nett und ordentlich zu machen, also muss ich nur die MyAbstractClass erweitern, ohne anderen Code oder unnötige Bedingungen hinzuzufügen. –

+1

Eine Möglichkeit, darüber nachzudenken, ist zu überlegen, ob der Platzhalter in diesem Kontext einen Vorteil gegenüber einfach MyService hat. –

Antwort

-1

Sie können in der Methodendeklaration keinen Platzhalter verwenden. Verwenden Sie stattdessen eine andere Typvariable und deklarieren Sie sie. Denken Sie daran,

public <S extends MyAbstractClass> S getMyService() { 
    // implementation omitted 
} 

dass S in diesem Fall ist nicht die gleiche wie T aus der Klassendeklaration. Deshalb gebe ich ihnen normalerweise verschiedene Namen.

Bei dem Verfahren Körper Sie dann so etwas wie verwenden:

Class<S> clazz = (Class<S>)Class.forName(packageName); 
return clazz.newInstance(); 
+0

Woher kommt der konkrete Wert für S? –

+0

Es ist eine Definition für den Umfang dieser Methode. Etwas wie: "Es gibt den Typ' S', der ein Untertyp von 'MyAbstractClass' ist und ich werde ihn in dieser Methode verwenden." Es ist genau so, wie einen generischen Typ für eine Klasse zu deklarieren, aber hier ist es für einen Methodenbereich deklariert. – Izruo

+0

Was ich meine ist, wie wird es parametrisiert? Im OP-Code ist der konkrete Typ nur zur Laufzeit bekannt. –

1

arbeiten Das wird nie:

new MyService<clazz>() 

wo clazz ist eine Variable.

Die Typparameter in einem new Aufruf können nur ein anderer Typparameter der umschließenden Methode oder Klasse sein (wenn die umschließende Methode nicht statisch ist) oder ein konkreter Klassenname.

Typparameter sind Entitäten, die sich nur auf die Kompilierungszeit auswirken, eingeführt werden, um den Typ des Parameters einzuschränken und Werte in generischen Klassen und Methoden zurückzugeben, und zum Kompilierungszeitpunkt fehlschlagen, wenn der Code möglicherweise den falschen Wert übergibt Laufzeit eingeben.

Der Wert von clazz kann nur zur Laufzeit ermittelt werden, daher kann der Compiler nicht herausfinden, was die tatsächliche Klasse zur Kompilierungszeit ist. Aus diesem Grund ist dies weder legal noch macht es Sinn, Klassenreferenz zu verwenden, die Variablen als Typparameter enthält.

Die Wahrheit ist, dass es ziemlich unmöglich ist, zu beschränken, was die zugrunde liegenden Typ-Parameter MyService Instanz zurückgegeben wird und MyService<? extends MyAbstractClass> ist so gut zurückzukehren, wie es nur geht.

Am allerwenigsten können Sie sicher, dass Ihre clazz in der Tat erstreckt MyAbstractService anstatt einfach blind glauben, dass der Fall ist:

class ServiceClass {} 
class MyService<T extends ServiceClass> {} 

class Main { 

    public MyService<? extends ServiceClass> getMyService() throws Exception { 
     final String className = "com.my.project." + ConfigUtils.get(env, "mypackage"); 
     @SuppressWarnings("unchecked") 
     final Class<? extends ServiceClass> clazz = (Class<? extends ServiceClass>) Class.forName(className); 
     if (!ServiceClass.class.isAssignableFrom(clazz)) 
     throw new RuntimeException("bad configuration blah blah"); 
     return new MyService<ServiceClass>(); 
    } 
} 

Beachten Sie, dass new MyService<ServiceClass>() nicht sehr ordentlich aussehen, als in der Tat clazz wird in der Regel etwas anderes sein; ein Nachkomme von ServiceClass aber nicht ServiceClass selbst ...Der Grund dafür, dass dies ohne Fehler kompiliert wird, ist die Tatsache, dass es bei dem bisherigen Code irrelevant ist, welchen Typ-Parameter Sie zurückgeben, da sie in der Laufzeit alle in denselben Code übersetzt werden.

In einem komplexeren Kontext jedoch, wenn Sie einige Operationen mit Objekten ausführen möchten, die sich auf den gleichen Typparameter beziehen, stoßen Sie auf Probleme bei der Kompilierung. So ist immer ratsam, eine saubere Art-Parameter-basierte Lösung von dem zur Verwendung von On-Set:

class ServiceClass {} 
class MyService<T extends ServiceClass> {} 

class Main { 

    public <S extends ServiceClass> MyService<S> getMyService() throws Exception { 
     final String className = "com.my.project." + ConfigUtils.get(env, "mypackage"); 
     @SuppressWarnings("unchecked") 
     final Class<S> clazz = (Class<S>) Class.forName(className); 
     if (!ServiceClass.class.isAssignableFrom(clazz)) 
     throw new RuntimeException("bad configuration blah blah"); 
     return new MyService<S>(); 
    } 
} 

Jetzt denke ich, es ziemlich seltsam ist, dass man nicht einen Verweis auf die Serviceklasse halten müßte irgendwo in MyService ... vielleicht würden Sie schließlich brauchen so etwas wie die folgenden Code:

class ServiceClass {} 

class MyService<S extends ServiceClass> { 
    private final Class<S> clazz; 
    MyService(Class<S> clazz) { 
    this.clazz = clazz; 
    } 

    Class<S> serviceClass() { 
    return clazz; 
    } 
} 

class Main { 
    public <S extends ServiceClass> MyService<S> getMyService() throws Exception { 
    final String className = "com.my.project." + ConfigUtils.get(env, "mypackage"); 
    @SuppressWarnings("unchecked") 
    final Class<S> clazz = (Class<S>) Class.forName(className); 
    if (!ServiceClass.class.isAssignableFrom(clazz)) 
     throw new RuntimeException("bad configuration blah blah"); 
    return new MyService<S>(clazz); 
    } 
} 

an diesem Punkt der Typ-Parameter S in getMyService beginnt Art notwendig.

Durch die Art und Weise, da Java 7 (glaube ich) Sie nicht die S in der neuen Anweisung angeben müssen, die vom Compiler abgeleitet werden:

return new MyService<>(clazz); 
Verwandte Themen