Android面试题之Java 泛型和Kotlin泛型

简介: **Java泛型是JDK5引入的特性,用于编译时类型检查和安全。泛型擦除会在运行时移除类型参数,用Object或边界类型替换。这导致几个限制:不能直接创建泛型实例,不能使用instanceof,泛型数组与协变冲突,以及在静态上下文中的限制。通配符如<?>用于增强灵活性,<? extends T>只读,<? super T>只写。面试题涉及泛型原理和擦除机制。

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点

定义:JDK5引入的一种参数化类型特性

继承和实现接口可以多个
static class A{
   }
static interface B{
   }
static interface C{
   }

//类必须在接口的前面
static class D<T extends A & B & C>{
   }
泛型原理

泛型擦除:

  • 做类型检查,T如果有做类型限制,会转化为第1种限制,否则会擦除为object
  • 生成桥方法,里面调用对应的接口方法,调用的时候会进行类型的强转,转为T的限制类型
  • 泛型擦除后,字节码中没有泛型信息了,但是类的常量池里保留了泛型信息。反射的时候提供了一套API可以拿到,比如getGenericType()
泛型带来的问题
  1. 泛型类型变量不能使用基本类型
比如没有ArrayList<int>,只有ArrayList<Integer>,
当泛型擦除后,ArrayList的原始类中的类型变量T替换成了Object,但Object不能存放基本数据类型
  1. 不能使用instanceof运算符
因为泛型擦除后,ArrayList<String>只剩下原始类型,泛型信息String不存在了
  1. 泛型在静态方法和静态类中的问题

    因为泛型类中的泛型参数的实例化是在定义泛型类型对象时候指定的,而静态成员是不需要使用对象来调用的,所有对象都没创建,无法确定泛型参数;

    静态方法中是可以的,因为调用的时候可以确定参数中的泛型类型

  2. 泛型类型中的方法冲突

    因为擦除后2个equales方法变成一样的了,参数都会变成object

@Override
public boolean equals(Object o) {
   
    super.equals(o);
}
@Override
public boolean equals(T o) {
   
    super.equals(o);
}
  1. 没法创建泛型实例,因为类型不确定
public static <E> void append(List<E> list){
   
    //编译会报错
    E element = new E();
    list.add(element);
}
不过可以通过反射来创建
 public static <E> void append(List<E> list, Class<E> cls) throws Exception {
   
    E element = cls.newInstance();
    list.add(element);
}
  1. 没有泛型数组,因为数组遵循协变原则
协变:Apple extend Fruit,Apple[] 的父类是Fruit[]
泛型,继承和子类
  • 给定两种具体的类型 A和B,无论A和B是否相关,MyClass和MyClass都没有半毛钱关系;
比如Apple继承自Fruit,那Plate<Fruit>和Plate<Apple>也没有任何关系;
也就是说苹果是水果,但装苹果的盘子不是装水果的盘子
  • 继承关系中,泛型可以有多个,但如果有一个泛型参数是一样的,继承关系就存在的,不会因为有的类多个个泛型参数,继承关系就不在了
比如Plate<Apple> <--AIPlate<Apple> <--BIgPlate<Apple> <-- ColorPlate<String, Apple>
泛型和通配符

通配符让泛型转型更灵活

  • Plate<?> 非限定通配符,是一个泛型类型 ?表示未知,等价于 Plate<? extends Object>;副作用是既不能读也不能写;可以促使进行安全检查

  • List和List<?>,前者不会进行安全检查,后者会进行类型的安全检查

限定通配符

  • Plate<? extends T> 限定上届,能读不能写,类似于生产者
  • Plate<? super T> 限定下届,能写不能读,类似于消费者
通配符总结
  • 如果只需要从集合中获得类型T,使用<? extends T>;

  • 如果只需要将类型T放到集合中,使用<? super T>;

  • 既要获取又要放置元素,则不使用通配符

用<? extends Fruit>的后遗症

<? extends Fruit>是上届通配符,相当于“只读”

Plate<? extends Fruit> fruitPlate = plate(Plate<Apple> 类型)
//下面会报错,因为具体类型丢失了,只能是Fruit
fruitPlate.set(new Apple())

解决方案:可以用反射来set,但是安全性降低

Method set = friutPlate.getClass().getMethod("set", Object.class);
set.invoke(fruitPlate, new Banana())
//什么都能set,安全性降低
set.invoke(fruitPlate, new Beef())

面试题

1、Java泛型原理?什么是泛型擦除机制?

Java泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实不支持泛型,所以Java实现的是伪泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息

2、Java编译器具体是如何擦除泛型的

  • 检查泛型类型,获取目标类型

  • 擦除类型变量,并替换为限定类型。如果泛型类型的类型变量没有限定(\),则Object为原始类型;

 如果有限定(\<T extends XClass>),则用XClass作为限定类型;
 如果有多个限定(T extends XClass1 & XClass2),则使用第一个边界XClass1作为原始类。
  • 在必要时插入类型转换以保持类型安全

  • 生成桥方法以在扩展时保持多态性

Kotlin泛型

Kotlin的泛型可以看文章:Android面试题之Kotlin泛型和reified关键字


欢迎关注我的公众号AntDream查看更多精彩文章!

目录
相关文章
|
10天前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
29 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
|
2天前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
18 8
|
3天前
|
SQL Java Unix
Android经典面试题之Java中获取时间戳的方式有哪些?有什么区别?
在Java中获取时间戳有多种方式,包括`System.currentTimeMillis()`(毫秒级,适用于日志和计时)、`System.nanoTime()`(纳秒级,高精度计时)、`Instant.now().toEpochMilli()`(毫秒级,ISO-8601标准)和`Instant.now().getEpochSecond()`(秒级)。`Timestamp.valueOf(LocalDateTime.now()).getTime()`适用于数据库操作。选择方法取决于精度、用途和时间起点的需求。
17 3
|
7天前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
17 6
|
13天前
|
NoSQL Java 应用服务中间件
Java高级面试题
Java高级面试题
|
13天前
|
网络协议 安全 前端开发
java面试题
java面试题
|
8天前
|
Android开发 Kotlin
Android经典面试题之Kotlin中Lambda表达式有哪些用法
Kotlin的Lambda表达式是匿名函数的简洁形式,常用于集合操作和高阶函数。基本语法是`{参数 -&gt; 表达式}`。例如,`{a, b -&gt; a + b}`是一个加法lambda。它们可在`map`、`filter`等函数中使用,也可作为参数传递。单参数时可使用`it`关键字,如`list.map { it * 2 }`。类型推断简化了类型声明。
10 0
|
8天前
|
Android开发 Kotlin
Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
**Kotlin中的匿名函数与Lambda表达式概述:** 匿名函数(`fun`关键字,明确返回类型,支持非局部返回)适合复杂逻辑,而Lambda(简洁语法,类型推断)常用于内联操作和高阶函数参数。两者在语法、返回类型和使用场景上有所区别,但都提供无名函数的能力。
8 0
|
1月前
|
Java API 容器
Java泛型的继承和通配符
Java泛型的继承和通配符
18 1
|
2月前
|
安全 Java API
Java一分钟之-泛型通配符:上限与下限野蛮类型
【5月更文挑战第19天】Java中的泛型通配符用于增强方法参数和变量的灵活性。通配符上限`? extends T`允许读取`T`或其子类型的列表,而通配符下限`? super T`允许向`T`或其父类型的列表写入。野蛮类型不指定泛型,可能引发运行时异常。注意,不能创建泛型通配符实例,也无法同时指定上下限。理解和适度使用这些概念能提升代码的通用性和安全性,但也需兼顾可读性。
42 3