和我一起学Effective Java之泛型

泛型

不要在新代码中使用原始类型

泛型(generic):声明中具有一个或多个类型参数

原始类型(raw type):不带任何实际类型参数的泛型名称

格式: 类或接口的名称 < 对应于泛型形式类型参数的实际参数 >

List<String> 就是对应于List<E>的实际参数为String的参数化类型

如与List<E>对应的原始类型是List

优点:

  • 在编译时发现插入类型的错误(越早发现错误越好,编译时发现好于运行时发现)
  • 不再需要手工转换类型
   //JDK5之前的写法,使用的是原始类型
    private static final List stringList = new ArrayList();

    //有了泛型之后的写法,使用泛型
    private static final List<String> stringList2 = new ArrayList<String>();

    //JDK7 能将后面<>里的类型省略,被称为Diamond
    private static final List<String> stringList3 = new ArrayList<>();

    public static void main(String[] args) {
        String str = "test";

        Integer integer = 1;

        stringList.add(str);
        stringList.add(integer);//可通过编译,但之后报ClassCastException错误

        stringList2.add(str);
//        stringList2.add(integer);//无法通过编译

        for(Iterator iterator = stringList.iterator();iterator.hasNext();){
            String string = (String) iterator.next();
            System.out.println(string);
        }
        for(Iterator iterator = stringList2.iterator();iterator.hasNext();){
            String string =  iterator.next();
            System.out.println(string);
    }

ListList<Object>之间的区别?

List逃避了泛型检查,List<Object>则是告知编译器,它能够持有任意类型的对象

无限制的通配符类型:
使用泛型,但不确定或者不关心实际的类型参数,可以用一个问号代替。如List<?>

泛型信息在运行时会被擦除

学习链接:

1.https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

2.http://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java

下面通过一个小demo说明类型擦除

      //类型擦除
        List<String> stringList = new ArrayList<>();
        List<Integer> integerList = new ArrayList<>();
        System.out.println(stringList.getClass().toString());
        System.out.println(integerList.getClass().toString());
        System.out.println(stringList.getClass()==integerList.getClass());



        integerList.add(100);
        Method method = integerList.getClass().getMethod("add",Object.class);
        method.invoke(integerList,"abc");

        System.out.println(integerList);

运行结果:

一般不在代码中使用原始类型,除了两种例外情况(都是因为泛型信息在运行时会被擦除):

  • 1.在类文字(class literals)中
如:
List.class,String[].class,int.class都合法
List<String>.class,List<String>.class都不合法
  • 2.instanceof
  if(o instanceof Set){   //原始类型(Raw Type)
  Set<?> set = (Set<?>)o;//通配符类型(WildCard Type)
}

下面的表格是泛型相关的术语:
泛型相关的术语

下面这张图很好的介绍了无限制通配符和其他泛型符号之间的关系:

消除非受检警告

始终在尽可能小的范围内使用SuppressWarnings注解

Java源码中的ArrayList类中有个toArray方法,其中就有强转的警告:

 @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

最好是将范围进一步缩小。将注解由整个方法到局部的变量声明上去。

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

列表优于数组

  • 数组是协变的(covariant),泛型则是不可变的

   Object[] objectArray = new String[1];
   List<Object> objectList = new ArrayList<String>();//无法通过编译 imcompatible types
   // String类是Object类的子类
   //String[]是Object[]的子类
   //而List<String>并不是List<String>的子类型  
  • 数组是具体化的(reified),在运行时才知道并检查它们的元素类型约束。而泛型通过擦除来实现的。泛型只在编译时强化类型信息,并在运行时擦除它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码可以互用。

          Object[] objectArray = new String[1];
          List<String> objectList = new ArrayList<String>();
          objectArray[0] = 3;//可通过编译,运行时报错
//        objectList.add(1);//编译时报错

数组和泛型不能很好地混合使用。可用列表代替数组。

总结:数组是协变且可具体化的,泛型是不可变的且可被擦除的。-->数组提供了运行时类型安全而编译时类型不安全。而泛型反之。

优先考虑泛型

泛型相比于Object的优点:

  • 不需要强制类型转换
  • 编译时类型安全
public class SomeClazz<T> {
    public Object dosthWithObj(Object obj){
        return obj;
    }
    
    public T dosthWithT(T t){
        return t;
    }

    public static void main(String[] args) {
        SomeClazz<Foo> someClazz = new SomeClazz<Foo>();
        Foo foo = new Foo();
        Foo foo1 = (Foo) someClazz.dosthWithObj(foo);
        Foo foo2 = someClazz.dosthWithT(foo);
    }
}

public class Stack<E> {
    private E [] elements;
    private static final int MAX_SIZE = 16;
    private int size = 0;

    @SuppressWarnings("unchecked")
    public Stack(){
        elements = (E[]) new Object[MAX_SIZE];
    }

    public void push(E e){
        ensureSize();
        elements[size++]=e;
    }

    public E pop(){
        if(size==0)
            throw new EmptyStackException();
        E e = elements[--size];
        elements[size]=null;
        return e;
    }

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

    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        for(int i =0;i<50;i++){
            stack.push(i);
        }
        for(int i = 0;i<10;i++){
            System.out.println(i+": "+stack.pop());
        }
    }


}
class EmptyStackException extends RuntimeException{

}

前面曾鼓励优先使用列表而不是数组。并不意味着所有的泛型中都要使用列表。况且Java并不是生来就支持列表的。

每个类型都是它自身的子类型。

如有 SomeClazz<E extends Number>

    SomeClazz<Number>是合法的

优先考虑泛型方法

方法可以考虑泛型化,特别是静态工具方法。

泛型方法语法:

方法修饰语 泛型 返回值 方法名()

public static <T> T foo(T args);

/**
     * 使用泛型方法
     * 返回两个集合的联合
     * @param s1
     * @param s2
     * @param <E>
     * @return
     *
     * 局限:两个参数和返回的结果的类型必须全部相同
     * 解决方法:使用有限制的通配符
     */
    public static <E> Set<E> unionGeneric(Set<E> s1,Set<E> s2){
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }

public static <K,V> Map<K,V> newHashMap(){
        return new HashMap<K,V>();
    }

泛型单例工厂:

 public interface UnaryFunction<T>{
        T apply(T arg);
    }
    private static UnaryFunction<Object> IDENTITY_FUNCTION =
            new UnaryFunction<Object>() {
                @Override
                public Object apply(Object arg) {
                    return arg;
                }
            };

    @SuppressWarnings("unchecked")
    public static <T> UnaryFunction<T> identityFunction(){
        return (UnaryFunction<T>) IDENTITY_FUNCTION;
    }

    /**
     * 每次都要创建一个,很浪费,而且它是无状态的.
     * 泛型被具体化了,每个类型都需要一个恒等函数,但是它们被擦除后,就只需要一个泛型单例了.
     * @param <T>
     * @return
     */
    public static <T> UnaryFunction<T> identityFunction2(){
        return new
                UnaryFunction<T>() {
                    @Override
                    public T apply(T arg) {
                        return arg;
                    }
                };
    }

递归类型限制:

通过某个包含该类型参数本身的表达式来限制类型参数

<T extends Comparable<T>>//针对可以与自身进行比较的每个类型T

利用有限制通配符来提升API的灵活性

参数化类型是不可变的。

虽然String类是Object类的子类,但是List<String>和List<Object>无关
/**
 * 栈的实现
 * @param <E>
 * API:
 *   public Stack();
 *   public void push(E e);
 *   public E pop();
 *   public boolean isEmpty();
 *
 * 新增API:
 *   before:
 *     public void pushAll(Iterable<E> i);
 *     public void popAll(Collection<E> c);
 *   after:
 *     使用有限制的通配符类型(bounded wildcard type)
 *    public void pushAll(Iterable<? extends E> i);
 *    public void popAll(Collection<? super E> c);
 *
 */

class Stack<E>{
    private E [] elements;
    private static final int INIT_CAPABILITY = 16;
    private int size = 0;
    @SuppressWarnings("unchecked")
    public Stack(){
        elements = (E[]) new Object [INIT_CAPABILITY];
    }
    public void push(E e){
        checkCapability();
        elements[size++]=e;
    }
    public E pop(){
        if(size==0)
            throw new RuntimeException("Empty Stack");
        E e = elements[--size];
        elements[size]=null;
        return e;
    }

    private void checkCapability() {
        if(size==elements.length)
            elements = Arrays.copyOf(elements,2*elements.length-1);
    }
    public boolean isEmpty(){
        return size==0;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        String start = super.toString();
        sb.append(start);
        for(int i = 0 ;i<size;i++){
            String s = " ["+elements[i]+"]";
            sb.append(s);
        }
        return sb.toString();
    }

    //before
//    public void pushAll(Iterable<E> i){
//        for(E e:i){
//            push(e);
//        }
//    }
//    public void popAll(Collection<E> c){
//        while (!isEmpty()){
//            c.add(pop());
//        }
//    }
    //after
    public void pushAll(Iterable<? extends E> i){
        for(E e:i){
            push(e);
        }
    }
    public void popAll(Collection<? super E> c){
        while(!isEmpty()){
            c.add(pop());
        }
    }



        Stack<Number> stack= new Stack<>();
        Iterable<Integer> integers = Arrays.asList(1,2,3,4,5);
        Collection<Object> objectCollection = new LinkedList<>();
        //before
//        stack.pushAll(integers);//参数类型不对
//        stack.popAll(objectCollection);//参数类型不对
        //after
        stack.pushAll(integers);
        System.out.println(stack);
        stack.popAll(objectCollection);
        System.out.println(stack);

从上面的Demo中我们知道,Java中提供了有限制的通配符类型来提高API的灵活性。

Collection<? extends E>

Collection<? super E>

一般在表示生产者消费者的输入参数上使用通配符类型。

PECS:Producer-extends Consumer-super

      ------------------
 * 参数化类型  通配符类型
 *  T生产者   extends
 *  T消费者   super
 * ------------------
原文地址:https://www.cnblogs.com/JohnTsai/p/5344733.html