java 泛型详解

  • Post author:
  • Post category:java


java 泛型详解

1. 泛型基本概念

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

使用字母类型建议: 泛型中字母没有限制, 但是为了可读性推荐一些

  • E — Element,常用在java Collection里,如:List,Iterator,Set
  • K,V — Key,Value,代表Map的键值对
  • N — Number,数字
  • T — Type,类型,如String,Integer等等

2. 泛型的三种使用


分别为:泛型类、泛型接口、泛型方法

2.1 案例讲解
2.1.1、

泛型类:


1)、单泛型变量类型, 基础用法:

//定义  
class Point<T>{// 此处可以随便写标识符号   
    private T x ;        
    private T y ;        
    public void setX(T x){//作为参数  
        this.x = x ;  
    }  
    public void setY(T y){  
        this.y = y ;  
    }  
    public T getX(){//作为返回值  
        return this.x ;  
    }  
    public T getY(){  
        return this.y ;  
    }  
};  
//IntegerPoint使用  
Point<Integer> p = new Point<Integer>() ;   
p.setX(new Integer(100)) ;   
System.out.println(p.getX());    

//FloatPoint使用  
Point<Float> p = new Point<Float>() ;   
p.setX(new Float(100.12f)) ;   
System.out.println(p.getX());    


2)、多泛型变量定义, 基础用法:

class MorePoint<T,U>{  
} 

class MorePoint<T,U,A,B,C>{  
}  

具体用法:

class MorePoint<T,U> {  
    private T x;  
    private T y;         
    private U name;  

    public void setX(T x) {  
        this.x = x;  
    }  
    public T getX() {  
        return this.x;  
    }  
    public void setName(U name){  
        this.name = name;  
    }  
    public U getName() {  
        return this.name;  
    }  
}  
//使用  
MorePoint<Integer,String> morePoint = new MorePoint<Integer, String>();  
morePoint.setName("harvic");  
Log.d(TAG, "morPont.getName:" + morePoint.getName());  
2.1.2、

泛型接口:

基础定义接口泛型:
interface Info<T>{        // 在接口上定义泛型    
    public T getVar() ;   // 定义抽象方法,抽象方法的返回值就是泛型类型    
    public void setVar(T x);  
}    


1)、使用方法一:非泛型类

class InfoImpl implements Info<String>{   // 定义泛型接口的子类  
    private String var ;                // 定义属性  
    public InfoImpl(String var){        // 通过构造方法设置属性内容  
        this.setVar(var) ;  
    }  
    @Override  
    public void setVar(String var){  
        this.var = var ;  
    }  
    @Override  
    public String getVar(){  
        return this.var ;  
    }  
}  

public class GenericsDemo24{  
    public  void main(String arsg[]){  
        InfoImpl i = new InfoImpl("harvic");  
        System.out.println(i.getVar()) ;  
    }  
};  

说明 : 要清楚的一点是InfoImpl不是一个泛型类!因为他类名后没有!

然后在在这里我们将Info中的泛型变量T定义填充为了String类型。所以在重写时setVar()和getVar()时,IDE会也我们直接生成String类型的重写函数。


2)、使用方法二:泛型类

interface Info<T>{        // 在接口上定义泛型  
    public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
    public void setVar(T var);  
}  
class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
    private T var ;             // 定义属性  
    public InfoImpl(T var){     // 通过构造方法设置属性内容  
        this.setVar(var) ;    
    }  
    public void setVar(T var){  
        this.var = var ;  
    }  
    public T getVar(){  
        return this.var ;  
    }  
}  
public class GenericsDemo24{  
    public static void main(String arsg[]){  
        InfoImpl<String> i = new InfoImpl<String>("harvic");  
        System.out.println(i.getVar()) ;  

        // 也可以
        InfoImpl<Integer> i = new InfoImpl<Integer>(2);  
        System.out.println(i.getVar()) ;  
    }  
};  

说明: InfoImpl 使用泛型T 说明可以使用任何类型进行new


3)、构造一个多个泛型变量的类,并继承自Info接口

泛型类中定义三个泛型变量T,K,U并且把第三个泛型变量U用来填充接口Info

class InfoImpl<T,K,U> implements Info<U>{   // 定义泛型接口的子类  
     private U var ;      
     private T x;  
     private K y;  
     public InfoImpl(U var){        // 通过构造方法设置属性内容  
         this.setVar(var) ;  
     }  
     public void setVar(U var){  
         this.var = var ;  
     }  
     public U getVar(){  
         return this.var ;  
     }  
 }  

使用和解释

public class GenericsDemo24{  
    public  void main(String arsg[]){  
        InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");  
        System.out.println(i.getVar()) ;  
    }  
}  
2.1.3 、

泛型方法:

概念: public 与 返回值中间< T >非常重要,可以理解为声明此方法为泛型方法。

1)、基本案例: 下面分别是静态泛型函数和常规泛型函数的定义方法

public class StaticFans {  
    //静态函数  
    public static  <T> void StaticMethod(T a){  
        Log.d("harvic","StaticMethod: "+a.toString());  
    }  
    //普通函数  
    public  <T> void OtherMethod(T a){  
        Log.d("harvic","OtherMethod: "+a.toString());  
    }  
}  

基本使用:

//静态方法  
StaticFans.StaticMethod("adfdsa");//使用方法一  
StaticFans.<String>StaticMethod("adfdsa");//使用方法二  

