【问题标题】:java: combined instanceof and cast?java:结合instanceof和cast?
【发布时间】:2010-10-16 14:07:37
【问题描述】:

(请不要建议我应该抽象 X 并添加另一个方法。)

在 C++ 中,当我有一个 X* 类型的变量 x 并且如果它也是 Y* 类型(YX 的子类)我想做一些特定的事情,我是写这个:

if(Y* y = dynamic_cast<Y*>(x)) {
    // now do sth with y
}

同样的事情在 Java 中似乎是不可能的(或者是吗?)。

我已经阅读了这段 Java 代码:

if(x instanceof Y) {
    Y y = (Y) x;
    // ...
}

有时,当您没有变量 x 但它是一个更复杂的表达式时,正是因为这个问题,您需要一个 Java 中的虚拟变量:

X x = something();
if(x instanceof Y) {
    Y y = (Y) x;
    // ...
}
// x not needed here anymore

(常见的情况是something()iterator.next()。你看到你也不能真正调用它两次。你真的需要虚拟变量。)

你在这里根本不需要x——你只是拥有它,因为你不能立即对演员表进行instanceof检查。再次将其与相当常见的 C++ 代码进行比较:

if(Y* y = dynamic_cast<Y*>( something() )) {
    // ...
}

因此,我引入了一个castOrNull 函数,它可以避免虚拟变量x。我现在可以写这个了:

Y y = castOrNull( something(), Y.class );
if(y != null) {
    // ...
}

castOrNull的实现:

public static <T> T castOrNull(Object obj, Class<T> clazz) {
    try {
        return clazz.cast(obj);
    } catch (ClassCastException exc) {
        return null;
    }
}

现在,有人告诉我using this castOrNull function in that way is an evil thing do to。这是为什么? (或者更笼统地说:你会同意并认为这是邪恶的吗?如果是,为什么会这样?或者你认为这是一个有效的(可能是罕见的)用例吗?)


如前所述,我不想讨论使用这种向下转换是否是一个好主意。但让我简要说明一下为什么我有时会使用它:

    1234563基本上,我可以选择添加函数doSomethingVeryVerySpecificIfIAmY() 或执行instanceof 检查。而且在这种情况下,我觉得后者更干净。
  1. 有时我有一些接口/基类的集合,对于Y 类型的所有条目,我想做一些事情,然后将它们从集合中删除。 (例如,我有一个树结构,我想删除所有空叶子的子节点。)

【问题讨论】:

  • 我会使用 instanceOf 而不是 class.cast 以避免在 castOrNull 中出现不必要的异常,但除此之外你似乎有完全正常的帮手。让 Tom 澄清他的意思。
  • 如果 Y 是 X 的 超类,则根本不需要强制转换 - 您的意思是 subclass 吗?
  • 虽然这不是 Java 中的单行代码,但 instanceof 和强制转换的性能相当不错。我在 Java7 中围绕解决问题的不同方法发布了一些时间:stackoverflow.com/questions/16320014/…
  • 您的两个示例似乎都违反了对子类的“是”测试。例如,为什么您的节点没有“deleteEmptyChildren”方法?我的猜测是因为你将它与其他一些类结合起来,它实际上应该“具有”指向另一个类的指针。一般来说,我认为你只是过度使用继承。请记住始终更喜欢封装而不是继承。

标签: java casting instanceof dynamic-cast


【解决方案1】:

现在,有人告诉我,以这种方式使用这个 castOrNull 函数是一件坏事。这是为什么呢?

我能想到几个原因:

  • 这是一种非常简单且晦涩难懂的方法。晦涩难懂的代码难以阅读、难以维护,是潜在的错误来源(当有人不理解时)并且因此是邪恶的。

  • castOrNull 方法工作的晦涩难懂的方式很可能无法通过 JIT 编译器进行优化。您最终会得到至少 3 个额外的方法调用,以及大量额外的代码来进行类型检查和反射性转换。不必要地使用反射是邪恶的。

(相比之下,简单的方法(instanceof 后跟一个类转换)使用特定的字节码进行 instanceof 和类转换。字节码序列几乎可以肯定会被优化,因此只有一个空检查和不再是在本机代码中测试对象的类型。这是一种常见的模式,JIT 编译器应该很容易检测和优化。)

当然,“邪恶”只是另一种说法,你真的不应该这样做。

您添加的两个示例都没有必要或可取地使用castOrNull 方法。 IMO,从可读性和性能的角度来看,“简单的方式”更好。

【讨论】:

  • 啊,感谢您提供的信息!所以你也会争辩说我不应该使用castOrNull?或者你会如何评价这些对立点与我给定的支持点? (毕竟,避免使用虚拟变量也可以使代码更具可读性和更短。)
  • 我能辨别的唯一优点是在某些情况下避免使用临时变量。对我来说,这纯粹是一个表面问题。使用临时代码不会降低 IMO 代码的可读性,更短的代码也不是可读性优势。 (顺便说一句,它不是一个“虚拟”变量,因为它被使用了。)
【解决方案2】:

从 Java 14 开始,您应该能够同时执行 instanceof 和强制转换。见https://openjdk.java.net/jeps/305

代码示例:

if (obj instanceof String s) {
    // can use s here
} else {
    // can't use s here
}

如果instanceof 的计算结果为真,则定义上述示例中的变量 s。变量的范围取决于上下文。有关更多示例,请参见上面的链接。

【讨论】:

    【解决方案3】:

    在大多数编写/设计良好的 Java 代码中,从不使用 instanceof 和 casts。随着泛型的添加,许多强制类型转换(以及 instanceof)的情况都不再需要了。它们确实如此,有时仍然会发生。

    castOrNull 方法是邪恶的,因为您让 Java 代码看起来“不自然”。从一种语言转换到另一种语言时最大的问题是采用新语言的约定。临时变量在 Java 中就很好。事实上,你的方法所做的只是隐藏临时变量。

    如果您发现自己编写了很多强制转换,您应该检查您的代码并找出原因并寻找删除它们的方法。例如,在您提到添加“getNumberOfChildren”方法的情况下,您可以检查节点是否为空,从而能够在不强制转换的情况下对其进行修剪(这是一种猜测,在这种情况下它可能不适合您)。

    一般来说,转换在 Java 中是“邪恶的”,因为它们通常是不需要的。您的方法更“邪恶”,因为它的编写方式与大多数人期望的 Java 编写方式不同。

    话虽如此,如果你想这样做,那就去做吧。在 Java 中,它实际上并不是“邪恶”的,只是不是“正确”的方式。

    【讨论】:

      【解决方案4】:

      恕我直言,您的castOrNull 不是邪恶的,只是毫无意义。你似乎痴迷于摆脱一个临时变量和一行代码,而对我来说更大的问题是为什么你的代码需要这么多的向下转换?在 OO 中,这几乎总是次优设计的症状。而且我宁愿解决根本原因而不是治疗症状。

      【讨论】:

      • 嗯,Java 中有很多我们没有设计但必须使用的库。 (提到的迭代器就是一个例子)
      • @Nikita,很公平,如果你使用一个设计糟糕但同时又不可或缺的 Java 库,你几乎没有选择。我个人的猜测是,这样的库应该非常罕见。如果一个库使用起来很尴尬,那么要么没有人使用它,要么迟早有人会开始开发更好的替代方案。
      • 我已经扩展了我的问题,以展示我有时使用它的 2 个案例。我真的不喜欢真正从不使用某些东西的硬性规则,我认为我提供的两个案例是您可以这样做的有效案例。你可能比我更极端,但我想你也不会极端到永远不会使用这样的代码。然后,如果你碰巧有这样的情况,我总是喜欢有最干净的解决方案,这就是我在这里要问的。 :)
      • @Albert,我也不喜欢说“从不”,尤其是在 SO ;-) 我职业生涯的大部分时间都在使用遗留代码,所以我必须学会务实关于生活在次优的解决方案中。使用instanceof 是一个标准的习惯用法,任何人都容易理解,而且——正如 Stephen C 所指出的——易于优化。而且我个人对临时变量没有任何问题。
      【解决方案5】:

      我不知道这个人为什么说它是邪恶的。但是,他们推理的一种可能性是,您事后发现了异常,而不是在施放之前进行检查。这是一种方法。

      public static <T> T castOrNull(Object obj, Class<T> clazz) {
          if ( clazz.isAssignableFrom(obj.getClass()) ) {
              return clazz.cast(obj);
          } else {
              return null;
          }
      }
      

      【讨论】:

      • 我怀疑他的意思是因为我要求更好的代码,而我在这里发布的代码是他自己的建议。
      • 在 cmets 中讨论了链接答案的类似替代方案,汤姆不喜欢上述变体。
      • @Albert:错过了......既然是这种情况,我想说这很可能就像彼得的回答所说的那样,是一个次优设计的问题。但实际上,为了弄清楚他为什么认为这样做是错误的,你必须等待他的回应(如果他回复你的话)。
      【解决方案6】:

      Java 异常很慢。如果您试图通过避免双重转换来优化您的性能,那么您使用异常代替逻辑是在自取其辱。永远不要依赖于为您可以合理检查和纠正的事情(正是您正在做的事情)捕获异常。

      How slow are Java exceptions?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-06-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多