假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?
答案是可以使用
Java 泛型
。
使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型将原来的具体的类型参数化,
类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),
然后在使用/调用时传入具体的类型(类型实参)。泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型
我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V 等等,这些通配符又都是什么意思呢?
常用的 T,E,K,V,?
本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,
并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:
-
E - Element (在集合中使用,因为集合中存放的是元素),E是对各方法中的泛型类型进行限制,以保证同一个对象调用不同的方法时,操作的类型必定是相同的。E可以用其它任意字母代替
-
T - Type(Java 类),T代表在调用时的指定类型。会进行类型推断
-
K - Key(键)
-
V - Value(值)
-
N - Number(数值类型)
-
? - 表示不确定的java类型,是类型通配符,代表所有类型。?不会进行类型推断
public void cycleList(List list) 可以用 public void cycleList(List list) 重载还是重写?
重载规则---必须具有不同的参数列表,可以有不同的返回类型;可以有不同的访问修饰符;可以抛出不同的异常。
重写规则---参数列表必须完全与被重写的方法相同,否则不能称其为重写;返回类型必须与被重写的方法相同,否则不能称其为重写
尽管两个cycleList方法具有相同的字节码,但是类型参数信息用 一个新的签名(signature) 属性记录在类模式中。JVM 在装载类时记录这个签名信息,
并在运行时通过反射使它可用。这就导致了这个方法既不能作为覆盖父类cycleList方法的方法,也不能作为cycleList方法的重载。
public class GenericTest {
public static void main(String[] args) {
Box name = new Box("corn");
Box age = new Box(712);
System.out.println("name class:" + name.getClass()); // com.xx.Box
System.out.println("age class:" + age.getClass()); // com.xx.Box
System.out.println(name.getClass() == age.getClass()); // true
}
}
由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,
即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,
也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
1
)
泛型接口
、
泛型类
类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,
是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
2
)泛型方法
根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的)。
每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。
3
)类型通配符
类型通配符一般是使用?代替具体的类型参数。例如
List>
在逻辑上是
List,List
等所有List<具体类型实参>的父类。
有界的类型参数
extends T> 和 super T>
可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。
要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。
public class MaximumTest {
public static > T maxnum(T t1, T t2, T t3) {
T max = t1;
if (t2.compareTo(max) > 0) {
max = t2;
}
if (t3.compareTo(max) > 0) {
max = t3;
}
return max;
}
public static void main(String[] args) {
System.out.println("maxnum(1,2,3) = " + maxnum(1, 2, 3));
System.out.println("maxnum(1.1,3.3,2.2) = " + maxnum(1.1, 3.3, 2.2));
System.out.println("maxnum(\"abc\",\"cda\",\"bdf\") = " + maxnum("abc", "cda", "bdf"));
}
}
上界通配符Plate<? extends Fruit>覆盖下图中蓝色的区域
下界通配符 Plate<? super Fruit>覆盖下图中红色的区域
PECS原则总结
从上述两方面的分析,总结PECS(Producer Extends Consumer Super)原则如下: