Effective java 总结4 - 泛型

本文探讨了Java中的泛型使用,强调避免原生态类型以提高安全性,使用无限制通配符优化API灵活性,并提倡消除非受检警告。推荐使用泛型列表而非数组,以及优先考虑泛型方法来增强类型安全性。此外,还讨论了泛型与可变参数的结合使用以及类型安全的异构容器设计。
摘要由CSDN通过智能技术生成

Effective java 总结4 - 泛型

第26条 请不要使用原生态类型

声明中具有一个 or 多个类型参数的类或接口, 就是泛型类或接口,统称泛型

每个泛型都定义一个原生态类型(泛型名称,eg: List 的原生态类型为 List)

泛型会自动进行编译期类型检查

如果使用原生态类型,就会失去泛型在安全性和描述性方面的优势

在不确定或者不在乎集合中类型时,使用无限制的通配符替代原生态类型 Set<?>

例外:

  • 必须在类文字中使用原生态类型:List.class、String[].class, List.class则是非法的
  • 参数化类型使用instanceof操作符非法,无限制通配符可以但没必要,直接使用原生态类型:if(o instanceof Set){ … }

Set : 参数化类型,表示可以包含任何对象的一个集合 (安全)

Set<?>: 通配符类型,只能包含某一种类型对象的集合 (安全)

Set:原生态类型,非泛型(不安全)

参数化类型List
实际类型参数String
泛型List
形式类型参数E
无限制通配符类型List<?>
原生态类型List
有限制类型参数
递归类型限制<T extends Comparable>
有限制通配符类型List< ? extends Number>
泛型方法static List asList(E[] a)
类型令牌String.class

第27条 消除非受检警告

尽可能消除每一个非受检警告

如果无法消除,且可以证明引起警告的代码是类型安全的可以使用 @SuppressWarnings(“unchecked”) 禁止警告

@SuppressWarnings("unchecked") 
T[] result = (T[]) Arrays.copyof(elements, size, a.getclass());
  • 尽可能范围小的使用@SuppressWarnings注解

  • 永远不要在整个类上使用@SuppressWarnings注解

  • 增加注释,写明为啥引起警告的代码是类型安全的


第28条 列表优于数组

数组与泛型

  • 数组是协变的(能够隐式、显式转换),泛型是可变的
  • 数组是具体化的(运行时强化元素类型),泛型擦除则只在编译时强化类型信息
  • 不可以创建泛型数组,因为不是类型安全的
  • E、List、List 等为不可以具体化类型(运行时表示法信息比编译时表示法信息少
  • 唯一可以具体化的参数化类型为无限制通配符类型,List<?>, Map<?, ?>,因为对于通配符的方式,最后取出数据需要显示的类型转换
public class Chooser{
    private final Object[] choiceArray;
  
    public Chooser(Collection choices){
        choiceArray = choices.toArray();
    }
    
    public Object choose(){
        Random rnd = ThreadLocalRandom.current();
        return choiceArray[rnd.nextInt(choriceArray.length)];
    }
}

问题:使用这个类需将 choose方法的返回值改为自己想要的类型

修改成泛型

public class Chooser<T>{
    private final T[] choiceArray;
  
    public Chooser(Collection<T> choices){
        choiceArray = (T[])choices.toArray();
    }
   	// choose 方法一样
}

问题:有非受检警告, 可以用注解禁止

更好的方法使用 列表代替数组

public class Chooser<T>{
    private final List<T> choiceList;
  
    public Chooser(Collection<T> choices){
        choiceList = new ArrayList<>(choices);
    }
    
    public Object choose(){
        Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size));
    }
}

第29条 优先考虑泛型

public class Stack{
    private int size = 0;
    private Object[] elements;
    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(0 == size) throw new EmptyStackError();
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }
    
    private void ensureCapacity(){
        if(elements.length == size){
            elements = Arrays.copyOf(elements, 2 * size + 1)
        }
    }
}

改为泛型

public class Stack<E>{
    private int size = 0;
    private E[] elements;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
    public Stack(){
        elements = new E[DEFAULT_INITIAL_CAPACITY];
    }
    
