解决不能使用不可恢复的参数与varargs

时间:2012-01-01 13:10:06

标签: java android generics variadic-functions

关于将泛型与varargs相结合的问题有很多问题。这将需要在实际代码尝试实例化它们时不存在的通用数组。此外,还有大量关于编译器的文档 - varargs方法的警告模糊性与不可恢复的参数。由于类型擦除,这会产生潜在的堆污染,因此会出现警告(在调用者的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> BookItem 因此是Item,是ProductI链接到此嵌套任务类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”。

我还在“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()});”主要是工作(不要问,MusicCDItemItem,就像BookItem一样。

解决方案?

然而在start()我无法知道我需要传递BookItem我只知道“I”。由于这是一个通用的,我无法创建作为varargs参数传递所需的通用数组。我认为我的案例的复杂程度是使用泛型的不同层次,以及并行层次结构。

如何解决此功能差距?我考虑过:

  • 删除泛型。有点激烈。
  • 在所有泛型代码中保持varargs param无处不在(即更改start()的方法签名),直到我们到达客户端代码,并且只有我通过仅一个产品元素,该元素属于实际类型BookItem。我会在那里得到相同的警告,但编译器将能够生成正确的通用数组。
  • 复制 AsyncTask代码并将doInBackground更改为不使用varargs参数,因为我目前可能不需要。我不想这样做,所以我在将来更新AsyncTask时会获得好处。
  • 也许start()中有一些反射代码。难看。

有什么更短,更本地的吗?

这个,或者我的代码中有一些非常愚蠢的错误。

1 个答案:

答案 0 :(得分:1)

您注意到SearchAddTask.start()调用execute()时会收到“未经检查的通用数组”警告。但是,实际警告有点误导。它说的是为varargs参数创建了ProductToAdd的通用数组,但它的真正含义是, ProductToAdd是一个类型变量,在运行时我无法创建数组那些,所以我只能用我最好的猜测。

如果您在调试器中逐步进入execute(),您会看到为P...声明创建的数组是Product[1] - 正是您所期望的从你得到的类演员例外。在运行时,这是JVM可以做的最好的,因为它是ProductToAdd最接近的未擦除的祖先。

不幸的是,在ItemSearchAddTask中,JVM也做得最好,它将I...声明转换为Item[]I最近的未擦除祖先});因此ClassCastException execute()试图调用doInBackground()

我能想到的最不可思议的方法就是通过在运行时保持ProductToAdd的具体类并自己创建args数组(正确类型)来回避Java的类型擦除:

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>时,但它会保持丑陋。