30、【JavaSE】【Java 核心类库(上)】泛型

1、概述

List<Object> list = new ArrayList<>();

Queue<Object> queue = new LinkedList<>();

好,那个时候,并没有对那个<>尖括号里的Object进行解释。但是那个时候,我们也发现了新创建的集合中,不管什么样类型的数据都能往里面放。实际上,当我们编写的时候,不要<>时,也是能够放不同类型的东西。

  • 通常情况下集合中可以存放不同类型的对象,是因为将所有对象都看作java.lang.Object类型放入的,因此从集合中取出元素时也是java.lang.Object类型,为了表达该元素真实的数据类型,则需要强制类型转换,而强制类型转换可能会引发类型转换异常。

  • 为了避免上述错误的发生,从 Java 5 开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,若放入其它类型的元素则编译报错。

  • 泛型只在编译时期有效,在运行时期不区分是什么类型。但是,虽然运行期间不区分类型,但也不能进行下面的这种操作:

List<String> list1 = new ArrayList<>();
List<Integer> list2 = list1; // 直接报错,编译不通过

2、基本使用

import java.util.ArrayList;
import java.util.List;

public class OneTest {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>(); // 只允许放入 String 类型
        list.add("one");
        list.add("two");
        System.out.println(list);
    }

}

自 Java 7 开始,有“菱形特性”,也就是说,List<String> list = new ArrayList<String>();语句中右侧<>中的数据类型可以不用写以简化代码,List<String> list = new ArrayList<>();

3、泛型的底层原理

  • 泛型的本质就是参数化类型,也就是让数据类型作为参数传递,其中 E 相当于形式参数负责占位,而使用集合时<>中的数据类型相当于实际参数,用于给形式参数 E 进行初始化,从而使得集合中所有的 E 被实际参数替换,由于实际参数可以传递各种各样广泛的数据类型,因此得名为泛型。
泛型本质

4、泛型类与泛型接口

  • 上面也提到过泛型的本质就是一种“类型参数”,但是这种参数能使得在开发中更为灵活,这个灵活就会体现在一些包括自定义的泛型接口、泛型类、泛型方法等地方。

  • 泛型接口:泛型接口和普通接口的区别就是后面添加了类型参数列表,可以有多个类型参数,如:<E, T, ...>等。

public interface IGenericity<T> {

    void set(T elem); // 方法的参数类型取决于泛型的具体情况

    T get(); // 方法的返回值类型取决于泛型的具体情况

}
// 泛型最终具体指定为 java.lang.Integer 类型
// 然后,注意看所要实现接口中的方法
public class GenericityImpl implements IGenericity<Integer> {

    @Override
    public void set(Integer elem) {

    }

    @Override
    public Integer get() {
        return null;
    }

}
  • 泛型类:泛型类和普通类的区别就是类名后面添加了类型参数列表,可以有多个类型参数,如:<E, T, ...>等。
// 泛型不一定只有1个!!!
public class Genericity<K, V> {

    private K key; // 成员变量使用泛型
    private V value;

    // 方法的参数类型使用泛型
    public void set(K key, V value) {

    }

    public V get(K key) {
        return null;
    }

}
public class GenericityTest {

    public static void main(String[] args) {
        Genericity<String, String> test = new Genericity<>();

        test.set("key", "value");
        
        String value = test.get("key");
    }

}
  • 实例化泛型类、实现泛型接口等情况下应该指定具体的数据类型(当然,不指定的话,一般会按照java.lang.Object进行处理,但是不提倡这样做,因为泛型的初衷就是为了让使用者指定自己所需要的),并且是引用数据类型而不是基本数据类型(定义的时候,可以用像 E、T、V、K 等来表示一种“占位”,待到具体实现的时候,需要将那个“占位”的换成具体的类型)!

5、泛型类继承过程中泛型的处理

  • 父类有泛型,被继承时,子类可以选择保留泛型也可以选择直接指定泛型类型。
// 父类
public class Genericity<K, V> {

