为什么学习泛型,来看一个例子:
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
c.add("王五");
c.add(20);
c.add(2.5);
// 遍历集合
for (Iterator it = c.iterator(); it.hasNext(); ) {
Object oj = it.next();
// 访问子类每一个元素所特有的方法
String s = (String) oj;//*****
System.out.println("字符串的长度" + s.length());
}
定义了一个集Collection类型的集合,先向其中加入了三个字符串类型的值,再加入一个Integer类型的值,最后加入一个Double类型的值。这是完全允许的,因为此时c默认的类型为Object类型。在之后的循环中,由于忘记了之前在c中也加入了Integer类型和Double的值或其他编码原因,很容易出现类似于//*****中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String”异常,不能将整数转换为java.lang.String。因此,导致此类错误编码过程中不易发现。
针对上述,可以使用 instanceof关键字做向下转型,修改代码如下:
for (Iterator it = c.iterator(); it.hasNext(); ) {
Object oj = it.next();
if (oj instanceof String) {
// 访问子类每一个元素所特有的方法
String s = (String) oj;
System.out.println("字符串的长度" + s.length());
} else if (oj instanceof Integer) {
Integer i = (Integer) oj;
System.out.println("整数的值: " + i.intValue());
} else if (oj instanceof Double) {
Double d = (Double) oj;
System.out.println("小数的值: " + d.doubleValue());
}
}
我们发现上述代码出现大量if-else
语句,导致程序的可读性下降,并且程序的扩展性也很弱,如果容器需要添加一个自定义学生类
c.add(new Student("1001", "张三", 30));
上述代码无法完全显示所有的集合内容:
//字符串的长度2
//字符串的长度2
//字符串的长度2
//整数的值:20
//小数的值:2.5
我们可以继续添加 else if语句,但是我们知道Object的子类是无数的,所以每次针对Object的子类做逐一判断是不可能的,所以安全隐患永远存在,因此我们可以使用泛型改进。
泛型的由来
1.泛型模仿了数组: 在编译时期就确定类型,不存在对所有的子类判断的问题
2.泛型模仿了方法:泛型是一种类似于方法的参数化类型
概念: 泛型是JDK1.5之后引入的新特性,是一种将元素的类型提前在编译时期确定,并且它是一种参数化类型的技术
格式 < E >:
- <>里面可以是任意的字母,一般泛型类会使用E,泛型方法会使用T
- 这里只能够定义引用类型,不能够定义基本书类型
- <>里面既可以定义一个泛型,也可以定义多个泛型
通过使用泛型改进代码,代码显示如下:
Collection<String> c = new ArrayList<String>();
c.add("张三");
c.add("李四");
c.add("王五");
// c.add(20); //***提示编译报错
// c.add(2.5); //***提示编译报错
// c.add(new Student("1001", "张三", 30)); //***提示编译报错
Iterator<String> it = c.iterator();
while (it.hasNext()) {
String s = it.next(); //**
System.out.println(s + "|" + s.length());
}
采用泛型写法后,在//***处想加入一个Integer类型和Double类型的对象时会出现编译错误,通过c < String >直接限定了c集合中只能含有String类型的元素,从而在//**处无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。
结合上面的泛型定义,我们知道在c< String>中,String是类型实参,也就是说,相应的c接口中肯定含有类型形参。且next()方法的返回结果也直接是此形参类型(也就是对应的传入的类型实参)。
泛型的好处
- 简化了代码
- 取消了黄色警告线
- 取消了强制类型转换,提高了程序的效率
- 提高了程序的安全性
- 提高了程序的扩展性和可维护性,满足了开闭原则【对扩展开放,对修改关闭】
泛型类
泛型类: 把泛型定义在类上。
我们看一个最简单的泛型类和方法定义:
public class GernericDemo02 {
public static void main(String[] args) {
GenericClass<String> gc = new GenericClass<String>();
gc.setE("张三");
String s = gc.getE();
System.out.println(s + "|" + s.length());
}
}
class GenericClass<E> {
private E e;
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
在泛型接口、泛型类和泛型方法的定义过程中,其中泛型接口和泛型方法下面会说。我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。
泛型接口
概念: 把泛型定义在接口
- 实现类确定泛型类型
实现类确定泛型类型
class GenericInterfaceImpl implements GenericInterface<String, Integer> {
@Override
public void test(String e) {
System.out.println(e);
}
@Override
public Integer add(Integer t) {
return t;
}
}
- 实现类不确定泛型,在调用的时候确定泛型
实现类不确定泛型
class GenericInterfaceImpl<E,T> implements GenericInterface<E, T> {
@Override
public void test(E e) {
System.out.println(e);
}
@Override
public T add(T t) {
return t;
}
}
在调用的时候确定泛型
GenericInterface<String, Double> gi = new GenericInterfaceImpl<String, Double>();
System.out.println(gi.add(2.5));
gi.test("hello");
- 匿名内部类确定泛型类型
GenericInterface<String, Boolean> gi = new GenericInterface<String, Boolean>(){
@Override
public void test(String e) {
System.out.println(e);
}
@Override
public Boolean add(Boolean t) {
return t;
}
};
泛型方法
概念: 把泛型定义在方法上,泛型方法又可以理解为局部泛型。
泛型方法的特点:
1. 泛型方法独立于泛型类或者泛型接口
2. 泛型方法在方法调用的时候确定类型
3. 一个泛型接口或者泛型类中可以有多个泛型方法
4. 一个泛型方法也可以定义多个泛型
泛型方法示例代码如下:
public class GenericDemo04 {
public static void main(String[] args) {
GenericMethod<String, Integer> gm = new GenericMethod<String, Integer>();
// 2.泛型方法在方法调用的时候确定类型
gm.show(20.5);
Character c = gm.test('c');
System.out.println(c);
gm.method(25, 2.5);
}
}
class GenericMethod<E,H> {
private E e;
private H h;
// 1.泛型方法独立于泛型类或者泛型接口
public <T> void show(T t) {
System.out.println(t);
}
// 3.一个泛型接口或者泛型类中可以有多个泛型方法
public <K> K test(K K) {
return K;
}
// 4.一个泛型方法也可以定义多个泛型
public <V, U> void method(V v, U u) {
System.out.println(v);
System.out.println(u);
}
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
public H getH() {
return h;
}
public void setH(H h) {
this.h = h;
}
}
泛型方法的应用
大家知道集合Collection中有一个将集合转换成数组的方法,如下所示:
Object[] toArray();
该方法存在安全隐患,代码如下所示:
Collection<String> con = new ArrayList<String>();
con.add("周星驰");
con.add("成龙");
con.add("李连杰");
Object[] objs = con.toArray();
for (Object oj : objs) {
Integer integer = (Integer) oj;//***
System.out.println(integer.intValue());
}
***处和上面说的一样,强制转换出错。
但是其实该方法也存在着另外一个重载的方法,如下所示:
<T> T[] toArray(T[] a);
方法的设计者无法知道使用者会往集合中存储何种数据类型,所以将将确定类型的权限交给调用来确定类型,那么我们就可以考虑使用泛型方法,使用泛型方法改进后,代码如下所示:
Collection<String> con = new ArrayList<String>();
con.add("周星驰");
con.add("成龙");
con.add("李连杰");
// <T> T[] toArray(T[] a); 取消了强制类型转换和安全隐患
String[] strs = con.toArray(new String[] {});
for (String s : strs) {
System.out.println(s);
}
注:泛型方法在我们写框架的时候应用非常广泛,所以我们有必要掌握它。
泛型限定符
1.泛型限定符的常用格式:
1. ?: 表示泛型可以是任意类型
2. ? extends E:表示泛型可以是E或者E的子类
3. ? super E :表示泛型可以是E或者E的父类
代码用例如下:
public class GenericDemo05 {
public static void main(String[] args) {
// Object并不是代表任意类型
Collection<Object> c1 = new ArrayList<Object>();
// ?: 表示泛型可以是任意类型
Collection<?> c2 = new ArrayList<Object>();
Collection<?> c3 = new ArrayList<Fu>();
Collection<?> c4 = new ArrayList<Daughter>();
// ? `extends` E:表示泛型可以是E或者E的子类
Collection<? extends Fu> c5 = new ArrayList<Fu>();
Collection<? extends Fu> c6 = new ArrayList<Daughter>();
Collection<? extends Fu> c7 = new ArrayList<Son>();
// Collection<? extends Fu> c8 = new ArrayList<Object>(); 编译报错
// ? `super` E :表示泛型可以是E或者E的父类
Collection<? super Fu> c9 = new ArrayList<Fu>();
// Collection<? super Fu> c10 = new ArrayList<Daughter>(); 编译报错
// Collection<? super Fu> c11 = new ArrayList<Son>(); 编译报错
Collection<? super Fu> c12 = new ArrayList<Object>();
}
}
class Fu {}
class Son extends Fu {}
class Daughter extends Fu{}
泛型限定符的应用
大家应该还记得Collection中有一些方法的形参是带有泛型限定符的,例如:
boolean addAll(Collection<? extends E> c)
~~~java
boolean addAll(Collection<? extends E> c)
在使用这个方法的时候可以采用如下几种形式:
Collection<Fu> c = new ArrayList<Fu>();
c.addAll(new ArrayList<Fu>());
c.addAll(new ArrayList<Son>());
c.addAll(new ArrayList<Daughter>());