代码之家  ›  专栏  ›  技术社区  ›  grustamli

铸造到原始类型

  •  3
  • grustamli  · 技术社区  · 6 年前

    我被一个代码卡住了,我不明白它为什么会工作。假设我创建了一个通用接口 Foo<T> 如下所示:

    interface Foo<T>{
       void set(T item);
    }
    

    然后我创建了一个名为 Bar 哪个实现 Foo<String> 如下所示:

    class Bar implements Foo<String>{ 
       @override
       public void set(String item){
          //useless body
       }
    }
    

    基于此,我们可以编写以下代码:

    Bar bar = new Bar();
    bar.set("Some string");
    Foo rawFoo = (Foo) bar;
    rawFoo.set(new Object()); // ClassCastException: Object cannot be cast to string
    

    最后一行是我不太明白的。众所周知,使用原始类型时,泛型参数类型将转换为 Object 。 在这种情况下,代码将编译,我们可以将对象传递给 set() 方法但是Java如何确定它必须在运行时将对象转换为字符串呢?

    4 回复  |  直到 6 年前
        1
  •  5
  •   Andy Turner    6 年前

    如果反编译 Bar 使用 javap :

    class Bar implements Foo<java.lang.String> {
      Bar();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public void set(java.lang.String);
        Code:
           0: return
    
      public void set(java.lang.Object);
        Code:
           0: aload_0
           1: aload_1
           2: checkcast     #2                  // class java/lang/String
           5: invokevirtual #3                  // Method set:(Ljava/lang/String;)V
           8: return
    }
    

    void set(java.lang.Object) 是一种合成桥方法。请注意 checkcast 指示等效的“真实”代码如下所示:

    public void set(Object object) {
      set((String) object);
    }
    

    编译器创建这个方法是为了让类在类型擦除下工作;实际的工作方法是 set(java.lang.String) ,桥接方法委托给它。

    正是这种桥方法实际上重写了基类中的方法。使用声明的方法 @Override 注释只是一个重载。

        2
  •  1
  •   rgettman    6 年前

    Java中的泛型是编译时类型的安全特性。因为它们是在版本1.5中引入的,所以它们必须与以前的版本向后兼容。这允许您使用原始类型——一个没有类型参数的变量,它引用一个具有类型参数的类。

    在运行时,大多数类型参数信息都会被删除,但不是全部。在里面 this tutorial page about type erasure ,我们了解到编译器将在必要时插入强制转换。

    • 如果类型参数是无界的,则用其边界或对象替换泛型类型中的所有类型参数。因此,生成的字节码只包含普通类、接口和方法。
    • 如有必要,插入类型转换以保持类型安全。
    • 生成桥接方法以在扩展泛型类型中保留多态性。

    在这里,编译器创建 桥梁法 要在运行时保留多态性,需要 Object ,将其参数强制转换为 String ,然后调用真实 set(String) 方法

    当你打电话的时候 rawFoo.set(new Object()); ,由于多态性,此桥接方法在中调用 Bar 这是你在这里看到的隐式演员阵容 ClassCastException

        3
  •  1
  •   MutantOctopus    6 年前

    首先,您所做的只是提供对现有对象的引用,而不是创建新对象。

    编译扩展参数化类的类或接口时 或实现参数化接口,编译器可能需要 创建一个称为桥接方法的合成方法,作为类型的一部分 擦除过程。

    类型擦除后,您的Foo变为:

    interface Foo{
       void set(Object item);
    }
    

    您的班级变成:

    class Bar implements Foo{ 
       @override
       public void set(String item){
          //useless body
       }
    }
    

    类型擦除后,方法签名不匹配。

    因此,Bar set方法不实现Foo set方法。

    为了解决这个问题并在类型删除后保留泛型类型的多态性,Java编译器生成一个桥接方法来确保子类型按预期工作。对于Bar类,编译器为set生成以下桥接方法:

    public void set(Object item){
        set((String) data);
    }
    

    https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html

        4
  •  0
  •   grustamli    6 年前

    事实证明,我的问题的答案是桥接方法。我目前正在读莫里斯·纳夫塔林(MauriceNaftalin)和菲利普·瓦德勒(PhilipWadler)的《Java泛型和集合》一书。实际上,我读了一节关于桥梁方法的内容,但我似乎没有仔细阅读。我很高兴所有的答案都有共同点。现在,我再去读一读那一节