目录
一、引言
二、泛型上界
1、什么是泛型的上界
2、泛型上界的语法
三、泛型方法
1、泛型方法的语法
2、泛型方法的类型推导
三、编程分析
1、MyCompare泛型类
2、泛型方法实现
四、总结
一、引言
初学Java时,同学们基本都会遇到这样一个基础编程题:
实验题目:获取三个整数的最大值。
它的答案非常简单,只需要比较三个int类型的变量即可:
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = 30;
int temp = (a > b) ? a : b;
int max = (temp > c) ? temp : c;
System.out.println("max = " + max);
}
}
那么问题来了:如果想要比较的对象不仅是整数,而是任意的数值类型(甚至任意的自定义类型),那该怎么办呢?
诚然,我们可以通过方法的重载,编写多个适用于不同数据类型的方法。在需要时,我们可以挨个进行调用。但这个未免还是有些吃力,毕竟光是数值类型就有7种,而这7种类型都是可以比较大小的。
因此,我们可以采用泛型类,将数据类型也作为参数传递,达到比较“任意的数值类型”的效果。
这个方法主要用到了泛型的上界这一知识点。对于泛型的基本语法,便不再赘述,需要的同学可以再自行搜索、熟悉。
二、泛型上界
1、什么是泛型的上界
为什么要引出泛型的上界?因为在实际编程中,在定义泛型类时,有时需要对传入的类型变量做一定的约束。比如上面这个题目,约束就是“数值类型”,也就是Number类型。Integer、Double等类型(注意:泛型类型是引用类型,要传基本数据类型只能传包装类)都可以进行大小比较,但此时String类型就不在数值类型的范围内。这就是约束,约束传入的数据类型是哪些,而不是随便哪个数据类型都可以传进去。
我们可以通过类型边界来约束。
2、泛型上界的语法
class 泛型类名称<类型形参 extends 类型边界> {
...
}
例如:
public class Test<E extends Number> {
...
}
<E extends Number>的含义就是,Test类只接受 Number 的子类(或Number类本身)作为 E 。如果没有指定E的上界(PS:泛型没有下届,泛型的边界就是指泛型是上界)。
因此:
Test<Integer> a; // 正确,因为 Integer 是 Number 的子类型
Test<String> b; // 编译错误,因为 String 不是 Number 的子类型
这样就达到了约束的效果。
但是这样还不够。如果要约束的上界不是一个类,而是一个接口,该如何呢?语法如下:
public class Test<E extends Comparable<E>> {
...
}
<E extends Comparable<E>>的含义是,传入的E必须实现了Comparable接口。
三、泛型方法
1、泛型方法的语法
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) {
...
}
例如:
public class Util {
//静态的泛型方法 需要在static后用<>声明泛型类型参数
public static <E> void swap(E[] array, int i, int j) {
E t = array[i];
array[i] = array[j];
array[j] = t;
}
}
此时,Util不是泛型类,但swap是泛型方法。该方法将在《Java泛型上界与泛型方法的应用 | 如何给数组排序》一文中详细介绍。此处我们只需要知道泛型方法的语法即可。
2、泛型方法的类型推导
类型推导是指,在调用泛型方法时,可以不写明方法的类型,而Java会根据传入参数的类型自动推导出泛型方法中的泛型是指那种类型。如我们既可以这样写:
Integer[] a = { ... };
swap(a, 0, 9);
String[] b = { ... };
swap(b, 0, 9);
//没有写明swap传入的类型是什么,而是Java自动推导
也可以这样写:
Integer[] a = { ... };
Util.<Integer>swap(a, 0, 9);
String[] b = { ... };
Util.<String>swap(b, 0, 9);
//写明了swap方法传入的类型
有了以上的知识,就可以进行编程了。
三、编程分析
1、MyCompare<T>泛型类
public class MyCompare<T extends Comparable<T>>{
/* public T getMax(T x, T y) {
return x > y ? x : y; //错误!
}*/
public T getMax(T x, T y) {
return x.compareTo(y) >= 0 ? x : y;
}
}
由于T本身代表的就是一个引用类型,因此,直接用大于小于号比较是不正确的。
不能写作:
调用compareTo()方法才是正确的比较方式。但compareTo()方法不是可以直接调用的,使用之前必须实现Comparable接口。因此,我们需要对传入的类型进行约束,令传入的类型一定得是实现了Comparable接口的,最后再调用类型的compareTo()方法进行比较。
因此,我们写作:
public class MyCompare<T extends Comparable<T>> {
//获取较大值的方法
...
}
含义为,MyCompare类只能传入实现过Comparable接口的类型T。
然后我们调用:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
double a = reader.nextDouble();
double b = reader.nextDouble();
double c = reader.nextDouble();
MyCompare<Double> m = new MyCompare<>();
double temp = m.getMax(a,b);
double max = m.getMax(temp,c);
System.out.println("max = " + max);
}
}
此时传入了Double类型(Double类型是实现了Comparable接口的)。
将Double改为Integer:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
int a = reader.nextInt();
int b = reader.nextInt();
int c = reader.nextInt();
MyCompare<Integer> m = new MyCompare<>();
int temp = m.getMax(a,b);
int max = m.getMax(temp,c);
System.out.println("max = " + max);
}
}
甚至可以传入String,因为String也实现了Comparable接口。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
String a = reader.next();
String b = reader.next();
String c = reader.next();
MyCompare<String> m = new MyCompare<>();
String temp = m.getMax(a,b);
String max = m.getMax(temp,c);
System.out.println("max = " + max);
}
}
也可以传入自定义的类型Student,需要手动给Student类实现Comparable接口。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
Student stu1 = new Student(10);
Student stu2 = new Student(20);
Student stu3 = new Student(30);
MyCompare<Student> m = new MyCompare<>();
Student temp = m.getMax(stu1,stu2);
Student max = m.getMax(temp,stu3);
System.out.println("max = " + max);
}
}
class Student implements Comparable<Student>{ //实现Comparable接口,接口也要传入泛型Student类
int age;
String name;
double grades;
public Student(int age) { //构造方法 由于只用到age,就只写了age
this.age = age;
}
@Override
public int compareTo(Student o) { //重写compareTo方法
return this.age-o.age;
}
@Override
public String toString() { //重写toString方法,便于打印
return "Student{" +
"age=" + age +
'}';
}
}
2、泛型方法实现
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
//创建待比较的对象
String a = new String("hello");
String b = new String("Hi");
String c = new String("nice");
//直接调用泛型方法
//类型推导
String temp = Util.getMax(a,b);
String max = Util.getMax(temp,c);
System.out.println("max = " + max);
}
}
//泛型方法
class Util {
public static <T extends Comparable> T getMax(T x,T y) {
return x.compareTo(y) > 0 ? x : y;
}
}
四、总结
-
泛型上界是对传入泛型类的类型变量做一定的约束,可以是约束类型变量必须是另一个类的子类或本身,也可以是约束类型变量必须实现了某一个接口。
-
泛型类型都是引用类型。要实现比较,传入的泛型类型必须是实现了Comparable接口的。语法为<T extends Comparable<T>>。通过调用引用变量自身的compareTo()方法,A.compareTo(B)这样来比较。
-
注意泛型的语法,包括泛型类实现接口、泛型方法的语法等。