写点什么

泛型由入门到精通(3)

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

    阅读完需:约 19 分钟

泛型由入门到精通(3)


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


没看过以往内容的朋友可以点击博主首页查看哦~



第五关 泛型的离奇失踪

小伙们看到这个标题,可能会大吃一惊,我们不是定义好泛型了吗,那么泛型还会突然离奇失踪啊。


我可以很负责的告诉小伙们: 泛型确实会消失!


  1. 泛型的擦除机制(泛型消失)


​ 我们定义的泛型类,泛型方法,只是编译时:规范类型参数的传递,然而在编译成 class 文件后,运行代码时,这时泛型就消失了。


原因就是:


在 JAVA 的虚拟机中并不存在泛型,泛型只是为了完善 java 体系,增加程序员编程的便捷性以及安全性而创建


的一种机制,在 JAVA 虚拟机中对应泛型的都是确定的类型,在编写泛型代码后,java 虚拟机中会把这些泛型参数类型都擦除,用相应的确


定类型来代替,代替的这一动作叫做类型擦除,而用于替代的类型称为原始类型,在类型擦除过程中,一般使用第一个限定的类型来替


换,若无限定,则使用 Object.


下面我们测试一下:


//1.在Demo.java类中: 定义一个泛型的集合List<String> list = new ArrayList<String>();list.add("hello");list.add("java");//2.在编译后的Demo.class文件中,通过反编译查看:泛型消失了List list = new ArrayList();list.add("hello");list.add("java");
复制代码


2.泛型的擦除补偿


​ 正如我们看到的,我们在代码运行时,泛型就消失了,那么如果我们在运行代码时需要确切的知道泛型的具


体类型该怎么办呢?特别是使用 new T(),或者使用 instanceof, 因为这两类操作要知道确切的类型。


我们可以通过泛型擦除后的补偿来满足我们的需求,一般我们会采用 java 中的设计模式来解决这个问题。


  • 方式一:简单工厂 (最简单)

  • 在此方法中,将类型作为参数,以一个万能的工厂类(其中有一个返回具体类型类的实例的泛型方法)用类的

  • newInstance()方法返回参数化类型的实例,如下所示:


    /**     * 定义泛型T     * @param <T>     */    public class GenericDemo9<T> {        //1.定义泛型变量: t        private  T t;        //2.定义方法:获取t        public T getInstance(Class<T> clz){            try {                this.t = clz.newInstance();            } catch (InstantiationException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            }            return  this.t;        }        //3.测试:        public static void main(String[] args) {            GenericDemo9<Date> gd =  new GenericDemo9<Date>();            Date date = gd.getInstance(Date.class);        }
}
复制代码




**缺点:**
**因为class的newInstance()是利用无参构造方法创建对象的,如果该泛型类没有无参构造方法,就会报错**
复制代码


  • 方式二:工厂方法(最灵活)

  • 与简单工厂相比,工厂方法更灵活,同时来解决了简单工厂中没有无参构造方法,不能创建对象的问题。

  • 如下所示:


    //步骤一: 定义工厂接口    /**     * 定义一个泛型接口     * @param <T>     */    public interface GenericFactory<T> {        T create();    }      //步骤二:定义具体创建对象的工厂    /**     * 定义生产汽车的工厂     */    class  CarFactory implements  GenericFactory<Car>{        //1.定义汽车的名称        private String name;        //2. 定义汽车对象        private  Car car;
public CarFactory() { }
public CarFactory(String name) { this.name = name; }
@Override public Car create() { if(name==null){//没有汽车名称:表示使用无参数构造 this.car = new Car(); }else{//有汽车名称:表示使用有参数构造 this.car = new Car(this.name); } return car; } //3.测试 public static void main(String[] args) { GenericFactory<Car> gf = new CarFactory(); Car car = gf.create();//使用无参构造创建对象 GenericFactory<Car> gf2 = new CarFactory("奔驰S500"); Car car2 = gf2.create();//使用有参构造创建对象
} } //步骤三:定义对象的类 //定义一个汽车类 class Car{ private String name; public Car() { } public Car(String name) { this.name = name; } }
复制代码


缺点:代码实现起来麻烦
**优点:创建对象的方式更加灵活**,使用有参和无参构造都能创建对象。
复制代码


  • 模板方法(最简捷)

  • 与工厂方法不同的地方在于:用模板类(抽象类)来控制整个实例化过程的流程,本质就是用模板类控制对象的

  • 创建过程,具体创建对象的实现由模板类的子类去实现,只不过在模板类中需要用工厂方法。

  • 如下所示:


    //1.创建模板类    public abstract class GenericTemplate<T> {        //1.定义泛型变量        private  T t;        //2.定义抽象方法        public  abstract  T create();    }    //2.创建模板类的生成者(实现类)    class CarCreator extends  GenericTemplate<Car> {        //1.定义工厂对象:引入工厂方法        private  CarFactory cf ;
