Ist es möglich, die Compiler-Warnung "Ein generisches Array von T wird für einen varargs-Parameter erstellt" zu lösen?

153

Dies ist eine vereinfachte Version des fraglichen Codes. Eine generische Klasse verwendet eine andere Klasse mit generischen Typparametern und muss einen der generischen Typen an eine Methode mit varargs-Parametern übergeben:

class Assembler<X, Y> {
    void assemble(X container, Y... args) { ... }
}

class Component<T> {
    void useAssembler(T something) {

        Assembler<String, T> assembler = new Assembler<String, T>();

        //generates warning:
        // Type safety : A generic array of T is
        // created for a varargs parameter
        assembler.assemble("hello", something);
    }

}}

Gibt es eine korrekte Möglichkeit, den generischen Parameter an eine varargs-Methode weiterzugeben, ohne auf diese Warnung zu stoßen?

Natürlich so etwas wie

assembler.assemble("hello", new T[] { something });

funktioniert nicht, da Sie keine generischen Arrays erstellen können.

matt b
quelle
3
Eine seltsame. Es scheint, dass der Compiler hier die volle Typensicherheit gewährleisten sollte.
erickson
3
In Verbindung stehender Eintrag in Angelika Langers Java Generics FAQ: angelikalanger.com/GenericsFAQ/FAQSections/…
Flow

Antworten:

88

Abgesehen vom Hinzufügen @SuppressWarnings("unchecked")denke ich nicht.

Dieser Fehlerbericht enthält weitere Informationen, läuft jedoch darauf hinaus, dass der Compiler Arrays generischer Typen nicht mag.

Kevin
quelle
3
Ich habe vergessen zu erwähnen, dass ich @SuppressWarnings vermeiden wollte ("nicht markiert"). Dieser Fehlerbericht gibt mir wenig Hoffnung!
Matt b
3
Wie Joshua Bloch es in Effective Java formuliert: "Mischen Sie keine Generika und Arrays."
Timmos
20
dann implizit: Verwenden Sie Varargs nicht mit Generika! Richtig ... die Entscheidung, varargs Array und nicht Collection zuzuordnen, wird Java für immer stechen. Schön gemacht, Mr. Gosling.
Bernstein
57

Tom Hawtin wies in einem Kommentar darauf hin, aber um es genauer zu sagen: Ja, Sie können dies an der Deklarationsstelle (anstelle der (möglicherweise vielen) Aufrufstellen) lösen: Wechseln Sie zu JDK7.

Wie Sie in Joseph Darcys Blog-Beitrag sehen können , akzeptierte die Project Coin-Übung zur Auswahl einiger kleiner inkrementeller Sprachverbesserungen für Java 7 Bob Lees Vorschlag , so etwas zuzulassen@SuppressWarnings("varargs") auf der Methodenseite , dass diese Warnung in Situationen, in denen dies bekannt war, verschwindet sicher.

Dies wurde in OpenJDK mit diesem Commit implementiert .

Dies kann für Ihr Projekt nützlich sein oder auch nicht (viele Leute würden nicht gerne zu einer instabilen Vorabversion der JVM wechseln!), Aber vielleicht ist es das - oder vielleicht jemand, der diese Frage später findet (nachdem JDK7 veröffentlicht wurde) ) wird es nützlich finden.

Cowan
quelle
7
Die erwähnte Project Coin-Funktion ist jetzt verfügbar - siehe @SafeVarargs in Java 7.
George Hawkins
Alternative E in Bobs Vorschlag ist verlockend.
Christopher Perry
Java 8 scheint @SafeVarargs anstelle von @SuppressWarnings ("varargs") zu verwenden
Paul Wintz
17

Wenn Sie eine fließende Benutzeroberfläche suchen, können Sie das Builder-Muster ausprobieren. Nicht so prägnant wie Varargs, aber typsicher.

Eine statische, generisch typisierte Methode kann bei Verwendung des Builders einen Teil der Kesselplatte entfernen und gleichzeitig die Typensicherheit gewährleisten.

Der Bauarbeiter

public class ArgBuilder<T> implements Iterable<T> {

    private final List<T> args = new ArrayList<T>();

    public ArgBuilder<T> and(T arg) {
        args.add(arg);
        return this;
    }

    @Override
    public Iterator<T> iterator() {
        return args.iterator();
    }

    public static <T> ArgBuilder<T> with(T firstArgument) {
        return new ArgBuilder<T>().and(firstArgument);
    }
}

Es benutzen

import static com.example.ArgBuilder.*;

public class VarargsTest {

    public static void main(String[] args) {
        doSomething(new ArgBuilder<String>().and("foo").and("bar").and("baz"));
        // or
        doSomething(with("foo").and("bar").and("baz"));
    }

