第五章泛型

作者: 后来丶_a24d | 来源:发表于2020-03-11 10:58 被阅读0次

目录

  • 不要使用原始类型
  • 消除非检查警告
  • 列表优于数组
  • 优先考虑泛型
  • 优先使用泛型方法
  • 使用限定通配符来增加API的灵活性
  • 合理地结合泛型和可变参数
  • 优先考虑类型安全的异构容器

泛型

不要使用原始类型

  • 如果你使用原始类型,则会丧失泛型的所有安全性和表达上的优势
  • 你必须在类字面值(class literals)中使用原始类型。egList.class,String [] .class和int.class都是合法的。o instanceof Set需要使用原始类型。
  • 限制类型参数<E extends Number》,递归类型限制<T extends Comparable<T>>,限制通配符类型List<? extends Number>,泛型方法 static <E> List<E> asList(E[] a)

消除非检查警告

  • 如果你不能消除警告,但你可以证明引发警告的代码是类型安全的,那么(并且只能这样)用@SuppressWarnings(“unchecked”)注解来抑制警告
  • 声明一个局部变量来保存返回值并标注它的声明
@SuppressWarnings("unchecked") T[] result =
            (T[]) Arrays.copyOf(elements, size, a.getClass());
retur result;

列表优于数组

  • 数组提供运行时类型的安全性,但不提供编译时类型的安全性,反之亦然。 一般来说,数组和泛型不能很好地混合工作。 如果你发现把它们混合在一起,得到编译时错误或者警告,你的第一个冲动应该是用列表来替换数组
  • 数组是协变的,泛型是收约束的。
// 运行时报错
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in"; // Throws ArrayStoreException

// 无法编译通过
List<Object> ol = new ArrayList<Long>(); // Incompatible types
ol.add("I don't fit in");
  • 数组提供了运行时类型安全性,不保证编译时安全性,泛型则反过来

优先考虑泛型

  • 尝试创建Stack<int>或Stack<double>将导致编译时错误。 这是Java泛型类型系统的一个基本限制
  • 泛型类型比需要在客户端代码中强制转换的类型更安全,更易于使用。 当你设计新的类型时,确保它们可以在没有这种强制转换的情况下使用
  • 这个类应该已经被参数化了,但是由于事实并非如此,我们可以对它进行泛型化。 就目前而言,客户端必须强制转换从堆栈中弹出的对象,而这些强制转换可能会在运行时失败。
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }
  • 可参考Stack对这种泛型的写法,它解决了初始化不能用new E()的问题,用到了在返回ele时强转Object到E。

优先使用泛型方法

  • 泛型方法比需要客户端对输入参数和返回值进行显式强制转换的方法更安全,更易于使用。 像类型一样,你应该确保你的方法可以不用强制转换,这通常意味着它们是泛型的。
// 这种会有警告
public static Set union(Set s1, Set s2) {

    Set result = new HashSet(s1);

    result.addAll(s2);

    return result;
}

//这种是安全的
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {

    Set<E> result = new HashSet<>(s1);

    result.addAll(s2);

    return result;

}
  • 递归类型限制
public static <E extends Comparable<E>> E max(Collection<E> c) {
    if (c.isEmpty()) throw new IllegalArgumentException("Empty collection");
    E result = null;
    for (E e : c){
        if (result == null || e.compareTo(result) > 0){
            result = Objects.requireNonNull(e);
        }
    }
    return result;
}

使用限定通配符来增加API的灵活性

  • 参数化类型是不变的。换句话说,对于任何两个不同类型的Type1和Type,List <Type1>既不是List <Type2>子类型也不是其父类型
  • 为了获得最大的灵活性,对代表生产者或消费者的输入参数使用通配符类型
//考虑以下这种,如果pushAll方法是public void pushAll(Iterable<E> src) 则会报错,因为参数化类型是不变的。
Stack<Number> numberStack = new Stack<>();

Iterable<Integer> integers = ... ;

numberStack.pushAll(integers);

//可以变成这种, 这样增加灵活性。生成栈使用的E实例使用extend
public void pushAll(Iterable<? extends E> src)
// 消费栈使用的E实例使用super 
public void popAll(Collection<? super E> dst)
// 所有Comparable和Comparator都是消费者
  • 返回类型仍然是Set <E>。 不要使用限定通配符类型作为返回类型
  • 灵活性体现