public CarCreator() { this.cf = new CarFactory(); }
public CarCreator(String carName) { this.cf = new CarFactory(carName); } //2.重写模板类的方法 @Override public Car create() { return cf.create(); } }
复制代码


优点:
方式最简捷,因为直接调用具体的生成类即可,我们创建对象时,并看不到模板类的出现。
复制代码


闯关练习


请描述 代码在运行过程中 泛型在擦除后,具体表示为什么类型?(单选)


A: Class 类型


B: T 类型


C: Object 类型


D: Type 类型


答案:C


解析:


在代码运行过程中,泛型会被擦除(也就是泛型会消失),这时泛型的类型通通都会表示为 Object 类型。


因为定义泛型时,可以指定任意类型,比如 List<String>,Set<Number>,所以在泛型擦除后,只有 Object 类型可以表示任意类型。

第六关 泛型和钻石的“神秘关系”

​ 小伙伴看到钻石是不是特别嗨,学泛型还有钻石呢?非也!在这里这个钻石指的是一种符号,我们开发者又称之为钻石操作符


  1. 钻石操作符介绍


​ 钻石操作符是在 java 7 中引入的,钻石操作符其实就是 < > ,可以在里面定义泛型,让代码更易读,但它不能


用于匿名的内部类。


如下所示:


List&lt;Integer&gt; list = new ArrayList&lt;&gt;();// &lt;&gt; 钻石操作符,java 7 以后可以使用//在java7中: 如果使用匿名内部类,&lt;&gt; 钻石操作符不能直接使用public class GenericDemo {    public static void main(String[] args) {        Operation&lt;Integer&gt; intOperation = new Operation&lt;Integer&gt;(1) {            @Override            public void handle() {                System.out.println(content);            }        };        intOperation.handle();    }}//定义一个抽象类:用来创建匿名内部类abstract class Operation&lt;T&gt; {    public T content;    public Operation(T content) {        this.content = content;    }    abstract void handle();}
复制代码


2.钻石操作符之 JDK9 新特性


​ 钻石操作符是在 java 7 中引入的,但它不能用于匿名的内部类。 在 java 9 中可以直接使用


如下所示:


//在java9中: 如果使用匿名内部类,&lt;&gt; 钻石操作符也可以直接使用public class GenericDemo {    public static void main(String[] args) {        Operation&lt;Integer&gt; intOperation = new Operation&lt;Integer&gt;(1) {            @Override            public void handle() {                System.out.println(content);            }        };        intOperation.handle();    }}//定义一个抽象类:用来创建匿名内部类abstract class Operation&lt;T&gt; {    public T content;    public Operation(T content) {        this.content = content;    }    abstract void handle();}
复制代码

第七关 泛型的点点滴滴

走到这里,小伙伴基本上把泛型学完了,是不是很有成就感啊,不要着急走开喔,后面还有些细节需要完善。


下面我们一起把泛型的细节看一下


1.异常中使用泛型


​ <u>由于泛型在使用过程会消失, 消失后泛型具体的类型就会用户 Object 类型来替代,Object 是没有 继承 Exception 或者 Throwable 异</u>


<u>常类的, 所以 在定义异常类时,不能使用泛型。</u>


​ <u>又是因为泛型在使用过程会消失,如果使用泛型来表示异常类型,泛型消失,那么泛型表示的异常就不复存在,就会造成泛型无法捕</u>


<u>获或者处理这种情况。</u>


<u>接下来,小伙们我们一起看如下代码: </u>



//1.自定义带泛型的异常类: 不合法,错误的写法。class MyEx1&lt;T&gt; extends Exception{ public MyEx1(String message) { super(message); }}//2.定义泛型方法,抛出泛型T: 不合法public class GenericDemo { public &lt;T&gt; T doTest( T t){ try { System.out.println(&quot;测试----&quot;); }catch (T t){//错误的写法 throw t;//错发的写法 } return null; }}

//3.定义泛型接口(类也可以): 合法,正确的写法,但不实用(没有实际用途)。class GenericTest&lt;E extends Exception&gt;{ void process() throws E;}
复制代码


通过代码演示,我们会发现在异常使用泛型存在诸多问题,我们来归纳总结下:


1.自定义带泛型的异常类,不合法。


​ 由于代码在运行时,泛型会被擦除,那么 T 会被擦拭成 Object 类,而 Object 类显然不会是 Throwable 的子类,


因此它不符合异常的有关规定,所以 java 编译器不编译这种错误的写法。


