• Java高级特性-泛型类


    Java高级特性-泛型类

    Java重载的知识中,在一个类内方法可以重载,Java编译器根据调用方法时传入的参数类型,参数个数来确定调用的是哪个方法。

    // java
    int add(int a, int b) {
        return a + b;
    }
    
    float add(float a, float b) {
        return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    假设上述add方法包含需要在设计时包含所有可相加的原始类型,那么就需要写多个功能相同的方法。那么是不是可以只写一个方法来涵盖所有的类型相加的功能呢?答案就是泛型。

    泛型优点

    泛型可以让类型(class/interface)在定义类时变成参数(parameter)。

    泛型的优点:

    1. 编译期更强的类型检查。

      Java编译器在编译时会对代码进行类型检查,如果代码违反类型安全规则,编译器会发出错误警告。这可以避免在运行时出现 ClassCastException。在编译期修改错误比在运行后修改出现的错误付出的代价要小。

    2. 消除类型转换。

      由于泛型提供了编译时的类型检查,所以不需要在代码中进行显式的类型转换。

      下面代码是不使用泛型的情况。

      List list = new ArrayList();
      list.add("hello");
      String s = (String) list.get(0); // 不使用泛型是,获取列表中item后还需要进行类型转换
      
      • 1
      • 2
      • 3

      在使用泛型之后。

      List<String> list = new ArrayList<String>();
      list.add("hello");
      String s = list.get(0);  // 使用泛型后,获取item后,不需要显示的转换
      
      • 1
      • 2
      • 3
    3. 代码重用:可以用一个类或者接口来操作不同类型的对象,这大大提高了代码的重用性。

    4. 提高程序可读性和稳定性:使用泛型可以使代码更易读,更稳定,更易于维护。

      例如:

      List<String> list = new ArrayList<String>();
      
      • 1

      这个通过泛型创建的String列表,再想添加其他类型,编译器就会报错。

    文章开头的代码在使用泛型后可以修改成。

    <T> T add(T a, T b) {  // 修改为泛型方法
        return a +b;
    }
    
    • 1
    • 2
    • 3

    泛型类

    泛型类型是在类型上参数化的泛型类或接口。

    下面是一个非泛型的具体类Box

    public class Box {
        private Object object;
    
        public void set(Object object) { this.object = object; }
        public Object get() { return object; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Box类中只定义了setget方法,参数Object表示可以向set方法方中传入任意的引用类型。Java编译器在编译这段代码时不会进行类型检查,因为set方法方中传入任意的引用类型,在使用过程中也可能造成意外的错误。

    泛型类定义

    泛型类的定义方式格式:

    public class ClassName<T1, T2, ..., Tn> { /* ... */ }
    
    • 1

    其中,T1, T2, …, Tn 是类型参数,它们在实例化类的时候会被具体的类型替换。

    看下使用了泛型定义后的Box类。

    public class Box<T> {
        private T t;
    
        public void set(T t) { this.t = t; }
        public T get() { return t; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意:类型参数T不能是原始数据类型。将原始类型传给类型参数,编译器会报错

    // 在代码中创建Box实例,将int类型传入,代码编译时报错。
    Box.java:15: error: unexpected type
        Box box = new Box();
            ^
      required: reference
      found:    int
    Note: Box.java uses unchecked or unsafe operations.
    Note: Recompile with -Xlint:unchecked for details.
    1 error
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    正确的创建使用方式。

    Box<Integer> integerBox = new Box<Integer>();
    integerBox.set(Integer.valueOf(13));
    Integer someInteger = integerBox.get();
    
    • 1
    • 2
    • 3

    类型参数命名

    泛型中类型参数的命名惯例一般是单个大写字母表示。

    下面是最常用的类型参数名:

    • E - Element (最常用来表示集合中的元素类型)
    • K - Key
    • N - Number
    • T - Type
    • V - Value
    • S,U,V etc. - 2nd, 3rd, 4th types

    使用泛型类创建实例时,需要指定类型参数的具体类型。

    Box<Integer> integerBox = new Box<Integer>();
    
    • 1

    在JDK 7之后,创建泛型类实例,调用类的构造方式时,可以省去类型参数,直接写空的<>括号。

    Box<Integer> integerBox = new Box<>();
    
    • 1

    类型参数的个数

    上述的Box类中包含了单个的T类型参数。其实泛型类中的类型参数个数可以有多个,最典型的就是android中Pair类。

    public class Pair<F, S> {
        public final F first;
        public final S second;
    
        public Pair(F first, S second) {
            this.first = first;
            this.second = second;
        }
    
        @Override
        public boolean equals(@Nullable Object o) {
            if (!(o instanceof Pair)) {
                return false;
            }
            Pair<?, ?> p = (Pair<?, ?>) o;
            return Objects.equals(p.first, first) && Objects.equals(p.second, second);
        }
    
        @Override
        public int hashCode() {
            return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
        }
    
        @Override
        public String toString() {
            return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}";
        }
    
        public static <A, B> Pair <A, B> create(A a, B b) {
            return new Pair<A, B>(a, b);
        }
    }
    
    • 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

    使用时,将类型参数 F,S 替换为具体类型。

    Pair<String, Integer> p1 = new Pair<>("Even", 8);
    Pair<String, String>  p2 = new Pair<>("hello", "world");
    
    • 1
    • 2

    类型参数传入参数化的类型

    已经具体化的类型也可以作为类型传给类型参数。

    List<Box<Integer>> list = new ArrayList<>();
    
    • 1

    这行代码中,Box是一个泛型类,创建的具体类型是Box,将这个参数化后的Box类作为类型放入到List的声明中。

    原始类型

    泛型类在用以创建实例时,类型参数位置可以不替换为任何类型,且<>括号也省去。

    Box b = new Box();
    
    • 1

    这里的BoxBox的原始类型。之所以可以使用原始类型,因为泛型是在JDK 5之后引入的,为了后向兼容,保留了原始类型的使用。

    以下两种程序,编译器会显示警告,但程序还是可以运行。

    // 将泛型实例赋值给一个原始类型变量
    Box<Integer> b = new Box<>(); 
    Box b1 = b; // Box is a raw type. References to generic type Box should be parameterized
    
    • 1
    • 2
    • 3
    // 将原始类型实例赋值给一个泛型实例变量
    MyBox b2 = new MyBox();
    MyBox<Integer> b3 = b2;
    
    • 1
    • 2
    • 3

    这个警告显示原始类型绕过了泛型类型检查,从而将捕获不安全代码的操作推迟到运行时。因此,您应该避免使用原始类型。

  • 相关阅读:
    算法刷题打卡第32天:预测赢家---动态规划
    B/S医院HIS绩效考核系统源码
    62.不同路径 63. 不同路径 II
    C++学习--基础
    设计模式——迭代器模式
    Vite 踩坑 —— require is not defined
    Elasticsearch7 入门 & 进阶
    JSP页面实现验证码校验
    21 | 多线程3
    Arduino驱动MAX30102心率血氧传感器模块
  • 原文地址:https://blog.csdn.net/snowgeneral/article/details/133208095