Java泛型介绍

101 阅读5分钟

泛型的概念

Java泛型(generics)是JDK5引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。

在JDK5之前,定义一个集合可以存储任何的数据类型,意味着它是一个Object类型的数据。这样是非常不安全的,必须明确知道存储的每个元素的数据类型,否则容易引发ClassCastException异常。

// 编译正常
List list = new ArrayList<>();
list.add("java");
list.add(100);
list.add(true); 
String str = (String) list.get(0);  // 每个元素都需要类型转换
Integer integer = (Integer) list.get(1); 

在有了泛型之后,集合的定义变得有点跟数组一样了。我们知道数组就是必须要求类型一致的,但是数组的长度是固定的,而集合是会动态扩容的。这样我们在设置和获取元素的时候,就不需要考虑类型不一致和转换异常的问题了。

List<String> list = new ArrayList();
list.add("java8");
list.add("java11");
list.add(100); // 编译错误,类型不一致
String str = list.get(0); // 不需要进行类型转换

泛型类

泛型类没有指定具体的数据类型,此时,操作类型是Object。参数只能是类类型,不能是基本数据类型。

定义语法如下:

class 类名称<泛型标识, 泛型标识, ...> {
   private 泛型标识 变量名;
   // ...
}

使用语法:

类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
// JDK7后,后面的<>中的具体数据类型可以省略
类名<具体的数据类型> 对象名 = new 类名<>();

举例:

public class Generic<T> {
    private T key;
 
    public Generic(T key) {
        this.key = key;
    }
 
    public T getKey() {
        return key;
    }
 
    public void setKey(T key) {
        this.key = key;
    }
}
​
public class MainTest {
    public static void main(String[] args) {
        // 如果创建时指定了类型,则会自动转换成对应的类型
        Generic<String> generic = new Generic<>("java");
        String key = generic.getKey();
     
        // 如果创建时没指定类型,则按照Object类型来操作
        Generic genericObj = new Generic("object");
        Object obj = genericObj.getKey();
    }
}
从泛型类派生子类

当子类也是泛型类,子类和父类的泛型类型要一致

class ChildGeneric<T> extends Generic<T>

当子类不是泛型类,父类要明确泛型的数据类型

class ChildGeneric extends Generic<String>

泛型接口

实现类如果不是泛型类,接口要明确数据类型。实现类也是泛型类,实现类和接口的泛型类型要一致。

定义语法如下:

interface 接口名称<泛型标识, 泛型标识, ...> {
    泛型标识 方法名();
    // ...
}

举例:

public interface Fruit<T> {
    T getKey();
}
​
// 实现泛型接口的类不是泛型类
public class Apple implements Fruit<String> {
    @Override
    public String getKey() {
        return "apple";
    }
}
​
// 实现泛型接口的类也是泛型类
public class Apple<T> implement Fruit<T> {
    private T key;
 
    public Apple(T key) {
        this.key = key;
    }

    @Override
    public T getKey() {
       return this.key;
  }
}

泛型方法

定义语法如下:

修饰符 <T, E, ...> 返回值类型 方法名(参数列表) {
   方法体...
}
  • public与返回值中间的非常重要,可以理解为声明此方法为泛型方法。
  • 只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
  • 表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
  • 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等用于表示泛型。

举例:

public class Product<T> {
    private List<T> list = new ArrayList<>();
 
    public void addProduct(T t) {
        this.list.add(t);
    }
 
    // 该方法没有使用<T>,只是返回T类型的成员方法,不是泛型方法。
    public T getFirst() {
        return list.isEmpty() ? null : list.get(0);
    }
 
    // 该方法是一个泛型方法,使用<E>声明,返回E类型。
    // 该类型由调用方法时指定,泛型方法的类型与类的泛型类型没有关系。
    public <E> E getFirst(List<E> list) {
        return list.isEmpty() ? null : list.get(0);
    }
}

类型通配符

类型通配符一般就是使用“?”代替具体的类型实参。所以,类型通配符是类型实参,而不是类型形参。

常用的通配符:

  • E:Element,一般用来代表集合的元素。
  • T:Type,一般代表Java类。
  • K:Key,键值。
  • V:Value,数据值。
  • N:Number,数值类型。
  • ?:表示不确定的Java类型。

类型通配符的上限

<? extends T>,表示这个泛型中的参数必须是T或者T的子类。

使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据。

public void getFirst(List<? extends Product> list) {
    // 这里不能再对list.add操作,因为上限通配符不知道是啥类型
    Product product = list.isEmpty() ? null : list.get(0);
    System.out.println(product);
}

类型通配符的下限

<? super T>,表示这个泛型中的参数必须是T或者T的父类。

使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据。

public void getFirst(List<? super Product> list) {
    // 这里可以再对list.add操作,因为下限通配符只需要保证是Product的子类即可
    Object obj = list.isEmpty() ? null : list.get(0);
    System.out.println(obj);
}

类型擦除

泛型是JDK5才引进的概念,在这之前是没有泛型的,但是,泛型代码能很好地和之前的版本代码兼容。那是因为泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为类型擦除。

无限制类型擦除
// 编译前
public class Generic<T> {
    public T key;
 
    public T getKey() {
        return key;
    }
 
    public void setKey(T key) {
        this.key = key;
    }
}
​
// 编译后
public class Generic {
    public Object key;
 
    public Object getKey() {
        return key;
    }
 
    public void setKey(Object key) {
        this.key = key;
    }
}
有限制的类型擦除
// 编译前
public class Generic<T extends Number> {
    public T key;
 
    public T getKey() {
        return key;
    }
 
    public void setKey(T key) {
        this.key = key;
    }
}
​
// 编译后
public class Generic {
    public Number key;
 
    public Number getKey() {
        return key;
    }
 
    public void setKey(Number key) {
        this.key = key;
    }
}
擦除方法中类型定义的参数
public <T extends Number> T getValue(T value) {
    return value;
}
​
public Number getValue(Number value) {
    return value;
}
桥接方法
// 编译前
public interface Info<T> {
    T info(T value);
}
​
public class InfoImpl implements Info<Integer> {
    @Override
    public Integer info(Integer value) {
        return value;
    }
}
​
// 编译后
public interface Info {
    Object info(Object var);
}
​
public class InfoImpl implements Info {
    public Integer info(Integer var) {
        return var;
    }
 
    // 桥接方法,保持接口和类的实现关系
    @Override
    public Object info(Object var) {
        return info((Integer)var);
    }
}

测试结果

public static void main(String[] args) {
    Method[] methods = InfoImpl.class.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println(method.getName() + " === " + method.getReturnType().getSimpleName());
    }
}
​
// 输出结果
info === Integer
info === Object

以上就是泛型的全部内容了,谢谢大家。