概述
java的泛型是在java1.5才引入的,也就是说不是天生的,是后天加入的,为了兼容之前的代码,java采用了一种擦除的方式实现了泛型。也就是因为擦除的原因,所以java的泛型使用起来很别扭。
本来泛型的出现,是为了让我们的代码更加泛化一些,但是java的泛型不够泛化。
如果是用不变的类型参数T定义了泛型类或者泛型方法,在泛型代码里是不能用泛型参数调用方法的,当然Object的方法除外,类型参数被擦除到了Object,也就是说泛型代码里不知道类型参数代表的具体类型的。也就是说泛化了,可是功能受限了。
这个问题在C++中是不存在的,T可以调用你设计的方法,这个思想有个专业概念是潜在的类型机制(Latent typing)或者结构化的类型机制,还有个更通俗和流程的叫法是鸭子类型机制。
为了解决上面的问题,java中重载了extends关键字,让泛型类型参数extends具体的类或者接口,这样泛型代码中就可以通过类型参数T调用具体类或者接口中方法。但是这么做的话,就让泛化
的概念打折扣了,不那么泛化了,传入类型必须是特定类或其子类。有时候,这种情况的话,就没必要使用泛型了,直接用接口就可以了。
泛型从更高的层次来看,其实是实现了类或者方法和其中使用类型的解耦。这就就能做到泛化
擦除
在《Think in java》中一句对擦除的描述:“在泛型代码内部中,无法获得任何有关泛型参数类型的信息”。通过事实证明这个理解是错的,Gson TypeToken的实现就证明的,这句话是错的。
根据目前自己的有限认知,说一下自己的理解:如果是定义泛型类,泛型类中的泛型类型形参数是会被擦除的,擦除到第一边界;但是,如果是在定义的类的时候,父类泛型类传入了实参的话,我们在运行期还是能获取到泛型类型参数的。我们在理解擦除概念的时候,要小心,不是想当然的认为,所有泛型都运行期都消失了。
关于Gson的TypeToken【Java】java.lang.reflect.Type详解
特殊说明一下:中文表述不够准确,泛型类型这个词被用的很混乱,它有时表示C,因为类其实也可以表示一个类型,有时T也被叫做泛型类型,T其实是表示类型的一个变量。
原生类型和泛型类型
java为了做到向后兼容,泛型类型的变量是可以赋给原生类型变量的,比如List list = new ArrayList();
在实际开发中,也会遇到把原生类型变量赋给泛型类型变量,也是可以正常工作,而且很常见。
<K, V> Map<K, V> getMap() {
Map map = new HashMap();
return map;
}
//采用目标类型进行类型推断
Map<String , Integer> map = this.getMap();
泛型在继承过程中的使用
java泛型,要使用好,还是挺难的,别说会用,有时看懂都费劲。没办法只能多阅读别人的代码,强化自己的认识。在阅读mosby的时候,就对泛型在继承中的使用有了新的认识。
直接看源码分析吧
public abstract class MvpFragment<V extends MvpView, P extends MvpPresenter<V>> extends Fragment
implements MvpDelegateCallback<V, P>, MvpView {
protected FragmentMvpDelegate<V, P> mvpDelegate;
protected P presenter;
public abstract P createPresenter();
@NonNull protected FragmentMvpDelegate<V, P> getMvpDelegate() {
if (mvpDelegate == null) {
mvpDelegate = new FragmentMvpDelegateImpl<>(this, this, true, true);
}
return mvpDelegate;
}
@NonNull @Override public P getPresenter() {
return presenter;
}
@Override public void setPresenter(@NonNull P presenter) {
this.presenter = presenter;
}
@NonNull @Override public V getMvpView() {
return (V) this;
}
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getMvpDelegate().onViewCreated(view, savedInstanceState);
}
@Override public void onDestroyView() {
super.onDestroyView();
getMvpDelegate().onDestroyView();
}
........
}
public interface MvpPresenter<V extends MvpView> {
@UiThread
void attachView(@NonNull V view);
@UiThread
@Deprecated
void detachView(boolean retainInstance);
@UiThread
void detachView();
@UiThread
void destroy();
}
public interface MvpDelegateCallback<V extends MvpView, P extends MvpPresenter<V>> {
@NonNull P createPresenter();
P getPresenter();
void setPresenter(P presenter);
V getMvpView();
}
public interface MvpView {
}
初学泛型,看到MvpFragment的定义是不是头大,好复杂呀!其实,多理解多训练强化还是可以理解的。
MvpFragment实现了泛型接口MvpDelegateCallback,并且继承了泛型类型形参V和P,首先看看V和P在MvpDelegateCallback中的定义,V extends MvpView,P extends MvpPresenter,也就是V是MvpView或者子类就可以,并且这个V是能满足MvpPresenter泛型参数定义的,P要是MvpPresenter或者子类。
编译器会进行检查的,如果V改成extends View,MvpPresenter的V就会报错Type parameter 'V' is not within its bound; should implement 'com.hannesdorfmann.mosby3.mvp.MvpView'
,也就是说,虽然不知道V到底是啥,但是得满足MvpPresenter泛型参数定义的边界。MvpDelegateCallback定义了V和P的边界。
MvpFragment继承了MvpDelegateCallback的V和P,在MvpFragment中V和P也是需要有边界的,并且只能比MvpDelegateCallback小或相等,要不然编译器检查后会报错。MvpFragment定义了一样的边界,后面会看到小边界的定义。
我们再看看MvpFragment的定义,MvpFragment还实现了MvpView。我一开始看到这样的定义,就很蒙圈。MvpFragment本身是个MvpView,泛型V定义说明MvpFragment里面用到了MvpView,其实如果是这样也没啥,只是V在MvpFragment的使用是getMvpView返回MvpFragment自己。这样做是,MvpView还有子接口的时候,在扩展MvpFragment定义新类的时候,能够知道更精确的MvpView。语言很难描述情况,看下面MvpLceFragment和MvpLceView的定义就明白了。
public abstract class MvpLceFragment<CV extends View, M, V extends MvpLceView<M>, P extends MvpPresenter<V>>
extends MvpFragment<V, P> implements MvpLceView<M> {
protected View loadingView;
protected CV contentView;
protected TextView errorView;
@CallSuper @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
loadingView = createLoadingView(view);
contentView = createContentView(view);
errorView = createErrorView(view);
....
}
@Override public void showLoading(boolean pullToRefresh) {
if (!pullToRefresh) {
animateLoadingViewIn();
}
@Override public void showContent() {
animateContentViewIn();
}
@Override public void showError(Throwable e, boolean pullToRefresh) {
String errorMsg = getErrorMessage(e, pullToRefresh);
if (pullToRefresh) {
showLightError(errorMsg);
} else {
errorView.setText(errorMsg);
animateErrorViewIn();
}
}
@Override public void onDestroyView() {
super.onDestroyView();
loadingView = null;
contentView = null;
errorView.setOnClickListener(null);
errorView = null;
}
}
public interface MvpLceView<M> extends MvpView {
@UiThread
void showLoading(boolean pullToRefresh);
@UiThread
void showContent();
@UiThread
void showError(Throwable e, boolean pullToRefresh);
@UiThread
void setData(M data);
@UiThread
void loadData(boolean pullToRefresh);
}
在这里先说一个编程技巧,MvpView定义了一个接口,MvpFragment实现了MvpView接口,后来发现MvpView定义的功能不够用,所以又基于MvpView扩展了一个接口MvpLceView,但是又不想破坏MvpFragment的代码,因为被其它地方多次使用,我们可以像MvpLceFragment一样,继承MvpFragment实现MvpLceView接口就好了。
MvpLceView继承MvpView,同时增加了泛型类型定义M。MvpLceFragment继承MvpFragment实现MvpLceView,因为MvpFragment和MvpLceView的泛型都没有实参化,所以在MvpLceFragment中还得列出并定义。M没有边界,什么类型都行;V边界变小了,变成了MvpLceView,这个编译器是可以接受的;P的边界有些变化,V的边界小了,也是能接受的;同时MvpLceFragment还添加了自己的泛型类型参数CV。
我理解,编译器在检查泛型的时候,只要满足边界就行,因为它也只知道边界信息。
下面看看MvpLceFragment是怎么用的
public class CountriesFragment
extends MvpLceFragment<SwipeRefreshLayout, List<Country>, CountriesView, CountriesPresenter>
implements CountriesView, SwipeRefreshLayout.OnRefreshListener {
}
public interface CountriesView extends MvpLceView<List<Country>> {
}
public interface CountriesPresenter extends MvpPresenter<CountriesView>{
void loadCountries(final boolean pullToRefresh);
}
List泛型的转换
上面这样写会报错,如果这样写允许,那么就会导致能把C放到B的list中,实际运行取值发生类型转换的错误。为了避免这种错误,编译器直接禁止这样写代码
为了解决上面的问题,可以使用通配符的方式,但是得做出牺牲,禁止使用set方法,这样可以避免类型转换的错误。