1.1 泛型的引入
例如:生产瓶子的厂家,一开始并不知道我们将来会用瓶子装什么,我们什么都可以装,但是有的时候,我们在使用时,想要限定某个瓶子只能用来装什么,这样我们不会装错,而用的时候也可以放心的使用,无需再三思量。我们生活中是在使用这个瓶子时在瓶子上“贴标签”,这样就轻松解决了问题。
还有,在Java中我们在声明方法时,当在完成方法功能时如果有未知的数据需要参与,这些未知的数据需要在调用方法时才能确定,那么我们把这样的数据通过形参表示。那么在方法体中,用这个形参名来代表那个未知的数据,而调用者在调用时,对应的传入值就可以了。
受以上两点启发,JDK1.5设计了泛型的概念。泛型即为“类型参数”,这个类型参数在声明它的类、接口或方法中,代表未知的通用的类型。例如:
java.lang.Comparable接口和java.util.Comparator接口,是用于对象比较大小的规范接口,这两个接口只是限定了当一个对象大于另一个对象时返回正整数,小于返回负整数,等于返回0。但是并不确定是什么类型的对象比较大小,之前的时候只能用Object类型表示,使用时既麻烦又不安全,因此JDK1.5就给它们增加了泛型。
public interface Comparable<T>{
int compareTo(T o) ;
}
public interface Comparator<T>{
int compare(T o1, T o2) ;
}
其中<T>就是类型参数,即泛型。
1.2 泛型的好处
那么我们在使用如上面这样的接口时,如果没有泛型或不指定泛型,很麻烦,而且有安全隐患。如果有了泛型并使用泛型,那么既能保证安全,又能简化代码。
JavaBean:圆类型
class Circle{
private double radius;
public Circle(double radius) {
super();
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
比较器
import java.util.Comparator;
public class CircleComparator implements Comparator{
测试类
public class TestGeneric {
public static void main(String[] args) {
CircleComparator com = new CircleComparator();
System.out.println(com.compare(new Circle(1), new Circle(2)));
System.out.println(com.compare("圆1", "圆2"));//运行时异常:ClassCastException
}
}
使用泛型:
比较器:
class CircleComparator implements Comparator<Circle>{
测试类
import java.util.Comparator;
public class TestGeneric {
public static void main(String[] args) {
CircleComparator com = new CircleComparator();
System.out.println(com.compare(new Circle(1), new Circle(2)));
// System.out.println(com.compare("圆1", "圆2"));//编译错误,因为"圆1", "圆2"不是Circle类型,编译器提前报错,而不是冒着风险在运行时再报错
}
}
其中:<T>是类型变量(Type Variables),Comparator<T>这种就称为参数化类型(Parameterized Types),Comparator<Circle>中的<Circle>是参数化类型的类型参数<Type Arguments of Parameterized Types>。
类比方法的参数,我们可以把<T>,称为类型形参,将<Circle>称为类型实参,有助于我们理解泛型。
2 参数类型:泛型类与泛型接口
当我们在声明类或接口时,类或接口中定义某个成员时,该成员有些类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型。
2.1 声明泛型类与泛型接口
语法格式:
【修饰符】 class 类名<类型变量列表>{
}
【修饰符】 interface 接口名<类型变量列表>{
}
注意:
-
<类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:<T>、<K,V>等。
-
<类型变量列表>中的类型变量不能用于静态成员上。
示例代码:
例如:我们要声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定,为什么呢,因为,语文老师希望成绩是“优秀”、“良好”、“及格”、“不及格”,数学老师希望成绩是89.5, 65.0,英语老师希望成绩是'A','B','C','D','E'。那么我们在设计这个学生类时,就可以使用泛型。
public class Student<T>{
private String name;
private T score;
public Student() {
super();
}
public Student(String name, T score) {
super();
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getScore() {
return score;
}
public void setScore(T score) {
this.score = score;
}
2.2 使用泛型类与泛型接口
在使用这种参数化的类与接口时,我们需要指定泛型变量的实际类型参数:
(1)实际类型参数必须是引用数据类型,不能是基本数据类型
(2)在创建类的对象时指定类型变量对应的实际类型参数
public class TestGeneric{
public static void main(String[] args) {
//语文老师使用时:
Student<String> stu1 = new Student<String>("张三", "良好");
//数学老师使用时:
//Student<double> stu2 = new Student<double>("张三", 90.5);//错误,必须是引用数据类型
Student<Double> stu2 = new Student<Double>("张三", 90.5);
//英语老师使用时:
Student<Character> stu3 = new Student<Character>("张三", 'C');
//错误的指定
//Student<Object> stu = new Student<String>();//错误的
}
}
JDK1.7支持简写形式:Student<String> stu1 = new Student<>("张三", "良好");
指定泛型实参时,必须左右两边一致,不存在多态现象
(3)在继承泛型类或实现泛型接口时,指定类型变量对应的实际类型参数
class ChineseStudent extends Student<String>{
public ChineseStudent() {
super();
}
public ChineseStudent(String name, String score) {
super(name, score);
}
}
public class TestGeneric{
public static void main(String[] args) {
//语文老师使用时:
ChineseStudent stu = new ChineseStudent("张三", "良好");
}
}
class Circle implements Comparable<Circle>{
private double radius;
public Circle(double radius) {
super();
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
2.3 类型变量的上限
当在声明类型变量时,如果不希望这个类型变量代表任意引用数据类型,而是某个系列的引用数据类型,那么可以设定类型变量的上限。
语法格式:
<类型变量 extends 上限>
如果有多个上限
<类型变量 extends 上限1 & 上限2>
如果多个上限中有类有接口,那么只能有一个类,而且必须写在最左边。接口的话,可以多个。
如果在声明<类型变量>时没有指定任何上限,默认上限是java.lang.Object。
例如:我们要声明一个两个数求和的工具类,要求两个加数必须是Number数字类型,并且实现Comparable接口。
class SumTools<T extends Number & Comparable<T>>{
private T a;
private T b;
public SumTools(T a, T b) {
super();
this.a = a;
this.b = b;
}
测试类
public static void main(String[] args) {
SumTools<Integer> s = new SumTools<Integer>(1,2);
Integer sum = s.getSum();
System.out.println(sum);
// SumTools<String> s = new SumTools<String>("1","2");//错误,因为String类型不是extends Number
}
2.4 泛型擦除
当使用参数化类型的类或接口时,如果没有指定泛型,那么会怎么样呢?
会发生泛型擦除,自动按照最左边的第一个上限处理。如果没有指定上限,上限即为Object。
public static void main(String[] args) {
SumTools s = new SumTools(1,2);
Number sum = s.getSum();
System.out.println(sum);
}
import java.util.Comparator;
public class CircleComparator implements Comparator{
2.5 练习
练习1
1、声明一个坐标类Coordinate<T>,它有两个属性:x,y,都为T类型 2、在测试类中,创建两个不同的坐标类对象, 分别指定T类型为String和Double,并为x,y赋值,打印对象
public class TestExer1 {
public static void main(String[] args) {
Coordinate<String> c1 = new Coordinate<>("北纬38.6", "东经36.8");
System.out.println(c1);
// Coordinate<Double> c2 = new Coordinate<>(38.6, 38);//自动装箱与拆箱只能与对应的类型 38是int,自动装为Integer
Coordinate<Double> c2 = new Coordinate<>(38.6, 36.8);
System.out.println(c2);
}
}
class Coordinate<T>{
private T x;
private T y;
public Coordinate(T x, T y) {
super();
this.x = x;
this.y = y;
}
public Coordinate() {
super();
}
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;
}
练习2
1、声明一个Person类,包含姓名和伴侣属性,其中姓名是String类型,而伴侣的类型不确定, 因为伴侣可以是Person,可以是Animal(例如:金刚),可以是Ghost鬼(例如:倩女幽魂), 可以是Demon妖(例如:白娘子),可以是Robot机器人(例如:剪刀手爱德华)。。。
2、在测试类中,创建Person对象,并为它指定伴侣,打印显示信息
public class TestExer3 {