问题 桥接方法如何工作?


我是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的用途是什么。请解释。


12732
2018-01-25 08:51


起源



答案:


从文档两者相同?为什么这种行为不同

两个代码示例之间存在不同行为的原因是因为当您使用Object替换泛型T时,间接导致setData()不再覆盖超类中的setData()。由于参数类型不同,您只需简单 超载 它在第二个例子中。所以在第二个例子中,你直接调用带有Object的超类。在第一个示例中,您正在调用带有Integer的子类。

当我们调用超级构造函数虽然没有创建超级对象,那么调用super的用途是什么

超类和子类是同一个对象(其代码分为两个类)。因此,调用超类构造函数来初始化超类中定义的任何字段。在您的示例中,如果您从未调用过super(数据),那么将永远不会设置this.data。


5
2018-01-25 09:08





如果您通读博客,他们会说类型擦除将替换类型参数将替换上面的代码如下

是的,这是对的。那是什么时候 桥法 开始行动了。所发生的是,在那种类型的擦除中, 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 类构造函数。

进一步阅读:


5
2018-01-25 10:06



那么为什么第一个例子失败了,如果你说桥接方法会被添加,抱歉,如果我完全理解你的回答是完整的 - sumedha
@sumedha是的,添加了桥接方法。这并不会导致您的方法调用失败 main。这完全是内部的。问题是,你正在通过 String 方法的类型参数(这正是一个桥接方法),然后在内部尝试将该参数强制转换为 Integer 类型,这是失败的地方。 - Rohit Jain
如果第二个例子有桥接方法的复制品那么为什么在第二个例子中它失败了请exaplain - sumedha
@sumedha你在谈论第二个例子?如果它是你问题中的那个,那么不,它不是真正的桥接方法的复制品。那里没有桥梁方法。记得, bridge方法是在子类中创建的。 - Rohit Jain


答案:


从文档两者相同?为什么这种行为不同

两个代码示例之间存在不同行为的原因是因为当您使用Object替换泛型T时,间接导致setData()不再覆盖超类中的setData()。由于参数类型不同,您只需简单 超载 它在第二个例子中。所以在第二个例子中,你直接调用带有Object的超类。在第一个示例中,您正在调用带有Integer的子类。

当我们调用超级构造函数虽然没有创建超级对象,那么调用super的用途是什么

超类和子类是同一个对象(其代码分为两个类)。因此,调用超类构造函数来初始化超类中定义的任何字段。在您的示例中,如果您从未调用过super(数据),那么将永远不会设置this.data。


5
2018-01-25 09:08





如果您通读博客,他们会说类型擦除将替换类型参数将替换上面的代码如下

是的,这是对的。那是什么时候 桥法 开始行动了。所发生的是,在那种类型的擦除中, 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 类构造函数。

进一步阅读:


5
2018-01-25 10:06



那么为什么第一个例子失败了,如果你说桥接方法会被添加,抱歉,如果我完全理解你的回答是完整的 - sumedha
@sumedha是的,添加了桥接方法。这并不会导致您的方法调用失败 main。这完全是内部的。问题是,你正在通过 String 方法的类型参数(这正是一个桥接方法),然后在内部尝试将该参数强制转换为 Integer 类型,这是失败的地方。 - Rohit Jain
如果第二个例子有桥接方法的复制品那么为什么在第二个例子中它失败了请exaplain - sumedha
@sumedha你在谈论第二个例子?如果它是你问题中的那个,那么不,它不是真正的桥接方法的复制品。那里没有桥梁方法。记得, bridge方法是在子类中创建的。 - Rohit Jain


在第二个版本 MyNode.setData 不会覆盖 Node.setData 因为它有不同的签名。因此,在您的第二个示例中,不会发生动态调度 Node.setData 被调用,可以处理String就好了。

在第一个例子中 MyNode.setData 确实超越 Node.setData,所以它被调用,但它无法处理 Strings,它只能处理 Integers所以你得到了 ClassCastException

因此,如果博客声称这正是内部发生的事情,那就错了。他们可能意味着什么:如果是这样的话就是这样的 MyNode.setData 仍然会覆盖 Node.setData 在第二个例子中。


4
2018-01-25 09:03