有人可以帮我理解为什么这段代码的行为与评论
中描述的一样number++;
它显然与泛型方法中的类型定义有关,它是必须以某种方式提供的信息......但为什么它是强制性的?在语法上的位置和方式,我应该从方法// 1) compiles
List<Integer> l = Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList<Integer>::addAll);
/*
* 2) does not compile
*
* Exception in thread "main" java.lang.Error: Unresolved compilation problems:
* Type mismatch: cannot convert from Object to <unknown>
* The type ArrayList does not define add(Object, Integer) that is applicable here
* The type ArrayList does not define addAll(Object, Object) that is applicable here
*/
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
// 3) compiles
Stream.of(1, 2, 3).collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll);
/*
* 4) does not compile
*
* Exception in thread "main" java.lang.Error: Unresolved compilation problems:
* Type mismatch: cannot convert from Object to <unknown>
* The type ArrayList does not define add(Object, Integer) that is applicable here
* The type ArrayList<Integer> does not define addAll(Object, Object) that is applicable here
*/
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList<Integer>::addAll);
和of()
的签名中找出它吗?
collect()
答案 0 :(得分:7)
虽然这不是分析http://download.oracle.com/otndocs/jcp/lambda-0_9_3-fr-eval-spec/index.html上的Lambda规范的答案,但我试图找出它所依赖的内容。
从Stream
类复制两个方法:
static class Stream2<T> {
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream2<T> of(T... values) {
return new Stream2<>();
}
public <R> R collect( Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner){return null;}
}
编译:
Stream2.of(1,2,3).collect(ArrayList::new, ArrayList::add, ArrayList::addAll );
喜欢OP(2)。
现在通过将第一个参数移动到第三个位置来更改collect
方法
public <R> R collect(BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner,
Supplier<R> supplier
){return null;}
这仍然有效(5):
Stream2.of(1,2,3).collect(ArrayList::add, ArrayList::addAll,ArrayList::new );
这也有效(6):
Stream2.of(1,2,3).collect(ArrayList::add, ArrayList::addAll,ArrayList<Integer>::new );
这些不起作用(7,8):
Stream2.of(1,2,3).collect(ArrayList<Integer>::add, ArrayList::addAll,ArrayList::new );
Stream2.of(1,2,3).collect(ArrayList<Integer>::add, ArrayList<Integer>::addAll,ArrayList::new );
但这再次起作用(9):
Stream2.of(1,2,3).collect(ArrayList<Integer>::add, ArrayList<Integer>::addAll,ArrayList<Integer>::new );
所以我想当供应商使用显式类型参数进行注释时,它似乎有效。当只有消费者时,却没有。但也许其他人知道为什么这会产生影响。
编辑:尝试使用TestList
,它甚至更奇怪:
public class StreamTest2 {
public static void main(String[] args) {
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll);
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll2);
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll3);
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll4);
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll5);
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll6);
}
}
class TestList<T> extends AbstractList<T> {
@Override
public T get(int index) {
return null;
}
@Override
public int size() {
return 0;
}
@Override
public boolean addAll(Collection<? extends T> c) {
return true;
}
public boolean addAll2(TestList<? extends T> c) {
return true;
}
public boolean addAll3(Collection<T> c) {
return true;
}
public boolean addAll4(List<? extends T> c) {
return true;
}
public boolean addAll5(AbstractList<? extends T> c) {
return true;
}
public boolean addAll6(Collection<? extends T> c) {
return true;
}
@Override
public boolean add(T e) {
return true;
}
}
addAll
不起作用,但addAll2
- 6
可行。即使addAll6
有效,与原始addAll
具有相同的签名。
答案 1 :(得分:1)
每当您为编译器错误而烦恼时,您应该包括您使用的编译器及其版本号。如果您使用的是标准javac
以外的编译器,则应尝试javac
并编译结果。
写作时
List<Integer> l = Stream.of(1, 2, 3)
.collect(ArrayList::new, ArrayList::add, ArrayList<Integer>::addAll);
编译器将使用目标类型List<Integer>
来推断类型R
(这里与目标类型完全匹配)。没有目标类型,如
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
编译器将推断供应商的类型R
并推断ArrayList<Object>
。由于ArrayList<Object>
能够保留Integer
个实例并提供必要的add
和addAll
方法,因此在使用标准javac
时,此构造可以毫无问题地进行编译。我尝试了jdk1.8.0_05
,jdk1.8.0_20
,jdk1.8.0_40
,jdk1.8.0_51
,jdk1.8.0_60
,jdk1.9.0b29
和jdk1.9.0b66
,以确保有没有涉及特定版本的错误。我想,您使用的是Eclipse,因为Java 8类型推断存在问题。
同样,使用
Stream.of(1, 2, 3).collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll);
有效,但现在您的提示会强制R
的推断类型为ArrayList<Integer>
。相比之下
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList<Integer>::addAll);
不起作用,因为编译器推断ArrayList<Object>
供应商的返回类型与方法ArrayList<Integer>::addAll
不兼容。但以下情况可行:
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList<Object>::addAll);
但是,使用标准javac
...
答案 2 :(得分:1)
当面对这种情况时,我觉得理解问题的最好方法是纯粹的推理和逻辑。类型推断是一种涵盖entire chapter of the JLS的野兽。让我们暂时忘记ECJ和javac
,仔细考虑4个示例,并确定 a 给定的编译器是否能够或应该能够根据JLS编译它。< / p>
因此,请考虑collect
的签名:
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
此签名的问题是:R
是什么以及编译器如何确定R
是什么?
我们可以争辩说,编译器能够使用我们作为R
的参数提供collect
的类型。例如,第一个是Supplier<R>
,所以如果,e。例如,我们将作为参数() -> new StringBuilder()
给出,编译器应该能够将R
推断为StringBuilder
。
让我们考虑以下情况:
List<Integer> l = Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
在此示例中,collect
的3个参数是3个方法引用,我们将结果分配给List<Integer>
。编译器可以采用的信息:我们说它使用的类型是Integer
。
好的,它应该编译吗?让我们分别考虑collect
的3个参数:
Supplier<ArrayList<Integer>>
,我们正在给ArrayList::new
。这个方法引用可以引用现有方法(在这种情况下是构造函数)吗?是的,它可以引用ArrayList
的空构造函数(作为lambda - () -> new ArrayList<Integer>()
)因为ArrayList<Integer>
可以绑定到List<Integer>
。到目前为止一切顺利。BiConsumer<ArrayList<Integer>, ? super Integer>
。请注意T = Integer
,因为Stream由整数文字组成,类型为int
。我们正在提供ArrayList::add
,可以引用add(e)
(作为lambda:(list, element) -> list.add(element)
)。BiConsumer<List<Integer>, List<Integer>>
,我们正在给ArrayList::addAll
。它还可以引用addAll(c)
:addAll
作为参数Collection<? extends Integer>
,List<Integer>
可以绑定到此类型。所以基本上,只有推理,这样的表达式应该编译。
现在,让我们考虑你的4个案例:
List<Integer> l = Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList<Integer>::addAll);
我们将表达式的结果指定为List<Integer>
,因此我们告诉编译器:R
此处为List<Integer>
。与上述情况的不同之处在于我们提供了方法参考ArrayList<Integer>::addAll
。让我们仔细看看这个。此方法引用尝试引用方法名称addAll
,该方法名称List<Integer>
(我们的R
)作为参数,应该应用于ArrayList<Integer>
(类型)我们在方法参考中明确地使用了。那么这与我们在上面的推理中得出的结论完全相同;它应该有效:R = List<Integer>
可以绑定到ArrayList<Integer>
。
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
与上述情况的不同之处在于我们没有分配表达式的结果。因此,编制者只能根据供应商推断出类型:ArrayList::new
,因此应将其推断为ArrayList<Object>
。
ArrayList::add
可以绑定到BiConsumer<ArrayList<Object>, ? super Integer>
,因为add
的{{1}}方法可以将ArrayList<Object>
作为参数。Integer
可以绑定到ArrayList::addAll
。所以编译器应该能够编译它。
BiConsumer<ArrayList<Object>, ArrayList<Object>>
与案例2的区别在于,我们明确告诉编译器供应商提供Stream.of(1, 2, 3).collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll);
个实例,而不仅仅是ArrayList<Integer>
。它有什么改变吗?它不应该,案例2中的推理仍然存在于此。所以它也应该编译。
ArrayList<Object>
与案例2的不同之处在于,这一次,我们正在给予Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList<Integer>::addAll);
。根据案例2,我们知道由于供应商(没有特定类型),编译器将ArrayList<Integer>::addAll
推断为R
。这应该会导致问题:ArrayList<Object>
尝试在ArrayList<Integer>::addAll
上引用方法addAll
,但我们看到,对于编译器,ArrayList<Integer>
被推断为this
} ArrayList<Object>
不 ArrayList<Object>
。所以这不应该编译。
我们可以做些什么来使其编译?
ArrayList<Integer>
类型。Integer
。<Integer>
。我使用Eclipse Mars 4.5.1和ArrayList::<Integer> addAll
1.8.0_60测试了这些示例。结果是javac
的行为与我们推断的结论完全相同:只有案例4没有编译。
总的来说,Eclipse有一个小bug。