Java编程协变与逆变

位置:首页>文章>详情   分类: 教程分享 > Java教程   阅读(639)   2023-03-28 11:29:14

一.什么是协变(Variance)

更复杂类型中子类型如何与其他子类相关处理。

“更复杂的类型”在这里指的是更高级别的结构,如容器和功能。 因此,协变是关于容器和函数之间的赋值兼容性,这些函数由通过类型层次结构连接的参数组成。 它允许参数和子类型多态性的安全集成1。 例如。 我可以将返回猫列表的函数的结果分配给“动物列表”类型的变量吗? 我可以将奥迪汽车列表传递给接受汽车列表的方法吗? 我可以在这个动物名单中插入一只狼吗?
 

二.4中类型协变

维基百科文章介绍:
  • Covariant 如果它接受子类型但不接受超类型
  • Contravariant 如果它接受超类型但不接受子类型
  • Bivariant 如果它同时接受超类型和子类型
  • Invariant 如果不接受超类型或子类型,则不变

三.Java中的Invariance

可用变量的一边必须是没有开放边界的。

如果A是B的超类型,则GenericType <A>不是GenericType <B>的超类型,反之亦然。
这意味着这两种类型彼此无关,在任何情况下都不能互换。

3.1不变容器

在Java中,不变可能是您将遇到的泛型的第一个例子,并且是最直观的。 类型参数的方法可以像人们期望的那样使用。 可以访问类型参数的所有方法。

他们无法交换:
// 类型层级: Person :> Joe :> JoeJr
List<Person> p = new ArrayList<Joe>(); // 编译错误 (a bit counterintuitive, but remember List<Person> is invariant)
List<Joe> j = new ArrayList<Person>(); // 编译错误
但是你可以添加以下对象:
// 类型层级: Person :> Joe :> JoeJr
List<Person> p = new ArrayList<>();
p.add(new Person()); // ok
p.add(new Joe()); // ok
p.add(new JoeJr()); // ok
你可以这样获取他们:
// 类型层级: Person :> Joe :> JoeJr
List<Joe> joes = new ArrayList<>();
Joe j = joes.get(0); // ok
Person p = joes.get(0); // ok

四.Java中的Covariance

可用变量必须在低层次类型有一个开口
如果B是A的子类型,则GenericType <B>是GenericType<? extends A>的子类型

4.1Java中的数组一直是协变的

在Java 1.5中引入泛型之前,数组是唯一可用的通用容器。 它们一直是协变的,例如。 Integer []是Object []的子类型。 编译器允许您将Integer []传递给接受Object []的方法。 如果方法插入了Integer的超类型,则会在运行时抛出ArrayStoreException。 协变泛型类型规则在编译时实现此检查,从一开始就不允许错误发生。
 
public static void main(String... args) {
  Number[] numbers = new Number[]{1, 2, 3, 4, 5};
  trick(numbers);
}
 
private static void trick(Object[] objects) {
  objects[0] = new Float(123);  // ok
  objects[1] = new Object();  // ArrayStoreException thrown at runtime
}

4.2协变容器

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

您可以为子类型交换超类型:
// 类型层级: Person :> Joe :> JoeJr
List<? extends Joe> = new ArrayList<Joe>(); // ok
List<? extends Joe> = new ArrayList<JoeJr>(); // ok
List<? extends Joe> = new ArrayList<Person>(); // 编译错误
从他们那里读取是直观的:
// 类型层级: Person :> Joe :> JoeJr
List<? extends Joe> joes = new ArrayList<>();
Joe j = joes.get(0); // ok
Person p = joes.get(0); // ok
JoeJr jr = joes.get(0); // 编译错误 (you don't know what subtype of Joe is in the list)
禁止写入它们(违反直觉)以防止上述阵列的陷阱。
// 类型层级: Person > Joe > JoeJr
List<? extends Joe> joes = new ArrayList<>();
joes.add(new Joe());  // 编译错误 (you don't know what subtype of Joe is in the list)
joes.add(new JoeJr()); // 编译错误(ditto)
joes.add(new Person()); // 编译错误 (intuitive)
joes.add(new Object()); // 编译错误 (intuitive)

