我依旧记得在大学里学习一种方法的返回类型应该尽可能地缩小,但我在网上搜索任何参考文献都是空的,而SonarQube称之为代码味道。例如。在下面的例子中(注意TreeSet
和Set
只是例子)
public interface NumberGetter {
Number get();
Set<Number> getAll();
}
public class IntegerGetter implements NumberGetter {
@Override
public Integer get() {
return 1;
}
@Override
public TreeSet<Number> getAll() {
return new TreeSet<>(Collections.singleton(1));
}
}
SonarQube告诉我
声明应使用Java集合接口(如“List”)而不是特定的实现类(如“LinkedList”)。 Java Collections API的目的是提供一个定义良好的接口层次结构,以隐藏实现细节。 (鱿鱼:S1319)
我看到了隐藏实现细节的观点,因为我无法在不破坏向后兼容性的情况下轻松更改IntegerGetter :: getAll()的返回类型。但通过这样做,我还向消费者提供了可能有价值的信息,即他们可以改变他们的算法以更适合使用TreeSet
。如果他们不关心这个属性,他们仍然可以使用IntegerGetter
(但是他们获得了它),如下所示:
Set<Number> allNumbers = integerGetter.getAll();
所以我有以下问题:
(如果我用TreeSet
替换SortedSet
,N.B.SonarQube不会抱怨。这个代码是否只是没有使用Collection API接口?如果我的特定问题只有一个具体的类怎么办?)
返回类型必须在调用者的需求和实现的需要之间取得平衡:您告诉调用者关于返回类型的越多,以后更难以更改该类型。
哪个更重要将取决于具体情况。你有没有看到这种类型的变化?了解来电者的类型有多大价值?
在IntegerGetter.get()
的情况下,如果返回类型发生变化将会非常令人惊讶,因此告诉调用者没有任何伤害。
在IntegerGetter.getAll()
的情况下,它取决于调用者使用该方法的内容:
Iterable
将是正确的选择。Collection
可能。Set
。SortedSet
。TreeSet
可能是正确的选择。我尝试 - 作为经验法则 - 在方法签名上使用支持我需要的API的最通用类型(类或接口无关紧要):更通用的类型,更少的API。所以,如果我需要一个表示相同类型对象族的参数,我开始使用Collection
;如果在特定的场景中是重要的排序的想法,我使用List
,避免发布我内部使用的特定List
实现的任何信息:我的想法是保持改变实现的能力(可能是为了性能优化,支持一个不同的数据结构,等等)没有中断客户端。
正如你所说,发布像我使用TreSet
这样的信息可以留给客户端优化 - 但我的想法是它依赖于:个案你可以评估特定场景是否需要放松一般规则以暴露更通用的接口您可以。
所以,回答你的问题:
IntegerGetter
接口的NumberGetter
实现中返回一个更窄的类型是合适的:Java允许你这样做而你没有打破我更通用的更美丽的规则:NumberGetter
接口使用Number
作为返回类型暴露更通用的接口但是在具体的实现中,我们可以使用较窄的返回类型来指导方法实现:引用更抽象的接口的客户端不受此选择的影响,引用特定子类的客户端可以尝试使用更具体的接口Integer
而不是Number
(如果我明确使用NumberGetter
,可能我认为就Integer
s,不是Number
s的术语),但是只有当你需要子类暴露的API而不是接口时,引用TreeSet
而不是Set
才有用。这是一个准哲学问题 - 我的答案也是如此:我希望它对你有用!
这不能只有一个答案,因为它取决于用例。
我的意思是,这取决于您希望实施的灵活程度,以及您希望为消费者提供API
的灵活程度。
我会给你一个更一般的答案。
您是否希望您的消费者仅限环路?返回一个Collection<T>
。
您希望您的消费者能够通过索引访问吗?返回一个List<T>
您是否希望您的消费者知道并能够有效地验证元素是否存在?返回一个Set<T>
等等。
相同的方法对输入参数有效。接受List<T>
,甚至是ArrayList<T>
或LinkedList<T>
这样的具体课程,如果你只是循环它有什么意义呢?您只是为代码提供了更少的灵活性,以便将来进行修改
你在这里使用IntegerGetter
的继承方法返回类型所做的就是类型特化。只要您继续向外部世界公开接口,这没关系。
我的经验法则是在处理外部世界时尽可能通用,并且在实现我的应用程序的关键(核心)部分时尽可能具体,限制可能的用例,保护自己免受滥用我刚刚编写的内容,以及用于文档目的。
您不应该绝对做的是使用instanceof
或class
比较来发现实际类型并采取不同的路线。这破坏了代码库。