• 唬人的Java泛型并不难


    泛型

    1. public interface Foo {}
    2. public interface Bar {}
    3. public interface Zar {}

    上面的代码有什么区别?

    泛型初探

    1、为何引入泛型?

    Java 泛型也是一种语法糖,使用泛型可以在代码编译阶段完成类型的转换,避免代码在运行时强制转换而出现ClassCastException的异常。

    网络搜索出来一大堆的名称解释,我们先看英文Generic type,从英文大概也能明白,Generic 这里可以理解为普通的,一般的,或者我们可以说通用的。

    其实可以理解为Java中的一种类型,通用类型。

    Java从1.5的版本就开始支持泛型,不过很多小伙伴对泛型还是模凌两可,今天大概讲讲泛型,基础好的小伙伴,就当复习复习。

    在1.5版本以前

    1. public static void main(String[] args){
    2.     List list = new ArrayList();
    3.     list.add("兔子托尼啊");
    4.     list.add(1234);
    5.     //正常运行
    6.     System.out.println((String)list.get(0));
    7.     //❌运行时报错
    8.     System.out.println((String)list.get(1));
    9. }

    从上面的代码可以看出了,第一句打印不报错,第二句打印会报错的。

    List默认是Object的类型的,向List里面存数据都是没有问题的,但是取数据的时候,必须要要进行类型的转换。

    List集合get数据的时候并不清楚里面存放的什么数据类型,默认取出来的都是Object的类型,如果取数据的时候转换的类型和原始存放存的类型不一样,会报ClassCastException的异常。

    2、引入了泛型

    看代码

    1. List list = new ArrayList();
    2. list.add("兔子托尼啊");
    3. //❌编译时错误
    4. list.add(1234);
    5. //不需要再进行转换了
    6. String str = list.get(0);

    3、泛型带来好处

    • 这在编码的时候就给我们解决了,类型转换的问题,可以放心写代码。

    • 取数据的时候再也不要考虑我前面存的什么类型,我应该转换为什么类型,不怕类型转换报错。

    类型擦除

    上面讲了泛型,泛型虽然带来了好处,但是泛型也带了一个问题叫做类型擦除。

    什么是类型擦除?

    Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。

    Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。

    1. class GenericU {
    2.     public void foo() {
    3.         System.out.println("GenericU.foo()");
    4.     }
    5. }
    6. public class Operater {
    7.     private T obj;
    8.     public Operater(T obj) {
    9.         this.obj = obj;
    10.     }
    11.     public void doIt() {
    12.         //❌报错,提示找不到foo方法
    13.         obj.foo(); 
    14.     }
    15.    public static void main(String[] args) {
    16.         GenericU genericU  = new GenericU();
    17.         Operater operater = new Operater<>(genericU);
    18.         operater.doIt();
    19.     }
    20. }

    上面的代码就是因为泛型擦除,带来编译就报错了,代码中的obj不知道是什么类型?

    正确的代码应该是什么,只要指定T的类型就好

    1.  class Operater2extends GenericU> {
    2.     private T obj;
    3.     public Operater2(T obj) { 
    4.       this.obj = obj; 
    5.       }
    6.     public void doIt() {
    7.       //正确☑️
    8.       obj.foo(); 
    9.     }
    10. }

    区分在Operater2Operater
    必须指定泛型的类型。

    上面的例子是运用在类上面的,方法中是什么效果呢?

    1. class Foo{
    2.   //定义泛型方法..
    3.   public  void show(T t) {
    4.       System.out.println(t);
    5.   }
    6. }

    调用方法

    1. public static void main(String[] args) {
    2.     //创建Foo对象
    3.     Foo foo = new Foo();
    4.     //不同的类型参数
    5.     foo.show("兔子托尼啊");
    6.     foo.show(1234);
    7.     foo.show(12.34);
    8. }

    通配符与上下界

    我们大家在java的源码中肯定看到这样的例子。一个下限,一个上限

    ? extends T VS ? super T

    • ? extends T - 这里的?表示类型T的任意子类型,包含类型T本身。
    • ? super T - 这里的?表示类型T的任意父类型,包含类型T本身。

    上限通配符 可以代表未知的T类型,或者通过关键字 extends 所继承的T类的任何一个子类。

    同样,下限通配符 可以代表未知的T类型,或者通过关键字super出来的的T类的任何一个父类。

    通配符和泛型方法

    1. //通配符
    2. public  void foo1(List list) {
    3. }
    4. //使用泛型方法
    5. public  void  foo2(List t) {
    6. }

    问: 上面两种代码都是可以的,但是什么场合用那种呢?

    • 如果当参数之间有依赖关系,或者返回的参数有依赖关系则用泛型,反之则用通配符。

    问:关于 ? extends T 和 ? super T 什么场景下用呢?

    我从网上搜索了下

    当你需要从一个数据结构中获取数据时(get),那么就使用 ? extends T;如果你需要存储数据(put)到一个数据结构时,那么就使用 ? super T; 如果你又想存储数据,又想获取数据,那么就不要使用通配符 ? ,即直接使用具体泛型T。

    最后

    泛型大概就讲了上面的内容,你看明白了吗?希望你又学到了,每天学一点,进步一点。升职加薪就是你了。

  • 相关阅读:
    logrus包学习(一)
    Docker学习_镜像和容器篇
    IDEA2022开发Java Web Servlet程序
    那些好用过头的键盘
    Vivado HLS 第1讲 软件工程师该怎么了解FPGA架构
    【微服务】SpringCloud-Nacos注册中心
    图像采集 deep OCR
    SpringBoot拦截器Interceptor的使用-基础篇
    Vue 之 vue-seamless-scroll 实现简单自动无缝滚动,且添加对应点击事件的简单整理
    抖音招聘直播报白是通过直播方式展现职位信息适用于企业和人力资源公司
  • 原文地址:https://blog.csdn.net/guanshengg/article/details/126379964