都2020了,还不好好学学泛型?

一、概述

Java 泛型( generics)是 JDK 1.5 中引入的一个新特性, 泛型提供了 编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型。

  • 泛型,即 参数化类型

一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

  • *泛型的本质是为了参数化类型

在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

@Test
public void genericDemo() {
    List list = new ArrayList();
    list.add("风尘博客");
    list.add(100);

    for(int i = 0; i< list.size();i++){
        String item = (String)list.get(i);
        log.info("item:{}", item);
    }
}

毫无疑问,程序的运行结果会以崩溃结束:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

ArrayList可以存放任意类型,例子中先添加了一个 String类型,又添加了一个 Integer类型。使用时都以 String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。

泛型只在编译阶段有效

二、泛型的使用

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如: ListSetMap

public class Generic {
    /**
     * key这个成员变量的类型为T,T的类型由外部指定
     */
    private T key;

    /**
     * 泛型构造方法形参key的类型也为T,T的类型由外部指定
     * @param key
     */
    public Generic(T key) {
        this.key = key;
    }

    /**
     * 泛型方法getKey()的返回值类型为T,T的类型由外部指定
     * @return
     */
    public T getKey(){
        return key;
    }
}

说明:

  1. 此处 T可以随便写为任意标识,常见的如 TEKV等形式的参数常用于表示泛型;
  2. 在实例化泛型类时,必须指定 T的具体类型。

  3. 指定泛型类型

@Test
public void genericDemoWithType() {
    //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型,比如这里Integer改为int编译将不通过
    Generic integerGeneric = new Generic(123456);
    log.info("integerGeneric key is:{}", integerGeneric.getKey());

    //传入的实参类型需与泛型的类型参数类型相同,即为String.

    Generic stringGeneric = new Generic("风尘博客");
    log.info("stringGeneric key is:{}", stringGeneric.getKey());
}
  • 不指定泛型类型

如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。

@Test
public void genericDemoWithOutType() {
    Generic generic = new Generic("111111");
    Generic generic1 = new Generic(4444);
    Generic generic2 = new Generic(55.55);
    Generic generic3 = new Generic(false);
    log.info("generic key is:{}",generic.getKey());
    log.info("generic1 key is:{}",generic1.getKey());
    log.info("generic2 key is:{}",generic2.getKey());
    log.info("generic3 key is:{}",generic3.getKey());
}

打印结果

... generic key is:111111
... generic1 key is:4444
... generic2 key is:55.55
... generic3 key is:false

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,例如:

public interface Generator {
    public T next();
}
  • 当实现泛型接口的类,未传入泛型实参时

未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中。

public class FruitGenerator implements Generator{

    public T next() {
        return null;
    }
}
  • 当实现泛型接口的类,传入泛型实参

在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型。

public class VegetablesGenerator implements Generator{

    private String[] vegetables = new String[]{"Potato", "Tomato"};

    public String next() {
        Random rand = new Random();
        return vegetables[rand.nextInt(2)];
    }
}

java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。

我们见到的大多数泛型类中的成员方法也都使用了泛型,有的甚至泛型类中也包含着泛型方法。

  • 泛型类和泛型方法的区别

名称 泛型类 泛型方法 区别 是在实例化类的时候指明泛型的具体类型 是在调用方法的时候指明泛型的具体类型

public  T showKeyName(GenericMethodDemo container){
    return null;
}
public class GenericMethodDemo {

    /**
     * 泛型类
     * @param
     */
    public class Generic {
        private T key;

        public Generic(T key) {
            this.key = key;
        }

        /**
         * 这里虽然在方法中使用了泛型,但是这并不是一个泛型方法,
         * 这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型,
         * 所以在这个方法中才可以继续使用 T 这个泛型。
         * @return
         */
        public T getKey() {
            return key;
        }

        /**
         * 这才是一个真正的泛型方法
         * @param container
         * @param
         * @return
         */
        public  T keyName(Generic container){
            T test = container.getKey();
            return test;        }

        /**
         * 这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic这个泛型类做形参而已。
         * @param obj
         */
        public void showKeyValue1(Generic obj){

        }

        /**
         * 这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?

         * @param obj
         */
        public void showKeyValue2(Generic obj){

        }

