为何Java Stream接口方法参数多使用下限通配符而非泛型类型?
Predicate<? super T>而非Predicate<T>? 这问题问到点子上了!很多刚摸Java泛型的同学都会疑惑——既然Predicate<T>也能处理Stream<T>的元素,为啥要多此一举用Predicate<? super T>?其实这个设计的核心优势全在代码复用和泛型灵活性上,咱们拿几个真实场景唠唠:
场景1:复用现成的父类型Predicate
假设你已经有一个通用的谓词,比如判断对象非空的Predicate<Object>,或者判断数字为正的Predicate<Number>,现在要处理Stream<Integer>,直接把这个现成的谓词传进去就行,完全不用重新写一个Predicate<Integer>版本:
// 已经定义好的通用非空判断谓词 Predicate<Object> nonNullChecker = Objects::nonNull; // 直接用在Integer流上 Stream<Integer> intStream = Stream.of(1, null, 3, null); intStream.filter(nonNullChecker).forEach(System.out::println); // 输出1、3
如果filter只接受Predicate<Integer>,你要么得冒险强转,要么得重复写一个Predicate<Integer> nonNullIntChecker = Objects::nonNull;——这完全是冗余代码,毕竟非空逻辑对所有Object子类都通用,复用它才是高效的做法。
场景2:配合泛型方法实现跨类型兼容
假设你写了一个泛型方法,要处理任意Number子类的流,需要一个判断数字是否大于0的逻辑。用Predicate<? super T>的话,你只需要一个Predicate<Number>就能搞定所有子类流:
// 通用的正数判断谓词 Predicate<Number> positiveChecker = num -> num.doubleValue() > 0; // 处理Integer流 Stream<Integer> ints = Stream.of(-1, 2, -3, 4); ints.filter(positiveChecker).forEach(System.out::println); // 输出2、4 // 处理Double流,不用改谓词 Stream<Double> doubles = Stream.of(-0.5, 1.2, -3.0, 5.7); doubles.filter(positiveChecker).forEach(System.out::println); // 输出1.2、5.7
要是filter只能接受Predicate<T>,你就得给Integer、Double甚至Long各写一个对应的正数判断谓词,代码重复率直接拉满。
从泛型原理看:消费型接口的逆变特性
从泛型的角度说,Predicate是消费型接口——它的test方法接收一个参数(消费这个参数),对于这类接口,使用<? super T>是符合逆变规则的:父类型的谓词一定能处理子类型的对象。比如Predicate<Object>能处理任何Object实例(包括Integer),Predicate<Number>能处理任何Number子类(包括Integer、Double),这完全符合里氏替换原则。
反过来,如果用Predicate<T>,就把接口的适用范围限制死了,只能接受精确匹配类型的谓词,大大降低了API的灵活性。
内容的提问来源于stack exchange,提问作者Arjun

