泛型
1 什么是泛型
泛型就是把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型。
2 泛型类
这么说可能比较抽象,接下来,我们通过一些例子逐步进行说明。在Java中,类、接口、方法都可以是泛型的,我们先来看泛型类。
public class Student {
T first;
T second;
T third;
public T getThird() {
return third;
}
public T getFirst(){
return first;
}
public T getSecond(){
return second;
}
public Student (T first, T second, T third){
this.first=first;
this.second=second;
this.third=third;
}
}
class Test {
public static void main(String[] args) {
Student student =new Student(97,96,100);
Integer chinaScore =student.getFirst();
Integer mathScore=student.getSecond();
Integer englishScore=student.getThird();
System.out.println("语文成绩为:"+chinaScore);
System.out.println("数学成绩为:"+mathScore);
System.out.println("英语成绩为:"+englishScore);
}
}
运行结果如下:
语文成绩为:97
数学成绩为:96
英语成绩为:100
在此案例中,Student就是泛型类,类名后面多了个< T>,Student类中还有3个成员变量,first,second,third类型都是T。Student< Integer >中的Intger就是实际传递的实际类型参数。Student类的代码和它处理的数据数据类型不是绑定的,具体类型可以变化。上面是Integer,也可以是String,比如:
Student student = new Student("Y0301001","李晓明","男");
JDK7以后能在new 后面省去类型参数,可以这样使用:
Student student = new Student<>("Y0301001","李晓明","男");
类型参数可以有多个,Student类中的first,second,third可以是不同的类型,多个类型之间以逗号分隔,再比如,我们可以这样改写Student类。
public class Student {
X first;
Y second;
Z third;
public Z getThird() {
return third;
}
public X getFirst(){
return first;
}
public Y getSecond(){
return second;
}
public Student (X first, Y second, Z third){
this.first=first;
this.second=second;
this.third=third;
}
}
class Test {
public static void main(String[] args) {
Student student =newStudent("李晓明",22,95.5);
String name =student.getFirst();
Integer age=student.getSecond();
Double score=student.getThird();
System.out.println("姓名为:"+name);
System.out.println("年龄为:"+age);
System.out.println("成绩为:"+score);
}
}
运行结果如下:
姓名为:李晓明
年龄为:22
成绩为:95.5
3 泛型的优点
Java泛型是通过擦除实现的,在程序运行过程中,不知道泛型的实际类型参数,比如Student,运行中只知道Student,而不知道Integer。正因为如此,泛型有如下的好处:
代码的安全性
如果,在编写代码时不用泛型,而只是用Object类作为参数
Student student =new (22,95.5,"李晓明");
String name =student.getFirst();
Integer age=student.getSecond();
Double score=student.getThird();
上述代码在编译时是不会报错的,但在运行时,由于参数类型弄错了,会抛出ClassCastException(类型转换异常)。如果使用了泛型,在编译时就会有提示错误,这样就保证了代码的安全性。
4 泛型方法和泛型接口
泛型方法
除了泛型类,还有泛型方法,而且,一个方法是不是泛型的,与它所在的类是不是泛型没有什么关系。比如下面的例子。
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
}
class MyCollection{
public static int size(T[] arr) {
int size=0;
if(null==arr||0==arr.length){
return 0;
}
for (T t : arr) {
if(null!=t)size++;
}
return size;
}
}
class Test {
public static void main(String[] args) {
Student[] arr =new Student[20];
Student student1=new Student("李晓明");
Student student2=new Student("马笑天");
Student student3=new Student("许丽丽");
arr[0]=student1;
arr[1]=student2;
arr[2]=student3;
int size=Mycollection.size(arr);
System.out.println("学生数为:"+size);
}
}
运行结果如下:
学生数为:3
在类MyCollection中,size方法就是个泛型方法,参数中的数组arr可以是Student类,也可是String,Integer及其他类。
泛型接口
接口也可以是泛型的,T是类型参数,实现接口时,应该指定具体的类型。
public interface List extends Collection {
//主体代码
}
5 泛型的上下边界
对于泛型,Java支持一个类型参数以另一个类型参数作为上下边界。
1 泛型上限extends
a)< T extends E>用于定义类型参数,它声明了一个类型参数T,可放在泛型类定义中类名后面、泛型方法返回值前面。
b)< ?extends E>用于实例化类型参数,它用于实例化泛型变量中的类型参数,只是这个具体类型是未知的,只知道它是E或E的某个子类型。
如何理解泛型的上边界,先看下面的例子:
Container类
//容器类代码
public class Container {
private T obj;
public Container(){}
public Container(T obj){
this.obj = obj;
}
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
Person类
//人类代码
public class Person {
private T obj;
public Person(T obj){
this.obj = obj;
}
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
@Override
public String toString() {
return obj.toString();
}
}
//学生类代码
class Student extends Person{
public Student(T obj) {
super(obj);
}
}
//老师类代码
class Teacher extends Person{
public Teacher(T obj) {
super(obj);
}
}
Test类
public class Test {
public static void main(String[] strs) {
Person person = new Person("人类");
//一位学生
Student student = new Student("李晓明");
//一位老师
Teacher teacher = new Teacher("王老师");
//装人的容器
Container persons = new Container();
//把人装进容器
persons.setObj(person);
//装学生的容器
Container students = new Container();
//把学生装进容器
students.setObj(student);
//装老师的容器
Container teachers = new Container();
//把老师装进容器
teachers.setObj(teacher);
System.out.println(persons.getObj());
System.out.println(students.getObj());
System.out.println(teachers.getObj());
}
}
运行结果如下:
人类
李晓明
王老师
上面的代码运行没有问题,修改Test类代码如下:
public class Test {
public static void main(String[] strs) {
Person person = new Person("人类");
//一位学生
Student student = new Student("李晓明");
//一位老师
Teacher teacher = new Teacher("王老师");
//装人的容器
Container persons = new Container();
//把人装进容器
persons.setObj(person);
//装学生的容器
Container students = new Container();
//把学生装进容器
students.setObj(student);
//装老师的容器。
Container teachers = new Container();
//把老师装进容器
teachers.setObj(teacher);
Container extends Person> container= new Container<>();
//此处代码编译报错
container.setObj(person);
//此处代码编译报错
container.setObj(student);
//此处代码编译报错
container.setObj(teacher);
container.setObj(person);
System.out.println(persons.getObj());
container.setObj(student);
System.out.println(persons.getObj());
container.setObj(teacher);
System.out.println(persons.getObj());
}
}
2 泛型下限super
与形式<?extends E>正好相反,它的形式为<?super E>,称为超类型通配符,表示E的某个父类型或超类型。
看下面的代码:
public class Test {
public static void main(String[] strs) {
Person person = new Person("人类");
//一位学生
Student student = new Student("李晓明");
//一位老师
Teacher teacher = new Teacher("王老师");
//装人的容器
Container persons = new Container();
//把人装进容器
persons.setObj(person);
//装学生的。
Container students = new Container();
//把学生装进容器
students.setObj(student);
//装老师的容器
Container teachers = new Container();
//把老师装进容器
teachers.setObj(teacher);
Container super Person> container= new Container<>();
container.setObj(person);
System.out.println(container.getObj());
container.setObj(student);
System.out.println(container.getObj());
container.setObj(teacher);
System.out.println(container.getObj());
}
}
Test类代码如果用< ? super Person>替代< ? extends Person>,程序正常执行,运行结果如下:
人类
李晓明
王老师
继续修改Test类代码,代码如下:
public class Test {
public static void main(String[] strs) {
Person person = new Person("人类");
//一位学生
Student student = new Student("李晓明");
//一位老师
Teacher teacher = new Teacher("王老师");
//装人的容器
Container persons = new Container();
//把人装进容器
persons.setObj(person);
//装学生的容器
Container students = new Container();
//把学生装进容器
students.setObj(student);
//装老师的容器
Container teachers = new Container();
//把老师装进容器
teachers.setObj(teacher);
Container super Student> container= new Container<>();
//此处编译时报错,装学生的容器不能装人。
container.setObj(person);
System.out.println(container.getObj());
container.setObj(student);
System.out.println(container.getObj());
//此处编译时报错,装学生的容器不能装老师。
container.setObj(teacher);
System.out.println(container.getObj());
}
}
由此可见,上限 extends T>和下限 super T>用法比较很灵活,但是也有一定的限定:
1 上限< ? extends T>只能读,不能写(即只能get,不能set)。set方法中的值,无论是Person,还是Student或Teacher,编译器都会报错。为什么呢?问号就是表示类型安全无知,?extends Person表示是Person的某个子类型,但不知道具体子类型,如果允许写入,Java就无法确保类型安全性,所以干脆禁止。
2 下限<?super E>使得对象可以写入父类型的容器,使得父类型方法可以应用于子类对象,这样实际上是放松了类型限制。Person的父类一直到Object类型的对象都可以往里读,但是写的时候,就只能写专用的类,不然就只能当成Object对象使用编译出错。