想要封装一个好用的网络框架,肯定绕不过泛型这关。
最近遇到网络请求相关问题,网上也没有和我相同的需求,只能自己动手了,顺便学习学习json
和泛型
,先学习泛型
[TOC]
个人认知:泛型的作用
为了让错误提前到编译期发现,使程序更安全
参考《Effective Java》
第五章泛型
第23条
raw type
List<E>
对应的raw type
是List
,raw type
可以直接使用,但是我们不应该这么做,这只是为了兼容问题做的保留,如果使用raw type
就失掉了泛型在安全性和表述性方面的所有优势
Gson
的new Type(){}
就是这样,不要去这样使用,使用时明确类型:new Type<Object>(){}
指定泛型
使用raw type
和<Object>
的区别,(List
和List<Object>
的区别)前者逃避了类型检查
{//代码示例...
List<String> list = new ArrayList<String>();
add1(list, 42);
add2(list, 42);//Error: 不兼容的类型: List<String>无法转换为List<Object>
}
void add1(List list, Object o) {
list.add(o);
}
void add2(List<Object> list, Object o) {
list.add(o);
}
无限制通配符
泛型Set<E>
的无限制通配符类型为Set<?>
读作"某个类型的集合"。
通配符是类型安全的,raw type
类型不安全
void add2(List<?> list, Object o) {
list.add("");
/*Error:(26, 13) 错误: 对于add(String), 找不到合适的方法
方法 Collection.add(CAP#1)不适用
(参数不匹配; String无法转换为CAP#1)*/
}
raw type
可以很容易的破坏集合的约束条件,而通配符则会限制这种情况的发生(null除外)
, 可以配合泛型方法generic method
和有限制的通配符类型bounded wildcard type
使用
快速回顾
-
Set<Object>
是个参数化类型,表示可以包含任何对象类型的一个集合 -
Set<?>
则是一个通配符类型,表示只能包含某种未知对象类型的一个集合 -
Set
则是一个原生态类型raw type
,它脱离了泛型系统,不安全。
- 参数化类型 : List<String>
- 实际类型参数:
String>
- 泛型:
List<E>
- 形式类型参数:
E
- 无限制通配符类型:
List<?>
- 原生态类型:
List
- 有限制类型参数:
<E extends Number>
- 递归类型限制:
<T extends Comparable<T>
- 有限制通配符类型:
List <? extends Number>
- 泛型方法:
static <E> List<E> func(E[] e)
- 类型令牌:
String.class
第24条
@SuppressWarnings
注解使用
- 也可以使用在变量上
- 不能直接在
return
上使用,这时创建局部变量接受返回值,然后注解
第25条 列表优先于数组
-
第一个区别:数组是协变
(covariant)
,泛型是不可变(invariant)
。//数组,编译时合法 Object[] objects = new Long[1]; //Long 是Object的子类 objects[0] = "aa";//运行时会抛出异常 ArrayStroeException //集合,编译时无法通过 List<Object> list = new ArrayList<Long>();//无法编译通过,类型不匹配 list.add("aa");
-
第二个区别:数组是具体化
(reified)
运行时才知道并检查类型,泛型是通过擦除(erasure)
来实现的,编译时就可以明确
创建泛型、参数化类型或者类型参数的数组是非法的。new List<E>[]
、new List<String>[]
、new E[]
都是不合法的。
如果是合法的话,将创建出来“列表数组”保存到一个
Objcect
数组,然后使用这个数组修改“列表数组”的类型,这时候再从原始的容器中取出内容就会发生错误
技术角度来说,像E、List<E>
和List<String>
这样的类型应该称作不可具体化(nonreifiable)
,
不可具体化
(nonreifiable)
,指其运行时表示法包含的信息比它编译时表示法包含的信息更少的类型唯一可具体化的参数化类型是无限制的通配符类型
第27条 优先考虑泛型方法
将含有警告的raw type 方法修改为泛型方法
//raw type
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
//泛型方法
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<E>(s1);
result.addAll(s2);
return result;
}
方法的局限性在于,三个集合的类型(两个输入参数和一个返回值)必须全部相同。
利用有限制的通配符类型,让这个方法变得更加灵活
泛型方法的一个显著特性是,无需明确指定类型参数的值,不像调用泛型构造器的时候是必须指定的。编译器通过检查方法参数的类型来计算类型参数的值。
通过某个包含该类型参数本身的表达式来限制类型参数是允许的。这就是递归类型限制 。递归类型限制最普遍的用途与Comparable
接口有关,他定义类型的自然顺序:
public interface Comparable<T> {
int compareTo(T o);
}
类型参数T 定义的类型,可以与实现Comparable<T>
的类型的元素进行比较。
下面是如何表达这种约束条件的一个示例
public static <T extends Comparable<T>> T max(List<T> list){...}
类型限制<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;
}
第28条 利用有限制通配符来提升API的灵活性
<? extends E>
为在刚刚的Stack
类添加一个新方法pushAll(Iterable<E> iterable)
public void pushAll(Iterable<E> iterable) {
for (E e : iterable) {
push(e);
}
}
Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = new ArrayList<>();
numberStack.pushAll(integers);//此时编译不通过
将pushAll(Iterable<E> iterable)
修改为Iterable<? extends E> iterable
此时可以编译通过
<? super E>
添加一个新方法
public void popAll(Collection<E> collection) {
while (!isEmpty())
collection.add(pop());
}
Stack<Number> numberStack1 = new Stack<>();
Collection<Object> objects = new ArrayList<>();
numberStack.popAll(objects);//此时编译不通过
将popAll(Collection<E> collection)
修改为popAll(Collection<? super E> collection)
此时可以编译通过
为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型
助记符 PECS
PECS 表示producer-extends
consumer-super
如果表示生产者就使用<? extends T>
,如果表示消费者就使用<? super T>
如果使用得当,通配符类型对于类的用户来说几乎是无形的。它们使方法能够接受它们应该接受的参数,并拒绝那些该拒绝的参数。如果类的用户必须考虑通配符类型,类的API或许就会出错
//示例
<E> E reduce(List<E> list, Function<E> function, E initVal) {...}
//修改后
<E> E reduce2(List<? extends E> list, Function<E> function, E initVal) {...}
//示例2
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {...}
//修改后 ,返回类型不要含有通配符
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2) {... }
//两者的区别在于
Set<Integer> integers = new HashSet<>();
Set<Double> doubles = new HashSet<>();
//使用通配符的方法可以通过一个【显示的类型参数】来告诉要使用哪种类型
Set<Number> numbers = Math2.<Number>union(integers, doubles);
修改第27条中的max
方法
public static <T extends Comparable<T>> T max(List<T> list)
//修改后, 应用PECS转换两次
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
//同时修改 Iterator<? extends T> iterator = list.iterator();
还有一个与通配符有关的话题值得探讨。类型参数和通配符之间具有双重性,许多方法都可以利用其中一个或者另一个进行声明。
//示例
public static <E> void swap(List<E> list ,int i ,int j);
public static void swap(List<?> list ,int i ,int j);
网友评论