Можно ли решить, что предупреждение компилятора «Общий массив T создан для параметра varargs»?

153

Это упрощенная версия рассматриваемого кода, один универсальный класс использует другой класс с параметрами универсального типа и должен передать один из универсальных типов в метод с параметрами 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);
    }

}

Есть ли правильный способ передачи универсального параметра в метод varargs без появления этого предупреждения?

Конечно как то

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

не работает, так как вы не можете создавать универсальные массивы.

Мэтт Б
источник
3
Странный. Похоже, что компилятор должен быть в состоянии обеспечить полную безопасность типов здесь.
Эриксон
3
Связанная запись в FAQ Дженерики Лангер по Java Generics: angelikalanger.com/GenericsFAQ/FAQSections/…
Flow

Ответы:

88

Кроме добавления @SuppressWarnings("unchecked"), я так не думаю.

Этот отчет об ошибке содержит больше информации, но сводится к тому, что компилятору не нравятся массивы универсальных типов.

Kevin
источник
3
Забыл упомянуть, что хотел избежать @SuppressWarnings («не проверено»). Это сообщение об ошибке дает мне мало надежды!
Мэтт б
3
Как сказал Джошуа Блох в «Эффективной Java»: «Не смешивайте дженерики и массивы».
Тиммос
20
затем, косвенно: не используйте Varargs с Generics! Правильно ... решение отобразить varargs в Array, а не в Collection, будет продолжать жалить Java всегда. Молодец, мистер Гослинг.
Бернштейн
57

Том Хотин указал на это в комментарии, но чтобы быть более точным: да, вы можете решить это на сайте объявлений (а не на (потенциально многих) сайтах вызовов): переключитесь на JDK7.

Как вы можете видеть в блоге Джозефа Дарси, упражнение «Project Coin» для выбора небольших дополнительных улучшений языка для Java 7 приняло предложение Боба Ли разрешить что-то вроде@SuppressWarnings("varargs") метода сбрасывало это предупреждение в ситуациях, когда оно было известно сейф.

Это было реализовано в OpenJDK с этим коммитом .

Это может или не может быть полезным для вашего проекта (многие люди не будут рады перейти на нестабильную версию JVM, выпущенную до выпуска!), Но, возможно, это так, или, возможно, кто-то, кто найдет этот вопрос позже (после выхода JDK7) ) найдет это полезным.

Коуэн
источник
7
Упомянутая функция Project Coin теперь доступна - см. @SafeVarargs в Java 7.
Джордж Хокинс
Альтернатива E в предложении Боба заманчива.
Кристофер Перри
Java 8, кажется, использует @SafeVarargs вместо @SuppressWarnings («varargs»)
Пол Винц
17

Если вам нужен свободный интерфейс, вы можете попробовать шаблон сборки. Не такой краткий, как varargs, но он безопасен от типов.

Статический метод общего типа может исключить некоторые из шаблонов при использовании компоновщика, сохраняя при этом безопасность типов.

Строитель

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

Используй это

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
источник
1
Сила композиции. Мне это нравится гораздо больше, чем varargs, это более выразительно.
Кристофер Перри
1
@ChristopherPerry хорошо, вы также должны учитывать вашу кодовую базу. Базовый Collection(в данном случае an ArrayList) навязывается вызывающей стороне, тогда как они могут знать, что a LinkedListболее подходит, или сам неизменяемый массив (например, varargs из OP-вопроса). В случае использования без специального назначения это может быть уместно, но просто указав, что это также является ограничением, в зависимости от кода, окружающего это, и ваших потребностей.
searchengine27
5

Явное приведение параметров к Object в вызове метода vararg сделает компилятор счастливым, не прибегая к @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" ) )

Я считаю, что проблема заключается в том, что компилятор должен выяснить, какой конкретный тип массива создать. Если метод не является универсальным, компилятор может использовать информацию о типе из метода. Если метод является общим, он пытается определить тип массива на основе параметров, используемых при вызове. Если типы параметров являются однородными, эта задача проста. Если они меняются, компилятор пытается быть слишком умным, на мой взгляд, и создает универсальный массив типа union. Тогда он вынужден предупредить вас об этом. Более простым решением было бы создать Object [], когда тип не может быть лучше сужен. Приведенное выше решение заставляет именно это.

Чтобы лучше это понять, поэкспериментируйте с вызовами вышеуказанного метода list по сравнению со следующим методом list2.

public static List<Object> list2( final Object... items )
{
    return Arrays.asList( items );
}
Константин Комиссарчик
источник
Это работает, например, так: Iterator <?> It = Arrays.asList ((Object) t) .iterator; if (if, hasNext ()) {class = it.next (). getClass (); } например, чтобы получить класс объекта из массива неизвестного типа.
ggb667
2

Вы можете добавить @SafeVarargs в метод начиная с Java 7, и вам не нужно комментировать клиентский код.

class Assembler<X, Y> {

    @SafeVarargs
    final void assemble(X container, Y... args) {
        //has to be final...
    }
}
Даниэль Хари
источник
1

Вы можете перегружать методы. Это не решит вашу проблему, но минимизирует количество предупреждений (и да, это взлом!)

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.
источник
23
Еа. Это как раз тот тип взлома, который должны предотвращать varargs.
Аманда С
1
Это может быть правильным подходом, например, посмотрите на гуавы в ImmutableSet.of .
Джонатан
1

Это очень простая проблема: используйте List<T>!

Массивы ссылочного типа следует избегать.

В текущей версии Java (1.7) вы можете пометить метод, с помощью @SafeVargsкоторого будет удаляться предупреждение от вызывающей стороны. Осторожней с этим, хотя, и вы все еще лучше без устаревших массивов.

См. Также Улучшенные предупреждения и ошибки компилятора при использовании формальных параметров без переопределения с техническими примечаниями Varargs .

Том Хотин - Tackline
источник
6
это неизбежно с параметром varargs, не так ли?
Мэтт б
4
Существует предложение для JDK7 разрешить подавление предупреждений в объявлении метода varargs, а не в его использовании.
Том Хотин -
11
Это полностью игнорирует важный аспект вопроса автора - параметры varargs действительно создают массив, и это генерирует это предупреждение.
Даниэль Янковский
2
Я согласен с @Tom Hawtin - tackline. Подробнее см. Bloch «Эффективная Java». Пункт 25. Предпочитать списки массивам.
Стан Курилин
2
Я в целом согласен с Блохом в этом вопросе, но varargs - явное исключение из правила ...
Joeri Hendrickx
0

При работе с массивами универсального типа я вынужден передавать ссылку на универсальный тип. С этим я могу сделать общий код, используя java.lang.reflect.Array.

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

KLE
источник
Я не работаю с массивами универсального типа, хотя, не напрямую, просто с переменными универсального типа.
Мэтт б