花几分钟把java泛型吃透(代码片段)

野生java研究僧 野生java研究僧     2023-02-24     193

关键词:

1.什么是泛型?

泛型是从jdk5开始引入的东西,所谓的泛型就是将参数类型化,就是将具体的参数类型进行类型化,调用的时候再传递具体的参数类型。在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类(泛型类)、接口(泛型接口),方法(泛型方法)中。

2.为什么要引入泛型?

  • 在没有引入泛型之前,如果要实现参数的任意类型化,那么就只能通过Object来实现,目的是达到了,但是这样的可读性不太好,而且不知道具体类型,需要进行强制类型装换,有的类型不兼容,会导致类型转换异常ClassCastException

  • 比如在将Object类型的实际值为String类型的变量,强转为Charset的时候,编译期不会报错,而在运行时就会抛出ClassCastException 这种很明显的异常应该在编译期就要进行发现的,不应该留到运行时再去处理。

  • java通过引入泛型机制,将这种隐患在编译期就进行处理,开发人员可以知道实际的类型,避免进行不确定的强转,这样提高了代码的可读性,灵活性。

3.使用泛型和未使用泛型对比

用一个我之前比较常犯的一个错误案例:之前使用List集合的时候,总是不理解后面跟的泛型是什么东西,导致我老是进行强制转换,类型对还好,如果类型不一致就抛出异常。

public static void main(String[] args) 
        // 未使用泛型
       List list = new ArrayList();
       list.add("123");
       list.add(new Character('A'));
       for (Object value:list)
           String str = (String) value;
           System.out.println(str);
       
    

以上代码,在编译期不会提示任何错误,但是在运行时就会抛出:

Exception in thread "main" java.lang.ClassCastException: java.lang.Character cannot be cast to java.lang.String

使用泛型:在编译期就解决这种比较低级的错误,明确类型,无需进行强转

  public static void main(String[] args) 
        // 使用泛型
        List<String> list = new ArrayList();
        list.add("123");
        list.add(new Character('A'));
        for (Object value:list)
            String str = (String) value;
            System.out.println(str);
        
    

使用泛型后,指定类型,在编译期就会出现错误提示,并不会等到运行时才会抛出异常

4.泛型中的通配符

在使用泛型时经常会看到T、E、K、V这些通配符,它们代表着什么含义呢?

本质上它们都是通配符,并没有什么区别,换成A-Z之间的任何字母都可以。这是开发者们的一些约定

  • T (type) 表示具体的一个java类型;
  • K (key) 表示java中的一个key 对应一个value (如:HashMap)
  • V (value)表示java中的一个value 对应一个key(如:HashMap)
  • E (element) 代表Element;

5.泛型消除

java中的泛型不是真泛型,只是在编译期有效,到运行时,都统一的转化为了Object类型

如下代码:在编译期报错,但是我们跳过编译期就不会报错,使用java的反射机制即可,反射出来的对象都是经过编译期了的。

上图中的代码对应的字节码文件:

public static void main(java.lang.String[]);
    Code:
       0: new           #2           / class java/util/ArrayList 
       3: dup							  
       4: invokespecial #3            // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4             // String hello 
      11: invokeinterface #5,  2  // InterfaceMethod java/util/List.add:(Ljava/lang/Object;) [最终add的是Object类型]
      16: pop
      17: return

使用反射跳过泛型检测机制,这就是所谓的泛型在运行时类型擦除

 public static void main(String[] args) throws Exception 
       List<Integer> list = new ArrayList<>();
        Method method = list.getClass().getDeclaredMethod("add", Object.class);
        method.invoke(list,"hello");
        System.out.println(list.get(0));
    
// 正确输出 hello , 并且不抛出任何异常信息

6.泛型的定义与使用

6.1 泛型类

泛型类的声明和非泛型类的声明类似,只是在类名后面添加了类型参数声明部分。由尖括号 <泛型通配符> 分隔的类型参数部分跟在类名后面。它指定类型参数(也称为类型变量)T1,T2,…Tn。一般将泛型中的类名称为原型,而将<>指定的参数称为类型参数。

