写点什么

Java 泛型介绍

作者:TaurusCode
  • 2023-03-19
    广东
  • 本文字数:3294 字

    阅读完需:约 11 分钟

Java泛型介绍

什么是泛型?

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,此时才可以在方法中使用泛型类型 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 === Integerinfo === Object
复制代码

以上就是泛型的全部介绍了,谢谢大家。


发布于: 2023-03-19阅读数: 51
用户头像

TaurusCode

关注

非宁静无以致远 2020-09-17 加入

还未添加个人简介

评论

发布
暂无评论
Java泛型介绍_Java泛型_TaurusCode_InfoQ写作社区