解决无法将不可具体化参数与 varargs 一起使用的问题

发布于 2024-12-23 13:30:32 字数 4238 浏览 1 评论 0原文

关于泛型与可变参数相结合的问题存在很多疑问。这将需要通用数组,当实际代码尝试实例化它们时,这些数组并不存在。此外,还有大量关于来自具有不可具体化参数的 varargs 方法的警告的编译器模糊性的文档。由于类型擦除,这会造成潜在的堆污染,因此会出现警告(在调用方的 Java 6 中)。然而,我的问题并不是关于这些问题本身。我想我明白有些事情是不可能的。 我想知道的是在我的复杂案例中优雅地解决这些问题的方法。

相关主题的链接:

我的情况是,

我有一个从 Android 扩展的 BookItemSearchAddTask AsyncTask 但其继承层次结构中的某个地方已经变得通用,在更高级别上更加抽象:

在更高级别上它是 SearchAddTask, 方法从知道传入 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);
    }
}

其中包含用于执行任务的 start() 方法,该 这是一个ItemSearchAddTask。这里按照 AsyncTask API 的要求实现了 doInBackground 方法。它仍然可以使用泛型。

public abstract class ItemSearchAddTask extends SearchAddTask<I> {

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

最后BookItemSearchAddTaskItemSearchAddTask。因此,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);”处收到编译时警告:“类型安全:为 varargs 参数创建了 ProductToAdd 的通用数组

原因

据我了解,JVM 发现在 doInBackground vararg 中它获取了传入的 Product[],而不是 Item[ ] (I[]) 它期望。除了难以想象的泛型数组之外,我认为正在发生的事情是狮子笼的情况 http://docs.oracle.com/javase/tutorial/java/generics/subtyping.html。不是通过我的代码,而是因为为 varargs 参数创建了 生成的 ProductToAdd 通用数组(它基本上扩展了 Product)。

我检查过,如果我没有参数传递给execute,那么它就可以工作。还使用“execute((ProductToAdd[])new MusicCDItem[]{new MusicCDItem()});”大部分工作(不要问,MusicCDItem是一个< code>Item,就像 BookItem 一样)。

解决方案?

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

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

  • 删除仿制药。有点激烈。
  • 在所有泛型代码位中始终保留可变参数参数(即更改 start() 的方法签名),直到我们到达客户端代码,并且只有在那里才可以我仅传递一个产品元素,该元素属于真实类型 BookItem。我会在那里得到相同的警告,但编译器将能够生成正确的通用数组。
  • 复制 AsyncTask 代码并更改 doInBackground 以不使用 varargs 参数,因为我目前可能不需要它。我不想这样做,这样当将来更新 AsyncTask 时我就能受益。
  • 也许start()中有一些反射代码。丑陋的。

有没有更短、更本地化的内容?

要么是这个,要么是我的代码中有一些非常愚蠢的拼写错误。

There are many questions about the issue of combining generics with varargs. This would require generic arrays which don't exist when actual code tries to instantiate them. Moreover, there's a good amount of documentation on the compiler-vagueness of warnings from varargs methods with non-reifiable parameters. Because of type erasure this creates potential heap pollution, hence the warning (in Java 6 at the caller). However, my question is not about these problems themselves. I think I understand that some things aren't possible. What I'd like to know is the way to elegantly workaround these problems in my complex case.

Links for related topics:

My case

I have a BookItemSearchAddTask that extends from the Android AsyncTask but somewhere along its inheritance hierarchy has been made generic, more abstract at higher levels:

At a higher level it's SearchAddTask, which contains the method start() to execute the task, called from a client that knows that it passes a BookItem product in.

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

A level lower it's an ItemSearchAddTask. Here the method doInBackground is implemented, as required by the AsyncTask API. It can still use generics.

