概述
定义
编写代码时使用一些以后才指定的类型,在实例化时(instantiate)作为参数指明这些类型。
目的
解决在输入元素不确定的情况下,在运行时发生的类型转换异常 ClassCastException。
注意
1. 泛型机制被Java 5以及更新版本支持,用于解决安全问题,是一个类型安全机制。2. Java 5以及更新版本的集合类希望在定义集合时,明确表明你要向集合中装入哪种类型的数据,无法加入指定类型以外的数据,以做到数据类型安全。
3. 将运行时期出现的问题ClassCastException,转移到了编译时期,方便于程序员能够更快发现问题,减少程序运行出错的概率。
4. 泛型是提供给Java编译编译阶段使用的技术,而在生成字节码文件时会进行类型擦除(定义在方法体内的类型参数),使程序运行效率不受影响。并且对参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
5. 在使用了泛型技术的集合中取出元素将不再需要进行强制类型转换。
6. 由于编译生成的字节码会去掉泛型的类型安全限制,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,如用反射得到集合,再调用add方法即可。
格式
通过<>来定义要操作的引用数据类型如List<String> list = new ArrayList<String>(); // 定义要存入集合中的元素指定为String类型
术语
- 泛型(Generic):适用于许多类型,是一个统称。
- 泛型类:类可以适用于许多类型。
- 泛型接口:接口可以适用于许多类型。
- 泛型方法:方法可以适用于许多类型。
- 原始类型(raw type):不带任何实际类型参数的泛型名称。每一个泛型都有一个原生态类型,与List<E>相对应的原生态类型是List。
- 形式类型参数:和方法类似,形式类型参数代表一系列类型参数,如K表示键,V表示值,E表示元素,T表示类型。
- 实际类型参数:形式类型参数的实际值,如String。
- 参数化的类型:被实际类型参数泛型的类,如List<String>。
参数化类型
1. 参数化类型与原始类型兼容性
- 参数化类型可引用一个原始类型的对象,编译警告。
List<String> list = new ArrayList();
- 原始类型可引用一个参数化类型的对象,编译警告
List list = new ArrayList<String>();
2. 参数的类型不考虑类型参数的继承关系
List<String> list = new ArrayList<Object>(); // 错误List<Object> list = new ArrayList<String>(); // 错误
List<String> list = new ArrayList(); // 警告
List list = new ArrayList<String>(); // 警告
3. 不允许创建带泛型机制对象的数组
List<String>[] list = new ArrayList<String>()[10]; // 错误4. 示例
public static void printCollection(List<String> list) {
for (Iterator<String> it = list.iterator(); it.hasNext();) {
System.out.println( it.next());
}
}
当传入参数List<Object> list = new ArrayList<Object>();时,编译器报错。
通配符
1. 当传入的类型不确定时,可以使用通配符“?”。使用通配符的好处是可以不用明确传入的参数类型,这样在使用泛型类或者泛型方法时,提高了扩展性。2. 不可以使用Object来替代通配符,因为如果使用Object,那么泛型类型就是确定的Object,在new对象或者其他需要泛型安全检查的场合将会报错。
3. 使用通配符泛型的变量,可以引用其他具体参数化的类型,但是不能调用和具体实际类型参数相关的方法。
示例
public static void printCollection(Collection<?> list) {
for (Iterator<?> it = list.iterator(); it.hasNext();) {
System.out.println( it.next());
}
list = new ArrayList<String>(); // 如果泛型使用Object,那么该行将编译失败
}
泛型限定
目的
由于通配符能够适用于任何类型,所以当需要将泛型限定为与特定类有关的类型时,通配符将无法做到这一点。所以引入关键字extends和super用于对通配符进行限定。
格式
? super T: 接收T类型或者T的父类型。下限。
? extends T: 接收T类型或者T的子类型。上限。
? extends T: 接收T类型或者T的子类型。上限。
示例
public static void printCollectionLtd(Collection<? extends Person> list ) {
for (Iterator<? extends Person> it = list .iterator(); it .hasNext();) {
System. out.println( it.next());
}
}
当传入的集合泛型不是Person类型或Person的子类型时,编译器报错。
泛型类和泛型接口
概述
- 被定义了泛型的类或接口,称之为泛型类或泛型接口。
- 当类中的引用数据类型不确定时,可以定义泛型类来完成扩展性要求。
注意
- 泛型类定义的泛型在整个类中有效。
- 泛型类的使用需要在使用到类名时后面加上泛型参数。
- 在对泛型进行参数化时,实际类型参数必须是引用类型,不能是基本类型。
- 泛型参数只能被实例变量和方法调用(包括内部类),而不能被静态域和静态方法调用,因为静态成员是被所有参数化的类共享的,所以静态成员不应该有类级别的类型参数。
- 当一个类实现泛型接口或者继承自泛型类时,该类也是泛型类,前后泛型要一致。
示例
带泛型的队列实现,可以实现任意类型元素的存取。
class QueueGen<E> {
private LinkedList<E> list = null;
QueueGen() {
list = new LinkedList<E>();
}
boolean isEmpty() {
return list.isEmpty();
}
void print() {
for (ListIterator<E> it = list.listIterator(list .size()); it .hasPrevious();) {
System.out.println( it.previous());
}
}
void enQueue(E e) {
list.addFirst( e);
}
E deQueue() {
return list.removeLast();
}
}
泛型接口
interface Inter<T> {
void print(T t);
}
class SubInter<T> implements Inter<T> { // 前后类型必须一致。
@Override
public void print(T t) {
System.out.println( t);
}
}
泛型方法
概述
- 被定义了泛型的方法,称之为泛型方法。
- 当不同的方法需要使用不同的类型参数时,可以将泛型定义在方法上。
- 泛型T只能替代引用类型而不可以是基本类型,单独T替代基本类型时会自动装箱,而T[]替代基本类型数组时则会报错。
注意
- 若静态方法需要使用泛型,那么必须将泛型定义在方法上。
- 静态方法不可以访问定义在类上的泛型参数。
- 方法上的泛型必须定义在所有的修饰符之后和返回值类型之前。
- 只有引用类型才能作为泛型方法的实际类型参数。
- 定义泛型限定可以使用“&”符号来指定多个边界。比如Collections工具类中的max方法:
static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) - 在泛型中可同时有多个类型参数,用逗号隔开即可。比如Collections工具类中的synchronizedMap方法:
static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) - 普通方法、构造函数和静态方法中都可以使用泛型。
示例
class Util {
public <T> int getHashCode(T e) {
return e.hashCode();
}
public static <T> void print(T t ) {
System.out.println( t);
}
}
案例
获取可比较集合的最大元素(即Collections.max方法)
Person.java
package bean;
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge( int age) {
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj ) {
if (this == obj) return true ;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false ;
Person other = (Person) obj;
if (age != other.age) return false;
if (name == null) {
if (other.name != null) return false;
} else if (!name.equals(other.name)) return false;
return true;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]" ;
}
@Override
public int compareTo(Person o) {
int margin = this.name.compareTo( o. name);
return margin == 0 ? this. age - o. age : margin;
}
}
Employee.java
package bean;
public class Employee extends Person {
private int salary ;
public Employee(String name, int age, int salary) {
super(name, age);
this.salary = salary;
}
public Employee() {
super();
}
public int getSalary() {
return salary;
}
public void setSalary( int salary) {
this.salary = salary;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + salary;
return result;
}
@Override
public boolean equals(Object obj ) {
if (this == obj) return true ;
if (!super.equals( obj)) return false ;
if (getClass() != obj.getClass()) return false ;
Employee other = (Employee) obj;
if (salary != other.salary) return false;
return true;
}
@Override
public String toString() {
return "Employee [Name=" + getName() + ", Age=" + getAge() + ", Salary=" + salary + "]" ;
}
}
ComparatorByAge.java
package comparator;
import java.util.Comparator;
import bean.Person;
public class ComparatorByAge implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
int margin = o1.getAge() - o2.getAge();
return margin == 0 ? o1 .compareTo(o2 ) : margin ;
}
}
ComparatorByLength.java
package comparator;
import java.util.Comparator;
public class ComparatorByLength implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
int result = s1.length() - s2.length();
return result == 0 ? s1 .compareTo(s2 ) : result ;
}
}
GenericTest.java
package generic;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import bean.Employee;
import bean.Person;
import comparator.ComparatorByAge;
import comparator.ComparatorByLength;
public class GenericTest {
public static void main(String[] args) {
// 案例:获取集合中最大值
HashSet<Person> set = new HashSet<Person>();
set.add(new Person( "tom", 23));
set.add(new Employee( "jack", 22, 9000)); // 自动进行类型提升
set.add(new Person( "nana", 24));
ArrayList<String> strs = new ArrayList<String>();
strs.add("java");
strs.add("niubility");
strs.add("zzz");
strs.add("it&java");
Person per_max = getMax(set );
System.out.println( "Max Person: " + per_max );
String str_max = getMax(strs );
System.out.println( "Max String: " + str_max );
Person per_max_com = getMax(set , new ComparatorByAge());
System.out.println( "Max_Age Person: " + per_max_com );
String str_max_com = getMax(strs , new ComparatorByLength());
System.out.println( "Max_Length String: " + str_max_com);
}
/*
* 1. 由于返回值类型不确定,所以需要定义泛型方法。
* 2. 由于实现需要使用到compareTo方法,所以方法中的类型必须限定在Comparable接口及其实现。
* 3. 当对泛型T进行限定时,需要用到Comparable接口,而该接口必须使用泛型确保类型安全。
* 这时返回的类型是T并且是比较器的子类,而比较器的类型可以是T及其父类,传入参数的类型可以是T及其子类。
* 也就是说返回值类型可以是传入集合元素类型的父类类型。
* 如果Collection泛型定义为<T>,那么传入集合元素是什么类型,返回就是什么类型集合。
* (因为比较需要用到类的属性,父类有的属性子类一定有,所以可比。)
*/
public static <T extends Comparable<? super T>> T getMax(Collection<? extends T> coll ) {
Iterator<? extends T> it = coll.iterator();
T max = it.next();
while ( it.hasNext()) {
T temp = it.next();
if (temp.compareTo( max) > 0) {
max = temp;
}
}
return max;
}
/*
* 1. 由于返回值类型不确定,所以需要定义泛型方法。
* 2. 当使用比较器时,该接口必须使用泛型确保类型安全。
* 这时比较的类型是T,而比较器的类型可以是T及其父类(因为比较需要用到类的属性,父类有的属性子类一定有,所以可比)。
*/
public static <T> T getMax(Collection<T> coll , Comparator<? super T> com ) {
Iterator<T> it = coll.iterator();
T max = it.next();
while ( it.hasNext()) {
T temp = it.next();
if (com.compare(temp, max) > 0) {
max = temp;
}
}
return max;
}
}
运行结果
Max Person: Person [name=tom, age=23]
Max String: zzz
Max_Age Person: Person [name=nana, age=24]
Max_Length String: niubility