立即降级有什么好处?

时间:2012-03-09 07:22:49

标签: java coding-style

我最近一直在寻找很多代码(为了我自己的利益,因为我还在学习编程),并且我注意到了许多Java项目(来自看起来很受尊敬的程序员)其中他们使用某种立即向下投射

我实际上有多个例子,但这是我直接从代码中提取的一个例子:

public Set<Coordinates> neighboringCoordinates() {
    HashSet<Coordinates> neighbors = new HashSet<Coordinates>();
    neighbors.add(getNorthWest());
    neighbors.add(getNorth());
    neighbors.add(getNorthEast());
    neighbors.add(getWest());
    neighbors.add(getEast());
    neighbors.add(getSouthEast());
    neighbors.add(getSouth());
    neighbors.add(getSouthWest());
    return neighbors;
}

在同一个项目中,这是另一个(也许更简洁)的例子:

private Set<Coordinates> liveCellCoordinates = new HashSet<Coordinates>();

在第一个示例中,您可以看到该方法的返回类型为Set<Coordinates> - 但是,该特定方法始终只返回HashSet - 而不是其他类型的Set }。

在第二个示例中,liveCellCoordinates最初定义为Set<Coordinates>,但会立即变为HashSet

这不仅仅是这个单一的特定项目 - 我发现在多个项目中就是这种情况。

我很好奇这背后的逻辑是什么?是否有一些代码约定会考虑这种良好做法?它能以某种方式使程序更快或更高效吗?它有什么好处?

8 个答案:

答案 0 :(得分:12)

当您设计方法签名时,通常最好只确定需要固定的内容。在第一个示例中,通过仅指定方法返回Set(而不是具体的HashSet),如果事实证明HashSetHashSet,则实施者可以自由地更改实现不是正确的数据结构。如果声明该方法返回HashSet,那么依赖于该对象的所有代码特别是Set而不是更通用的neighboringCoordinates()类型也需要修改。< / p>

一个现实的例子是,如果确定Set需要返回一个线程安全的return Collections.synchronizedSet(neighbors); 对象。如上所述,这将非常简单 - 用以下方法替换方法的最后一行:

Set

事实证明,synchronizedSet()返回的HashSet对象与Set不是分配兼容的。声明该方法返回liveCellCoordinates的好方法!

类似的考虑适用于第二种情况。使用Set的类中的代码不应该只知道它是Set<Coordinates> neighbors = new HashSet<Coordinates>(); 。 (事实上​​,在第一个例子中,我原本希望看到:

{{1}}

位于方法的顶部。)

答案 1 :(得分:4)

因为现在如果他们将来更改类型,则不必更新任何依赖于neighborCoordinates的代码。

让你拥有:

HashedSet<Coordinates> c = neighboringCoordinates()

现在,让我们说他们改变他们的代码以使用不同的set实现。猜猜看,你必须改变你的代码。

但是,如果你有:

Set<Coordinates> c = neighboringCoordinates()

只要他们的集合仍然实现了set,​​他们就可以在不影响代码的情况下在内部更改他们想要的内容。

基本上,为了隐藏内部细节,它只是最不具体的(在合理范围内)。您的代码只关心它可以作为一个集合访问集合。它不关心它是什么特定类型的集合,如果这是有道理的。那么,为什么要将代码与HashedSet耦合?

答案 2 :(得分:2)

在第一个示例中,该方法将始终仅返回HashSet,这是该类用户不必知道的实现细节。如果需要,这可以使开发人员使用不同的实现。

答案 3 :(得分:2)

这里的设计原则是“总是喜欢指定抽象类型”。

Set是抽象的;没有这样的具体类Set - 它是一个接口,根据定义是抽象的。该方法的合同将返回Set - 由开发人员选择要返回的{em>种 <{1}}。

您也应该使用字段来执行此操作,例如:

Set

不是

private List<String> names = new ArrayList<String>;

稍后,您可能希望更改为使用private ArrayList<String> names = new ArrayList<String>; - 指定抽象类型允许您在没有代码更改的情况下执行此操作(当然,初始化除外)。

答案 4 :(得分:2)

问题是你想如何使用变量。例如在您的上下文中重要它是HashSet吗?如果没有,你应该说出你需要的东西,这只是Set

如果您使用例如,情况会有所不同TreeSet在这里。然后您将丢失Set已排序的信息,如果您的算法依赖于此属性,则将实现更改为HashSet将是一场灾难。在这种情况下,最好的解决方案是编写SortedSet<Coordinates> set = new TreeSet<Coordinates>();。或者想象你会写List<String> list = new LinkedList<String>();:如果你想使用list作为列表,那就没关系,但你不能再使用LinkedList作为deque,就像offerFirstpeekLast不在List界面上。

所以一般规则是:尽可能一般,但具体要求。问问自己你真正需要什么。某个界面是否提供您需要的所有功能和承诺?如果是,那就使用它。否则更具体,使用另一个接口或类本身作为类型。

答案 5 :(得分:2)

这是另一个原因。这是因为更一般的(抽象)类型具有更少的行为,这是好的,因为没有更多的空间来搞乱。

例如,假设您实现了这样的方法:List<User> users = getUsers();实际上您可以使用更像这样的抽象类型:Collection<User> users = getUsers();。现在Bob可能错误地假设您的方法按字母顺序返回用户并创建错误。如果你使用Collection,就不会有这样的混乱。

答案 6 :(得分:1)

这很简单。

在您的示例中,该方法返回Set。从API设计者的角度来看,与返回HashSet相比,这有一个显着的优势。

如果在某个时刻,程序员决定使用SuperPerformantSetForDirections,那么如果新类扩展Set,他可以在不更改公共API的情况下执行此操作。

答案 7 :(得分:1)

技巧是“接口的代码”。

原因在于,在99.9%的情况下,您只希望HashSet/TreeSet/WhateverSet的行为符合所有人实现的Set接口。它使您的代码更简单,您实际需要说HashSet的唯一原因是指定您需要的行为

您可能知道HashSet相对较快,但以看似随机的顺序返回项目。 TreeSet有点慢,但按字母顺序返回项目。您的代码无关紧要,只要它的行为类似于Set。

这样可以提供更简单的代码,更易于使用。

请注意,Set的典型选择是HashSet,Map是HashMap,List是ArrayList。如果你使用非典型(for you)实现,那么应该有一个很好的理由(比如,需要按字母顺序排序),并且应该将这个原因放在new语句旁边的注释中。使未来的维护者的生活更轻松。