Czy można rozwiązać ostrzeżenie kompilatora „Generic array of T is created for a varargs parameter”?

153

To jest uproszczona wersja omawianego kodu, jedna klasa ogólna używa innej klasy z parametrami typu ogólnego i musi przekazać jeden z typów ogólnych do metody z parametrami 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);
    }

}

Czy istnieje poprawny sposób przekazania parametru ogólnego do metody varargs bez napotkania tego ostrzeżenia?

Oczywiście coś takiego

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

nie działa, ponieważ nie można tworzyć tablic ogólnych.

matowe b
źródło
3
Dziwny. Wygląda na to, że kompilator powinien być w stanie zapewnić tutaj pełne bezpieczeństwo typu.
erickson
3
Powiązany wpis w FAQ generycznym języka Java Angeliki Langer: angelikalanger.com/GenericsFAQ/FAQSections/…
Flow

Odpowiedzi:

88

Poza dodaniem @SuppressWarnings("unchecked")nie sądzę.

Ten raport o błędzie zawiera więcej informacji, ale sprowadza się do tego, że kompilator nie lubi tablic typów ogólnych.

Kevin
źródło
3
Zapomniałem wspomnieć, że chciałem uniknąć @SuppressWarnings („niezaznaczone”). Ten raport o błędzie daje mi małą nadzieję!
mat b
3
Jak ujął to Joshua Bloch w Effective Java: „Nie mieszaj typów generycznych i tablic”.
Timmos,
20
następnie, pośrednio: nie używaj Varargs z generykami! Zgadza się ... decyzja o mapowaniu varargów na Array, a nie Collection, będzie nadal kłuć java na zawsze. Dobra robota, panie Gosling.
bernstein,
57

Tom Hawtin zwrócił na to uwagę w komentarzu, ale żeby być bardziej wyraźnym: tak, możesz rozwiązać ten problem w witrynie deklaracji (zamiast (potencjalnie wielu) witryn wywołania): przełącz na JDK7.

Jak widać w poście na blogu Josepha Darcy'ego , ćwiczenie Project Coin, mające na celu wybranie niewielkich, przyrostowych ulepszeń języka dla Java 7, przyjęło propozycję Boba Lee, aby zezwolić na coś podobnego @SuppressWarnings("varargs")po stronie metody, aby ostrzeżenie zniknęło w sytuacjach, w których było wiadomo bezpieczny.

Zostało to zaimplementowane w OpenJDK z tym zatwierdzeniem .

Może to być przydatne lub nie dla twojego projektu (wiele osób nie byłoby szczęśliwych, gdyby przełączyło się na przedpremierową niestabilną wersję JVM!), Ale być może tak jest - a może ktoś, kto znajdzie to pytanie później (po wydaniu JDK7 ) uzna to za przydatne.

Cowan
źródło
7
Wspomniana funkcja Project Coin jest już dostępna - zobacz @SafeVarargs w Javie 7.
George Hawkins
Alternatywa E w propozycji Boba jest kusząca.
Christopher Perry,
Wydaje się, że Java 8 używa @SafeVarargs zamiast @SuppressWarnings („varargs”)
Paul Wintz
17

Jeśli szukasz interfejsu płynnego, możesz wypróbować wzorzec konstruktora. Nie tak zwięzłe jak varargs, ale jest bezpieczne dla typów.

Statyczna metoda oparta na typie ogólnym może wyeliminować część schematu podczas korzystania z konstruktora, zachowując jednocześnie bezpieczeństwo typu.

Budowniczy

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

Użyj tego

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
źródło
1
Siła kompozycji. Podoba mi się to znacznie bardziej niż varargi, jest bardziej wyraziste.
Christopher Perry,
1
@ChristopherPerry, musisz również wziąć pod uwagę swój kod. Podstawowy Collection(w tym przypadku a ArrayList) jest wymuszany na dzwoniącym, podczas gdy mogą wiedzieć, że a LinkedListjest bardziej odpowiedni, lub sama niezmienna tablica (taka jak varargs z pytania OP). W przypadku zastosowania nie wyspecjalizowanego może to być właściwe, ale wystarczy wskazać, że jest to również ograniczenie, w pewnym sensie, w zależności od kodu otaczającego to i twoich potrzeb.
Searchengine27,
5

