java基础
2 泛型
泛型是JDK1.5及以上才可以使用的特性/语法,它的本质是 类型参数化(Parameterized by types).
2.1 概述
在声明一个类、接口、方法的时候,需要涉及到到一个问题:要给属性确定一个类型,或者给方法的返回值确定一个类型,或者给方法的参数确定一个类型。
之前,定义类、接口、方法的时候,上面所描述的类型都是直接写死,不会变化的。
public class Point{
int x;
int y;
}
Point
类表示一个坐标点,它的的俩个属性x
坐标和y
坐标的类型是int
,这个int
是在定义Point
类的时候就已经写好了,并且不会自动改变。
现在,希望Point
类中的x
属性和y
属性的类型变的灵活一点,可以在将来使用的时候,临时通过传参的方式,来确定属性x和y的具体类型。
修改Point
类的代码,添加泛型参数T:
public class Point<T>{
T x;
T y;
}
注意,T是泛型参数,表示一种数据类型,具体是什么类型,需要将来使用Point的时候进行传参来确定
注意,如果将来Point在使用的时候,没有给泛型参数T传值,那么T默认就表示为Object类型
注意,T是泛型参数的名字,也就是相当于形参,名字随便起,但是一般用一个有意义的大写字母
Point<T>
类的使用:
public static void main(String[] args) {
//p1对象中x和y的属性类型都是Integer
Point<Integer> p1 = new Point<Integer>();
p1.x = 1;
p1.y = 2;
//p2对象中x和y的属性类型都是String
Point<String> p2 = new Point<String>();
p2.x = "1";
p2.y = "2";
//p3对象中x和y的属性类型都是Object
Point p3 = new Point();
p3.x = new Object();
p3.y = new Object();
}
可以看出,Point类的中x和y属性的类型,是可以根据我们在使用时所传的参数,进行临时变化的。
如果没有传这个泛型参数,那么这个参数T就默认是Object类型
注意,给泛型参照传的值,只能是引用类型,不能是基本类型:
Point<int>
编译报错
在类上面添加泛型参数后,在类中的任何可以使用类型的地方,都可以先使用这个泛型参数:
class Point<T>{
private T x;
private T y;
public Point(){
}
public Point(T x, T y) {
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
思考,为什么泛型也叫做 类型参数化(Parameterized by types)
2.2 集合的泛型
了解泛型的意思之后,接下来可以再看下之前学习过的集合中的泛型:
public interface Collection<E>{
boolean add(E e);
}
Collection是一个泛型接口,泛型参数是E,在接口中,add方法的参数类型也使用了E。
那么就说明在使用Collection接口的时候,如果没有给泛型参数传值,那么这个E就默认表示为Object,add方法就可以接收任意类型的对象。
public static void main(String[] args) {
//没有给泛型参数传值,那么泛型默认表示为Object类型
Collection c = new ArrayList();
c.add("hello1");
c.add("hello2");
c.add("hello3");
c.add(1);
for(Object obj:c){
String str = (String) obj;
System.out.println(str);
}
}
//运行结果:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
在这种情况下,集合中如果存储的数据类型不一致,就会在强制转换的时候出现类型转换异常
如果在使用Collection接口的时候,给泛型参数指定了具体类型,那么就会防止出现类型转换异常的情况,因为这时候集合中添加的数据已经有了一个规定的类型,其他类型是添加不进来的。
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("hello1");
c.add("hello2");
c.add("hello3");
//编译报错,add(E e) 已经变为 add(String e)
//int类型的数据1,是添加不到集合中去的
//c.add(1);
for(String str : c){
System.out.println(str);
}
}
可以看出,传入泛型参数后,add方法只能接收String类型的参数,其他类型的数据无法添加到集合中,同时在遍历集合的时候,也不需要我们做类型转换了,直接使用String类型变量接收就可以了,JVM会自动转换的
Collection<String> c = new ArrayList<String>();
可以简写成:
Collection<String> c = new ArrayList<>();
Map
接口使用泛型:
public interface Map<K,V>{
V put(K key, V value);
Set<Map.Entry<K, V>> entrySet();
}
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
//根据泛型类型的指定,put方法中的key只能是Integer类型,value只能是String类型
map.put(1,"hello1");
map.put(2,"hello2");
map.put(3,"hello3");
map.put(4,"hello4");
//根据上面列出的源码可知,当前指定Map的泛型类型为:Map<Integer,String> map
//entrySet方法返回的类型就应该是Set<Map.Entry<Integer, String>>
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
for(Map.Entry entry:entrySet){
System.out.println(entry.getKey()+" : "+entry.getValue());
}
}
2.3 泛型的种类
java中的泛型分三种使用情况:
- 泛型类
- 泛型接口
- 泛型方法
泛型类,如果泛型参数定义在类上面,那么这个类就是一个泛型类
在类中,就可以使用这个T来代表某一个类型,这个类型具体是什么将来使用的时候再传参确定
public class Point<T>{
...}
public static void main(String[] args){
Point<String> p = new Point<>();
}
泛型接口,如果泛型参数定义在接口上面,那么这个接口就是一个泛型接口
在接口中,就可以使用这个T来代表某一个类型,这个类型具体是什么将来使用的时候再传参确定
public interface Action<T>{
...}
public static void main(String[] args){
//创建匿名内部类
Action<String> a = new Action<>(){
//...
};
}
泛型方法,如果泛型参数定义在方法上面,那么这个方法就是一个泛型方法
public class Test{
public <T> T test(T t){
//..
}
}
public static void main(String[] args){
Test t = new Test();
String str = t.test("hello");
Integer i = t.test(1);
Double d = t.test(10.5D);
}
可以看出,调用test方法时候,我们传什么类型的参数,test方法的返回类型就是什么类型的。
这种效果,在不使用泛型的情况下,是不可能实现的。
2.4 泛型的类型
先看俩种错误的情况:
//编译通过
//父类型的引用,指向子类对象
Object o = new Integer(1);
//编译通过
//Object[]类型兼容所有的【引用】类型数组
//arr可以指向任意 引用类型 数组对象
Object[] arr = new Integer[1];
//编译失败
//注意,这个编译报错,类型不兼容
//int[] 是基本类型数组
Object[] arr = new int[1];
//编译失败
//错误信息:ArrayList<Integer>无法转为ArrayList<Object>
//在编译期间,ArrayList<Integer>和ArrayList<Object>是俩个不同的类型,并且没有子父类型的关系
ArrayList<Object> list = new ArrayList<Integer>();
注意,=号俩边的所指定的泛型类型,必须是要一样的
注意,这里说的泛型类型,指的是<>中所指定的类型
虽然Integer
是Object
的子类型,但是ArrayList<Integer>
和ArrayList<Object>
之间没有子父类型的关系,它们就是俩个不同的类型
所以,
Object o = new Integer(1);
编译通过
ArrayList<Object> list = new ArrayList<Integer>();
编译报错
也就是说,俩个类型,如果是当做泛型的指定类型的时候,就没有多态的特点了
2.5 通配符
观察下面代码:
public void test1(Collection<Integer> c){
}
public void test2(Collection<String> c){
}
public void test3(Collection<Object> c){
}
test1方法【只能】接收泛型是Integer类型的集合对象
test2方法【只能】接收泛型是String类型的集合对象
test3方法【只能】接收泛型是Object类型的集合对象原因:由于泛型的类型之间没有多态,所以=号俩边的泛型类型必须一致
在这种情况下,就可以使用通配符(?)来表示泛型的父类型:
public void test(Collection<?> c){
}
注意,这时候test方法中的参数类型,使用了泛型,并且使用问号来表示这个泛型的类型,这个问号就是通配符,可以匹配所有的泛型类型
test方法可以接收 泛型是任意类型的 Collection集合对象
public static void main(String