写点什么

泛型由入门到精通

  • 2022-11-11
    北京
  • 本文字数:5401 字

    阅读完需:约 18 分钟

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

在正式开始学习之前,我们先来看一段经常书写的代码,分析一下代码存在那些问题?

代码如下:

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


运行代码,会报如下图的异常:



那么小伙们,我们来分析一下原因,到底是因为什么报这个异常呢?

在代码中我们定义了一个 List 类型的集合,先向其中加入了两个 String 类型的值,随后加入一个 Integer 类型的

值。这是完全允许的,因为此时 list 默认的类型为 Object 类型,所以在代码编译期间没有任何问题。

但是在运行代码时,<u>由于 list 集合中既有 String 类型的值,又有 Integer 类型的值,致使 list 集合无法区分值是什么类型,很容易出现上图中</u>

<u>的错误。</u>因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此导致此类错误编码过程中不易发现。

分析完了,小伙们现在明白了吧,通过分析我们发现上述代码主要存在两个问题

  1. 当我们将数据存入集合时,集合不会记住数据的类型,默认数据类型都是 Object。

  2. 当我们遍历集合中的数据时,人为进行强制类型转换,很容易报“java.lang.ClassCastException”。强制类型转换异常

那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就

不会出现“java.lang.ClassCastException”异常呢?

答案就是使用泛型。

那么什么是泛型呢?

第一关 让我们一起走入泛型

1.泛型的概述

1.1 什么是泛型

​ 泛型,泛指任意类型,可以应用在接口上,类上,变量上,方法上,以及方法的参数中。

  • 百度百科介绍:

    泛型是 jdk1.5 的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

    在 jdk1.5 之前,没有泛型的情况的下,通过对类型 Object 的引用来实现参数的**“任意化”“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况 ,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患**。

    泛型的好处:使用泛型,首先可以通过 IDE 进行代码类型初步检查,然后在编译阶段进行编译类型检查,以保

    证类型转换的安全性;并且所有的强制转换都是自动和隐式的,可以提高代码的重用率。

  • 个人理解:

    简单来说:泛型,即“参数化类型”,那么类型“参数化”到底怎么理解呢?

    顾名思义,类型“参数化”就是将类型由原来的具体类型,变成参数化的“类型”,有点类似于方法中的变量参

    数,不过此时是类型定义成参数形式(你可以理解为类型形参),然后在使用时传入具体的类型(也就是类型实参)。为什么这样操作呢?因为它能让类型"参数化",也就是在不创建新的类型的情况下,通过泛型可以指定不同类型来控制形参具体限制的类型。

  • 总结:

    泛型介绍完了, 小伙伴看完上述解释后,能理解泛型是什么了吗?我们可以用两句话来概括一下:

    泛型在声明时,用标记符表示,仅仅作为“参数化的类型”,可以理解为形式参数。

    比如:

    //定义List集合时,用标记符E表示任意类型,E可以理解为形式参数,没有具体的类型值 public interface List&lt;E&gt; extends Collection&lt;E&gt; { ---------- }


  • 2.泛型在使用时,需要指定具体的类型,也就是类型实际的参数。

    比如:

    //在使用List集合时,需要确定E的具体类型,String可以理解为具体的类型,也就是实际的参数值 List&lt;String&gt; list = new ArrayList&lt;String&gt;();




1.2 常用的泛型标记符

  • E - Element (集合使用,因集合中存放元素)

  • T - Type(Java 类)

  • K - Key(键) V - Value(值)

  • N - Number(数值类型)

  • ? - 表示不确定的 java 类型

  • S、U、V - 2nd、3rd、4th types

你可能会有疑问,弄这么多标识符干嘛,直接使用万能的 Object 难道不香么?我们知道 Object 是所有类的基类

(任何类如果没有指明其继承类,都默认继承于 Object 类),因此任何类的对象都可以设置 Object 的引用,只不

过在使用的时候可能要类型强制转换。但是如果设置了泛型 E、T 等这些标识符,那么在实际使用之前类型就已经

确定,因此不再需要类型强制转换

2.泛型的语法使用

