如何编写高质量代码--泛型篇

502 阅读5分钟

新年寄语:
2020终将过去,2021披荆斩棘

1 为什么要用泛型?

自Java5之前,没有泛型这一概念,从集合中读取的每一个对象都必须进行转换,当你插入的错误类型的对象时,会在运行时期出错,在运行时期得到这样的结果是糟糕的表现,于是泛型就显得尤为重要,它会将运行期错误在编译期体现出来,使我们的程序按照期望运行,减少错误的发生。

术语 范例
参数化类型 List<String>
实际类型参数 String
泛型 List<E>
形式类型参数 E
无限制通配符 List<>
原生态类型 List
递归排序类型 < T extends Comparable<T>>
有限制通配符(上限) List<? extend Number>
有限制通配符(下限) List<? super Number>
泛型类型 String.class
泛型方法 static <T> T[] toArray(T[],Object t)

2 切勿使用原生态类型

//List<E>的原生态类型是List,看下边这段程序
public static void main(String[] args) {
   List<String> strList = new ArrayList<>();
   unsaveAdd(strList,Integer.valueOf(11));
   String result = strList.get(0);
}

private static void unsaveAdd(List list, Object obj){
    list.add(obj);
}
//这段程序编译期并不会报错,但是在运行时期会报错:java.lang.Integer cannot be cast to java.lang.String
//我们通过泛型改进一下
private static void unsaveAdd(List<String> list, Object obj){
    list.add(obj);
}
//list.add(obj)将会在编译期报错:add(java.lang.String)in List cannot be appliedto(java.lang.Object)

类对象中必须使用原生态类型:List.class、String[].class、int.class,但是不能将类对象和泛型一起用,例如:List<String.class>、List<?>.class

3 消除非受检警告

如果无法消除警告,同时确保证明警告的代码类型是安全的,我们可以通过@SuppressWarnings("unchecked")来消除警告。

//ArrayList类中的toArray方法
//编译期警告
public <T> T[] toArray(T[] a) {
  if (a.length <size) {
    return (T[]) Arrays.copyOf(elements,size,a.getClass());
  }
  if (a.length >size) {
      a[size] = null;
  }
}
//上边Arrays.copyOf()方法会产生警告,在确保证明警告的代码类型是安全的情况下,可以这样做
public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}
@SuppressWarnings("unchecked")
public static <T> T[] copyOf(T[] original, int newLength) {
      return (T[]) copyOf(original, newLength, original.getClass());
}

4 优先考虑泛型列表,而不是数组

数组在运行时才强化类型信息,泛型列表在编译期强化类型时期,在运行时期擦除

Object[] obj = new Long[1];
obj[0] = "Hello World";//Throws ArrayStoreException

List<Object> list = new ArrayList<long>();//Incompatible types
list.add(Hello World);

//对比上边两种实现方式,可以清楚看到不管是报错位置和时期,采用列表方式都优先于数组方式

5 优先考虑泛型和泛型方法

//非受检错误版本
public static void main(String[] args) {
    Set s1 = new HashSet();
    s1.add("黄焖鸡");
    Set s2  = new HashSet();
    s2.add("酸菜鱼");
    System.out.println(Food.eatAll(s1,s2));
}
public static Set eatAll(Set s1, Set s2){
    Set result = s1;
    result.addAll(s2);
    return result;
}
//泛型版本
public static void main(String[] args) {
    Set<String> food1 = new HashSet<>();
    food1.add("黄焖鸡");
    Set<String> food2 = new HashSet<>();
    food2.add("酸菜鱼");
    System.out.println(Food.eatAll(food1, food2));
}

private static <E> Set eatAll(Set<E> s1, Set<E> s2) {
    Set<E> set = new HashSet<>(s1);
    set.addAll(s2);
    return set;
}

上边两版代码,没用泛型会出现一堆非受检提示(Unchecked call to 'addAll(Collection<? extends E>)' as a member of raw type 'java.util.Set'),使用泛型之后,idea界面没有任何提示,泛型在编译期帮助我们规避异常问题。

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

通过改写上边代码来展现出有限通配符的灵活性,常在方法的形参上使用

public static void main(String[] args) {
    System.out.println();
    Set<Integer> first = new HashSet<>();
    first.add(1);
    Set<Long> two = new HashSet<>();
    two.add(2L);
    Set<Number> result = Food.union(first,two);
    System.out.println(result);
}
public static <E> Set union(Set<? extends E> s1, Set<? extends E> s2) {
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);
    return result;
}

<? extends E> 表示上限为E,根据入参而定,Set result = Food.union(first,two)表示类型最大为Number,可以接受Number的子类型,例如Long,Integer.....子类型,通配符能够极高提升API的灵活性。

以上就是关于Java泛型的全部内容,如果对您有帮助,点赞-关注-转发三连

参考资料:

[1] Effective Java