Можно ли решить "Генерируемый массив T создан для параметра varargs", предупреждающий о компиляторе?

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

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

Ответ 2

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

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

Это было реализовано в OpenJDK с this commit.

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

Ответ 3

Если вы используете свободный интерфейс, вы можете попробовать шаблон построителя. Не так лаконично, как 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);
        }
    }
}

Ответ 4

Явное назначение параметров 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 [], когда тип не может быть лучше сужен. Вышеупомянутое решение заставляет именно это.

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

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

Ответ 5

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

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) { ... }
}

Ответ 6

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

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

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

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

Ответ 8

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

class Assembler<X, Y> {

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