2.1 泛型在集合中的使用

  • 单列集合中 List 中

public interface List&lt;E&gt; extends Collection&lt;E&gt; {    ----    &lt;T&gt; T[] toArray(T[] a);    ----     }
复制代码


  • 双列集合 Map 中

public class HashMap&lt;K,V&gt; extends AbstractMap&lt;K,V&gt;    implements Map&lt;K,V&gt;, Cloneable, Serializable {      ---      V get(Object key){---};      V put(K key, V value){---};      V remove(Object key){---};      void putAll(Map&lt;? extends K, ? extends V&gt; m){---};      -----    }
复制代码


小伙们可以看到:List,HashMap 的源码,在声明集合时或者定义方法时,使用采用尖括号内加占位符的形式 ,这里的占位符就是我们上面说的泛型标记符,泛型标记符号 E,K,V,T 等用来表示任意类型(E,K,V,T 也就是“泛型形参”,在实例化集合对象时需要明确的具体的类型(也就是“泛型的实际参数”))。

通过观察集合的源码,那么我们自己也可以定义泛型接口,泛型类以及泛型方法,下面我们一起操作一下吧。

2.2 声明泛型接口

泛型应用于接口。例如生成器(GeneratorType),这是一种专门负责创建对象的类。当使用生成器创建新的对象时,它不需要任何参数,也就是说生成器无需额外的信息就知道如何创建新对象。

一般而言,一个生成器只定义一个方法,该方法用以产生新的对象。

