一. 为什么要使用泛型?
public class Test{
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("name");
list.add("location");
list.add(18956);
for (int i = 0; i < list.size(); i++) {
String name = (String) list.get(i); // 1
System.out.println("name:" + name);
}
}
}
此时list默认的类型为Object类型,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。在之后的输出中,之前加入了一个Integer类型,所以出现//1中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。
当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。
那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。
二.什么是泛型?
泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("name");
list.add("location");
list.add(100); //1
for (int i = 0; i < list.size(); i++) {
String name = list.get(i); // 2
System.out.println("name:" + name);
}
}
}
采用泛型写法后,在//1处想加入一个Integer类型的对象时会出现编译错误,通过List,直接限定了list集合中只能含有String类型的元素,此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。
三.自定义泛型接口、泛型类和泛型方法
自定义泛型类:
格式:
class 类名<字母列表>{
修饰符 字母 属性;
修饰符 构造器(字母){ }
修饰符 返回类型 方法(字母)
}
//不能使用在静态属性、静态方法上
eg:
public class Test {
public static void main(String[] args) {
B<String> b=new B<String>("ray2580");
System.out.println("name:" + b.get());
}
}
class B<T>{
public T data;
public B() {
}
public B(T data) {
this.data = data;
}
public T get(){
return data;
}
}
在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参。
泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
自定义泛型方法:
定义使用:<字母>
修饰符<字母>返回类型 方法名(字母){ }
要定义泛型方法,将于泛型参数列表置返回值前。
public class Method {
public static void main(String[] args){
test("a"); //T----->String
}
//泛型方法
public static <T> void test(T a){
System.out.println(a);
}
}
四.类型通配符
有时我们需要一个在逻辑上可以用来表示同时是Box和Box的父类的一个引用类型,由此,类型通配符应运而生。
类型通配符一般是使用 ? 代替具体的类型实参。且Box<?>在逻辑上是Box、Box…等所有Box<具体类型实参>的父类。
public class Test {
public static void main(String[] args) {
Box<String> name = new Box<String>("ray2580");
Box<Integer> age = new Box<Integer>(71);
Box<Number> number = new Box<Number>(34);
getData(name);
getData(age);
getData(number);
}
public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
}
}
我们还需要类型通配符上限和类型通配符下限。具体有是怎么样的呢?
在上面的例子中,如果需要定义一个功能类似于get()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。
public class Test {
public static void main(String[] args) {
Box<String> name = new Box<String>("ray2580");
Box<Integer> age = new Box<Integer>(712);
Box<Number> number = new Box<Number>(314);
getData(name);
getData(age);
getData(number);
//getUpperNumberData(name); // 1
getUpperNumberData(age); // 2
getUpperNumberData(number); // 3
}
public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
}
public static void getUpperNumberData(Box<? extends Number> data){
System.out.println("data :" + data.getData());
}
}
此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。
类型通配符上限通过形如Box<? extends Number>形式定义,相对应的,类型通配符下限为Box<? super Number>形式,其含义与类型通配符上限正好相反。
泛型中的约束和局限性
1,不能实例化泛型类
2,静态变量或方法不能引用泛型类型变量,但是静态泛型方法是可以的
3,基本类型无法作为泛型类型
4,无法使用instanceof关键字或==判断泛型类的类型
5,泛型类的原生类型与所传递的泛型无关,无论传递什么类型,原生类是一样的
6,泛型数组可以声明但无法实例化
7,泛型类不能继承Exception或者Throwable
8,不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出
9,对于泛型参数是继承关系的泛型类之间是没有继承关系的
10,泛型类可以继承其它泛型类,例如: public class ArrayList extends AbstractList
11,泛型类的继承关系在使用中同样会受到泛型类型的影响