泛型的基本概念
泛型是java se1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
java语言引入泛型的好处是安全简单。
在java se1.5之前,没有泛型的情况下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的,对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
示例如下:
List list = new ArrayList();
list.add("CSDN_SEU_Cavin");
list.add(100);
for (int i = 0; i < list.size(); i++) {
//取出Integer时,运行时出现异常
String name = (String) list.get(i);
System.out.println("name:" + name);
}
本例向list类型集合中加入了一个字符串类型的值和一个Integer类型的值。(这样合法,因为此时list默认的类型为Object类型)。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他原因,运行时会出现java.lang.ClassCastException异常。为了解决这个问题,泛型应运而生。
泛型只在编译阶段有效
ArrayList<String>a = new ArrayList<String>();
ArrayListb = new ArrayList();
Classc1 = a.getClass();
Classc2 = b.getClass();
System.out.println(c1 + " " + c2);
System.out.println(c1 == c2); //true
上面程序c1和c2都是class java.util.ArrayList类,输出结果为true。因为所有反射的操作都是在运行时的,既然为true,就证明了编译之后,程序会采取去泛型化的措施。
也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,成功编译过后的class文件中是不包含任何泛型信息的。
泛型类的使用
一个泛型类(genericclass)就是具有一个或多个类型变量的类。
Java的泛型就是创建一个用类型作为参数的类。就象我们写类的方法一样,方法是这样的method(String str1, String str2),方法中参数str1、str2的值是可变的。而泛型也是一样的,这样写class Generics<K, V>,这里边的K和V就象方法中的参数str1和str2也是可变。
定义Pair泛型类:
package com.zxt.pair;
public classPair<T> {
private T first;
private T second;
public Pair() {
this.first = null;
this.second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
Pair类引入了一个类型变量T,用尖括号(<>)括起来,并放在类名的后面。
泛型类可以有多个类型变量。例如:public class Pair<T, U> { … }。
类定义中的类型变量指定方法的返回类型以及域和局部变量的类型。
使用Pair类:
PairAlg.java
package com.zxt.pairtest;
import com.zxt.pair.Pair;
public classArrayAlg {
public static Pair<String>minmax(String[] a){
if (a == null || a.length == 0) {
return null;
}
Stringmin = a[0];
Stringmax = a[0];
for (int i = 1; i < a.length; i++) {
if (min.compareTo(a[i]) > 0) {
min = a[i];
}
if (max.compareTo(a[i]) < 0) {
max = a[i];
}
}
return new Pair<String>(min, max);
}
}
PairTest1.java
package com.zxt.pairtest;
import com.zxt.pair.Pair;
public classPairTest1 {
public static void main(String[] args) {
String[]words = { "Mary", "had", "a", "little","lamb" };
Pair<String>mm = ArrayAlg.minmax(words);
System.out.println(mm.getFirst());
System.out.println(mm.getSecond());
}
}
泛型方法的使用
除了泛型类外,还可以只单独定义一个方法作为泛型方法,可以指定方法参数或者返回值指定为泛型类型的声明,留待运行时确定。泛型方法就可以声明在泛型类中,也可以声明在普通类中。
泛型类在多个方法签名间实施类型约束。例如在List<V>中,类型参数V出现在get()、add()、contains()等方法的签名中。当创建一个Map<K, V>类型的变量时,你就在方法之间宣称一个类型约束。你传递给add()的值将与get()返回的值的类型相同。
类似地,之所以声明泛型方法,一般是因为你想要在该方法的多个参数之间宣称一个类型约束。
一个简单的例子:
package com.zxt.generic;
public classGenericMethodTest {
public static void main(String[] args) {
put("Hello");
System.out.println(get(100));
}
// T表示泛型类型的对象,无论给put方法什么参数,只要不为空就都可以输出
public static<T> void put(T t) {
if(t != null) {
System.out.print(t.toString()+" ");
}
}
// 无论给get方法什么参数,只要不为空就都可以返回
public static<T> T get(T t) {
if(t != null) {
return t;
}
return null;
}
}
泛型接口的使用
泛型接口的使用和泛型类差不多,
泛型接口定义:public interface IPool <T> { T get(); int add(T t); }
泛型接口的实现:public class GenericPool<T>implements IPool<T> { … }
publicclass GenericPool implements IPool<Account>{ … }
简单的例子如下:
package com.zxt.generic;
import java.util.Date;
public classGenericTest implementsGenericInterfaceTest<String, Integer> {
public static void main(String[] args) {
GenericTestgt = new GenericTest();
gt.show("Hi", 12306);
Test<String,Date> t = new Test<String,Date>();
t.show("Hello",newDate());
}
public void show(String t, Integer u) {
t = t + t;
u = u * 100;
System.out.println(t + u);
}
}
class Test<T, U> implements GenericInterfaceTest<T, U> {
public void show(T t, U u) {
System.out.println(t.toString() + u.toString());
}
}
泛型变量的限定
定义泛型变量的上界:public class NumberGeneric< T extendsNumber>
泛型变量上界的说明:上述方式的声明规定了NumberGeneric类所能处理的类型变量其类型和Number有继承关系;
extends关键字所声明的上界既可以是一个类,也可以是一个接口;当泛型变量这样声明时,在实例化一个泛型类时,需要明确类型必须为指定上界类型或者子类。
<T extends Bounding Type>表示T应该是绑定类型的子类型(subtype)。T和绑定类型可以是类,也可以是接口。选择关键字extends的原因是更接近子类的概念。一个类型变量或通配符可以有多个限定,限定类型用“&”分割。例如:T extends Comparable & Serializable
简单示例:
ArrayAlg2.Java
package com.zxt.pairtest;
import com.zxt.pair.Pair;
@SuppressWarnings({ "unchecked", "rawtypes" })
public classArrayAlg2 {
public static <T extends Comparable>Pair<T> minmax(T[] a) {
if (a == null || a.length == 0) {
return null;
}
Tmin = a[0];
Tmax = a[0];
for (int i = 1; i < a.length; i++) {
if (min.compareTo(a[i]) > 0) {
min = a[i];
}
if (max.compareTo(a[i]) < 0) {
max = a[i];
}
}
return new Pair<T>(min, max);
}
}
PairTest2.java
package com.zxt.pairtest;
import java.util.Calendar;
import java.util.GregorianCalendar;
import com.zxt.pair.Pair;
public classPairTest2 {
public static void main(String[] args) {
GregorianCalendar[]birthdays = {
new GregorianCalendar(1906,Calendar.DECEMBER,9),
new GregorianCalendar(1815,Calendar.DECEMBER,10),
new GregorianCalendar(1903,Calendar.DECEMBER,3),
new GregorianCalendar(1910,Calendar.JUNE,22)
};
Pair<GregorianCalendar>mm = ArrayAlg2.minmax(birthdays);
System.out.println(mm.getFirst().get(GregorianCalendar.YEAR));
System.out.println(mm.getSecond().get(GregorianCalendar.YEAR));
}
}
定义泛型变量的下界:List<? super CashCard> cards = newArrayList<T>();
泛型变量下界的说明:通过使用super关键字可以固定泛型参数的类型为某种类型或者其超类。当程序希望为一个方法的参数限定类型时,通常可以使用下限通配符。
public static <T> void sort(T[] a, Comparator<? super T> c){ … }
通配符类型
通配符:“?”符号表明参数的类型可以是任何一种类型,它和参数T的含义是有区别的。T表示一种未知类型,而“?”表示任何一种类型。这种通配符一般有以下三种用法:
单独的?,用于表示任何类型。
? extends type,表示带有上界。
? super type,表示带有下界。
可以使用无限定的通配符,例如,Pair<?>。初看起来,这好像与原始的Pair类型一样。实际上,有很大的不同。Pair<?>和Pair本质的不同在于:可以用任意Object对象调用原始的Pair类的setObject方法。
实例
Employee.java
package com.zxt.pair;
import java.util.Date;
import java.util.GregorianCalendar;
public classEmployee {
private String name;
private double salary;
private Date hireDay;
public Employee(String name, double salary, int year, int month, int day) {
this.name = name;
this.salary = salary;
GregorianCalendarcalendar = new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Date getHireDay() {
return hireDay;
}
public void setHireDay(Date hireDay) {
this.hireDay = hireDay;
}
}
Manager.java
package com.zxt.pair;
public classManager extendsEmployee {
private double bonus;
public Manager(String name, double salary, int year, int month, int day) {
super(name, salary, year, month, day);
this.bonus = 0;
}
public double getSalary() {
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
}
PairAlg.java
package com.zxt.pair;
public classPairAlg {
public static boolean basNulls(Pair<?> p) {
return p.getFirst() == null || p.getSecond() == null;
}
public static void swap(Pair<?> p) {
swapHelper(p);
}
public static <T> voidswapHelper(Pair<T> p) {
Tt = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
}
PairTest3.java
package com.zxt.pairtest;
import com.zxt.pair.Employee;
import com.zxt.pair.Manager;
import com.zxt.pair.Pair;
import com.zxt.pair.PairAlg;
public classPairTest3 {
public static void main(String[] args) {
Managerceo = new Manager("Gus Greedy",800000, 2003, 12, 15);
Managercfo = new Manager("Sid Sneaky",600000, 2003, 12, 15);
Pair<Manager>buddies = new Pair<Manager>(ceo, cfo);
printBuddies(buddies);
ceo.setBonus(1000000);
cfo.setBonus(500000);
Manager[]managers = { ceo, cfo };
Pair<Employee>result = new Pair<Employee>();
minmaxBonus(managers, result);
System.out.println("first: "+ result.getFirst().getName()+ ", second: " + result.getSecond().getName());
maxminBonus(managers, result);
System.out.println("first: "+ result.getFirst().getName()+ ", second: " + result.getSecond().getName());
}
public static void printBuddies(Pair<? extends Employee> p) {
Employeefirst = p.getFirst();
Employeesecond = p.getSecond();
System.out.println(first.getName() + " and "+ second.getName()+ " are buddies");
}
public static void minmaxBonus(Manager[] a, Pair<? super Manager> result) {
if (a == null || a.length == 0) {
return;
}
Managermin = a[0];
Managermax = a[0];
for (int i = 0; i < a.length; i++) {
if (min.getBonus() > a[i].getBonus()) {
min = a[i];
}
if (max.getBonus() < a[i].getBonus()) {
max = a[i];
}
}
result.setFirst(min);
result.setSecond(max);
}
public static void maxminBonus(Manager[] a, Pair<? super Manager> result) {
minmaxBonus(a, result);
PairAlg.swapHelper(result);
}
}
泛型类型的继承规则
Java语言中的数组是协变的(covariant),也就是说,如果Integer扩展了Number(事实也是如此),那么不仅Integer是Number,而且Integer[]也是Number[],在要求Number[]的地方完全可以传递或者赋予Integer[]。(更正式地说,如果Number是Integer的超类型,那么Number[]也是Integer[]的超类型)。
由于Employee是Manager的超类,因此可以将一个Manager[]数组赋给一个类型为Employee[]的变量:
Manager[] managerBuddies ={ceo, cfo};
Employee[] employeeBuddies =managerBuddies;
你也许认为这一原理同样适用于泛型类型——Pair<Employee>是Pair<Manager>的超类型,那么可以将一个Pair<Manager>赋给一个类型为Pair<Employee>的变量。不幸的是,情况并非如此。
Pair<Manager>managerBuddies = new Pair<Manager>(ceo, cfo);
Pair<Employee>employeeBuddies = managerBuddies; //illegal
employeeBuddies.setFirst(lowlyEmployee);
不允许这样做有一个很充分的理由:这样做将破坏要提供的类型安全泛型。Java语言中泛型类不具协变性。如果能够将List<Integer>赋给List<Number>。那么下面的代码就允许将非Integer的内容放入List<Integer>:
List<Integer> li = newArrayList<Integer>();
List<Number> ln = li;// illegal
ln.add(new Float(3.1415));
泛型类可以扩展或实现其它的泛型类。就一点而言,与普通的类没有什么区别。 例如,ArrayList<T>类实现List<T>接口。这意味着,一个ArrayList<Manager>可以被转换为一个List<Manager>。
总结
在定义一个泛型类的时候,在“<>”之间定义形式类型参数,例如:“class TestGeneric<K, V>”,其中“K” , “V”不代表值,而是表示类型。实例化泛型对象的时候,一定要在类名后面指定类型参数的值(类型),一共要有两次书写。例如:
TestGeneric<String, String>t = new TestGeneric<String, String>();
泛型中<T extends Object>,extends并不代表继承,它是类型范围限制。
泛型不是协变的。
使用泛型的优点
1、类型安全。通过知道泛型定义的变量类型限制,编译器可以更有效地提高Java程序的类型安全。
2、消除强制类型转换。消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。所有的强制转换都是自动和隐式的。
3、向后兼容。
4、层次清晰。
5、性能较高。用GJ(泛型JAVA)编写的代码可以为java编译器和虚拟机带来更多的类型信息,这些信息对java程序做进一步优化提供条件。
使用泛型的注意事项
1、不能用基本类型实例化类型参数
2、运行时类型查询只适用于原始类型
3、不能抛出也不能捕获泛型类实例
4、参数化类型的数组不合法
5、不能实例化类型变量
6、泛型类的静态上下文中类型变量无效
7、注意擦除后的冲突