Обходной путь - невозможность использования неперечисляемых параметров с varargs - PullRequest
2 голосов
/ 01 января 2012

Есть много вопросов о проблеме объединения дженериков с varargs.Это потребует универсальных массивов, которые не существуют, когда реальный код пытается их создать.Более того, имеется большое количество документации по неопределенности предупреждений от методов varargs с параметрами non-reifiable.Из-за стирания типа это создает потенциальное загрязнение кучи, отсюда и предупреждение (в Java 6 у вызывающей стороны).Однако мой вопрос не об этих проблемах.Я думаю, что понимаю, что некоторые вещи невозможны. То, что я хотел бы знать, - это способ элегантного решения этих проблем в моем сложном случае.

Ссылки по связанным темам:

Мой случай

У меня есть BookItemSearchAddTask, который простирается от Android AsyncTask, но где-то в его иерархии наследования сделан общий, более абстрактный на более высоких уровнях:

На более высоком уровне это SearchAddTask, который содержит метод start() для выполнения задачи, вызываемый клиентом, который знает, что он передает BookItem Продукт в.

public abstract class SearchAddTask<ProductToAdd extends Product & NamedProduct> 
        extends AddTask<ProductToAdd, ProductToAdd> {

    public void start(ViewActivity context, ProductToAdd product) throws SpecificAddTaskDomainException, TaskExistsException, TaskUnavailableException {
        super.start(context, product);
            //more stuff ...
            execute(product);
    }
}

Уровень ниже это ItemSearchAddTask.Здесь реализован метод doInBackground, как того требует API AsyncTask.Он по-прежнему может использовать дженерики.

public abstract class ItemSearchAddTask extends SearchAddTask<I> {

    public I doInBackground(I... params) {
            I product = params[0];
            //do stuff ...
            return product;
    }
}

Наконец BookItemSearchAddTask равен ItemSearchAddTask<BookItem>.A BookItem, следовательно, является Item, является Product. "I" связан с классом, в котором находится этот вложенный класс задач ItemSearchAddTask:

public abstract class ItemSearchAddWindow<I extends Item & ImageRepresentedProduct & NamedProduct> extends ViewActivity implements View.OnClickListener,
        AdapterView.OnItemClickListener {}

Проблема

Теперь,когда я запускаю этот код, я получаю следующее ошибка :

Caused by: java.lang.ClassCastException: [Lnet.lp.collectionista.domain.Product;
    at net.lp.collectionista.ui.activities.items.book.ItemSearchAddWindow$ItemSearchAddTask.doInBackground(ItemSearchAddWindow.java:1)

Обратите внимание на "[L".

Я также получаю время компиляции предупреждения at "execute(product);": "Type safety: A generic array of ProductToAdd is created for a varargs parameter"

Причина

Насколько я понимаю, JVM обнаруживает, что в doInBackground vararg он получает Product[] передано, а не Item[] (I[]), ожидаемое .Помимо общих массивов, о которых трудно думать, я думаю, что происходит с клеткой льва в http://docs.oracle.com/javase/tutorial/java/generics/subtyping.html. Не по моему коду, а потому что сгенерировал универсальный массив ProductToAdd (который в основном расширяет Product) был создан для параметра varargs.

Я проверил, что если я передаю без аргумента в execute, то это работает .Также использование "execute((ProductToAdd[])new MusicCDItem[]{new MusicCDItem()});" работает в основном (не спрашивайте, MusicCDItem - это Item, точно так же, как BookItem -).

Решение?

Однаков start() я не могу знать, что мне нужно передать в BookItem. Я знаю только о "I".Так как это универсальный , я не могу создать универсальный массив, который требуется передать как параметр varargs.Я думаю, что сложным в моем случае являются разные уровни использования обобщений, а также параллельные иерархии.

Как мне обойти этот пробел?Я рассмотрел:

  • Удаление дженериков .Немного радикально.
  • Держать параметр varargs повсюду во всех универсальных битах кода (т. Е. Изменять сигнатуру метода start()), пока мы не достигнем кода клиента, и толькоя передаю только один элемент product, и этот элемент имеет реальный тип BookItem.Там я получу то же предупреждение, но компилятор сможет сгенерировать правильный общий массив.
  • Дублирование кода AsyncTask и изменение doInBackground, чтобы не использовать параметр varargs, потому что в настоящее время он мне может не понадобиться.Я предпочитаю этого не делать, поэтому я получаю преимущества при обновлении AsyncTask в будущем.
  • Возможно, какой-то отражение код в start().Ужасно.

Есть ли что-нибудь более короткое и локальное?

Либо это, либо в моем коде действительно глупая опечатка.

1 Ответ

1 голос
/ 24 апреля 2013

Вы заметили, что вы получаете предупреждение «Unchecked generic array» в SearchAddTask.start(), когда он вызывает execute(). Однако фактическое предупреждение слегка вводит в заблуждение. Для параметра varargs создается общий массив ProductToAdd, но на самом деле это означает, что ProductToAdd - это переменная типа, и во время выполнения я не могу создать массив те, так что я просто буду использовать мою лучшую догадку.

Если вы перейдете к execute() в отладчике, вы увидите, что массив, созданный для объявления P..., представляет собой Product[1] - именно то, что вы ожидаете от исключения приведения класса. получил. Во время выполнения это лучшее, что может сделать JVM, потому что это ближайший не стертый предок ProductToAdd.

К сожалению, в ItemSearchAddTask JVM также сделала все возможное, что преобразовало объявление I... в Item[] (ближайший не стертый предок I); таким образом, ClassCastException, когда execute() пытается вызвать doInBackground().

Наименее ужасный способ, который я могу придумать, чтобы обойти это, - обойти стирание типов Java, сохраняя конкретный класс ProductToAdd во время выполнения и создавая массив args (правильного типа) самостоятельно:

abstract class SearchAddTask<ProductToAdd extends Product & NamedProduct> 
  extends AddTask<ProductToAdd, ProductToAdd> {

    private final Class<ProductToAdd> productClass;

    SearchAddTask(Class<ProductToAdd> productClass) {
        this.productClass = productClass;
    }

    public void start(ViewActivity context, ProductToAdd product) {
        super.start(context, product);
        ProductToAdd[] argsArray = (ProductToAdd[]) Array.newInstance( productClass, 1 );
        argsArray[0] = product;
        execute( argsArray );
    }
}

Это означает, что вы должны убедиться, что BookItem.class пропущено, возможно, когда вы создаете AddWindow<BookItem>, но оно содержит в себе безобразие.

...