泛型是从 Java 5 开始增加的对于数据类型的规范和检测机制,使用泛型有如下两个优点
- 可以有效地避免
ClassCastException
,在编译期就可以使用泛型发现类型转换错误,或者类型不匹配
- 避免强类型转换
一、泛型引入
请看下面的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class ShowExample { public static void main(String[] args) { List list = new ArrayList(); list.add("这是一个String"); list.add(123); printSomeThing(list); } public static void printSomeThing(List list) { for (int i = 0; i < list.size(); i++) { String temp = (String) list.get(i); System.out.println(temp); } } }
|
运行结果如下
1 2
| 这是一个String Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
|
第一行被正确打印,但是第二行的整形却在进行强制类型转换的时候失败了,这个如果发生在程序运行期间,会给整个项目带来安全问题。所以,如果能在编译期间就发现这个问题,会对程序的健壮性有很大的提高。
泛型使用一对尖括号来设置变量的类型,多用在集合框架中。比如,如下代码定义了一个只能存放 String
类型数据的 List
。
1
| List<String> stringList = new ArrayList<String>();
|
二、泛型类
当类中要操作的引用数据类型不确定时,就需要使用泛型。
JDK 1.5 之前使用 Object 来完成扩展
现在我们需要定义一个工具类用来设置和获取属性值,在没有泛型之前,可以通过下面的方法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class GenericClassDemo { public static void main(String[] args) { ToolOld toolOld = new ToolOld(); toolOld.setObject(new Cat()); Cat cat = (Cat) toolOld.getObject(); } } class Cat { } class Dog { }
class ToolOld { private Object object;
public Object getObject() { return object; }
public void setObject(Object object) { this.object = object; } }
|
运行结果没有报错,因为 Object
是所有类的父类,上面的 ToolOld
通过 object
属性暂存数据,当需要获取数据时再通过强制转换,转换成原来的类型。
定义泛型类
泛型类的定义方法如下
1 2 3 4 5 6 7
| public class ClassName<泛型标识符> { private 泛型标识符 属性名称; private void testMethod(T t) { ... } }
|
使用泛型类
上面 Object
的方法可以使用泛型来代替。下面定义了一个泛型类 ToolGeneric
,每次创建对象时可以传入类型,在调用 set
方法时,如果类型不匹配,则编译不会通过。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class GenericClassDemo { public static void main(String[] args) { ToolGeneric<Cat> toolGeneric = new ToolGeneric<Cat>(); toolGeneric.setT(new Cat()); Cat cat = toolGeneric.getT(); } }
class Cat {
} class Dog {
}
class ToolGeneric<T> { private T t;
public T getT() { return t; } public void setT(T t) { this.t = t; } }
|
另一个泛型类的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class GenericClassShowCase { public static void main(String[] args) { PrintTool<String> printToolStr = new PrintTool<String>(); printToolStr.printSomeThing("你好");
PrintTool<Integer> printToolInt = new PrintTool<Integer>(); printToolInt.printSomeThing(3123412); } }
class PrintTool<T> { public void printSomeThing(T t) { System.out.println(t); }
}
|
运行结果 :
上面的泛型类通过每次指定不同的类型,可以打印不同的数据类型。但是每打印一个新的数据类型都需要运行 PrintTool<XXX> printTool = new PrintTool<XXX>()
来创建新的类,这样也不方便,所以有必要使用泛型方法。
泛型方法
泛型方法定义时需要将一对尖括号包裹的泛型类型写在函数的返回值之前。
1 2 3
| public <T> void getName(T t) { // TODO }
|
下面是一个泛型方法的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class GenericMethodShow { public static void main(String[] args) { GenericMethod<String> stringGenericMethod = new GenericMethod<String>(); stringGenericMethod.show("你好");
stringGenericMethod.print("你好"); stringGenericMethod.print(123124);
GenericMethod.showStatic("static");
} }
class GenericMethod<T> { public void show(T t) { System.out.println("show: " + t); }
public <T2> void print(T2 t) { System.out.println("print: " + t); }
public static <T3> void showStatic(T3 t3) { System.out.println("static: " + t3); } }
|
1 2 3 4
| show: 你好 print: 你好 print: 123124 static: static
|
泛型接口
泛型接口的定义方式为
IterName{1 2
| interface IterName<T>{ }
|
泛型接口使用时可以在泛型类的定义时传入泛型的类型,也可以在调用方法时通过参数来确定泛型的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class GenericInterfaceShow { public static void main(String[] args) { ShowInterImpl1 showInterImpl1 = new ShowInterImpl1(); showInterImpl1.show("哈哈");
ShowInterImpl2 showInterImpl2 = new ShowInterImpl2(); showInterImpl2.show("哈哈2"); showInterImpl2.show(1234); } }
interface ShowInter<T> { void show(T t); }
class ShowInterImpl1 implements ShowInter<String> { @Override public void show(String s) { System.out.println(s); } }
class ShowInterImpl2<T> implements ShowInter<T> {
@Override public void show(T t) { System.out.println(t); } }
|
运行结果如下
泛型限定
当不知道数据类型时,可以用 ?
作为通配符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class GenericExtends { public static void main(String[] args) { ArrayList<Person> personList = new ArrayList<Person>(); personList.add(new Person("aaa")); personList.add(new Person("bbb")); personList.add(new Person("ccc"));
ArrayList<Student> studentList = new ArrayList<Student>(); personList.add(new Person("aaa1")); personList.add(new Person("bbb2")); personList.add(new Person("ccc3"));
ArrayList<String> strings = new ArrayList<String>(); strings.add("a"); strings.add("b"); strings.add("c");
ArrayList<Integer> integers = new ArrayList<Integer>(); integers.add(1); integers.add(2); integers.add(3); printData(strings); printData(integers);
}
public static void printData(ArrayList<?> dataList) { Iterator<?> iterator = dataList.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } public static <T> void printData2(ArrayList<T> dataList) { Iterator<T> iterator = dataList.iterator(); while (iterator.hasNext()) { T t = iterator.next(); System.out.println(t); } } }
|
运行结果如下
可以看出:使用 ?
可以匹配不同类型的数据
这个例子说明 子父类之间的泛型不能通过多态调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class GenericExtends { public static void main(String[] args) { ArrayList<Person> personList = new ArrayList<Person>(); personList.add(new Person("aaa")); personList.add(new Person("bbb")); personList.add(new Person("ccc"));
ArrayList<Student> studentList = new ArrayList<Student>(); studentList.add(new Student("aaa1")); studentList.add(new Student("bbb2")); studentList.add(new Student("ccc3")); printPerson(personList); } public static void printPerson(ArrayList<Person> personList) { Iterator<Person> iterator = personList.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next().getName()); } } }
class Person { private String name;
public Person(String name) { this.name = name; }
public String getName() { return name; } }
class Student extends Person { public Student(String name) { super(name); }
}
|
现在我们确实需要使用 printPerson
方法来打印 studentList
,将 printPerson
申明为泛型方法是一个途径:
1 2 3 4 5 6 7 8
| public static <T> void printPerson(ArrayList<T> personList) { Iterator<T> iterator = personList.iterator(); while (iterator.hasNext()) {
System.out.println(((Person) iterator.next()).getName()); }
}
|
或者使用 ?
1 2 3 4 5 6 7
| public static void printPerson(ArrayList<?> personList) { Iterator<?> iterator = personList.iterator(); while (iterator.hasNext()) { System.out.println(((Person) iterator.next()).getName()); }
}
|
但是这两种方法都有局限性:无论 personList
中存储的是什么类型的数据都可以传入。这就存在了风险:就上面的例子来说,如果 personList
里边存储的不是 Person
类(或者子类),就会造成类型转换异常。为了解决这个问题,引入了类型限定。
使用 <? extends Person>
来限定 personList
只能存储 Person
类或者其子类型。
下面是一个类型限定的例子
1 2 3 4 5 6
| public static void printPersonExtend(ArrayList<? extends Person> personList) { Iterator<? extends Person> iterator = personList.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next().getName()); } }
|
增加一个汽车类 Car
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Car { private String brandName;
public Car(String brandName) { this.brandName = brandName; }
public String getBrandName() { return brandName; }
public void setBrandName(String brandName) { this.brandName = brandName; } }
|
调用打印方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class GenericExtends { public static void main(String[] args) { ArrayList<Person> personList = new ArrayList<Person>(); personList.add(new Person("aaa")); personList.add(new Person("bbb")); personList.add(new Person("ccc"));
ArrayList<Student> studentList = new ArrayList<Student>(); studentList.add(new Student("aaa1")); studentList.add(new Student("bbb2")); studentList.add(new Student("ccc3")); // 打印 Person printPersonExtend(personList); // 因为 Stduent 是 Person 的子类,所以也可以打印 printPersonExtend(studentList);
ArrayList<Car> carList = new ArrayList<Car>(); carList.add(new Car("Benz")); carList.add(new Car("BMW")); carList.add(new Car("Porsche")); // 编译失败,因为 Car 类和 Person 类没有任何关系 // printPersonExtend(carList);
}
public static void printPersonExtend(ArrayList<? extends Person> personList) { Iterator<? extends Person> iterator = personList.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next().getName()); } }
|
与此同时,使用 <? super Student>
限定只能传入 Student
类及其父类,TreeSet
的一个构造方法就使用如下的限定符:
1 2 3
| public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<E,Object>(comparator)); }
|
下面是一个使用 <? super E>
的例子:
定义一个 Worker
类,它同时也是 Person
的子类
1 2 3 4 5 6
| class Worker extends Person {
public Worker(String name) { super(name); } }
|
现在我们用 TreeSet
分别存储 学生 和 工人,并将它们倒序输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public class GenericSuper { public static void main(String[] args) { TreeSet<Student> studentTreeSet = new TreeSet<Student>(new StudentComparator()); studentTreeSet.add(new Student("s--001")); studentTreeSet.add(new Student("s--002")); studentTreeSet.add(new Student("s--003")); Iterator<Student> studentIterator = studentTreeSet.iterator();
while (studentIterator.hasNext()) { System.out.println(studentIterator.next().getName()); } TreeSet<Worker> workerTreeSet = new TreeSet<Worker>(new WorkerComparator()); workerTreeSet.add(new Worker("w--001")); workerTreeSet.add(new Worker("w--002")); workerTreeSet.add(new Worker("w--003"));
Iterator<Worker> workerIterator = workerTreeSet.iterator(); while (workerIterator.hasNext()) { System.out.println(workerIterator.next().getName()); } } }
class StudentComparator implements Comparator<Student> {
@Override public int compare(Student s1, Student s2) { return s2.getName().compareTo(s1.getName()); } }
class WorkerComparator implements Comparator<Worker> {
@Override public int compare(Worker w1, Worker w2) { return w2.getName().compareTo(w1.getName()); } }
|
输出结果如下
1 2 3 4 5 6
| s--003 s--002 s--001 w--003 w--002 w--001
|
上面两种不同的对象比较,使用了不同的比较器,是否可以将其合并成一个呢?
查看源码 TreeSet
当前的构造函数如下
1 2 3
| public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<E,Object>(comparator)); }
|
这个源码说明比较器参数可以传入 E
类型,或者 E
类型的 父类型
现在重写一个通用的比较器
1 2 3 4 5 6 7 8
|
class AllPersonComp implements Comparator<Person> { @Override public int compare(Person s1, Person s2) { return s2.getName().compareTo(s1.getName()) } }
|
在定义 TreeSet
时传入上面的比较器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class GenericSuper { public static void main(String[] args) { TreeSet<Student> studentTreeSet = new TreeSet<Student>(new AllPersonComp()); studentTreeSet.add(new Student("s--001")); studentTreeSet.add(new Student("s--002")); studentTreeSet.add(new Student("s--003")); Iterator<Student> studentIterator = studentTreeSet.iterator();
while (studentIterator.hasNext()) { System.out.println(studentIterator.next().getName()); }
TreeSet<Worker> workerTreeSet = new TreeSet<Worker>(new AllPersonComp()); workerTreeSet.add(new Worker("w--001")); workerTreeSet.add(new Worker("w--002")); workerTreeSet.add(new Worker("w--003"));
Iterator<Worker> workerIterator = workerTreeSet.iterator(); while (workerIterator.hasNext()) { System.out.println(workerIterator.next().getName()); } }
}
|
运行结果与上面相同