Effective java笔记-泛型

本文笔记探讨了泛型在Java编程中的应用,强调避免使用原生态类型,消除非受检警告,优先选择列表而非数组,重视泛型和泛型方法的使用,利用有限制通配符提升API灵活性,以及采用类型安全的异构容器来增强代码安全性。
摘要由CSDN通过智能技术生成

泛型

第23条 请不要在新代码中使用原生态类型

书上示例代码:
public class Raw {
    // Uses raw type (List) - fails at runtime! - Page 112
    public static void main(String[] args) {
        List<String> strings = new ArrayList<String>();
        unsafeAdd(strings, new Integer(42));
        String s = strings.get(0); // Compiler-generated cast
    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }

    // Use of raw type for unknown element type - don't do this! - Page 113
    static int rawNumElementsInCommon(Set s1, Set s2) {
        int result = 0;
        for (Object o1 : s1)
            if (s2.contains(o1))
                result++;
        return result;
    }
    //如果要使用泛型,但不确定或者不关心实际的类型参数(如上面的例子),就可以使用一个问好代替。例如,泛型Set<E>的无限制通配符类型为Set<?>(读作“某个类型的集合”)
    // Unbounded wildcard type - typesafe and flexible - Page 113
    static int numElementsInCommon(Set<?> s1, Set<?> s2) {
        int result = 0;
        for (Object o1 : s1)
            if (s2.contains(o1))
                result++;
        return result;
    }
}
    //为什么说Set<?>是类型安全的:你不能将除了null外的任何元素放入Collection<?>中

第24条 消除非受检警告

如果无法消除警告,同时可以证明引起警告的代码是类型安全的,(只有在这种情况下才)可以用一个@SuppressWarnings(“unchecked”)注解来禁止这条警告,并且要在尽可能小粒度上使用该注解。

第25条 列表优先于数组

1.数组是协变的(covariant),列表是不可变的(invariant):如果Sub为Super的子类型,那么Sub[]就是Super[]的子类型;对于任意两个不同的类型Type1和Type2,List<Type1>既不是List<Type2>的子类型,也不是List<Type2>超类型。
2.数组是具体化的(reified)。因此数组会在运行时才知道并检查它们的元素类型约束。泛型时通过擦除(erasure)来实现的,因此泛型只在编译时强化它们的类型信息,并在运行时丢弃(或擦除)它们的元素类型信息。
3.由于以上两点,数组和泛型不能很好的混合使用(创建泛型、参数化类型或类型参数的数组时非法的),为什么非法:因为它们不是类型安全的,要是它合法,编译器在其他正确程序中的转换就会失败,**这就违背了泛型系统提供的基本保证**,例如:
List<String>[] stringLists = new List<String>[1];
List<Integer> intList = Arrays.asList(42);
Object[] objects = stringLists;
objects[0] = intList;
String s = stringLists[0].get(0);//ClassCastException
直观的说,不可具体化的类型是指其运行时表示法包含的信息比他编译时表示法包含的信息更少的类型。唯一可具体化的参数化类型是无限制的通配符类型,虽然不常用,但是创建无限制通配符类型的数组是合法的。

25条后半段涉及到什么同步列表,没看懂,贴下代码:
interface Function<T> {
    T apply(T arg1, T arg2);
}



public class Reduction {
    static <E> E reduce(List<E> list, Function<E> f, E initVal) {
        List<E> snapshot;
        synchronized (list) {
            snapshot = new ArrayList<E>(list);
        }
        E result = initVal;
        for (E e : snapshot)
            result = f.apply(result, e);
        return result;
    }

    // A few sample functions

    private static final Function<Integer> SUM = new Function<Integer>() {
        public Integer apply(Integer i1, Integer i2) {
            return i1 + i2;
        }
    };

    private static final Function<Integer> PRODUCT = new Function<Integer>() {
        public Integer apply(Integer i1, Integer i2) {
            return i1 * i2;
        }
    };

    private static final Function<Integer> MAX = new Function<Integer>() {
        public Integer apply(Integer i1, Integer i2) {
            return Math.max(i1, i2);
        }
    };

    private static final Function<Integer> MIN = new Function<Integer>() {
        public Integer apply(Integer i1, Integer i2) {
            return Math.min(i1, i2);
        }
    };

    public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(2, 7, 1, 8, 2, 8, 1, 8, 2, 8);

        // Reduce intList using each of the above reducers
        System.out.println(reduce(intList, SUM, 0));
        System.out.println(reduce(intList, PRODUCT, 1));
        System.out.println(reduce(intList, MAX, Integer.MIN_VALUE));
        System.out.println(reduce(intList, MIN, Integer.MAX_VALUE));
    }
}

第26条 优先考虑泛型

考虑自己编写一个带泛型的Stack,第一种:
public class EmptyStackException extends RuntimeException {
}

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

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }

    // Little program to exercise our generic Stack
    public static void main(String[] args) {
        Stack<String> stack = new Stack<String>();
        for (String arg : args)
            stack.push(arg);
        while (!stack.isEmpty())
            System.out.println(stack.pop().toUpperCase());
    }
}
第二种:
public class EmptyStackException extends RuntimeException {
}

