从findViewById中看泛型的使用

图片来自网络

文/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.


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容