    public void push(E e){
        ensureCapacity();
        elements[size++] = e;
    }
    
    public Object pop(){
        if(0 == size) throw new EmptyStackError();
        E result = elements[--size];
        elements[size] = null;
        return result;
    }
    ...
}

问题:不能创建不可具体化类型的数组 elements = new E[DEFAULT_INITIAL_CAPACITY];

解决1

创建Object数组,类型转换为泛型数组类型,并注解(@SuppressWarnings)消除未受检警告

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

可读性强,更加简洁, 只需要转换一次(创建数组的时候),可能导致堆污染

解决2

将elements域的类型从E[] 改为Object,每次读取数组元素时强转为 E

private Object[] elements;
...
@SuppressWarnings("unchecked")
E result = (E)elements[--size];

每次读取一个元素数组的时候都要转换一次

应用

public static void main(String[] args){
    Stack<String> stack = new Stack<>();
    for(String arg: args){
        stack.push(arg);
    }
    while (!stack.isEmpty()){
        System.out.println(stack.pop().toUpperCase());
    }
}

第30条 优先考虑泛型方法

静态工具方法尤其适合泛型化, eg. collections中所有算法方法都泛型化了

public static Set<E> union(Set<E> s1, Set<E> s2){
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);
    return result;
}
//
Set<String> ss = Set.of("1", "sss", "haha");
Set<String> si = Set.of("aaa");
Set<String> uns = Set.union(ss, si);

泛型单例工厂

public interface GenericityIn<T>{
    T apply(T args);
}

public class GenericityImp{
    // private static GenericityIn<Object> IDENTITY_FUNCTION = (t) -> t;
    private static GenericityIn<Object> IDENTITY_FUNCTION = new GenericityIn<Object>(){
        public Object apply(Object arg){
            return arg;
        } 
    };
    
    // 恒等函数分发器
    @SuppressWarnings("unchecked")
    public static <T> GenericityIn<T> identityFunction() {
        return (GenericityIn<T>)IDENTITY_FUNCTION;
    }
    
    public static void main(String[] args){
        String[] ss = {"a", "b"};
        GenericityIn<String> gs = identityFunction();
        for(String s: ss)
        	sout(gs.apply(s))
        GenericityIn<Integer> gi = identityFunction();
        ...
    }
}

递归类型限制

通常与Comparable接口有关

 // 针对可以与自身比较的每个类型E: <E extends Comparable<E>>
public static <E extends Comparable<E>> E max(Collection<E> c){
    if(c.isEmpty())
        throw new Ill.....
    E result = null;
    for(E e: c){
        if(result == null || e.comparaTo(result) > 0){
            result = e;
        }
    }
    return result;
}

泛型方法就像泛型一样,使用起来比要求客户端转换输入参数并返回值的方法更加安全,容易。


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

类型化参数是不变的, 也就是说 List 与List 没有任何上下关系

有限制的通配符类型(确定了子类型后,每个类型即是自身的子类型,即便没有扩展)

public void pushAll(Iterable<? extends E> src){
    for(E e: src){
        push(e);
    }
}
// use
Stack<Number> ns = new Stack<>();
Iterable<Integer> in = ...;
ns.pushAll(in);
public void popAll(Collection<? super E> dst){
    while(!dst.isEmpty()){
        dst.add(pop());
    }
}
// use
Collection<Object> co = ...;
ns.popAll(co);

为了最大限度的灵活性,要在表生产者或者消费者的输入参数上使用通配符类型,但不用通配符类型作返回类型

PECS ( producer-extends, consumer-super), 所有的Comparble和Comparator都是消费者

public static <T extends Comparable<T>> T max(List<T> list)
// 使用通配符类型声明
// 需要用通配符支持那些不直接实现Comparable(or Comparator)而是扩展实现了该接口的类型
public static <T extends Comparable<? super T> T max(List<? extends T> list){}

如果类型参数只在方法声明中出现一次,可以用通配符取代

