有时候听别人说泛型泛型的,心想啥是泛型啊,当时就不能愉快的交流了。所以今天赶紧看看啥是泛型,提高一下自己。
泛型的概念:所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态的指定(即传入实际的类型参数)。Java 5改写了集合框架中的全部类和接口,为这些类和接口增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
为什么引入泛型?使用泛型可以使错误在编译时被探测到,从而增加程序的健壮性。
平时使用泛型最多的地方就是集合了。
下面这一段代码
List strList = new ArrayList();
strList.add("hello world");
strList.add(3);
String s = (String) strList.get(1);//强转成String类型
System.out.println(s);
我们没有使用泛型,我们声明了一个strList,添加了 String 类型的数据,如果我们一不留神添加了一个Integer类型的数据,编译的时候是没有报错的,但是在运行的时候就出错了。
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
如果我们使用泛型的话,就可以在编译期间发现这种错误,从而增加程序的健壮性。
List<String> strList = new ArrayList();
strList.add("hello world");
//这句代码编译通不过。
strList.add(3);
这个声明的List只能保存String类型的数据。而且从集合中取数据的时候并不需要强制类型转换。
List<String> strList = new ArrayList();
strList.add("hello world");
strList.add("hello 2017");
String s = strList.get(1);//不用强制类型转换,可以愉快的取出String。
System.out.println(s);
定义泛型类、泛型接口
1 定义泛型类
//定义Apple类使用泛型声明
public class Apple<T> {
//使用T类型形参定义实例变量
private T info;
public Apple() {
}
//使用T类型形参定义构造函数
public Apple(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
}
当我们使用泛型类的时候,应该传入一个具体类型参数
public static void main(String args[]) {
//由于传给T形参的是String,所以构造函数参数只能是String
Apple<String> a1 = new Apple<>("hello apple");
//由于传给T形参的是Double类型,所以构造函数参数只能是Double或者double
Apple<Double> a2 = new Apple<>(3.4);
System.out.println(a1.getInfo());
System.out.println(a2.getInfo());
}
如果我们不指定一个具体的类型参数,像下面这样,编辑器会有一个未检查警告。
Apple a3 = new Apple("hello apple");
2定义泛型接口
//这里声明了两个类型参数 K ,V
public interface Pair<K, V> {
K getKey();
V getValue();
}
当我们定义好了泛型接口或者泛型类以后,就可以愉快的派生子类了。
//第一种写法
public class IPhoneApple<T> extends Apple<T> {
}
//第2种写法,Apple 中的 T 类型会被当做Object类型
public class IPhoneApple extends Apple {
}
//第三种写法
public class IPhoneApple extends Apple<String> {
}
上面3种写法都可以,但是我们继承泛型类或者实现泛型接口的目的何在?是为了得到一个类型明确的具体的类,所以应该选用第三种写法。下面定义一个具体的类继承Apple类并实现Pair接口。
public class IPhoneApple extends Apple<String> implements Pair<Integer, String> {
private String iPhoneInfo;
private int key;
public IPhoneApple(String info, String iPhoneInfo, int key) {
super(info);
this.iPhoneInfo = iPhoneInfo;
this.key = key;
}
//覆盖Apple中的方法
@Override
public String getInfo() {
return super.getInfo();
}
//实现接口中的方法
@Override
public Integer getKey() {
return key;
}
//实现接口中的方法
@Override
public String getValue() {
return iPhoneInfo;
}
public static void main(String args[]) {
IPhoneApple iPhoneApple = new IPhoneApple("手机信息:", "苹果几啊?", 7);
//输出结果 手机信息:苹果几啊?7
System.out.println(iPhoneApple.getInfo() + iPhoneApple.getValue() + iPhoneApple.getKey());
}
}
类型通配符
问题引出:假设现在要定义一个方法,方法需要一个List类型的参数,但是List 里面元素类型是不确定的,那这个方法改怎样定义呢?
首先想到的是Object搓起来,不管你是什么形,反正你是Object。
public static void test(List<Object> objectList) {
for (Object o : objectList) {
System.out.println(o);
}
}
然后调用一下这个方法。
public static void main(String args[]){
List<String> stringList=new ArrayList<>();
test(stringList);//编译不通过
}
但是意外出现了,竟然编译不通过。编辑器给的提示是:
test(java.lang.util.List<Object>) can not be applied to (java.lang.util.List<String>)
表明List<String>
并不能当做List<Object>
使用,说明List<String>
并不是List<Object>
的子类。
注意:如果Foo是Bar的一个子类(子类或者子接口)而G是具有泛型声明的类或者接口G<Foo>
并不是G<Bar>
的子类型!
可以使用通配符解决上面编译不通过的问题。把test方法的参数列表改为List<?> objectList
将一个?作为List 的类型形参,表示这个List中的元素可以匹配任何类型,无论是List<String>,还是List<Integer>都来者不拒。
如下所示。
public static void test(List<?> objectList) {
for (Object o : objectList) {
System.out.println(o);
}
}
调用没问题。
List<String> stringList = new ArrayList<>();
test(stringList);
List<Integer> integerList = new ArrayList<>();
test(integerList);
但是新的问题来了。List<?> objectList
这样的List是不能愉快的向里面添加元素的。
//添加啥都不好使。
objectList.add(new Object());//编译不通过
List的 add 方法如下所示,往里面添加的元素的类型是E类型的。E 就是我们声明一个具体的List传入的实际类型参数 比如List<Strinng>
,这个E 就是String。
boolean add(E e);
List<?> objectList
但是这样写的话,就无法确定要添加的元素是什么类型,所以就没法添加一个新的元素到List里面。
设定类型通配符的上限
前面我们的引出问题现在变了:假设现在要定义一个方法,方法需要一个List类型的参数但是List 里面元素类型是不确定的,但是有一点是可以肯定的,就是这些元素具有共同的父类,我们可以放三角形,圆形,长方形等等,它们有共同的父类Shape。
Shape 类。
public abstract class Shape {
public abstract void draw();
}
我们的圆形继承自Shape。
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("画一个圆形");
}
}
我们的长方形继承自Shape。
public class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("画一个长方形");
}
}
现在test方法就可以这样定义了。
public static void test(List<? extends Shape> shapeList) {
for (Shape shape : shapeList) {
shape.draw();
}
}
test 方法的形参 List<? extends Shape>
中的 ? 代表 List 中的元素类型是Shape类型或者是Shape的子类。但是到底是 Circle ,还是 Rectangle 还是Shape 是不确定的。
调用test方法。
//加入圆形
List<Circle> shapeCircles = new ArrayList<>();
shapeCircles.add(new Circle());
test(shapeCircles);
//加入长方形
List<Rectangle> shapeRectangle = new ArrayList<>();
shapeRectangle.add(new Rectangle());
test(shapeRectangle);
输出结果,也是可以的。
画一个圆形
画一个长方形
但是由于? 表示Shape 的未知类型,向List<? extends Shape> shapeList
中添加元素也是不行的。
shapeList.add(new Rectangle());//编译不通过
Java 泛型不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时,设定上限,用于限定传给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类。如下所示:
public class MyApple<T extends Number> {
T number;
public static void main(String[] args) {
MyApple<Integer> ai = new MyApple();
MyApple<Double> ad = new MyApple();
//下面代码编译异常,因为String 不是 Number 的子类。
MyApple<String> as=new Apple<>();//注释1
}
}
上面定义了MyApple泛型类,MyApple类的类型形参上限是Number类,这表明使用MyApple的时候为形参T传入的实际类型参数只能是Number或者Number的子类。上面代码注释1的地方传入的实际类型是String类型,既不是Number类型,又不是Number的子类,所以编译会出错。
**泛型方法:**就是在声明方法时定义一个或多个类型形参。格式如下。方法声明中声明得类型形参只能在方法内部使用。
修饰符<T,S,...>返回值类型 方法名(形参类表){
//方法体...
}
上面的尖括号里面表示一个或者多个类型参数,用逗号隔开。下面定义一个具体的泛型方法,用于把一个集合中的元素,拷贝到一个Collection里面。
public static <T> void fromArrayToCollection(Collection<T> a, Collection<T> c) {
for (T t : a) {
c.add(t);
}
}
调用妥妥的。
//String 类型
Collection<String> stringa = new ArrayList<>();
Collection<String> stringCollection = new ArrayList<>();
fromArrayToCollection(stringa, stringCollection);
我们发现我们在调用泛型方法的时候并没有传入实际的类型参数。是因为编译器根据我们传递的实际的参数类型,推断出了类型形参的实际类型。
但是你要是调用方法的时候传递的实际参数类型有问题,那编译器就懵了。如下所示
Collection<Integer> inta1= new ArrayList<>();
Collection<String> stringCollection1 = new ArrayList<>();
//调用下面方法就有问题了。编译器无法确定类型形参T应该是Integer还是String.
fromArrayToCollection(inta1, stringCollection1);
为了避免这种错误,可以修改fromArrayToCollection方法如下所示:
public static <T> void fromArrayToCollection(Collection<? extends T> a, Collection<T> c) {
for (T t : a) {
c.add(t);
}
}
设定类型通配符的下限
? super Type
,这个通配符表示它必须是Type本身,或者Type的父类,所以称之为下限。
public static <T> T copy(Collection<? super T> des, Collection<T> src) {
T last = null;
for (T t : src) {
last = t;
des.add(t);
}
return last;
}
通配符:List<?>
,可以添加数据,取出来的数据只能用Object类型来引用。
通配符上限:List<? extends Shape>
可以从集合里面取数据,不能添加。为什么不能添加呢?因为集合里放的是Shape类或者Shape的子类。比如Shape的子类有Square和Circle,如果我既往里面放Square又放Circle,那么不就出问题了。
通配符下限:List<? super Shape>
可以往集合里面添加数据,不能取数据(取出来的元素只能用Object来引用)。为什么不能取呢?因为? super Type
,这个通配符表示它必须是Type本身,或者Type的父类,取出来的是什么东西呢?不知道,只能是Object。
参考: