写点什么

泛型由入门到精通(2)

  • 2022-11-14
    北京
  • 本文字数:5370 字

    阅读完需:约 18 分钟

泛型由入门到精通(2)

Hi,小伙伴你好~欢迎进入泛型第二节内容的学习,在学习之前友情提醒一下:学习泛型需要小伙伴们具备一定的 javaSE 基础,如果之前小伙伴们没有接触过 java,大家可以移步到千锋北京 java 好程序员的 javaSE 课程进行学习。


没看过第一节内容的朋友可以点击博主首页查看第一关内容哦~

第二关 泛型的横空出世

  1. 为什么引入泛型/引入泛型的必要性


​ 来,我们深入分析下上述代码运行时出现错误的主要原因:


​ ArrayList 的本质是一个 Object 数组 Object[] elementArr,这种设计虽然体现出来了泛型的思想(泛型:泛


指任意类型),但是有以下问题:


  1. ArrayList 实例化之后,可以随意添加任意类型的对象(Obeject 是任意引用类型的基类)。

  2. 获取元素的前提是:需要提前知道列表元素的类型。

  3. 获取列表元素时然后进行操作,都需要进行显式类型转换,容易发生类型转换出错的问题。


​ 由于早期我们开发者经常操作集合对象,所以频繁的出现运行期间异常问题,这种问题困扰了很多人,SUN


官方的设计师们也下定决心解决这个问题,这些大佬们在想: 如果集合能和数组一样,在定义时就指定好类


型,这样就不会出现运行期间异常问题了,也就规避了很多安全隐患。


所以 SUN 官方提出了泛型技术来解决操作集合中存在的这些问题。


  1. 泛型的引入时机


SUN 官方在推出 jdk1.5 版本时,就对这样的问题提出了解决方案: 泛型


泛型的由来:通过 Object 转型问题引入 ,早期的 Object 类型可以接收任意的对象类型,泛型其实指的就是任


意的对象类型,但是在实际的使用中,会有类型转换的问题。也就存在这隐患,所以 Java 提供了泛型来解决这


个安全问题。


所以后期 SUN 官方的这些大佬们,对泛型的设计核心思想就两条:


  1. 在定义时,泛型可以指任意的对象类型。

  2. 在使用时,必须明确泛型的具体对象类型。


来我们看一下,SUN 官方 jdk1.5 之后,对 List 集合的改造:


/**  * @param <E> the type of elements in this list: 定义List时,E可以指代任意对象的类型 */public interface List<E> extends Collection<E> {    ----------}
复制代码


下面我们看一下 List 的使用:


public class GenericsDemo {    public static void main(String[] args) {
//1.创建一个List对象:指定泛型为String List<String> list = new ArrayList<String>(); //2.向List中添加数据:必须添加String类型的数据 list.add("corn"); list.add("java"); //3.遍历集合 for (int i = 0; i <list.size() ; i++) { //4.把集合中的每个元素转成String类型 String ele = (String) list.get(i); //5.打印-测试结果 System.out.println("元素的值:"+ele); } }}
复制代码


那么如果我们使用 List<String> list 添加其它类型的数据呢?


可以看到下图:不允许添加除 String 类型以外的,其它类型的数据,这样后期操作就不会有问题了。



小伙伴,我们来简单的总结一下:


虽然泛型在定义时可以表示任意对象的类型,但是我们在使用是,必须明确泛型指代的具体类型,那么


就这么一点小小的改动带来的确实本质性的变化: 一劳永逸,体现了泛型的通用性,规避了很多安全问题。


  1. 泛型能做的哪些事


经过我们刚才的"一顿分析与操作",小伙伴应该基本清楚了泛型能够解决那些问题了。


jdk1.5 之后加入泛型,主要是为了解决类型转换的安全隐患,具体体现如下:


  1. 解决泛型对象实例化之后,可以随意添加任何类型的对象的问题。

  2. 解决获取泛型元素前,需要提前确定元素的类型的问题。

  3. 解决获取元素时,需要进行显式类型转换的问题。

