通常,注解,泛型,反射,在普通的开发中, 我们只是在用,但谈及怎么去实现,有些朋友可能还不太清楚,因为这些技术在程序开发的日常中,用不到。
但是这些技术又会给我们的开发带来很多的便利,所以我觉得应该了解一些。
今天就结合着模板设计模式来讲一下泛型在哪用,怎么用,以及哪些要注意的地方。
首先谈一谈我对模板模式的理解。
在一个系列的行为中,有一些是确定的,有一些是不明确的,我们把确定的行为定义在一个抽象类中,不确定的行为定义为抽象方法,由具体的子类去实现,这种不影响整个流程,但可以应对各种情况的方法就可以称之为模板模式。
比如说:我在前面文章提到的通用Adapter的设计,
由adapter的用法可分析到这样一个流程:
- 要有一组数据源,这个数据源可以确定是一个List但不知道具体的对象是什么
- getItem()这个只要有了数据源,就可以得到,
- getCount()只要有了数据源,就可以得到,
- getView()中,要什么样的view不确定,要怎么样填数据不确定
- 但是如何复用convertView是可以确定的,这个复用又牵涉到viewholder的问题,要做成通用,
- 我们没有办法去知道使用者会定义一个什么样的viewholder,所以必须强加一个约束。
对于不明确的对象,我们可以用泛型来代表
对于不明确的行为,我们要用抽象方法来隔离。比如:要什么样的view不确定,要怎么样填数据不确定
对于不确定的viewHolder,我们应该定义一个父类的规则,让子类去遵守,并用泛型去代表变化量
所以代码就成了这样:
public abstract class LBaseAdapter extends BaseAdapter {
private Context context;
private List dataSource = new ArrayList<>(); //初始化一个防止getCount()空指针
public LBaseAdapter(Context context) {
this.context = context;
}
public Context getContext() {
return context;
}
//替换原有数据源
public void setDataSource(List dataSource) {
setDataSource(dataSource,true);
}
//如果isClear==true,则替换原有数据源,否则加到数据源后面
public void setDataSource(List dataSource, boolean isClear) {
if (isClear) this.dataSource.clear();
this.dataSource = dataSource;
notifyDataSetChanged();
}
//只加一个数据
public void addData(E data) {
this.dataSource.add(data);
notifyDataSetChanged();
}
//通过下标移除一条数据
public void removeData(int position) {
this.dataSource.remove(position);
notifyDataSetChanged();
}
//通过对象移除一条数据
public void removeData(E data) {
this.dataSource.remove(data);
notifyDataSetChanged();
}
@Override
public int getCount() {
return this.dataSource.size();
}
@Override
public E getItem(int position) {
return this.dataSource.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
V viewHolder = null;
if (convertView == null) {
viewHolder = createViewHolder(position, parent);
if (viewHolder == null || viewHolder.getRootView() == null) {
throw new NullPointerException("createViewHolder不能返回null或view为null的实例");
}
convertView = viewHolder.getRootView();
convertView.setTag(viewHolder);
}else{
viewHolder = (V) convertView.getTag();
}
//给当前复用的holder一个正确的position
viewHolder.setPosition(position);
bindViewHolder(viewHolder,position,getItem(position));
return viewHolder.getRootView();
}
protected abstract V createViewHolder(int position, ViewGroup parent);
protected abstract void bindViewHolder(V holder,int position, E data);
public static class BaseViewHolder {
private View rootView;
private SparseArray viewCache = new SparseArray<>();
private int position = -1;
public View getRootView() {
return rootView;
}
void setPosition(int position) {
this.position = position;
}
public int getPosition() {
return position;
}
public BaseViewHolder(View rootView) {
this.rootView = rootView;
}
public R getView(@IdRes int viewID) {
View cachedView = viewCache.get(viewID);
if(null == cachedView) {
cachedView = rootView.findViewById(viewID);
viewCache.put(viewID, cachedView);
}
return (R) cachedView;
}
}
}
所有的公用行为以及常用行为都抽到一个父类中,
利用泛型解决了不明确类型的问题,
得用抽象方法解决了不明确行为的问题
这样对于子类来说,它只用关心到自己的逻辑,也就是,需要的实体,要创建的view,以及实体要如何绑定数据,
代码就不帖过来了,因为这篇我要讲的是泛形。
在比如,在安卓开发中, 我们常常要请求接口以获取数据,且通常会定义一个公用的返回格式,举个例子:
{
"success":true,
"error":"",
"data":{
"xx":"xx
}
}
{
"success":true,
"error":"",
"data":[{
"xx":"xx
}]
}
对于单数据,data会返回一个json对象,对于一个列表数据,data会代表一个json数组,这种情况下,如果把返回json转成一个类实例,应该怎么写呢?不用泛型的话:
//单个对象情况:
public class UserLoginBean {
public boolean success;
public String error;
public User data;
public static class User{
public String userName;
public String userId;
public String userNickName;
}
}
//多个对象情况:
public class ArticleListBean {
public boolean success;
public String error;
public List data;
public static class Article{
public String articleTitle;
public String articleContent;
public String articleCreateTime;
}
}
这样写可以,但有这么几个问题
1.对于懒癌晚期的我,不能忍受写这么多重复代码。
2.如果Article有只获取一篇文章的情况,那么又要复制过去再重写一个ArticleBean?
3.如果项目二期,或者后期,经过开会商量返回值里面的error不好,要改个名字,或者加几个字段,
比如一次请求的总条数,那么很悲剧。你要改掉所有的bean.
针对这种情况:
public class BaseRet {
public boolean success;
public String error;
public T data;
}
String json = "xxxx后台返回的json";
BaseRet ret = new Gson().fromJson(json, new TypeToken>() {
}.getType());
if (ret != null) {
if (ret.success) {
User user = ret.data;
// 填值
} else {
String error = ret.error;
// Toast 显示error
}
}
String json = "xxxx后台返回的json";
BaseRet> ret = new Gson().fromJson(json, new TypeToken>>() {
}.getType());
if (ret != null) {
if (ret.success) {
List user = ret.data;
// 填值
} else {
String error = ret.error;
// Toast 显示error
}
}
}
我为了演示方便,没有写getters和setters,这种写法并不好,应该这样
public class BaseRet {
public boolean success;
public String error;
public T data;
public boolean isSuccess() {
return success;
}
public String getError() {
return error;
}
public T getData() {
return data;
}
}
为什么只提供了getters,这是为了应对前面所说的,如果这个返回格式改了一个名字,我们可以这样应对,
比如:error改成msg
public class BaseRet {
public boolean success;
public String msg;
public T data;
public boolean isSuccess() {
return success;
}
public String getError() {
return msg;
}
public T getData() {
return data;
}
}
如果你之前的代码都用的isSuccess(),getError(),则不需要更改。当然这也是getters存在的意义,如果不存在这种情况,直接用public的属性,不通过方法的活,效率会更好一点。
以上是一点经验之谈,也是简单的模板模式的应用,对于这种情况,变化的是data的类型,用泛型解决就可。
泛型分为声明和使用,两步,
比如一个方法,我们经常遇到TextView txt = (TextView)findViewById(R.id.txt):
public static R findView(Activity v, @IdRes int viewId) {
return (R) v.findViewById(viewId);
}
//用法
TextView txt = findView(this, R.id.txt);
ImageView img = findView(this, R.id.img);
findView方法中,声明了一个泛型
, 并指定返回值为R , 编译器会在编译时检测调用时要的类型,从而把R替换成具体的类型,所以
TextView txt = findView(this, R.id.txt);
这时声明的R就被替换成了TextView,最终的结果就成了
TextView txt = (TextView)activity.findViewById(viewId);
在举一个例子,
我要实现一个把一种数据转成另一种数据的方法,
public static List map(List param) {
//声明一个R做为返回值,一个T做为参数,
//但是到这里的时候,我进行不下去了,因为我什么都不知道,对我来说,T,R都是未知的,我也同样不知道要抽什么属性到一个数组里面去,但是我知道怎么去循环param,
List list = new ArrayList();
for(T entity : param) {
String result = 某种转换之后
list.add(result);
}
return list;
}
有了不确定的行为了,就需要抽象来帮忙,这个具体的行为只有使用者结合他的业务才知道,所以我抽象一个接口:
这个接口在类上做了两个声明,一个R做为返回值的代表,一个T做为传入值的代表
public interface Func {
public R apply(T t);
}
于是可以:
public static List mapToList(Func func,List t) {
List list = new ArrayList<>();
for(T entity : t) {
list.add(func.apply(entity));
}
return list;
}
怎么用呢
User user = new User("张三",22);
User user1 = new User("李四",22);
User user2 = new User("王五",22);
User user3 = new User("赵六",22);
List asList = Arrays.asList(user,user1,user2,user3);
List names = mapToList(new Func() {
@Override
public String apply(User t) {
return t.name;
}
},asList);
System.out.println(names);
//lambda
names = mapToList(it->it.name, asList);
System.out.println(names);
[张三, 李四, 王五, 赵六]
剩下的还有边界的问题,还是举adapter的那个例子吧,
我需要一个子类的ViewHolder,但是我又需要子类的ViewHolder能有一个我知道的规范,比如提供view的方法,这样我才能在getView里面setTag做常规的复用逻辑,这种情况下,我需要强制子类继承我定的规范,所以我写了一个BaseViewHolder,但是怎么做约束呢?
public abstract class LBaseAdapter extends BaseAdapter
这里面写到了 V extends LBaseAdapter.BaseViewHolder , 这是泛型里面做限定的一种写法,意味着 V这个类型必须是BaseViewHolder的子类,如果使用者传了一个不相干的类型,则开发工具会报错。
泛型的另一种好处可能就是运行时类型检查和去除强转吧,这个不说了,用的很多:
List
list.add(1) ; //会报错
String str = get(0);//正确
int object = get(0);会报错
java的泛型比如弱,而且简单,是一种基于jvm的伪泛型,所以能做的事情并不多,
但是如果结合了反射,会灵活一些,也麻烦些,这个在后面的文章中我会继续讲述。
如果这篇文章能给你带来新的认识,希望你能帮我顶起来,让更多的人看到
如果我的理解有误,或者用法不当,恳请指出