夯实JAVA基本之一 —— 泛型详解(1):基本使用(转)

1、引入
一、泛型是什么
首先告诉你们ArrayList就是泛型。那ArrayList能完成哪些想不到的功能呢?先看看下面这段代码:
ArrayList<String> strList = new ArrayList<String>();
ArrayList<Integer> intList = new ArrayList<Integer>();
ArrayList<Double> doubleList = new ArrayList<Double>();
你们对ArrayList很熟悉,这里构造了三个List,分别盛装String、Integer和Double;这就是ArrayList的过人之处:即各类类型的变量均可以组装成对应的List,而没必要针对每一个类型分别实现一个构建ArrayList的类。这里可能看不懂,开篇老是困难的,下面看看若是没有泛型的话,咱们要怎么作;
二、没有泛型会怎样
先看下面这段代码:
咱们实现两个可以设置点坐标的类,分别设置Integer类型的点坐标和Float类型的点坐标:
//设置Integer类型的点坐标
class IntegerPoint{
private Integer x ; // 表示X坐标
private Integer y ; // 表示Y坐标
public void setX(Integer x){
this.x = x ;
}
public void setY(Integer y){
this.y = y ;
}
public Integer getX(){
return this.x ;
}
public Integer getY(){
return this.y ;
}
}
//设置Float类型的点坐标
class FloatPoint{
private Float x ; // 表示X坐标
private Float y ; // 表示Y坐标
public void setX(Float x){
this.x = x ;
}
public void setY(Float y){
this.y = y ;
}
public Float getX(){
return this.x ;
}
public Float getY(){
return this.y ;
}
}
那如今有个问题:你们有没有发现,他们除了变量类型不同,一个是Integer一个是Float之外,其它并无什么区别!那咱们能不能合并成一个呢?
答案是能够的,由于Integer和Float都是派生自Object的,咱们用下面这段代码代替:
class ObjectPoint{
private Object x ;
private Object y ;
public void setX(Object x){
this.x = x ;
}
public void setY(Object y){
this.y = y ;
}
public Object getX(){
return this.x ;
}
public Object getY(){
return this.y ;
}
}
即所有都用Object来代替全部的子类;
在使用的时候是这样的:
ObjectPoint integerPoint = new ObjectPoint();
integerPoint.setX(new Integer(100));
Integer integerX=(Integer)integerPoint.getX();
在设置的时候,使用new Integer(100)来新建一个Integer
integerPoint.setX(new Integer(100));
而后在取值的时候,进行强制转换:
Integer integerX=(Integer)integerPoint.getX();
因为咱们设置的时候,是设置的Integer,因此在取值的时候,强制转换是不会出错的。
同理,FloatPoint的设置和取值也是相似的,代码以下:
ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
Float floatX = (Float)floatPoint.getX();
但问题来了:注意,注意,咱们这里使用了强制转换,咱们这里setX()和getX()写得很近,因此咱们明确的知道咱们传进去的是Float类型,那若是咱们记错了呢?
好比咱们改为下面这样,编译时会报错吗:
ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
String floatX = (String)floatPoint.getX();
不会!!!咱们问题的关键在于这句:
String floatX = (String)floatPoint.getX();
强制转换时,会不会出错。由于编译器也不知道你传进去的是什么,而floatPoint.getX()返回的类型是Object,因此编译时,将Object强转成String是成立的。必然不会报错。
而在运行时,则否则,在运行时,floatPoint实例中明明传进去的是Float类型的变量,非要把它强转成String类型,确定会报类型转换错误的!
那有没有一种办法在编译阶段,即能合并成同一个,又能在编译时检查出来传进去类型不对呢?固然,这就是泛型。
下面咱们将对泛型的写法和用法作一一讲解。
2、各类泛型定义及使用
一、泛型类定义及使用
咱们先看看泛型的类是怎么定义的:
//定义
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());
先看看运行结果:java

从结果中能够看到,咱们实现了开篇中IntegerPoint类和FloatPoint类的效果。下面来看看泛型是怎么定义及使用的吧。数组

(1)、定义泛型:Point<T>
首先,你们能够看到Point<T>,即在类名后面加一个尖括号,括号里是一个大写字母。这里写的是T,其实这个字母能够是任何大写字母,你们这里先记着,能够是任何大写字母,意义是相同的。
(2)类中使用泛型
这个T表示派生自Object类的任何类,好比String,Integer,Double等等。这里要注意的是,T必定是派生于Object类的。为方便起见,你们能够在这里把T当成String,即String在类中怎么用,那T在类中就能够怎么用!因此下面的:定义变量,做为返回值,做为参数传入的定义就很容易理解了。ide

