转载

关于JAVA泛型那点事

概述

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 在面向对象编程及各种设计模式中有非常广泛的应用。泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是 参数化类型 ,也就是说所操作的数据类型被指定为一个参数。

注意参数的类型只能是引用类型,不能是原始类型(int, char, double 这种),据说是当初设计师偷懒!!!

根据惯例,类型参数是单个大写字母,该字母用于指示所定义的参数类型。下面列出每个用例的标准类型参数:

  • T 类型
  • E 元素
  • K 键
  • V 值
  • N 数字
  • S、U、V 等:多参数情况中的第 2、3、4 个类型

泛型类别

泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分,类型可以有多个:。最典型的就是各种容器类,如:List, Set, Map

List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
Map<String, Object> map = new HashMap<>();
复制代码

举个例子

通常我们在开发中会定义一个统一的接口返回类型,根据传入的参数类型 T 不同可以返回不同的数据类型,其中 T 可以用其他标识代替,如K, V, U

public class HttpResult<T> {

    private T data;

    public HttpResult(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static void main(String[] args) {
        HttpResult<String> strResult = new HttpResult<>("String类型");
        HttpResult<Boolean> booleanResult = new HttpResult<>(true);
        HttpResult<Integer> integerResult = new HttpResult<>(100);
        System.out.println("str:" + strResult.getData());
        System.out.println("boolean:" + booleanResult.getData());
        System.out.println("integer:" + integerResult.getData());
    }
}
复制代码

原始类 (raw class)

如果我们使用一个泛型类,但是不带类型参数,那么我们使用的是原始类。

List list = new ArrayList();
list.add(new Object());
list.add(99);
list.add("123");
复制代码

泛型方法

  1. 泛型方法,和泛型类相似,是在调用方法的时候指明泛型的具体类型 。
  2. 所有泛型方法声明都有一个 类型参数声明部分(由尖括号分隔: )方法返回类型之前
  3. static 方法和static 域均不可以引用类的类型变量,因为存在 泛型擦除 ,后面讲
  4. 只有声明了类型参数的方法才是泛型方法,泛型类中使用了泛型的方法不是泛型方法

扩展上面的例子

public class HttpResult<T> {

    private int code;

    private String msg;

    /**
     * 具体返回数据,定义为泛型
     * T 可以为其他任意标识 E,K,V等
     */
    private T data;