// T为任意标识,比如用T、E、K、V等表示泛型 也可以是A~Z,a~z,或者是其他字母的组合,但是一般见名知意比较好
class User<T>
    // 泛化的成员变量,T的类型由外部指定
    private T userInfo;

    // 构造方法类型为T,T的类型由外部指定
    public User(T userInfo)
        this.userInfo = userInfo;
    

    // 方法返回值类型为T,T的类型由外部指定
    public T getInfo() 
        return userInfo;
    

    public static void main(String[] args) 
        // 实例化泛型类时,需要指定T的具体类型,这里为String,如果不指定那么就是Object。
        // 传入的实参类型需与泛型的类型参数类型相同,这里为String。
        User<String> user = new User("小明");
        String userInfo = user.getInfo();
        System.out.println(userInfo);

    


当然也可以不使用泛型化,直接:User user = new User(“小明”) 这种方式不推荐, 但是既然设计为泛型类,那么就使用泛型化

6.2 泛型接口

泛型接口的声明与泛型类一致,泛型接口语法形式:多个泛型参数可以用逗号进行分割。

interface UserDao<T>

    T selectById(String id);
    int updateUserInfo(T user);

泛型接口有两种实现方式:子类明确声明泛型类型和子类不明确声明泛型类型。

子类明确泛型类型:

class  UserDaoImpl implements UserDao<User>
    @Override
    public User selectById(String id) 
        return null;
    

    @Override
    public int updateUserInfo(User user) 
        return 0;
    

子类不明确泛型类型:

class  UserDaoImpl implements UserDao
    @Override
    public Object selectById(String id) 
        return null;
    

    @Override
    public int updateUserInfo(Object user) 
        return 0;
    

6.3 泛型方法

泛型类是在实例化类时指明泛型的具体类型;泛型方法是在调用方法时指明泛型的具体类型。泛型方法可以是普通方法、静态方法、抽象方法、final修饰的方法以及构造方法

泛型方法语法形式如下:

public <T> T method(T object) 
    

尖括号内为类型参数列表,位于方法返回值T或void关键字之前。尖括号内定义的T,可以用在方法的任何地方,比如参数、方法内和返回值

   static<K, V> V method(Map<K,V> map) 
        Map<K, V> hashMap = new HashMap<>();
        return map.get("");
    

需要注意的是,泛型方法与类是否是泛型无关。另外静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

**泛型方法和可变参数的配合使用:**如果有多个参数,可变参数需要放在方法参数的最后一个位置,可变参数位置的参数可以是不同的类型

 public void batchDelete(int cache,T... userId)
      T[] userIds = userId;
      for (int i = 0; i < userId.length; i++) 
          System.out.println(userId[i]);
      
  
// 调用:new User().batchDelete(1,"1","2","3",4);

小总结:如果能使用泛型方法尽量使用泛型方法,这样能将泛型所需到最需要的范围内。如果使用泛型类,则整个类都进行了泛化处理。

6.4 通配符

无界通配符:?

类型通配符一般是使用?代替具体的类型实参。当操作类型时不需要使用类型的具体功能时,只使用Object类中的功能,那么可以用?通配符来表未知类型。例如List<?>在逻辑上是 List<String>List<User>等…所有List<具体类型实参>的父类。

    // 方法调用实参传递过来的时候,只能是String类型,不能是别的类型
    public void methodOne(List<String> args)

        List<String> list1 = args;
    
    
    // 方法调用实参传递过来的可以是任意类型,实参传递的是什么类型就是什么类型,这种方式比较灵活
    public void methodTwo(List<?> args)
        List<?> list= args;

    
    // 方法调用实参传递过来的是Object类型,只要是Object类型的子类即可,不过这样容易强转
    public void methodThree(List<Object> args)
        List<?> list= args;

    

无界通配符,有两种应用场景:

  • 使用Object类,可以是Object的任意子类
  • 使用 ? 来泛型化具体参数

6.5 范围限制

既然有 无界通配符,那也就有 有界通配符,也就是不在是任意类型,而是限制你的范围,就比如说泛型参数必须是某个类的子类

上界通配符: 泛型参数必须是某个类的子类或实现类

   public static void main(String[] args) 
       
        User<String> user1 = new User<>("hello");
        User<Float> user2 = new User<>(1.5F);
        User<Integer> user3 = new User<>(10);
       
        //user1这个参数会报错,因为有规定必须是Number类的一个子类,String并不是Number的一个子类
        method(user1);
        method(user2);
        method(user3);
    

    public static void method(User<? extends Number> arg)
        System.out.println(arg);
    

