为何Java函数式接口中要使用下界?以Optional.map为例
嘿,这个问题戳中了很多Java开发者的盲区——集合的PECS规则大家多少都能说两句,但到了函数式接口里,尤其是像Optional.map这种高频方法的泛型签名,确实容易让人犯懵。我来给你掰扯清楚:
? super T? 先看一下Optional.map的完整签名:
<U> Optional<U> map(Function<? super T, ? extends U> mapper)
这里的T是当前Optional持有的对象类型。为什么Function的输入类型要用? super T而不是直接T?核心还是PECS原则在函数式接口上的延伸——但这里要换个角度理解:
Function是一个“转换器”,它消费输入的T类型对象,然后生产出U类型对象。对于“消费”的部分,我们遵循「消费者用Super」:允许传入能处理T或T父类的Function,因为父类型的引用可以接收子类对象(里氏替换原则)。
这么设计的优势是什么?
最直接的好处是提升代码的复用性和灵活性:
- 假设你已经有一个通用的Function:
Function<Object, String> objToString = Object::toString;,它能把任何Object转成字符串。如果Optional.map的签名是Function<T, ? extends U>,那你只能给Optional<String>用这个Function,但用了? super T之后,Optional<Integer>、Optional<Double>都能直接复用这个Function,不用专门为每个类型写重复的逻辑。 - 再比如,如果你有一个处理
Number类型的Function:Function<Number, String> numToString = n -> "Number: " + n;,它可以直接用在Optional<Integer>、Optional<Long>上——因为Integer和Long都是Number的子类,符合? super Integer(Number是Integer的父类)的要求。
虽然你没贴代码,但我遇到过无数开发者栽在这个场景里:
// 示例:编译失败的代码 Optional<Number> numOpt = Optional.of(100L); // 这个Function只能处理Integer类型,是Number的子类 Function<Integer, String> intToString = i -> "Integer: " + i; // 这里会编译报错! numOpt.map(intToString);
为什么编译失败?
因为numOpt的map方法要求传入的Function能处理? super Number类型的输入——也就是说,这个Function得能接受任意Number的子类(比如Long、Float)。但intToString只能处理Integer,无法处理Long(numOpt里存的就是Long),编译器当然会拦着你,避免运行时出现类型错误。
反过来,如果你的Function是处理父类的,比如Function<Object, String>,就可以顺利传入numOpt.map,因为Object是Number的父类,能处理所有Number类型的对象。
其实还是PECS的逻辑,只是要对应函数式接口的“输入/输出”方向:
- 对于函数的输入参数(比如Function的第一个泛型):属于「消费」行为,用
? super T,允许接受能处理T父类的函数,扩大适用范围。 - 对于函数的返回值(比如Function的第二个泛型):属于「生产」行为,用
? extends U,允许返回U的子类,保证类型安全。
这样设计的本质,是在类型安全和灵活性之间找最优解——既不让你写出运行时会炸的代码,又能让你最大化复用已有的函数逻辑。
内容的提问来源于stack exchange,提问作者g6380647