    private K key;
    private V value;

    public void set(K key, V value) {

    }

    public V get(K key) {
        return null;
    }

}
  • 情况一:“泛型擦除”的一种,即子类不再保留父类的泛型特性,在这种情况下,一般会按照java.lang.Object处理(不建议这么做)。
public class SubGenericity extends Genericity {
    
}
public class GenericityTest {

    public static void main(String[] args) {
        SubGenericity test = new SubGenericity();
        // set(Object key, Object value)
        test.set(1, "xxxx");
        // Object get(Object key);
        Object obj = test.get(1);
    }

}
  • 情况二:“泛型擦除”的一种,即子类不再保留父类的泛型特性,但是在继承的时候,直接指定具体的类型。
public class SubGenericity extends Genericity<Integer, String> {

}
public class GenericityTest {

    public static void main(String[] args) {
        // 这种情况下,如果希望使用“父类引用指向子类对象”,那么父类的引用的类型必须将其泛型指定为和子类一样的,否则是错误的
        Genericity<Integer, String> one = new SubGenericity();

        // 由于继承时,已经失去了泛型特性,所以,没有 <> 了 
        SubGenericity two = new SubGenericity();
    }

}
  • 情况三:子类保留父类的泛型特性。
// 注意:这种情况下,两个 <> 中的表示 类型参数 的字母顺序、数量、内容要完全一致
//  像 class SubGenericity<E, V> extends Genericity<K, V> 是不允许的
// 父类的 <> 中的数量必须和之前父类定义的时候是一致的
// 像 class SubGenericity<K, V> extends Genericity<K, V, E> 是一定不被允许的
// class SubGenericity<V, E> extends Genericity<V, E> 是允许的
public class SubGenericity<K, V> extends Genericity<K, V> {
    
}
public class GenericityTest {

    public static void main(String[] args) {
        Genericity<String, String> one = new SubGenericity<>();

        SubGenericity<Integer, String> two = new SubGenericity<>();
    }

}
  • 情况四:子类保留父类的泛型特性,但同时,子类可以增加自己的泛型。子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型。
// 其中 E 是子类自身所增加的泛型
// 注意:子类增加的泛型放在子类的 <> 中的哪一个位置上没有规定,但是增加泛型的前提是保证父类的 <> 中的东西,子类的 <> 中也必须有
// class SubGenericity<K, V, E> extends Genericity<K, V> 可以
// class SubGenericity<E, K, V> extends Genericity<K, V> 可以
public class SubGenericity<K, E, V> extends Genericity<K, V> {

    private E elem;

}
public class GenericityTest {

    public static void main(String[] args) {
        Genericity<Integer, String> one = new SubGenericity<Integer, Boolean, String>();

        SubGenericity<Integer, Boolean, String> two = new SubGenericity<>();
    }

}

6、泛型方法

  • 泛型方法(包括自定义泛型方法)就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。我们在调用这个泛型方法的时需要对泛型参数进行实例化。

  • 关于泛型方法,大家可能会将下面的这样的情况误认为是泛型方法:

public class Genericity<K, V> {

    private K key;
    private V value;

    public void set(K key, V value) {

    }

    public V get(K key) {
        return null;
    }

}

上面的代码中,可以看到setget这两个方法中都带有“泛型参数”,但是严格意义上说它们不是泛型方法,因为这些方法中的“泛型参数”,在被调用之前,就已经被具体化了,因为要使用这些方法的前提是必须先实例化对象,而一旦实例化就必须指定类的泛型参数,进而就指定这些方法中的“泛型参数”。

真正意义上的泛型方法,应该是其泛型参数与这个类的泛型参数无关。

public class GenericityTest {

    public static void main(String[] args) {
        Genericity<Integer, String> one = new Genericity<>();
        // 方法中的“泛型参数”已经被具体化了
        // void set(Integer key, String value) {······}
        // String get(Integer key) {······}
    }

}
  • 泛型方法的格式:
    [访问权限] [static] <泛型> 返回值类型 方法名([泛型标识 参数名称]) { 方法体; }

  • 泛型方法的举例:

