泛型Supplier与Lambda的类型检查问题
先看你写的这两个泛型方法:
private <T> void compareValues(Supplier<T> supplier, T value) { System.out.println(supplier.get() == value); } private <T> void setValue(Consumer<T> consumer, T value) { consumer.accept(value); }
你提到compareValues(this::getString, "Foo")是合法调用,这点很好理解——编译器一眼就能推断出T是String:getString()返回String,传入的value也是String,类型完全对齐,自然没问题。
那为啥调用这两个方法时,编译器的判定逻辑会有差异?核心在于两个方法的泛型参数T的约束方向和类型兼容性规则不一样:
1. compareValues的双向兼容逻辑
这个方法里,Supplier<T>是产出T的角色,T value是接受T或其子类的角色(因为向上转型是Java默认允许的)。编译器会找两个参数的「共同最小父类型」作为T:
比如你如果写compareValues(() -> Integer.valueOf(1), 1L),编译器会推断T为Number——Integer和Long都是Number的子类,Supplier<Number>能产出Integer(向上转型),Number value也能接受Long(同样向上转型),所以这个调用是合法的。
简单说,只要两个参数的类型存在共同的父类,编译器就能找到合适的T让调用通过。
2. setValue的严格消费约束
这个方法里,Consumer<T>是**消费T**的角色,它的accept方法要求传入的参数必须是T或其子类(因为子类可以向上转成T),但反过来不行:
比如你如果写setValue((Long l) -> {}, Integer.valueOf(1)),编译器会直接报错——Consumer<Long>只能接受Long类型的参数,而Integer和Long是平级的兄弟类,无法互相转型,所以这个调用不合法。
但如果是setValue((Object o) -> {}, "Foo")就没问题,因为Consumer<Object>可以接受任何Object的子类,String自然符合要求。
简单说,Consumer<T>的泛型类型必须能兼容value的类型(要么T是value的父类,要么两者类型完全一致),否则调用就会失败。
一句话总结差异
compareValues是「找共同父类就能兼容」,因为两个参数都是往T的方向向上兼容;setValue是「消费者必须能吃下传入的value」,如果消费者的泛型类型比value的类型更具体(比如消费者要Long,给Integer),就会直接报错。
内容的提问来源于stack exchange,提问作者Patrick Peer