public class Stack<E> {
    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(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    // Appropriate suppression of unchecked warning
    public E pop() {
        if (size == 0)
            throw new EmptyStackException();

        // push requires elements to be of type E, so cast is correct
        @SuppressWarnings("unchecked")
        E result = (E) elements[--size];

        elements[size] = null; // Eliminate obsolete reference
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }

    // Little program to exercise our generic Stack
    public static void main(String[] args) {
        Stack<String> stack = new Stack<String>();
        for (String arg : args)
            stack.push(arg);
        while (!stack.isEmpty())
            System.out.println(stack.pop().toUpperCase());
    }
}
在上面两个示例中我们都使用了数组,这似乎于第25条矛盾,但是在有些地方为了性能的考虑必需用数组如ArrayList<E>,Stack<E>中。当数组与泛型一起出现的时候就可能出现一些问题,比如在第一种实现中我们我们声明了elements为E[],但java中无法new E[],所以采用了强制类型转换(E[])。第二种实现中,elements被声明为Object[],所以某些地方就要强制转换(E)。推荐第二种。

第27条 优先考虑泛型方法

public class Union {

    // Generic method
    public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
        Set<E> result = new HashSet<E>(s1);
        result.addAll(s2);
        return result;
    }

    // Simple program to exercise generic method
    public static void main(String[] args) {
        Set<String> guys = new HashSet<String>(Arrays.asList("Tom", "Dick",
                "Harry"));
        Set<String> stooges = new HashSet<String>(Arrays.asList("Larry", "Moe",
                "Curly"));
        Set<String> aflCio = union(guys, stooges);
        System.out.println(aflCio);
    }
}
上面union方法的局限性在于,三个集合的类型(两个输入参数和一个返回值)必需全部相同。利用有限制的通配符类型,可以使这个方法变得更加灵活(见第28条)。

泛型方法有类型推导的特性,但构造器没有,可以利用这个特性,使创建参数化类型实例变得更加轻松(第一条就已经谈过):
public class GenericStaticFactory {
    // Generic static factory method
    public static <K, V> HashMap<K, V> newHashMap() {
        return new HashMap<K, V>();
    }

    public static void main(String[] args) {
        // Parameterized type instance creation with static factory
        Map<String, List<String>> anagrams = newHashMap();
    }
}
泛型单例工厂:有时会需要创建不可变但又适合于许多不同类型的对象(比如一些函数对象如Collections.reverseOrder)
例子:假设有一个接口,描述了一个方法,该方法接受和返回某个类型T的值:
public interface UnaryFunction<T> {
    T apply(T arg);
}
现在假设要提供一个恒等函数(identity function)(一个函数对象).可以这样:
public class GenericSingletonFactory {
    // Generic singleton factory pattern
    private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>() {
        public Object apply(Object arg) {
            return arg;
        }
    };

    // IDENTITY_FUNCTION is stateless and its type parameter is
    // unbounded so it's safe to share one instance across all types.
    @SuppressWarnings("unchecked")
    public static <T> UnaryFunction<T> identityFunction() {
        return (UnaryFunction<T>) IDENTITY_FUNCTION;
    }

    // Sample program to exercise generic singleton
    public static void main(String[] args) {
        String[] strings = { "jute", "hemp", "nylon" };
        UnaryFunction<String> sameString = identityFunction();
        for (String s : strings)
            System.out.println(sameString.apply(s));

        Number[] numbers = { 1, 2.0, 3L };
        UnaryFunction<Number> sameNumber = identityFunction();
        for (Number n : numbers)
            System.out.println(sameNumber.apply(n));
    }
}
递归类型限制:
public class RecursiveTypeBound {
    // Returns the maximum value in a list - uses recursive type bound
    public static <T extends Comparable<T>> T max(List<T> list) {
        Iterator<T> i = list.iterator();
        T result = i.next();
        while (i.hasNext()) {
            T t = i.next();
            if (t.compareTo(result) > 0)
                result = t;
        }
        return result;
    }

    public static void main(String[] args) {
        List<String> argList = Arrays.asList(args);
        System.out.println(max(argList));
    }
}

第28条 利用有限制通配符来提升API灵活性

如25条所述,参数化类型是不可变的(invariant),这样如果我们有:
public class Stack<E>{
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}
    然后我们想要增加一个方法,让它按顺序将一系列的元素全部放到堆栈中,如下:
