1.为什么需要泛型?
public class GenericTest {
public static void main(String[] args) {
List arrayList = new ArrayList();
arrayList.add("zhang");
arrayList.add("genericTest");
arrayList.add(2017);
for (int i=0; i<arrayList.size(); i++) {
//1.java.lang.ClassCastException
//String message = (String) arrayList.get(i);
//System.out.println("message:"+message);
//2.OK
Object message = arrayList.get(i);
System.out.println(message.getClass()+" "+"message:"+message);
}
}
}
定义一个ArrayList类型的集合,先向其中加入两个字符串类型的值,随后加入一个Integer类型的集合,此处完全没问题,因为此时的arrayList默认的类型为Object类型。在之后的for循环中由于忘记了arrayList类中含有Integer类型的元素,将Integer类型强制转换为String类型,出现1处的错误。
在上面的编码过程中,主要存在两个问题:
(1)将一个对象放入到一个集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,由于该对象在编译类型为Object类型,但其运行时类型依然为其本身类型,所以在1处运行时出现 Java.lang.ClassCastException 异常,导致此类错误在编译过程中不易被发现。
(2)取出集合元素时,需要人为强制转化为具体的目标类型
2.什么是泛型?
泛型,即“参数化类型”,顾名思义,就是将原来的具体的类型参数化,就像方法中的变量参数,此时类型也定义成参数形式(“类型形参”),然后在使用时传入具体的类型(”类型实参”)。
将上面的例子采用泛型的写法。
public class GenericTest {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<String>();
arrayList.add("zhang");
arrayList.add("genericTest");
//arrayList.add(2017);1.提示编译错误
for (int i=0; i<arrayList.size(); i++) {
String message = arrayList.get(i);//2
System.out.println("message:"+message);
}
}
}
采用泛型写法后,在1处加入一个Integer类型的对象时会出现编译错误,通过List<String>限定arrayList集合中只能含有String类型的元素,因此在2处不需要强制类型转换。
3.泛型接口、泛型类和泛型方法
(1)泛型类定义格式:
访问权限 class 类名称<泛型类型标识1,泛型类型标识2,..泛型类型标识n>{
访问权限 泛型类型标识 变量名称;
访问权限 泛型类型标识 方法名称(){};
访问权限 返回值类型声明 方法名称(泛型类型标识 变量1,泛型类型标识 变量2,...){}
(2)泛型对象的定义格式:
类名称<具体类> 对象名称 = new 类名称<具体类>();
package generic;
class Point<T> {
private T var;
public void setVar(T var) {
this.var = var;
}
public T getVar() {
return this.var;
}
}
public class GenericClass {
public static void main(String[] args) {
Point<String> name = new Point<String>();
name.setVar("zhang");
System.out.println(name.getVar());
}
}
在泛型接口、泛型类和泛型方法的定义过程中,常见 T、E、K、V 形式的参数表示泛型形参,思考:对于不同传入的类型实参,生成的相应对象实例类型是不是一样呢?
package generic;
public class GenericType {
public static void main(String[] args) {
Point<String> stringPoint = new Point<String>();
Point<Integer> integerPoint = new Point<Integer>();
System.out.println(stringPoint.getClass());//class generic.Point
System.out.println(integerPoint.getClass());//class generic.Point
System.out.println(stringPoint.getClass() == integerPoint.getClass());//true
}
}
由此发现,在使用泛型时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,泛型类在内存中只有一个,即还是原来最基本的类型(本例中为Point)。泛型只作用于代码编译阶段,成功编译过后的class文件中是不包含任何泛型信息的,泛型信息不会进入到运行时阶段,泛型类型在逻辑上可以看作是多个不同的类型,实际上都是相同的基本类型。
4.类型通配符
接着上面内容,思考:Point<Number>和Point<Integer>实际上都是Point类型,那么在逻辑上,是否可以将二者看成具有父子关系的泛型类型呢?简单例子:
package generic;
public class GenericGeneral {
public static void main(String[] args) {
Point<Number> name = new Point<Number>();
Point<Integer> age = new Point<Integer>();
getVar(name);
//The method getVar(Point<Number>) in the type GenericGeneral is not applicable
//for the arguments (Point<Integer>)
//getVar(age);1
}
public static void getVar(Point<Number> data) {
System.out.println("data:"+data.getVar());
}
}
编译上面代码,在1处提示错误信息,因此在逻辑上Point<Number>不能视为Point<Integer>的父类。因此引入类型通配符的概念,逻辑上用来表示这种继承关系。看下面类型通配符使用实例:
package generic;
public class Generic_01 {
public static void main(String[] args) {
Point<String> name = new Point<String>();
Point<Integer> age = new Point<Integer>();
Point<Number> number = new Point<Number>();
getVar(name);
getVar(age);
getVar(number);
}
public static void getVar(Point<?> data) {
System.out.println(data.getVar());
}
}
类型通配符上限和类型通配符下限,比如在上面的例子上,定义一个同样的相似的getVar方法,但是实参类型进一步限制,只能是Number类型及其子类,具体实例如下:
package generic;
public class Generic_01 {
public static void main(String[] args) {
Point<String> name = new Point<String>();
Point<Integer> age = new Point<Integer>();
Point<Number> number = new Point<Number>();
getVar(name);
getVar(age);
getVar(number);
//getUpperNumberVar(name);1
getUpperNumberVar(number);2
getUpperNumberVar(age);3
}
public static void getVar(Point<?> data) {
System.out.println(data.getVar());
}
public static void getUpperNumberVar(Point<? extends Number> data) {
System.out.println(data.getVar());
}
}
在1处代码将会出现错误提示,2和3处调用正常。
类型通配符下限形式为Point<? super Numer>,其含义与类型通配符上限正好相反。