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