程序员子龙(Java面试 + Java学习) 程序员子龙(Java面试 + Java学习)
首页
学习指南
工具
开源项目
技术书籍

程序员子龙

Java 开发从业者
首页
学习指南
工具
开源项目
技术书籍
  • 基础

    • java 学习路线图
    • HashMap 详解
    • Java 8 日期和时间 - 如何获取当前时间和时间戳?
    • Java 模板变量替换(字符串、占位符替换)
    • JDK 代理
    • Java SPI 详解
    • java stream 看这一篇文章就够了
    • Java 泛型详解
      • Java 动态修改注解值
      • 如何正确遍历删除List中的元素
      • 什么是Java注解
      • 异步编程神器:CompletableFuture详解
      • FIFO 、LRU、LFU算法
      • 十进制转十六进制
      • java中double类型数据加减乘除操作精度丢失问题及解决方法
      • JAVA利用反射清除实体类对应字段
      • JSON转换问题最全详解(json转List,json转对象,json转JSONObject)
      • java 8 List 根据某个字段去重
      • Java List排序
      • 压缩算法:字符串(JSON)压缩和解压【JDK之Deflater压缩与Inflater解压】
      • BCD码是什么?
      • Java二进制、八进制、十进制、十六进制转换
      • Java String字符串 与 ASCII码相互转换
      • 什么是跨域?解决方案有哪些?
      • Java 16进制字符串转10进制
      • LinkedHashMap实现LRU - 附重点源码解析
      • 去掉 if...else 的七种绝佳之法
      • 一眼看清@JSONField注解使用与效果
    • JVM

    • Spring

    • 并发编程

    • Mybatis

    • 网络编程

    • 数据库

    • 缓存

    • 设计模式

    • 分布式

    • 高并发

    • SpringBoot

    • SpringCloudAlibaba

    • Nginx

    • 面试

    • 生产问题

    • 系统设计

    • 消息中间件

    • Java
    • 基础
    程序员子龙
    2024-01-29
    目录

    Java 泛型详解

    # 前言

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

    泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

    使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList就是一个无处不在的集合类。

    泛型带来的好处

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

    那么泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。

    # 泛型的使用

    泛型有三种常用的使用方式:泛型类,泛型接口和泛型方法。下面讲一一简单介绍这三种使用方法:

    # 泛型类

    一个泛型类(generic class)就是具有一个或多个类型变量的类。作用在类上的泛型:当数据类型不确定的时候,可以⼀个泛型,通过创建对象传递过来⼀个类型,来确定T的类型 。下面通过一个简单的Computer类作为例子。对于这个类来说,我们只关注泛型,而不会为数据存储的细节烦恼。

    修饰符 class 类名<代表泛型的变量> { }
    
    1

    泛型类,是在实例化类的时候指明泛型的具体类型

    举个例子

    /*
     * 泛型类
     * Java库中 E表示集合的元素类型,K 和 V分别表示表的关键字与值的类型
     * T(需要时还可以用临近的字母 U 和 S)表示“任意类型”
     */
    public class Computer<T> {
        
        private T name;
        private T price;
    
        public Computer() {
        }
    
        public Computer(T name, T price) {
            this.name = name;
            this.price = price;
        }
    
        // 省略setter、getter方法
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    Computer类引入了一个类型变量T,用尖括号(<>)括起来,并放在类名的后面。泛型类可以有多个类型变量。例如,可以定义Computer类,其中第一个域和第二个域使用不同的类型:

    public class Computer<T,U> { ... }
    
    1

    类方法中的类型变量指定方法的返回类型以及域和局部变量的类型。例如:

    private T first; //uses the type variable
    
    1

    用具体的类型替换类型变量就可以实例化泛型类型,例如:

      Computer<String> computer = new Computer<>();
      computer.setName("xiaomi");
      computer.setPrice("2999");
    
    1
    2
    3

    注意:泛型的类型参数参数只能是类类型,不能是简单类型。

    # 泛型方法
    修饰符 <代表泛型的变量> 返回值类型 ⽅法名(参数){ }
    
    1

    什么时候确定泛型?调用方法时,确定泛型的类型

    public class Test {
    
        // 静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
        // 即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
        public static <M> void test(M m){
            //静态⽅法上要单独声明,它不能使⽤类上⾯的声明的 泛型,调⽤静态⽅法的时候,确定类型
             System.out.println("检测到了静态⽅法上的泛型:"+m.getClass());
    
        }
        
        /**
         * 泛型方法的基本介绍
         * @param tClass 传入的泛型实参
         * @return T 返回值为T类型
         * 说明:
         *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
         *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
         *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
         *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
         */
        public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
          IllegalAccessException{
                T instance = tClass.newInstance();
                return instance;
        }
        
        public static void main(String[] args){
            Test.test(1);
            Test.test("2");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31

    泛型方法使用比较复杂,在举个例子

    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<T>{
            public void show_1(T t){
                System.out.println(t.toString());
            }
    
            //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
            //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
            public <E> void show_3(E t){
                System.out.println(t.toString());
            }
    
            //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
            public <T> void show_2(T t){
                System.out.println(t.toString());
            }
        }
    
        public static void main(String[] args) {
            Apple apple = new Apple();
            Person person = new Person();
    
            GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
            //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);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    # 泛型接口

    定义格式:

    修饰符 interface 接⼝名<代表泛型的变量> { }
    
    1
    public interface Demo<T1,T2> {
    
        void test1(T1 t1);
        void test2(T2 t2);
    }
    
    // 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
    class  Demo2<T1,T2> implements Demo<T1,T2>{
    
        @Override
        public void test1(T1 t1) {
            
        }
    
        @Override
        public void test2(T2 t2) {
    
        }
    }
    //当实现泛型接口的类,传入泛型实参时:
    class DemoImpl implements Demo<String,Integer> {
    
        @Override
        public void test1(String s) {
            
        }
    
        @Override
        public void test2(Integer integer) {
    
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    # 泛型通配符

    当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是⼀旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

    # 常用的 T,E,K,V,?

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

    • ?表示不确定的类型,代表集合中可以存储任意类型的数据。
    • T (type) 表示具体的一个类型
    • K V (key value) 分别代表键值中的Key Value
    • E (element) 代表Element

    List<Object>:该容器有数据类型:Object

    List<?>:该容器的数据类型可以是任意的

    通过 T 来 确保 泛型参数的一致性

    / 通过 T 来 确保 泛型参数的一致性
    public <T extends Number> void test(List<T> dest, List<T> src)
    
    //通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型
    public void test(List<? extends Number> dest, List<? extends Number> src)
    
    1
    2
    3
    4
    5

    # 泛型中extends和super的区别

    # 上界通配符 < ? extends E>

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

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

    • 如果传入的类型不是 E 或者 E 的子类,编译不成功
    • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
    # 下界通配符 < ? super E>

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

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

    public class Demo1 {
    
        class Person {
        }
    
        class Student extends Person {
        }
    
        //?代表可以接收任意类型
        public static void test1(List<?> list) {
    
        }
    
        public static void test2(List<Object> list) {
        }
    
        //? extends Person限定所有类型是Person以及Person的⼦类,
        public static void test3(List<? extends Person> list) {
        }
    
        //? super Student限定所有类型是Student以及Student的⽗类
        public static void test4(List<? super Student> list) {
        }
    
        public static void main(String[] args) {
            List<String> list1 = new ArrayList<String>();
            List<Integer> list2 = new ArrayList<>();
            test1(list2);
            List<Object> list3 = new ArrayList<>();
            list3.add(1);
            list3.add("wowo");
            test2(list3);
            List<Person> list4 = new ArrayList<>();
            List<Student> list5 = new ArrayList<>();
            test3(list4);
            test4(list4);
            
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40

    # Class<T>和 Class<?>区别

    前面介绍了 ?和 T 的区别,那么对于,Class<T>和 <Class<?>又有什么区别呢?Class<T>和 Class<?>

    最常见的是在反射场景下的使用,这里以用一段发射的代码来说明下。

    // 通过反射的方式生成  multiLimit
    // 对象,这里比较明显的是,我们需要使用强制类型转换
    MultiLimit multiLimit = (MultiLimit) Class.forName("com.test.generic.MultiLimit").newInstance();
    
    1
    2
    3

    对于上述代码,在运行期,如果反射的类型不是 MultiLimit 类,那么一定会报 java.lang.ClassCastException 错误。

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

    // 可以
    public Class<?> clazz;
    // 不可以,因为 T 需要指定类型
    public Class<T> clazzT;
    
    1
    2
    3
    4

    那如果也想 public Class<T> clazzT;这样的话,就必须让当前的类也指定 T ,

    public class Test3<T> {
        public Class<?> clazz;
        // 不会报错
        public Class<T> clazzT;
     }
    
    1
    2
    3
    4
    5
    上次更新: 2024/01/30, 15:08:57
    java stream 看这一篇文章就够了
    Java 动态修改注解值

    ← java stream 看这一篇文章就够了 Java 动态修改注解值→

    最近更新
    01
    一个注解,优雅的实现接口幂等性
    11-17
    02
    MySQL事务(超详细!!!)
    10-14
    03
    阿里二面:Kafka中如何保证消息的顺序性?这周被问到两次了
    10-09
    更多文章>
    Theme by Vdoing | Copyright © 2024-2024

        辽ICP备2023001503号-2

    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式