/** * 定义一个泛型接口:生成任意对象 * @param &lt;T&gt;: 泛型形式参数,可以是任意类型 */public interface GeneratorType&lt;T&gt; {    T create();}
/** * 测试泛型接口 */class DemoGeneratorType{ public static void main(String[] args) { //1.使用生成器:创建random对象 GeneratorType&lt;Random&gt; gt= new GeneratorType&lt;Random&gt;() { @Override public Random create() { return new Random(); } }; //2.使用 GeneratorType:创建对象 Random random = gt.create(); }}
复制代码


<u>来,小伙伴们,我们一起分析下上面的代码:</u>

  1. <u>我们声明了一个泛型接口 GeneratorType<T>,目的用来生成任意类型的对象,在这里 T 可以表示任意类型。</u>

  2. <u>我们在测试类中,通过 GeneratorType 创建对象时,可以传递任意类型。</u>

    <u>比如 GeneratorType<Random>,那么就可以生成 Random 对象了</u>

<u>注意: 在这里,我们通过匿名内部类的方式创建了 Random 对象,这种写法大家要慢慢熟悉喔。</u>

2.3 声明泛型类

泛型应用于类上面。例如订单类(Order),这是一个专门负责封装订单里面商品的类,当我们购物生成订单时,

订单里面可以包含任何商品信息。

请注意,在类上定义的泛型,在类的变量、方法的参数以及方法中同样也能使用(静态方法除外)。

/** * 定义一个订单类:封装任意类型的商品信息 * @param &lt;T&gt; */public class Order&lt;T&gt; {    private  T t ;//在变量中使用: T表示任意商品类型    public T get(){//在普通方法中使用:T表示任意商品类型        return  t;    }    public  void set(T t){//在方法的参数使用: T表示任意类型        this.t = t;    }    //测试:    public static void main(String[] args) {        Order&lt;Phone&gt; order = new Order&lt;Phone&gt;();//创建订单对象:封装Phone商品        order.set(new Phone(&quot;华为Mate20&quot;,3899.0));        System.out.println(&quot;商品名称:&quot;+order.get().getPhoneName());    }}//定义手机商品类class Phone{    private  String phoneName;    private  Double phonePrice;
public Phone(String phoneName, Double phonePrice) { this.phoneName = phoneName; this.phonePrice = phonePrice; }
public Phone() { }
public String getPhoneName() { return phoneName; }
public void setPhoneName(String phoneName) { this.phoneName = phoneName; }
public Double getPhonePrice() { return phonePrice; }
public void setPhonePrice(Double phonePrice) { this.phonePrice = phonePrice; }}
复制代码


<u>ok,泛型类我们声明完成了,大家看一下是不是和我们声明泛型接口很相似啊,确实是一样的。</u>

声明的语法就是:类名<T>,在这里 T 可以表示任意类型。

<u>小伙伴也可以看到,我们定义了一个带泛型的 Order 类,在我们创建订单对象时,可以传入任意类型的商品对象,使我们的操作更加灵活</u>

2.4 声明泛型方法

泛型应用于方法上面。前面说过在泛型类上定义的泛型,在类的方法中也能使用(静态方法除外)。但是有

的时候我们只想在某个方法上使用泛型,而不是整个类,这也是被允许的,下面我和小伙们一起来体验一下。

比如 FactoryBean 工厂类,通过泛型方法,创建任意类型的对象。

package cn.qf;/** * 定义一个工厂Bean: */public class FactoryBean {    /*      定义不带泛型的方法     */    public static Object  createObject0(String className) throws Exception{        return  Class.forName(className).newInstance();    }    /*     定义一个普通的泛型方法:className表示类的全路径    */    public &lt;T&gt; T createObject1(String className) throws Exception{        return (T) Class.forName(className).newInstance();    }    /*    定义一个静态的泛型方法:className表示类的全路径    */    public static  &lt;T&gt; T createObject2(String className)throws Exception{        return (E) Class.forName(className).newInstance();    }	//测试:    public static void main(String[] args) throws Exception {        //创建一个Phone对象 : 不使用泛型方法,需要类型强转        Phone p1 = (Phone) FactoryBean.createObject0(&quot;cn.qf.Phone&quot;);        //创建一个Phone对象 :泛型方法,不需要类型强转        Phone p2 = FactoryBean.createObject2(&quot;cn.qf.Phone&quot;);    }}
class Phone{ private String phoneName; private Double phonePrice; ----}
复制代码


<u>在这里我们使用工厂模式来创建对象,为了在我们获取对象时,不用类型强转,我们也使用了泛型。</u>

<u>小伙伴通过代码可以看到,不使用泛型的方法,在获取对象时,需要类型强转(可能会引起类型强转异常)。</u>

<u>在使用泛型方法获取对象时,不需要类型强转(可以避免引起类型强转异常)。</u>

2.5 泛型方法、泛型接口、泛型类小结

从上面的介绍小伙伴也看到了,泛型类的好处就是在泛型类上定义的泛型,在类的方法中也能使用(普通静态方法除外)。而泛型方法的

最大优点就是能独立于类,不受类是否是泛型类的限制。因此当你考虑使用泛型的时候,优先考虑定义泛型方法。如果非要定义泛型类,

个人建议通过使用泛型方法来将整个类泛型化,因为这样就不用担心静态方法的事,如果有静态方法那必然是泛型方法。这样就能避免普通

静态方法无法获取泛型类泛型的尴尬局面。

你以为这就把泛型介绍完了吗?并没有,小伙伴们先休息片刻,稍后我们继续喔。

闯关练习

需求:

定义一个泛型类:

  1. 包含与类的泛型一样的变量,

  2. 包含与类的泛型一样的方法,参数也使用泛型

  3. 同时定义一个类的泛型不相同的泛型方法

答案:

/** * 定义泛型类: * @param &lt;T&gt;: 泛型T */public class GenericDemo4&lt;T&gt; {    //1.定义一个与T 一样的变量    private T t;    //2.定义一个与T一样的方法    public  T  test1(T outer){        System.out.println(outer);        return outer;    }    //3.定义一个与T不一样的方法    public &lt;E&gt; E test2(E e){        System.out.println(&quot;自定义泛型的方法:&quot;+e);        return e;    }    //测试:    public static void main(String[] args) {        //1.创建对象:指定T的泛型为 String        GenericDemo4&lt;String&gt;  gt = new  GenericDemo4&lt;String&gt;();        //2.调用 与T 一样的泛型方法        gt.test1(&quot;hello world&quot;);       //3.调用 与T 不一样的泛型方法        gt.test2(10);    }}
复制代码



第二关及之后的关卡会稍后发布哦~

感兴趣就点个关注吧~

用户头像

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

还未添加个人简介

评论

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