Java泛型详解教程:从基础到进阶

发表时间: 2024-06-21 05:03

1、泛型介绍

1.1 泛型引入背景

早期没有泛型概念时,创建集合存入的元素类型都当成Object,使用的时候必须清楚集合中每个元素的类型,否则当我们对元素进行操作时容易出错,例如转换元素的类型可能出现ClassCastException异常。

示例代码:

如下代码,for循环循环到第二个元素的时候,由于元素原始类型为int,而代码强制转换为String,出现了ClassCastException。

public class Main {    public static void main(String[] args) {        ArrayList arrayList = new ArrayList();        arrayList.add("ggy");   //存入String类型        arrayList.add(123);     //存入int类型        arrayList.add(true);    //存入bool类型        //使用集合时,我们并清楚集合中每个元素类型,假设都当成String进行处理        for (int i = 0; i < arrayList.size(); i++) {            String s = (String)arrayList.get(i);//此次进行强转            System.out.println(s);        }    }}

针对以上问题,Jdk5开始引入泛型,我们来看下使用泛型,如何处理上述的问题。

如下图,当我们对集合设置了泛型后,集合添加了不同类型的元素时,代码处直接标红,编译不同。

1.2 泛型的概念

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

1.3 泛型的作用

  • 提供了编译时类型安全监测机制,保证数据类型安全
  • 消除了强制类型的转换

2、泛型类

2.1 泛型类定义语法

泛型标识可以作为成员变量的类型,和成员方法的返回值类型及参数类型

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

常用的泛型标识:T、E、K、V

示例代码

public class Generice <T>{ //T为泛型类的泛型标识    private T key; //成员变量的参数类型被指定为T泛型标识    public Generice(T key) { //泛型标识作为构造器的参数类型        this.key = key;    }    public T getKey() { //泛型标识作为成员方法的返回值类型        return key;    }    public void setKey(T key) { //泛型标识作为成员方法的参数类型        this.key = key;    }}

2.2 泛型类使用

2.2.1 语法

Java1.7以前

类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();

Java1.7以后

后面的<>中的类型可以省略

类名<具体的数据类型> 对象名 = new 类名<>();

示例代码:

public class Animal <T>{    private T name;    public T getName() {        return name;    }    public void setName(T name) {        this.name = name;    }}class Main{    public static void main(String[] args) {        Animal<String> animal = new Animal<>(); //泛型类使用        animal.setName("Ggy");        System.out.println(animal.getName());    }}

2.2.2 泛型类注意事项

1)泛型类,如果没有指定具体的数据类型,此时操作类型是Object

如下第一张图,没有指定类型时,idea显示形参化类的原始使用,第二张图指定了String没有显示这个信息。

2)泛型的类型参数只能是类类型,不能是基本数据类型

3)泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型

可以结合后面的泛型擦除理解,泛型擦除之后 Animal< Integer> 和Animal< String>都是Animal类。

2.3 从泛型类派生子类

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

语法:class ChildGeneric<T> extends Generic<T>

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

语法:class ChildGeneric extends Generic<String>

3、泛型接口

3.1 泛型接口定义语法

泛型标识可以作为成员方法的返回值类型及参数类型

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

常用的泛型标识:T、E、K、V

示例代码:

public interface Animal<T> {    void getName(T t);        T getName();}

3.2 泛型接口的使用

3.2.1 语法

接口名<具体数据类型> 变量名 = new 实现类名<>();

示例代码:

public interface Animal<T> {    T getName();    void setName(T name);}class Cat<T> implements Animal<T>{    private T name;    @Override    public T getName() {        return name;    }    @Override    public void setName(T name) {        this.name = name;    }}class Main{    public static void main(String[] args) {        //使用泛型接口        Animal<String> cat1 = new Cat<>();        Cat<String> cat2 = new Cat<>();    }}

3.2.2 使用泛型接口注意点

1)实现类也是泛型类,实现类和接口的泛型类型要一致

2)实现类也是泛型类,实现类和接口的泛型类型要一致

4、泛型方法

4.1 定义

泛型类,是在实例化类的时候指明泛型的具体类型,泛型方法,是在调用方法的时候指明泛型的具体类型。

4.2 泛型方法使用

语法:

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

4.3 泛型类和内部泛型方法的泛型标识的理解

【注】泛型接口\类的泛型标识,如果与接口\类内部的泛型方法的泛型标识同名,这两个泛型标识是不能等同的。

4.4 泛型方法与可变参数

按如下方式可传递多个参数

 public <E> void print(E... e){        for (E e1 : e) {            System.out.println(e);        } }

示例代码

public interface Generic {    public <E> List<E> getList(E...e);}class SubGeneric implements Generic{    @Override    public <E> List<E> getList(E... e) {        ArrayList<E> list = new ArrayList<>();        Arrays.stream(e).forEach(a->{            list.add(a);        });        return list;    }}class Main{    public static void main(String[] args) {        SubGeneric subGeneric = new SubGeneric();        //传递多个参数        List<? extends Serializable> list = subGeneric.getList("ggy", 123, "czp", true);        System.out.println(list);    }}

4.5 泛型方法总结

  • 泛型方法能使方法独立于类而产生变化
  • 如果static方法要使用泛型能力,就必须使其成为泛型方法

5、类型通配符

5.1 什么事类型通配符

类型通配符一般是使用"?"代替具体的类型实参。类型通配符是类型实参,而不是类型形参。

5.2 类型通配符上限

语法

类/接口<? extends 实参类型>

要求该泛型的类型,只能是实参类型,或实参类型的子类类型。

示例代码:

5.3 类型通配符下限

语法

类/接口<? super实参类型>

要求该泛型的类型,只能是实参类型,或实参类型的父类类型。

示例代码:

5.4 类型通配符使用注意

只能用在方法的返回值或者方法的参数列表中,不能用在类上。

用在类上报错:

用在方法上:

5.5 泛型实参后误解点

下述代码我通常会误解认为应该编译通过,但是编辑器显示编译出现问题,这是因为当泛型指定了实参后,传入的参数的类型应该和指定的实参类型一致,如下printCollection方法的参数的泛型指定为Object,那么我们调用这个方法时,传入的参数的泛型也应该是Object.

那么上面的案例能否优化呢?可以通过“?”来实现,“?”代表任意的实参,所以编译可以通过。

或者我们通过泛型类实现也可以

或者通过泛型方法实现

6、类型擦除

6.1 概念

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

6.2 类型擦除种类

6.2.1 无限制类型擦除

6.2.2 有限制类型擦除

6.2.3 桥接方法

6.2.4 擦除方法中类型定义的参数