从findViewById中看泛型的使用

从findViewById中看泛型的使用_第1张图片
图片来自网络

文/JamFF

01

周末帮人做一个Android小应用,没有使用ButterKnife或者DataBinding,老老实实的findViewByById,竟然提示是个多余的操作。

从findViewById中看泛型的使用_第2张图片

赶紧点进去看看,果然是通过泛型实现的。

@SuppressWarnings("TypeParameterUnusedInFormals")
@Override
public  T findViewById(@IdRes int id) {
    return getDelegate().findViewById(id);
}

我这里使用的是appcompat-v7-26.1.0下的AppCompatActivity
再看下API 26中的android.app.Activity也是用了泛型。

@Nullable
public  T findViewById(@IdRes int id) {
    return getWindow().findViewById(id);
}

API 25中是这个样子,

@Nullable
public View findViewById(@IdRes int id) {
    return getWindow().findViewById(id);
}

想到很久以前没有ButterKnifeDataBinding时,自己一直都是这么做的,

public  T $(int id) {
    return (T) findViewById(id);
}

当时也有很多像AfinalxUtils通过运行时注解的方式去解决,但是由于反射影响效率,之后就没有用过。

02

话说回来,这里面泛型是什么意思呢?

上面是泛型方法的一种使用,
在修饰符后返回值前,定义一个继承View的泛型T,当然也可以不继承View,继承的目的有两个,

一是,增加限定,只允许View及其子类接收findViewById返回的结果;
二是,方便使用,直接拥有View的方法,例如下面写法,可以少定义一个变量。

findViewById(R.id.btn).setOnClickListener(this);

再举一个例子,

public  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 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().getClass();
    Class c2 = new ArrayList().getClass(); 
    System.out.println(c1 == c2);
    

    这个打印结果是true
    原因就是类型擦除,编译器生成的字节码,在运行期间并不包含泛型的类型信息,运行时两个都是ArrayList类型。

    例2:

    private   T cast(Class 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 {
         T value(T t) {
            return t;
        }
    }
    
    private void fun() {
        Test test1 = new Test();
        // 只能推断为Object
        Object o = test1.value(0.1);
    
        Test test2 = new Test<>();
        // 可以推断为Double
        Double aDouble = test2.value(0.1);
    }
    

如果泛型类中定义的泛型没有被使用,与泛型方法同时使用时,并且该泛型方法不是静态方法时,就会出现上面的问题,这个在Java语言规范中有写到。

4.8. Raw Types

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.


你可能感兴趣的:(从findViewById中看泛型的使用)