Effective Java 学习笔记之泛型

1 篇文章 0 订阅

1.不要在新代码中使用原生态类型

1) 声明中具有一个或者多个类型参数的类或者接口就是泛型类或接口
例如List<E>(读作‘E的列表’) 泛型类和接口统称作泛型

2)每个泛型都定义一个原生态类型,即不带任何实际类型参数的泛型名称。例如List<E>相对应的原生态类型是List

3)使用原生态类型,如果放入的类型不一致,在编译和运行时不会报错,直到从其中取出元素的时候才会收到提示

        List strList = new ArrayList();
        strList.add("hello");
        strList.add(1);
        //以上不会出现错误提示

        Iterator iterator = strList.iterator();
        while (iterator.hasNext()) {
            //java.lang.ClassCastException:java.lang.Integer cannot be cast to java.lang.String
            String str = (String) iterator.next();
            System.out.println(str);
        }

但是有了泛型之后,可以使用改进后的类型声明来告诉编译器应该插入的是何种类型

List<String> strings = new ArrayList<>();
strings.add("world");
/*Error:(28, 16) java: 对于add(int), 找不到合适的方法
方法 java.util.Collection.add(java.lang.String)不适用
        (参数不匹配; int无法转换为java.lang.String)*/
strings.add(1);

当插入错误类型时编译时期就会报错

4) 另一个好处是从集合中操作元素时不需要再进行手工转换了,编译器会替你插入隐式的转换,并确保他们不会失败

List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    list.add("str" + i);
}
//for-each循环
for (String s : list) {
    System.out.println(s);
}
//for-loop循环
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
    String s = iterator.next();
    System.out.println(s);
}

5)Java中的原生态类型是为了提供兼容性,使得之前大量没有使用泛型的代码可以与新代码互用

6)虽然不应该使用List这样的原生态类型,但是使用参数化的类型以允许插入任意类型的对象例如List<Object>还是可行的

7)List<Obejct>List的区别是后者逃避了泛型检查,而前者明确告诉编译器他能够持有任意类型的对象
比如你可以将List<String>传递给List的参数,但是不可传给List<Obejct>

8)泛型有子类型化的规则。List<String>是原生态List的子类型而不是List<Obejct>

9)当在不确定或者不在乎集合中元素类型的情况下,可以使用无限制的通配符类型
例如Set<E>的无限制通配符类型为Set<?>(读作‘某个类型的集合’)

10)不要在新代码中使用原生态类型有两个例外:

  • 1.在类文字中必须使用原生态类型–泛型擦除
    • List.class,String[].class,int.class都合法但是List<String>.classList<?>.class不合法
  • 2.instance判断时有无泛型对结果不会产生任何影响,因此加有泛型就显得累赘

总结:

  • Set<Object>是一个参数化类型 表示可以包含任何对象类型的一个集合
  • Set<?>则是一个通配符类型 表示只能包含某种未知对象类型的一个集合
  • Set是一个原生态类型 脱离了泛型系统
  • 前两种是安全的 最后一种不安全

2.消除非受检警告

1)使用泛型时会遇到很多编译器警告:

  • 非受检强制转化警告unchecked cast warnings
  • 非受检方法调用警告,非受检普通数组创建警告
  • 非受检转换警告unchecked conversion warnings

2)如果无法消除警告并且可以证明警告代码是类型安全的才可以使用@SuppresssWarnings("uncheck")来禁止警告

3)SuppresssWarnings最好在尽可能小的范围内使用,并且添加注释说明为啥是安全的 这样可以帮助理解代码

3.列表优先于数组

1)数组和泛型对比有两个不同点:

  • 1.数组是协变的 泛型是不可变的
    • 如果Sub是Super的子类型,那么数组类型Sub[]就是Super[]的子类型,相反对于泛型是不成立的
      例子:
Object[] objs = new Long[1];
objs[0] = "123";//java.lang.ArrayStoreException 运行时异常

/*Error:(48, 32) java: 不兼容的类型: java.util.ArrayList<java.lang.Long>无法转换为java.util.List<java.lang.Object>*/
List<Object> objects = new ArrayList<Long>();//编译时异常
  • 2.数组是具体化的 会在运行时才知道并且检查它们的元素类型约束,而泛型是通过擦除来实现的 只在编译时强化它们的类型信息并在运行时丢弃[擦除]它们的元素类型信息
    • 擦除就是使泛型可以和没有使用泛型的代码随意进行互用

由于上述区别,数组和泛型不能很好的混合使用。例如创建泛型、参数化类型或者类型参数的数组是非法的
List<E>[],new List<String>[],new E[]—-genic array creation exception
但是创建无限制统配类型的数组是合法的 new List<?>[],new Map<?,?>