    /**
     * 这个不是泛型方法,虽然用到了 T
     * 但是返回类型前面没有使用 类型声明
     */
    public HttpResult(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    /**
     * 这个是泛型方法
     * 返回类型前面有类型声明<T>,T 可以为其他标识
     * !注意 这里的类型声明和类的类型申明都是 <T>,但这2个声明是不相关的,
     * static 方法和static 域均不可以引用类的类型变量,应为存在泛型擦除。
     * 为了防止混淆,这里可以使用其他标识D代替:
     * public static <D> HttpResult<D> success(D data){
     *     return new HttpResult<>(200, "success", data);
     * }
     */
    public static <T> HttpResult<T> success(T data) {
        return new HttpResult<>(200, "success", data);
    }
复制代码

泛型接口

和定义泛型类相似。

public interface GenericService<T> {

    T getOwn();
}
复制代码

接口实现未传入具体类型的,仍然以泛型的形式实现

public class GenericServiceImpl<T> implements GenericService<T> {

    /**
     * 仍然显示泛型
     */
    @Override
    public T returnSelf(T oldEntity) {
        System.out.println(oldEntity.getClass());
        return oldEntity;
    }
}
复制代码

接口实现传入具体类型的,显示具体类型

public class GenericServiceImpl<Integer> implements GenericService<Integer> {

    /**
     * 这里用传入的Integer类型替换掉原来的泛型
     */
    @Override
    public Integer returnSelf(Integer oldEntity) {
        System.out.println(oldEntity.getClass());
        return oldEntity;
    }
}
复制代码

通配符

定义基本关系

// 基本树形结构
class BaseTree {}

// 部门树
class DepartmentTree extends BaseTree {}

// 菜单树继承基本树结构
class MenuTree extends BaseTree {}
复制代码

无界通配符 ?

问号 ( ? ) 通配符可用于使用泛型代码表示未知类型。通配符可用于参数、字段、局部变量和返回类型。但最好不要在返回类型中使用通配符,因为确切知道方法返回的类型更安全。

假设我们想编写一个方法来验证指定的 List 中是否存在指定的对象。我们希望该方法接受两个参数:一个是未知类型的 List ,另一个是任意类型的对象。

public static <T> void checkList(List<?> myList, T obj){
    if(myList.contains(obj)){
        System.out.println("The list contains the element: " + obj);
    } else {
        System.out.println("The list does not contain the element: " + obj);
    }
}
复制代码

上限通配符

限制类型为特定类型或者特定类型的子类

当获取数据是会隐式的转为其基类(或者Object基类)

    /**
     * 只能传 BaseTree 或者它的子类 DepartmentTree、 menuTree
     *
     * @param tree 传入的树结构
     */
    public static <T extends BaseTree> T UpperBoundType(T tree) {
        if (tree instanceof DepartmentTree) {
            System.out.println("depTree");
        } else if (tree instanceof MenuTree) {
            System.out.println("menuTree");
        } else {
            System.out.println("baseTree");
        }
        return tree;
    }
复制代码
    /**
     * 指定类型 T 返回类型 T
     */
    public static <T extends BaseTree> T UpperBoundList(List<T> treeList) {
        T tree = treeList.get(0);
        if (tree instanceof DepartmentTree) {
            System.out.println("depTree");
        } else if (tree instanceof MenuTree) {
            System.out.println("menuTree");
        } else {
            System.out.println("baseTree");
        }
        return tree;
    }
复制代码
    /**
     * 使用 <? extends BaseTree>
     * 获取列表元素时只能使用父类去接受,
     * 但还是可以获取到子类的类型
     */
    public static BaseTree UpperBoundList(List<? extends BaseTree> treeList) {
        // 这里只能使用BaseTree去接收参数,要用子类型需要强转
        BaseTree tree = treeList.get(0);
        // 传入DepartmentTree 或者 MenuTree时,下面的依然可以执行
        if (tree instanceof DepartmentTree) {
            System.out.println("depTree");
            DepartmentTree base = (DepartmentTree) tree;
        } else if (tree instanceof MenuTree) {
            System.out.println("menuTree");
            MenuTree ment = (MenuTree) tree;
        } else {
            System.out.println("baseTree");
            BaseTree base = (BaseTree) tree;
        }
        return tree;
    }
复制代码

下限通配符

限制类型为特定类型或者特定类型的超类

    /**
     * 只能传MenuTree或者它的父类的List
     * 接收类型为Object
     * 需要强转到对应的类型
     */
    public static void LowerBoundType(List<? super MenuTree> menuTree) {
        Object obj = menuTree.get(0);
        if (obj instanceof MenuTree) {
            System.out.println("menuTree");
            MenuTree ment = (MenuTree) obj;
        } else if (obj instanceof BaseTree) {
            System.out.println("baseTree");
            BaseTree base = (BaseTree) obj;
        }
    }
复制代码

泛型擦除

泛型类可以由编译器通过所谓的类型擦除过程而转变成非泛型类。这样,编译器就生成了一种与泛型类同名的 原始类 ,但是类型参数都被删除了。对应的类型变量由他们的类型界限来代替,上限通配符的为其父类,下限通配符的为Object。

泛型的限制

基本类型

基本类型(int,double)不能用做类型参数,必须使用包装类(Integer, Double)

static 语境

在一个泛型类中,static方法和static域都不可以引用类的类型变量,因为类在类型擦除后就不存在类型变量了。

泛型类的实例化

不能创建一个泛型类型的实例化,下面代码是错误的:

T obj = new T();
复制代码

在类型擦除后T由它的限界代替,可能是Object(甚至是抽象类),因此对new没有调用意义。

泛型数组

同样不能创建一个泛型数组,下面代码是错误的:

T [] arr = new T(10);
复制代码

在类型擦除后T由它的限界代替,很可能是Object T,于是对T[]的类型转换将无法进行,应为Object[] IS-NOT-A T[]。

一般不建议创建泛型数组,尽量使用ArrayList来代替泛型数组。下面提供了一种创建泛型数组的方法

public class GenericArrayFactory<T> {

    private T[] array;

    /**
     * 初始化数组
     *
     * @param componentType 数组的类别
     * @param length        容量
     */
    public void init(Class<T> componentType, int length) {
        // 这里会有一个类型转换警告 Object to T[]
        //noinspection unchecked
        array = (T[]) Array.newInstance(componentType, length);
    }

    /**
     * 赋值
     *
     * @param index 索引
     * @param item  值
     */
    public void put(int index, T item) {
        array[index] = item;
    }

    /**
     * 获取数组
     *
     * @return arr
     */
    public T[] getArray() {
        return array;
    }

    public static void main(String[] args) {
        GenericArrayFactory<Integer> genericArrayFactory = new GenericArrayFactory<>();
        // 初始化Integer类型的数组
        genericArrayFactory.init(Integer.class, 3);
        genericArrayFactory.put(0, 9);
        genericArrayFactory.put(1, 8);
        genericArrayFactory.put(2, 7);
        Integer[] intArray = genericArrayFactory.getArray();
        for (Integer integer : intArray) {
            System.out.println(integer);
        }
    }
}
复制代码
原文  https://juejin.im/post/5de508caf265da05f84bbadd
正文到此结束
Loading...