泛型概述
泛型概述
Author: Msuenb
Date: 2023-02-20
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类 型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException异常。同时,代码更加简洁、健壮。
泛型类与泛型接口
把类名或接口名后面带<K,V>
等的类或接口称为泛型类或泛型接口。
-
Collection 集合相关类型
ArrayList<Integer> list = new ArrayList<>(); // 指定泛型为 Integer for (int i = 0; i < 5; i++) { list.add(i); } Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }
-
Comparable 接口
class Person implements Comparable<Person>{ // 指定泛型为 Person String name; int age; @Override public int compareTo(Person o) { return this.age - o.age; } }
自定义泛型类与泛型接口
当在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么可以使用泛型。
语法格式:
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 父接口】 {}
【修饰符】 interface 接口名<类型变量列表> 【extends 父接口】 {}
-
自定义泛型类:
例如:声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定。因为,语文老师希望成绩是“优秀”、“良好”、“及格”、“不及格”;数学老师希望成绩是89.5, 65.0;英语老师希望成绩是'A','B','C','D','E'。那么在设计这个学生类时,就可以使用泛型。
class Student<T> { private String name; private T score; public Student() { } public Student(String name, T score) { 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; } @Override public String toString() { return "name='" + name + '\'' + ", score=" + score ; } }
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);//错误,90是int,不能自动装箱为Double Student<Double> stu2 = new Student<Double>("张三", 90.0);//可以 Student<Double> stu3 = new Student<Double>("张三", 90D); //英语老师使用时: Student<Character> stu4 = new Student<Character>("张三", 'C'); //错误的指定 //Student<Object> stu5 = new Student<String>();//错误的 }
-
自定义泛型接口
案例需求:
定义一个计算器接口Calculator<T,R>,T代表操作数的类型,R代表计算结果的类型
- 包含两个数计算的方法caculate,要求操作数的类型相同,但具体类型不确定,计算结果可能与操作数的类型不同。
- 包含求两个数最大值的方法max,要求操作数的类型相同,结果与操作数的类型也相同
编写实现类实现计算器接口
- 两个Integer整数相加及最大值,
- 相加结果用Long表示
- 返回两个整数中更大的那个,如果一样大,就返回第1个
- 两个String相加及最大值,
- 相加结果仍然是String,
- 返回两个字符串中更长的字符串,如果一样长,就返回第1个
interface Calculator<T, R> { R calculator(T t1, T t2); T max(T t1, T t2); }
@Test public void test01() { Calculator<Integer, Long> calculator = new Calculator<Integer, Long>() { @Override public Long calculator(Integer t1, Integer t2) { return (long) t1 + t2; } @Override public Integer max(Integer t1, Integer t2) { return t1 > t2 ? t1 : t2; } }; Long sum = calculator.calculator(Integer.MAX_VALUE, Integer.MAX_VALUE); System.out.println("sum = " + sum); Integer max = calculator.max(2, 9); System.out.println("max = " + max); } @Test public void test02() { Calculator<String, String> calculator = new Calculator<String, String>() { @Override public String calculator(String t1, String t2) { return t1 + t2; } @Override public String max(String t1, String t2) { return t1.length() >= t2.length() ? t1 : t2; } }; String sum = calculator.calculator("hello", "world"); System.out.println("sum = " + sum); String max = calculator.max("hello", "world"); System.out.println("max = " + max); }
泛型注意事项
-
<类型变量列表>:可以是一个或多个类型变量,<E, T, R>
-
<类型变量列表>:中的类型变量不能用于静态成员上
class AAA<T> { static T field; // 不能用于静态属性类型 static T method(T t) {} // 不能用于静态方法 返回值/参数类型 }
-
在同一个类或接口中同一个类型变量代表同一种数据类型
-
<实际类型参数>必须是引用类型,不能是基本数据类型
-
可以在创建泛型类型的对象时指定<类型变量>对应的<实际类型>
-
指定泛型实参时左右两边必须一致
ArrayList<Object> list = new ArrayList<Sting>(); // 错误 这并不是多态
-
JDK1.7支持自动类型推断的简写形式
ArrayList<String> list = new ArrayList<>();
泛型如果不指定,将被擦除,泛型对应的类型均按Object处理,但不等价于指定为Object。
ArrayList list = new ArrayList(); // 泛型擦除 编译时不会类型检查
-
-
不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
-
子类继承父类时,子接口继承泛型父接口、实现类实现泛型父类接口时
-
可以指定<类型变量>对应的<实际类型>,此时子类或实现类不再是泛型类
// ChineseStudent不再是泛型类 public class ChineseStudent extends Student<String>{} // 继承时指定实际类型 public class Rectangle implements Comparable<Rectangle>
-
用子类/子接口的类型变量指定父类或父接口的类型变量、此时子类、子接口、实现类仍然是泛型类或泛型接口
public interface Iterable<T> public interface Collection<E> extends Iterable<E> // 字母可以不一样 public class ArrayList<E> extends AbstractList<E> implements List<E>
-
泛型方法
方法也可以被泛型化,不管它所在的类是不是泛型类。在泛型方法中可以定义泛型参数,参数的类型就是传入数据的类型。
泛型方法格式:
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
//...
}
自定义泛型方法
在方法的返回值类型前面为这个方法单独声明泛型的<类型变量>
,这个方法可以是静态方法,也可以是非静态方法。
import java.util.Collection;
public class MyCollections {
public static <T> void addAll(Collection<T> coll, T... args){
for (T t : args) {
coll.add(t);
}
}
}
import java.util.ArrayList;
public class MyCollectionsTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
MyCollections.addAll(list, "hello","world","java");
System.out.println(list);
}
}
泛型类与泛型方法区别
-
<泛型变量>生命的位置不同
-
声明方法时,在修饰符与返回值类型之间声明类型变量,把声明(而不是使用)了类型变量的方法称为泛型方法
class AAA<T> { static T method(T t) {} // 不是泛型方法 }
-
声明类或接口时,在类名或接口名后面声明类型变量,把这样的类或接口称为泛型类或泛型接口
-
-
<泛型变量>使用范围不同
- 在类或接口名后面声明的<范型变量>在整个类中都可以使用,而且同名的范型变量代表的类型是相同的
- 在方法返回值类型前声明的<泛型变量>仅限于当前方法使用,和其他方法同名的<泛型变量>代表的类型是无关的
// 同一个Demo对象的m1和m2的T类型是有关联的,是同一种类型 class Demo<T> { void m1(T t1){ System.out.println("t1 = " + t1); } void m2(T t2){ System.out.println("t2 = " + t2); } } //同一个Example对象的m1和m2的T类型是无关的,独立的 class Example{ <T> void m1(T t1){ System.out.println("t1 = " + t1); } <T> void m2(T t2){ System.out.println("t2 = " + t2); } }
泛型上限与泛型擦除
当在声明类型变量时,如果不希望这个类型变量代表任意引用数据类型,而是某个系列的引用数据类型,那么可以设定类型变量的上限。
语法格式:<类型变量 extends 上限>
,如果有多个上限:<类型变量 extends 上限1 & 上限2>
-
定义泛型类的<泛型变量>时指定上限
例如:声明一个两个数算术运算的工具类,要求两个数必须是Number数字类型,并实现Comparable接口
class NumberTools<T extends Number & Comparable<T>> { private T a; private T b; public NumberTools(T a, T b) { this.a = a; this.b = b; } public T getSum() { if(a instanceof BigInteger){ return (T) ((BigInteger) a).add((BigInteger)b); }else if(a instanceof BigDecimal){ return (T) ((BigDecimal) a).add((BigDecimal)b); }else if(a instanceof Byte){ return (T)(Byte.valueOf((byte)((Byte)a+(Byte)b))); }else if(a instanceof Short){ return (T)(Short.valueOf((short)((Short)a+(Short)b))); }else if(a instanceof Integer){ return (T)(Integer.valueOf((Integer)a+(Integer)b)); }else if(a instanceof Long){ return (T)(Long.valueOf((Long)a+(Long)b)); }else if(a instanceof Float){ return (T)(Float.valueOf((Float)a+(Float)b)); }else if(a instanceof Double){ return (T)(Double.valueOf((Double)a+(Double)b)); } throw new UnsupportedOperationException("不支持该操作"); } }
public class NumberToolsTest { public static void main(String[] args) { NumberTools<Integer> tools = new NumberTools<Integer>(8,5); Integer sum = tools.getSum(); System.out.println("sum = " + sum); } }
-
定义泛型方法的<类型变量>时指定上限
编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,调用元素对象的compareTo方法比较元素的大小关系。要求数组的元素类型必须是java.lang.Comparable
class MyArrays { public static <T extends Comparable<T>> void sort(T[] arr) { for (int i = arr.length - 1; i > 0; i--) { for (int j = 0; j < i; j++) { if (arr[j].compareTo(arr[j + 1]) > 0) { T temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } }
public class MyArraysTest { public static void main(String[] args) { int[] arr = {3,2,5,1,4}; // MyArrays.sort(arr); // 错误的,因为int[]不是对象数组 String[] strings = {"hello","java","chai"}; MyArrays.sort(strings); System.out.println(Arrays.toString(strings)); } }
当使用参数化类型的类或接口时,如果没有指定泛型,会发生泛型擦除,自动按照最左边的第一个上限处理。如果没有指定上限,上限即为Object。
import java.util.ArrayList;
import java.util.Collection;
public class TestErase {
public static void main(String[] args) {
NumberTools tools = new NumberTools(8,5);
Number sum = tools.getSum();//自动按照Number处理
System.out.println("sum = " + sum);
ArrayList list = new ArrayList();
list.add("hello");
list.add(1);
for (Object o : list) { // 自动按照Object处理
System.out.println(o);
}
}
}
类型通配符
当声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,但无法确定这个泛型类或泛型接口的类型变量的具体类型,此时可以使用类型通配符 ?
。
类型通配符的三种使用形式
<?>
:此时 ? 表示任意类型<? extends Comparable>
:只允许泛型为实现Comparable接口的实现类<? super Number>
:只允许泛型为Number及Number父类
public static boolean different(Collection<?> c1, Collection<?> c2){
return c1.containsAll(c2) && c2.containsAll(c1);
}
public static <T> void addAll(Collection<? super T> c1, T... args){
for (int i = 0; i < args.length; i++) {
c1.add(args[i]);
}
}
public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src){
for (T t : src) {
dest.add(t);
}
}
泛型 T 与通配符 ? 的区别
-
T 可以单独使用,而 ? 必须依赖于泛型类或泛型接口使用
public static <T> void test1(T t){ System.out.println(t); } // public static void test2(? t){ // 错误 public static void test2(ArrayList<?> list){ System.out.println(list); }
-
T 只能在声明时指定上限,? 可以指定上限和下限
public static <T extends Number> void test1(T t){ System.out.println(t); } public static void test2(ArrayList<? extends Number> list){ System.out.println(list); } public static void test3(ArrayList<? super Number> list){ System.out.println(list); }
-
同一个方法中多个 T 代表相同的类型,多个 ? 没有关联性
public static <T> void test1(Collection<T> c1, Collection<T> c2){ c1.addAll(c2); // c1和c2的<T>是同一个类型 } public static <T> void test2(Collection<? super T> c1, Collection<? extends T> c2){ c1.addAll(c2); // c1和c2的<T>是同一个类型 } public static void test3(Collection<?> c1, Collection<?> c2) { // c1.addAll(c2);//报错 // c1和c2的<?>没有关联 }
-
T 是可以确定的类型,? 是不能确定的类型