泛型
一、什么是泛型
泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
二、为什么需要泛型
Java中引入泛型最主要的目的是将类型检查工作提前到编译时期,将类型强转(cast)工作交给编译器,从而让你在编译时期就获得类型转换异常以及去掉源码中的类型强转代码。在没有泛型之前类型的检查和类型的强转都必须由我们程序员自己负责,一旦出现错误,直接导致项目崩溃,这是很严重的问题。在jdk1.5后,泛型应运而生。让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。编译器在编译时期即可完成类型检查工作,并提出错误,这样就不会出现或是少数会出现类型问题导致项目崩溃。
三、泛型类
泛型作用的对象
泛型有三种使用方式,分别为:泛型类、泛型接口和泛型方法。
我们先介绍泛型类:在类的声明时指定参数,即构成了泛型类,例如下面代码中就指定T为类型参数,那么在这个类里面就可以使用这个类型了。
我们声明一个学生类,用来演示我们的泛型的作用:
//声明Student类时,同时约定数据类型T
public class Student<T>{
//声明T类型的变量name
public T name;
//声明T类型的形参name为T类型的成员变量赋值
public Student(T name) {
this.name=name;
}
//声明T类型返回值的方法say
public T say() {
return name;
}
}
在使用类时就可以传入相应的类型,构建不同类型的实例,传入String,Integer,Boolean3个类型:
public static void main(String[] args) {
//泛型中只能使用引用数据类型
Student<String> s1=new Student<String>("张三");
Student<Integer> s2=new Student<Integer>(100);
Student<Boolean> s3=new Student<Boolean>(true);
System.out.println("Strng类型"+s1.name);
System.out.println("Integer类型"+s2.name);
System.out.println("Boolean类型"+s3.name);
}
注意:泛型类型中只能使用引用数据类型,需要使用相应基本数据类型的包装类
四、泛型接口
泛型接口的定义,与类相似,类对象在实例化时确定泛型的具体类型,接口是在实现时确定泛型的具体类型。
public interface IStudentService<T> {
public T say();
}
public class StudentService implements IStudentService<String>{
@Override
public String say() {
// TODO Auto-generated method stub
return null;
}
}
public class StudentService_2 implements IStudentService<Integer>{
@Override
public Integer say() {
// TODO Auto-generated method stub
return null;
}
}
五、泛型方法
定义泛型方法时,必须在返回值前边加一个< T >,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。
public class Student<T> {
public String name;
public int age;
public <E> E m(E e) {
return e;
}
public <E> void m1(E e) {
System.out.println(e);
}
}
泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。
六、泛型通配符
泛型通配符是指使用"?"代表任意类型。T、E、K、V、?这些究竟是何方神圣?
其实这些全都属于java泛型的通配符,刚开始如果看到这么多通配符,可能觉得就够自己喝一壶的了,实际上这几个其实没啥区别,只不过是一个约定好的字母标识,不要害怕。其实也可以 使用大写字母A,B,C,D…X,Y,Z定义的,就都是泛型,把T换成A也一样,这里T只是名字上的意义而已。
- E - Element(在集合中使用,因为集合中存放的是元素),E是对各方法中的泛型类型进行限制,以保证同一个对象调用不同的方法时,操作的类型必定是相同的。E可以用其它任意字母代替
- T - Type(Java 类),T代表在调用时的指定类型。会进行类型推断
- K - Key(键)
- V - Value(值)
- N - Number(数值类型)
- ?- 表示不确定的java类型,是类型通配符,代表所有类型。?不会进行类型推断
public void m(List<?> list) {
return;
}
但是说实话,单独一个"?"意义不大,因为大家可以看到,从集合中获取到的对象的类型是Object 类型的,也就只有那几个默认方法可调用,几乎没什么用。如果你想要使用传入的类型那就需要强制类型转换,这是我们接受不了的,不然为什么使用泛型。其真正强大之处是可以通过设置其上下限达到类型的灵活使用
- 通配符上界
通配符上界使用<? extends T>的格式,意思是需要一个T类型或者T类型的子类,一般T类型都是一个具体的类型。
public void m(List<? extends T> list) {
return;
}
- 通配符下界
通配符下界使用<? super T>的格式,意思是需要一个T类型或者T类型的父类,一般T类型都是一个具体的类型。
public void m(List<? super T> list) {
return;
}
至于什么时候使用通配符上界,什么时候使用下界,在《Effective Java》中有很好的指导意见:即producer-extends,consumer-super. 换句话说,如果参数化类型表示一个生产者,就使用 <? extends T>;如果参数化类型表示一个消费者,就使用<? super T>。