public class Genericity {

    public <T> void printArray(T[] arr) {
        for (T elem : arr) {
            System.out.println(elem);
        }
    }

}

可以看到,上面的这个方法,是真正意义上的泛型方法。泛型参数的具体类型,在该方法使用的时候才被指定。

public class GenericityTest {

    public static void main(String[] args) {
        Genericity genericity = new Genericity();

        Integer[] arr1 = {1, 2, 3};
        genericity.printArray(arr1);

        String[] arr2 = {"Tom", "Jerry"};
        genericity.printArray(arr2);
    }

}

1
2
3
Tom
Jerry
  • 静态方法使用泛型参数的时候,需要我们把静态方法定义为泛型方法。这一点,也比较容易理解,一个方法中如果出现泛型参数,无外乎两种情况,一种情况是该方法的泛型参数是与类一致的,另一种情况是该方法是泛型方法。如果是前者,显然这个方法是绝不可能使用static修饰,否则的话,这个方法中的泛型参数如何与类保持一致?至于后者,方法中的泛型参数和类的泛型参数(如果有的话)是无关的,所以,是可以将其用static修饰以方便使用。
public class Genericity {

    public static <T> void printArray(T[] arr) {
        for (T elem : arr) {
            System.out.println(elem);
        }
    }

}
public class GenericityTest {

    public static void main(String[] args) {
        Integer[] arr1 = {1, 2, 3};
        // 通过类名直接调用
        Genericity.printArray(arr1);

        String[] arr2 = {"Tom", "Jerry"};
        Genericity.printArray(arr2);
    }

}

7、泛型通配符

  • 如果 B 是 A 的一个子类或子接口,而 G 是具有泛型声明的类或接口,则 G<B> 并不是 G<A> 的子类型!比如:String 是 Object 的子类,但是 List<String> 并不是 List<Object> 的子类。

  • 泛型中有三种通配符形式:
    <?>:无限制通配符,表示可以传入任意类型的参数。
    <? extends E>:表示类型的上界是 E,只能是 E 或者是 E 的子类。
    <? super E>:表示类型的下界是 E,只能是 E 或者是 E 的父类。

import java.util.ArrayList;
import java.util.List;

public class GenericityTest {

    public static void main(String[] args) {
        List<?> list = new ArrayList<>();
        // 注意,只能获取不能添加
    }

}
public class Genericity {

}
public class SubGenericity extends Genericity {

}
import java.util.ArrayList;
import java.util.List;

public class GenericityTest {

    public static void main(String[] args) {
        List<? extends Genericity> list = new ArrayList<>();
        // 注意,只能获取不能添加

        // 解释一下为什么不能添加:首先要明确 List<? extends Genericity> 的意义
        // List<? extends Genericity> 的含义应该是 List<Genericity> 或者 List<Genericity的某个子类>
        // 所以,这样的话,add(new Genericity()) 是不合适的,万一是 List<SubGenericity> 
        // add(new SubGenericity()) 也是不合适的,万一是 List<OtherSubGenericity>
        // 如果是获取的话,必须使用 Genericity 类型的引用来接收,其他的类型都不可以,因为不管怎么样,放在其中的东西,都是 Genericity 类或其子类
    }

}
import java.util.ArrayList;
import java.util.List;

public class GenericityTest {

    public static void main(String[] args) {
        List<? super Genericity> list = new ArrayList<>();

        list.add(new Genericity());
        list.add(new SubGenericity());
        // 注意,可以获取也可以添加,但是添加的必须是 Genericity 类或其子类,因为下限是 Genericity 
        // 尽管能添加了,但还是说要理解 List<? super Genericity> 的意义,List<Genericity> 或 List<Genericity的某个父类>
    }

}
  • 关于泛型的这个通配符?及其限制通配符,一般在 Java 的源码中比较常见,而开发的时候,不大常用,一般理解其含义即可。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容