    static void doSomething(Iterable<String> args) {
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}
npgall
quelle
1
Die Kraft der Komposition. Ich mag das viel mehr als Varargs, es ist ausdrucksvoller.
Christopher Perry
1
@ChristopherPerry Nun, Sie müssen auch Ihre Codebasis berücksichtigen. Der Basiswert Collection(in diesem Fall an ArrayList) wird dem Anrufer aufgezwungen, während er möglicherweise weiß, dass a LinkedListgeeigneter ist, oder ein unveränderliches Array selbst (wie die Varargs aus der OP-Frage). In einem nicht spezialisierten Anwendungsfall mag dies angemessen sein, aber nur darauf hinweisen, dass dies in gewisser Weise auch eine Einschränkung darstellt, abhängig vom Code, der dies umgibt, und Ihren Anforderungen.
Searchengine27
5

Das explizite Umwandeln von Parametern in Object beim Aufruf der vararg-Methode macht den Compiler glücklich, ohne auf @SuppressWarnings zurückzugreifen.

public static <T> List<T> list( final T... items )
{
    return Arrays.asList( items );
}

// This will produce a warning.
list( "1", 2, new BigDecimal( "3.5" ) )

// This will not produce a warning.
list( (Object) "1", (Object) 2, (Object) new BigDecimal( "3.5" ) )

// This will not produce a warning either. Casting just the first parameter to 
// Object appears to be sufficient.
list( (Object) "1", 2, new BigDecimal( "3.5" ) )

Ich glaube, das Problem hier ist, dass der Compiler herausfinden muss, welche konkrete Art von Array erstellt werden soll. Wenn die Methode nicht generisch ist, kann der Compiler Typinformationen aus der Methode verwenden. Wenn die Methode generisch ist, versucht sie, den Array-Typ anhand der beim Aufruf verwendeten Parameter zu ermitteln. Wenn die Parametertypen homogen sind, ist diese Aufgabe einfach. Wenn sie variieren, versucht der Compiler meiner Meinung nach zu klug zu sein und erstellt ein generisches Array vom Typ Union. Dann fühlt es sich gezwungen, Sie davor zu warnen. Eine einfachere Lösung wäre gewesen, Object [] zu erstellen, wenn der Typ nicht besser eingegrenzt werden kann. Die obige Lösung erzwingt genau das.

Um dies besser zu verstehen, spielen Sie mit Aufrufen der obigen Listenmethode im Vergleich zur folgenden list2-Methode.

public static List<Object> list2( final Object... items )
{
    return Arrays.asList( items );
}
Konstantin Komissarchik
quelle
Dies funktioniert auch zum Beispiel: Iterator <?> It = Arrays.asList ((Object) t) .iterator; if (if, hasNext ()) {class = it.next (). getClass (); }, um beispielsweise die Klasse eines Objekts aus einem Array unbekannten Typs abzurufen.
ggb667
2

Sie können der Methode seit Java 7 @SafeVarargs hinzufügen , und Sie müssen keine Anmerkungen zum Clientcode machen.

class Assembler<X, Y> {

    @SafeVarargs
    final void assemble(X container, Y... args) {
        //has to be final...
    }
}
Daniel Hári
quelle
1

Sie können die Methoden überladen haben. Dies löst Ihr Problem nicht, minimiert aber die Anzahl der Warnungen (und ja, es ist ein Hack!)

class Assembler<X, Y> {
  void assemble(X container, Y a1) { ... }
  void assemble(X container, Y a1, Y a2) { ... }
  void assemble(X container, Y a1, Y a2, Y a3) { ... }
  void assemble(X container, Y a1, Y a2, Y a3, Y a4) { ... }
  void assemble(X container, Y... args) { ... }
}
EA.
quelle
23
Ew. Dies ist genau die Art von Hack, die Varargs verhindern sollen.
Amanda S
1
Dies kann ein gültiger Ansatz sein. Schauen Sie sich beispielsweise Guavas ImmutableSet.of an .
Jonathan
1

Es ist ein sehr leicht zu lösendes Problem: Verwenden List<T> !

Arrays vom Referenztyp sollten vermieden werden.

In der aktuellen Version von Java (1.7) können Sie die Methode mit markieren @SafeVargs der die Warnung vom Aufrufer entfernt wird. Seien Sie vorsichtig damit, und ohne ältere Arrays sind Sie immer noch besser dran.

Siehe auch den technischen Hinweis zu verbesserten Compiler-Warnungen und -Fehlern bei Verwendung nicht überprüfbarer formaler Parameter mit Varargs-Methoden .

Tom Hawtin - Tackline
quelle
6
Dies ist mit einem varargs-Parameter unvermeidlich, nicht wahr?
Matt b
4
Es gibt einen Vorschlag für JDK7, die Warnungsunterdrückung für die varargs-Methodendeklaration und nicht für deren Verwendung zuzulassen.
Tom Hawtin - Tackline
11
Dies ignoriert einen wichtigen Aspekt der Frage des Autors vollständig - varargs-Parameter erstellen ein Array, und diese generiert diese Warnung.
Daniel Yankowsky
2
Ich bin mit @Tom Hawtin einverstanden - Tackline. Einzelheiten finden Sie unter Bloch << Effecive Java >> Punkt 25: Listen Arrays vorziehen.
Stan Kurilin
2
Ich stimme Bloch in diesem Punkt im Allgemeinen zu, aber Varargs ist eine klare Ausnahme von der Regel ...
Joeri Hendrickx
0

Wenn ich mit Arrays vom generischen Typ arbeite, muss ich einen Verweis auf den generischen Typ übergeben. Damit kann ich den generischen Code mit java.lang.reflect.Array erstellen.

http://java.sun.com/javase/6/docs/api/java/lang/reflect/Array.html

KLE
quelle
Ich arbeite jedoch nicht mit Arrays vom generischen Typ, nicht direkt, sondern nur mit Varargs vom generischen Typ.
Matt b