对Java通配符的个人理解(以集合为例)


  • 前言:最近在学习Java,当学到了泛型的通配符时,不是很理解PECS(Producer Extends Consumer Super)原则,以及<? extends E> 不能使用add方法和<? super E> 不能使用get方法(注意:仅能使用Object o = list.get(0);取得是Object对象),所以我对它进行了学习和理解,这篇博文用了简单通俗的方法去讲解add和get在通配符中的使用场景以及PECS原则。可能本人的水平有限,如果我的理解有误或者内容错误,欢迎指出来,我好进行及时修改。

一、什么是泛型通配符


<一>简单定义泛型统配符

1. 在了解泛型的统配符之前,我们先了解下什么是泛型,泛型是一种包含类型参数的类,值得注意的一点是这里的类型必须是引用数据类型,而且放在尖括号< >内,这里引进了类型参数,将类直接作为了参数。

2. 那么是什么泛型统配符呢,我没有找到定义,所以我自己给它下了个定义。泛型通配符是在泛型的使用中,用来表示对泛型类型进行类型范围限定的特殊符号。这里用通配符就是为了表明要输入的类型要在一定范围之内,说的通俗一些其实就是一个类型取值范围,而最大值是Object这是确定的。

<二>泛型通配符的分类

1. <?>:无限通配符,可以在?中放入里放入任何引用数据类型,用来接收任意引用数据类型。

2. <? extends E>:这个表明的是通配符的上界,通俗讲就如同取值范围中的负无穷到E,即小于等于E的所有类型,
因为E是最大的类型(最大可以达到Object),表明可以输入所有的E的子类和E,等下会进行细致的讲解。

3. <? super E>:这个表明的是通配符的下界,通俗讲其取值范围就是E到最大值Object(因为Object是所有类的基类),就是大于等于E,小于等于Object类。

  • 注意:这里能制定上界或者下界,但是不能同时制定,然后<? extends E>中的extends不一定表示类与类的继承还可以表示实现的关系,然后通配符一般是用在方法的形参声明和方法调用上,无法用于定义类和接口中。

二、泛型通配符讲解


<一>通配符的使用以及代码演示

      1.无限通配符<?>的使用:可以传入任何引用数据类型

A 在调用方法时使用?通配符的过程中无法使用add方法。

原因分析:因为通配符?代表任意的数据类型,但是当我们调用的时候或者用在方法的声明上,其实这个时候还是没有给?通配符一个指定的引用数据类型,那么Java出于安全起见,就不可能允许添加元素。

B 以上的add方法虽然无法调用,add(null)是例外。

原因分析:因为null可以给任意引用数据类型赋值,代表任意引用数据,但是很容易引起NullPointerException。

C 注意使用List<?>和List<Object>当作形参时的作用不能等同,比如当传入List<Integer>时List<?>可以接收,但是List<Object>无法接收。

原因分析:因为?代表任何参数类型可以接收,但是List<Object>中虽然Object是所有子类的父类,但是List<Object>不是List<Integer>的父类,List<Object>是ArrayList<Object>等类的父类,这就是为什么泛型前后要一致的原因,从数组的角度去理解集合就比如Object[ ] arr不是Integer[ ] arr1的父类。

  1. package com.test;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.List;
  5.  
  6. public class InterviewList {
  7.  
  8. public static void main(String[] args) {
  9. // ArrayList<Object> list2 = new ArrayList<Integer>();// 泛型前后要一致
  10. ArrayList<?> list = new ArrayList<Integer>();
  11. // list.add("122");// 注意此处无法添加"122"字符串,但是可以添加null,因为null可以给任何引用数据类型赋值
  12. list.add(null);// 添加null没有报错
  13. Object o = list.get(0);
  14. System.out.println(o);
  15.  
  16. ArrayList<Integer> list1 = new ArrayList<Integer>();
  17. list1.add(34);
  18. // Error:(18, 24) java: 不兼容的类型: java.util.ArrayList<java.lang.Integer>无法转换为java.util.List<java.lang.Object>
  19. // 报错了,原因因为这里要接收的是List<Object>,那么可以接收的有ArrayList<Object>或者List<Object>等与List<Object>有继承关系的集合,但是无法接收ArrayList<Integer>,因为和List<Object>没有继承关系,写成List<?>才能使用
  20. // printCollection1(list1);
  21.  
  22. printCollection(list1);// ? 通配符可以正常接收
  23. }
  24.  
  25. public static void printCollection(List<?> col) {//此方法使用了无限通配符
  26. for (Object object : col) {
  27. System.out.println(object);
  28. }
  29. }
  30.  
  31. public static void printCollection1(List<Object> col) {//此方法没有使用泛型通配符
  32. for (Object object : col) {
  33. System.out.println(object);
  34. }
  35. }
  36. }

      2.上界通配符<? extends E>的使用:可以传入E和E的子类