// 不能将除了null之外的任何值放入 List<?>
public static void swap(List<?> list, int i, int j){
    swapHelper(list, i, j);
}
// 利用私有的辅助方法来捕捉通配符类型,辅助方法必须是泛型方法
private static <E> swapHelper(List<E> list, int i, int j){
    list.set(i, list.set(j, list.get(i)));
}


第32条 谨慎并用泛型和可变参数

技术露底:调用一个可变参数方法时,会创建一个数组来存放可变参数,数组应该是一个实现细节,是可见的,但可变参数有泛型或参数化类型时,则会产生混乱

当一个参数化类型的变量指向一个不是该类型的对象时,产生堆污染

static void dangerous(List<String>... stringLists){
    List<Integer> intList = List.of(42);
    Object[] objects = stringLists;
    object[0] = intList;               // heap pollution
    String s = stringLists[0].get(0);  // ClassCastException
}
// 将值保存在泛型可变参数数组参数中是不安全的

Q:显示创建可变参数数组非法,但是泛型可变参数声明方法是合法的?

A:泛型可变参数或者参数化的类型方法在实践中很有用,类库方法中是安全的,eg: Arrays.asList(T… a), Collections.addAll(Collecntion<? super T> c, T… elements), EnumSet.of(E first, E… rest) …

注解SafeVarargs

让带泛型varargs参数的方法设计者能自动禁止客户端的警告,通过方法的设计者做出承诺,声明这是类型安全的

允许另一个方法访问一个泛型可变参数数组是不安全的,例外:1. 将数组传给另一个用SafeVarargs的可变参数方法;2. 将数组传给只计算数组内容部分函数的非可变参数方法

eg:

@SafeVarargs
static <T> List<T> flatten(List<? extends T>... lists){
    List<T> result = new ArrayList<>();
    for(List<? extends T> list: lists){
        result.addAll(list);
    }
}

何时使用SafeVarargs?

对于每一个带有泛型可变参数或者参数化类型的方法时,都使用SafeVarargs注解,并且只能用在静态方法,final实例,和私有实例上(java9)。

泛型可变参数方法安全:

  • 没有在可变参数数组中保存任何值
  • 没有对不信任的代码开放该数组

用list参数代替可变参数

static <T> List<T> flatten(List<List<? extends T>> lists){
    List<T> result = new ArrayList<>();
    for(List<? extends T> list: lists){
        result.addAll(list);
    }
    return result;
}
// 前提静态工厂方法List.of 已经使用 SafeVarargs 注解
audience = flatten(List.of(friends, romans, countrymen));

优势:编译器可以证明该方法是类型安全的,这样得到的代码只使用泛型,没有用数组!!!


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

class 类: 特殊类,可以获取其他类的属性,名称,方法,基类等。每个类都有Class类对象,由jvm创建

class: 泛型class, 其实和普通List差不多,可以传Class,Class…

异构容器:非异构容器Map<k,v>只能指定key、value为一种类型,而异构容器的Key可以是任意类型

类型令牌:一个类的字面被用在方法中,传达编译时和运行时的类型信息, Class 通常被称为类型令牌

安全的异构容器

public class Favorites{
    private Map<Class<?> ,object> favorites = new HashMap<>();
    // put
    public void <T> putFavorites(Class<T> type, T instance){
        favorites.put(type, instance);
        // favorites.put(type, type.cast(instance)); // 检验instance是否为type的实例
    }
    // get
    public T getFavorites(Class<T> type){
        // cast是Java的转换操作符的动态模拟,只检验参数是否为Class对象所表示的类型的实例,是就返回,不是            抛出异常
        return type.cast(favorites.get(type));
    }
}

Class 不能传入List 之类的对象,不能用在不可具体化的类型中 。

注解API

public <T extends Annotation> T getAnnotation(Class<T> annotaionType){...}

有限制的类型令牌方法

Class<?> -> Class<? extends Annotation>
// 利用asSubclass 读取类型未知的注解
static Annoation getAnnoation(AnnoatatedElement element, String annoationTypeName){
    Class<?> antype = null;
    try{
        antype = Class.forName(annoationTypeName);
    }...
    return element.getAnnotation(antype.asSubClass(Annoation.class));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值