Java 最佳实践:转换对象与接口
Java best practice: casting objects vs interfaces
假设我们有以下玩具界面:
interface Speakable
{
public abstract void Speak();
}
interface Flyer
{
public abstract void Fly();
}
我们有一个 class 实现了两个接口:
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()
函数与其参数分离开来,并且由于编译时类型检查而避免了严重的错误。
为什么第一种方法在实践中被广泛使用而后一种方法却没有?
您可以强制参数同时为 Speakable
和 Flyer
使方法具有类型交集的泛型:
private <T extends Speakable & Flyer> static void DangerousSpeakAndFly(T x) {
// use any of `Speakable` or `Flyer` methods of `x`
}
因此您不需要转换或创建额外的界面。
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.
这本书最初是在 2004 年出版的,我认为 Java 当时并不支持泛型。所以 unsafe casting 是当时非常常用的东西。可能,如果我在 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
的内容,并且可以在编译时检测到此类大胆的尝试。
Why is the first method used so extensively in practice and the latter isn't?
我想您可能已经看到了很多遗留代码。 :)
假设我们有以下玩具界面:
interface Speakable
{
public abstract void Speak();
}
interface Flyer
{
public abstract void Fly();
}
我们有一个 class 实现了两个接口:
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()
函数与其参数分离开来,并且由于编译时类型检查而避免了严重的错误。
为什么第一种方法在实践中被广泛使用而后一种方法却没有?
您可以强制参数同时为 Speakable
和 Flyer
使方法具有类型交集的泛型:
private <T extends Speakable & Flyer> static void DangerousSpeakAndFly(T x) {
// use any of `Speakable` or `Flyer` methods of `x`
}
因此您不需要转换或创建额外的界面。
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.
这本书最初是在 2004 年出版的,我认为 Java 当时并不支持泛型。所以 unsafe casting 是当时非常常用的东西。可能,如果我在 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
的内容,并且可以在编译时检测到此类大胆的尝试。
Why is the first method used so extensively in practice and the latter isn't?
我想您可能已经看到了很多遗留代码。 :)