五.Java中的逆变

The use-site must have an open upper bound on the type parameter.
如果A是B的超类型,则 GenericType<A>是GenericType<? super B>的超类

5.1逆变容器

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

您可以为超类型交换子类型:
// 类型层级: Person > Joe > JoeJr
List<? super Joe> joes = new ArrayList<Joe>();  // ok
List<? super Joe> joes = new ArrayList<Person>(); // ok
List<? super Joe> joes = new ArrayList<JoeJr>(); // 编译错误
从中读取时无法捕获特定类型:
// 类层级: Person > Joe > JoeJr
List<? super Joe> joes = new ArrayList<>();
Joe j = joes.get(0); // 编译错误 (could be Object or Person)
Person p = joes.get(0); // 编译错误 (ditto)
Object o = joes.get(0); // 允许的 because everything IS-A Object in Java
您可以添加“下限”的子类型:
// 类层级: Person > Joe > JoeJr
List<? super Joe> joes = new ArrayList<>();
joes.add(new JoeJr()); // 允许
但是你不能添加超类型:
// 类型层级: Person > Joe > JoeJr
List<? super Joe> joes = new ArrayList<>();
joes.add(new Person()); // 编译错误 (again, could be a list of Object or Person or Joe)
joes.add(new Object()); // 编译错误 (ditto)

六、Java中的Bivariance

The use-site must declare an unbounded wildcard on the type parameter.
具有无界通配符的泛型类型是相同泛型类型的所有有界变体的超类型。
例如。 GenericType <?>是GenericType <String>的超类型。
由于unbounded类型是类型层次结构的根,因此它遵循其参数类型的类型,它只能从java.lang.Object继承的方法访问。

思考一下,将GenericType<?>视为GenericType<Object>

七、构造函数含多个类型变量

那些更复杂的类型如函数呢? 同样的原则适用,您只需要考虑更多类型参数:
// 类型层次: Person > Joe > JoeJr
 
// Invariance
Function<Person, Joe> personToJoe = null;
Function<Joe, JoeJr> joeToJoeJr = null;
personToJoe = joeToJoeJr; // COMPILE ERROR (personToJoe is invariant)
 
// Covariance
Function<? extends Person, ? extends Joe> personToJoe = null; // covariant
Function<Joe, JoeJr> joeToJoeJr = null;
personToJoe = joeToJoeJr;  // ok
 
// Contravariance
Function<? super Joe, ? super JoeJr> joeToJoeJr = null; // contravariant
Function<? super Person, ? super Joe> personToJoe = null;
joeToJoeJr = personToJoe; // ok

八、Variance 与Inheritance

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() {} // overridden
  public void fail() throws IOException {} // overridden
}
但是尝试使用协变参数覆盖方法只会导致过载:
interface Person {
  void add(Person p);
}
 
interface Joe extends Person {
  void add(Joe j);
}
 
class JoeImpl implements Joe {
  public void add(Person p) {}  // overloaded
  public void add(Joe j) {} // overloaded
 }
地址:https://www.leftso.com/article/572.html

相关阅读

Java泛型变量协变与逆变“更复杂的类型”在这里指的是更高级别的结构,如容器和功能。 因此,协变是关于容器和函数之间的赋值兼容性
Java编程之java static关键字,Java编程,static关键字
Java编程软件有哪些?常用Java编程软件下载、安装和使用说明
java多线程编程_java多线程安全_java多线程实现安全锁CAS机制,CAS在java多线程中相当于数据库的乐观锁,synchronized相当于数据库的乐观锁。
java编程之java jwt token使用,autho0的Java-jwt框架使用,java编程,java-jwt
Java编程之spring boot FastDFS Java client使用,Java编程,FastDFS Java客户端
Java编程中使用Arrays.asList()和Collections.singletonList()创建单个元素的List
Java编程中Spring Boot整合RabbitMQ实现消息中间件RabbitMQ的使用
Java编程中纯jdk java方式编写webservice服务(server)和客服端(client)