Potential issue with one of Oracle's trails on Java generics

I was reviewing one of the Oracle trails on Java generics, entitled "Effects of Type Erasure and Bridge Methods", and I could not convince myself of an explanation given. Curious, I tested the code locally and I could not even reproduce the behavior which the trail explains. Here is the relevant code:

public class Node<T> {
    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

The Oracle trail claims the following behavior for this code snippet:

MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
n.setData("Hello");     
Integer x = mn.data;    // Causes a ClassCastException to be thrown.

This code snippet should look like the following after type erasure:

MyNode mn = new MyNode(5);
Node n = (MyNode)mn;         // A raw type - compiler throws an unchecked warning
n.setData("Hello");
Integer x = (String)mn.data; // Causes a ClassCastException to be thrown.

I did not understand the casts being used here or the behavior. When I tried running this code locally using IntelliJ with Java 7, I got this behavior:

MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
n.setData("Hello");     // Causes a ClassCastException to be thrown.
Integer x = mn.data;

In other words, the JVM will not allow a String to be used with setData() . This is actually intuitive to me, and it agrees with my understanding of generics. Since MyNode mn was constructed with Integer , the compiler should be casting every call to setData() with Integer to ensure type safety (ie an Integer is being passed in).

Can someone shed some light on this apparent bug in the Oracle trail?


You've misread the Oracle page. If you read all the way to the end, you'll find it states that what happens is what you describe.

It's not a well-written page; the author says "blah happens" when what they mean is "IF THIS WERE THE CASE THEN blah happens BUT AS WE'LL SEE THAT IS NOT THE CASE". They are being too loose with their language.

The point of the page - bridge methods - is to explain how the real behaviour is as you observed, when the predicted behaviour (based on Generics acutal design + implementation) is what they "suggested" at the start.


Well, it's explained in the trail.

In theory, when the class Node is compiled, its base type T is erased to Object .

So in reality, it is compiled into something like

class Node {
    public Object data;

    public Node(Object data) {this.data = data; }

    public void setData(Object data) {
         System.out.println("Node.setData");
         this.data = data;
    }
}

Then you create a child class MyNode , that has its own setData(Integer data) . As far as Java is concerned, this is an overload of the setData method, not an override of it. Each MyNode object has two setData methods. One is setData(Object) it inherited from Node , and the other is setData(Integer) .

So basically, if you use the raw type, and you call setData with any reference that is not an Integer , Java's normal interpretation of this would be to call the overload setData(Object) .

This would not cause a problem on assignment, because data is declared as Object , not as Integer . The problem would be only when you try to assign the data back to an Integer reference. This plain behavior of Java would cause the MyNode object to be "contaminated" with inappropriate data.

However, as the trail says, the compiler adds a "bridge" method to make the child class behave more like the way you intuitively think of it. It adds an override of setData(Object) to MyNode , so that you cannot call the original, non-safe Node.setData(Object) . In this overriding bridge method, there is an explicit cast to Integer that ensures that you will not be able to assign a non-integer reference to the data .

And that's the behavior you see when you actually compile and run the example.

If you run javap -p on the MyNode.class file, you'll see that indeed, it has two setData methods:

class MyNode extends Node<java.lang.Integer> {
  public MyNode(java.lang.Integer);
  public void setData(java.lang.Integer);
  public void setData(java.lang.Object);
}
链接地址: http://www.djcxy.com/p/89864.html

上一篇: 重复一段时间后,间隔不会立即清除

下一篇: 甲骨文在Java泛型方面的一个潜在问题