Java泛型概念相关面试题汇总。
前语:不要为了读文章而读文章,一定要带着问题来读文章,勤思考。
来源:ick/G2m
问:Java 的泛型是什么?有什么好处和优点?JDK 不同版本的泛型有什么区别?
答: 泛型是 Java SE 1.5 的新特性,泛型的本质是参数化类型,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。在 Java SE 1.5 之前没有泛型的
情况的下只能通过对类型 Object 的引用来实现参数的任意化,其带来的缺点是要做显式强制类型转换,而这种强制转换编译期是不做检查的,容易把问题留到运行时,所以 泛型的好处是在编译时检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率,避免在运行时出现 ClassCastException。
JDK 1.5 引入了泛型来允许强类型在编译时进行类型检查;JDK 1.7 泛型实例化类型具备了自动推断能力,譬如 List list = new ArrayList(); 可以写成 List llist = new ArrayList<>(); 了,JDK 具备自动推断能力。下面几种写法可以说是不同版本的兼容性了:
问:Java 泛型是如何工作的?什么是类型擦除?
答: 泛型是通过类型擦除来实现的,编译器在编译时擦除了所有泛型类型相关的信息,所以
在运行时不存在任何泛型类型相关的信息,譬如 List 在运行时仅用一个 List 来表示,这样做的目的是为了和 Java 1.5 之前版本进行兼容。泛型擦除具体来说就是在编译成字节码时首先进行类型检查,接着进行类型擦除(即所有类型参数都用他们的限定类型替换,包括类、变量和方法),接着如果类型擦除和多态性发生冲突时就在子类中生成桥方法解决,接着如果调用泛型方法的返回类型被擦除则在调用该方法时插入强制类型转换。
问:Java 泛型类、泛型接口、泛型方法有什么区别?
答: 泛型类是在实例化类的对象时才能确定的类型,其定义譬如 class Test {},在实例化该类时必须指明泛型 T 的具体类型。
泛型接口与泛型类一样,其定义譬如 interface Generator { E dunc(E e); }。
泛型方法所在的类可以是泛型类也可以是非泛型类,是否拥有泛型方法与所在的类无关,所以在我们应用中应该尽可能使用泛型方法,不要放大作用空间,尤其是在 static 方法时 static 方法无法访问泛型类的类型参数,所以更应该使用泛型的 static 方法(声明泛型一定要写在 static 后返回值类型前)。泛型方法的定义譬如 void func(T val) {}。
问:Java 如何优雅的实现元组?
答: 元组其实是关系数据库中的一个学术名词,一条记录就是一个元组,一个表就是一个关系,纪录组成表,元组生成关系,这就是关系数据库的核心理念。很多语言天生支持元组,譬如 Python 等,在语法本身支持元组的语言中元组是用括号表示的,如 (int, bool, string) 就是一个三元组类型,不过在 Java、C 等语言中就比较坑爹,语言语法本身不具备这个特性,所以在 Java 中我们如果想优雅实现元组就可以借助泛型类实现,如下是一个三元组类型的实现:
java面试题要背多久问:下面程序块的运行结果是什么,为什么?
答: 上面代码段结果为 true,解释如下。
因为 load 的是同一个 class 文件,存在 ArrayList.class 文件但是不存在 ArrayList.class 文件,即便是通过 TypeParameters() 方法获取类型信息也只能获取到 [T] 一样的泛型参数占位符。泛型是通过擦除来实现的,所以编译后任何具体的泛型类型都被擦除了(替换为非泛型上边界,如果没有指定边界则为 Object 类型),泛型类型只有在静态类型检查期间才出现,上面都被擦除成了 ArrayList 类型,所以运行时加载的是同一个 class 文件。
问:为什么 Java 泛型要通过擦除来实现?擦除有什么坏处或者说代价?
答: 可以说 Java 泛型的存在就是一个不得已的妥协,正因为这种妥协导致了 Java 泛型的混乱,甚至说是 JDK 泛型设计的失败。Java 之所以要通过擦除来实现泛型机制其实是为了兼容性考虑,只有这样才能让非泛化代码到泛化代码的转变过程建立在不破坏现有类库的实现上。正是因为这种兼容也带来了一些代价,譬如泛型不能显式地引用运行时类型的操作之中(如向上向下转型、instanceof 操作等),因为所有关于参数的信息都丢失了,所以任何时候使用泛型都要提醒自己背后的真实擦除类型到底是什么;此外擦除和兼容性导致了使用泛型并不是强制的(如 List list = new ArrayList(); 等写法);其次擦除会导致我们在编写代
码时十分谨慎(如不想被擦除为 Object 类型时不要忘了添加上边界操作等)。
问:下面三个 funcX 方法有问题吗,为什么?
答:func1、func2、func3 三个方法均无法编译通过。
因为泛型擦除丢失了在泛型代码中执行某些操作的能力,任何在运行时需要知道确切类型信息的操作都将无法工作。
问:下面代码段有问题吗,运行效果是什么,为什么?
答: 由于在程序中定义的 ArrayList 泛型类型实例化为 Integer 的对象,如果直接调用 add 方法则只能存储整形数据,不过当我们利用反射调用 add 方法时就可以存储字符串,因为 Integer 泛型实例在编译之后被擦除了,只保留了原始类型 Object,所以自然可以插入。
问:请比较深入的谈谈你对 Java 泛型擦除的理解和带来的问题认识?
答:Java 的泛型是伪泛型,因为在编译期间所有的泛型信息都会被擦除掉,譬如 List 在运行时仅用一个 List 来表示(所以我们可以通过反射 add 方法来向 Integer 的泛型列表添加字符串,因为编译后都成了 Object),这样做的目的是为了和 Java 1.5 之前版本进行兼容。泛型擦除具体来说就是在编译成字节码时首先进行类型检查,接着进行类型擦除(即所有类型参数都用他们的限定类型替换,包括类、变量和方法,如果类型变量有限定则原始类型就用第一个边界的类型来替换,譬如 class Prd<T extends Comparable & Serializable> {} 的原始类型就是 Comparable),接着如果类型擦除和多态性发生冲突时就在子类中生成桥方法解决,接着如果调用泛型方法的返回类型被擦除则在调用该方法时插入强制类型转换。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论