Ich bin vor kurzem auf Java gestoßen @SafeVarargs
Anmerkung. Das Googeln nach dem, was eine variadische Funktion in Java unsicher macht, hat mich ziemlich verwirrt (Haufenvergiftung? gelöschte Typen?), also würde ich gerne ein paar Dinge wissen:
-
Was macht eine variadische Java-Funktion im @SafeVarargs
sinnvoll (am besten in Form eines vertiefenden Beispiels erläutert)?
-
Warum bleibt diese Anmerkung dem Ermessen des Programmierers überlassen? Ist das nicht etwas, was der Compiler überprüfen können sollte?
-
Gibt es einen Standard, an den man sich halten muss, um sicherzustellen, dass seine Funktion tatsächlich varagssicher ist? Wenn nicht, was sind die Best Practices, um dies sicherzustellen?
1) Es gibt viele Beispiele im Internet und auf StackOverflow zu dem speziellen Problem mit Generics und Varargs. Grundsätzlich ist dies der Fall, wenn Sie eine variable Anzahl von Argumenten eines Typparametertyps haben:
<T> void foo(T... args);
In Java sind Varargs ein syntaktischer Zucker, der zur Kompilierzeit einfach “umgeschrieben” wird: ein Varargs-Parameter vom Typ X...
wird in einen Parameter vom Typ konvertiert X[]
; und jedes Mal, wenn diese varargs-Methode aufgerufen wird, sammelt der Compiler alle “variablen Argumente”, die in den varargs-Parameter eingehen, und erstellt genau wie ein Array new X[] { ...(arguments go here)... }
.
Dies funktioniert gut, wenn der Typ varargs konkret ist String...
. Wenn es sich um eine Typvariable wie z T...
es funktioniert auch wenn T
ist bekanntermaßen ein konkreter Typ für diesen Anruf. zB wenn die obige Methode Teil einer Klasse wäre Foo<T>
und du hast eine Foo<String>
Referenz, dann anrufen foo
auf es wäre okay, weil wir wissen T
ist String
an dieser Stelle im Code.
Es funktioniert jedoch nicht, wenn der “Wert” von T
ist ein weiterer Typparameter. In Java ist es unmöglich, ein Array eines Typparameter-Komponententyps (new T[] { ... }
). Also verwendet Java stattdessen new Object[] { ... }
(hier Object
ist die Obergrenze von T
; Wenn die Obergrenze etwas anderes wäre, wäre es das anstelle von Object
) und gibt Ihnen dann eine Compilerwarnung aus.
Also, was ist falsch am Erstellen new Object[]
Anstatt von new T[]
oder Wasauchimmer? Nun, Arrays in Java kennen ihren Komponententyp zur Laufzeit. Somit hat das übergebene Array-Objekt zur Laufzeit den falschen Komponententyp.
Für die wahrscheinlich häufigste Verwendung von varargs, einfach über die Elemente zu iterieren, ist dies kein Problem (Sie interessieren sich nicht für den Laufzeittyp des Arrays), also ist dies sicher:
@SafeVarargs
final <T> void foo(T... args) {
for (T x : args) {
// do stuff with x
}
}
Für alles, was vom Laufzeitkomponententyp des übergebenen Arrays abhängt, ist es jedoch nicht sicher. Hier ist ein einfaches Beispiel für etwas, das unsicher ist und abstürzt:
class UnSafeVarargs
{
static <T> T[] asArray(T... args) {
return args;
}
static <T> T[] arrayOfTwo(T a, T b) {
return asArray(a, b);
}
public static void main(String[] args) {
String[] bar = arrayOfTwo("hi", "mom");
}
}
Das Problem dabei ist, dass wir von der Art abhängig sind args
sein T[]
um es als zurückzugeben T[]
. Aber eigentlich ist der Typ des Arguments zur Laufzeit keine Instanz von T[]
.
3) Wenn Ihre Methode ein Argument vom Typ hat T...
(wobei T ein beliebiger Typparameter ist), dann:
- Sicher: Wenn Ihre Methode nur davon abhängt, dass die Elemente des Arrays Instanzen von sind
T
- Unsicher: Wenn es darauf ankommt, dass das Array eine Instanz von ist
T[]
Zu den Dingen, die vom Laufzeittyp des Arrays abhängen, gehören: Rückgabe als Typ T[]
indem Sie es als Argument an einen Parameter vom Typ übergeben T[]
um den Array-Typ mit abzurufen .getClass()
und übergibt es an Methoden, die vom Laufzeittyp des Arrays abhängen, wie z List.toArray()
und Arrays.copyOf()
etc.
2) Die oben erwähnte Unterscheidung ist zu kompliziert, um einfach automatisch unterschieden zu werden.
Berücksichtigen Sie dies für Best Practices.
Wenn Sie dies haben:
public <T> void doSomething(A a, B b, T... manyTs) {
// Your code here
}
Ändern Sie es so:
public <T> void doSomething(A a, B b, T... manyTs) {
doSomething(a, b, Arrays.asList(manyTs));
}
private <T> void doSomething(A a, B b, List<T> manyTs) {
// Your code here
}
Ich habe festgestellt, dass ich normalerweise nur Varargs hinzufüge, um es für meine Anrufer bequemer zu machen. Für meine interne Implementierung wäre es fast immer bequemer, a zu verwenden List<>
. Also zum Huckepack Arrays.asList()
und sicherstellen, dass ich auf keinen Fall Heap Pollution einführen kann, das ist, was ich tue.
Ich weiß, das beantwortet nur deine #3. newacct hat eine großartige Antwort für Nr. 1 und Nr. 2 oben gegeben, und ich habe nicht genug Ruf, um dies einfach als Kommentar zu hinterlassen. 😛
Haben Sie das Beispiel (und die Erklärung) in gesehen JavaDoc?
– jlordo
9. Januar 2013 um 8:32 Uhr
Für Ihre dritte Frage besteht eine Praxis darin, immer ein erstes Element und andere zu haben:
retType myMethod(Arg first, Arg... others)
. Wenn Sie nicht angebenfirst
ein leeres Array ist zulässig, und Sie haben möglicherweise eine Methode mit demselben Namen und demselben Rückgabetyp, die keine Argumente annimmt, was bedeutet, dass es für die JVM schwierig sein wird, festzustellen, welche Methode aufgerufen werden soll.– fg
9. Januar 2013 um 8:42 Uhr
@jlordo habe ich getan, aber ich verstehe nicht, warum es im varargs-Kontext angegeben ist, da man diese Situation leicht außerhalb einer varargs-Funktion replizieren kann (bestätigt, kompiliert mit erwarteten Typsicherheitswarnungen und -fehlern zur Laufzeit).
– Ören
9. Januar 2013 um 8:51 Uhr