Java 中的对象切片

10 Sept 2024 | 4 分钟阅读

对象切片”这个术语指的是当派生类对象被赋值给基类实例时发生的情况。这会导致派生类对象丢失方法和成员变量。这被称为信息切片。例如,

因为 B 扩展了 A,所以 B 现在包含两个成员变量 a1 和 b1。因此,如果您创建一个类型为 B 的 a1 变量 b1,然后创建一个类型为 A 的变量并将其赋值为 b1,您将丢失成员变量 b1。例如,

在这种情况下,关于 b 的信息在 a.bar 中丢失了。这被称为成员切片。

将对象转换为信息更少的事物(通常是超类)称为对象切片。当对象按值传递并且复制参数值导致向上转型时,这在 C++ 中会发生,这被认为是有问题的,因为它可能导致非常微妙的问题。对象切片会浪费信息。但是,在某些情况下,这可能正是我们想要的。

Java 中的对象是按引用传递的,并且所有方法都是虚方法,因此对象切片不会意外发生。即使对象被向上转型,它的真实类型也不会丢失;这是多态性的定义。

考虑最基本的对象切片形式。假设 Child 类扩展了 Parent 类,并且我们有一个 Child 对象。我们想“提取”它的 Parent 部分。

这行不通,因为编译器会确保点后面跟着 super 的一个有效标识符。

同样,由于这是多态的,从 Parent 类返回它的引用是没有用的。将调用重写的方法而不是 Parent 类的方法,并且可以调用任何额外的。这正是我们想要阻止的。我们想要一个指向对象底层类型的代理,而不仅仅是限制接口,也不仅仅是数据和行为的副本。

我们可以通过这种方式建立一个指向对象基类型的代理:对于该 Parent 类型的子类,如果一个方法被重写,我们还可以添加一个使用 super 调用重写的方法。然后我们开发一个包装器来将调用重定向到适当的方法。然而,这并不优雅,因为它需要跟踪过多的信息。面向对象的方法是使用内部类。此外,由于我们无法使用反射调用 super 的方法,因此我们无法使用动态代理来避免实现基类的每个方法。

如何在 Java 中执行对象切片?

让我们回想一下,为了实例化一个内部类,需要一个外部类的实例,并且可以从内部类访问外部对象的所有成员。

我们的计划是利用这个特性以及与 this 不同,super 不是多态的事实。

我们在 Child 类中构建一个内部类 Slice,它扩展了 Parent 并通过将所有 Parent 方法转发给外部对象的 Parent 部分来重写所有 Parent 方法。Child 类的 getParent() 函数返回一个 Slice 实例,它充当对象 Parent 部分的代理。

这是一个测试类,它演示了 getParent() 按预期工作,即它切片了对象

ObjectSlicing.java

输出

der

您可能想知道这有什么好处。毕竟,我们可以通过让派生类存储一个 Base 类型的引用来避免继承,该引用将在构造函数中创建或通过 setter 函数赋值。这通常不是一个好的做法。考虑需要通过 Male 和 Female 类进行增强的 Human 类。

从先前的 Human 实例构建 Male 和 Female 实例是没有意义的。我们有一个真正的 IS-A 关系,Male 和 Female 类应该有助于扩展 Human 类。而且,如果我们想在一个层中防止类型歧视,我们必须删除子类引入的额外信息或行为更改。


下一个主题Oracle Java