从findViewById中看泛型的使用

作者: JamFF | 来源:发表于2018-02-05 08:02 被阅读67次
图片来自网络

文/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);
}

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

public <T extends View> T $(int id) {
    return (T) findViewById(id);
}

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

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语言规范中有写到。

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中看泛型的使用

    文/JamFF 01 周末帮人做一个Android小应用,没有使用ButterKnife或者DataBinding...

  • 适配

    1.andpermission 2.使用泛型findviewbyid 3.greendao

  • 泛型——Dart(五)

    泛型 从字面意思理解,就是广泛的类型,我们可以在集合中看到泛型的影子: 为什么要有泛型? 以集合为例,假如没有泛型...

  • Java-API-集合框架(三)-泛型

    泛型的由来和基本使用 泛型的擦除 泛型类的使用 泛型方法的使用 泛型接口 泛型通配符(?) 通配符? 在api中的...

  • RxJava-泛型详解及手写实现3

    一、为什么使用泛型? 架构中看到泛型是最多的,为什么要用泛型呢?1.运行时不确定类型;2.类型安全;3.消除强制转...

  • 夯实JAVA基础之 - 泛型

    泛型的定义及使用 1. 定义泛型: 2. 类中使用泛型 3. 使用泛型类 4. 使用泛型的优势? 多泛型变量的定义...

  • Java反射(三)泛型

    一、泛型和Class类从JDK 1.5 后,Java中引入泛型机制,Class类也增加了泛型功能,从而允许使用泛型...

  • Java泛型

    本文介绍的知识点 泛型是什么? 泛型的使用在反射中使用泛型在集合类中使用泛型 关于泛型擦除如何理解?如何避免泛型擦...

  • java基础-day13-泛型

    泛型 1. 泛型 1.1 为什么要使用泛型 1.2 泛型的基本格式 1.3 泛型在方法中使用 1.3 泛型在类内的...

  • 四 集合 ——第二节 泛型

    文章目录 1、 泛型概念2、 使用泛型的好处3、 泛型的定义与使用 3、1 定义和使用含有泛型的类3、2 含有泛型...

网友评论

    本文标题:从findViewById中看泛型的使用

    本文链接:https://www.haomeiwen.com/subject/oqjmzxtx.html