类型转换与提供返回所需类型的方法(Java)的优缺点



我正在做一些关于学习我正在贡献的框架的游戏,然后出现了一个有趣的问题。EDIT:我正在Okapi框架中做一些基本的过滤器,如本指南中所述,注意过滤器必须返回不同的事件类型才能有用,并且资源必须通过引用使用(因为相同的资源可能稍后在其他过滤器中使用)。下面是我正在使用的代码:

    while (filter.hasNext()) {
        Event event = filter.next();
        if (event.isTextUnit()) {
            TextUnit tu = (TextUnit)event.getResource();
            if (tu.isTranslatable()) {
                //do something with it
            }
        }
    }

注意在第4行将资源强制转换为texunit对象。这是有效的,我知道它是一个TextUnit,因为事件是isTextUnit()将始终有一个TextUnit资源。但是,另一种方法是向IResource接口添加一个asTextUnit()方法,该方法将事件作为TextUnit返回(以及每个公共资源类型的等效方法),这样一行将变成:

            TextUnit tu = event.getResource().asTextUnit;

另一种方法可能是在TextUnit本身中提供静态强制转换方法,如下所示:

            TextUnit tu = TextUnit.fromResource(event.getResource());

我的问题是:这样做或那样做的一些论点是什么?是否存在性能差异?

我能想到的asTextUnit()(或.fromResource)的主要优点是,如果有人试图将资源作为错误的类型(即使用像"Cannot get this RawDocument type resource as a TextUnit - use asRawDocument()""The resource is not a TextUnit"这样的消息),可以抛出更合适的异常。

.asTextUnit()的主要缺点我能想到的是,每个资源类型必须实现所有的方法(其中大部分只会抛出一个异常),如果添加另一个主要的资源类型会有一些重构新方法添加到每一个资源类型(尽管没有理由.asSomething()方法必须为每个可能的类型,定义不太常见的资源可能只是演员,虽然这将会导致不一致的方法)。这不会是.fromResource()的问题,因为它每个类型只有一个方法,可以根据偏好为每个类型添加或不添加。

如果目的是测试对象的类型并强制转换它,那么我认为创建/使用自定义isXyzasXyz方法没有任何价值。你只会得到一堆额外的方法,这些方法对代码的可读性几乎没有什么影响。

回复:你关于适当的异常消息的观点,我想说这很可能不值得。我们有理由认为,在需要TextUnit的情况下没有TextUnit是某个地方存在bug的征兆。在我看来,试图为bug提供"用户友好"的诊断是不值得的。信息的目标对象是Java程序员,对于这个人来说,常规ClassCastException的默认消息和堆栈跟踪(以及源代码)提供了所需的所有信息。(把它翻译成漂亮的语言没有任何实际价值。)

另一方面,两种形式之间的性能差异不太可能是显著的。但是考虑一下:

    if (x instanceof Y) {
        ((Y) x).someYMethod();
    }

    if (x.isY()) {
        x.asY().someYMethod();
    }
    boolean isY(X x) { return x instanceof Y; }
    Y asY(X x) { return (Y) x; }

优化器可能在第一个方面比第二个方面做得更好。

  • 在第二种情况下,它可能不会内联方法调用,特别是如果它更改为使用instanceof并抛出自定义异常。

  • 在第二种情况下,不太可能发现实际上只需要一个类型测试。(第一种情况也可能不是……

但无论哪种方式,性能差异都很小。


总之,这些花哨的方法并不值得花精力,尽管它们没有任何真正的危害。


现在,如果isXyzasXyz方法正在测试对象的状态(不仅仅是对象的Java类型),或者如果asXyz返回包装器,那么答案将是不同的…

你也可以直接进入

if (event instanceof TextUnit) {
    // ...
}

为你自己省去麻烦。

要回答您关于是否使用asTextUnit()TextUnit.fromResource的问题,性能差异将取决于您如何实际实现这些方法。

在静态转换器的情况下,您需要创建并返回一个TextUnit类型的新对象。然而,在成员函数的情况下,你可以简单地返回this类型转换,或者你可以创建一个返回一个新对象——这取决于你的用例。

无论哪种方式,似乎instanceof可能是这里最干净的方式。

如果您的过滤器被扩展(或包装)为只返回文本单元事件会怎样?实际上,如果它只返回文本单元事件的资源呢?这样循环就简单多了。我认为最干净的方法是使用第二个过滤器,它只返回文本单元事件,然后是一个Extractor,它返回正确转换的资源。

如果您有一个公共基类,您可以在那里为每个派生类使用单个asXMethod,并且不需要重构所有派生类:

abstract class Base {
   A asA () { throw new InstantiationException ("not an A"); }
   B asB () { throw new InstantiationException ("not an B"); }
   C asC () { throw new InstantiationException ("not an C"); }
   // much more ...
}
class A extends Base {
   A asA () { /* hard work */ return new A (); }  
   // no asB, asC requiered
}
class B extends Base {
   B asB () { /* hard work */ return new B (); }  
   // no asA, asC required
}
// and so on. 

看起来很聪明。对于一个新的类N,只需在Base中添加一个新的方法,所有的派生类都会得到它。只需N实现asN。

但是它有味道。

如果方法a总是失败,为什么B要有方法a ?这不是一个好的设计。如果生成器中的异常没有被触发,那么它们的代价是很低的。只有抛出的异常可能代价高昂。

是的,有区别。创建新的不可变元素比强制类型转换更好。将所有可序列化的数据(非瞬态或可计算数据)传递给Builder并构建相应的类。

最新更新