Why
JDK1.5增加泛型支持在很大程度上是为了让集合能记住其元素的数据类型。在没有泛型前,一旦把一个对象“丢进”java集合中,集合就会忘记对象的类型,把所有对象当成Object类型处理。当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换不仅使代码臃肿,而且容易引起ClassCastException异常。
增加了泛型支持后的集合的优点:
- 可以记住集合中元素的类型,并可以在编译时检查集合中元素的类型,如果试图向集合中添加不满足类型要求的对象,编译器就会提示错误;
- 可以让代码更加简洁,程序更加健壮;
除此之外,Java泛型还增强了枚举类、反射等方面的功能。
总结起来就是在编译时会检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码重用率。
import java.util.List;
import java.util.ArrayList;
public class Solution {
public static void main(String[] args) {
//创建一个只想保存字符串的List集合
List strList = new ArrayList();
strList.add("疯狂java讲义");
strList.add("疯狂Android讲义");
//“不小心”把一个Integer对象“丢进”了集合
strList.add(5); //①
strList.forEach(str->System.out.println(((String)str).length())); //②
}
}
What、When、Where
所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
Java引入泛型的好处是安全简单。
规则限制
- 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型;
- 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同的版本的泛型类实例是不兼容的;
- 泛型的类型参数可以有多个;
- 泛型的参数类型可以使用extends语句,例如< T extends superclass>。习惯上称为“有界类型”;
- 泛型的参数类型还可以是通配符类型。例如Class< ? > classType = Class.forName(“java.lang.String”).
How
1、定义泛型接口、类
public class Apple<T> {
private T info;
public Apple(){}
public Apple(T info) {
this.info = info;
}
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return this.info;
}
public static void main(String[] args) {
Apple<String> a1 = new Apple<>("苹果");
System.out.println(a1.getInfo());
Apple<Double> a2 = new Apple<>(5.67);
System.out.println(a2.getInfo());
}
}
注意:当创建泛型类声明自定义类,为该类定义构造函数时,构造函数名还是原来的类名,不要增加泛型声明。例如,为Apple< T>类定义构造函数,其构造函数名依然是Apple,而不是Apple< T>!调用该构造函数时却可以使用Apple< T>的形式,当然应该为T形参传入实际的类型参数。Java7提供了菱形语法,允许省略< >中的类型参数。
事实上,并不存在泛型类,例如,
import java.util.List;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass()==l2.getClass());
}
}
结果为true,因为不管泛型的实际类型参数是什么,他们在运行时总有同样的类(class)处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或者静态变量声明和初始化中不允许使用类型形参。比如下面代码:
public class R<T> {
//下面代码错误,不能在静态变量声明中使用类型参数
static T info;
T age;
public void foo(T msg){}
//下面代码错误,不能在静态方法声明中使用类型参数
public static void bar(T msg){}
}
由于系统并不会真正生成泛型类,所以intanceof运算符后不能使用泛型类。例如,下面代码是错误的:
java.util.Collection<String> cs = new java.util.ArrayList<>();
//下面代码编译时引起错误
if(cs intanceof java.util.ArrayList<String>(){}
2、设定类型通配符的上限
//定义一个抽象类Shape
public abstract class Shape {
public abstract void draw(Canvas c);
}
//定义一个Shape的子类Circle
public class Circle extends Shape {
public void draw(Canvas c) {
System.out.println("在画布"+c+"上画一个圆");
}
}
//定义一个Shape的子类Rectangle
public class Rectangle extends Shape {
public void draw(Canvas c) {
System.out.println("在画布"+c+"上画一个矩形");
}
}
public class Canvas {
public void drawAll(List<? extends Shape> shapes) {
for(Shape s : shapes) {
s.draw(this);
}
}
}
List< ? extends Shape > 是受限制通配符的例子,此处的问号(?)代表一个未知的类型,但是此处的这个未知类型一定是Shape的子类型,因此可以把Shape称为这个通配符的上限。
3、定义泛型方法
泛型方法的语法格式如下:
修饰符 <T,S> 返回值类型 方法名(形参列表){
//方法体
}