概述
- Java 泛型(generics)是JDK5中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
- 在创建对象或调用方法的时候才明确下具体的类型,好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
使用方法
- 你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
- 下面是定义泛型方法的规则:
- 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 )。
- 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
- 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
- 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)。
泛型中通配符:
- 本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(Java 类)
- K - Key(键)
- V - Value(值)
- N - Number(数值类型)
- ? - 表示不确定的 java 类型
实例
下面的例子演示了如何使用泛型方法打印不同类型的数组元素:
public class GenericMethodTest
{
// 泛型方法 printArray
public static < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
public static void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println( "整型数组元素为:" );
printArray( intArray ); // 传递一个整型数组
System.out.println( "\n双精度型数组元素为:" );
printArray( doubleArray ); // 传递一个双精度型数组
System.out.println( "\n字符型数组元素为:" );
printArray( charArray ); // 传递一个字符型数组
}
}
泛型参数声明
- 可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。
- 要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。
实例
下面的例子演示了"extends"如何使用在一般意义上的意思"extends"(类)或者"implements"(接口)。该例子中的泛型方法返回三个可比较对象的最大值。
public class MaximumTest
{
// 比较三个值并返回最大值
public static <T extends Comparable<T>> T maximum(T x, T y, T z)
{
T max = x; // 假设x是初始最大值
if ( y.compareTo( max ) > 0 ){
max = y; //y 更大
}
if ( z.compareTo( max ) > 0 ){
max = z; // 现在 z 更大
}
return max; // 返回最大对象
}
public static void main( String args[] )
{
System.out.printf( "%d, %d 和 %d 中最大的数为 %d\n\n",
3, 4, 5, maximum( 3, 4, 5 ) );
System.out.printf( "%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n\n",
6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );
System.out.printf( "%s, %s 和 %s 中最大的数为 %s\n","pear",
"apple", "orange", maximum( "pear", "apple", "orange" ) );
}
}
泛型类声明
- 泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
public class Box<T> {
private T t;
}
类型通配符
?无界通配符
- 对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 <?> ),表示可以持有任何类型。
- 例如 List<?> 在逻辑上是 List,List 等所有 List<具体类型实参> 的父类。
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
- 我有一个父类 Animal 和几个子类,如狗、猫等,现在我需要一个动物的列表:
List<Animal> listAnimals
List<? extends Animal> listAnimals
通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。
static int countLegs (List<? extends Animal > animals ) {
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
上界通配符 < ? extends E>
- 用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
- 类型通配符上限通过形如List来定义,如此定义就是通配符泛型值接受Number及其下层子类类型。
public static void getUperNumber(List<? extends Number> data) {
System.out.println("data :" + data.get(0));
}
-
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
- 如果传入的类型不是 E 或者 E 的子类,编译不成功
- 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
-
类型参数列表中如果有多个类型参数上限,用逗号分开
private <K extends A, E extends B> E test(K arg1, E arg2){
E result = arg2;
arg2.compareTo(arg1);
//.....
return result;
}
下界通配符 < ? super E>
- 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,如 Object 类型的实例。
- 在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。
private <T> void test(List<? super T> dst, List<T> src){
for (T t : src) {
dst.add(t);
}
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = new ArrayList<>();
new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子类
class Dog extends Animal {
}
?和 T 的区别
- T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
- 区别1:通过 T 来 确保 泛型参数的一致性;
//通过T来确保泛型参数的一致性
public <T extends Number>void
test(List<T>dest,List<T>src)
/通配符是不确定的,所以这个方法不能保证两个List具有相同的元素类型
public void
test(List<?extends Number>dest,List<?extends Number>src)
- 区别2:类型参数可以多重限定而通配符不行, 使用 & 符号设定多重边界(Multi Bounds),指定泛型类型 T 必须是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子类型,此时变量 t 就具有了所有限定的方法和属性。对于通配符来说,因为它不是一个确定的类型,所以不能进行多重限定。
public class MultiLimit implements MultiLimitInterfaceA,MultiLimitInterfaceB
public static<T extends MultiLimitInterfaceA MultiLimitInterfaceB>void test(Tt){
}
*接口A
interface MultiLimitInterfaceA {
}
*接口B
interface MultiLimitInterfaceB {
}
- 区别3:通配符可以使用超类限定而类型参数不行
类型参数 T 只具有 一种 类型限定方式:
T extends A
但是通配符 ? 可以进行 两种限定:
? extends A
? super A
Class和 Class<?>区别
- Class在实例化的时候,T 要替换成具体类。Class<?>它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。
- 当不知道定声明什么类型的 Class 的时候可以定义一 个Class<?>。
public class Test3{
/可以
public Class<?>clazz;
//不可以,因为T需要指定类型
public Class<T>clazzT;
}
- 那如果也想 public Class clazzT;这样的话,就必须让当前的类也指定 T ,
public class Test3<T>{
public Class<?>clazz;
//不会报错
public Class<T>clazzT;
}
参考文档
- https://www.runoob.com/java/java-generics.html 菜鸟教程
- https://mp.weixin.qq.com/s/sS0wXGGQ0IK7LaeDMtSarQ