[英]Java best practice: casting objects vs interfaces
假设我们有以下玩具界面:
interface Speakable
{
public abstract void Speak();
}
interface Flyer
{
public abstract void Fly();
}
我们有一个实现两个接口的类:
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");
}
}
在这一点上,我看到了在Duck
上调用方法的不同方法,我无法确定哪一个是最佳实践。 考虑这种情况:
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);
}
}
这个程序将按预期运行,因为传入函数的对象恰好可以转换为Flyer
和Speakable
,但是当我看到这样的代码时,我感到畏缩,因为它不允许编译时类型检查,并且由于它可以抛出紧密耦合意外的异常,例如当一个不同类型的对象(不能转换为任何一个或一个接口)作为参数传入时,或者如果Duck
实现更改,那么它不再实现Flyer
。
我看到Java代码一直都是这样编写的,有时候是在教科书中(例如O'Reilly的“Head First Design Patterns”第300页)所以我必须要有一个我缺少的优点。
如果我要编写类似的代码,我会尽量避免向下转换为无法保证的类型或接口。 例如,在这种情况下,我会做这样的事情:
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!");
}
}
这将允许我这样做:
private static void SafeSpeakAndFly(SpeakingFlyer x)
{
x.Speak();
x.Fly();
}
public static void main(String[] args)
{
BuzzLightyear bly= new BuzzLightyear();
SafeSpeakAndFly(bly);
}
这是不必要的矫枉过正吗? 这样做有什么陷阱?
我觉得这个设计将SafeSpeakAndFly()
函数与其参数分离,并且由于编译时类型检查而阻止了令人讨厌的错误。
为什么第一种方法在实践中如此广泛地使用而后者不是?
我看到Java代码一直都是这样编写的,有时候是在教科书中(例如O'Reilly的“Head First Design Patterns”第300页)所以我必须要有一个我缺少的优点。
这本书最初发表于2004年,我认为Java当时并不支持Generics。 因此,不安全的铸造是当时非常常用的。 可能,如果我没有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();
}
}
但是,拥有泛型可以让我们这样做:
private static <T extends Speakable & Flyer> void reallySafeSpeakAndFly(T x) {
x.Speak();
x.Fly();
}
在这里,编译器可以确保我们没有传递一些没有实现Speakable
和Flyer
东西,并且可以在编译时检测到这些Speakable
尝试。
为什么第一种方法在实践中如此广泛地使用而后者不是?
我想,你可能已经看过很多遗留代码了。 :)
您可以强制参数与Speakable
和Flyer
同时使用类型交集创建方法泛型:
private <T extends Speakable & Flyer> static void DangerousSpeakAndFly(T x) {
// use any of `Speakable` or `Flyer` methods of `x`
}
因此,您不需要转换或创建其他接口。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.