Jawne rzutowanie parametrów na Object w wywołaniu metody vararg sprawi, że kompilator będzie zadowolony bez uciekania się do @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" ) )

Uważam, że problem polega na tym, że kompilator musi dowiedzieć się, jaki konkretny typ tablicy ma utworzyć. Jeśli metoda nie jest ogólna, kompilator może użyć informacji o typie z metody. Jeśli metoda jest ogólna, próbuje ustalić typ tablicy na podstawie parametrów użytych podczas wywołania. Jeśli typy parametrów są jednorodne, zadanie to jest łatwe. Jeśli się różnią, kompilator stara się moim zdaniem być zbyt sprytny i tworzy ogólną tablicę typu unii. Wtedy czuje potrzebę ostrzeżenia cię przed tym. Prostszym rozwiązaniem byłoby utworzenie Object [], gdy nie można lepiej zawęzić typu. Powyższe rozwiązanie właśnie to wymusza.

Aby lepiej to zrozumieć, baw się z wywołaniami powyższej metody list w porównaniu z następującą metodą list2.

public static List<Object> list2( final Object... items )
{
    return Arrays.asList( items );
}
Konstantin Komissarchik
źródło
Działa to również na przykład: Iterator <?> It = Arrays.asList ((Object) t) .iterator; if (if, hasNext ()) {class = it.next (). getClass (); } na przykład, aby pobrać klasę obiektu z tablicy nieznanego typu.
ggb667
2

Możesz dodać @SafeVarargs do metody od wersji Java 7 i nie musisz dodawać adnotacji do kodu klienta.

class Assembler<X, Y> {

    @SafeVarargs
    final void assemble(X container, Y... args) {
        //has to be final...
    }
}
Daniel Hári
źródło
1

Możesz przeładować metody. To nie rozwiązuje problemu, ale minimalizuje liczbę ostrzeżeń (i tak, to 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.
źródło
23
Ew. To jest dokładnie taki rodzaj włamania, któremu mają zapobiegać warargi.
Amanda S,
1
To może być ważne podejście, na przykład przyjrzeć guawy za ImmutableSet.of .
Jonathan,
1

Jest to bardzo łatwy problem do rozwiązania: użyj List<T>!

Należy unikać tablic typu referencyjnego.

W aktualnej wersji Javy (1.7) można oznaczyć metodę, @SafeVargsktóra usunie ostrzeżenie z wywołującego. Ostrożnie z tym jednak, a nadal lepiej bez starszych tablic.

Zobacz także Ulepszone ostrzeżenia i błędy kompilatora podczas używania nierefikowalnych parametrów formalnych z uwagą techniczną Varargs Methods .

Tom Hawtin - haczyk
źródło
6
jest to nieuniknione w przypadku parametru varargs, prawda?
mat b
4
Istnieje propozycja dla JDK7, aby umożliwić pomijanie ostrzeżeń w deklaracji metody varargs zamiast jej użycia.
Tom Hawtin - tackline
11
To całkowicie ignoruje ważny aspekt pytania autora - parametry varargs DO tworzą tablicę, co generuje to ostrzeżenie.
Daniel Yankowsky
2
Zgadzam się z @Tom Hawtin - tackline. Aby uzyskać szczegółowe informacje, zobacz Bloch << Efektywna Java >> Punkt 25: Preferuj listy od tablic.
Stan Kurilin
2
Generalnie zgadzam się z Blochem w tej sprawie, ale varargs jest wyraźnym wyjątkiem od reguły ...
Joeri Hendrickx
0

Podczas pracy z tablicami typu ogólnego jestem zmuszony przekazać odwołanie do typu ogólnego. Dzięki temu mogę faktycznie wykonać kod ogólny, używając java.lang.reflect.Array.

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

KLE
źródło
Nie pracuję jednak z tablicami typu ogólnego, nie bezpośrednio, tylko z varargami typu ogólnego.
mat b