//pushAll method withod wildcard type - dificient!
public void pushAll(Iterable<E> src){
    for(E e :src)
        push(e);
}
假如有一个Stack<Number>,并且调用了pushAll(intVal),这里的intVal就是Integer类型的,这会产生编译时错误,因为参数化类型是不可变的,类似的还有popAll方法,它将Stack中的元素放入另一个容器中。解决方法:
public class EmptyStackException extends RuntimeException {
}

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

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }

    // pushAll method without wildcard type - deficient!
    // public void pushAll(Iterable<E> src) {
    // for (E e : src)
    // push(e);
    // }

    // Wildcard type for parameter that serves as an E producer
    public void pushAll(Iterable<? extends E> src) {
        for (E e : src)
            push(e);
    }

    // popAll method without wildcard type - deficient!
    // public void popAll(Collection<E> dst) {
    // while (!isEmpty())
    // dst.add(pop());
    // }

    // Wildcard type for parameter that serves as an E consumer
    public void popAll(Collection<? super E> dst) {
        while (!isEmpty())
            dst.add(pop());
    }

    // Little program to exercise our generic Stack
    public static void main(String[] args) {
        Stack<Number> numberStack = new Stack<Number>();
        Iterable<Integer> integers = Arrays.asList(3, 1, 4, 1, 5, 9);
        numberStack.pushAll(integers);

        Collection<Object> objects = new ArrayList<Object>();
        numberStack.popAll(objects);

        System.out.println(objects);
    }
}
PECS:producer-extends,consumer-super.上面代码中pushAll中的src是T的producer,popAll中dst是T的consumer

再来看之前已经看过的一些例子,用本条的原则来修改它们以提高其灵活性:
interface Function<T> {
    T apply(T arg1, T arg2);
}

public class Reduction {
    // Wildcard type for parameter that serves as an E producer
    static <E> E reduce(List<? extends E> list, Function<E> f, E initVal) {
        List<E> snapshot;
        synchronized (list) {
            snapshot = new ArrayList<E>(list);
        }
        E result = initVal;
        for (E e : snapshot)
            result = f.apply(result, e);
        return result;
    }

    private static final Function<Number> MAX = new Function<Number>() {
        public Number apply(Number n1, Number n2) {
            return Double.compare(n1.doubleValue(), n2.doubleValue()) > 0 ? n1
                    : n2;
        }
    };

    public static void main(String[] args) {
        // We can use a Number functionto reduce a list of Integer or Double
        List<Integer> intList = Arrays.asList(2, 7, 1, 8, 2, 8, 1, 8, 2, 8);
        System.out.println(reduce(intList, MAX, Integer.MIN_VALUE));

        List<Double> doubleList = Arrays.asList(2.718281828, 3.141592654,
                1.61803399);
        System.out.println(reduce(doubleList, MAX, Double.NEGATIVE_INFINITY));
    }
}
public class Union {

    public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2) {
        Set<E> result = new HashSet<E>(s1);
        result.addAll(s2);
        return result;
    }

    // Simple program to exercise flexible generic method
    public static void main(String[] args) {
        Set<Integer> integers = new HashSet<Integer>();
        integers.add(1);
        integers.add(3);
        integers.add(5);

        Set<Double> doubles = new HashSet<Double>();
        doubles.add(2.0);
        doubles.add(4.0);
        doubles.add(6.0);

        // Won't compile; see page 137
        // Set<Number> numbers = union(integers, doubles);

        // Explicit type parameter is necessary here
        Set<Number> numbers = Union.<Number> union(integers, doubles);//这里虽然编译器对泛型方法使用了类型推导,但这里编译器无法推导出E到底是什么,所以要给他一个显式的类型参数

        System.out.println(numbers);
    }
}
public class RecursiveTypeBound {
    public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
        Iterator<? extends T> i = list.iterator();
        T result = i.next();
        while (i.hasNext()) {
            T t = i.next();
            if (t.compareTo(result) > 0)
                result = t;
        }
        return result;
    }

    public static void main(String[] args) {
        List<String> argList = Arrays.asList(args);
        System.out.println(max(argList));
    }
}
Comparable是T的消费者,因为Comparable消费T并产生一个代表顺序关系的整值

public class Swap {
    public static void swap(List<?> list, int i, int j) {
        swapHelper(list, i, j);
    }

    // Private helper method for wildcard capture
    private static <E> void swapHelper(List<E> list, int i, int j) {
        list.set(i, list.set(j, list.get(i)));
    }

    public static void main(String[] args) {
        // Swap the first and last argument and print the resulting list
        List<String> argList = Arrays.asList(args);
        swap(argList, 0, argList.size() - 1);
        System.out.println(argList);
    }
}

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

泛型经常用于容器,但如果我们要一个里面可以存不同类型对象的容器,且要保证其类型安全(即类型安全的异构容器),应该将键进行参数化(可以用Class对象充当参数化键)而不是对容器参数化,这利用了方法的类型推导:
public class Favorites {
    // Typesafe heterogeneous container pattern - implementation
    private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();

    public <T> void putFavorite(Class<T> type, T instance) {
        if (type == null)
            throw new NullPointerException("Type is null");
        favorites.put(type, instance);
    }

    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }

    // Typesafe heterogeneous container pattern - client
    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());
    }
}
Favorites实例是类型安全的:当你向它请求String的时候,它从来不会返回一个Integer给你。
cast方法是Java的cast操作符的动态模拟。他只检验他的参数是否为Class对象所表示的类型的实例,如果是,就返回参数;否则就throw ClassCastException(不过我们知道favorites映射中的值会始终与键的类型相匹配)。
29条讨论Favorites类局限性及其解决方法的部分看不懂
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值