zoukankan      html  css  js  c++  java
  • Effective Java 读书笔记(四):泛型

    1 不要使用原始类型

    (1)术语

    术语 例子
    参数化类型(Parameterized type) List<String>
    实际类型参数(Actual type parameter) String
    泛型类型(Generic type) List<E>
    形式类型参数(Formal type parameter) E
    无限制通配符类型(Unbounded wildcard type) List<?>
    原始类型(Raw type) List
    有限制类型参数(Bounded type parameter) <E extends Number>
    递归类型限制(Recursive type bound) <T extends Comparable<T>>
    有限制通配符类型(Bounded wildcard type) List<? extends Number>
    泛型方法(Generic method) static <E> List< E > asList(E[] a)
    类型令牌(Type token) String.class

    (2)为什么不使用原始类型?

    • 原始类型
    // 按照这么写并不会报错(List内部由一个Object数组维护),但是使用上很容易出错。
    List list = new ArrayList();
    list.add("Hello");
    list.add(100);
    
    • 原始类型失去了安全性(强转异常)
    • 原始类型失去了可读性(Object)

    2 消除未检查警告

    (1)概述

    • 消除未检查警告可以减少ClassCastException的发生。
    • 当警告无法消除且代码没有问题的情况下,使用@SuppressWarnings("unchecked")注解来禁止这个警告。
    • 消除警告尽可能具体。

    (2)示例

    public <T> T[] toArray(T[] a) {
        if (a.length < size) {
            @SuppressWarnings("unchecked") 
            T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass());
            return result;
        }
        System.arraycopy(elements, 0, a, 0, size); 
        if (a.length > size)
            a[size] = null; 
        return a;
    }
    

    3 列表优于数组

    (1)数组是协变的,泛型是收约束的。

    • 如果Sub是Super的一个子类型,那么数组类型Sub[]也是数组类型Super[]的子类型。
    // 运行时报错
    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");
    

    (2)数组是具化的,泛型是可擦除的。

    • 数组在运行时才知道并检查元素类型。

    • E,List<E>,和List<String>这些类型在技术上都被称为不可具化类型,即运行时展示信息比编译时展示信息要少的类型。

    (3)数组提供了运行时类型安全性,不保证编译时安全性,泛型则反过来。

    4 优先考虑泛型

    public class Stack<E> {
        private E[] elements;
        private int size = 0;
        private static final int DEFAULT_INITIAL_CAPACITY = 16;
        
        // The elements array will contain only E instances from push(E).
    	// This is sufficient to ensure type safety, but the runtime 
    	// type of the array won't be E[]; it will always be Object[]! 
    	@SuppressWarnings("unchecked")
        public Stack() {
            elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
        }
        
        public void push(E e) { 
            ensureCapacity(); 
            elements[size++] = e;
        }
        
        public E pop() { 
            if (size == 0) throw new EmptyStackException();
            E result = elements[--size];
            elements[size] = null; // Eliminate obsolete reference return result;
        }
        
        // no changes in isEmpty or ensureCapacity 
    }
    

    5 优先使用泛型方法

    (1)泛型方法

    • 原始类型方法
    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;
    }
    

    (2)恒等函数分发器

    • 每次创建新的恒等函数对象
    public static <T> UnaryOperator<T> identityFunction() {
        return (t) -> t;
    }
    
    • 缓存一个泛型单例,节约内存且足够应付所有的情况
    private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
    
    @SuppressWarnings("unchecked")
    public static <T> UnaryOperator<T> identityFunction() {
        return (UnaryOperator<T>) IDENTITY_FN;
    }
    

    (3)递归类型限制

    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;
    }
    

    注:当列表是空的时候,这个方法会抛出IllegalArgumentException异常。一种更好的办法是返回一个Optional<E>。

    6 使用有限制通配符来增加API的灵活性

    (1)demo1

    public class Stack<E> {
        public Stack();
        public void push(E e);
        public E pop();
        public boolean isEmpty();
        
        // 生产者
        public void pushAll(Iterable<? extends E> src) {
            for (E e : src)
                push(e);
        }
        
        // 消费者
        public void popAll(Collection<? super E> dst) { 
            while (!isEmpty())
                dst.add(pop()); 
        }
    }
    
    // 使用pushAll
    Stack<Number> numberStack = new Stack<>();
    Iterable<Integer> integers = ... ;
    numberStack.pushAll(integers);
    
    // 使用popAll
    Stack<Number> numberStack = new Stack<Number>(); 
    Collection<Object> objects = ... ; 
    numberStack.popAll(objects);
    
    • ? extends E修饰的集合:存放E或E的子类
    • ? super E修饰的集合:存放E或E的父类

    (2)demo2

    // 修改前
    public static <E> Set<E> union(Set<E> s1, Set<E> s2);
    // 修改后
    public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2);
    // 使用
    Set<Integer> integers = Set.of(1, 3, 5);
    Set<Double> doubles = Set.of(2.0, 4.0, 6.0); 
    Set<Number> numbers = union(integers, doubles);
    
    • 泛型作为返回值时,E代表返回E或E的子类。

    具体参考:泛型总结

    (3)demo3

    // 修改前
    public static <T extends Comparable<T>> T max(List<T> list);
    // 修改后
    public static <T extends Comparable<? super T>> T max(List<? extends T> list);
    // 使用
    // ScheduledFuture不直接实现Comparable,但是它的父类接口实现了Comparable,所以为了支持这种情况需要修改为<T extends Comparable<? super T>>。
    List<ScheduledFuture<?>> scheduledFutures = ... ;
    max(scheduledFutures);
    
    • Comparable接口通常都是消费者,所以在一般情况下,你应该优先用Comparable<? super T>,而不是Comparable<T>。
    • 对于Comparator接口也应该如此,也就是应该优先使用Comparator<? super T>,而不是Comparator<T>

    (4)demo4

    // 无界类型参数
    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)));
    }
    

    7 考虑类型安全的异构容器

    (1)异构容器

    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> void putFavorite(Class<T> type, T instance) {
            favorites.put(type, type.cast(instance));
        }
        
        public <T> T getFavorite(Class<T> type) {
            return type.cast(favorites.get(type));
        }
    }
    
    // 使用
    public static void main(String[] args) {
        Favorites f = new Favorites();
        f.putFavorite(String.class, "Java");
        f.putFavorite(Integer.class, 0xcafebabe);
        f.putFavorite(Class.class, Favorites.class);
        String favoriteString = f.getFavorite(String.class);
        int favoriteInteger = f.getFavorite(Integer.class);
        Class<?> favoriteClass = f.getFavorite(Class.class);
        System.out.printf("%s %x %s%n", favoriteString,
        favoriteInteger, favoriteClass.getName());
    }
    

    (2)限定类型的令牌

    // 表示类,方法,属性和其他程序元素的反射类型实现
    public interface AnnotatedElement {
    	<T extends Annotation> T getAnnotation(Class<T> annotationType);
    }
    
    // 如果一个Class<?>的对象希望传递给接收Class<T>的泛型方法,可以将对象转换为Class<? extends Annotation>,但是会有编译时警告,所以需要借助asSubclass转换所调用的Class对象来表示由其参数表示的类的子类。
    static Annotation getAnnotation(AnnotatedElement element,String annotationTypeName) {
        Class<?> annotationType = null; // Unbounded type token
        try {
            annotationType = Class.forName(annotationTypeName);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
        return element.getAnnotation(annotationType.asSubclass(Annotation.class));
    }
    
  • 相关阅读:
    洛谷P2661: 信息传递(图的遍历)
    洛谷P1305: 新二叉树
    洛谷 P1030 :求先序排列
    POJ 3041:Asteroids(二分图最大匹配)
    洛谷P2774 :方格取数问题( 网络流24题 奇偶建图+最小割)
    hdu 3061:Battle(最大权闭合图)
    hdu 1532:Drainage Ditches(Dinic算法)
    洛谷P1345: [USACO5.4]奶牛的电信Telecowmunication(拆点+最小割)
    hihoCoder1121 : 二分图一•二分图判定
    (转载)javascript客户端生成MD5值的函数代码
  • 原文地址:https://www.cnblogs.com/linzhanfly/p/10232456.html
Copyright © 2011-2022 走看看