你了解泛型通配符与上下界吗?(代码片段)

author author     2023-01-27     135

关键词:

在进入主题之前, 我们先简单说一下 Java 的泛型(generics)。它是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。

今天我们主要说如下内容:

  • 泛型的背景

  • 通配符以及上下界

  • 泛型及通配符的使用场景

为什么使用泛型及背后的问题?
我们来看一下官方的说法:

Stronger type checks at compile time.A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.

Elimination of casts.

Enabling programmers to implement generic algorithms.By using generics, programmers can implement generic algorithms that work on collections of different types, can be customized, and are type safe and easier to read.

是的, 终止目的就是想把程序员解放出来,关注他们更应该关注的事情上面去。当我第一次学习 Java 的泛型时,总感觉它类似于 C++ 中的模板。但随着慢慢的深入了解发现它们之间有本质的区别。

Java 中的泛型基本上完全在编译器中实现,由编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码。这种实现技术称为 擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除),这项技术有一些奇怪,并且有时会带来一些令人迷惑的后果。

对于泛型概念的引入,开发社区的观点是褒贬不一。从好的方面来说,上面已经说了,主要是在编译时刻就能发现很多明显的错误。而从不好的地方来说,主要是为了保证与旧有版本的兼容性,Java 泛型的实现上存在着一些不够优雅的地方。

下面我们来看一下,泛型类型的一个定义,后面我们要在这个的基础上进行改造
public class Box<T>

// T stands for "Type"

private T t;

public Box(T t) ? ? this.t = t;? ?

public void set(T t)  this.t = t; 

public T get()  return t; 

接下来下面我们来聊聊 Java 泛型的通配符, 记得刚开始看到通配符(?)时我是惊喜的,因为既然有通配符那么就可以这样定义:

public void doSometing(List<?> list)

list.add(1); //illegal

可是我们如上写法,总是出现编译错误,然后从惊喜变成惊吓,心想有什么卵用了。最后发现原因是在于通配符的表示的类型是未知的。那在这种情况下,我们可以使用上下界来限制未知类型的范围。好吧,写了那么多, 终于等到今天的主角登场了,容易吗?

还记得我们上面定义的 Box 吗, 现在我们再定义 Fruit 类以及它的子类 Orange 类。

class Fruit

class Orange extends Fruit

现在我们想它里面能装水果,那么我可以这么写。
Box<Fruit> box = Box<Orange>(new Orange) //illegal

不幸的是编译器会报错,这就尴尬了,why?why? why?实际上,编译器认为的容器之间没有继承关系。所以我们不能这样做。

为了解决这样的问题, 大神们想出来了<? extens T> 和 <? super T> 的办法,来让它们之间发生关系。
上界通配符(Upper Bounded Wildcards)
现在我们把上面的 Box 定义改成:
Box<? extends Fruit>

这就是上界通配符, 这样 Box 及它的子类如 Box 就可以赋值了。
Box<? extends Fruit> box = new Box<Orange>(new Orange)

当我们扩展一下上面的类, 食物分成为水果和蔬菜类, 水果有苹果和橘子。
在上面的结构中, Box<? extends Fruit> 涵盖下面的蓝色的区域。
技术分享图片
上界只能外围取,不能往里放
我们先看一下下面的例子:

Box<? extends Fruit> box = new Box<Orange>(new Orange);

//不能存入任何元素

box.set(new Fruit); //illegal

box.set(new Orange);//illegal

//取出来的东西只能存放在Fruit或它的基类里

Fruit fruit = box.get();

Object fruit1 = box.get();

Orange fruit2 = box.get(); //illegal

上面的注释已经很清楚了, 往 Box 里放东西的 set() 方法失效, 但是 get() 方法有效。