A <? extends E>作为形参时例如List<? extends E>可以使用集合的get方法来获取E或者E类型本身的元素。

原因分析:当我们用get方法时我们其实是在获取集合里内部的元素,但是我们的集合的数据类型还没有确定,但是我们可以获得一些明确的已知条件,那就是在<? extends E>中最大的类型是E,而且这个E最大是Object,所以我们可以利用这一点,那么我们就可以清楚地了解到该集合里面的获取的元素肯定是E或者Object的子类,他们的范围肯定小于E或者Object,那么我们就可以用Object和E这两个范围比集合里面的元素大的类去接收集合里面的元素。(注:可能略显啰嗦但是我就是想解释清楚。)

B 在使用上界通配符时,无法调用add方法来添加非null的元素。

原因分析:由于上面已经说得很清楚了,<? extends E>作为形参时例如List<? extends E>这时最大类型是E和Object,但是我们不清楚最小的类型是什么,因为此时?这个通配符没有被赋值,我们调用add方法是要添加集合元素或者集合元素的子类,但是我们没法明确肯定该集合元素类型,或者比该集合元素范围更小的子类,那么Java就不会允许添加元素。

  1. public class WildCardTest2 {
  2.  
  3. public static void main(String[] args) {
  4. ArrayList<? extends Number> list = new ArrayList<>();
  5. // list.add(3);报错了,无法添加非null元素
  6. list.add(null);//没有报错
  7. Object o = list.get(0);//用Object接收没有报错
  8. Number n = list.get(0);//用Number接收没有报错
  9. }
  10.  
  11. }

       3.下界通配符<? super E>的使用:可以传入E或者E的父类

A 在使用下界通配符时,无法使用get方法获取Object以外的元素,或者需要向下转型,但是可能出现ClassCastException的异常。

原因分析:上界通配符,在使用get方法的时候,此时类型没有明确还是问号?我们只能明确其最大父类或者接口时,我们才能接收,但是我们只能明白<? super E>作为形参时例如List<? super E>时,只能明确Object是最大父类,其他的一概不知,所以只能Object o = list.get(0)。

B 可以使用集合的add方法添加E或者E的子类。

原因分析:上界通配符已经解释很清楚了,add方法添加元素时,?类型不确定就要明确该?类型的最小子类,只要比可能存在的最小子类或者子接口小的任意引用数据类型的对象,我们都可以将其添加,而下界通配符<? super E>当作形参时例如List<? super E>,此时E就是最小子类,此时add方法可以添加E或者E的子类。

  1. public class WildCardTest3 {
  2.  
  3. public static void main(String[] args) {
  4. ArrayList<? super Number> list = new ArrayList<>();
  5. list.add(3);//没有报错,自动装箱成Integer,Number的子类
  6. list.add(3.4F);//没有报错,自动装箱成Float,Number的子类
  7. list.add(32L);//没有报错,自动装箱成Long,Number的子类
  8. Object o = list.get(1);
  9. // Integer i = list.get(0);//报错了,无法用Integer接收
  10. }
  11.  
  12. }

<二>对PECS原则的解读

      1.什么是PECS原则?

            PECS是Producer Extends Consumer Super的递归缩写,是Java中使用泛型通配符的原则。

    2.阿里巴巴的通配符使用规约


泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法,而 < ? super T> 不能使用 get 方法,做为接口调用赋值时易出错。
说明:扩展说一下 PECS<Producer Extends Consumer Super> 原则:第一、频繁往外读取内容的,适合用<? extends E>。第二、经常往里插入的,适合用<? super E> 。

      

      3.对PECS原则的简单解读

字面意思是生产者要被继承要被当作上界通配符<? extends E>的上界E,消费者要继承其他类要被当成下界通配符<? super E>的下界E,再借助下阿里巴巴的泛型开发规约去理解下,应该就是当这个被传入的类型需要进行很多get操作获取数据的话,那么请使用上界通配符这时这个上界就如同生产者一样,因为它能被不断get到,而当需要不断进行add方法添加数据的话,请使用下界通配符这时这个下界就如同消费者一样,因为它不断地索取,因为我们在不断地add元素给它。

