在 Consumers<X> 数组中的 ArrayStoreException

huangapple go评论73阅读模式
英文:

ArrayStoreException in array of Consumers<X>

问题

我正在存储对适应于Consumer&lt;Integer&gt;BiConsumers&lt;Integer, X&gt;的引用:

public void setConsumer(BiConsumer&lt;Integer, X&gt; consumer) {
    fieldConsumer = integer -&gt; consumer.accept(integer, fieldSubject);
}

但我需要两个引用,所以我将代码更改为使用一个数组:

private Consumer&lt;Integer&gt;[] fieldConsumers;

public MyClass(int numberOfConsumers) {
    Consumer&lt;Integer&gt; consumer = integer -&gt; {};
    fieldConsumers= (Consumer&lt;Integer&gt;[]) Array.newInstance(consumer.getClass(), numberOfObservers);
}

public void addConsumer(int consumerIndex, BiConsumer&lt;Integer, X&gt; consumer) {
    // 有问题的行
    fieldConsumers[consumerIndex] = responseType-&gt; consumer.accept(responseType, fieldSubject);
}

以便可以通过以下方式触发回调:

for (Consumer&lt;Integer&gt; consumer: fieldConsumers) {
    consumer.accept(responseType);
}

我得到了错误:

java.lang.ArrayStoreException:

在这一行:

fieldConsumers[consumerIndex] = responseType-&gt; consumer.accept(responseType, fieldSubject);

现在,如果您还在阅读这个,我还有一个问题:

如果我这样做,与使用旧的fieldConsumers.add(consumer)(其中fieldConsumersList&lt;BiConsumer&lt;Integer, X&gt;&gt;)相比,我是否仍然保留对外部Consumers的引用?

英文:

I’m storing references to BiConsumers&lt;Integer, X&gt; adapted to Consumer&lt;Integer&gt;:

public void setConsumer(BiConsumer&lt;Integer, X&gt; consumer) {
    fieldConsumer = integer -&gt; consumer.accept(integer, fieldSubject);
}

But I need 2 of them, so I changed the code to use an array:

private Consumer&lt;Integer&gt;[] fieldConsumers;

public MyClass(int numberOfConsumers) {
    Consumer&lt;Integer&gt; consumer = integer -&gt; {};
    fieldConsumers= (Consumer&lt;Integer&gt;[]) Array.newInstance(consumer.getClass(), numberOfObservers);
}

public void addConsumer(int consumerIndex, BiConsumer&lt;Integer, X&gt; consumer) {
    // Offending line
    fieldConsumers[consumerIndex] = responseType-&gt; consumer.accept(responseType, fieldSubject);

}

So that the callback can be triggered with a:

for (Consumer&lt;Integer&gt; consumer: fieldConsumers) {
    consumer.accept(responseType);
}

I got the error:

java.lang.ArrayStoreException:

on this line:

fieldConsumers[consumerIndex] = responseType-&gt; consumer.accept(responseType, fieldSubject);

Now, If you are still reading this, I have one more question:

Am I still holding reference to outside Consumers if I do it this way, as opposed to using the old fieldConsumers.add(consumer) where fieldConsumers is a List&lt;BiConsumer&lt;Integer, X&gt;&gt; ?

答案1

得分: 2

你使用了 Array.newInstance(consumer.getClass(), numberOfObservers) 来创建 Consumer<Integer>[] 数组。但是 consumer.getClass() 返回的是你调用该方法的对象的实际类,这总是接口的 实现类。这种元素类型的数组只能容纳相同具体类的对象,而不能容纳接口的任意实现。

这与例如以下情况没有区别:

CharSequence cs = "hello";
CharSequence[] array = (CharSequence[]) Array.newInstance(cs.getClass(), 1);
array[0] = new StringBuilder();

在这里,cs 的类型是 CharSequence,反射创建数组似乎创建了一个类型为 CharSequence[] 的数组,因此存储 StringBuilder 应该是可能的。但由于 cs.getClass() 返回的是实际的实现类 String,因此数组实际上是类型为 String[],因此尝试存储 StringBuilder 会产生 ArrayStoreException