        /**
         * 该方法编译器会报错
         * 虽然我们声明了,也表明了这是一个可以处理泛型的类型的泛型方法。
         * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
         * @param container
         * @param
         * @return
         */
        public  T showKeyName(Generic container){
            return null;
        }

    }
}

泛型方法可以出现杂任何地方和任何场景中使用,但是有一种情况是非常特殊的, 泛型方法出现在泛型类中

public class GenericFruit {

    class  Fruit{
        @Override
        public String toString() {
            return "fruit";
        }
    }

    class Apple extends Fruit{
        @Override
        public String toString() {
            return "apple";
        }
    }

    class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }

    class GenerateTest{

        public void show_1(T t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
        //由于泛型方法在声明的时候会声明泛型,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
        public  void show_3(E t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
        public  void show_2(T t){
            System.out.println(t.toString());
        }
    }

    @Test
    public void test() {

        Apple apple = new Apple();
        Person person = new Person();

        GenerateTest generateTest = new GenerateTest();
        //apple是Fruit的子类,所以这里可以
        generateTest.show_1(apple);
        //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
        //generateTest.show_1(person);

        //使用这两个方法都可以成功
        generateTest.show_2(apple);
        generateTest.show_2(person);

        //使用这两个方法也都可以成功
        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}

静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

如果写成如下,编译器会报错

public staticvoid show(T t){

}
  • 正确写法:
public static  void show(T t){

}

泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:

无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个 static的方法而已,无法访问泛型类型的参数。所以如果 static方法要使用泛型能力,就必须使其成为泛型方法。

三、泛型通配符

我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 TEKV 等等,这些通配符又都是什么意思呢?

本质上这些都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下, TEKV&#xFF1F; 是这样约定的:

对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 <?> ),表示可以持有任何类型。

上界:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

public void showKeyValue(Generic obj){
    log.info("value is {}", obj.getKey());
}

@Test
public void testForUp() {
    Generic generic1 = new Generic("11111");
    Generic generic2 = new Generic(2222);
    Generic generic3 = new Generic(2.4f);
    Generic generic4 = new Generic(2.56);

    /*// 这一行代码编译器会提示错误,因为String类型并不是Number类型的子类
    showKeyValue(generic1);*/

    showKeyValue(generic2);
    showKeyValue(generic3);
    showKeyValue(generic4);
}

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object

在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。

泛型的上下边界添加,必须与泛型的声明在一起

?T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ? 不行,比如如下这种 :

// 可以
T t = operate();

// 不可以
? car = operate();

即: T 是一个确定的类型,通常用于泛型类和泛型方法的定义, ?是一个不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

Class<t></t> 在实例化的时候, T 要替换成具体类。 Class<?> 它是个通配泛型, ?可以代表任何类型,所以主要用于声明时的限制情况。比如,我们可以这样做申明:

// 可以
public Class clazz;

// 不可以,因为 T 需要指定类型
public Class clazzT;

所以当不知道定声明什么类型的 Class 的时候可以定义一 个 Class<?>
那如果也想 public Class<t> clazzT</t>; 这样的话,就必须让当前的类也指定 T

public class Wildcard {

    public Class clazz;

    public Class clazzT;
}

四、泛型中值得注意的地方

泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

public class GenericTypeErase {

    public static void main(String[] args) {
        List l1 = new ArrayList();
        List l2 = new ArrayList();
        System.out.println(l1.getClass() == l2.getClass());

    }
}

打印的结果为 true;是因为 List<string></string>List<integer></integer>jvm 中的 Class 都是 List.class,泛型信息被擦除了。

需要使用它们对应的包装类。

List[] li2 = new ArrayList[];
List li3 = new ArrayList[];

List<integer></integer>List<boolean></boolean>jvm 中等同于 List<object></object>,所有的类型信息都被擦除,程序也无法分辨一个数组中的元素类型具体是 List<integer></integer>类型还是 List<boolean></boolean>类型。

它抽离了数据类型与代码逻辑,本意是提高程序代码的简洁性和可读性,并提供可能的编译时类型转换安全检测功能。

五、总结

Original: https://www.cnblogs.com/VanFan/p/12198770.html
Author: 风尘博客
Title: 都2020了,还不好好学学泛型?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/573580/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Mybatis配置解析(核心配置文件)

    4、配置解析 4.1、核心配置文件 Mybatis的配置文件包含了会深深影响mybatis行为的设置和属性信息 mybatis-config.xml properties(属性)重…

    Java 2023年6月13日
    0114
  • 插入排序(java)

    一、概念及其介绍 插入排序(InsertionSort),一般也被称为直接插入排序。 对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记…

    Java 2023年6月5日
    0113
  • 某CTF比赛writeup

    看到群里别人参加比赛发上来的附件,自己尝试解了一下。 1、提示RSA,提供flag.enc和pub.key附件 一看就是解RSA,公钥分解得到n和e n=8693448229604…

    Java 2023年6月5日
    085
  • SpringBoot接口-如何生成接口文档之非侵入方式(通过注释生成)Smart-Doc?

    通过Swagger系列可以快速生成API文档,但是这种API文档生成是需要在接口上添加注解等,这表明这是一种侵入式方式; 那么有没有非侵入式方式呢, 比如通过注释生成文档? 本文主…

    Java 2023年6月6日
    077
  • 你还在用BeanUtils进行对象属性拷贝?

    在做业务的时候,为了隔离变化,我们会将 DAO查询出来的 DO和对前端提供的 DTO隔离开来。大概 90%的时候,它们的结构都是类似的;但是我们很不喜欢写很多冗长的 b.setF1…

    Java 2023年6月6日
    069
  • Sonarquber-docker安装教程

    本文安装 Sonarqube是基于docker-compose的安装教程,安装 Sonarqube前需安装docker和docker-compose 因为sonarqube采用el…

    Java 2023年6月8日
    0116
  • Java 继承

    继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。 在 Java 中通过 extends 关键字可以申…

    Java 2023年6月5日
    076
  • 琐碎的想法(四)键盘布局、快捷键的由来

    写这篇文章的意义 很多不太了解电脑的同学会觉得键盘的快捷键这么多,这么复杂,为什么不设计的简单点,有的键可能一辈子都用不上,这些发明有什么意义? Q & A 问:现在的键盘…

    Java 2023年6月8日
    053
  • 计算机网络学习—物理层

    二、 物理层 2.1 物理层的基本概念 物理层所要解决的问题(⭐) 物理层考虑的是怎样才能 在连接各种计算机的传输媒体上传输数据比特流。 物理层 为数据链路层屏蔽了各种传输媒体的差…

    Java 2023年6月7日
    065
  • 碎碎念六二

    07.01 心,完全沉静下来,阅读与创作,便是快乐的开始。 心静下来,做什么事情都会快乐的吧! 又是什么,让心静不下来呢?忧疑与恐惧,名利与渴望。 07.03 小心保存情感,蓄积,…

    Java 2023年6月9日
    068
  • TestLink在线Excel用例转换xml

    项目功能 TestLink在线Excel用例转换xml将符合用例模板的Excel测试用例,转换成xml,用于导入TestLink进行用例管理。 使用方法 1、编写测试用例 2、打开…

    Java 2023年6月6日
    0118
  • Hash

    系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加例如:第一章 Python 机器学习入门之pandas的使用 提示:写完文章后,目录可以自动生成,如何…

    Java 2023年6月5日
    083
  • Maven配置私有仓库

    前言 当公司或个人具有自己独有的jar时,不想公开,一般就会放在自己的私有Maven仓库中,在项目中需要引用,此时就需要将公司私有仓库配置到maven当中,一般我们的maven配置…

    Java 2023年6月8日
    076
  • Spring(二)-生命周期 + 自动装配(xml) +自动装配(注解)

    1、生命周期 Spring容器的 bean的生命周期; Truck @Data @ToString public class Truck { //品牌 private String…

    Java 2023年6月15日
    072
  • Redis概念

    概念:redis是一款高性能的NOSQL系列的非关系型数据库 Original: https://www.cnblogs.com/ailhy/p/16610114.htmlAuth…

    Java 2023年6月6日
    076
  • Spring Cloud Gateway 不小心换了个 Web 容器就不能用了

    最近组员修改微服务的一些公共依赖,在某个依赖中需要针对我们微服务使用的 Undertow 容器做一些订制,所以加入了 web 容器 Undertow 的依赖。但是,一般这种底层框架…

    Java 2023年6月7日
    0144
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球