为什么显式实现后返回的是list接口,而不是ArrayList接口实现

Why list interface is returned instead of ArrayList interface implementation after explicit implementation

我知道我的代码内部发生了什么,也知道如何修复错误。但是,我不明白为什么事情会这样发生。具体来说,为什么我的方法 returns interface List 而不是 ArrayList 接口实现?

最让我困惑的是在将 moons 实现为 ArrayList 之后,它仍然是 returns 接口。

提前致谢!

    private static ArrayList<HeavenlyBody> makingSetOfMoons(){
        List<HeavenlyBody> moons = new ArrayList<>();
        for(HeavenlyBody planet : Main.planets){
            moons.addAll(planet.getSatellites());
        }
        return moons;
    }

错误输出:

Incompatible types. Found: 'java.util.List<com.practice.HeavenlyBody>', required: 'java.util.ArrayList<com.practice.HeavenlyBody>'

编译器期望 return 类型 ArrayList 或更具体类型的对象(扩展 ArrayList 的 class )的方法。但是,从该方法中编辑的 moons 变量 return 的类型不太具体,即 List。 在这种情况下,对于编译器来说,分配给 moons 变量的实例具有什么特定类型并不重要 - 您正在询问的实现 - 只有变量类型很重要。

我希望这能回答你的问题,如果没有请问,我会尽力详细说明。

让我们看看你的方法声明:

private static ArrayList<HeavenlyBody> makingSetOfMoons(){

Java 期望方法 return 一个 ArrayList<HeavenlyBody>

但是你 return 声明的 moons 变量:

List<HeavenlyBody> moons = ....

分配给变量的列表子类型类型无关紧要——Java 将变量视为 List<HeavenlyBody>,纯粹而简单。即使您将 ArrayList<HeavenlyBody> 分配给此变量,Java 编译器也无法使用它为您执行自动隐式转换,因为您稍后可以在方法中更改变量引用的对象,现在或更晚,这可能会导致问题。正如 Jon Skeet 所说 here:

The compiler uses the declared type of the variable to know how it can be used... it's as simple as that.

一个解决方案是将 moons 变量更改为:

ArrayList<HeavenlyBody> moons = ...

这可行但会出错,因为“编写接口代码”几乎总是更好(参见:What does it mean to “program to an interface”?)。最好在其声明中更改方法期望的 return 类型:

private static List<HeavenlyBody> makingSetOfMoons() {

这一切都归结为引用变量和引用(或对象)之间的区别,特别是两者的类型。这是一个很好的区别,但也是一个重要的区别。

变量及其类型:

List<HeavenlyBody> moons

这个变量是一个 reference 变量,它引用一个对象,而不是一个 primitive 变量,它引用一个原始类型。

当前您将此引用分配给变量:

new ArrayList<>();

所以变量的类型是 List<HeavenlyBody> 并且它引用的引用的类型是 ArrayList<HeavenlyBody>

您在理解中似乎遗漏了以下表达式的两个部分:

Type identifier = new SubType();

注意,identifier 类型 Type 的变量(并且 不直接 类型 SubType),并且它可以存储对任何其他类型对象的引用,只要该“其他”类型是 Type 的子类型(包括 Type 本身,因为类型是Java).

中自身的子类型

因此:

private static ArrayList<HeavenlyBody> makingSetOfMoons() {
    List<HeavenlyBody> moons = new ArrayList<>();
    ...
    return moons;
}

不会编译,因为 moons 的类型是 List<HeavenlyBody>


问:为什么这样设计?

答:Because

in Java programming language, every variable and every expression has a type that is known at compile time.JSL - Chapter 4: Types, Values, and Variables

假设你有:

public ArrayList<SomeType> something(List<SomeType> list) {
    ...
    return list;
}

你可以观察到,List<SomeType> list 可以接受 任何 的子类型;但是,在这种情况下,如果您被允许 return listList<SomeType> 类型),您很有可能会收到运行时异常,因为并非每个子List<E> 的类型可以转换为 ArrayList<E>.