ChatGPT解决这个技术问题 Extra ChatGPT

Is it possible to solve the "A generic array of T is created for a varargs parameter" compiler warning?

This is a simplified version of the code in question, one generic class uses another class with generic type parameters and needs to pass one of the generic types to a method with varargs parameters:

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

}

Is there any correct way to pass along the generic parameter to a varargs method without encountering this warning?

Of course something like

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

does not work since you cannot create generic arrays.

A weird one. It seems like the compiler should be able to assure full type safety here.
Related entry in Angelika Langer's Java Generics FAQ: angelikalanger.com/GenericsFAQ/FAQSections/…

A
Aleksandr Dubinsky

In Java 6, other than adding @SuppressWarnings("unchecked"), I don't think so.

This bug report has more information but it boils down to the compiler not liking arrays of generic types.


Forgot to mention I wanted to avoid @SuppressWarnings("unchecked"). That bug report gives me little hope!
As Joshua Bloch puts it in Effective Java: "Don't mix generics and arrays."
then, implicitely: do not user Varargs with Generics! Right... the decision to map varargs to Array and not Collection will keep stinging java forever. Nicely done Mr. Gosling.
A
Aleksandr Dubinsky

In Java 7, annotate the method declaration with @SafeVarargs


The mentioned Project Coin feature is now available - see @SafeVarargs in Java 7.
Alternative E in Bob's proposal is enticing.
Java 8 appears to use @SafeVarargs instead of @SuppressWarnings("varargs")
Unfortunately, the method needs to be final in order for @SafeVarargs to work, so if you're using abstract methods (like I do), it will return a compiler error: "@SafeVarargs annotation cannot be applied to non-final instance method".
n
npgall

If you're after a fluent-type interface, you could try the builder pattern. Not as concise as varargs but it is type safe.

A static generically-typed method can eliminate some of the boilerplate when using the builder, while retaining the type safety.

The builder

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

Using it

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

The power of composition. I like this much more than varargs, it's more expressive.
@ChristopherPerry well you have to consider your code base as well. The underlying Collection (in this case an ArrayList) is forced upon the caller, whereas they may know that a LinkedList is more appropriate, or an immutable array itself (such as the varargs from the OP question). In an un-specialized use case this may be appropriate, but just pointing out that this is also a limitation, in a way, depending on the code surrounding this and your needs.
K
Konstantin Komissarchik

Explicitly casting parameters to Object in vararg method invocation will make the compiler happy without resorting to @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" ) )

I believe the issue here is that the compiler needs to figure out what concrete type of array to create. If the method is not generic, the compiler can use type information from the method. If the method is generic, it tries to figure out the array type based on parameters used at invocation. If the parameter types are homogenic, that task is easy. If they vary, the compiler tries to be too clever in my opinion and creates a union-type generic array. Then it feels compelled to warn you about it. A simpler solution would have been to create Object[] when type cannot be better narrowed down. The above solution forces just that.

To understand this better, play around with invocations to the above list method compared to the following list2 method.

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

This works, also for instance: Iterator it = Arrays.asList((Object)t).iterator; if(if,hasNext()){ class = it.next().getClass(); } for instance to get the class of a object out of an array of unknown type.
T
Tom Hawtin - tackline

It is a very easy problem to solve: Use List<T>!

Arrays of reference type should be avoided.

In the current version of Java (1.7), you can mark method with @SafeVargs which will remove the warning from the caller. Careful with that though, and you're still better off without legacy arrays.

List.of() provides a relatively concise way of writing an (unmodifiable) List until Java gains an appropriate literal representation.

See also the Improved Compiler Warnings and Errors When Using Non-Reifiable Formal Parameters with Varargs Methods tech note.


this is unavoidable with a varargs parameter, isn't it?
Arrays of references should be avoided because they are a legacy and play badly with generics (they have incorrect subtyping rules, for instance). List is more useful, whereas array is a raw implementation detail.
There is a proposal for JDK7 to allow the warning suppression to go on the varargs method declaration rather than its usage.
This completely ignores an important aspect of the author's question - varargs parameters DO create an array, and that generates this warning.
I'm agree with @Tom Hawtin - tackline. For details see Bloch <> Item 25: Prefer lists to arrays.
D
Daniel Hári

You can add @SafeVarargs to method since Java 7, and you don't have to annotate on client code.

class Assembler<X, Y> {

    @SafeVarargs
    final void assemble(X container, Y... args) {
        //has to be final...
    }
}

E
EA.

You can have overload the methods. This does not solve your problem but it minimizes the number of warnings (and yes, it's a 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) { ... }
}

Ew. This is exactly the kind of hack that varargs are supposed to prevent.
This can be a valid approach, for example, take a look at Guava's ImmutableSet.of.
K
KLE

When workings with arrays of generic type, I am forced to pass a reference to the generic type. With that, I can actually do the generic code, using java.lang.reflect.Array.

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


I'm not working with arrays of generic type though, not directly, just varargs of a generic type.