实用泛型说明
泛型是一种将运行时异常转化为编译时异常的一种技术,在与集合有关的应用中,我们经常使用泛型来对集合元素的类型做出约束以让代码更加健壮。
使用泛型是一件非常容易的事情。 但是将泛型的理念带入带我们的模块设计中就不是那么容易的事情了。如果你对通配符,类型推导,有限制通配符等等名词还不是很了解,那么本篇文章应该能够帮到你。
类型推导
泛型类/泛型方法
类型推导是指,编译器通过检查方法参数的类型,来计算类型参数的值。这样的过程称为类型推导。 类型推导的代表性用法为泛型类和泛型方法,比如下面这个Stack。
/**
* 泛型测试
* 泛型方法,泛型集合
*/
public class GenericApplication<E> {
private LinkedList<E> list;
/**
* 创建集合对象
*/
private GenericApplication() {
list = new LinkedList();
}
/**
* 压栈
*
* @param e 压栈成员
*/
private void push(E e) {
list.addLast(e);
}
/**
* 弹栈
*
* @return 栈成员
*/
private E pop() {
return list.pop();
}
/**
* 判断栈成员是否为空
*
* @return true:为空 false:不为空
*/
private boolean isEmpty() {
return list.isEmpty();
}
/**
* 通过泛型方法,返回泛型检查过的集合。
*
* @param s1 泛型集合1
* @param s2 泛型集合2
* @param <T> 泛型参数
* @return 合并过后的泛型集合
*/
private <T> Set<T> unionWithGeneric(Set<T> s1, Set<T> s2) {
Set<T> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
/**
* 泛型方法测试函数。
*
* @param args
*/
public static void main(String args[]) {
Set<String> set1 = new HashSet();
set1.add("1");
Set<String> set2 = new HashSet();
set2.add("2");
GenericApplication<Number> ga = new GenericApplication();
Set<String> result = ga.unionWithGeneric(set1, set2);
}
}
通过泛型类GenericApplication可以创建出支持任意类型,并且受到泛型条件约束的集合对象。 通过泛型方法unionWithGeneric可以根据参数的类型返回相应类型的集合对象。 可见,通过类型推导进一步增强了模块的健壮性,使你的API运行更加可靠。
通配符
无限制通配符
Set<?> set = new HashSet();
无限制的通配符?代表任何可能的类型。在上面的代码中,它和Set一起代表了一个未知类型元素的Set集合,并且用户无法向?修饰的集合中add任何元素。
无限制通配符怎么用
/**
* 返回两个集合中相同元素的个数
*
* @param s1 集合1
* @param s2 集合2
* @return 相同元素的个数
*/
private int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o1 : s1) {
if (s2.contains(o1)) {
result++;
}
}
return result;
}
无限制通配符存在的意义在于:加强集合的约束条件,减少元素类型约束被破坏的可能性。 换句话说,如果将s1、s2改为源生集合对象,则可能在下面的逻辑中被add、addAll等api破坏掉元素的类型一致性,引起安全隐患。 如果你不关心集合对象的具体类型,使用无限制的通配符是你最好的选择。因为他可以通过禁止增加元素的手段,帮你避开运行时异常,让你的代码变得更加健壮。
有限制的通配符
有限制的通配符是用来解决api灵活性的。要理解这句话,首先需要明确参数化类型具有不可变性。
有限制的通配符怎么用
什么叫不可变性? 我们尝试往本章的例子中增加上面两个方法。一个是集体压栈方法。一个是集体弹栈,并且将元素插入到另外一个集合中的方法。
/**
* 将集合中所有的元素全部压栈
*
* @param iterable 集合对象
*/
private void pushAllFromEs(Iterable<E> iterable) {
for (E e : iterable) {
list.addLast(e);
}
}
/**
* 将所有元素弹栈,并追加到参数集合中.
*
* @param collection 集合对象,必须是泛型参数的父类
*/
private void popAllIntoEs(Collection<E> collection) {
while (!isEmpty()) {
collection.add(pop());
}
}
使用下面的代码进行测试。
/**
* 泛型方法测试函数。
*
* @param args
*/
public static void main(String args[]) {
GenericApplication<Number> ga = new GenericApplication();
List<Integer> listInteger = new ArrayList<>();
// 有限制的通配符测试 ? extends E
List<Integer> listInteger = new ArrayList<>();
ga.pushAllFromEs(listInteger);
// 有限制的通配符测试 ? super E
List<Object> objects = new ArrayList<>();
ga.popAllIntoEs(objects);
}
现在你将得到一个编译时异常。原因就在于,类型推导、参数化类型具有不可变性。无论Type1与Type2是否是继承关系,都有
List<Type1> != List<Type2>
但是,根据面向对象的思想,子类对象又是能够放进父类对象的集合里的。如果你不适用参数化类型(类型推导),这么做也完全可以。 那么,是否能够在使用参数化类型的同时又能够将子类对象放入父类对象的集合呢? 这就是有限制通配符的用法。
? extends E
/**
* 将集合中所有的元素全部压栈
* 通过使用有限制的通配符类型来提升api的灵活性。集合对象的元素必须是E的子类。
*
* @param iterable 集合对象
*/
private void pushAll(Iterable<? extends E> iterable) {
for (E e : iterable) {
list.addLast(e);
}
}
? super E
/**
* 将所有元素弹栈,并追加到参数集合中.
*
* @param collection 集合对象,必须是泛型参数的父类
*/
private void popAll(Collection<? super E> collection) {
while (!isEmpty()) {
collection.add(pop());
}
}
/**
* 泛型方法测试函数。
*
* @param args
*/
public static void main(String args[]) {
GenericApplication<Number> ga = new GenericApplication();
// 有限制的通配符测试 ? extends E
List<Integer> listInteger = new ArrayList<>();
ga.pushAll(listInteger);
//ga.pushAllFromEs(listInteger);
// 有限制的通配符测试 ? super E
List<Object> objects = new ArrayList<>();
ga.popAll(objects);
//ga.popAllIntoEs(objects);
}
总结
- 使用基于类型推导的泛型类,泛型方法以使代码变得更加健壮。
- 避免使用源生类型,对于未知类型的集合请使用通配符,以加强代码的约束性。
- 合理使用有限制的通配符,提升你的API灵活性。
Copyright 2017/08/15 by Chuck Lin
若文章有幸帮到了您,您可以捐助我,以鼓励我写出更棒的作品!