// 无界类型参数
public static <E> void swap(List<E> list, int i, int j); 

// 无界通配符:该方式优于上一个方式,但是由于无界通配符类型无法修改,即需要借助helper进行修改,但这对于调用者无需关心。
public static void swap(List<?> list, int i, int j) { 
    swapHelper(list, i, j);
}

private static <E> void swapHelper(List<E> list, int i, int j) { 
    list.set(i, list.set(j, list.get(i)));
}
汇总
  1. 上边界类型通配符(<? extends 父类型>):因为可以确定父类型,所以可以以父类型去获取数据(向上转型)。但是不能写入数据。
  2. 下边界类型通配符(<? super 子类型>):因为可以确定最小类型,所以可以以最小类型去写入数据(向上转型)。而不能获取数据。
  3. 无边界类型通配符(<?>) 等同于 上边界通配符<? extends Object>,所以可以以Object类去获取数据。List list 相当于List<Object> list

合理地结合泛型和可变参数

  • 可变参数和泛型不能很好地交互,因为可变参数机制是在数组(协变,)上面构建的脆弱的抽象,并且数组具有与泛型不同的类型规则。 虽然泛型可变参数不是类型安全的,但它们是合法的。 如果选择使用泛型(或参数化)可变参数编写方法,请首先确保该方法是类型安全的

优先考虑类型安全的异构容器

  • 泛型API的通常用法(以集合API为例)限制了每个容器的固定数量的类型参数。 你可以通过将类型参数放在键上而不是容器上来解决此限制。 可以使用Class对象作为此类型安全异构容器的键。 以这种方式使用的Class对象称为类型令牌。
  • 与普通Map不同,所有的键都是不同的类型。 因此,我们将Favorites称为类型安全异构容器
public class Favorites {
    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(Objects.requireNonNull(type), instance);
    }

    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}
  • 如果你尝试保存你最喜欢的List <String>,程序将不能编译。 原因是无法获取List <String>的Class对象。 List <String> .class是语法错误,也是一件好事。 List <String>和List <Integer>共享一个Class对象
  • 现有实践是把各个Service类作为key

参考文章

相关文章

  • 泛型 & 注解 & Log4J日志组件

    掌握的知识 : 基本用法、泛型擦除、泛型类/泛型方法/泛型接口、泛型关键字、反射泛型(案例) 泛型 概述 : 泛型...

  • 【泛型】通配符与嵌套

    上一篇 【泛型】泛型的作用与定义 1 泛型分类 泛型可以分成泛型类、泛型方法和泛型接口 1.1 泛型类 一个泛型类...

  • 泛型的使用

    泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法 泛型类 泛型接口 泛型通配符 泛型方法 静态方法与...

  • Java 泛型

    泛型类 例如 泛型接口 例如 泛型通配符 泛型方法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型上下边...

  • 探秘 Java 中的泛型(Generic)

    本文包括:JDK5之前集合对象使用问题泛型的出现泛型应用泛型典型应用自定义泛型——泛型方法自定义泛型——泛型类泛型...

  • Web笔记-基础加强

    泛型高级应用 自定义泛型方法 自定义泛型类 泛型通配符? 泛型的上下限 泛型的定义者和泛型的使用者 泛型的定义者:...

  • 重走安卓进阶路——泛型

    ps.原来的标题 为什么我们需要泛型? 泛型类、泛型接口和泛型方法(泛型类和泛型接口的定义与泛型方法辨析); 如何...

  • Kotlin泛型的高级特性(六)

    泛型的高级特性1、泛型实化2、泛型协变3、泛型逆变 泛型实化 在Java中(JDK1.5之后),泛型功能是通过泛型...

  • Java 19-5.1泛型

    泛型类定义泛型类可以规定传入对象 泛型类 和泛型方法 泛型接口 如果实现类也无法确定泛型 可以在继承类中确定泛型:

  • 【Swift】泛型常见使用

    1、Swift泛型4种 泛型函数泛型类型泛型协议泛型约束 2、泛型约束3种 继承约束:泛型类型 必须 是某个类的子...

网友评论

    本文标题:第五章泛型

    本文链接:https://www.haomeiwen.com/subject/lkkqjhtx.html