文/JamFF
01
周末帮人做一个Android小应用,没有使用ButterKnife
或者DataBinding
,老老实实的findViewByById
,竟然提示是个多余的操作。
赶紧点进去看看,果然是通过泛型实现的。
@SuppressWarnings("TypeParameterUnusedInFormals")
@Override
public <T extends View> T findViewById(@IdRes int id) {
return getDelegate().findViewById(id);
}
我这里使用的是appcompat-v7-26.1.0
下的AppCompatActivity
,
再看下API 26
中的android.app.Activity
也是用了泛型。
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
API 25
中是这个样子,
@Nullable
public View findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
想到很久以前没有ButterKnife
和DataBinding
时,自己一直都是这么做的,
public <T extends View> T $(int id) {
return (T) findViewById(id);
}
当时也有很多像Afinal
和xUtils
通过运行时注解的方式去解决,但是由于反射影响效率,之后就没有用过。
02
话说回来,这里面泛型是什么意思呢?
上面是泛型方法的一种使用,
在修饰符后返回值前,定义一个继承View的泛型T,当然也可以不继承View,继承的目的有两个,
一是,增加限定,只允许View及其子类接收findViewById
返回的结果;
二是,方便使用,直接拥有View的方法,例如下面写法,可以少定义一个变量。
findViewById(R.id.btn).setOnClickListener(this);
再举一个例子,
public <T> void print(T t) {
System.out.println(t);
}
调用该方法时,参数就可以是任意类型了,省去了重载的方法。
这里可能有人说,用Object也一样啊,再看下面一个例子,
public void test() {
String str = test1("object");// 返回为String
String obj = (String) test2("object");// 返回为Object
}
private <E> E test1(E t) {
return t;
}
private Object test2(Object t) {
return t;
}
由此可见,使用泛型的两个好处,
第一,方便,不需要强转,使用Object需要强转;
第二,安全,使用Object强转时,可能出会出现ClassCastException
。
那么问题来了,使用泛型,将一个Button
类型让ImageView
接收,结果会怎么样呢?
ImageView iv = findViewById(R.id.btn);
首先Android Studio很感人啊,直接给出提示,注意这不是泛型的提示,而是IDE给出的。
当然如果你不管它,直接运行,还是会出现了
ClassCastException
异常的。
03
说到泛型,就必须提Java两个泛型的坑
-
泛型的类型擦除
简单的理解就是,类型参数只存在于编译期,在运行时,JVM并不知道泛型的存在。例1:
Class c1 = new ArrayList<Integer>().getClass(); Class c2 = new ArrayList<String>().getClass(); System.out.println(c1 == c2);
这个打印结果是
true
原因就是类型擦除,编译器生成的字节码,在运行期间并不包含泛型的类型信息,运行时两个都是ArrayList
类型。例2:
private <T> T cast(Class<T> clazz, Object obj) { T t = (T) obj; return t; }
调用
Long aLong= cast(Long.class, 4);// 运行报错,不能将Integer转换为Long
第一,在cast方法中
T t = (T) obj;
运行不会报错,因为在运行期间,泛型擦除,相当于Object t = (Object ) obj;
并没有任何作用。
第二,针对上面例子,cast方法不会将Integer强转为Long,返回值的t就是Integer类型,所以将返回值赋给Long类型时,会运行报错;
第三,那么将返回值赋值给Integer,就可以了吗?当然不是,由于泛型的限制,编译时要求返回值为T类型,也就是Long,如果返回Integer,编译期间都不会通过。Integer integer = cast(Long.class, 4);// 编译报错,返回值为泛型,应该是Long
所以,唯一的办法就是先用Object接收,然后再使用Integer强转。
Object cast = cast(Long.class, 4); Integer integer = (Integer) cast;
-
泛型类的定义会影响泛型方法
例:public class Test<V> { <T> T value(T t) { return t; } } private void fun() { Test test1 = new Test(); // 只能推断为Object Object o = test1.value(0.1); Test<String> test2 = new Test<>(); // 可以推断为Double Double aDouble = test2.value(0.1); }
如果泛型类中定义的泛型没有被使用,与泛型方法同时使用时,并且该泛型方法不是静态方法时,就会出现上面的问题,这个在Java语言规范中有写到。
To facilitate interfacing with non-generic legacy code, it is possible to use as a type the erasure (§4.6) of a parameterized type (§4.5) or the erasure of an array type (§10.1) whose element type is a parameterized type. Such a type is called a raw type.
More precisely, a raw type is defined to be one of:
The reference type that is formed by taking the name of a generic type declaration without an accompanying type argument list.
引用类型是通过使用泛型类型声明的名称,而没有实际的类型参数列表来形成的。(说白了就是定义了泛型T,在调用时却没有使用)An array type whose element type is a raw type.
一个数组类型,其元素类型为raw type。A non-
static
member type of a raw type R that is not inherited from a superclass or superinterface of R.
一种非静态类型的原始类型R,并且它没有继承父类或实现接口。
上面就是简单的介绍一些泛型的优缺点,在日后设计代码的时,可以利用泛型完善代码,简化代码,增加扩展性及安全性。
我是JamFF,希望今天的文章对你有帮助。
END.