文章目录
  • 一、概述
  • 1、什么是泛型
  • 2、用泛型最直接的优点
  • 3、分类
  • 二、泛型详解
  • 1、泛型类
  • (1)概述
  • (2)代码实例
  • 2、泛型派生类
  • (1)概述
  • (2)代码实例
  • (3)扩展:泛型接口
  • 3、泛型方法
  • (1)概述
  • (2)代码实例
  • (3)可变参数的泛型方法
  • 4、泛型通配符
  • (1)什么是泛型通配符
  • (2)代码实例
  • (3)上边界
  • (4)下边界
  • 5、泛型擦除
  • (1)什么是泛型擦除
  • (2)代码实例
  • 6、如何创建泛型数组
  • (1)方案一:使用反射
  • (2)方案二:参考ArrayList存储Object数组

一、概述

1、什么是泛型

是在定义类、接口和方法时,可以在声明时通过一定的格式指定其参数类型。
使用时再指定具体的类型,从而使得类、接口和方法可以被多种类型的数据所实例化或调用。
这种可以在编译时进行参数类型检查的技术被称为泛型,是JDK5中引入的一个新特性。
本质是参数化类型给类型指定一个参数,在使用时再指定此参数具体的值,那这个类型就可以在使用时决定
优点:把运行时的错误,提前到编译时,这样就可以在编译时把错误提示出来,避免了运行时出现错误;使用泛型可以提高代码的复用性,因为它可以支持多种类型的数据。

2、用泛型最直接的优点

在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换。

如果插入了错误的类型对象,在运行时的转换处理就会出错。

集合容器里面如果没指定类型,默认都是Object类型,那什么到可以插入。

减少了源代码中的强制类型转换,代码更加可读。

Java核心 - 泛型详解_java

3、分类

可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法

泛型字母通常类型参数都使用大写的单个字母:
T:任意类型 type
E:集合中元素的类型 element
K:key-value形式 key
V:key-value形式 value

// 泛型类
public class 类名 <泛型类型1, ...> {

}

// 泛型接口
interface 接口名 <泛型类型1, ...> {
	泛型类型 方法名();
	......
}