  4. 解决容易出现类型转换出错的问题。


那么小伙伴们,我们通过下面代码来细细品味一番:


public class GenericsDemo2 {    public static void main(String[] args) {
//1.创建一个泛型为String的集合: 解决泛型对象实例化之后,可以随意添加任何类型的对象的问题。 List&lt;String&gt; list = new ArrayList&lt;String&gt;(); //2.向List中添加String类型的数据:解决获取泛型元素前,需要提前确定元素的类型的问题。。 list.add(&quot;corn&quot;); list.add(&quot;java&quot;); //list.add(66);//报编译错误:不能添加int型的数据 //3.遍历集合 for (int i = 0; i &lt;list.size() ; i++) { //4.把集合中的每个元素:解决获取元素时,需要进行显式类型转换的问题。 String ele = list.get(i);// 不需要类型强转:解决容易出现类型转换出错的问题。 //5.打印-测试结果 System.out.println(&quot;元素的值:&quot;+ele); } }}
复制代码


其实看完这些代码后,想必小伙伴心中都有了一个明确的答案:


之前我们没有使用泛型操作集合,可以添加任意类型的数据,在后期运行代码时,进行类型转换就会出问题。


如果我们使用了泛型,那么在添加数据时,如果添加的数据类型不对,编译就会出问题,更不用说后期运行了。


所以我们用一句话总结泛型:


泛型主要是将运行期间的异常问题,转移到编译期间来体现,避免了类型强制转换的问题。


闯关练习


需求:创建一个指定泛型为 Integer 的 Set 集合,添加数字 1 到 100,取出里面的偶数。


答案:


public class GenericsDemo3 {    public static void main(String[] args) {        //1.创建一个泛型为Integer的集合        Set&lt;Integer&gt; numbers = new HashSet&lt;Integer&gt;();        //2.向set集合中添加数字:1-100        for (int i = 1; i &lt;=100 ; i++) {            numbers.add(i);        }        //3.遍历set集合,获取里面的偶数并打印        for (Integer elementData : numbers) {        //4.条件判断:            if(elementData%2==0){        //5.打印测试:                System.out.println(&quot;偶数是:&quot;+elementData);            }        }    }}
复制代码



第三关 领略泛型之美

走到这里,相信小伙伴们已经知道泛型的基本使用了,那么泛型之美到底体现在什么地方呢? 我们一起揭开这


位“美人”的神秘面纱。


泛型之美具体体现如下三个方面:


  1. 编译期间类型检查。


如下代码:


Set&lt;Integer&gt; set = new HashSet&lt;Integer&gt;();//指定set集合的泛型为Integer set.add(100);//添加数字 set.add(&quot;java&quot;);//报编译期间异常:集合的泛型为Integer,不能添加String的字符串
复制代码


<u>来,小伙们我们来分析下这段代码:</u>


<u>1.我们创建了一个带 Integer 泛型的 Set 集合对象,指定 Set 集合只能添加 Integer 类型的数据</u>


<u>2.如果添加其它类型的数据,java 的编译器就会检查,并且提示错误信息,就好像老师检查作业一样,在出错的地方标记红线。</u>


<u>在编译过程,java 的编译器都会自动检查添加的数据与我们指定的泛型是否一致,以后再也不怕添加错误的数据了,很赞吧。</u>


  1. 避免强转类型转换


如下代码:


 //1.定义没有泛型的方法: 创建对象    public static Object createObj(Object obj){        return obj;    }    //2.定义有泛型的方法: 创建对象    public static &lt;T&gt; T createT(T t){        return  t;    }    //3.测试    public static void main(String[] args) {         Date date1 = (Date) createObj(new Date());//没有泛型的方法: 类型强转         Date  date2 = createT(new Date());//有泛型的方法:不需要类型强转    }
复制代码


<u>为了加强对比,我们定义了两个方法,一个带泛型,一个不带泛型。</u>


<u>根据测试的结果,显而易见:</u>


  1. <u>带泛型的方法在创建对象时,传入什么类型,就得到什么类型的对象</u>

  2. <u>不带泛型的方法,根据传入的类型获取对象时,需要强制转换一下。</u>


<u>所以我们在开发中,特别是在定义创建对象的方法时,一般都使用泛型来进行定义,从而避免后期的类型强转。</u>


