我是java的新手。我仍然觉得我必须了解很多,所以如果这个问题看起来很愚蠢,请原谅我。
现在我正在经历 http://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html
在这里,我发现了很多困惑。
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);
}
public static void main(String[] args) {
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;
}
}
当我运行此代码时,我得到以下错误
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at MyNode.setData(MyNode.java:1)
at MyNode.main(MyNode.java:14)
以下是混乱
1)为什么显示第1行
2)如果你通过博客阅读他们说类型擦除将替换类型参数将替换上面的代码如下
public class Node {
private Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println(data);
super.setData(data);
}
public static void main(String[] args) {
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
}
}
当我运行上面的代码我没有错误,代码运行正常
从文档两者相同?为什么这种行为不同
现在关于oop的另一个最重要的问题是当我们扩展一个类时,当我们调用超级构造函数时,虽然没有创建超级对象,那么调用super的用途是什么。请解释。
从文档两者相同?为什么这种行为不同
两个代码示例之间存在不同行为的原因是因为当您使用Object替换泛型T时,间接导致setData()不再覆盖超类中的setData()。由于参数类型不同,您只需简单 超载 它在第二个例子中。所以在第二个例子中,你直接调用带有Object的超类。在第一个示例中,您正在调用带有Integer的子类。
当我们调用超级构造函数虽然没有创建超级对象,那么调用super的用途是什么
超类和子类是同一个对象(其代码分为两个类)。因此,调用超类构造函数来初始化超类中定义的任何字段。在您的示例中,如果您从未调用过super(数据),那么将永远不会设置this.data。
如果您通读博客,他们会说类型擦除将替换类型参数将替换上面的代码如下
是的,这是对的。那是什么时候 桥法 开始行动了。所发生的是,在那种类型的擦除中, setData()
子类中的方法不再是超类的覆盖等效项 setData()
方法。因此,编译时的行为不会持续到运行时。为了保留行为,编译器在内部生成一个 桥法。
Bridge方法通常由编译器生成,当类型扩展或实现参数化类或接口时,类型擦除会更改任何继承方法的签名。
以下是它的工作原理。编译器在子类中生成以下方法:
// Bridge method
public void setData(Object data) {
setData((Integer) data);
}
现在,这种方法覆盖了 setData(Object)
超级方法。正如您所注意到的,此方法仅在内部调用原始方法,然后进行转换 data
至 Integer
。这是你得到的地方 ClassCastException
。该 data
是真的 String
类型,不能转换为 Integer
。
当我运行上面的代码我没有错误,代码运行正常
正如我上面所说,在类型擦除后, setData()
子类中的方法不会覆盖超类方法中的方法。所以,当你调用方法时 Node
参考,它会调用 Node
仅限类方法,其签名为:
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
这不会有问题,因为你没有投出任何东西。你正在存储 String
类型 data
到了 Object
类型参考。没事儿。
当我们调用超级构造函数虽然没有创建超级对象,那么调用super的用途是什么。
那应该是一个单独的问题。无论如何,关键是,对象的状态包括在该类中声明的数据成员,以及它的所有超类。该特定类中的构造函数仅初始化该类中的状态。要初始化超类中对象的状态,必须链接 super
类构造函数。
进一步阅读:
从文档两者相同?为什么这种行为不同
两个代码示例之间存在不同行为的原因是因为当您使用Object替换泛型T时,间接导致setData()不再覆盖超类中的setData()。由于参数类型不同,您只需简单 超载 它在第二个例子中。所以在第二个例子中,你直接调用带有Object的超类。在第一个示例中,您正在调用带有Integer的子类。
当我们调用超级构造函数虽然没有创建超级对象,那么调用super的用途是什么
超类和子类是同一个对象(其代码分为两个类)。因此,调用超类构造函数来初始化超类中定义的任何字段。在您的示例中,如果您从未调用过super(数据),那么将永远不会设置this.data。
如果您通读博客,他们会说类型擦除将替换类型参数将替换上面的代码如下
是的,这是对的。那是什么时候 桥法 开始行动了。所发生的是,在那种类型的擦除中, setData()
子类中的方法不再是超类的覆盖等效项 setData()
方法。因此,编译时的行为不会持续到运行时。为了保留行为,编译器在内部生成一个 桥法。
Bridge方法通常由编译器生成,当类型扩展或实现参数化类或接口时,类型擦除会更改任何继承方法的签名。
以下是它的工作原理。编译器在子类中生成以下方法:
// Bridge method
public void setData(Object data) {
setData((Integer) data);
}
现在,这种方法覆盖了 setData(Object)
超级方法。正如您所注意到的,此方法仅在内部调用原始方法,然后进行转换 data
至 Integer
。这是你得到的地方 ClassCastException
。该 data
是真的 String
类型,不能转换为 Integer
。
当我运行上面的代码我没有错误,代码运行正常
正如我上面所说,在类型擦除后, setData()
子类中的方法不会覆盖超类方法中的方法。所以,当你调用方法时 Node
参考,它会调用 Node
仅限类方法,其签名为:
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
这不会有问题,因为你没有投出任何东西。你正在存储 String
类型 data
到了 Object
类型参考。没事儿。
当我们调用超级构造函数虽然没有创建超级对象,那么调用super的用途是什么。
那应该是一个单独的问题。无论如何,关键是,对象的状态包括在该类中声明的数据成员,以及它的所有超类。该特定类中的构造函数仅初始化该类中的状态。要初始化超类中对象的状态,必须链接 super
类构造函数。
进一步阅读:
在第二个版本 MyNode.setData
不会覆盖 Node.setData
因为它有不同的签名。因此,在您的第二个示例中,不会发生动态调度 Node.setData
被调用,可以处理String就好了。
在第一个例子中 MyNode.setData
确实超越 Node.setData
,所以它被调用,但它无法处理 String
s,它只能处理 Integer
s所以你得到了 ClassCastException
。
因此,如果博客声称这正是内部发生的事情,那就错了。他们可能意味着什么:如果是这样的话就是这样的 MyNode.setData
仍然会覆盖 Node.setData
在第二个例子中。