¿Es posible resolver la advertencia del compilador "Se crea una matriz genérica de T para un parámetro varargs"?

153

Esta es una versión simplificada del código en cuestión, una clase genérica usa otra clase con parámetros de tipo genérico y necesita pasar uno de los tipos genéricos a un método con parámetros varargs:

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);
    }

}

¿Hay alguna forma correcta de pasar el parámetro genérico a un método varargs sin encontrar esta advertencia?

Por supuesto algo como

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

no funciona ya que no puede crear matrices genéricas.

mate b
fuente
3
Uno raro. Parece que el compilador debería poder garantizar la seguridad de tipo completo aquí.
erickson
3
Entrada relacionada en las Preguntas frecuentes sobre genéricos Java de Angelika Langer: angelikalanger.com/GenericsFAQ/FAQSections/…
Flujo el

Respuestas:

88

Aparte de agregar @SuppressWarnings("unchecked"), no lo creo.

Este informe de error tiene más información, pero se reduce a que al compilador no le gustan las matrices de tipos genéricos.

Kevin
fuente
3
Olvidé mencionar que quería evitar @SuppressWarnings ("sin marcar"). ¡Ese informe de error me da pocas esperanzas!
mate b
3
Como Joshua Bloch lo pone en Java efectivo: "No mezcle genéricos y matrices".
Timmos
20
entonces, implícitamente: ¡no utilices Varargs con genéricos! Correcto ... la decisión de asignar varargs a Array y no a Collection seguirá picando a Java para siempre. Bien hecho Sr. Gosling.
bernstein
57

Tom Hawtin señaló esto en un comentario, pero para ser más explícito: sí, puede resolver esto en el sitio de declaración (en lugar de los (potencialmente muchos) sitios de llamada): cambie a JDK7.

Como puede ver en la publicación del blog de Joseph Darcy , el ejercicio Project Coin para seleccionar algunas pequeñas mejoras incrementales del lenguaje para Java 7 aceptó la propuesta de Bob Lee de permitir que algo así como @SuppressWarnings("varargs")en el lado del método haga que esta advertencia desaparezca en situaciones donde se sabía que era seguro.

Esto se ha implementado en OpenJDK con esta confirmación .

Esto puede o no ser útil para su proyecto (¡muchas personas no estarían felices de cambiar a una versión inestable de JVM previa al lanzamiento!) Pero tal vez lo sea, o tal vez alguien que encuentre esta pregunta más tarde (después de que JDK7 salga) ) lo encontrará útil.

Cowan
fuente
77
La característica mencionada de Project Coin ya está disponible: consulte @SafeVarargs en Java 7.
George Hawkins
La alternativa E en la propuesta de Bob es atractiva.
Christopher Perry
Parece que Java 8 usa @SafeVarargs en lugar de @SuppressWarnings ("varargs")
Paul Wintz
17

Si buscas una interfaz de tipo fluido, puedes probar el patrón de construcción. No es tan conciso como varargs, pero es de tipo seguro.

Un método estático de tipo genérico puede eliminar parte de la placa repetitiva cuando se utiliza el constructor, al tiempo que conserva la seguridad de tipo.

El constructor

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);
    }
}

Usándolo

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
fuente
1
El poder de la composición. Esto me gusta mucho más que varargs, es más expresivo.
Christopher Perry
1
@ChristopherPerry, también debes considerar tu código base. El subyacente Collection(en este caso, an ArrayList) es forzado sobre la persona que llama, mientras que pueden saber que a LinkedListes más apropiado, o una matriz inmutable en sí misma (como los varargs de la pregunta OP). En un caso de uso no especializado, esto puede ser apropiado, pero solo señala que esto también es una limitación, en cierto modo, dependiendo del código que rodea esto y sus necesidades.
searchengine27
5

La conversión explícita de parámetros a Object en la invocación del método vararg hará feliz al compilador sin recurrir a @SuppressWarnings.

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" ) )

Creo que el problema aquí es que el compilador necesita descubrir qué tipo concreto de matriz crear. Si el método no es genérico, el compilador puede usar información de tipo del método. Si el método es genérico, intenta averiguar el tipo de matriz en función de los parámetros utilizados en la invocación. Si los tipos de parámetros son homogéneos, esa tarea es fácil. Si varían, el compilador intenta ser demasiado inteligente en mi opinión y crea una matriz genérica de tipo unión. Entonces se siente obligado a advertirte al respecto. Una solución más simple hubiera sido crear Object [] cuando el tipo no se puede reducir mejor. La solución anterior obliga a eso.

Para entender esto mejor, juegue con invocaciones al método de lista anterior en comparación con el siguiente método de lista2.

public static List<Object> list2( final Object... items )
{
    return Arrays.asList( items );
}
Konstantin Komissarchik
fuente
Esto funciona, también por ejemplo: Iterator <?> It = Arrays.asList ((Object) t) .iterator; if (if, hasNext ()) {class = it.next (). getClass (); }, por ejemplo, para sacar la clase de un objeto de una matriz de tipo desconocido.
ggb667
2

Puede agregar @SafeVarargs al método desde Java 7, y no tiene que anotar en el código del cliente.

class Assembler<X, Y> {

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

Puede haber sobrecargado los métodos. Esto no resuelve su problema, pero minimiza la cantidad de advertencias (y sí, ¡es un truco!)

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.
fuente
23
Ew. Este es exactamente el tipo de pirateo que se supone que previenen los varargs.
Amanda S
1
Esto puede ser un enfoque válido, por ejemplo, echar un vistazo a Guava ImmutableSet.of .
Jonathan
1

Es un problema muy fácil de resolver: List<T>¡ Usar !

Se deben evitar las matrices de tipo de referencia.

En la versión actual de Java (1.7), puede marcar el método con el @SafeVargsque eliminará la advertencia de la persona que llama. Sin embargo, tenga cuidado con eso, y aún estará mejor sin matrices heredadas.

Consulte también las Advertencias y errores mejorados del compilador al usar parámetros formales no re-confiables con la nota técnica Métodos de Varargs .

Tom Hawtin - tackline
fuente
66
Esto es inevitable con un parámetro varargs, ¿no?
mate b
44
Hay una propuesta para que JDK7 permita que la supresión de advertencia vaya a la declaración del método varargs en lugar de su uso.
Tom Hawtin - tackline
11
Esto ignora por completo un aspecto importante de la pregunta del autor: los parámetros varargs SÍ crean una matriz, y eso genera esta advertencia.
Daniel Yankowsky
2
Estoy de acuerdo con @Tom Hawtin - tackline. Para más detalles, consulte Bloch << Effecive Java >> Elemento 25: Prefiera listas a las matrices.
Stan Kurilin
2
Generalmente estoy de acuerdo con Bloch en este caso, pero varargs es una clara excepción a la regla ...
Joeri Hendrickx
0

Cuando se trabaja con matrices de tipo genérico, me veo obligado a pasar una referencia al tipo genérico. Con eso, puedo hacer el código genérico, usando java.lang.reflect.Array.

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

KLE
fuente
Sin embargo, no estoy trabajando con matrices de tipo genérico, no directamente, solo varargs de tipo genérico.
mate b