  1. 可读性和灵活性


如下代码:


public class GenericDemo6 {   public static void main(String[] args) {       //1.带泛型的map集合        Map&lt;String,Student&gt; map = new HashMap&lt;String,Student&gt;();//使用泛型:可读性强        map.put(&quot;01号&quot;,new Student(&quot;乔丹&quot;,23));//01号: 学生乔丹        map.put(&quot;02号&quot;,new Student(&quot;皮蓬&quot;,36));//02号: 学生皮蓬    //2.不带泛型的map集合        Map map2 = new HashMap();//没有泛型:可读性差        map2.put(new Student(&quot;乔丹&quot;,23),&quot;01号&quot;);//学生在前:编号在后        map2.put(&quot;02号&quot;,new Student(&quot;皮蓬&quot;,36));//学生在后:编号在前    }}class  Student{    String username;    Integer age;
public Student(String username, Integer age) { this.username = username; this.age = age; } public Student() { }}
复制代码


<u>通过这段代码,我们可以看到泛型能规范代码的书写,让我们的代码可读性更强,便于后期我们对数据的处理。</u>


<u>如果我们使用不带泛型的 map 集合保存数据,那么 map 的数据保存很混乱,不便于后期对数据进行处理。</u>


闯关练习


请描述下列哪些选项是泛型的优点:


A: 泛型可以避免类型强转


B: 泛型可以在编译期间进行检查


C: 泛型可以提高代码的可读性


D: 泛型可以提高代码的灵活性


答案:ABCD



第四关 泛型之飞天遁地

​ 小伙伴们,泛型我们已经学到第四关了,是不是感觉泛型很强大啊!但是不要得意,泛型也有“软肋”,


泛型在使用时,有时并不能随意指定任意类型,也就是说,泛型在使用时具有类型限制,具体体现为泛型之飞天 ;


就是泛型的上限,泛型之遁地 ; 就是泛型的下限。


​ 来吧,我们一起来看一下泛型的上限和下限。


在泛型上限和下限的分析过程中,我们会看到 ?符号经常出现,在这里 ?表示通配符,表示任意类型,小伙伴们需要注意一下喔。


1.泛型通配符


切记: ?表示通配符,表示任意的类型。


如下代码:


ArrayList&lt;?&gt; list1 ;        ArrayList&lt;String&gt; list2 = new ArrayList&lt;String&gt;();        ArrayList&lt;Integer&gt; list3 = new ArrayList&lt;Integer&gt;();        list1 = list2;// ?  表示 String类型        list1 = list3;// ? 表示 Integer类型                  
复制代码


<u>在这里,小伙伴一定要注意: ?这个符号神通广大,可以用来表示任意的泛型类型。</u>


<u>上述代码中创建了一个带?泛型 list1。</u>


  1. <u>如果把创建带 String 泛型的 list2 赋值为 list1, 那么此时?表示 String 类型</u>

  2. <u>如果把创建带 Integer 泛型的 list3 赋值为 list1, 那么此时?表示 Integer 类型</u>


<u>讲到这里,小伙伴基本上明白了?符号的含义了,通常 ?会出现在泛型的上限和下限定义中使用中,我们接下来看看?这个通配符,</u>


<u>在泛型的上限和下限过程怎么使用的,let`s go。</u>


2.泛型的上限


定义的基本语法:定义基本语法:类名或者接口名<? extends T>,那么此时 ?表示 T 类型,或者 T 的子类型


我们通过一段 List 接口的源码来分析一下泛型的上限,源码如下:


public interface List&lt;E&gt; extends Collection&lt;E&gt; {      /**      *  方法作用:将一个集合 添加到 List中,      * 这时集合的类型 ? extends  E ,这里? 表示是E的子类类型,?的上限不能超过E      */     boolean addAll(Collection&lt;? extends E&gt; c);    ----}
复制代码


代码演示:


 List&lt;Number&gt; list = new ArrayList&lt;Number&gt;();//创建一个集合: 泛型为Number List&lt;Number&gt; son1 = new ArrayList&lt;Number&gt;() ; List&lt;Long&gt; son2 = new ArrayList&lt;Long&gt;() ; List&lt;String&gt; str_list = new ArrayList&lt;String&gt;(); list.addAll(son1);// ? 表示 Number,上限是Number list.addAll(son2);// ? 表示Long,Long是Number的子类 // list.addAll(str_list);//编译报错: 原因 String 和 Number 没有继承关系
复制代码


泛型的上限,就是在使用泛型时,不能超过 extends 后面定义的类型,所以大家在使用时,一定要注意类型的子、父级关系。


3.泛型的下限


定义基本语法: 类名或者接口名<? super T>,那么此时 ?表示 T 类型,或者 T 的父类型


刚刚看了泛型的上限,那么下限对于小伙伴来说就会简单很多了。


我们还是通过 List 集合来分析泛型的下限。


比如:


List&lt;? super Integer&gt; list = new ArrayList&lt;Integer&gt;();//创建一个集合list = new ArrayList&lt;Integer&gt;();// ? 表示Integer  list = new ArrayList&lt;Number&gt;();// ? 表示Number: Number是Integer的父类 list = new ArrayList&lt;Object&gt;();// ? 表示Object: Object是Integer的父类 // list = new ArrayList&lt;String&gt;();//编译报错: ? 表示String: String和Integer没有关系
复制代码


泛型的下限,就是在使用泛型时,必须高于 super 后面的定义的类型,所以大家在使用时,一定要注意类型的父、子级关系


闯关练习


请描述 ? 通配符 在泛型中的具体使用(多选)。


A:在定义泛型的上限时: 可以使用?通配符 表示 泛型的子类


B:在定义泛型的下限时: 可以使用?通配符 表示 泛型的父类


C:在定义泛型时,可以不用 ?通配符


D: 以上说法都不对


答案:


ABC




第三节及之后的内容会稍后发布哦~


感兴趣就点个关注吧~

发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2022-10-21 加入

还未添加个人简介

评论

发布
暂无评论
泛型由入门到精通(2)_Java_好程序员IT教育_InfoQ写作社区