对Java通配符的个人理解(以集合为例)的更多相关文章

  1. java基础笔记(9)----集合之list集合

    集合 对于集合的理解,集合是一个容器,用于存储和管理其它对象的对象 集合,首先了解所有集合的父接口----collection 特点:存储任意object元素 方法 boolean add(Objec ...

  2. Java基础学习笔记十五 集合、迭代器、泛型

    Collection 集合,集合是java中提供的一种容器,可以用来存储多个数据. 在前面的学习中,我们知道数据多了,可以使用数组存放或者使用ArrayList集合进行存放数据.那么,集合和数组既然都 ...

  3. Java常用排序算法及性能测试集合

    测试报告: Array length: 20000 bubbleSort : 573 ms bubbleSortAdvanced : 596 ms bubbleSortAdvanced2 : 583 ...

  4. Java中hashcode的理解

    Java中hashcode的理解 原文链接http://blog.csdn.net/chinayuan/article/details/3345559 怎样理解hashCode的作用: 以 java. ...

  5. 深入Java源码剖析之Set集合

    Java的集合类由Collection接口和Map接口派生,其中: List代表有序集合,元素有序且可重复 Set代表无序集合,元素无序且不可重复 Map集合存储键值对 那么本篇文章将从源码角度讨论一 ...

  6. Java创建List、Map等集合对象的同时进行赋值操作

    title: Java创建List.Map等集合对象的同时进行赋值操作 date: 2019-11-28 23:25:47 tags: JavaSE categories: JavaSE 问题简介   ...

  7. Java语言的个人理解

    Java语言的个人理解(比价深层次吧) 大四的生活确实十分的奢靡,不锻炼,不读书,几乎就是当一天和尚撞一天钟的生活,太颓废了,还好自己不是这个样子,不过身体确实差了很多,昨天跑了一圈内环(4KM),今 ...

  8. Java常量字符串String理解

    Java常量字符串String理解 以前关于String的理解仅限于三点:1.String 是final类,不可继承2.String 类比较字符串相等时时不能用“ == ”,只能用  "eq ...

  9. java的final变量理解

    java的final变量理解 final基本类型是数值不能改变 final对象是引用不能改变,对象其自身是可以被修改的.

随机推荐

  1. 【LOJ】#2090. 「ZJOI2016」旅行者

    题解 每次按较长边把矩形分成两半,找一个中间轴,轴上的每个点跑一边最短路更新所有的答案 然后把矩形分成两半,递归下去 代码 #include <bits/stdc++.h> #define ...

  2. echarts地图定时切换散点及多图表级联联动

    本文目录 1. 摘要 2.引入ECharts以及地图相关json 3. 界面布局 4. js实现图形布局 5.定时循环jquery实现 6. 总结 1.  摘要 最近做项目遇到个统计相关需求,一个页面 ...

  3. jenkins自动构建部署

    环境 centos7   tomcat8.5.37   maven3.3.9   jdk8   git1.8.3.1 安装jdk,tomcat,maven,git(环境变量,配置文件什么的自行百度) ...

  4. 墨者学院靶场之PHP代码分析溯源(第1题)

    申请靶场环境后 点开题目 一段奇怪的PHP代码 因为我自己电脑重装了win10系统,php+apache+mysql的环境本地主机觉得没必要弄了,于是我们用在线的PHP编码(百度一下到处都是) 复制进 ...

  5. 解决apache上访问 cgi脚本时总是在网页中显示出脚本的源代码而不是执行结果的问题

    apache是支持cgi脚本的,但是需要保证四个条件: 1.放置cgi脚本的文件夹本身需要对apache服务器这个用户(一般默认用户名是www,linux下的用户机制请自行百度)开放x(即可执行)权限 ...

  6. Intel Code Challenge Elimination Round (Div.1 + Div.2, combined) C. Destroying Array 带权并查集

    C. Destroying Array 题目连接: http://codeforces.com/contest/722/problem/C Description You are given an a ...

  7. ELASTIC的备份与恢复

    前言 elasticsearch官方并没有提供合适的备份工具,然而生产场景中备份却是的确需要的. 本文介绍了使用自己写的php脚本以及第三方工具来进行索引的备份,恢复以及删除等操作. 全量备份 ela ...

  8. python使用递归实现一个分形图形

    代码如下: import turtle def main(): t = turtle.Turtle() t.hideturtle() t.speed(10) level = 12 fract(t,-8 ...

  9. java native方法与JNI实现

    native方法定义: 简单地讲,一个Native Method就是一个java调用非java代码的接口.一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如 ...

  10. 使用UltraISO制作Windows 10启动U盘

    1.从官方网站下载制作工具UltraISO:http://cn.ultraiso.net/uiso9_cn.exe 这是个试用版,但也足够用一次了. 2.在电脑上插入一块U盘,容量最好不少于8GB,接 ...