目录
一般的类和方法都使用的是具体的类型。但是有了泛型这个语法后,泛型类和泛型方法就可以使用各种各样的类型。泛型从本质上来说就是对类型实现了参数化。
引入
class Array{
private Object[] arr = new Object[10];
public void setPos(int pos, Object val){
arr[pos] = val;
}
public Object getPos(int pos){
return arr[pos];
}
}
public class Test {
public static void main(String[] args) {
Array arr = new Array();
//Object类为所有类型的父类,这里发生了向上转型
arr.setPos(0, "hello");
arr.setPos(1, 2);
arr.setPos(3, 1.5);
//得到里面的数据时,每次必须强转才可以
String str = (String)arr.getPos(0);
System.out.println(str);
int a = (int)arr.getPos(1);
System.out.println(a);
}
}
这里的数组arr可以存各种数据,但是使用其数据时是不方便的。这就要用到泛型这一语法来改造上述代码了。泛型可以自动检查数据是否为自己想要的数据类型,使用时会自动强转。
泛型类
语法
class ClassName<T, T2, T3>{
//类中可以使用T, T2, T3 等类型参数
}
<T>代表占位符,表示当前的类为泛型类
一般的:
E 表示 ElementK 表示 KeyV 表示 ValueN 表示 NumberT 表示 TypeS, U, V 等等 - 第二、第三、第四个类型
class Array<T> {
//不能new泛型的数组
//private T[] arr = new T[10];
//这种写法可以达到目的,但也不是最优解
private T[] arr = (T[]) new Object[10];
//最好通过反射来创建
public void setPos(int pos, T val){
arr[pos] = val;
}
public T getPos(int pos){
return arr[pos];
}
}
public class Test {
public static void main(String[] args) {
//这里我想存一组整数
//<>中不能为基本数据类型 如int、float等
//只能为对应的包装类型或其他类
//Array<int> arr = new Array();
Array<Integer> arr = new Array();
//可以省略后面实参Integer,编译器会与前面的形参匹配出后面的实参
//Array<Integer> arr = new Array<Integer>();
//自动检查,非整数的类型不能存放
//arr.setPos(0, "hello");
//arr.setPos(3, 1.5);
arr.setPos(1, 2);
//不需要强转了
int a = arr.getPos(1);
System.out.println(a);
}
}
泛型方法
一般方法
class Fun<T> {
//这是一个一般方法,需要实例化出对象才可以使用
//在返回类型前加 <参数类型列表>
public <T> void Swap(T[] arr, int a, int b){
T t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
}
public class Test {
public static void main(String[] args) {
//实例化出f
Fun<Integer> f = new Fun<Integer>();
Integer[] arr = {4, 7};
//使用一般方法
f.Swap(arr, 0, 1);
}
}
静态方法
class Fun{
//这是一个静态方法,不需要实例话对象,直接调用类方法即可
//在返回类型前加 <参数类型列表> 只不过多了static
public static <T> void Swap(T[] arr, int a, int b){
T t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
}
public class Test {
public static void main(String[] args) {
Integer[] arr = {4, 7};
Fun.Swap(arr, 0, 1);
}
}
擦除机制
擦除机制是泛型当中发生在编译过程中的机制。它是把所有泛型的类型都替换成了Object(在没有设上界的时候)。
所以编译器生成的字节码文件中没有泛型的类型,只有Object类。
泛型的上界
class ClassName<T extends 父类/接口>{
}
1. 泛型的好处是用户可以自己随便使用类型,但是有了泛型的上界后,用户只能使用父类或者其子类。
2. 没有上界时,擦除成Object类,但是有了上界,就会擦除成父类
3. 如果extends接口后,必须实现该接口,就可以使用接口了。
比如,当我们extends Number这个父类后,我们只能用其本身或者其子类。
当上界是父类时:
class TestNumber<T extends Number>{
public void Swap(T[] arr, int a, int b){
T t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
}
public class Test {
public static void main(String[] args) {
//Integer是number的子类
Integer[] arr1 = {1, 2};
TestNumber<Integer> a1 = new TestNumber<>();
a1.Swap(arr1, 0, 1);
//Double是number的子类
Double[] arr2 = {3.0, 4.0};
TestNumber<Double> a2 = new TestNumber<>();
a2.Swap(arr2, 0, 1);
//String不是number的子类,无法使用
String[] arr3 = {"hello", "world"};
TestNumber<String> a3 = new TestNumber<String>();
a3.Swap(arr3, 0, 1);
}
}
当上界是接口时:
//此时上界为Comparable<T>这个接口
//因为目的要找最大值,此接口中有两数的比较方法
class Max<T extends Comparable<T>>{
public T findMax(T[] arr){
T max = arr[0];
for (int i = 1; i < arr.length; i++) {
if(max.compareTo(arr[i]) < 0){
max = arr[i];
}
}
return max;
}
}
public class Test {
public static void main(String[] args) {
Max<Integer> a = new Max<>();
Integer[] arr = {2, 6, 3, 8, 4};
Integer max = a.findMax(arr);
System.out.println(max);
}
}
通配符
通配符是用来解决泛型无法协变的问题。协变:若Student是Person的子类,则 类名<Student> 也应该是 类名<Person> 的子类,但是泛型不支持这样的语法。如下代码。
class TestDemo<T> {
private T number;
public T getNumber() {
return number;
}
public void setNumber(T number) {
this.number = number;
}
}
public class Test {
public static void print(TestDemo<String> a) {
System.out.println(a.getNumber());
}
public static void main(String[] args) {
TestDemo<String> s = new TestDemo<>();
s.setNumber("这是为了测试通配符");
print(s);
}
}
class TestDemo<T> {
private T number;
public T getNumber() {
return number;
}
public void setNumber(T number) {
this.number = number;
}
}
public class Test {
//这里print方法只能打印String类的,当变成Integer类时就报错了
public static void print(TestDemo<String> a) {
System.out.println(a.getNumber());
}
public static void main(String[] args) {
TestDemo<Integer> s = new TestDemo<>();
s.setNumber(233);
print(s);
}
}
对于print方法的解决方案就是使用通配符 ?
public static void print(TestDemo<?> a) {
System.out.println(a.getNumber());
}
这样不管是String还是Integer类,它都可以打印。
通配符的上界
<? extends 父类>
//这里可以传入父类的子类或父类本身
class TestDemo<T>{
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
public class Test {
//通配符的上界只能接受任意类型,不能修改
//因为修改要有明确的类型,如果不知道,那么就不能修改
//在编译的时候已经擦除成 TestDemo<Number> 了
public static void fun(TestDemo<? extends Number> x){
//可以接受,相当于发生向上转型
Number i = x.getT();
//x.setT(4);
}
public static void main(String[] args) {
TestDemo<Integer> a = new TestDemo<>();
a.setT(2);
fun(a);
}
}
通配符的下届
<? super 子类>
//这里可以传入子类的父类或子类本身
class TestDemo<T>{
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
public class Test {
public static void fun(TestDemo<? super Integer> x){
//通配符的下届可以修改,不能接收
//被擦除成 TestDemo<Integer>
x.setT(4);
//不能确定是哪个父类,所以无法接受
//Integer i = x.getT();
}
public static void main(String[] args) {
TestDemo<Number> a = new TestDemo<>();
a.setT(2);
fun(a);
}
}
有什么错误评论区指出。