Java 泛型之上界下界通配符

简介: 本Java教程是为JDK 8编写的。本页描述的示例和实践没有利用后续版本中引入的改进。

Java 泛型之上界下界通配符


本Java教程是为JDK 8编写的。本页描述的示例和实践没有利用后续版本中引入的改进。


泛型,继承和子类

如你所知,只要类型兼容,就可以将一种类型的对象分配给另一种类型的对象。例如,你可以指定一个整数一个对象,因为对象是一个整数的超类型:

Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger; // 好

在面向对象的术语中,这被称为“是一种”关系。由于Integer 是一种Object,因此允许赋值。但是Integer也是一种Number,所以下面的代码也是有效的:

public void someMethod(Number n){/ * ... * /}
someMethod(new Integer(10)); // 好
someMethod(new Double(10.1)); // 好

泛型也是如此。您可以执行泛型类型调用,将Number作为其类型参数传递,如果参数与Number兼容,则允许任何后续的add调用:

Box <Number> box = new Box <Number>();
box.add(new Integer(10)); // 好
box.add(new Double(10.1)); // 好

现在考虑以下方法:

public void boxTest(Box <Number> n){/ * ... * /}

它接受什么类型的论据?通过查看其签名,您可以看到它接受一个类型为Box<Number>的参数。但是,这是什么意思?您是否可以按照您的预期传递Box<Integer>Box<Double>?答案是“”,因为Box<Integer>Box<Double>不是Box<Number>的子类型。

这是在使用泛型编程时一个常见的误解,也是一个需要学习的重要概念。