原因是 Java 编译器只知道容器内是 Fruit 或者它的派生类, 但是不知道是什么类型。可能是 Fruit、 可能是 Orange、可能是Apple?当编译器在看到 box 用 Box 赋值后, 它就把容器里表上占位符 “AAA” 而不是 “水果”等,当在插入时编译器不能匹配到这个占位符,所有就会出错。
下界通配符(Lower Bounded Wildcards)
和上界相对的就是下界 ,语法表示为:
<? super T>

表达的相反的概率:一个能放水果及一切水果基类的 Box。 对应上界的那种图, 下图 Box<? super Fruit> 覆盖×××区域。
技术分享图片

下界不影响往里存,但往外取只能放在Object 对象里
同上界的规则相反,下界不影响往里存,但往外取只能放在Object 对象里。

因为下界规定元素的最小的粒度,实际上是容器的元素的类型控制。所以放比 Fruit 粒度小的如 Orange、Apple 都行, 但往外取时, 只有所有类的基类Object对象才能装下。但是这样的话,元素的类型信息就全部消失了。

使用场景
在使用泛型的时候可以遵循一些基本的原则,从而避免一些常见的问题。

在代码中避免泛型类和原始类型的混用。比如List

和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。当需要利用JDK 5之前开发的遗留代码,而不得不这么做时,也尽可能的隔离相关的代码。
在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。

