【问题标题】:Casting between Interfaces and Classes接口和类之间的转换
【发布时间】:2016-08-15 16:01:33
【问题描述】:

昨天我刚开始学习接口,一直在做一些简单的例子,我注意到我在理解类和接口之间的转换时遇到了很多麻烦,所以我阅读了Java Cast Interface to ClassInterfaces Oracle Java Tutorials

但是当我的书在底部给出一个小评论问题时,我注意到我仍然没有完全理解,而且我买的书没有解决方案。以下是问题(我给出了我的尝试和推理,这对某些人来说可能是错误的,所以任何帮助都是好的!)

假设 Sandwich 类实现了 Edible 接口,而你 给定变量声明

Sandwich sub = new Sandwich();

Rectangle cerealBox = new Rectangle(5, 10, 20, 30); 

Edible e = null;

以下哪些赋值语句是合法的?

  1. e = sub;

    由于 Sandwich 类实现了接口Edible,所以这没问题。我们可以说e = sub 没有问题。 有效

  2. sub = e;

    由于我们试图将 Sandwitch 类中的对象 sub 更改为 interface 类型,因此如果不进行强制转换,我们将无法执行此操作。 不行

  3. sub = (Sandwich) e

    有效!这解决了我们的老问题

  4. sub = (Sandwich) cerealBox;

    我不知道.. 但它应该工作吗? cerealBoxRectangle 所以我们将(Sandwich) 转换为sub,它是Sandwich 的一部分

  5. e =cerealBox;

    不这么认为。 Rectangle 没有实现 Edible 接口,所以它不应该工作

  6. e = (Edible) cerealBox;

    现在应该工作了。 (Edible) 就像它实现了接口一样。

  7. e = (Rectangle) cerealBox;

    不确定。我认为这行不通,我的意思是 cerealBox 是 Rectangle 类型,我们为什么要再次将其设为 rectangle

  8. e = (Rectangle) null;

    完全不确定

【问题讨论】:

  • 你为什么不自己试试呢?编译器会告诉你答案。
  • 我对答案不感兴趣。我想知道为什么
  • 但是你写这个问题好像你没有尝试过,想知道你的答案是对还是错。我建议你edit 它并添加类似“我认为这会起作用,但它不起作用。为什么?”。例如,您回答 8 并不表明您在请求推理。
  • casting 不会转换任何东西,你告诉编译器你知道正确的类型是什么,编译器应该闭嘴让它工作。如果你错了,你会在运行时得到一个错误。
  • 要回答所有这些问题,您需要更多信息:RectangleSandwich 的超类吗? SandwichRectangle 的超类吗? Rectangle 是否实现 Edible。可能所有这些问题的答案都是“否”,但如果没有这些信息,就不可能回答所有问题。

标签: java interface


【解决方案1】:

您需要了解的是,每个对象都有一个具体的类。它“是”该类的一个实例,并且它“是”该类继承的每个类的一个实例,同时。不仅如此,它还“是”每个接口这些类中的任何一个同时继承的实例。

所以,假设:

 class Animal
 class Mammal extends Animal implements Suckler
 class Dog extends Mammal implements Woofer

...如果我创建一个new Dog(),那么该对象“是”一个Object(因为所有对象都继承Object),一个Animal,一个Mammal,一个Suckler,一个@ 987654328@ 和 Woofer同时

但是,变量与对象不同。一个变量指向一个对象,一个变量有一个类型。类型必须与分配的对象兼容,仅此而已。

所以:

Suckler s = new Dog();

有效,但从那一刻起,所有编译器通过s 变量 知道对象是Suckler。它不知道自己是狗;它不知道自己是哺乳动物。所以我们不能去:

Dog d = s;

...因为编译器不能保证s指向的变量是Dog

变量的类型永远不能改变。 s 在其整个生命周期中都具有 Suckler 类型,无论如何。我们可以将SheepPig 分配给s,但除了Suckler 定义中的操作之外,我们无法对这些对象执行任何操作


我将假设各种类和接口是这样定义的:

 public interface Edible {
     ...
 }

 public class Sandwich implements Edible {
     ...
 }

 public class Rectangle {  // note, does not implement or extend anything
                           // (except Object)
     ...
 }

所以:

Sandwich sub = new Sandwich();
Edible e = null;
e = sub;

这很好。 subSandwichSandwichEdible 的一种。


Sandwich sub = new Sandwich();
Edible e = null;
sub = e;

这不会编译。 e 是一个Edible,但可以有任意数量的类实现Edible,以及Sandwichsub 必须是 Sandwich 并且由于编译器不能确定 e 是一个三明治,它会拒绝编译。


Sandwich sub = new Sandwich();
Edible e = null;
sub = (Sandwich) e;

这行得通。正如你正确计算出来的那样,演员告诉编译器“好的,你不能确定 eSandwich,但作为编码员,我告诉你它是。

如果您这样做,并且在运行时 e 实际上是 Apple implements Edible,而不是 Sandwich,则 JRE 将抛出 ClassCastException。确保不会发生这种情况是您的工作 - 避免强制转换是最好的方法。


Sandwich sub = new Sandwich();
Rectangle cerealBox = new Rectangle(5, 10, 20, 30); 
sub = (Sandwich) cerealBox;

... 将拒绝编译。 SandwichRectangle 彼此不相关。编译器知道没有Sandwich也是Rectangle,所以拒绝编译。

