一、使用泛型的原因
1、提出问题:
public class Max {
public static void main(String[] args){
String str = "hello world";
Integer number = new Integer(2);
System.out.println(max(str, number));
}
public static Comparable max(Comparable o1, Comparable o2){
if(o1.compareTo(o2) > 0){
return o1;
}
else{
return o2;
}
}
}
String 类和 Integer类都实现了Comparable接口,在编译时是不会报错的,但是Integer和String是无法比较的,因此在运行时会抛出 java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。怎样才能在编译时就报错呢?
使用泛型可以指定类或方法一起工作的对象的类型,从而在编译时就找出不相容的对象(比如Integer和String不相容)。
改进代码如下:
public static <E extends Comparable<E>> E max(E o1, E o2){
if(o1.compareTo(o2) > 0){
return o1;
}
else{
return o2;
}
}
二、在类,方法,接口中使用泛型
1、类
public class GenericStack<T> {
private List<T> list = new ArrayList<T>();
public void push(T t){
list.add(t);
}
public T pop(){
int size = list.size();
if(size > 0){
return list.get(list.size() - 1);
}
return null;
}
}
为了定义个类为泛型类型,需要将泛型类型放在类名之后,例如:GenericStack<T>
public class Max {
public static void main(String[] args) {
Integer[] numbers = { 4, 2, 6, 1, 2 };
Integer max = Max.<Integer>max(numbers);
System.out.println(max);
}
public static <E extends Comparable<E>> E max(E[] objects){
int length = objects.length;
if (length == 0) {
return null;
}
E max = objects[0];
for (int i = 1; i < length; i++) {
if (max.compareTo(objects[i]) < 0) {
max = objects[i];
}
}
return max;
}
}
为了定义一个方法为泛型类型,需要将泛型类型放在方法的返回类型之前。如
public static <E extends Comparable<E>> E max(E[] objects)
1、问题:
定义一个泛型栈类型MyStack<T>,我们申明如下方法:
public static double max(MyStack<Number> myStack){
...
}
但是当我们如下调用时:
MyStack<Integer> myStack = new MyStack<Integer>();
myStack.push(12);
myStack.push(11);
System.out.println("The max is " + max(myStack));
却会报错,那是因为:Number是Integer的父类,但是MyStack<Number> 不是MyStack<Integer> 的父类,因而MyStack<Number>无法引用MyStack<Integer>。
改进:将方法中的变量MyStack<Number> 改为MyStack<? extends Number> 则可以通配 MyStack<Integer> 、MyStack<Double> ...各种类型。
2、通配泛型的类型有三种:?, ? extends T 或者 ? super T, 其中T是个泛型。
?称为非受限的通配,和? extends Objects一样。
? extends T 称为受限通配,表示T或者T的一个子类型,例子如上
? super T 称为下限通配,表示T或者T的一个父类型,例子如下
/*
* 将stack2中的值放到stack1中
* stack1要容纳stack2中的值,那么stack1中值得类型必须是stack2中的类型或是它的父类
*/
public static <T> void add(MyStack<? super T> stack1, MyStack<T> stack2){
while(!stack2.isEmpty()){
stack1.push(stack2.pop());
}
}
3、原始类型
MyStack<Integer> myStack = new Mystack<Integer>(); 这是使用泛型类同时指定具体类型为Integer
MyStack stack = new MyStack(); 这是使用泛型类但是没有指定具体类型,像这样不指定具体类型的泛型类称为原始类型,等价于 MyStack<Object> myStack = new MyStack<Object>();
四、泛型的运作原理
泛型只能存在于编译时,它存在的意义只是让编译器确认是否可以安全使用,一旦确定,编译器就会把它转为原始类型。举例如下:
1、泛型类型不受限
ArrayList<String> list = new ArrayList<String>();
list.add("hello");
当编译器确认字符串hello是符合要求的String类型,就会把这段代码变为:
ArrayList list = new ArrayList();
list.add("hello");
2、泛型类型受限
public static <E extends Number> E max(E o1, E o2)
E是Number的子类的泛型类型
编译器编译后,是
public static Number max(Number o1, Number o2)
3、总结:
a、当泛型类型不受限时,编译器编译时,会用Object类型来具体泛型类型
b、当泛型类型受限时,那么编译器会用一个受限的类型来替换它
c、不管实际的类型是什么,泛型类都是被所有的实例共享的。例如,ArrayList<Integer> list1 和 ArrayList<String> list2 的泛型类都是ArrayList,因此list1 instanceof ArrayList 和list2 instanceof ArrayList都是true;
d、泛型类型只存在于编译时,不存在运行时。
e、不能使用 new E();即 E object = new E();是错误的
f、不能使用 new E[]来申明数组
g、静态方法的参数不能有泛型,静态数据域也不能有泛型
h、异常类不能是泛型,即泛型类不能继承java.lang.Throwable