泛型类最好不要同数组一块使用。你只能创建new List<?>[10]这样的数组,无法创建new List[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。因此,当需要类似数组的功能时候,使用集合类即可。

不要忽视编译器给出的警告信息。

PECS 原则
如果要从集合中读取类型T的数据, 并且不能写入,可以使用 上界通配符(<?extends>)—Producer Extends。

如果要从集合中写入类型T 的数据, 并且不需要读取,可以使用下界通配符(<? super>)—Consumer Super。

如果既要存又要取, 那么就要使用任何通配符。

java泛型泛型用法(泛型编译期擦除|上界通配符<?extendst>|下界通配符<?supert>)(代码片段)

文章目录一、泛型擦除二、泛型的上界通配符<?extendsT>三、泛型的下界通配符<?superT>一、泛型擦除泛型只保留到编译期,在编译完毕后,泛型就不存在了;在运行时,通过反射,调用泛型类,即使违反了泛型规则,也能进行相关操... 查看详情

了解上界和下界?在 Java 泛型中

...布时间】:2013-11-1617:22:04【问题描述】:我真的很难理解通配符参数。我对此有几个问题。?作为类型参数只能在方法中使用。例如:printAll(MyList&lt;?extendsSerializable&gt;)我不能用?定义类作为类型参数。我了解?的上限 查看详情

详解java泛型(代码片段)

...例化泛型类型数组4.了解裸类型5.泛型的上界6.泛型方法7.通配符(?)7.1理解通配符7.2通配符上界7.3通配符下界 8.包装类8.1基本数据类型对应包装类8.2装箱和拆箱1为什么使用泛型普通的类和方法,只能使用具体的类型&... 查看详情

深入java泛型(三泛型的上下边界)(代码片段)

...入Java泛型三、泛型的上下边界3.1<?extendsE>:上界通配符(UpperBoundsWildcards)3.2<?superE>:下界通配符(LowerBoundsWildcards)3.3上下通配符的副作用三、泛型的上下边界?extendE是泛型的上边界, 查看详情

深入java泛型(三泛型的上下边界)(代码片段)

...入Java泛型三、泛型的上下边界3.1<?extendsE>:上界通配符(UpperBoundsWildcards)3.2<?superE>:下界通配符(LowerBoundsWildcards)3.3上下通配符的副作用三、泛型的上下边界?extendE是泛型的上边界,?superT是... 查看详情

java泛型和内部类(代码片段)

...的定义-类型边界Number类2.泛型方法3.泛型中的父子类型4.通配符?通配符上界通配符下界5.泛型的限制三、内部类1.本地内部类2.实例内部类3.静态内部类4.匿名内部类一、泛型的概述1.概念我们都用过Java集合类,比如Arra 查看详情

泛型通配符(代码片段)

1.通配符泛型通配符有两种上界通配符<?extendsxx> 下界通配符<?superxx>2.举例说明先上代码吧classAclassBextendsAclassCextendsApublicclassNonCovariantGenericspublicstaticvoidmain(String[]args)List<?extendsA>list1=newArrayList<B>();List<?superB&... 查看详情

大数据必学java基础(五十五):泛型深入了解(代码片段)

...泛型接口2、泛型方法3、泛型参数存在继承关系的情况4、通配符5、使用通配符后的细节6、泛型受限泛型深入了解一、引入1、什么是泛型(Genericÿ 查看详情

java泛型使用上下边界通配符解决泛型擦除问题(代码片段)

文章目录前言一、使用上边界通配符示例二、分析字节码的附加信息前言上一篇博客【Java泛型】泛型用法(泛型编译期擦除|上界通配符<?extendsT>|下界通配符<?superT>)一、泛型擦除章节中,讲到了泛型擦除问题,... 查看详情

聊聊java泛型(代码片段)

目录引子好处泛型通配符泛型位置  出现在类或者接口上  出现在方法中使用,包括返回值,参数  出现在变量声明中  注意事项泛型边界分类无边界 上界下界作用时机类型擦除部分擦除哪些被擦除了,哪些没被... 查看详情

java数据结构基础——泛型通配符(代码片段)

...)裸类型(RawType)4.泛型的擦除机制5.泛型的上界6.泛型方法7.通配符通配符上界通配符下界1.泛型概念泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化一般的类和方法,只能使用具体的类型:要么是... 查看详情

你真的懂java泛型吗(代码片段)

...t=newArrayList<Apple>();//无法通过编译但我们可以使用通配符来解决ArrayList<?extendsFruit>flist=newArrayList<Apple>();//使用通配符解决协变问题通配符上界通配符List<?extendsFruit>flist=Arrays.asList(newApple());Applea=(Apple)f... 查看详情

java——聊聊<?extendst>和<?supert>的含义及区别(代码片段)

文章目录:1.简介2.上界通配符3.下界通配符4.简单小总结1.简介<?extendsT> 和  <?superT> 是Java泛型中的“通配符(Wildcards)”和“边界(Bounds)”的概念。<?extendsT>:是指 “上界通配符(Upp... 查看详情

java——聊聊<?extendst>和<?supert>的含义及区别(代码片段)

文章目录:1.简介2.上界通配符3.下界通配符4.简单小总结1.简介<?extendsT> 和  <?superT> 是Java泛型中的“通配符(Wildcards)”和“边界(Bounds)”的概念。<?extendsT>:是指 “上界通配符(Upp... 查看详情

你真的了解泛型generic嘛?(代码片段)

...论的内容意在给大家提供全局的视野看待泛型Generic,大致了解主流语言的实现泛型会提供哪些便利呢ÿ 查看详情

java中的泛型t与通配符?概念入门(代码片段)

使用泛型的目的是利用Java编译机制,在编译过程中帮我们检测代码中不规范的有可能导致程序错误的代码。例如,我们都知道List容器可以持有任何类型的数据,所以我们可以把String和Integer等类型同时放入同一个List容器中,但... 查看详情

你真的了解java中的泛型e、t、k、v吗?

...有的强制转换都是自动和隐式的。本质上T,E,K,V都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的T,我们可以换成A-Z之间的任何一个字母都可以,并不会影响程序的正常运行。但是如果换成... 查看详情

java泛型概述与应用(代码片段)

...1.3泛型方法1.3.1示例代码1.4泛型接口1.4.1示例代码1.5类型通配符1.5.1类型通配符:<?>1.5.2类型通配符上限:<?extends类型>1.5.3类型通配符下限:<?super类型>1.5.4泛型通配符的使用泛型1.1泛型概述泛型是JDK5中引入的特性,它... 查看详情