// 泛型方法
修饰符 <T, E, ...> 返回值类型 方法名() {
	...
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

二、泛型详解

1、泛型类

(1)概述

泛型类必须是引用类型,即类类型(不能使用基本数据类型)
在类名后添加一对尖括号,并在尖括号中填写类型参数。
如果参数可以有多个,多个参数使用逗号分隔。

// 定义
public class 类名 <泛型类型, ...> {
	private 泛型类型 变量名;
	
	public 泛型类型 方法名() {}

	public 返回值 方法名(泛型类型 t) {}

	......
}

// 使用(JDK1.7之后,结尾的具体类型可以不用写)
类名<具体数据类型> 对象名 = new 类名<>();
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

注意:
泛型类创建的使用没有指定类型,则默认是object类型。
泛型类型从逻辑上看是多个类型,实际都是相同类型。Java 可以创建对应的泛型对象和泛型数组引用,但不能直接创建泛型对象和泛型数组
Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型,因此创建泛型对象就相当于创建了一个 Object 类型的对象,所以直接创建泛型对象和泛型数组也的行为被编译器禁止。泛型类中使用了泛型的方法,不能定义为static!

(2)代码实例
/**
 * 栈
 * 泛型类
 */
public class ArrayStack<T> {

    // 声明属性
    private Object[] arr;
    private int top;

    public ArrayStack(int capacity) {
        // 这样是不可以的
        // arr = new T[capacity];
        arr = new Object[capacity];
        top = -1;
    }

    /**
     * 入栈
     */
    public void push(T value) {
        if (top == arr.length - 1) {
            throw new RuntimeException("栈满");
        }
        arr[++top] = value;
    }

    /**
     * 出栈
     */
    public T pop() {
        if (top == -1) {
            throw new RuntimeException("栈空");
        }
        return (T) arr[top--];
    }
}
  • 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.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
public class Test {
    public static void main(String[] args) {
        ArrayStack<String> stack = new ArrayStack<>(10);
        stack.push("测试");
        // stack.push(123); // 编译出错
        System.out.println(stack.pop());


        // 不能用基本数据类型
        // ArrayStack<int> stack2 = new ArrayStack<>(10);

        // 可以这样用
        ArrayStack<Integer> stack3 = new ArrayStack<>(10);

        // class类型是相同的
        System.out.println(stack.getClass());
        System.out.println(stack3.getClass());
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

2、泛型派生类

(1)概述

如果泛型类的子类也是泛型类,那父类和子类的类型要一致。
如果子类泛型有多个,那需要包括父类的泛型类型。

class Child<T, E, F> extends Parent<T> {
	...
}
  • 1.
  • 2.
  • 3.
(2)代码实例
public class Parent<T> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

/**
 * √ 可以子类没有泛型,父类就必须指定泛型:
 * class Child extends Parent<String>
 *
 * √ 可以子类没有泛型,父类也没有泛型,那么默认泛型就是Object类型
 * class Child extends Parent
 *
 * √ 子类有泛型,比如要包含了父类的:
 * class Child<T> extends Parent<T>
 * class Child<T, E, F> extends Parent<T>
 *     
 * x 以下子类没有包含父类的,就会编译出错:
 * class Child<E, F> extends Parent<T>
 */
class Child<T, E, F> extends Parent<T> {

    public void setValue(T value) {
        super.setValue(value);
    }
}
  • 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.
  • 33.
(3)扩展:泛型接口
interface 接口名称 <泛型类型1, ...> {
	泛型类型 方法名();
	...
}
  • 1.
  • 2.
  • 3.
  • 4.

规则和泛型类一样
如果实现类是泛型类,那接口和实现类的泛型类型要一致;如果实现类泛型有多个,那需要包括接口的泛型类型。
如果实现类不是泛型类,那接口要明确泛型类的类型。

3、泛型方法

(1)概述

调用方法的时候指定泛型的具体类型。

修饰符 <T, E, ...> 返回值类型 方法名(参数列表) {
	方法体
}
  • 1.
  • 2.
  • 3.

注意,修饰符和返回值中间有<T, E...>才是泛型方法,泛型类里面的普通返回值类型不是泛型方法。

Java核心 - 泛型详解_泛型_02

泛型类的类型和泛型方法的类型是互相独立的,同名也不影响,同名的话泛型方法上的泛型优先。 声明了【泛型方法】在参数列表和方法体里面才可以用对应的泛型。

泛型方法可以定义为静态方法

Java核心 - 泛型详解_开发语言_03

(2)代码实例
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 栈
 * 泛型类
 */
public class ArrayStack<T> {

    // 声明属性
    private Object[] arr;
    private int top;

    public ArrayStack(int capacity) {
        // 这样是不可以的
        // arr = new T[capacity];
        arr = new Object[capacity];
        top = -1;
    }

    /**
     * 入栈
     */
    public void push(T value) {
        if (top == arr.length - 1) {
            throw new RuntimeException("栈满");
        }
        arr[++top] = value;
    }

    /**
     * 出栈
     */
    public T pop() {
        if (top == -1) {
            throw new RuntimeException("栈空");
        }
        return (T) arr[top--];
    }

    /**
     * 返回一个元素
     *
     * 泛型方法,泛型类的类型和泛型方法的类型是互相独立的,同名也不影响,就近优先
     * 声明了【泛型方法】在参数列表和方法体里面才可以用对应的泛型
     */
    public <T> T getRandomElement(List<T> list) {
        Random random = new Random();
        return list.get(random.nextInt(list.size()));
    }

    public static void main(String[] args) {
        // 类泛型是Integer
        ArrayStack<Integer> stack = new ArrayStack<>(10);

        // 方法泛型是String
        List<String> list = new ArrayList<>();
        list.add("111");
        list.add("222");
        list.add("333");
        String randomElement = stack.getRandomElement(list);
        System.out.println(randomElement);
    }
}
  • 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.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
(3)可变参数的泛型方法

我们可以看出,可变参数是不限制参数的类型的。

public static void main(String[] args) {
    print(1, "test", "e1", "e2", 33);
    /**
     * class java.lang.Integer
     * class java.lang.String
     * e1
     * class java.lang.String
     * e2
     * class java.lang.String
     * 33
     * class java.lang.Integer
     */
}


/**
 * 支持多个泛型类型的,可变参数的泛型方法
 */
public static <E, F, K> void print(F f, K k, E... arr) {
    System.out.println(f.getClass());
    System.out.println(k.getClass());
    for (E e : arr) {
        System.out.println(e);
        System.out.println(e.getClass());
    }
}
  • 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.

4、泛型通配符

(1)什么是泛型通配符

Java泛型的通配符是用于解决泛型之间引用传递间的特殊语法。

//表示类型参数可以是任何类型
public class CustomCollection<?>{)
//表示类型参数必须是A或者是A的子类
public class CustomCollection<T extends A>{)
//表示类型参数必须是A或者是A的超类型
public class CustomCollection<T supers A>{}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

通用类型通配符 <?>,如List<?>
主要作用就是让泛型能够接受未知类型的数据。
可以把?看成所有泛型类型的父类,是一种真实的类型,类型通配符是实参,不是形参。

固定上边界的通配符 采用<? extends E>的形式:
使用固定上边界的通配符的泛型,只能够接受指定类及其子类类型的数据。
采用<? extends E>的形式,这里的E就是该泛型的上边界。
注意:这里虽然用的是extends关键字,却不仅限于继承了父类E的子类,也可以代指显现了接口E的类。

固定下边界的通配符,采用<? super E>的形式:
使用固定下边界的通配符的泛型,只能够接受指定类及其父类类型的数据。
采用<? super E>的形式,这里的E就是该泛型的下边界。
可以为一个泛型指定上边界或下边界,但是不能同时指定上下边界。

(2)代码实例
public class NumberCollection <T>{
    private T value;

    public NumberCollection(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }



    public static void main(String[] args) {
        NumberCollection<Integer> numberCollection = new NumberCollection<>(1);
        NumberCollection<Long> numberCollection2 = new NumberCollection<>(2L);
        NumberCollection<String> numberCollection3 = new NumberCollection<>("3");

        print(numberCollection);
        print(numberCollection2);

        print(numberCollection3);

    }

    public static void print(NumberCollection<?> numberCollection) {
        // 使用泛型通配符,直接获取到父类
        Object value = numberCollection.getValue();
        System.out.println(value);
    }
}
  • 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.
  • 33.
  • 34.
  • 35.
(3)上边界

固定上边界的通配符 采用<? extends E>的形式。
使用固定上边界的通配符的泛型,只能够接受指定类及其子类类型的数据
采用<? extends E>的形式,这里的E就是该泛型的上边界。
注意:这里虽然用的是extends关键字,却不仅限于继承了父类E的子类,也可以代指显现了接口E的类。

/**
 * 只能接受Number及其子类的类型
 */
public class NumberCollection <T extends Number>{
    private T value;

    public NumberCollection(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }



    public static void main(String[] args) {
        NumberCollection<Integer> numberCollection = new NumberCollection<>(1);
        NumberCollection<Long> numberCollection2 = new NumberCollection<>(2L);
        // NumberCollection<String> numberCollection3 = new NumberCollection<>("3"); // 编译报错,String不属于Number的子类

        print(numberCollection);
        print(numberCollection2);

        // print(numberCollection3); // 编译报错,String不属于Number的子类

    }

    public static void print(NumberCollection<? extends Number> numberCollection) {
        // 使用泛型通配符,直接获取到父类
        Number value = numberCollection.getValue();
        System.out.println(value);
    }
}
  • 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.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

Java核心 - 泛型详解_泛型类型_04

(4)下边界

固定下边界的通配符,采用<? super E>的形式。
使用固定下边界的通配符的泛型,只能够接受指定类及其父类类型的数据
采用<? super E>的形式,这里的E就是该泛型的下边界。
可以为一个泛型指定上边界或下边界,但是不能同时指定上下边界。

public class NumberCollection <T>{
    private T value;

    public NumberCollection(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }



    public static void main(String[] args) {
        NumberCollection<Integer> numberCollection = new NumberCollection<>(1);
        NumberCollection<Long> numberCollection2 = new NumberCollection<>(2L);
        NumberCollection<String> numberCollection3 = new NumberCollection<>("3"); // 编译报错,String不属于Number的子类

        print(numberCollection);
        // print(numberCollection2); 不可以用了

        // print(numberCollection3); // 编译报错,String不属于Number的子类

    }

    public static void print(NumberCollection<? super Integer> numberCollection) {
        // 只能获取Object类型
        Object value = numberCollection.getValue();
        System.out.println(value);
    }
}
  • 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.
  • 33.
  • 34.
  • 35.

5、泛型擦除

(1)什么是泛型擦除

泛型是jdk1.5后出现的,但泛型代码和常规版本代码可以兼容,主要原因是泛型信息是在代码编译阶段。代码编译完成后进入JVM运行前,相关的泛型类型信息会被删除,这个即泛型类型擦除
作用范围:类泛型,接口泛型,方法泛型。

(2)代码实例

无限制类型擦除,擦除后都是Object类型。

有限制类型擦除,擦除后就是指定的父类。

import java.lang.reflect.Field;

// 没指定类型,则擦除后是Object类型,这是最顶级的父类
public class Generic <T extends Number, K>{

    private T age;

    private K name;

    public static void main(String[] args) {
        Generic<Integer, String> generic = new Generic<>();

        // 反射获取字节码文件class对象
        Class<? extends Generic> aClass = generic.getClass();

        // 获取所有成员变量
        Field[] declaredFields = aClass.getDeclaredFields();

        for (Field declaredField : declaredFields) {
            // 获取每个属性名和类型
            System.out.println(declaredField.getName() + ", type is:" + declaredField.getType().getSimpleName());

            /**
             * age, type is:Number
             * name, type is:Object
             */
        }
    }
}
  • 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.

6、如何创建泛型数组

背景:
在Java 中是不能直接创建泛型对象和泛型数组的。
主要原因是Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型或者对应的上限类型。
那定义的类中如果需要用到泛型数组,如何解决这个问题?
需求:创建一个类里面支持泛型数组和返回全部数组的方法。

(1)方案一:使用反射
import java.lang.reflect.Array;

public class GenericsArray<T> {
    private T[] array;

    public GenericsArray(Class<T> clz)
    {
        // 通过反射创建数组,然后强制类型转换
        this.array = (T[])Array.newInstance(clz, 10);
    }

    public T[] getArray() {
        return array;
    }

    public void setArray(T[] array) {
        this.array = array;
    }

    public static void main(String[] args) {
        GenericsArray<String> genericsArray = new GenericsArray<>(String.class);
        genericsArray.setArray(new String[]{"1", "2", "3"});
        String[] array = genericsArray.getArray();
        System.out.println(array);
    }
}
  • 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.
(2)方案二:参考ArrayList存储Object数组
public class GenericsArray2<T> {
    // 通过Object来避免泛型数组的编译问题
    private Object[] array;

    public GenericsArray2()
    {
        this.array = new Object[10];
    }

    // 无法强转,会报错
    public T[] getArray() {
        return (T[])array;
    }

    public static void main(String[] args) {
        GenericsArray2<String> genericsArray = new GenericsArray2<>();
        String[] array = genericsArray.getArray();
        System.out.println(array);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

获取数组的时候,无法进行强转,会报错。

ArrayList会最终有个toArray的方法:

Java核心 - 泛型详解_泛型类型_05