//定义变量
private T x ;
//做为返回值
public T getX(){
return x ;
}
//做为参数
public void setX(T x){
this.x = x ;
}
(3)使用泛型类
下面是泛型类的用法:
//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());
首先,是构造一个实例:
Point<String> p = new Point<String>() ;
这里与普通构造类实例的不一样之点在于,普通类构造函数是这样的:Point p = new Point() ;
而泛型类的构造则须要在类名后添加上<String>,即一对尖括号,中间写上要传入的类型。
由于咱们构造时,是这样的:class Point<T>,因此在使用的时候也要在Point后加上类型来定义T表明的意义。
而后在getVar()和setVar()时就没有什么特殊的了,直接调用便可。
从上面的使用时,明显能够看出泛型的做用,在构造泛型类的实例的时候:
//IntegerPoint使用
Point<Integer> p = new Point<Integer>() ;
//FloatPoint使用
Point<Float> p = new Point<Float>() ;
尖括号中,你传进去的是什么,T就表明什么类型。这就是泛型的最大做用,咱们只须要考虑逻辑实现,就能拿给各类类来用。
前面咱们提到ArrayList也是泛型,咱们顺便它的实现:
public class ArrayList<E>{
…………
}
看到了吧,跟咱们的Point实现是同样的,这也就是为何ArrayList可以盛装各类类型的主要缘由。
(4)使用泛型实现的优点
相比咱们开篇时使用Object的方式,有两个优势:
(1)、不用强制转换
//使用Object做为返回值,要强制转换成指定类型
Float floatX = (Float)floatPoint.getX();
//使用泛型时,不用强制转换,直接出来就是String
System.out.println(p.getVar());
(2)、在settVar()时若是传入类型不对,编译时会报错函数

能够看到,当咱们构造时使用的是String,而在setVar时,传进去Integer类型时,就会报错。而不是像Object实现方式同样,在运行时才会报强制转换错误。this

二、多泛型变量定义及字母规范
(1)、多泛型变量定义
上在咱们只定义了一个泛型变量T,那若是咱们须要传进去多个泛型要怎么办呢?
只须要在相似下面这样就能够了:
class MorePoint<T,U>{
}
也就是在原来的T后面用逗号隔开,写上其它的任意大写字母便可。想加几个就加几个,好比咱们想加五个泛型变量,那应该是这样的:
class MorePoint<T,U,A,B,C>{
}
举个粟子,咱们在Point上再另加一个字段name,也用泛型来表示,那要怎么作?代码以下:
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());
从上面的代码中,能够明显看出,就是在新添加的泛型变量U用法与T是同样的。
(2)、字母规范
在定义泛型类时,咱们已经提到用于指定泛型的变量是一个大写字母:
class Point<T>{
…………
}
固然不是的!!!!任意一个大写字母均可以。他们的意义是彻底相同的,但为了提升可读性,你们仍是用有意义的字母比较好,通常来说,在不一样的情境下使用的字母意义以下:
 E — Element,经常使用在java Collection里,如:List<E>,Iterator<E>,Set<E>
 K,V — Key,Value,表明Map的键值对
 N — Number,数字
 T — Type,类型,如String,Integer等等
若是这些还不够用,那就本身随便取吧,反正26个英文字母呢。
再重复一遍,使用哪一个字母是没有特定意义的!只是为了提升可读性!!!!
三、泛型接口定义及使用
在接口上定义泛型与在类中定义泛型是同样的,代码以下:.net

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的定义:
class InfoImpl implements Info<String>{
…………
}
要清楚的一点是InfoImpl不是一个泛型类!由于他类名后没有<T>!
而后在在这里咱们将Info<String>中的泛型变量T定义填充为了String类型。因此在重写时setVar()和getVar()时,IDE会也咱们直接生成String类型的重写函数。
最后在使用时,没什么难度,传进去String类型的字符串来构造InfoImpl实例,而后调用它的函数便可。
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl i = new InfoImpl("harvic");
System.out.println(i.getVar()) ;
}
};
(2)、使用方法二:泛型类
在方法一中,咱们在类中直接把Info<T>接口给填充好了,但咱们的类,是能够构形成泛型类的,那咱们利用泛型类来构造填充泛型接口会是怎样呢?blog

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()) ;
}
};
最关键的是构造泛型类的过程:
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 ;
}
}
在这个类中,咱们构造了一个泛型类InfoImpl<T>,而后把泛型变量T传给了Info<T>,这说明接口和泛型类使用的都是同一个泛型变量。
而后在使用时,就是构造一个泛型类的实例的过程,使用过程也不变。
public class GenericsDemo24{
public static void main(String arsg[]){
Info<String> i = new InfoImpl<String>("harvic");
System.out.println(i.getVar()) ;
}
};
使用泛型类来继承泛型接口的做用就是让用户来定义接口所使用的变量类型,而不是像方法一那样,在类中写死。
那咱们稍微加深点难度,构造一个多个泛型变量的类,并继承自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 ;
}
}
在这个例子中,咱们在泛型类中定义三个泛型变量T,K,U而且把第三个泛型变量U用来填充接口Info。因此在这个例子中Info所使用的类型就是由U来决定的。
使用时是这样的:泛型类的基本用法,再也不多讲,代码以下:
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");
System.out.println(i.getVar()) ;
}
}
四、泛型函数定义及使用
上面咱们讲解了类和接口的泛型使用,下面咱们再说说,怎么单独在一个函数里使用泛型。好比咱们在新建一个普通的类StaticFans,而后在其中定义了两个泛型函数:
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());
}
}
上面分别是静态泛型函数和常规泛型函数的定义方法,与以往方法的惟一不一样点就是在返回值前加上<T>来表示泛型变量。其它没什么区别。
使用方法以下:
//静态方法
StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二