//常规方法  
StaticFans staticFans = new StaticFans();  
staticFans.OtherMethod(new Integer(123));//使用方法一  
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二 

解释 : 从结果中我们可以看到,这两种方法的结果是完全一样的,但他们还有些区别的,区别如下:

方法一,可以像普通方法一样,直接传值,任何值都可以(但必须是派生自Object类的类型,比如String,Integer等),函数会在内部根据传进去的参数来识别当前T的类别。但尽量不要使用这种隐式的传递方式,代码不利于阅读和维护。因为从外观根本看不出来你调用的是一个泛型函数。

方法二,与方法一不同的地方在于,在调用方法前加了一个来指定传给的值,如果加了这个来指定参数的值的话,那StaticMethod()函数里所有用到的T类型也就是强制指定了是String类型。这是我们建议使用的方式。

同样,常规泛型函数的使用也有这两种方式:


2)、进阶:返回值中存在泛型

上面我们的函数中,返回值都是void,但现实中不可能都是void,有时,我们需要将泛型变量返回,比如下面这个函数:

public static <T> List<T> parseArray(String response,Class<T> object){  
    List<T> modelList = JSON.parseArray(response, object);  
    return modelList;  
}  

解释 : 函数返回值是List类型。至于传入参数Class object的意义,我们下面会讲。这里也就是想通过这个例子来告诉大家,泛型变量其实跟String,Integer,Double等等的类的使用上没有任何区别,T只是一个符号,可以代表String,Integer,Double……这些类的符号,在泛型函数使用时,直接把T看到String,Integer,Double……中的任一个来写代码就可以了。唯一不同的是,要在函数定义的中在返回值前加上标识泛型;

3. 其它用法:Class< T >类传递及泛型数组


1)、使用Class传递泛型类Class对象

注意 :


方法内部转化的时候比较好用

有时,我们会遇到一个情况,比如,我们在使用JSON解析字符串的时候,代码一般是这样的

public static List<SuccessModel> parseArray(String response){  
    List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);  
    return modelList;  
}  

解释: 其中SuccessModel是自定义的解析类,代码如下,其实大家不用管SuccessModel的定义,只考虑上面的那段代码就行了。写出来SuccessModel的代码,只是不想大家感到迷惑,其实,这里只是fastJson的基本用法而已。

这段代码的意义就是根据SuccessModel解析出List的数组。

改进成上面的案例代码:

public static <T> List<T> parseArray(String response,Class<T> object){  
    List<T> modelList = JSON.parseArray(response, object);  
    return modelList;  
}  

使用:

String myJson = "自己实际的json字符串";
List<SuccessModel> modelList  =  parseArray(myJson, SuccessModel.class);


2)、定义泛型数组


定义:

public static <T> T[] fun1(T...arg){  // 接收可变参数    
       return arg ;                   // 返回泛型数组    
}    

解释: 定义了一个静态函数,然后定义返回值为T[],参数为接收的T类型的可变长参数。

使用:

//定义  
public static <T> T[] fun1(T...arg){  // 接收可变参数    
       return arg ;            // 返回泛型数组    
}    

//使用  
public static void main(String args[]){    
       Integer i[] = fun1(1,2,3,4,5,6) ;  
       Integer[] result = fun1(i) ;  
}   

4. 泛型边界问题

概念: 在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。

  • 为泛型添加上边界,即传入的类型实参必须是指定类型的子类型

1)、

类形式的下边界

public class Generic<T extends Number>{
    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey(){
        return key;
    }
}

下面的使用中, 必须为Number 的子类否则不能编译通过

//这一行代码也会报错,因为String不是Number的子类
Generic<String> generic1 = new Generic<String>("11111");
Generic<Integer> generic2 = new Generic<Integer>(2222);
Generic<Float> generic3 = new Generic<Float>(2.4f);

-2)、

方法形式的下边界:

在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的< T >上添加上下边界,即在泛型声明的时候添加


错误类型:

public <T> T showKeyName(Generic<T extends Number> container)
编译器会报错:"Unexpected bound"


正确类型:

public <T extends Number> T showKeyName(Generic<T> container){
    System.out.println("container key :" + container.getKey());
    T test = container.getKey();
    return test;
}

5. 泛型通配符

概念: 类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。重要说三遍!此处’?’是类型实参,而不是类型形参 ! 此处’?’是类型实参,而不是类型形参 !再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。

可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

  • 定义和使用

    错误案例:
public void showKeyValue1(Generic<Number> obj){
    Log.d("泛型测试","key value is " + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);

showKeyValue(gNumber);

// showKeyValue这个方法编译器会为我们报错:Generic<java.lang.Integer> 
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);

正确定义:(这里不是泛型方法, 只是使用了泛型通配符)

public void showKeyValue1(Generic<?> obj){
    Log.d("泛型测试","key value is " + obj.getKey());
}

总结使用对比:

// 泛型方法
public <T> List<T>  parseArray(String response,Class<T> object)
private static <T> void listAddAvoidNull(List<T> dest, T source)
// 非泛型方法, 使用泛型通配符
public void showKeyValue1(Generic<?> obj)

还有一点: 非泛型方法不能使用Class< T > 必须使用泛型通配符Class< ? >

# 6. 参考文章:


https://blog.csdn.net/qq_27093465/article/details/73229016



http://www.runoob.com/java/java-generics.html



https://blog.csdn.net/s10461/article/details/53941091



版权声明:本文为zhongzunfa原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。