Java面试题——泛型

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

在Java1.4之前,在集合中存储对象并在使用前进行类型转换是多么的不方便。泛型防止了那种情况的发生。它提供了编译期的类ClassCastException。

泛型是Java SE1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

好处:

1 类型安全,提供编译期间的类型检测

2 前后兼容

3 泛化代码,代码可以更多的重复利用

4 性能比较高,用GJ(泛型Java)编写的代码可以为Java编译器和虚拟机带来更多的类型信息,这些信息对Java程序做进一步优化提供条件

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

类型擦除的定义:编译通过后,准备进入JVM运行时,就不再有类型参数的概念,换句话说:每定义一个泛型类型,JVM会自动提供一个对应的原生类;

泛型通过类型擦除来将变量变为一个类型,编译器在编译时擦出了所有类型相关的信息,所以在运行时不存在任何类型相关的信息,例如 List 在运行时仅以 List 来表示;

这样做可以确保能和 Java 5 之前的版本开发二进制类库兼容;

无法在运行时访问到数据类型,因为编译器已经把泛型类型转换成了原始类型;

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

限定通配符对类型进行了限制。有两种限定通配符,一种是它通过确保类型必须是T的子类来设定类型的上界,另一种是它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面表 示了非限定通配符,因为可以用任意类型来替代。更多信息请参阅我的文章泛型中限定通配符和非限定通配符之间的区别。

4 List<? extends T>和List <? super T>之间有什么区别 ?

List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。在本段出现的连接中可以找到更多信息。

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

编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。泛型方法的例子请参阅Java集合类框架。最简单的情况下,一个泛型方法可能会像这样:

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

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

这是一种混合的数据结构,我们需要在哈希表的基础上建立一个链表。但是Java已经为我们提供了这种形式的数据结构-LinkedHashMap!它甚至提供可覆盖回收策略的方法。唯一需要我们注意的事情是,该链表的顺序是插入的顺序,而不是访问的顺序。但是,有一个构造函数提供了一个选项,可以使用访问的顺序。

import java.util.LinkedHashMap;
import java.util.Map;
 
public LRUCache<K, V> extends LinkedHashMap<K, V> {
  private int cacheSize;
 
  public LRUCache(int cacheSize) {
    super(16, 0.75, true);
    this.cacheSize = cacheSize;
  }
 
  protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    return size() >= cacheSize;
  }
}


7 你可以把List传递给一个接受List

原始类型可以引用一个参数化类型的对象,编译报告警告,例如,
Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
泛型中的类型参数严格说明集合中装载的数据类型是什么和可以加入什么类型的数据,
注意:Collection<String>和Collection<Object>是两个没有转换关系的参数化的类型。
假设Vector<String> v = new Vector<Object>();可以的话,那么以后从v中取出的对象当作String用,而v实际指向的对象中可以加入任意的类型对象;假设Vector<Object> v = new Vector<String>();可以的话,那么
以后可以向v中加入任意的类型对象,而v实际指向的集合中只能装String类型的对象。肯定会报错了

8 Array中可以用泛型吗?

当然前提是你要知道Array事实上并不支持泛型,这也是为什么Joshua Bloch在Effective Java一书中建议使用List来代替Array,因为List可以提供编译期的类型安全保证,而Array却不能。

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

如果你把泛型和原始类型混合起来使用,例如下列代码,Java 5的javac编译器会产生类型未检查的警告
(unchecked warning),例如
List<String> list = new ArrayList();
任何一个未检查的警告在运行时都可能导致抛出ClassCastException,因此应该尽量消除警告信息,
如上代码应修改为:
List<String> list=new ArrayList<String>();
如果确定代码不会触发ClassCastException,那么可以使用@SuppressWarnings(“unchecked”)
去除未检查警告。此注释应该用在尽可能小的范围。


10 Java 泛型类、泛型接口、泛型方法有什么区别?

		泛型类是在实例化类的对象时才能确定的类型,其定义譬如 class Test<T> {},在实例化该类时必
须指明泛型 T 的具体类型。
		泛型接口与泛型类一样,其定义譬如 interface Generator<E> { E dunc(E e); }。
泛型方法所在的类可以是泛型类也可以是非泛型类,是否拥有泛型方法与所在的类无关,所以在我们
应用中应该尽可能使用泛型方法,不要放大作用空间,尤其是在 static 方法时 static 方法无法访问
泛型类的类型参数,所以更应该使用泛型的 static 方法(声明泛型一定要写在 static 后返回值类型前)
。泛型方法的定义譬如 <T> void func(T val) {}。

11 Java 如何优雅的实现元组?

元组其实是关系数据库中的一个学术名词,一条记录就是一个元组,一个表就是一个关系,纪录组成表
,元组生成关系,这就是关系数据库的核心理念。很多语言天生支持元组,
譬如 Python 等,在语法本身支持元组的语言中元组是用括号表示的,如 (int, bool, string) 就
是一个三元组类型,不过在 Java、C 等语言中就比较坑爹,语言语法本身不具备这个特性,
所以在 Java 中我们如果想优雅实现元组就可以借助泛型类实现,如下是一个三元组类型的实现:
Triplet<A,B,C>{
    private A a;
    private B a;
    private C a;
    public Triplet(A a,B b,C c){
        this.a =a;
        this.b =b;
        this.c =c;
}
}


12 java泛型中<?>和<T>有什么区别?

T 代表一种类型

加在类上:class SuperClass{}

加在方法上:

 public void fromArrayToCollection(T[] a, Collection c){}

方法上的代表括号里面要用到泛型参数,

若类中传了泛型,此处可以不传,调用类型上面的泛型参数,前提是方法中使用的泛型与类中传来的泛型一致。

class People{
 public void show(T a) {
}
}


T extends T2 指传的参数为T2或者T2的子类型。

?是通配符,泛指所有类型

一般用于定义一个引用变量,这么做的好处是,

如下所示,定义一个sup的引用变量,就可以指向多个对象。

SuperClass<?> sup = new SuperClass(“lisi”);
sup = new SuperClass(new People());
sup = new SuperClass(new Animal());


若不用?,用固定的类型的话,则:

SuperClass sup1 = new SuperClass(“lisi”);
SuperClass sup2 = new SuperClass(“lisi”);
SuperClass sup3 = new SuperClass(“lisi”);

这就是?通配符的好处。

 ? extends T

指T类型或T的子类型

 ? super T

指T类型或T的父类型

这个两个一般也是和 ? 一样用在定义引用变量中,但是传值范围不一样

T和?运用的地方有点不同, ?是定义在引用变量上, T是类上或方法上

如果有泛型方法和非泛型方法,都满足条件,就会执行非泛型方法

public void show(String s){
System.***out\***.println("1");
}
@Override
public void show(T a) {
System.***out\***.println("2");

}


在整个类中只有一处使用了泛型,

使用时注意加了泛型了参数 不能调用 与 参数类型有关的方法比如“+”,

比如打印出 任意参数化类型集合中的所有内容,就适合用通配符泛型

public static void printCollecton(Collection <?> collection){
		for(Object obj: collection){
			System.out.println(obj);
	}
}


当一个类型变量用来表达 两个参数之间 或者 参数与返回值之间的关系是,

即统一各类型变量在方法签名的两处被使用,

或者类型变量在方法体代码中也被使用而不仅仅在签名的时候使用,、

这是应该用自定义泛型。

泛型方可以调用一些时间类型的方法。比如集合的add方法。

public static <T> T autoConvertType(T obj){
   return(T)obj;
}

泛型三种:

 [1] ArrayList al=new ArrayList();

指定集合元素只能是T类型

 [2] ArrayList<?> al=new ArrayList<?>();

集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法

 [3] ArrayList<? extends E> al=new ArrayList<? extends E>();

泛型的限定:

? extends E:接收E类型或者E的子类型。

?super E:接收E类型或者E的父类型。

java泛型的两种用法:List是泛型方法,List是限制通配符

List一般有两种用途:

1、定义一个通用的泛型方法。

public interface Dao{
  List<T> getList(){};
}
List<String> getStringList(){
  return dao.getList();//dao是一个实现类实例
}
List<Integer> getIntList(){
  return dao.getList();
}


上面接口的getList方法如果定义成List ,后面就会报错。

2、限制方法的参数之间或参数和返回结果之间的关系。

 List<T> getList<T param1,T param2>

这样可以限制返回结果的类型以及两个参数的类型一致。

List一般就是在泛型上起一个限制作用。

public Clss Fruit(){}
public Class Apple extends Fruit(){}
public void test(? extends Fruit){};
test(new Fruit());
test(new Apple());
test(new String()); //这个就会报错,


参数必须是Fruit或其子类。

““和”",首先要区分开两种不同的场景:

第一,声明一个泛型类或泛型方法。

第二,使用泛型类或泛型方法。

类型参数“”主要用于第一种,声明泛型类或泛型方法。

无界通配符“”主要用于第二种,使用泛型类或泛型方法



泛型详情介绍,一起学习的朋友可以点点关注,会持续更新,文章有帮助的话可以长按点赞有惊喜!!!文章比较长,大家可以先 收藏转发后看有什么补充可以在下面评论,谢谢大家

举报
评论 0