jdk8 lambda 表达式推断问题

2019-01-15 16:52:03 +08:00
 beijiaxu

目前用的是 jdk8 144 版本,写了如下一个方法

Map<String, Object> map = new HashMap<>();
map.put("a", new ArrayList<String>());

methodA((Collection) map.get("a")).stream()
    .forEach(p -> System.out.println(p.getClass()));
methodA((Collection<String>) map.get("a")).stream()
    .forEach(p -> System.out.println(p.getClass()));

如下为 methodA

List<String> l = new ArrayList<>();
l.add("a");
l.add("b");
return l;

两端输出都是 String 类型, 然而推断类型的时候,第一个调用的参数 p 为 Object,第二个为 String。。。

3797 次点击
所在节点    Java
23 条回复
openthinks
2019-01-15 17:22:00 +08:00
楼主期望输出是什么?
String s1 = "xx";
Object o1="xx";
s1.getClass()
o1.getClass()
mazai
2019-01-15 17:31:32 +08:00
没明白你的意思
anzu
2019-01-15 17:44:07 +08:00
没问题呀
rizon
2019-01-15 18:10:18 +08:00
你没贴 methodA 的返回值类型啊。方法返回值决定了后续操作的对象类型啊
bumz
2019-01-15 18:42:17 +08:00
你的 methodA 是这样的吗?

```
private static <E> Collection<E> methodA(Collection<E> a) {
List<String> l = new ArrayList<>();
l.add("a");
l.add("b");
return (Collection<E>) l;
}
```

这样就重现了你描述中的情景
然而这不是很正常吗
beijiaxu
2019-01-15 20:27:49 +08:00
@rizon methodA 返回的 List<String> 类型
beijiaxu
2019-01-15 20:29:05 +08:00
@openthinks 不是什么期望输出,只是我在 lambda 函数里变量推导的类型可能是 Object,可能是具体的我要的类型。。
beijiaxu
2019-01-15 20:35:22 +08:00
可能大家都没太懂,我再写下。。
首先有个如下方法 methodA
List<String> methodA(Collection<String> c) {return ...}

然后调用该方法,使用 map 来获得变量
Map<String, Object> map = new HashMap<>();
map.put("a", new ArrayList<String>());

第一种方式:强转类型不加泛型类型
methodA( (Collection) map.get("a") )
.stream().forEach(p -> 这里的参数 p 推导类型为 Object )
第二种方式:强转类型有泛型类型
methodA( (Collection<String>) map.get("a") )
.stream().forEach(p -> 这里的参数 p 推导类型为 String )

我不明白的是,为什么方法签名的泛型会影响到 lambda 函数推导方法返回值的类型,我已经在方法返回值里指定了泛型类型了呀。
beijiaxu
2019-01-15 20:38:50 +08:00
因为今天正好碰到了这个问题,我偷懒没个方法签名加集合的泛型,导致 lambda 里推导出的参数调用方法编译错误,然后我加了给集合加了个<String>, 就能正常工作了,感觉好奇怪。
chocotan
2019-01-15 20:49:03 +08:00
楼主所说的"推断类型"是指啥?
编译错误? IDE 的提示?
cyspy
2019-01-15 20:52:22 +08:00
Collection 不加参数默认是 Collection<Object> ,编译器不知道里面的类型
m2276699
2019-01-15 20:59:13 +08:00
符合预期
WangYanjie
2019-01-15 23:33:09 +08:00
interface Collection<?>{} 是 generic type.

Collection<String> 是 parameterized type, an instance of generic type `Collection`
Collection 是 raw type, java 5 之前的产物
Collection<?> 是 unbound wildcard parameterized type
type erasure 的 **一部分** 是指编译器会在编译期间,把 parameterized type 中的 type parameter 都消去,
Collection<String>, Collection, Collection<?> 应该都是对应的一种运行时的类

尝试了以下并没有你这样的效果。

import java.util.*;

public class Demo {
public static List<String> methodA(Collection<String> cs) {
List<String> stringList = new ArrayList<>();
for (String s : cs) {
stringList.add(s);
}
return stringList;
}

public static void main(String args[]) {
Map<String, Object> map = new HashMap<>();
List<String> stringList = new ArrayList<>();
stringList.add("s");
map.put("A", stringList);
methodA((Collection) map.get("A")).stream().forEach(p -> System.out.println(p.getClass()));
methodA((Collection<String>) map.get("A")).stream().forEach(p -> System.out.println(p.getClass()));
}
}


class java.lang.String
class java.lang.String

➜ ~ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
cassia
2019-01-15 23:46:09 +08:00
类型擦除,了解下
a123321456b
2019-01-16 08:36:22 +08:00
Collection 可以当做 Collection<Object> 里面的 p 自然也是 Object 因为 java 有类型擦除所以得不到原始的类型 可以运行时判断 p instanceof String == true
mazai
2019-01-16 08:49:48 +08:00
第一种你 get 到的是 object 类型,强转的时候也没有指明,所以是 object
beijiaxu
2019-01-16 09:21:58 +08:00
@WangYanjie 并不是期望输出 class type...这个 2 种调用输出都一样。
想问的是在 lambda 函数里参数 p 的编译时推导的类型,一个是 Object,一个是 String,所以参数 p 调用方法时若不指定 methodA 签名里的泛型,会需要用到强转,否则编译错误。给出的提示可用方法只有 Object 的方法,String 的方法一个都没有。
mezi04
2019-01-16 10:12:33 +08:00
建议楼主看看 collection 的源码。methodB 指定了 Collection 泛型的类型,编译器自然可以推断出 p 的类型了
WangYanjie
2019-01-16 21:55:09 +08:00
@beijiaxu 看错题了,尴尬

我理解的,明天去学习下能不能看到编译后的代码的
1 泛型在编译期间会有 type erasure 的过程,会导致 Collection<String> 等价于 Collection<Object>,
2 在 type erasure 的过程中,编译器会按需要自动加上
WangYanjie
2019-01-16 22:28:04 +08:00
@WangYanjie
类型转换
3 Collection 是 raw type, 不参与 type erasure

一种猜想是 type ensure 的过程中加了 type cast,
一种猜想是 type ensure 的过程中加了 bridge method.

我倾向与后者
当使用 Collection 时,lambda 编译时对应的是 A
interface Consumer<T> {
accept(T o)
}
class A implements Consumer {
accept(Object o) {}
}
编译后
interface Consume {
accept(Object o)
}
class A implements Consumer {
accept(Object o) {}
}

当使用 Collection<String> 时,lambda 编译时对应的是 A
class Consumer<T> {
accept(Object o)
}
class A implements Consumer<String> {
accept(String o) {}
}
编译后会增加 i 一个 bridge method
class Consumer {
accept(Object o)
}
class A implements Consumer<String> {
accept(String o) {}
accept(Object o) {this.accept((String) o)}
}

主要的信息来源是 Java Generic FAQs: http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ102

从头阅读风味更佳

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/527291

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX