Java Stream协变与逆变原理及Optional类型转换问题答疑
一、Java泛型的协变与逆变核心逻辑
Java泛型默认是不变类型:即便User实现了UserDetails,Optional<User>和Optional<UserDetails>之间也不存在继承关系,不能直接互相赋值。只有通过通配符? extends(协变)或? super(逆变),才能实现有限的类型兼容:
- 协变:用
? extends 父类型限定泛型上界,允许子类泛型对象赋值给父类型泛型引用,比如List<? extends UserDetails>可以接收List<User>,但只能读取不能写入(避免类型安全风险)。 - 逆变:用
? super 子类型限定泛型下界,允许父类型泛型对象赋值给子类型泛型引用,比如List<? super User>可以接收List<UserDetails>,但只能写入不能读取到具体子类型(只能读到Object)。
二、代码案例逐个解析
1. 直接返回Optional<User>无法运行
public Optional<UserDetails> getUserDetails(String username) { Optional<User> userOptional = userService.findByUsername(username); return userOptional; }
原因:Optional<T>是不变泛型,Optional<User>不是Optional<UserDetails>的子类。如果允许这种转换,后续若有人往这个Optional<UserDetails>中存入其他UserDetails实现类的对象,底层实际是Optional<User>,会触发类型转换异常,因此编译器直接禁止该操作。
2. 使用Optional.map()可以运行
public Optional<UserDetails> getUserDetails(String username) { Optional<User> userOptional = userService.findByUsername(username); return userOptional.map(user -> user); }
原因:看Optional.map()的方法签名:
<U> Optional<U> map(Function<? super T, ? extends U> mapper)
这里编译器会自动推断泛型参数U为UserDetails,user -> user这个lambda将User(T类型)转换为UserDetails(U类型),符合? extends U的限定(User是UserDetails的子类)。map方法最终返回Optional<UserDetails>,与方法返回类型完全匹配,因此编译通过。
3. 转Stream后findFirst()无法运行
public Optional<UserDetails> getUserDetails(String username) { Optional<User> userOptional = userService.findByUsername(username); return userOptional.stream().map(user -> user).findFirst(); }
原因:Optional.stream()返回Stream<User>,未指定泛型的map(user -> user)会让编译器推断Stream的泛型仍为User,因此findFirst()返回的还是Optional<User>,又回到了第一个案例的问题——无法直接转换为Optional<UserDetails>。
如果要让这段代码运行,需要显式指定map的泛型类型,让编译器把Stream<User>转换为Stream<UserDetails>:
// 显式指定泛型U为UserDetails return userOptional.stream().<UserDetails>map(user -> user).findFirst();
或者给lambda表达式指定目标类型:
return userOptional.stream().map((UserDetails) user -> user).findFirst();
这样findFirst()会返回Optional<UserDetails>,就能匹配方法返回类型了。
内容的提问来源于stack exchange,提问作者Santa Monica

