我是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的用途是什么。请解释一下。
答案 0 :(得分:5)
从文档两者相同?为什么这种行为不同
两个代码示例之间存在不同行为的原因是因为当您使用Object替换泛型T时,间接导致setData()不再覆盖超类中的setData()。由于参数类型不同,您在第二个示例中只是重载。所以在第二个例子中,你直接调用了带有Object的超类。在第一个例子中,您正在调用带有Integer的子类。
当我们调用超级构造函数但是没有创建超级对象时,调用超级
的用途是什么
超类和子类是同一个对象(其代码分为两个类)。因此,调用超类构造函数来初始化超类中定义的任何字段。在您的示例中,如果您从未调用过super(数据),那么this.data将永远不会被设置。
答案 1 :(得分:5)
如果您通过博客阅读他们说类型擦除将替换类型参数将替换上面的代码如下
是的,这是对的。那就是桥接方法开始行动的时候。发生的情况是,通过该类型擦除,子类中的setData()
方法不再是超类setData()
方法的覆盖等效方法。因此,编译时的行为不会持续到运行时。为了保留该行为,编译器在内部生成桥接方法。
以下是它的工作原理。编译器在子类中生成以下方法:
// 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
类构造函数。
进一步阅读:
答案 2 :(得分:4)
在第二个版本中,MyNode.setData
不会覆盖Node.setData
,因为它具有不同的签名。因此,在您的第二个示例中,没有动态调度发生,Node.setData
被调用,这可以很好地处理String。
在第一个示例中,MyNode.setData
会覆盖Node.setData
,因此会调用它,但它无法处理String
,它只能处理Integer
s所以你得到ClassCastException
。
因此,如果博客声称这正是内部发生的事情,那就错了。它们可能意味着什么:如果MyNode.setData
在第二个示例中仍会覆盖Node.setData
,则其工作方式如此。