NJ3AOZ{GDRY5UKOS0QP~HVM.png

Box<Integer>不是Box<Number>的子类型,即使IntegerNumber的子类型。

注意:给定两个具体类型 A 和 B(例如, NumberInteger), MyClass<A>MyClass<B>无关,无论 A 和 B 是否相关。 MyClass<A>MyClass<B> 的公共父是 Object

有关如何在类型参数相关时在两个泛型类之间创建类似子类型关系的信息,请参阅下面的通配符和子类型一节。


泛型类和子类型化

您可以通过扩展(extends)泛型类或实现(implements)泛型接口来对其进行子类型化。一个类或接口的类型参数与另一个类或接口的类型参数之间的关系由extendsimplements子句确定。

使用Collections类作为示例,ArrayList<E> 实现 List<E>List<E> 扩展Collection<E>。所以 ArrayList<String>List<String>的子类型,它是Collection<String>的子类型。只要不改变类型参数,就会在类型之间保留子类型关系。

Collection层次结构示例

J{~$RH3T$G1Z]9(SUMTRUE2.png

显示Collection层次结构示例的图表: ArrayList<String>List<String>的子类型,二者都是 Collection<String>的子类型。

现在假设我们想要定义我们自己的列表接口PayloadList,它将可选值泛型类型参数P的与每个元素相关联。它的声明可能如下:

interface PayloadList<E,P> extends List<E> {
  void setPayload(int index, P val);
  ...

}

PayloadList的以下参数化是List<String>的子类型:

PayloadList<String,String>

PayloadList<String,Integer>

PayloadList<String,Exception>

P48N{QX4J6JIB7T5ZCVR]54.png

PayloadList层次结构示例

PayLoadList层次结构的示意图: PayloadList<String,String>List<String>的子类型,它是 Collection<String>的子类型。 在 PayloadList<String,String>的相同级别是 PayloadList <String,Integer>PayloadList<String,Exceptions>


通配符和子类型

泛型,继承和子类一节中所述,泛型类之间或接口之间几乎并不因它们的类型参数而相关。但是,您可以使用通配符在泛型类或接口之间创建关系。

给定以下两个常规(非泛型)类:

class A { /* ... */ }
    class B extends A { /* ... */ }

编写以下代码是合理的:

B b = new B();
    A a = b;

此示例显示常规类的继承遵循此子类型规则:如果B扩展A,则类B是类A的子类型。此规


则不适用于泛型类型

List<B> lb = new ArrayList<>();
List<A> la = lb;   //编译时错误

鉴于IntegerNumber的子类型,List<Integer>List<Number> 之间的关系是什么?

公共父类是List<?>

Y(PLEES5E8G(~9C8YEJPXHW.png

该图表显示 List<Number>List<Integer> 的公共父级是未知类型的 List.

尽管IntegerNumber的子类型,但List<Integer>不是List<Number>的子类型,实际上,这两种类型不相关。List<Number>List<Integer> 的公共父是 List<?>


上界(extends)的通配符与下界(super)通配符

为了在这些类之间创建关系以便代码可以通过 List<Integer> 的元素访问Number的方法,请使用上界的通配符:

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK, List<?extends Integer>是 List< ? extends Number>的子类型

因为IntegerNumber的子类型,而numListNumber对象的列表,所以intList(是一个Integer对象列表)和numList之间现在存在关系。下图显示了使用上限和下限通配符声明的多个 List 类之间的关系。

Q4]VN`SD[$MJP(21K9_8KES.png

几个通用List类声明的层次结构。

图表显示: List<Integer>List <? extends Integer>List<?super Integer>的子类型。 List<? extends Integer>List<? extends Number> 的子类型,它是 List <?>的子类型。 List<Number>List <?super Number>List<? extends Number>的子类型。 List<? super Number>List <? super Integer>的子类型, 且都是 List<?>的子类型。


通配符使用指南

学习使用泛型编程时,更令人困惑的一个方面是确定何时使用上限有界通配符以及何时使用下限有界通配符。本文提供一些设计代码时要遵循的一些准则。

为讨论方便,认为变量具备两个功能:

一个“In”变量

“in”变量向代码提供数据。想象一下带有两个参数的复制方法:copy(src,dest)。该SRC参数提供的数据被复制,因此它是“in”参数。

一个“Out”变量

“out”变量保存数据以供其他地方使用。在复制示例中,copy(src,dest),dest参数接受数据,因此它是“out”参数。

当然,一些变量既用于“in”又用于“out”目的 - 这种情况也在本文中也用到了。

在决定是否使用通配符以及适合使用哪种类型的通配符时,可以使用“in”和“out”原则。以下列表提供了遵循的准则:

通配符指南:

  • 使用extends关键字, 定义带有上界通配符的“in”变量。
  • 使用super关键字,使用下界通配符定义“out”变量。
  • 在可以使用Object类中定义的方法访问“in”变量的情况下,使用无界通配符。
  • 在代码需要作为“in”和“out”变量访问变量的情况下,不要使用通配符。

这些指南不适用于方法的返回类型。应该避免使用通配符作为返回类型,因为它强制程序员使用代码来处理通配符。

List<? extends ...> 可以被非正式地认为是只读的,但这不是一个严格的保证。假设您有以下两个类:

class NaturalNumber {
    private int i;
    public NaturalNumber(int i) { this.i = i; }
    // ...
}
class EvenNumber extends NaturalNumber {
    public EvenNumber(int i) { super(i); }
    // ...
}

请考虑以下代码:

List<EvenNumber> le = newArrayList<>();

List<? extendsNaturalNumber> ln = le;

ln.add(newNaturalNumber(35));  // compile-time error //编译时错误

因为List<EvenNumber>List<? extends NaturalNumber>,您可以赋值leln。但是你不能使用ln将自然数添加到偶数列表中。列表中的以下操作是可能的:

  • 您可以添加null。
  • 你可以调用清除。
  • 您可以获取迭代器并调用remove。
  • 您可以捕获通配符并写入从列表中读取的元素。

你可以看到List<? extends NaturalNumber>在严格意义上不是只读的,但您可能会这样想,因为您无法存储新元素或更改列表中的现有元素。

相关文章
|
3月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
75 2
|
16天前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
14 0
[Java]泛型
|
25天前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
10 1
|
1月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
26 5
|
30天前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
17 1
|
2月前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
34 9
Java——包装类和泛型
|
1月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
18 2
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
30天前
|
安全 Java 编译器
Java基础-泛型机制
Java基础-泛型机制
13 0
|
2月前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。
下一篇
无影云桌面