泛型
引入
为什么需要学习泛型?,来看一下这个例子:
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());
}
程序出现如下异常:
java.lang.ClassCastException: java.lang.Integer cannot be cast to 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));
上述代码无法完全显示所有的集合内容:
我们可以继续添加 else if语句,但是我们知道Object的子类是无数的,所以每次针对Object的子类做逐一判断是不可能的,所以安全隐患永远存在,因此我们可以使用泛型改进。
泛型的由来:
1.泛型模仿了数组: 在编译时期就确定类型,不存在对所有的子类判断的问题
2.泛型模仿了方法:泛型是一种类似于方法的参数化类型
概念
泛型是JDK1.5之后引入的新特性,是一种将元素的类型提前在编译时期确定,并且它是一种参数化类型的技术
格式
- <>里面可以是任意的字母,一般泛型类会使用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());
}
泛型的好处
- 简化了代码
- 取消了黄色警告线
- 取消了强制类型转换,提高了程序的效率
- 提高了程序的安全性
- 提高了程序的扩展性和可维护性,满足了开闭原则【对扩展开放,对修改关闭】
泛型类
泛型类: 把泛型定义在类上。
JDK1.5之前没有使用泛型的时候,代码如下:
public class GernericDemo02 {
public static void main(String[] args) {
GenericClass gc = new GenericClass();
gc.setObj("张三");
Object oj = gc.getObj();
String s = (String) oj;
System.out.println(s + "|" + s.length());
}
}
class GenericClass {
private Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
以上代码存在安全隐患,如果添加如下代码:
gc.setObj(10);
会出现类型转换异常,我们可以使用泛型类来改进,继续看如下代码:
泛型类改进:
public class GernericDemo02 {
public static void main(String[] args) {
GenericClass<String> gc = new GenericClass<String>();
gc.setE("张三");
// gc.setE(10);
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;
}
}
泛型接口
概念:把泛型定义在接口
泛型接口代码如下:
interface GenericInterface<E, T> {
void test(E e);
T add(T t);
}
泛型接口的使用方式有如下三种:
-
实现类确定泛型类型
// 1.实现类确定泛型类型 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. ?: 表示泛型可以是任意类型
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)
在使用这个方法的时候可以采用如下几种形式:
Collection<Fu> c = new ArrayList<Fu>();
c.addAll(new ArrayList<Fu>());
c.addAll(new ArrayList<Son>());
c.addAll(new ArrayList<Daughter>());
泛型嵌套
泛型嵌套:泛型中可以包含泛型
有以下常见几种情况:
- Collection嵌套Collection集合
- Collection嵌套Map集合
- Map嵌套Collection集合
- Map嵌套Map集合
代码实例如下:
public class GenericDemo06 {
public static void main(String[] args) {
// 存储: 由内到外添加到集合中
Collection<Student> class01 = new ArrayList<>();
class01.add(new Student("1001", "张三", 30));
class01.add(new Student("1002", "李四", 31));
class01.add(new Student("1003", "王五", 32));
Collection<Student> class02 = new ArrayList<>();
class02.add(new Student("1001", "张三", 30));
class02.add(new Student("1002", "李四", 31));
class02.add(new Student("1003", "王五", 32));
Collection<Student> class03 = new ArrayList<>();
class03.add(new Student("1001", "张三", 30));
class03.add(new Student("1002", "李四", 31));
class03.add(new Student("1003", "王五", 32));
Collection<Collection<Student>> school = new ArrayList<Collection<Student>>();
school.add(class01);
school.add(class02);
school.add(class03);
// 遍历: 由外到内
int index = 1;
for (Collection<Student> classList : school) {
System.out.println(index + "班");
for (Student s : classList) {
System.out.println("\t" + s);
}
index ++;
}
}
}
泛型案例
- 使用
LinkedList 或者 ArrayDeque
模拟栈结构和队列结构 【要求使用泛型】 - 使用反射 + 泛型 编写
Map <-> Bean
相互转换的工具类 - 使用反射 + 泛型 编写
Map <-> List
相互转换的工具类 - 使用 反射 + 泛型 + 注解 实现 数据库的查询 【要求能够查询数据库的单条记录和多条记录】