**下界通配符:**泛型参数必须是某个类的父类 或 某个类实现了该接口的类型

   public static void main(String[] args) 
        User<Collection> user1 = new User<>();
        User<Iterable> user2 = new User<>();
        User<Integer> user3 = new User<>(10);
        // List继承自Collection接口编译通过
        method(user1);
        // Collection继承自Iterable编译通过
        method(user2);
        // 此处编译通过,因为Integer并不是List父类型
        method(user3);
    
    public static void method(User<? super List> arg)
        System.out.println(arg);
        public static void main(String[] args) 
        User<Collection> user1 = new User<>();
        User<Iterable> user2 = new User<>();
        User<Integer> user3 = new User<>(10);
        // List继承自Collection接口编译通过
        method(user1);
        // Collection继承自Iterable编译通过
        method(user2);
        // 此处编译通过,因为Integer并不是List父类型
        method(user3);
    
    public static void method(User<? super List> arg)
        System.out.println(arg);
    

注意:基本数据类型不能使用泛型。如果要使用请使用他们对应的包装类型:int,long… 无法用于泛型,在使用的过程中需要通过它们的包装类 Integer, Long,…

6.6 泛型小案例:

该案例是JDBC中的一个通用的查询方法,让代码变的更加的灵活

    /**
     * 通用查询
     *
     * @param clazz 需要查询的类型 类名.Class
     * @param sql 需要查询的sql语句
     * @param args 占位符 可变参数,可以不需要 不传就是查询全部
     * @param <T>  使用泛型机制,让这个查询使用于所有的实体类
     * @return 如果查询到就返回一该对象的list集合 ,没有查询到返回null
     */
    public <T> List<T> QueryForList(Class<T> clazz, String sql, Object... args)
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try 
            connection = JDBCUtils.getConnection();

            preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) 
                preparedStatement.setObject(i + 1, args[i]);
            

            resultSet = preparedStatement.executeQuery();
            // 获取结果集的元数据 :ResultSetMetaData
            ResultSetMetaData rsmd = resultSet.getMetaData();
            // 通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();

            //创建集合对象
            ArrayList<T> list = new ArrayList<T>();
            while (resultSet.next()) 
                T t = clazz.newInstance();
                // 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
                for (int i = 0; i < columnCount; i++) 

                    // 获取列值
                    Object columValue = resultSet.getObject(i + 1);
                    // 获取每个列的列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    // 给t对象指定的columnName属性,赋值为columValue:通过反射

                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    //给指定对象属性名赋值
                    field.set(t, columValue);
                
                list.add(t);

            
            //返回查询结果集
            return list;
         catch (Exception e) 
            e.printStackTrace();
         finally 
            //释放数据库连接资源
          JDBCUtils.closeStatement(connection,preparedStatement,resultSet);

        
        return null;
    

到这里泛型的内容就差不多结束了,希望对看到此文章的小伙伴有所帮助。

参考连接:https://www.choupangxia.com/2021/02/25/java-generic/

十分钟吃透java内存模型(代码片段)

十分钟吃透Java内存模型Java内存模型把Java虚拟机内部划分为线程栈和堆。这张图演示了Java内存模型的逻辑视图。每个运行在JVM里的线程都拥有自己的线程栈。线程栈包含了该线程调用的方法的当前执行点相关的信息。一个线程... 查看详情

十分钟吃透java内存模型(代码片段)

十分钟吃透Java内存模型Java内存模型把Java虚拟机内部划分为线程栈和堆。这张图演示了Java内存模型的逻辑视图。每个运行在JVM里的线程都拥有自己的线程栈。线程栈包含了该线程调用的方法的当前执行点相关的信息。一个线程... 查看详情

5分钟吃透reactnativeflexbox(代码片段)

今天我们来聊聊Flexbox,它是前端的一个布局方式。在ReactNative中是主流布局方式。如果你刚刚入门ReactNative,或者没有多少前端的技术经验,亦或者对其半知半解,那么这篇文章将很好的帮助你参透Flexbox的整个全貌。purpose通过... 查看详情

十分钟深刻理解java高级——泛型(代码片段)

文章目录【1】什么是泛型?【2】为什么需要泛型?【3】如何使用泛型?一、泛型类和泛型接口二、一个类如何实现使用泛型接口的类?三、泛型方法的使用①什么是泛型方法?②普通方法:③泛型方法&#x... 查看详情

换个姿势,十分钟拿下java/kotlin泛型(代码片段)

0x1、引言解完BUG,又有时间摸鱼学点东西了,最近在复习Kotlin,跟着朱涛的《Kotlin编程第一课》查缺补漏。看到泛型这一章时,想起之前面一家小公司时的面试题:说下你对泛型协变和逆变的理解?读者可... 查看详情

