简体   繁体   English

Java最佳实践:转换对象与接口

[英]Java best practice: casting objects vs interfaces

Suppose we have the following toy interfaces: 假设我们有以下玩具界面:

interface Speakable
{
    public abstract void Speak();
}

interface Flyer
{
    public abstract void Fly();
}

and we have a class that implements both interfaces: 我们有一个实现两个接口的类:

class Duck implements Speakable, Flyer
{
    public void Speak()
    {
        System.out.println("quack quack don't eat me I taste bad.");
    }

    public void Fly()
    {
        System.out.println("I am flying");
    }
}

At this point I see different ways to invoke methods on Duck and I can't decide which one is best practice. 在这一点上,我看到了在Duck上调用方法的不同方法,我无法确定哪一个是最佳实践。 Consider this scenario: 考虑这种情况:

public class Lab 
{
        private static void DangerousSpeakAndFly(Object x)
        {
            Speakable temp  = (Speakable) x;
            temp.Speak();
            Flyer temp2= (Flyer) x;
            temp2.Fly();
        }

        public static void main(String[] args) 
        {
            Duck daffy= new Duck();
            DangerousSpeakAndFly(daffy);
        }
}

This program will behave as expected, because the object passed in to the function happens to be castable to Flyer and Speakable , but I cringe when I see code like this because it does not allow compile time type checking and due to tight coupling it can throw unexpected exceptions for example when a differently typed object (not castable to either or one of the interfaces) is passed in as parameter, or if implementation of Duck changes down the line so it no longer implements Flyer . 这个程序将按预期运行,因为传入函数的对象恰好可以转换为FlyerSpeakable ,但是当我看到这样的代码时,我感到畏缩,因为它不允许编译时类型检查,并且由于它可以抛出紧密耦合意外的异常,例如当一个不同类型的对象(不能转换为任何一个或一个接口)作为参数传入时,或者如果Duck实现更改,那么它不再实现Flyer

I see Java code written like this all the time, sometimes in textbooks (for example pg. 300 of "Head First Design Patterns" by O'Reilly) so there must be a merit in it that I am missing. 我看到Java代码一直都是这样编写的,有时候是在教科书中(例如O'Reilly的“Head First Design Patterns”第300页)所以我必须要有一个我缺少的优点。

If I were to write similar Code I would try to avoid downcasting to a type or interface that is not guaranteed. 如果我要编写类似的代码,我会尽量避免向下转换为无法保证的类型或接口。 for example in this scenario I would do something like this: 例如,在这种情况下,我会做这样的事情:

interface SpeakingFlyer extends Flyer, Speakable
{

}

class BuzzLightyear implements SpeakingFlyer
{
    public void Speak()
    {
        System.out.println("My name is Buzz");
    }
    public void Fly()
    {
        System.out.println("To infinity and beyond!");
    }
}

Which would allow me to do: 这将允许我这样做:

private static void SafeSpeakAndFly(SpeakingFlyer x)
{
    x.Speak();
    x.Fly();
}

public static void main(String[] args) 
{
    BuzzLightyear bly= new BuzzLightyear();
    SafeSpeakAndFly(bly);
}

Is this an unnecessary overkill? 这是不必要的矫枉过正吗? what are the pitfalls for doing this? 这样做有什么陷阱?

I feel like this design decouples the SafeSpeakAndFly() function from its parameters and keeps nasty bugs at bay due to compile time type checking. 我觉得这个设计将SafeSpeakAndFly()函数与其参数分离,并且由于编译时类型检查而阻止了令人讨厌的错误。

Why is the first method used so extensively in practice and the latter isn't? 为什么第一种方法在实践中如此广泛地使用而后者不是?

I see Java code written like this all the time, sometimes in textbooks (for example pg. 300 of "Head First Design Patterns" by O'Reilly) so there must be a merit in it that I am missing. 我看到Java代码一直都是这样编写的,有时候是在教科书中(例如O'Reilly的“Head First Design Patterns”第300页)所以我必须要有一个我缺少的优点。

This book was initially published back in 2004 and I don't think Java was supporting Generics at that time. 这本书最初发表于2004年,我认为Java当时并不支持Generics。 So unsafe casting was something that was very commonly used then. 因此,不安全的铸造是当时非常常用的。 Probably, if I didn't have the support of parametric polymorphism in Java, I would first check if the parameter is an instance of the type I'd like to cast it to and then do the actual cast: 可能,如果我没有Java中参数多态的支持,我首先要检查参数是否是我想要将其转换为的类型的实例,然后执行实际的转换:

private static void dangerousSpeakAndFly(Object x) {
    if (x instanceof Speakable) {
        Speakable temp  = (Speakable) x;
        temp.Speak();
    }
    if (x instanceof Flyer) {
        Flyer temp2= (Flyer) x;
        temp2.Fly();
    }
}

Having Generics, however, lets us do this: 但是,拥有泛型可以让我们这样做:

private static <T extends Speakable & Flyer> void reallySafeSpeakAndFly(T x) {
    x.Speak();
    x.Fly();
}

Here, the compiler can make sure we're not passing something that doesn't implement Speakable and Flyer and can detect such sassy attempts at compile-time. 在这里,编译器可以确保我们没有传递一些没有实现SpeakableFlyer东西,并且可以在编译时检测到这些Speakable尝试。

Why is the first method used so extensively in practice and the latter isn't? 为什么第一种方法在实践中如此广泛地使用而后者不是?

It might be that you've seen a lot of legacy code, I suppose. 我想,你可能已经看过很多遗留代码了。 :) :)

You can enforce the argument to be at the same time Speakable and Flyer making a method generic with type intersection: 您可以强制参数与SpeakableFlyer同时使用类型交集创建方法泛型:

private <T extends Speakable & Flyer> static void DangerousSpeakAndFly(T x) { 
    // use any of `Speakable` or `Flyer` methods of `x`
}

thus you don't need casting nor creating additional interface. 因此,您不需要转换或创建其他接口。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM