是否为HashSet或其他实现使用Java变量类型Collection?

sto*_*r96 8 java collections types interface declaration

我经常看到类的声明List<String> list = new ArrayList<>();Set<String> set = new HashSet<>();类的声明。对我来说,使用变量类型的接口来提供实现的灵活性是非常有意义的。上面的示例仍然定义Collection了必须使用的类型,分别允许的操作以及在某些情况下的行为(由于docs)。

现在考虑以下情况:使用类中的字段实际上仅需要Collection(或什至Iterable)接口的功能,Collection而实际上的类型无关紧要,或者我不想对其进行过多说明。因此,我选择例如HashSet作为实现,并将字段声明为Collection<String> collection = new HashSet<>();

那么,Set在这种情况下,该字段实际上是否应该是类型?这样的声明是不好的做法吗,如果是这样,为什么?还是最好的做法是指定尽可能少的实际类型(并仍然提供所有必需的方法)。之所以这样问,是因为我几乎从未见过这样的声明,而最近在只需要指定Collection接口功能的情况下,我会得到更多的信息。

例:

// Only need Collection features, but decided to use a LinkedList
private final Collection<Listener> registeredListeners = new LinkedList<>();

public void init() {
    ExampleListener listener = new ExampleListener();
    registerListenerSomewhere(listener);
    registeredListeners.add(listener);
    listener = new ExampleListener();
    registerListenerSomewhere(listener);
    registeredListeners.add(listener);
}

public void reset() {
    for (Listener listener : registeredListeners) {
        unregisterListenerSomewhere(listener);
    }

    registeredListeners.clear();
}
Run Code Online (Sandbox Code Playgroud)

Stu*_*rks 5

由于您的示例使用私有字段,因此与隐藏实现类型无关紧要。您(或者维护此类的人)总是可以仅查看字段的初始化程序以了解其内容。

但是,根据使用方式的不同,可能值得为该字段声明一个更具体的接口。声明为a List表示允许重复,并且顺序很重要。声明为a Set表示不允许重复且顺序不重要。如果有重要意义,您甚至可以声明该字段具有特定的实现类。例如,声明为LinkedHashSet表示不允许重复,但是顺序重要。

如果类型出现在类的公共API中,以及该类的兼容性约束是什么,则选择是否使用接口以及使用哪种接口就变得更加重要。例如,假设有一种方法

public ??? getRegisteredListeners() {
    return ...
}
Run Code Online (Sandbox Code Playgroud)

现在,返回类型的选择会影响其他类。如果您可以更改所有呼叫者,也许没什么大不了的,那么您只需要编辑其他文件即可。但是假设调用者是您无法控制的应用程序。现在,接口的选择非常关键,因为在不破坏应用程序的情况下就无法更改接口。此处的规则通常是选择最抽象的接口,以支持您希望调用者执行的操作。

大多数Java SE API返回Collection。这为基础实现提供了一定程度的抽象,但同时也为调用方提供了一组合理的操作。调用者可以迭代,获取大小,进行包含检查或将所有元素复制到另一个集合。

一些代码库Iterable用作返回的最抽象的接口。它所做的只是允许调用方进行迭代。有时这是必需的,但与相比可能有所限制Collection

另一种选择是返回Stream。如果您认为调用方可能想使用流的操作(例如,筛选,映射,查找等)而不是迭代或使用收集操作,则这将很有帮助。

请注意,如果选择返回CollectionIterable,则需要确保返回不可修改的视图或进行防御性复制。否则,调用者可能会修改您班级的内部数据,这可能会导致错误。(是的,甚至Iterable可以允许进行修改!考虑获取一个Iterator,然后调用该remove()方法。)如果返回Stream,则不必担心,因为您不能使用a Stream来修改基础源。

请注意,我把您关于字段声明的问题变成了关于方法返回类型声明的问题。这种“编程到接口”的思想在Java中非常普遍。在我看来,对于局部变量没有太大的意义(这就是为什么通常使用var),对于私有字段则无关紧要,因为根据定义,那些(几乎)只影响声明它们的类。但是,“接口编程”原则对于API签名非常重要,因此在这些情况下,您确实需要考虑接口类型。私人领域,没有那么多。

(最后一点:在某些情况下,您需要关注私有字段的类型,那就是在使用直接操作私有字段的反射框架时。在这种情况下,您需要将这些字段视为公开-就像方法返回类型一样-即使未声明它们public。)