2016-03-28 3 views
4

Während ich versuchte, ein Problem zu lösen, schrieb ich eine Methode mit einem Typparameter, der weder in den Argumenten noch im Rückgabetyp erwähnt wird, nämlich eine <T> void authenticate(Credentials credentials) Methode. Die einzige Abhängigkeit von T ist im Methodenkörper (Details voraus). Also meine Frage ist: Wie ist dieser generische Typ gelöst? Ich konnte keinen anderen Beispielcode mit dieser Art von generischem Typparameter finden.Wie löst Java generische Typparameter auf, die nicht in der Methodendeklaration enthalten sind?

So ist der Körper dieser Methode

so etwas wie ist
public <T extends AuthResponse> void authenticate(Credentials credentials) { 
    Call<T> call = getAuthCall(credentials); 
    Response<T> response = call.execute(); 

    // doSomething is an AuthResponse method 
    response.body().doSomething(); 
} 

, die ein Verfahren mit nur einem allgemeinen Rückgabetyp aufruft, die so geht:

private Call<T extends AuthResponse> getAuthCall(Credentials credentials) { 
    if (credentials instanceof CredentialsA) 
     return (Call<T>) service.authenticate((CredentialsA) credentials); 
    else if (credentials instanceof CredentialsB) 
     return (Call<T>) service.authenticate((CredentialsB) credentials); 
    else 
     throw new IllegalArgumentException(); 
} 

So sind die Typparameter der beiden Methoden scheinen verwandt zu sein. Aber ich konnte nicht verstehen wie. Auch die (Call<T>) Besetzung scheint nicht wie eine schöne Lösung für mein Problem - service ist eine Retrofit-Schnittstelle wie folgt aus:

public interface AuthServiceInterface { 
    @POST("auth/a") Call<AuthResponseA> authenticate(CredentialsA credentials); 
    @POST("auth/b") Call<AuthResponseB> authenticate(CredentialsB credentials); 
} 
+0

einfache Antwort - Java resove es nicht, wenn Sie es entfernen und T mit AuthResponse ersetzen - nichts wird sich ändern –

Antwort

3

Compiler in der Lage, Typ Parameter T in Rückgabetyp <T extends A> auf der Basis der Zielgröße abzuleiten. Zum Beispiel kann Compiler verstehen, dass T ist String, wenn Sie

schreiben
List<String> empty = Collections.emptyList(); 

In Ihrem Beispiel ist der Typ <T extends AuthResponse> auf beiden Seiten.


sollten Sie diese Art T in zwei Methoden verstehen könnte anders sein:

private Call<T extends AuthResponse> getAuthCall(Credentials credentials) { 
    return (Call<T>) new BadAuthCall<BadResponse>(); // unchecked warning 
} 

public <T extends AuthResponse> void authenticate(Credentials credentials) { 
    Call<T> call = getAuthCall(credentials); 
    ... 

// then somewhere in your code 
Call<GoodResponse> response = service.authenticate(credentials); 
// this code compiles, but will throw ClassCastException in runrime 

Erste Methode gibt nicht markiert Warnung, weil Rückgabewert Call<T> gegossen wird, die etwas auf der Anruferseite sein kann. Der Compiler versucht, uns von genau dieser Situation abzuhalten, wenn wir Call<BadResponse> auf Call<GoodResponse> werfen.

P.S. Mach dir keine Sorgen, Casts sind in deinem Beispiel höchstwahrscheinlich sicher.


Und das letzte, was: da Istwert T nicht innerhalb authenticate Methode bekannt ist, ist es erlaubt, nur die Methoden der Klasse AuthResponse zu nennen. Streng genommen, extends Schlüsselwort spielt keine große Rolle innerhalb der Methode, es betrifft nur die Rückgabe Wert Zuordnung zu einer Variablen.

1

Der Zweck von "Inferenz", wenn Sie eine generische Methode aufrufen und keinen expliziten Typ Zeugen bereitstellen, ist herauszufinden, ob mindestens ein Typ existiert, der für T ausgewählt werden kann Aufruf kompilieren. Es kann mehr als eine Option für T geben, die funktionieren würde, und in diesem Fall muss nicht berücksichtigt werden, welche ausgewählt ist - der Compiler muss kein Typargument auswählen, da es nicht in die kompiliert wird Bytecode sowieso - alles, was es braucht, ist zu befriedigen, dass es mindestens eine gültige Wahl gibt, und dann ist seine Aufgabe erledigt.

Wenn der Typparameter in keinem Parameter oder in Rückgabetypen verwendet wird, wirkt sich die Auswahl des Typs für T per Definition nicht darauf aus, ob der Aufruf kompiliert wird oder nicht. Das heißt, der Compiler kann den Typparameter vollständig ignorieren, da er für die Typprüfung des Aufrufs irrelevant ist.

Oder anders gesagt, der Compiler kann willkürlich jeden Typ innerhalb der Grenzen für T, z.B. Es kann AuthResponse oder YourSubclassOfAuthResponse oder MadeUpSubclassOfAuthResponseThatDoesntExistInYourCode wählen. Macht nichts. Die Auswahl hat keinen Einfluss darauf, ob der Anruf kompiliert wird oder nicht.

Diese Absurdität weist darauf hin, dass ein Typparameter einer generischen Methode, die nicht in den Parametern oder Ergebnistypen erscheint völlig sinnlos ist, und dient nicht möglich Zweck.

Warum muss Ihre Methode generisch sein? Wenn Ihre generische Methode kompiliert, dann bedeutet das, dass ich es nehmen kann, und alle Vorkommen T durch AuthResponse (oder sogar mit einer neu definierten UselessSubclassOfAuthResponse, die ich in Ihrem Code hinzufügen) ersetzen, und es wird auch kompilieren und eine vollkommen gültige sein nicht-generische Version Ihrer Methode. Bedenken Sie das für einen Moment. Wenn ich das tue, wird in der ersten Zeile Call<UselessSubclassOfAuthResponse> call = getAuthCall(credentials); angezeigt. Gibt Ihre getAuthCall() Methode eine Call<UselessSubclassOfAuthResponse> (oder Call<WhateverTheCallerWantsWithoutKnowingWhatItIs>) zurück? Wahrscheinlich nicht. Wenn die Typargumente Call und Response beliebig geändert werden können, ohne die Gültigkeit des Codes zu beeinflussen, dann entweder 1) Sie sind unbenutzt und nicht notwendig, oder 2) Ihr Code ist grundsätzlich typenunsicher irgendwo.

Verwandte Themen