📜  Java中的差异

📅  最后修改于: 2022-05-13 01:55:16.692000             🧑  作者: Mango

Java中的差异

差异是指更复杂类型之间的子类型如何与其组件之间的子类型相关。这里的“更复杂的类型”是指更高级别的结构,如容器和函数。因此,方差是关于容器和由通过类型层次结构连接的参数组成的函数之间的分配兼容性。它允许参数和子类型多态性的安全集成。

方差的种类:方差有以下4种。 类型构造函数具有以下类型:

Covariant If it accepts subtypes but not supertypes
ContravariantIf it accepts supertypes but not subtypes
BivariantIf it accepts both supertypes and subtypes
Invariant If it accepts neither supertypes nor subtypes.

Java中的不变性:使用站点在类型参数上必须没有开放边界。如果 A 是 B 的超类型,则 GenericType 不是 GenericType 的超类型,反之亦然。这意味着这两种类型彼此没有关系,在任何情况下都不能互换。

类型 1:不变容器

在Java中,不变量可能是您将遇到的第一个泛型示例,并且最直观。类型参数的方法可以按预期使用。类型参数的所有方法都可以访问。它们不能交换,您可以从它们中读取两者,如下图所示。

图 1:不可更换

插图 2:从它们读取对象

类型 2: Java中的协方差

使用站点必须在类型参数上有一个开放的下限。如果 B 是 A子类型,则 GenericType 是 GenericType

注意: Java中的数组一直是协变的

在Java 1.5中引入泛型之前,数组是唯一可用的泛型容器。它们一直是协变的,例如。 Integer[]Object[]的子类型。编译器允许您将Integer[]传递给接受Object[]的方法。如果该方法插入Integer的超类型,则在运行时抛出 ArrayStoreException。协变泛型类型规则在编译时实现这种检查,首先不允许错误发生。

例子

输出:

现在让我们讨论协变容器。 Java允许子类型化(协变)泛型类型,但它根据最小惊讶原则对可以“流入和流出”这些泛型类型的内容进行了限制。换句话说,具有类型参数返回值的方法是可访问的,而具有类型参数输入参数的方法是不可访问的。

示例 1:可以将超类型替换为子类型:

插图 2:从它们中读取是直观的:

禁止写入它们(违反直觉)以防止上述数组的陷阱。例如。在下面的示例代码中,如果其他人的方法具有协变参数List< ? extends Person>添加了一个Jill。

类型 3:逆变容器

逆变容器的行为违反直觉:与协变容器相反,无法访问具有类型参数的返回值的方法,而具有类型参数的输入参数的方法是可访问的:

图 1:您可以将子类型替换为超类型:

图 2:从它们读取时无法捕获特定类型:

图 3:您可以添加“下限”的子类型:

图 4:但是您不能添加超类型:

类型 4: Java中的二元性

使用站点必须在类型参数上声明一个无界通配符

具有无界通配符的泛型类型是同一泛型类型的所有有界变体的超类型。例如GenericTypeGenericType的超类型。由于无界类型是类型层次结构的根,它遵循其参数类型,它只能访问从Java.lang.Object 继承的方法。

Think of GenericType as GenericType.

让我们讨论具有 N 型参数的结构的方差。更复杂的类型(例如函数)呢?相同的原则适用,您只是需要考虑更多类型参数。下图说明了回答此类困境的方法。

插图:

Function personToJoe = null;
Function joeToJoeJr = null;
personToJoe = joeToJoeJr; // compile error

// Covariance
Function personToJoe
    = null;
Function jorToJorJr = null;
personToJoe = joeToJoeJr; // ok

// Contravariance
Function joeToJoeJr = null;
Function personToJoe = null;
joeToJoeJr = personToJoe; // ok

变异和继承

插图 1: Java允许覆盖具有协变返回类型和异常类型的方法:

interface person {
    Person get();
    void fail() throws Exception;
}
interface Joe extends Person {
    JoeJr get();
    void fail() throws IOException;
}
class JoeImpl implements Joe {
    public JoeJr get() {} // overriden
    public void fail throws IOException {} // Overriden
}

说明 2:尝试使用协变参数覆盖方法只会导致重载:

interface Person {
    void add(Person p);
}
interface Joe extends Person {
    void add(Joe j);
}
class JoeImpl implements Joe {
    public void add(Person p) {} // overload
    public void add(Joe j) {} // overload
}

结论:差异给Java带来了额外的复杂性。虽然围绕方差的类型规则很容易理解,但有关类型参数方法的可访问性的规则是违反直觉的。理解它们不仅仅是“显而易见的”。它需要停下来思考逻辑后果。

方差在我的日常编程中提供了适度的净收益,特别是当需要与子类型兼容时(这在 OOP 中很常见)。