泛型和泛型擦除

泛型和泛型擦除一 泛型泛型是 JDK1 5 中一个最重要的特征 通过引入泛型 我们将获得编译时类型的安全和运行时更小的抛出 ClassCastExc 的可能 泛型 即 参数化类型 创建集合时就指定集合元素的类型 该集合只能保存其指定类型的元素 避免使用强制类型转换 二 泛型擦除 Java 编译器生成的字节码是不包涵泛型信息的 泛型类型信息将在编译处理是被擦除 这个过程即类型擦除 泛型擦除可以简单的理解为将

一、泛型之前

public class BeforeGeneric { static class ArrayList{ 
  //泛型之前的通用程序设计 private Object[] elements=new Object[0]; public Object get(int i){ return elements[i]; } public void add(Object o){ //这里的实现,只是为了演示,不具有任何参考价值 int length=elements.length; Object[] newElments=new Object[length+1]; for(int i=0;i 
  
    public 
   static 
   void 
   main(String[] args) { ArrayList stringValues= 
   new ArrayList(); stringValues.add( 
   1); 
   //可以向数组中添加任何类型的对象 
   //问题1——获取值时必须强制转换  String str=(String) stringValues. 
   get( 
   0); 
   //问题2——上述强制转型编译时不会出错,而运行时报异常java.lang.ClassCastException } } 
  

二、泛型

针对利用继承来实现通用程序设计所产生的问题,泛型提供了更好的解决方案:类型参数。例如,ArrayList类用一个类型参数来指出元素的类型。

ArrayList<String> stringValues=new ArrayList<String>(); 

这样的代码具有更好的可读性,我们一看就知道该集合用来保存String类型的对象,而不是仅仅依赖变量名称来暗示我们期望的类型。

public class GenericType { public static void main(String[] args) { ArrayList 
  
    stringValues= 
   new ArrayList 
   
     (); stringValues.add( 
    "str"); stringValues.add( 
    1); 
    //编译错误  } } 
    
  

三、Java泛型的实现原理

擦除

public class GenericType { public static void main(String[] args) { ArrayList 
  
    arrayString= 
   new ArrayList 
   
     (); ArrayList 
    
      arrayInteger= 
     new ArrayList 
     
       (); System. 
      out.println(arrayString.getClass()==arrayInteger.getClass()); } } 
      
     
    
  

原始类型就是泛型类型擦除了泛型信息后,在字节码中真正的类型。无论何时定义一个泛型类型,相应的原始类型都会被自动提供。原始类型的名字就是删去类型参数后的泛型类型的类名。擦除类型变量,并替换为限定类型(T为无限定的类型变量,用Object替换)。

//泛型类型  class Pair 
  
    { 
   private T 
   value; 
   public T 
   getValue() { 
   return 
   value; } 
   public 
   void 
   setValue(T 
   value) { 
   this. 
   value = 
   value; } } 
  
//原始类型  class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } } 

因为在Pair中,T是一个无限定的类型变量,所以用Object替换。如果是Pair,擦除后,类型变量用Number类型替换。

public class ReflectInGeneric { public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { ArrayList 
  
    array= 
   new ArrayList 
   
     (); array.add( 
    1); 
    //这样调用add方法只能存储整形,因为泛型类型的实例为Integer  array.getClass().getMethod( 
    "add", Object.class).invoke(array, 
    "asd"); 
    for ( 
    int i= 
    0;i 
    
      out.println(array. 
     get(i)); } } } 
     
    
  
public class Test { 
    public static void main(String[] args) { /不指定泛型的时候*/ int i=Test.add(1, 2); //这两个参数都是Integer,所以T替换为Integer类型  Number f=Test.add(1, 1.2);//这两个参数一个是Integer,另一个是Float,所以取同一父类的最小级,为Number  Object o=Test.add(1, "asd");//这两个参数一个是Integer,另一个是String,所以取同一父类的最小级,为Object  /指定泛型的时候*/ int a=Test. 
  
    add( 
   1, 
   2); 
   //指定了Integer,所以只能为Integer类型或者其子类  
   int b=Test. 
   
     add( 
    1, 
    2.2); 
    //编译错误,指定了Integer,不能为Float  Number c=Test. 
    
      add( 
     1, 
     2.2); 
     //指定为Number,所以可以为Integer和Float  } 
     //这是一个简单的泛型方法  
     public 
     static 
     
       T 
      add(T x,T y){ 
      return y; } } 
      
     
    
  

正确的运转

Pair<Integer> pair=new Pair<Integer> (); pair.setValue(3); Integer integer=pair.getValue(); System.out.println(integer);
ArrayList<String> arrayList1=new ArrayList<Object>();//编译错误  ArrayList<Object> arrayList1=new ArrayList<String>();//编译错误 

我们先看第一种情况,将第一种情况拓展成下面的形式:

ArrayList<Object> arrayList1=new ArrayList<Object>(); arrayList1.add(new Object()); arrayList1.add(new Object()); ArrayList<String> arrayList2=arrayList1;//编译错误 
ArrayList<String> arrayList1=new ArrayList<String>(); arrayList1.add(new String()); arrayList1.add(new String()); ArrayList<Object> arrayList2=arrayList1;//编译错误
ArrayList<String> arrayList=new ArrayList<String>();

因为类型擦除之后,ArrayList只剩下原始类型,泛型信息String不存在了。那么,运行时进行类型查询的时候使用下面的方法是错误的

if( arrayList instanceof ArrayList<String>)

java限定了这种类型查询的方式,?为通配符,也即非限定符。

if( arrayList instanceof ArrayList 
   >) 
public class Test2 
  
    { 
   public 
   static T one; 
   //编译错误  
   public 
   static T 
   show(T one){ 
   //编译错误  
   return 
   null; } } 
  
public class Test2 
  
    { 
   public 
   static 
   
     T 
    show(T one){ 
     
    //这是正确的  
    return 
    null; } } 
    
  
  1. Java中的泛型是什么 ? 使用泛型的好处是什么?
    泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。
    泛型是一种编译时类型确认机制。它提供了编译期的类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现ClassCastException。
    2、Java的泛型是如何工作的 ? 什么是类型擦除 ?
    泛型的正常工作是依赖编译器在编译源码的时候,先进行类型检查,然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令实现的。
    编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List在运行时仅用一个List类型来表示。为什么要进行擦除呢?这是为了避免类型膨胀。




  2. 什么是泛型中的限定通配符和非限定通配符 ?
    限定通配符对类型进行了限制。有两种限定通配符,一种是
public V put(K key, V value) { return cache.put(key, value); } 
  1. Java中如何使用泛型编写带有参数的类?
    这是上一道面试题的延伸。面试官可能会要求你用泛型编写一个类型安全的类,而不是编写一个泛型方法。关键仍然是使用泛型类型来代替原始类型,而且要使用JDK中采用的标准占位符。
  2. 编写一段泛型程序来实现LRU缓存?
    对于喜欢Java编程的人来说这相当于是一次练习。给你个提示,LinkedHashMap可以用来实现固定大小的LRU缓存,当LRU缓存已经满了的时候,它会把最老的键值对移出缓存。LinkedHashMap提供了一个称为removeEldestEntry()的方法,该方法会被put()和putAll()调用来删除最老的键值对。
  3. 你可以把List传递给一个接受List参数的方法吗?
    对任何一个不太熟悉泛型的人来说,这个Java泛型题目看起来令人疑惑,因为乍看起来String是一种Object,所以List应当可以用在需要List的地方,但是事实并非如此。真这样做的话会导致编译错误。如果你再深一步考虑,你会发现Java这样做是有意义的,因为List可以存储任何类型的对象包括String, Integer等等,而List却只能用来存储Strings。
List<Object> objectList; List<String> stringList; objectList = stringList; //compilation error incompatible types 
  1. Array中可以用泛型吗?
    这可能是Java泛型面试题中最简单的一个了,当然前提是你要知道Array事实上并不支持泛型,这也是为什么Joshua Bloch在Effective Java一书中建议使用List来代替Array,因为List可以提供编译期的类型安全保证,而Array却不能。
  2. 如何阻止Java中的类型未检查的警告?
    如果你把泛型和原始类型混合起来使用,例如下列代码,Java 5的javac编译器会产生类型未检查的警告
    ,例如List rawList = new ArrayList()
    注意: Hello.java使用了未检查或称为不安全的操作;
    这种警告可以使用@SuppressWarnings(“unchecked”)注解来屏蔽。
    11、Java中List和原始类型List之间的区别?
    原始类型和带参数类型之间的主要区别是,在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查,通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String或Integer。这道题的考察点在于对泛型中原始类型的正确理解。它们之间的第二点区别是,你可以把任何带参数的泛型类型传递给接受原始类型List的方法,但却不能把List传递给接受List的方法,因为会产生编译错误。
    12、Java中List






List 
    listOfAnyType; List<Object> listOfObject = new ArrayList<Object>(); List<String> listOfString = new ArrayList<String>(); List<Integer> listOfInteger = new ArrayList<Integer>(); listOfAnyType = listOfString; //legal  listOfAnyType = listOfInteger; //legal  listOfObjectType = (List<Object>) listOfString; //compiler error - in-convertible types 
List listOfRawTypes = new ArrayList(); listOfRawTypes.add("abc"); listOfRawTypes.add(123); //编译器允许这样 - 运行时却会出现异常  String item = (String) listOfRawTypes.get(0); //需要显式的类型转换  item = (String) listOfRawTypes.get(1); //抛ClassCastException,因为Integer不能被转换为String  List 
  
    listOfString = 
   new ArrayList(); listOfString. 
   add( 
   "abcd"); listOfString. 
   add( 
   1234); 
    //编译错误,比在运行时抛异常要好  
   item = listOfString. 
   get( 
   0); 
    //不需要显式的类型转换 - 编译器自动转换 
  

通配符

通配符上界

常规使用

public class Test { public static void printIntValue(List 
   list) { for (Number number : list) { System.out.print(number.intValue()+" "); } System.out.println(); } public static void main(String[] args) { List 
  
    integerList= 
   new ArrayList 
   
     (); integerList.add( 
    2); integerList.add( 
    2); printIntValue(integerList); List 
    
      floatList= 
     new ArrayList 
     
       (); floatList.add(( 
      float) 
      3.3); floatList.add(( 
      float) 
      0.3); printIntValue(floatList); } } 
      
     
    
  
public class Test { 
    public static void fillNumberList(List 
    extends Number> list) { list.add(new Integer(0));//编译错误  list.add(new Float(1.0));//编译错误  } public static void main(String[] args) { List 
     extends Number> list=new ArrayList(); list.add(new Integer(1));//编译错误  list.add(new Float(1.0));//编译错误  } } 

List

List 
    extends Number> list1=new ArrayList 
   
     (); 
    List 
     
      extends Number> list2=new ArrayList 
     
       (); 
      
   
public class Test { 
    public static void fillNumberList(List 
    super Number> list) { list.add(new Integer(0)); list.add(new Float(1.0)); } public static void main(String[] args) { List 
     super Number> list=new ArrayList(); list.add(new Integer(1)); list.add(new Float(1.1)); } } 
public static void printList(List list) { for (Object elem : list) System.out.println(elem + ""); System.out.println(); } 

可以选择改为如下实现

public static void printList(List 
      list) { for (Object elem: list) System.out.print(elem + ""); System.out.println(); } 

这样就可以兼容更多的输出,而不单纯是List,如下:

List<Integer> li = Arrays.asList(1, 2, 3); List<String> ls = Arrays.asList("one", "two", "three"); printList(li); printList(ls); 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/176935.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午8:41
下一篇 2026年3月26日 下午8:41


相关推荐

  • 电信宽带_错误676_电话占线 解决办法

    电信宽带_错误676_电话占线 解决办法

    2021年11月17日
    53
  • DataGrip 2021.11.4激活码【2021免费激活】[通俗易懂]

    (DataGrip 2021.11.4激活码)JetBrains旗下有多款编译器工具(如:IntelliJ、WebStorm、PyCharm等)在各编程领域几乎都占据了垄断地位。建立在开源IntelliJ平台之上,过去15年以来,JetBrains一直在不断发展和完善这个平台。这个平台可以针对您的开发工作流进行微调并且能够提供…

    2022年3月30日
    75
  • 用例图详解_用例图include是用什么画的

    用例图详解_用例图include是用什么画的对于用例图来说我们需要了解的是什么叫用例图,构成用例图的要素,用例图有哪些重要的元素,各个用例之间的关系。当然最重要的是如何根据需求创建用例图。具体的创建通过一个简单的学生管理的例子说明创建的过程和例子。  我的所有例子都是是使用Rose这个软件来画的,现在虽然有新的UML模型画图软件,但是我比较喜欢用这个Rose,如果你还没有装这个软件需要先装一个,或者使用你比较喜欢的UML画图软件。下面我们

    2025年9月30日
    3
  • Codex配置教程,支持gpt-5-codex

    Codex配置教程,支持gpt-5-codex

    2026年3月15日
    2
  • MySQL 之全文索引「建议收藏」

    MySQL 之全文索引「建议收藏」最近在复习数据库索引部分,看到了fulltext,也即全文索引,虽然全文索引在平时的业务中用到的不多,但是感觉它有点儿意思,所以花了点时间研究一下,特此记录。引入概念通过数值比较、范围过滤等就可以完成绝大多数我们需要的查询,但是,如果希望通过关键字的匹配来进行查询过滤,那么就需要基于相似度的查询,而不是原来的精确数值比较。全文索引就是为这种场景设计的。你可能会说,用like…

    2022年6月21日
    30
  • Nacos 2.0_一个数的0倍是多少

    Nacos 2.0_一个数的0倍是多少点击关注公众号,Java干货及时送达3月20号,Nacos2.0.0正式发布了!Nacos简介:“一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。通俗点讲,Nacos…

    2026年1月31日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号