避免返回类型用泛型通配符:适配Iterable<SomeType<?>>参数的方案
Sonar提示「泛型通配符类型不应用于返回类型」,现在需要调用一个接受Iterable<SomeType<?>>作为参数的API,要求在独立方法中创建该参数(而非内联List<SomeType<?>> param = new ArrayList();),如何操作才能不违反Sonar规则?同时有以下测试代码及相关疑问:
interface X<T> {} static class Blarp { void addSomeBlarps(Iterable<X<?>> blub) {} } @Test void testGenerics() { Blarp b = new Blarp(); X<?> c = new X<>() {}; X d = new X() {}; List<X> blarpList = new ArrayList(); blarpList.add(c); blarpList.add(d); List<X<?>> blubList = new ArrayList<>(); blubList.add(c); blubList.add(d); // blubList.addAll(blarpList); 无法运行——为何单个X可添加但批量不行? // blubList.addAll((Collection<? extends X<?>>) blarpList); IDE无报错但编译失败 // b.addSomeBlarps(blarpList); 无法运行——原因? b.addSomeBlarps(blubList); }
符合Sonar规则的参数创建方式
Sonar的规则核心是禁止在公共方法的返回类型中使用泛型通配符,避免调用者处理复杂的通配符逻辑。针对你的场景,有两种合规的实现方式:
使用私有泛型方法创建参数
编写私有泛型方法,返回不带通配符的泛型集合,再安全转型为API需要的Iterable<X<?>>。因为私有方法的调用范围可控,不会对外暴露通配符类型,同时规避Sonar的规则检查:private <T> List<X<T>> createBlarpItems() { List<X<T>> items = new ArrayList<>(); items.add(new X<T>() {}); items.add(new X<T>() {}); // 可以添加任意X的实例,包括原始类型的X对象 items.add((X<T>) new X() {}); return items; }调用时:
// 安全转型,因为任何X<T>都是X<?>的实例 Iterable<X<?>> blarpItems = (Iterable<X<?>>) createBlarpItems(); b.addSomeBlarps(blarpItems);返回非通配符的具体泛型集合,再包装为目标类型
如果不想用转型,可以创建具体类型的X实现类,返回该类型的集合,再通过ArrayList的构造器包装为List<X<?>>(此操作仅在方法内部完成,方法返回类型仍为具体泛型集合):static class XImpl<T> implements X<T> {} private List<XImpl<Object>> createConcreteBlarpItems() { List<XImpl<Object>> items = new ArrayList<>(); items.add(new XImpl<>()); items.add(new XImpl<>()); return items; }调用时:
List<X<?>> blarpItems = new ArrayList<>(createConcreteBlarpItems()); b.addSomeBlarps(blarpItems);这种方式的方法返回类型无通配符,完全符合Sonar规则,同时
XImpl<Object>可以安全向上转型为X<?>。
测试代码疑问解答
为什么单个X可以添加到blubList,但批量addAll不行?
blubList是List<X<?>>,单个添加时,原始类型X的实例会被编译器自动适配为X<?>(原始类型对象可以赋值给任意泛型通配符类型)。但addAll要求参数是Collection<? extends X<?>>,而原始类型List<X>会被泛型擦除为List<Object>,Object并非X<?>的子类型,因此无法匹配addAll的参数要求,导致编译失败。强制转型后IDE无报错但编译失败?
原始类型List<X>和泛型类型Collection<? extends X<?>>的底层类型擦除后不兼容,编译器不允许这种跨泛型体系的强制转型,即使IDE没有实时报错,编译阶段也会抛出类型转换错误。为什么blarpList无法直接传递给addSomeBlarps?
blarpList是原始类型List<X>,它实现的是Iterable<X>(原始类型),而addSomeBlarps需要的是Iterable<X<?>>。Java泛型是不变的,Iterable<X>不能被视为Iterable<X<?>>的子类型,因此无法直接传递。
内容的提问来源于stack exchange,提问作者Hans Wurst

