前言
为什么要总结泛型的使用?泛型在项目中是如何体现价值的?不多说,总结一波。从实践中到理论,最后回归泛型本质。
1. 什么是泛型?为什么要用泛型?
定义:
泛型:就是“宽泛的数据类型”,任意的数据类型。
作用:
- 泛型可以解决数据类型的安全问题,它的主要原理是:在类声明的时候通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型
- 在开发中常用用于代码的抽象和封装,使其工具化,通用化。可有效降低代码的冗余,使代码的可读性更高。
在 ArrayList
源码中,可以发现泛型的使用到处存在。
public class ArrayList<E> extends AbstractList<E>
public boolean addAll(int index, Collection<? extends E> c) {rangeCheckForAdd(index);
boolean modified = false;
for (E e : c) {add(index++, e);
modified = true;
}
return modified;
}
注意点:
-
泛型通常用 <> 和一个大写字母表示,而一般常用的就是
<T>
、<?>
、<? extends Object>
、<? super Object>
四种方式。- 其中,
T
表示同一种类型,?
表示任意类型,<? extends XX>
表示 xx 类型,和 xx 类型的子<? super XX >
表示 xx 类型,和 xx 类型的父类型。 -
class<T>
:实例化的时候,需要指定具体类型。class<?>
: 可以表示所有类型。
- 其中,
- 不建议使用
<G>
、<Q>
这种奇葩定义方式。 - 泛型不能使用基本数据类型,如 int,double 等,只能使用它们的容器类如
Integer
、Double
。因为 Java 的泛型是用类型擦除实现的,在运行阶段用的都是Object
,而基本数据类型不是继承自Object
,也就无法在泛型中使用。 -
Kotlin 中的
out
和in
和 Java 泛型一样,Kolin 中的泛型本身也是不可变的。
- 使用关键字
out
来支持协变,等同于 Java 中的上界通配符? extends
。Foo<? extends Bar>
对应Foo<out Bar!>!
- 使用关键字
in
来支持逆变,等同于 Java 中的下界通配符? super
。Foo<? supter Bar >
对应Foo<in Bar!>!
- 使用关键字
2.Android 中的使用场景
2.1 findViewById 的使用变化
/**
* FindViewById 的泛型封装,减少强转代码
*/
public <T extends View> T findViewById(@IdRes int id) {return getDelegate().findViewById(id);
}
// 使用前:Button btnReload =(Button) findViewById(R.id.btn_reload);
// 现在:Button btnReload = findViewById(R.id.btn_reload);
经历了 Android 版本的不停迭代,从最开始必须强制转换,到后来内部已经实现了泛型的封装就可以不用再强转。当然,现在流行 Kotlin 后这一步都可以直接省略了。
2.2 BaseAdapter 实现封装的 Adapter
在 ListView
使用盛行的时候,我们一般会类似这样封装一个 Adapter。
public abstract class CommonAdapter<T> extends BaseAdapter {
protected LayoutInflater mLayoutInflater;
protected List<T> mDatas;
protected Context mContext;
public CommonAdapter(Context mContext) {this.mContext = mContext;}
public CommonAdapter(List<T> mDatas, Context mContext) {
this.mDatas = mDatas;
this.mContext = mContext;
}
public CommonAdapter(LayoutInflater mLayoutInflater, List<T> mDatas, Context mContext) {
this.mLayoutInflater = mLayoutInflater;
this.mDatas = mDatas;
this.mContext = mContext;
}
@Override
public int getCount() {return mDatas.size();
}
@Override
public T getItem(int position) {return mDatas.get(position);
}
@Override
public long getItemId(int position) {return position;}
@Override
public abstract View getView(int position, View convertView, ViewGroup parent);
}
在 RecycleView
出世的时候,我们可以这样用泛型,具体可以看看这篇文章中有关 RecycleView
使用的总结,点击前往
Adapter 通常都需要接受一个数据或者集合, 但是数据类型却是不确定的, 因而像这样因为要接受宽泛的数据类型的场景使用泛型是很自然而然的事情. 通用性就是泛型的一大好处。
2.3 网络请求数据
开发中,网络请求数据时返回的 json 数据一般长这样的,比如返回一个用户信息:
"code": 0,
"message": "success",
"data": {
"token": "pbGUiLCJuYmYiOjE1NzI4NTg0NjIsInN1YiI6IjMxNDQifQ.YJ2A1wl2Jo9hbyRfkMlthoMkhfuKtlZh0vrkgi-rPpw",
"uid": "a41b3bb3e6b050b6c915",
"phone": "13908213909",
"name": "小羊子说",
"shopName": "成都银泰城",
"authTrueName": 1
}
}
或者返回一个分组信息:
"code": 0,
"message": "成功",
"data": {
"groups": [
{
"name": "我爱你中国",
"count": 12,
"groupId": 116
},
{
"name": "富二代",
"count": 1,
"groupId": 97
},
{
"name": "高富帅",
"count": 2,
"groupId": 96
},
{
"name": "未定义的分组",
"count": 21886,
"groupId": -1
}
],
"linkman": "大贵贵"
}
}
其中 code
、message
、data
三个字段中的 key
值是不变的,可以理解为共同的基础数据不变,不管服务器返回什么数据,这些都是不变的。
只有 data
里面的数据是不同的,可以理解为需求数据不同。
找到共同的返回信息后,我们就可以开始泛化封装了。BaseBean
就是对基础数据进行封装处理。
首先封装通用的BaseBean
:
public class BaseBean<T> {
public int code;
public String message;
public T data;
@Override
public String toString() {
return "code:" + code +
"\nmessage:" + message +
"\ndata:" + data;
}
}
在使用时就方便多了。
// 如检测版本信息中的实现类
public class VersionBean extends BaseBean<VersionBean.DataBean> {
public static class DataBean {
public String appname; //App 应用名
public String detailhtml; // 更新详情
public String downloadurl;// 渠道下载地址
public String downloadurl_common; // 通用下载地址 备用
public String detail; // 更新详情
public String updatetime;
public String version; // 版本名 如版本“1.1.2”public int versionCode;// 版本号 每次升级
public int isforceupdate; // 是否强制升级 如 0 正常升级 1 强制升级
}
}
// 如多个关键字返回的列表中的实体类
public class KeywordBean extends BaseBean<List<KeywordBean.KeyWord>> {
public static class KeyWord {
public int id;
public String title;
}
}
同时结合 RxJava+Retrofit
请求接口修改返回的数据类型:
@GET("getAppVersion.php")
Observable<VersionBean> checkVersion(@Query("deviceType") int deviceType,
@Query("channel") String channel,
@Query("versionCode") int versionCode);
@GET("keyWords.php")
Observable<KeywordBean> keyWord();
网络请求中的泛型简单示例就先介绍到这里,具体使用可以参考我之前的一篇总结,点击前往
2.4 其他应用场景
比如开发中采用了 MVP 架构,也会用到。熟悉运用泛型尤为重要。
如何使用泛型和抽象优化 MVP 的结构就变成了我们用好 MVP 的关键了。当然,对于这个问题我们可以通过泛型参数、抽象父类的方式,将一些公用的 Model 及 Presenter 抽象出来。这应该就是使用 MVP 架构的精髓了。
3. 总结
泛型的使用 在开发运用十分广泛,后期会不断更新。
扩展阅读:
1. 码上开学 -Kotlin 的泛型
2.Google 官方 MVP 架构解析