//常规方法
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
结果以下:继承

首先,咱们看静态泛型函数的使用方法:接口

StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二
从结果中咱们能够看到,这两种方法的结果是彻底同样的,但他们还有些区别的,区别以下:
方法一,能够像普通方法同样,直接传值,任何值均可以(但必须是派生自Object类的类型,好比String,Integer等),函数会在内部根据传进去的参数来识别当前T的类别。但尽可能不要使用这种隐式的传递方式,代码不利于阅读和维护。由于从外观根本看不出来你调用的是一个泛型函数。
方法二,与方法一不一样的地方在于,在调用方法前加了一个<String>来指定传给<T>的值,若是加了这个<String>来指定参数的值的话,那StaticMethod()函数里全部用到的T类型也就是强制指定了是String类型。这是咱们建议使用的方式。
一样,常规泛型函数的使用也有这两种方式:
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
能够看到,与日常同样,先建立类的实例,而后调用泛型函数。
方法一,隐式传递了T的类型,与上面同样,不建议这么作。
方法二,显示将T赋值为Integer类型,这样OtherMethod(T a)传递过来的参数若是不是Integer那么编译器就会报错。

进阶:返回值中存在泛型
上面咱们的函数中,返回值都是void,但现实中不可能都是void,有时,咱们须要将泛型变量返回,好比下面这个函数:
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
函数返回值是List<T>类型。至于传入参数Class<T> object的意义,咱们下面会讲。这里也就是想经过这个例子来告诉你们,泛型变量其实跟String,Integer,Double等等的类的使用上没有任何区别,T只是一个符号,能够表明String,Integer,Double……这些类的符号,在泛型函数使用时,直接把T看到String,Integer,Double……中的任一个来写代码就能够了。惟一不一样的是,要在函数定义的中在返回值前加上<T>标识泛型;
五、其它用法:Class<T>类传递及泛型数组
(1)、使用Class<T>传递泛型类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<SuccessModel>的数组。
public class SuccessModel {
private boolean success;

public boolean isSuccess() {
return success;
}

public void setSuccess(boolean success) {
this.success = success;
}
}
那如今,咱们把下面这句组装成一个泛型函数要怎么来作呢?
public static List<SuccessModel> parseArray(String response){
List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);
return modelList;
}
首先,咱们应该把SuccessModel单独抽出来作为泛型变量,但parseArray()中用到的SuccessModel.class要怎么弄呢?
先来看代码:
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
注意到,咱们用的Class<T> object来传递类的class对象,即咱们上面提到的SuccessModel.class。
这是由于Class<T>也是一泛型,它是传来用来装载类的class对象的,它的定义以下:
public final class Class<T> implements Serializable {
…………
}
经过Class<T>来加载泛型的Class对象的问题就讲完了,下面来看看泛型数组的使用方法吧。
(2)、定义泛型数组
在写程序时,你们可能会遇到相似String[] list = new String[8];的需求,这里能够定义String数组,固然咱们也能够定义泛型数组,泛型数组的定义方法为 T[],与String[]是一致的,下面看看用法:
//定义
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) ;
}
咱们先看看 定义时的代码:
public static <T> T[] fun1(T...arg){ // 接收可变参数
return arg ; // 返回泛型数组
}
首先,定义了一个静态函数,而后定义返回值为T[],参数为接收的T类型的可变长参数。若是有同窗对T...arg的用法不了解,能够去找下JAVA 可变长参数方面的知识。
因为可变长参数在输入后,会保存在arg这个数组中,因此,咱们直接把数组返回便可。

好了,这篇到这里就结束了,这篇中主要讲述了泛型在各方面的定义及用法,下篇,咱们将讲述,有关泛型限定相关的知识。

若是本文有帮到你,记得加关注哦

本文所涉及源码下载地址:http://download.csdn.net/detail/harvic880925/9275047

请你们尊重原创者版权,转载请标明出处哦: http://blog.csdn.net/harvic880925/article/details/49872903 谢谢。

相关文章
相关标签/搜索