定义一个方法,可以接受任何类型的对象,可能会想到用Object类型去接受参数就可以了,返回也是Object。这种方式虽然能够实现预想的功能,但是返回的结果我们还需要依据我们所传入的对象类型进行强转,那有没有可能,不需要我们强转,能根据我们所传参数的类型自动返回相应类型的对象呢?
答案是肯定的,实现方式就是利用了Java中的泛型!
对于我们上一篇博文中提到的集合,就可以使用泛型,使用形式可以如下:
ArrayList<String> list = new ArrayList<String>(); //推荐使用这种形式
ArrayList list2 = new ArrayList<String>();
ArrayList<String> list3 = new ArrayList();
ArrayList<Object> list4 = new ArrayList<String>(); //错误
ArrayList<String> list5 = new ArrayList<Object>(); //错误
第一种是最标准的,也是推荐使用的一种定义方式。
至于第二种,第三种为什么也是正确的,主要是考虑了兼容性问题,因为泛型是在JDK1.5才出现的,但是集合在JDK1.2就出现了,可想而知以前利用JDK低版本写的代码中必定会有以ArrayList类型作为参数的方法和有返回值类型为ArrayList,为了与我们目前应用了泛型的集合ArrayList<String> list = new ArrayList<String>();
相兼容,所以我们自定义的ArrayList<String> list
可以接收返回值为ArrayList类型的集合,即ArrayList<String> list3 = new ArrayList();
该形式正确;同时传入参数为ArrayList<String> list
到未使用泛型的集合也应该可行,所以ArrayList list2 = new ArrayList<String>();
形式也得支持,从而保证了兼容性。
第四种,第五种,好像是多态,泛型没有多态的概念,左右两边的数据类型必须要一致,或者只写一边的泛型类型。
那么,使用Java泛型有什么好处呢?
<String>
添加后表示该集合容器只接受String类型的对象,所以如果添加了其他类型的参数,那么编译时就会报错,即将运行的错误提前至编译时。而且获取数据时也无需再进行无谓的强制类型转换。
下面我们来看一下自定义的泛型该如何使用?也就是看看如何解决我们一开篇就提出的问题
Java泛型:JDK1.5出现的新特性
自定义泛型:就是一个数据类型的占位符或者是一个数据类型的变量
1.方法上自定义泛型,语法规则如下:
修饰符 <声明自定义泛型>返回数据类型 函数名(使用自定义泛型){
}
static <T>T getData(T t){
return t;
}
注意:
1.泛型中不能使用基本数据类型,如果需要使用,就使用基本数据类型的包装器
2.方法上自定义泛型,这个泛型的具体数据类型是在调用该方法传入实参时才确定的
3.自定义泛型只要符合标识符的定义规则就可以,但是习惯使用一个大写字母表示,常见的有T Type E Element
2.类上自定义泛型:语法规则
假设我们现在需要写一个数组的工具类,可以实现数组的翻转的打印,当然可以接受不同类型的数组,利用我们上述提到的在方法上自定义泛型,我们可以定义如下类:
class ArrayTool{
public <T>void reverse(T[] arr){
for(int startIndex=0,endIndex=arr.length-1;startIndex<arr.length/2&&endIndex>=0;startIndex++,endIndex--){
T temp = arr[startIndex];
arr[startIndex] = arr[endIndex];
arr[endIndex] = temp;
}
}
public <T>void toString(T[] arr){
for(int i=0;i<arr.length;i++){
if(i == 0){
System.out.print("["+arr[i]+",");
}else if(i == arr.length-1){
System.out.print(arr[i]+"]");
}else {
System.out.print(arr[i]+",");
}
}
}
}
我们发现上述类中的每个方法上都需要声明自定义泛型T,那我们就想有没有一种可以一次性声明自定义泛型的方法呢?答案是肯定的,那就是需要我们在类上自定义泛型,类上自定义泛型的规则:
class 类名<声明自定义泛型>{
}
那么对于上述自定义的类我们可以修改成在类上进行自定义泛型:
class ArrayTool<T>{
public void reverse(T[] arr){
for(int startIndex=0,endIndex=arr.length-1;startIndex<arr.length/2&&endIndex>=0;startIndex++,endIndex--){
T temp = arr[startIndex];
arr[startIndex] = arr[endIndex];
arr[endIndex] = temp;
}
}
public void toString(T[] arr){
for(int i=0;i<arr.length;i++){
if(i == 0){
System.out.print("["+arr[i]+",");
}else if(i == arr.length-1){
System.out.print(arr[i]+"]");
}else {
System.out.print(arr[i]+",");
}
}
}
}
使用上述自定义泛型的类进行创建对象的时候,类比于集合对象的创建
ArrayTool<Integer> arrayTool = new ArrayTool<Integer>();
注意:
1.在类上自定义泛型的具体数据类型在我们创建该类对象的时候确定的
2.如果在定义该类的时候声明了自定义泛型,但是在创建该类对象的时候并没有指定具体的数据类型,那么默认的数据类型是Object
3.在类上自定义泛型不能应用于静态方法上,如果静态方法需要使用自定义泛型,那么就需要在静态方法上声明使用
3.在接口上自定义泛型
语法规则:
class 接口名<声明自定义泛型 >{
}
注意:
1.接口自定义泛型的实际数据类型是在实现一个接口的时候具体指定的
interface Dao<T>{
public void add(T t);
}
public class FanXingInterface implements Dao<String>{ //具体指定了String类型
public void add(String o){
}
}
2.如果在接口实现类的时候没有指定具体的数据类型,默认为Object类型
interface Dao<T>{
public void add(T t);
}
public class FanXingInterface implements Dao{ //没有指定具体的数据类型
public void add(Object o){
}
}
那么,如果我想实现的是这样一种效果,就是在创建实现接口类对象的时候才去指定具体的数据类型,应该如何实现呢?
interface Dao<T>{
public void add(T t);
}
public class FanXingInterface<T> implements Dao<T>{ //继续在类上声明自定义泛型
public static void main(String[] args){
FanXingInterface<String> d = new FanXingInterface<String>();
}
public void add(T o){
}
}
泛型的上下限
之前我们自定义泛型,无非就是想接受任意类型的对象,但是假如现在对接受的对象的类型有了进一步的要求,那么我们应该怎么实现呢?
不着急,先来看一个小问题,现在我们定义一个可以接受任意类型的集合对象的函数
public void print(Collection c){
}
当然在这里我们并不需要泛型,既然可以接受任何集合,那我们就用集合的顶层接口Collection接收实参就可以了。
但是上述问题我们还没说完,接受的集合对象只能接受Integer或者Integer父类的数据,这可怎么实现呢?
泛型中的通配符:“?”可以匹配任意类型,但是并不会单独使用。
//泛型的下限
//? super Integer 表示接收的实参,是一个集合,而且集合中的元素类型是Integer或者Integer的父类类型
public void print(Collection<? super Integer> c){
}
//泛型的上限
//? extends Number 表示接受的实参,是一个集合,而且集合中的元素类型是Number或者Number的子类类型
public void print(Collection<? extends Number> c){
}