Java SafeVarargs-Annotation, gibt es einen Standard oder eine Best Practice?

175

Ich bin kürzlich auf die Java- @SafeVarargsAnnotation gestoßen . Das Googeln nach dem, was eine variable Funktion in Java unsicher macht, hat mich ziemlich verwirrt (Haufenvergiftung? Gelöschte Typen?), Daher möchte ich ein paar Dinge wissen:

  1. Was macht eine variadische Java-Funktion in dem @SafeVarargsSinne unsicher (vorzugsweise in Form eines ausführlichen Beispiels erklärt)?

  2. Warum liegt diese Anmerkung im Ermessen des Programmierers? Sollte der Compiler dies nicht überprüfen können?

  3. Gibt es einen Standard, den man einhalten muss, um sicherzustellen, dass seine Funktion tatsächlich varags-sicher ist? Wenn nicht, welche Best Practices sind erforderlich, um dies sicherzustellen?

Oren
quelle
1
Haben Sie das Beispiel (und die Erklärung) in JavaDoc gesehen ?
Lordo
2
Bei Ihrer dritten Frage besteht eine Übung darin, immer ein erstes Element und andere zu haben : retType myMethod(Arg first, Arg... others). Wenn Sie nichts angeben first, ist ein leeres Array zulässig, und Sie haben möglicherweise eine Methode mit demselben Namen und demselben Rückgabetyp, die keine Argumente akzeptiert. Dies bedeutet, dass die JVM Schwierigkeiten hat, zu bestimmen, welche Methode aufgerufen werden soll.
fge
4
@jlordo Ich habe es getan, aber ich verstehe nicht, warum es im varargs-Kontext angegeben ist, da man diese Situation außerhalb einer varargs-Funktion leicht wiederholen kann (dies wurde überprüft, kompiliert mit erwarteten Typ-Sicherheitswarnungen und Fehlern zur Laufzeit).
Oren

Antworten:

244

1) Es gibt viele Beispiele im Internet und in StackOverflow zu dem speziellen Problem mit Generika und Varargs. Grundsätzlich ist es, wenn Sie eine variable Anzahl von Argumenten eines Typ-Parameter-Typs haben:

<T> void foo(T... args);

In Java sind Varargs ein syntaktischer Zucker, der zur Kompilierungszeit einfach "neu geschrieben" 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 im varargs-Parameter enthalten sind, und erstellt ein Array wie folgt new X[] { ...(arguments go here)... }.

Dies funktioniert gut, wenn der Vararg-Typ konkret ist String.... Wenn es sich um eine Typvariable handelt T..., funktioniert dies auch, wenn Tbekannt ist, dass es sich um einen konkreten Typ für diesen Aufruf handelt. Wenn die oben beschriebene Methode Teil einer Klasse wäre Foo<T>und Sie eine Foo<String>Referenz haben, foowäre es in Ordnung , sie aufzurufen , da wir wissen, dass sie Tsich Stringan diesem Punkt im Code befindet.

Es funktioniert jedoch nicht, wenn der "Wert" von Tein anderer Typparameter ist. In Java ist es unmöglich, ein Array eines Typparameter-Komponententyps ( new T[] { ... }) zu erstellen . Also verwendet Java stattdessen new Object[] { ... }(hier Objectist die Obergrenze von T; wenn die Obergrenze etwas anderes wäre, wäre es das anstelle von Object) und gibt Ihnen dann eine Compiler-Warnung.

Was ist also falsch daran, new Object[]statt new T[]oder was auch immer zu kreieren ? Nun, Arrays in Java kennen ihren Komponententyp zur Laufzeit. Daher hat das übergebene Array-Objekt zur Laufzeit den falschen Komponententyp.

Für die wahrscheinlich häufigste Verwendung von varargs, um einfach über die Elemente zu iterieren, ist dies kein Problem (der Laufzeittyp des Arrays ist Ihnen egal), daher 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 hierbei ist , dass wir von der Art der abhängig argszu sein T[], um sie als zurückzukehren T[]. Tatsächlich ist der Typ des Arguments zur Laufzeit jedoch 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 von der Tatsache abhängt, dass die Elemente des Arrays Instanzen von sind T
  • Unsicher: Wenn es davon abhängt, dass das Array eine Instanz von ist T[]

Dinge , die auf dem Laufzeittyp des Arrays abhängig sind: es als Typ - Rückkehr T[], es als Argument für einen Parameter vom Typ vorbei T[], immer den Array - Typ verwendet .getClass(), überschreiten sie sich auf Verfahren , die auf dem Laufzeittyp des Arrays abhängen, wie List.toArray()und Arrays.copyOf(), etc.

2) Die oben erwähnte Unterscheidung ist zu kompliziert, um automatisch unterschieden zu werden.

newacct
quelle
7
Nun, jetzt macht alles Sinn. Vielen Dank. Um zu sehen, dass ich Sie vollständig verstehe, nehmen wir an, wir haben eine Klasse. FOO<T extends Something>Wäre es sicher, das T [] einer varargs-Methode als Array von Somthings zurückzugeben?
Oren
30
Es könnte erwähnenswert sein, dass ein Fall, in dem die Verwendung (vermutlich) immer korrekt @SafeVarargsist, darin besteht, dass Sie das Array nur an eine andere Methode übergeben, die bereits so kommentiert ist (zum Beispiel: Ich schreibe häufig vararg-Methoden, die existieren, um ihr Argument mit einer Arrays.asList(...)anderen Methode in eine Liste umzuwandeln und an eine andere Methode zu übergeben (ein solcher Fall kann immer mit Anmerkungen versehen werden, @SafeVarargsda Arrays.asListdie Anmerkung vorhanden ist).
Jules
3
@newacct Ich weiß nicht, ob dies in Java 7 der Fall war, aber Java 8 erlaubt es nicht, es @SafeVarargssei denn, die Methode ist staticoder final, weil es sonst in einer abgeleiteten Klasse mit etwas Unsicherem überschrieben werden könnte.
Andy
3
und in Java 9 ist es auch für privateMethoden zulässig , die ebenfalls nicht überschrieben werden können.
Dave L.
3

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 in dieses:

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<>. Arrays.asList()Um dies zu verhindern und sicherzustellen, dass ich Heap Pollution nicht einführen kann, ist dies das, was ich tue.

Ich weiß, dass dies nur Ihre Nummer 3 beantwortet. newacct hat eine großartige Antwort für # 1 und # 2 oben gegeben, und ich habe nicht genug Ruf, um dies nur als Kommentar zu hinterlassen. : P.

Kühlschrank
quelle