Java泛型、类、方法、通配符,擦除、面试题

Java泛型、类、方法、通配符,擦除、面试题

​ 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用Java泛型。

类型参数的魅力在于:使得程序具有更好的可读性和安全性。

泛型类

​ 一个泛型类( generic class ) 就是具有一个或多个类型变量的类。泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

public class Box<T> { 
    private T t; 
    public void add(T t) { 
        this.t = t; 
    } 
    public T get() { 
        return t;
    }

泛型方法

你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

public static <E>void printArray(E[] inputArray) { 
    for (E element : inputArray){ 
        System.out.printf("%s", element); 
    } 
}
  1. 表示该通配符所代表的类型是T类型的子类。
  2. 表示该通配符所代表的类型是T类型的父类。

类型通配符?

类型通配符一般是使用?代替具体的类型参数。例如List在逻辑上是List、List等所有List<具体类型实参>的父类。

类型擦除

​ 虚拟机没有泛型类型对象——所有对象都属于普通类。在泛型实现的早期版本中, 甚至能够将使用泛型的程序编译为在1.0 虚拟机上运行的类文件!这个向后兼容性在Java泛型开发的后期被放弃了。

​ Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List和List等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。

Java泛型转换的事实:

  • 虚拟机中没有泛型, 只有普通的类和方法
  • 所有的类型参数都用它们的限定类型替换
  • 桥方法被合成来保持多态
  • 为保持类型安全性,必要时插人强制类型转换

约束与局限性

  • 不能用基本类型实例化类型参数
  • 运行时类型查询只适用于原始类型
  • 不能创建参数化类型的数组
  • Varargs 警告
  • 不能实例化类型变置
  • 不能构造泛型数组
  • 泛型类的静态上下文中类型变量无效
  • 不能抛出或捕获泛型类的实例
  • 可以消除对受查异常的检查
  • 注意擦除后的冲突

10 道 Java 泛型面试题

  • Java中的泛型是什么 ? 使用泛型的好处是什么?

那些拥有Java1.4或更早版本的开发背景的人都知道,在集合中存储对象并在使用前进行类型转换是多么的不方便。泛型防止了那种情况的发生。它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。

  • Java的泛型是如何工作的 ? 什么是类型擦除 ?

泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息,确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。

  • 什么是泛型中的限定通配符和非限定通配符 ?

​ 有两种限定通配符,一种是它通过确保类型必须是T的子类来设定类型的上界,另一种是它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面表示了非限定通配符,因为可以用任意类型来替代。

  • List和List 之间有什么区别 ?

List可以接受任何继承自T的类型的List,而List可以接受任何T的父类构成的List。

  • 如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?

编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。

public V put(K key, V value) {
    return cache.put(key, value);
}

  • Java中如何使用泛型编写带有参数的类?

用泛型编写一个类型安全的类,而不是编写一个泛型方法。关键仍然是使用泛型类型来代替原始类型,而且要使用JDK中采用的标准占位符。

  • 编写一段泛型程序来实现LRU缓存?

​ LinkedHashMap可以用来实现固定大小的LRU缓存,当LRU缓存已经满了的时候,它会把最老的键值对移出缓存。LinkedHashMap提供了一个称为removeEldestEntry()的方法,该方法会被put()和putAll()调用来删除最老的键值对。当然,如果你已经编写了一个可运行的JUnit测试,你也可以随意编写你自己的实现代码。

  • 你可以把List传递给一个接受List参数的方法吗?

​ 不可以,List可以存储任何类型的对象包括String, Integer等等,而List却只能用来存储Strings。

       List<Object> objectList;
       List<String> stringList;
       objectList = stringList;  //compilation error incompatible types
  • Array中可以用泛型吗?

​ Array不支持泛型,这也是为什么Joshua Bloch在Effective Java一书中建议使用List来代替Array,因为List可以提供编译期的类型安全保证,而Array却不能。

  • 如何阻止Java中的类型未检查的警告?

如果你把泛型和原始类型混合起来使用,例如下列代码,Java 5的javac编译器会产生类型未检查的警告,例如List rawList = new ArrayList()

你可能感兴趣的:(java)