1.泛型定义
泛型是JavaSE 1.5的新特性,本质是参数化类型,也就是所操作的数据类型被指定为一个参数,将类型由原来的具体的参数类型化,类似于方法中的变量参数,此时类型也定义成参数形式(类型形参),然后在使用/调用时传入具体的类型(类型实参)。这种参数可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
2.泛型类
2.1泛型类定义
泛型类的声明和非泛型类的声明类似,除了在类名后面添加参数声明部分。泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号分隔。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化类型。
定义方法:
class 类名 <泛型标识(如E,K,T,V等)>{
private 泛型标识 test;
}
2.1泛型类测试
示例:
class Test<T>{
private T test;
public Test(T test){
this.test = test;
}
public T getTest(){
return test;
}
}
测试:
public static void main(String[] args) {
Test<Integer> test = new Test<>(123);
Integer num = test.getTest();
System.out.println(num);
Test<String> strTest = new Test<>("myTest");
String str = strTest.getTest();
System.out.println(str);
}
分别输出:123,myTest,在调用时需要保证传入的实参类型和泛型类型参数相同。
2.3 注意点
- 泛型的类参只能是引用数据类型,不能是基本数据类型。
基本数据类型8种:byte,short,int,long,char,boolean,float,double;
引用数据类型3中:类,接口,数组; - 泛型会自动对类型进行检查并进行类型转换。
- 不能对确切的泛型类使用instanceof操作。
- 不能直接new泛型数组。
- 不能产生泛型类型的对象。
- 在静态方法中不能使用泛型类型参数,因为泛型是靠类对象去传参,而static不依赖对象。
3. 泛型接口
3.1 泛型接口定义
interface interfaceTest<T>{
T test();
}
3.2 测试
class Test<T> implements interfaceTest<T>{
private T test;
public Test(T test){
this.test = test;
}
public T getTest(){
return test;
}
public void setTest(T test){
this.test = test;
}
@Override
public T test() {
return null;
}
}
4.泛型方法
4.1泛型方法定义
public T mytest(){
return test;
}
上面这种并不是叫做泛型方法,它只是返回一个泛型类型参数的对象方法。
下面这种方法才是泛型方法:
public static <T> T stringTest(Test<T> test){
return test.getTest();
}
public static void main(String[] args) {
Test<String> test = new Test<>("apple");
System.out.println(stringTest(test));
}
输出:apple
4.2 实例测试
交换函数:
public static <T>void swap(T[] element,int index1,int index2){
T temp = element[index1];
element[index1] = element[index2];
element[index2] = temp;
}
基于泛型的插入排序:
//插排(泛型),小->大
public static <T extends Comparable<T>>T[] insertSort(T[] arr){
if(arr == null || arr.length < 2){
return arr;
}
T temp;
int j = 0;
for (int i = 1; i < arr.length; i++) {
//待插元素暂存temp
temp = arr[i];
j = i-1;
//扫描待插元素前的元素,若大于待排元素则后移动一位
while(j >= 0 && temp.compareTo(arr[j]) < 0){
arr[j+1] = arr[j--];
}
arr[j+1] = temp;
}
return arr;
}
基于泛型的选择排序:
//选排(泛型),小->大
public static <T extends Comparable<T>>T[] selectSort(T[] arr){
if(arr == null || arr.length < 2){
return arr;
}
int pos = 0;
for (int i = 0; i < arr.length; i++) {
pos = i;
//选择最小元素
for (int j = i+1; j < arr.length; j++) {
if(arr[pos].compareTo(arr[j]) > 0){
pos = j;
}
}
//最小元素与无序序列的第一个元素交换
swap(arr, i, pos);
}
return arr;
}
测试:
public static <T extends Comparable<T>>void display(T[] arr){
for(T t:arr){
System.out.print(t+" ");
}
}
public static void main(String[] args) {
Integer[] arr = new Integer[]{3,6,1,1,8,4,7};
Integer[] newArr = TestDemo.selectSort(arr);
display(newArr);
}
两种排序方法都正常。注意:若泛型方法的泛型参数和类型的泛型参数相同,会产生警告。
5.类型擦除机制
5.1 类型擦除定义
Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。
- 消除类型参数声明,即删除<>及其包围的部分。
- 根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
- 为了保证类型安全,必要时插入强制类型转换代码。
- 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。
5.2 擦除分析
Test<String> test1 = new Test<>("apple");
Test<Integer> test2 = new Test<>(123);
System.out.println(test1.getClass() == test2.getClass());
可以看出,上方定义了两个不同引用类型的对象,但是进行类信息比较时输出的却是true,说明泛型类型的String和Integer都被擦除掉了,只剩下原始类型。原始类型就是擦除泛型信息最后体现在字节码中的类型是变量的真正类型,泛型原始类型和泛型类型的实例化对象是一样的。无论何时定义一个泛型类型,都会被自动的被擦除到原生类型并使用其限定类型,无限定类型的用Object替换。限定类型如Number下有Integer,Long,Byte等,Number是它们的上界,在使用时可以向上造型,而不能向下造型。
class Erasure <T>{
T object;
public Erasure(T object) {
this.object = object;
}
}
Erasure 是一个泛型类,打印它的类型信息:
Erasure<String> erasure = new Erasure<String>("test");
Class c= erasure.getClass();
System.out.println(c.getName());
结果如上,Class的类型是Erasure而不是Erasure<T>的形式。再查看它在jvm中的类型发现是Object类型。
但如果用extends来限定泛型参数的上界的话,类型就会变成限定类型。
将上方代码改写:
public class Erasure <T extends Integer>{
T object;
public Erasure(T object) {
this.object = object;
}
}
再输出会发现类型变成了Integer。
extends主要限定泛型参数的上界,常见的使用:
<T extends 基类> //T只能是基类或基类的派生类
<T extends 基接口> //T只能是实现基接口的派生类
6.通配符
6.1. 无边界的通配符
无边界的通配符的主要作用就是让泛型能够接受未知类型的数据,形式为<?>, 比如List<?>.
public static void testFun(ArrayList<?> arrayList){
Iterator<?> iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
public static void main(String[] args) {
Object o1 = new Integer(10);
Object o2 = new String("test");
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
testFun(arrayList);
}
6.2. 固定上边界的通配符:
使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据. 要声明使用该类通配符, 采用<? extends E>的形式, 这里的E就是该泛型的上边界. 注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类.
public static void testFun(ArrayList<? extends Number> arrayList){
Iterator<?> iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
extends的类必须为限定上界类也就是Number 的子类。
6.3. 固定下边界的通配符:
使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据. 要声明使用该类通配符, 采用<? super E>的形式, 这里的E就是该泛型的下边界. 注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界.
public static void testFun(ArrayList<? super Number> arrayList){
Iterator<?> iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}