变量sub 必须始终指向Sandwich,而cerealBox 必须始终指向RectangleRectangle 可能是 Sandwich 的唯一方法是 Rectangle 继承 Sandwich,反之亦然。由于这两种情况都不是,它不会编译。

这是假设上面的声明。一个类可以实现多个接口,所以如果是public class Sandwich implements Edible, Rectangle {...},这段代码就可以了。


Rectangle cerealBox = new Rectangle(5, 10, 20, 30); 
Edible e = null;
e = cerealBox;

... 不会编译。 Rectangle 不是 Edible


Rectangle cerealBox = new Rectangle(5, 10, 20, 30); 
Edible e = null;
e = (Edible) cerealBox;

.. 乍一看,你可能认为不会编译。 Rectangle 不是 Edible,你不能告诉编译器它是。但是编译器不能保证不存在这样的类:

public class Flapjack extends Rectangle implements Edible { ... }

FlapjackRectangle 的一种,也是Edible,由于编译器不够聪明,无法知道cerealBox 不是Flapjack,它必须编译(它将在运行时失败)。

一个非常聪明的编译器可能能够分析程序逻辑以查看cerealBox 已被初始化为new Rectangle(),并且在运行时没有机会改变它。但是 Java 标准没有那种复杂的静态分析。

Rectangle 的作者可以通过将 public final class Rectangle 定义为 public final class Rectangle 来确保 Square 不存在——final 关键字禁止子类。


Rectangle cerealBox = new Rectangle(5, 10, 20, 30); 
Edible e = null;
e = (Rectangle) cerealBox;

... 不会编译。 e 仍然是 Edible。您不能为其分配Rectangle,因为EdibleRectangle 不相关。


Edible e = null;
e = (Rectangle) null;

将 null 转换为 Rectangle 很好,但是 eEdible,并且您不能将 Rectangle 分配给 Edible,因为它们是不相关的类型。

【讨论】:

  • 我对 sub = (Sandwich) grainBox; 感到困惑,(Sandwich) 告诉编译器“嘿,让 grainBox(a rectangle) 成为 Sandwitch 类型。” sub也是三明治类型。那么问题是什么?
  • 我想我上面的逻辑可能适用于 e = (Edible) grainBox;以及.. 编辑:OOPS 显然它确实根据第一条评论编译
  • 并不是说“编译器知道没有Sandwich也是Rectangle”。这是因为编译器知道没有一个类可以同时扩展SandwichRectangle,并且它知道Rectangle 不能扩展Sandwich。 Java 不支持多重继承,因此它可以假定这种关系是不可能的。
  • 谢谢,给出了这个正确的答案,因为它更详细。虽然答案很好
【解决方案2】:

#4、6、7 和 8 是您需要帮助的部分。他们都是关于铸造的。仅当源类型和目标类型可能通过继承存在关系时,强制转换才是合法的。仅当右侧具有相同类型或左侧的子类型时,变量赋值才是合法的。

#4 sub = (Sandwich) cerealBox;

转换后双方都是Sandwich 类型,但是(Sandwich) cerealBox 是非法转换。 这不起作用。这是非法转换的原因是因为转换不能在不同继承分支上的类之间发生。不存在Rectangle 可能是Sandwich 的情况,因此这是编译器错误。

#6 e = (Edible) cerealBox;

这与#4 不同,因为它是一个接口。这是合法的,但可能会引发运行时错误。请参阅this question 以获得很好的解释。在编译时,无法知道cerealBox 是否实际上是Rectangle 的子类型,即Edible这将起作用。如果 Rectanglefinal,这将不起作用。

#7 e = (Rectangle) cerealBox;

将变量强制转换为相同类型是合法的。将Rectangle 分配给Edible 类型的变量是不合法的。 Rectangle 没有实现 Edible这行不通。

#8 e = (Rectangle) null;

null 转换为任何类型都是合法的。 这不起作用,因为Rectangle 没有实现Edible

【讨论】:

  • “不同的继承链”,不太清楚这意味着什么(继承是 ch 11)我对 sub =(三明治)谷物盒感到困惑;,(三明治)告诉编译器“嘿,让谷物盒(一个矩形)变成三明治类型。” sub也是三明治类型。那么问题是什么?
  • 我想我上面的逻辑可能适用于 e = (Edible) grainBox;以及..(您和下面的人所说的不起作用)
  • #6 会起作用,但#4 不会。 #4 不能编译,因为一个类只能有一个超类。如果Rectangle 可以转换为Sandwich,那将是因为Rectangle extends Sandwich 不知何故。编译器能够根据Rectangle 的类定义对cerealBox 做出假设,因此转换在编译时失败。一旦你了解了继承,#4 会更有意义。
  • 好的,我暂时忘记 6。 4的基本思想如下: 尽管sub = (Sandwich) grainBox;,两边都是Sandwich类型,但是原本是矩形的graeBox没有实现Rectangle。因此,我们有两个要求。我们所指的类必须实现 Rectangle 并且我们使用像 sub = (Sandwich) grainBox 这样的强制转换。 IE 为了解决这个问题,我们做 public class Sandwich implements Rectangle
  • 我一直在到处看到这个扩展关键字,但甚至没有触及继承。这让我很难理解,不知道为什么我的书首先订购了接口:(
猜你喜欢
  • 1970-01-01
  • 2012-10-24
  • 2013-03-27
  • 2016-02-05
  • 2011-02-08
  • 2012-07-08
  • 1970-01-01
  • 2012-05-24
  • 2012-02-25
相关资源
最近更新 更多