4.优先考虑泛型

类型参数列表DelayQueue<E extends Delayed>要求实际类型E必须是Delayed的子类型,每个类的都是自身的子类型,使用DelayQueue<Delayed>也是合法的

5.优先考虑泛型方法

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

1)泛型方法的一个显著特性是,无需明确指定类型参数的值,不像调用泛型构造器的时候是必须指定的,编译器通过检查方法参数的类型来计算类型参数的值。
例如 编译器发现union的两个参数都是Set<String>类型 因此知道类型参数E必须是String 这个过程叫做类型推导

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

public static <T extends Comparable<T>> T max(List<T> list) {
    Iterator<T> iterator = list.iterator();
    T result = iterator.next();
    while (iterator.hasNext()) {
        T t = iterator.next();
        if (t.compareTo(result) > 0) {
            result = t;
        }
    }
    return result;
}

6.利用有限制通配符来提高API的灵活性

例如之前的Stack类

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

要增加一个方法让它按顺序把一系列的元素全部放到堆栈中

public void pushAll(Iterable<E> iterable){
    for(E e : iterable){
            push(e);
    }
}
public static void main(String[] args){
    Stack<Number> numberStack = new Stack<>();
        Iterable<Integer> integers = new Iterable<Integer>() {
            @Override
            public Iterator<Integer> iterator() {
                return new HashSet<>(Arrays.asList(1, 2, 3)).iterator();
            }
    };
    numberStack.pushAll(integers);
}

当往Stack<Number>中传入Stack<Integer>时会出现错误,因为泛型是不可变的,虽然Integer是Number的子类,但是List<Integer>却不是List<Number>的子类型

此时可以使用有限制的通配符来处理

public void pushAll(Iterable<? extends E> iterable){
    for(E e : iterable){
            push(e);
    }
}

再比如添加一个方法用于从堆栈中弹出每个元素并添加到指定的集合中

 public void popAll(Collection<E> collection) {
   while (!isEmpty()) {
        collection.add(pop());
    }
}
public static void main(String[] args){
    Collection<Object> objects = new HashSet<>();
    numberStack.popAll(objects);
    System.out.println(numberStack.size);
}

会出现同样的错误,Stack<Number>无法转换到Collection<Object>

同样使用有限制的通配符来处理

 public void popAll(Collection<? super E> collection) {
   while (!isEmpty()) {
        collection.add(pop());
    }
}

下面的助记符便于记住使用哪种通配符类型:

PECS表示 
    producer-extends
    consumer-super
如果参数化类型表示一个生产者 就使用<? extends T>
如果表示一个消费者 就使用<? super T>

在Stack示例中,pushAll的iterable用于产生E实例供Stack使用,因此使用

Set<Integer> intergers=...
Set<Double> doubles = ...
Set<Number> numbers = Union.<Number>union(intergers,doubles);

2)如果类型参数只在方法声明中出现一次,就可以用通配符取代它
public static <E> void swap(List<E> list,int i,int j);//无限制的类型参数
public sttaic void swap(List<?> list,int i,int j);//无限制的通配符 【推荐】

但是使用第二种方法会有一个问题

public sttaic void swap(List<?> list,int i,int j){
    list.set(i,list.set(j,list.get(i));//不能把元素放回到刚刚取出的列表中
}

问题在于不能把null之外的任何值放到List

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

1)泛型最常用于集合以及单元素的容器如ThreadLocal和AtomicReference.
但是有时候会需要更多的灵活性,比如数据库的行可以有任意多的列,如果能以类型安全的方式访问所有列就好了

可以将键key进行参数化而不是容器参数化,然后将参数化的键提交给容器,来插入或者获取值。用泛型系统来确保值的类型和键相符
例子:
Favorite类 允许其客户端从任意数量的其他类中,保存并获取一个最喜爱的实例
Class对象充当参数化的键的部分,jdk1.5之后Class被泛型化,String.class属于Class<String>类型,Integer.class属于Class<Integer>类型

public class Favorites {

    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(type, 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 favorStr = f.getFavorite(String.class);
        System.out.println(favorStr);
        Integer favorInt = f.getFavorite(Integer.class);
        System.out.println(favorInt);
        Class<?> aClass = f.getFavorite(Class.class);
        System.out.println(aClass);
    }
}

问题:

1.原生态形式使用Class对象会造成unchecked异常
解决方法:put时检验instance是否真的是type所表示的类型的实例

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

2.无法保存List<String>等不可具体化的类型,因为List<String>.class是语法错误的


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值