在使用 lambda 表达式的情况下,情况会稍微复杂一些,因为函数接口的实际实现类是在运行时提供的,故意未指定的。你在构造函数中使用了 lambda 表达式 integer -> {} 来创建数组,这会在 addConsumer 方法内部的 responseType-> consumer.accept(responseType, fieldSubject) 中计算出不同的实现类,在这个特定的运行时

这种行为与这个答案中描述的最常用环境的行为一致。尽管如此,其他实现可能会展现不同的行为,例如对于特定函数接口,所有 lambda 表达式可能会计算为相同的实现类。但也有可能同一 lambda 表达式的多次计算会产生不同的类。

因此,修复方法是使用预期的接口元素类型,例如:

fieldConsumers = (Consumer<Integer>[]) Array.newInstance(Consumer.class, numberOfObservers);

但实际上完全不需要使用反射来创建数组。你可以使用:

fieldConsumers = new Consumer[numberOfObservers];

你不能写成 new Consumer<Integer>[numberOfObservers],因为不允许创建泛型数组。这就是为什么上面的代码使用原始类型。使用反射来代替也不会改善情况,因为无论哪种情况下都是一个未经检查的操作。你可能需要为此添加 @SuppressWarnings。更清晰的替代方案是使用 List<Consumer<Integer>>,因为它会将你从数组和泛型的奇特之处中解脱出来。

关于“引用外部 Consumers”的含义不太清楚。无论哪种情况,你都引用了作为参数传递给 addConsumerBiConsumer 实现所捕获的 Consumer 实现的引用。

英文:

You used Array.newInstance(consumer.getClass(), numberOfObservers) to create the Consumer&lt;Integer&gt;[] array. But consumer.getClass() returns the actual class of the object you’re invoking the method on, which is always an implementation class of the interface. An array of this element type can only hold objects of the same concrete class, not arbitrary implementations of the interface.

This is not different to, e.g.

CharSequence cs = &quot;hello&quot;;
CharSequence[] array = (CharSequence[]) Array.newInstance(cs.getClass(), 1);
array[0] = new StringBuilder();

Here, cs has the type CharSequence and the reflective array creation appears to create an array of type CharSequence[], so storing a StringBuilder should be possible. But since cs.getClass() returns the actual implementation class String, the array is actually of type String[], hence, the attempt to store a StringBuilder produces an ArrayStoreException.

In case of lambda expressions, things get slightly more complicated, as the actual implementation classes of the functional interface are provided at runtime and intentionally unspecified. You used the lambda expression integer -&gt; {} for the array creation in the constructor, which evaluated to a different implementation class than the responseType-&gt; consumer.accept(responseType, fieldSubject) within the addConsumer method, in this particular runtime.

This behavior is in line with this answer describing the behavior of the most commonly used environment. Still, other implementations could exhibit different behavior, e.g. evaluate to the same implementation class for a particular functional interface for all lambda expressions. But it’s also possible that multiple evaluations of the same lambda expression produce different classes.

So the fix is to use the intended interface element type, e.g.

fieldConsumers=(Consumer&lt;Integer&gt;[])Array.newInstance(Consumer.class, numberOfObservers);

But there is no need for a reflective array creation at all. You can use:

fieldConsumers = new Consumer[numberOfObservers];

You can not write new Consumer&lt;Integer&gt;[numberOfObservers], as generic array creation is not allowed. That’s why the code above uses a raw type. Using Reflection instead wouldn’t improve the situation, as it is an unchecked operation in either case. You might have to add @SuppressWarnings for it. The cleaner alternative is to use a List&lt;Consumer&lt;Integer&gt;&gt;, as it shields you from the oddities of arrays and generics.

It’s not clear what you mean with “reference to outside Consumers” here. In either case, you have references to Consumer implementations capturing references to BiConsumer implementations you received as arguments to addConsumer.

huangapple
  • 本文由 发表于 2020年10月11日 00:30:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/64295745.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定