2.定义泛型方法:抛出泛型 T,不合法


其实道理和自定义带泛型的异常类的一样,最后 T 会被擦拭成 Object 类,显然不是异常类,无法捕获、亦无法抛出


3.定义泛型接口(类也可以),正确的写法。


因为我们定义了泛型的上限,即使运行时,T 会被擦除,那么 java 编译器依然会把它看成异常,所以不会报错。


但是这种写法,还不如定义非泛型接口或者非泛型类,没有多大实际用途,所以小伙伴知道就可以了。


2.数组与泛型


不能声明参数化类型的数组, 数组可以记住自己的元素类型,不能用普通方法建立一个泛型数组。


(当然 你如果用反射还是可以创建的,用 Array.newInstance。因为在反射时,泛型就消失了)


如下所示:


//定义泛型测试类:class ArrDemo&lt;T&gt;{  public  T[] arr1;//标准写法,后期运行时不会引发问题  public ArrDemo&lt;Integer&gt;[] arr2;//不标准写法,后期运行时可能会引发问题   public static void main(String[] args) {       //1.创建对象       ArrDemo&lt;String&gt; ad = new ArrDemo&lt;String&gt;();      //2.操作数组      String[] str_arr1 = ad.arr1;//在创建对象是T指的是String ,arr1就是String类型的数组。      ArrDemo&lt;Integer&gt;[]   ad_arr3 = ad.arr2;//ad的泛型是String ,新定义的Integer可能会引发问题     }}
复制代码


3.泛型方法


在能够使用泛型方法的时候,尽量避免使整个类泛化。


如下所示:


public class GenericDemo4 {//1.标准: 推荐的写法    //1.定义一个泛型方法:    public &lt;E&gt; E test2(E e){        System.out.println(&quot;自定义泛型的方法:&quot;+e);        return e;    }}
复制代码


4.泛型类型必须是应用类型,不能是基本类


如下所示:


List&lt;String&gt; list2 = new ArrayList&lt;String&gt;();//正确的写法: 引用类型
List&lt;int&gt; list2 = new ArrayList&lt;int&gt;();//错误的写法: 基本类型
复制代码


5.虚拟机中没有泛型,只有普通类和普通方法


因为泛型在编译阶段我们能看到,可以规范我们开发者使用的类型。


但是泛型在编译后的 class 文件,以及最终到 JVM 虚拟机中运行,这是泛型被擦除了,所以在虚拟机中只有


普通类和普通方法。


闯关练习


请选择下面描述正确的选项


A: 泛型可以使用基本类型表示


B: 泛型类中必须定义泛型方法


C: 在异常操作中,通常不使用泛型


D: 泛型类型必须是引用类型


答案:C D

第八关 课程总结

这个小节的内容已经学习完了,小伙伴们是不是感觉收获多多!


刚才我们一起了解了泛型的基本信息和使用,同时完成了几个闯关小练习,小伙伴现在对泛型的感觉如何呢。


现在,我们一起做个总结吧。


  1. 了解泛型是什么 泛型即: "参数化类型",可以把泛型作为"参数", 泛型可以指任意类型,规范 java 代码的书写

  2. 了解泛型的优点 泛型可以进行编译期间进行类型检查 泛型可以避免类型强转

  3. 掌握泛型的定义和使用

  4. ​ 定义泛型接口

  5. ​ 定义泛型类

  6. ​ 定义泛型方法

  7. ​ 在实际开发的场景中能够使用上面定义的泛型

  8. 了解泛型的通配符,以及泛型的上限和下限

  9. ​ 泛型的上限,有时又称之为上边界,指的是泛型不能超过 extends 的类型

  10. ​ 泛型的下限,有时又称之为下边界,指的是泛型得高于 super 的类型

  11. 了解泛型的擦除机制和补偿措施

  12. ​ 泛型在编译后的 class 文件中就消失了。

  13. ​ 泛型在使用时应尽量避免 new T(),或者使用 instanceof, 因为这两类操作要知道确切的类型。

  14. ​ 泛型的补偿措施:简单工厂,工厂方法,模板类,需要小伙们好好学习一下。

  15. 了解什么是钻石操作符

  16. ​ java7 之后,规定< > 尖括号就是钻石操作符,里面的泛型可以省略不写,还是建议不要省略


这就是今天学习到的内容,整体来说,难度呢还是有几分的。


相信你已经掌握了大部分的泛型知识点了。对于还没有理解的知识点。


希望你有时间可以多看看,多敲敲代码,多多练习。


最后希望大家通过这一节泛型的学习,能够对泛型有一个深入的理解,并且能够在实际开发中熟练的运用!




帮助到你的话就点个关注吧~


用户头像

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

还未添加个人简介

评论

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