新年寄语:
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