public abstract class ItemSearchAddTask extends SearchAddTask<I> {

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

Finally BookItemSearchAddTask is ItemSearchAddTask<BookItem>. A BookItem therefore is an Item, is a Product. The "I" is linked to the class in which this nested task class, ItemSearchAddTask, finds itself:

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

The problem

Now, when I run this code I get the following error:

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

Note the "[L".

I also get compile time warnings at "execute(product);": "Type safety: A generic array of ProductToAdd is created for a varargs parameter"

The cause

To my understanding, the JVM finds that in the doInBackground vararg it gets a Product[] passed in, rather than the Item[] (I[]) it expects. Apart from the generic arrays, which are hard to think about, I think what's going on is the case of the lion cage at http://docs.oracle.com/javase/tutorial/java/generics/subtyping.html. Not by my code, but because the generated generic array of ProductToAdd (which basically extends Product) was created for the varargs param.

I checked that if I pass no argument to execute, that it works. Also using "execute((ProductToAdd[])new MusicCDItem[]{new MusicCDItem()});" worked mostly (don't ask, a MusicCDItem is an Item, just like a BookItem is).

A solution?

However in start() I can't know that I need to pass in a BookItem. I only know about "I". Since that is a generic, I can't create the generic array that is required to pass as the varargs param. I think what is complex about my case is the different levels of using generics, as well as the parallel hierarchies.

How do I work around this feature gap? I considered:

  • Removing generics. A bit drastic.
  • Holding on the varargs param everywhere in all the generics bits of code (i.e. change the method signature of start()), until we reach the client code, and only there do I pass one product element only, and that element is of a real type, BookItem. I will get the same warning there but the compiler will be able to generate the correct generic array.
  • Duplicating the AsyncTask code and changing doInBackground to not use a varargs param because I currently may not need one. I prefer not to do this so I get the benefits when AsyncTask is updated in the future.
  • Perhaps some reflection code in start(). Ugly.

Is there anything shorter and more local?

Either this, or there is some really stupid typo in my code.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

小忆控 2024-12-30 13:30:32

您注意到,当 SearchAddTask.start() 调用 execute() 时,您会收到“Unchecked generic array”警告。然而,实际的警告有点误导。它所说的是为可变参数参数创建了 ProductToAdd 的通用数组,但它的真正含义是,ProductToAdd 是一个类型变量,在运行时我无法创建数组其中,我只能使用我的最佳猜测。

如果您在调试器中单步执行 execute(),您将看到为P... 声明是一个Product[1] ——正是您所期望的类转换异常。在运行时,这是 JVM 可以做的最好的事情,因为它是 ProductToAdd 最接近的未擦除祖先。

不幸的是,在 ItemSearchAddTask 中,JVM 也尽力了,即将 I... 声明转换为 Item[]I 最近的未擦除祖先);因此,当 execute() 尝试调用 doInBackground() 时,会出现 ClassCastException

我能想到的解决这个问题的最不糟糕的方法是通过在运行时保留 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 时,但它保留了丑陋的内容。

You noted that you get an "Unchecked generic array" warning in SearchAddTask.start() when it calls execute(). However, the actual warning is slightly misleading. What it says is A generic array of ProductToAdd is created for a varargs parameter, but what it really means is, ProductToAdd is a type variable and at run-time I can't create an array of those, so I'll just have to use my best guess.

If you step through into execute() in the debugger, you'll see that the array that was created for the P... declaration is a Product[1] -- exactly what you'd expect from the class cast exception you got. At run time, this is the best the JVM can do, because it's the closest un-erased ancestor of ProductToAdd.

Unfortunately, in ItemSearchAddTask, the JVM's also done the best it can, which is convert the I... declaration into an Item[] (the closest un-erased ancestor of I); thus the ClassCastException when execute() tries to call doInBackground().

The least awful way I can think of offhand to get around this is to sidestep Java's type erasure by keeping ProductToAdd's concrete class around at run time and creating the args array (of the correct type) yourself:

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

It does mean you have to ensure that BookItem.class gets passed in, probably when you create the AddWindow<BookItem>, but it keeps the ugliness contained.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文