为什么需要泛型?
public static void main(String[] args) {ArrayList list = new ArrayList();// 由于集合没有做任何限定,任何类型都可以给其中存放list.add("abc");list.add("def");list.add(5);Iterator it = list.iterator();while (it.hasNext()) {// 需要打印每个字符串的长度,就要把迭代出来的对象转成String类型String str = (String) it.next();System.out.println(str.length());}
}
思考:上面这段代码有什么问题吗?如果有,那么出现问题的原因是什么?
编译不报错,但运行发生了报错。
因为没使用泛型,导致我们想使用的时候,一旦强转就会出现类型转换异常,而在我们工作中,其实很多时候,我们都只需要一个集合容器里面放一个类型,如果有多个类型,我们就分多个容器来存放,那么进行约束存放类型的功能,就叫泛型(Generic)语法,让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。
泛型的使用
打开ArrayList的源码, 会发现在定义类的时候, 在类名后面加了个,然后这个E, 在多个方法参数里面都进行了使用, 这个E到底是什么?
class ArrayList<E> {public boolean add(E e) { }public E get(int index) { }...
}class ArrayList<String> {public boolean add(String e) { }public String get(int index) { }...
}class ArrayList<Integer> {public boolean add(Integer e) { }public Integer get(int index) { }...
}
我 们 发 现 , 只 要 我 们 在 newArrayList<>()的时候,** 在<>括号里面放什么类型, 那么这个E就是什么类型**,比如我们构建的集合是String类型, 那么 E 就 是 String , 我 们 构 建 的 集 合 是Integer类型, 那么E就是Integer;
当然,这个E是可换成自定义的名字,比如说MyType。
使用泛型定义类
新建一个类,然后在<>里面写一个不存在的类型,这个类型甚至不需要我们创建类,然后为了展示,书写了该属性对应的get和set方法。
public class MyGenericClass<MyType> {
// private E e;
//
// public E getE() {
// return e;
// }
//
// public void setE(E e) {
// this.e = e;
// }private MyType myType;public MyType getMyType() {return myType;}public void setMyType(MyType myType) {this.myType = myType;}}
在测试类中,我们创建了2次之前所构建的类对象,两次所传递的泛型类型是不一样的,但是它都能进行接收,并且接收完成之后,也可以通过调用set方法来进行赋值,也能通过调用get方法来进行输出该数据。
public class TestMyGenericClass {public static void main(String[] args) {//模拟构建ArrayList对象的形式来构建一个MyGenericClass类对象MyGenericClass<String> my = new MyGenericClass<String>();my.setMyType("张三");System.out.println(my.getMyType());//再来创建一次其他类型的泛型MyGenericClass<Integer> my1 = new MyGenericClass<Integer>();my1.setMyType(1001);System.out.println(my1.getMyType());}
}
使用泛型定义接口
public interface MyGenericInterface<E> {void add(E e);E getE();
}
定义了一个带泛型的接口之后,这个接口被使用的时候,可以直接在写implements的时候,就把泛型给写死了,代表定义类的时候就确定好了泛型的类型了。
public class MyImp2 implements MyGenericInterface<String>{@Overridepublic void add(String e) {// TODO Auto-generated method stub}@Overridepublic String getE() {// TODO Auto-generated method stubreturn null;}}
定义了一个带泛型的接口之后,这个接口被使用的时候,也可以在该类上仍然不把泛型的类型给确定,让它在构建该类对象的时候去确定该类型。
public class MyImp1<E> implements MyGenericInterface<E>{@Overridepublic void add(E e) {// TODO Auto-generated method stub}@Overridepublic E getE() {// TODO Auto-generated method stubreturn null;}
}
MyImp2类在使用时无需给出泛型,但MyImp1类在使用时必须指明泛型。
public class TestGenericInterface {public static void main(String[] args) {MyImp1<Integer> my1 = new MyImp1<Integer>();//随着你指定的泛型类型不一样,那么它里面的两个方法参数和返回类型也发生了变化my1.add(1001);//这个类型的创建,不需要你使用泛型了,因为我们在该类构建的时候,就已经确定了父接口的//泛型的类型,所以它的方法都跟着变了,以后就不能发生改变了!MyImp2 my2 = new MyImp2();my2.add("你好");}
}
使用泛型定义方法
在方法的返回值前面如果加上泛型的话,那么参数里面的MyType将会报错,所以,通过泛型在方法上的运用,可以让我们在书写方法重载的时候,变的更加的方便
public class MyGenericMethod {//在修饰词的位置写一个泛型的尖括号,里面随便写个类型//这样的效果同等于在类或者是接口名后加<>,只不过这样//的范围就只局限于当前的方法public <E> void show1(E e) {System.out.println(e.getClass());}public <E> E show2(E e) {return e;}public static void main(String[] args) {MyGenericMethod mm = new MyGenericMethod();mm.show1(123);mm.show1("abc");mm.show1(3.14);System.out.println(mm.show2(123));}
}
通配符
然而,如果设 Foo 是 Bar 的一个子类型(子类或者子接口),而 G 是具有泛型声明的类或接口,G并不是 G 的子类型!这一点非常值得注意,因为它与大部分人的习惯认为是不同的。
造成这个原因是因为泛型不存在继承关系!
类型通配符的上下限
如果你想限制一个方法,限制其参数的泛型类型时,你有两种方式:
-
使用<? extends 类>来约束泛型必须要是该类或者是该类的子类;
-
使用<? super 类>来约束泛型必须要是该类或者是该类的父类;
为什么指定通配符上限的集合不能添加元素?
// 定义一个抽象类 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 void addRectangle(List<? extends Shape> shapes) {// 下面代码引起编译错误shapes.add(0, new Rectangle());
}
《java疯狂讲义》给出的答案是:
与使用普通通配符相似的是,shapes.add() 的第二个参数类型是? extends Shape,它表示 Shape 未知的子类,程序无法确定这个类型是什么,所以无法将任何对象添加到这种集合中。
简而言之,这种指定通配符上限的集合,只能从集合中取元素(取出的元素总是上限的类型或其子类),不能向集合中添加元素(因为编译器没法确定集合元素实际是哪种子类型)。
我的更通俗理解:List<? extends Shape> shapes传进来的是一个类别Shape本身或者子类(这里我们假设有子类A和B,且A与B之间不存在继承关系)的集合,如果shapes添加子类A的对象,那万一shapes运行时传进来的是子类B的集合,那么"shapes.add(0, new Rectangle());"这行代码是存在问题的;同理shapes添加Shape的对象也是如此。
因此通配符上限的集合,即<? extends Shape>是无法添加的元素,而通配符下限的集合,即<? super Shape>确可以。因为<? super Shape>传进来的都是Shape的父类,而我们添加的元素只要是Shape类别或者Shape的子类就是合法的。