赞
踩
在编写Java代码时,可能会有将所有类型的元素都可存放入一个数组存储的想法,此时自然而然能想到,使用超类Object类创建Object[]即可接收所有类的对象。但是这样的做法存在弊端:每当要使用Object[]内的元素时,要对元素进行强制转换,这又需要知道取出的元素为哪种类型,否则这种行为是不安全的。这种存储方式便偏离了方便使用的初衷。所以为了方便存储和调用,能否对这个Object[]进行规定?规定其存放的类型,在取用时就可直接使用,若能如此则十分便利。
泛型是JDK5引入的一个特性。泛型机制
在Java程序进行编译时进行安全检测,允许使用者在编译阶段检测出非法的类型。
class ClassName<T1,T2,...,Tn>{
}
class ClassName<T1,T2,...,Tn> extends ParentClass<T1>{
}
Java常用中泛型标记符
符号 | |
---|---|
E - Element | 元素 |
T - Type | 类型(非8种数据类型) |
K - Key | 键 |
V - Value | 值 |
N - Number | 数值类型 |
S,U,V等 | 第二、第三、第四个类型 |
? | 表示不确定的java类型 |
List<Integer> s = new ArrayList<Integer>();
List<Integer> s = new ArrayList<>();
//两条语句效果相同
泛型将数据类型参数化进行传递,在编译时进行检测。只能使用类,不能使用8种基本的数据类型,若要使用则需要使用其包装类(每一个包装对象是不可变的,存储着基本数据类型原值,并提供获得基本数据类型值的方法,Java5后包装类支持自动装箱/拆箱)
使用表示当前类是泛型类
例:
public class Generics {
public static void main(String[] args) {
Integer[] arr = {1, 2, 3, 4, 5};
print(arr);
}
//泛型static方法
public static <E> void print (E[] input){
for (E element: input) {
System.out.printf("%s ", element);
}
System.out.println();
}
}
在泛型方法中的类型参数位于返回类型前
通配符< ? >是用来弥补泛型和泛型集合无法协变的不足。
Java中的数组是类型兼容的,叫作协变数组类型。数组是协变的,而泛型和泛型集合不是协变的。
//实现A、B类,B和A间为子类和父类关系
A[] test1 = new B[10];//编译通过
List<A> test2 = new ArrayList<B>();//编译报错
List<?> test3 = new ArrayList<B>();//<?>在此代表可以接收各种类型
假设A类和B类存在继承关系,A类为B类的父类,f(A)为A类更复杂的类型转换,如:ArrayList
协变:变化后保持对应关系,即 B ≤ A -> f(B) ≤ f(A)
逆变:变化后逆转对应关系,即 B ≤ A -> f(A) ≤ f(B)
不变:变化后不满足上述两种关系
但是这样又会带来另外的问题
看下面这段代码
//定义一个泛型类Fruit,提供构造,get和set方法 class Fruit<T>{ private T fruit; public Fruit() { } public Fruit(T fruit) { this.fruit = fruit; } public void setFruit(T fruit) { this.fruit = fruit; } public T getFruit() { return fruit; } } public class Generics { //main方法 public static void main(String[] args) { Fruit<String> test1 = new Fruit<>("草莓"); Fruit<Integer> test2 = new Fruit<>(3); printFruit(test1);//输出"草莓" printFruit(test2);//无法编译,因为printFruit方法中要求的参数类型为Fruit<String> } public static void printFruit(Fruit<String> fruit){ System.out.println(fruit.getFruit()); } }
此时我们只需将上述printFruit方法
改成如下,即可运行
public static void printLife(Fruit<?> fruit){
System.out.println(fruit.getShelfLife());
}
但是上述的改法并不安全,因为<?>可以接收任何泛型,这不是我们想要看到的,应当要加以限制,这就引出了泛型上界
和泛型下界
泛型上界基于协变性质
<? extends 上界>
public static int count(Fruit<? extends Fruit> fruit){
//可传入的实参类型被限定为Fruit及其子类
}
? extends设置的上界不能写入数据,只能进行数据读取
//在public class Generics中添加该方法,同时构建一个Apple类继承Fruit类
public static void correctFruit(Fruit<? extends Fruit> fruit){
fruit.setFruit(new Apple());//发生错误
}
对于? extends限定的类型操作
List<? extends Fruit> wareHouse;
//Fruit为父类,Apple,Banana,Melon等为子类
对于wareHouse来说可以读取到Fruit的对象,因为其中存储的是为Fruit类和Fruit的子类,而不一定能读取到Apple,因为可能其中存储的是Banana或Melon,出于数据安全考虑,一般用Fruit(基类
)进行接收
对于wareHouse来说,我们不能存储Fruit入wareHouse中,因为wareHouse可能为List,不能存入Apple,因为wareHouse有可能为List,所以不能进行写入操作
泛型下界基于逆变性质
<? super 下界>
public static int count(Fruit<? super Fruit> fruit){
//可传入的实参类型被限定为Fruit及其父类
}
? super设置的上界不能读取数据,只能进行数据存储
对于? super限定的类型操作
List<? super Fruit> wareHouse;
对于wareHouse来说,我们不能保证能读取到Fruit类型的数据,因为wareHouse可能存储的是Fruit的父类类型数据;不一定能读取Fruit的父类类型数据,有可能其中存储的是Object类型的数据,可以确定的是我们能读取的是Object类型的实例(见泛型擦除)
对于wareHouse来说,可以存入Fruit及其子类类型的数据,但是不能存入Fruit父类,如Object类类型数据
泛型类可以由编译器通过类型擦除转变成非泛型类
class Fruit<T>{
//在编译器编译时,T 会被替换成Object
}
编译器生成了一种与泛型类同名的原始类
,类型参数都被删去,类型变量由类型界限来代替,这就是擦除机制
优点:程序员可省略一些类型转换代码,由编译器进行类型检查
不能创建一个泛型类型的实例
T obj = new T(); //非法
不能创建一个泛型数组
T[] obj = new T[5]; //非法
不能进行参数化类型的数组的实例化
Fruit<String>[] arr = new Fruit<String>[10];//非法
[1] Java 之泛型通配符 ? extends T 与 ? super T 解惑
[3] 数据结构与算法分析 [美]Mark Allen Weiss
本文的主要目的是充当学习笔记,同时强化学习效果,且兼有分享所学知识之意,欢迎批评指正
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。