2024年Android谈起泛型,大家都很佛性,flutter 界面

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

image

七大模块学习资料:如NDK模块开发、Android框架体系架构…

image

2021大厂面试真题:

image

只有系统,有方向的学习,才能在短时间内迅速提高自己的技术,只有不断地学习,不懈的努力才能拥有更好的技术,才能在互联网行业中立于不败之地。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

现在有测试类Test1th:

public class Test1th extends TestBase {

@Override

public String toString() {

return getClass().getSimpleName();

}

}

还有测试类Test2th:

public class Test2th extends TestBase {

@Override

public String toString() {

return getClass().getSimpleName();

}

}

在使用时,我们可以这样为泛型类Tested指定泛型参数类型。迭代如下:

public void startTest() {

Tested<Test1th, Test2th> ts = new Tested<>(new Test1th(), new Test2th());

for (TestBase t : ts) {

String str = t.toString();

}

}

泛型方法

是否拥有泛型方法,与其所在的类是否是泛型类是没有关系的。说白了就是泛型方法是可以独立于类的,而且泛型方法也能更清楚的表达意图。

此外,由于static的方法无法访问泛型类的泛型参数的,所以,如果static方法想拥有泛型能力,就必须使其成为泛型方法了。

还是举个栗子,上文在使用泛型类的时候,是将泛型参数列表放在类后的尖括号内,而使用泛型方法,是将泛型参数列表置于返回值之前(列表嘛,可支持多个),如下:

public class Tested {

public void startTest(T t) { }

}

泛型方法也可以和可变参数一起使用,来看下Arrays 底层的asList方法。

public static List asList(T… a) {

return new ArrayList<>(a);

}

如果没有理解,那在Tested 内,换种方式,是不是懂了:

public class Tested {

/**

  • 泛型方法与可变参数搭配

*/

public List asList(T… a) {

List list = new ArrayList<>();

for (T t : a)

list.add(t);

return list;

}

}

擦除与边界

说起泛型,不可避免的会说到擦除和边界,其实也很好理解。在说擦除之前,可以来看下这段代码,输出结果?

public class Tested {

public boolean startTest() {

Class c1 = new ArrayList().getClass();

Class c2 = new ArrayList().getClass();

return c1 == c2;

}

}

结果是true,为何?因为JAVA泛型就是使用擦除来实现的。在泛型代码内部,我们无法获得任何有关泛型参数类型的信息,任何具体的类型信息都被擦除了,我们唯一知道的就是在使用一个对象。

因此List< String >和List< Integer >在运行时事实上是相同类型,这两种形式都被擦除成它们的“原生”类型,即List。

再看如下代码Tested,由于T没有指明具体类型,所以这样写t.test() 是编译都无法通过的:

public class Tested {

private T t;

public void set(T t) {

this.t = t;

}

public T get() {

return t;

}

public void sT() {

t.test(); // err : 无法编译

}

}

现有测试类TestBase:

public class TestBase {

public void test() { }

}

为了调用test 方法,我们必须协助泛型类,给定泛型类的边界,以此告知编译器只能接受遵循这个边界的类型。这里使用extend 关键字来设定边界,来看下代码:

public class Tested {

private T t;

public void set(T t) {

this.t = t;

}

public T get() {

return t;

}

public void sT() {

t.test(); // suc : 编译成功

}

}

边界< T extends TestBase >声明T必须具有TestBase 类型或其子类型,这样就可以调用test 方法了。

而泛型的类型参数会被擦除到它的第一个边界,也就是我们的编译器会把类型参数替换为它的擦除边界类,就像上面示例,T被擦除到了TestBase,就好像在类的声明中用TestBase 替换了T 一样。

泛型类型只有在类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换成他们的非泛型上界。

例如:LIst< T >会被擦除为List,而普通的类型变量 T 会被擦除成Object。因为擦除移除了类型信息,所以,用无界的泛型参数调用的方法只是那些可以用Object调用的方法。

而如果T 不指定类型,T 是没有办法用于显示地引用运行时类型的操作的,所以new T(),obj instance T, (T) obj 这些都是错误的。举个栗子:

public class Tested {

private T t;

public void set(T t) {

this.t = t;

new T(); // 无法编译

}

public T get() {

return t;

}

}

虽然T 会被擦除成了 Object,但我们依然能给这个Object t对象 赋值一个 具体类型的对象E,随后将 t 强转成 E类型,可以参考下面的栗子:

public void startTest() {

Tested td = new Tested<>();

td.set(“test”);

String obj = td.get();

}

也许你会疑惑,既然Tested 在类型检查后,编译期将泛型 T 都擦除成了Object,为何 String obj = td.get();

能直接获取到 String 对象,而不是Object 对象呢?其实这是编译器帮我们在Class文件中自动插入了转型的代码,与下面的代码是没有区别的,因为编译后的Class文件是一样的。

public class Tested {

private Object t;

public void set(Object t) {

this.t = t;

}

public Object get() {

return t;

}

}

public void startTest() {

Tested td = new Tested();

td.set(“test”);

String obj = (String) td.get();

}

说了这么多,那为何JAVA 要引入擦除呢?主要是一个兼容性问题,怎么保证低版本或未使用泛型的类库能正常被使用的问题。

例如A使用了泛型,但是A使用的类库B无泛型,怎么办?这就要在实际的“通信”中,将A的泛型信息擦除,来保证“通信”的类库都是一致的。

擦除补偿

擦除丢失了在泛型代码中执行某些操作的能力。如上面说的对于 T 的new,instance,转型操作都是错误的。这就非常难受了,好好的泛型类(接口、方法)如果不能对 T 进行上述的操作,那还有啥用。好在有刚刚提到的 extend 关键字,能给泛型设定一个边界。

这里先不说 extend,除了extend 这些通配符,其实我们还能使用 类型标签 来对于擦除进行补偿。来看段代码:

public class Tested {

private Class tc;

public void set(Class tc) {

this.tc = tc;

}

public boolean isInstance(Object obj) {

// 判断obj是否能够转型成T

return tc.isInstance(obj);

}

}

如上面类内的Class 就是我们添加的类型标签,当然其他类同理。在这里就可以使用 tc 的 isInstance 方法了。下面是测试类:

public class TestBase { }

测试Test1th:

public class Test1th { }

测试Test2th:

public class Test2th extends TestBase { }

类比较简单,来测试下:

public void startTest() {

Tested td = new Tested<>();

td.set(TestBase.class);

boolean td1 = td.isInstance(new Test1th()); // td1 == false

boolean td2 = td.isInstance(new Test2th()); // td2 == true

}

能够发现,td1 = false, td2 = true,这说明Tested 内的泛型 T 即使被擦除成了 Object,但编译器依然会帮我们确保类型标签可以匹配泛型参数。这是很重要的属性

泛型数组

之所以将泛型数组放在后面,是因为泛型数组是擦除的受害者,讲泛型数组就离不开擦除。来看下代码:

public class Tested {

private T[] array;

@SuppressWarnings(“unchecked”)

public Tested(int size) {

array = (T[]) new Object[size];

}

public void set(int index, T item) {

array[index] = item;

}

public T[] get() {

return array;

}

}

当我们执行代码,会代码转型报错:

public void startTest() {

Tested td = new Tested<>(4);

td.set(0, 10);

try {

Integer[] array = td.get();

} catch (Exception e) {

// java.lang.Object[] cannot be cast to java.lang.Integer[]

}

}

代码转型报错。为何?还是上文说的擦除问题,即使我们将 Tested 参数类型设置成了Integer,并且在构造中进行了Integer[] 转型,但是这个信息呢,只存在于编译期的类型检查阶段,在之后它仍然是一个Object数组,所以我们无法强转。

泛型就是这般,底层规定的数组类型是Object[],那么它就只能是Object[],推翻不了。那么我们就没法得到想要的结果了吗?上面不是说了类型标记了嘛,我们再来看代码:

public class Tested {

private T[] array;

@SuppressWarnings(“unchecked”)

public Tested(Class type, int size) {

array = (T[]) Array.newInstance(type, size);

}

public void set(int index, T item) {

array[index] = item;

}

public T[] get() {

return array;

}

}

我们再来执行下,与我们的预期相同。

public void startTest() {

Tested td = new Tested<>(Integer.class, 4);

td.set(0, 10);

Integer[] array = td.get();

int t = array[0]; // t == 10

}

通配符

再看< ? extend MyClass> 之前,先看段简单代码,见下:

public class Tested {

List list1 = new ArrayList<>();

List list2 = new ArrayList<>();

public Tested() {

list1.add(new TestBase());

list2.add(new Test1th());

}

public T get(List t) {

return t.get(0);

}

public List getList1() {

return list1;

}

public List getList2() {

return list2;

}

}

测试基类TestBase:

public class TestBase { }

自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

list1 = new ArrayList<>();

List list2 = new ArrayList<>();

public Tested() {

list1.add(new TestBase());

list2.add(new Test1th());

}

public T get(List t) {

return t.get(0);

}

public List getList1() {

return list1;

}

public List getList2() {

return list2;

}

}

测试基类TestBase:

public class TestBase { }

自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

[外链图片转存中…(img-s1CfSlPu-1715714891480)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值