花几分钟把java泛型吃透

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的反射机制即可,反射出来的对象都是经过编译期了的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BB1w8eE3-1639108720359)(C:\Users\14823\AppData\Roaming\Typora\typora-user-images\image-20211210014242238.png)]

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

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/

Guess you like

Origin blog.csdn.net/m0_46188681/article/details/121847591