一文简单全面了解策略模式的使用花几分钟轻松掌握一个知识点(代码片段)

您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦。本文重点:介绍策略模式概念以及实际应用。干货满满,建议收藏,需要用到时常看看。小伙伴们如有问题及需要,欢迎踊跃留言哦~~~。文章... 查看详情

花几分钟分析了csdn各领域榜单,我得到了这样的结果(代码片段)

我从CSDN排行榜上发现惊人的秘密,快进来看看吧~本篇文章尽量不使用的晦涩复杂的技术手段,仅仅利用简单的前端知识和相关api,配合浏览器获得我们想要的数据,进行分析希望本篇文章可以让技术大佬会... 查看详情

一文吃透接口调用神器resttemplate(代码片段)

...求3.1、普通请求3.2、url中含有动态参数3.3、接口返回值为泛型3.4、下载小文件3.5、下载大文件3.6、传递头3.7、综合案例:含头、url动态参数4、POST请求4.1、post请求常见的3种类型4.2、普通表单请求4.3、上传本地文件4.4、通过流... 查看详情

java_泛型笔记(代码片段)

文章目录Java_泛型简介Java_泛型的概念Java_为什么存在泛型Java_自定义泛型结构Java_泛型的声明Java_泛型的实例化Java_泛型类Java_泛型方法Java_泛型在继承上的体现Java_通配符的使用Java_泛型简介集合容器类在设计阶段/声明阶段不能确... 查看详情

10分钟掌握go泛型(代码片段)

为什么需要泛型比如现在实现一个add函数,支持int累加funcadd(x,yint)int returnx+y现在新增一个float32累加,如下实现funcadd(x,yfloat32)float32 returnx+y再要支持其它类型比如float/string等,应该如何做呢?每种类型定义一... 查看详情

java泛型常问面试题总结(代码片段)

文章目录Java泛型常问问题1.Java中的泛型是什么?使用泛型的好处是什么?2.如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?3.下面能编译通过?4.Array中可以用泛型吗?5.编写Employee类6.你可以把`List`传递给... 查看详情

java泛型常问面试题总结(代码片段)

文章目录Java泛型常问问题1.Java中的泛型是什么?使用泛型的好处是什么?2.如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?3.下面能编译通过?4.Array中可以用泛型吗?5.编写Employee类6.你可以把`List`传递给... 查看详情

java-基础(集合泛型)(代码片段)

JAVA-基础(集合泛型)1.什么是泛型?可以在类或方法中预支地使用未知的类型。2.泛型的好处?将运行时期的ClassCastException,转移到了编译时期变成了编译失败。避免了类型强转的麻烦。(集合中是可以存放任意对象的,只要把... 查看详情

java的泛型---(英雄联盟集合嵌套案例)(代码片段)

目录Java的泛型JDK1.5以后出现的机制为什么会有泛型呢?泛型泛型类泛型方法泛型接口泛型通配符?extendsE?superE增强for泛型类的测试 泛型方法的测试 泛型接口的测试集合的嵌套遍历 案例一案例二 集合嵌套案例(英雄联... 查看详情

一文带你吃透java中的继承(代码片段)

继承继承的概念继承的格式定义父类的格式:(一个普通的类定义)publicclass父类名称 //...定义子类的格式:publicclass子类名称extends父类名称 //...举例配合理解:继承中成员变量的访问特点举例配合理解:区分子类方法中重名的三种变... 查看详情

java遗珠之泛型类型擦除(代码片段)

擦除规则泛型的作用之前已经介绍过了只是用于编译之前更为严格的类型检查,其他的一些特性也都是编译之前的,在编译之后泛型是会被擦除掉的。类型擦除所做的事情如下:如果是无界限的则会把类型参数替换成... 查看详情

java筑基.泛型,反射,注解-利用注解加反射练习(代码片段)

文章目录泛型:泛型类泛型方法泛型接口子类明确泛型类的类型参数变量子类不明确泛型类的类型参数变量限定类型变量通配符泛型注解元注解注解的应用场景反射:注解+反射练习泛型:把类型明确的工作推迟到创建对象或调用... 查看详情

一文带你吃透java中的接口(代码片段)

接口接口的概述和生活举例接口定义的基本格式如何定义一个接口的格式:publicinterface接口名称 //接口内容接口的抽象方法定义/*在任何版本的Java中,接口都能定义抽象方法。格式:publicabstract返回值类型方法名称(参数列表);注意... 查看详情