我是Java编程的新手,试图掌握OOP的诀窍。

所以我创建了这个抽象类:

public abstract class Vehicle{....}

和2个子类:

public class Car extends Vehicle{....}
public class Boat extends Vehicle{....}

Car和Boat还拥有一些不常见的独特字段和方法(没有相同的名称,所以我无法在Vehicle中为它们定义抽象方法)。

现在在mainClass中我已经设置了我的新车库:

Vehicle[] myGarage= new Vehicle[10];
myGarage[0]=new Car(2,true);
myGarage[1]=new Boat(4,600);

我对多态性非常满意,直到我尝试访问Car特有的一个字段,例如:

boolean carIsAutomatic = myGarage[0].auto;

编译器不接受。我使用类型转换解决了这个问题:

boolean carIsAutomatic = ((Car)myGarage[0]).auto;

工作……但它对方法没有帮助,只对字段有帮助。意思是我做不到

(Car)myGarage[0].doSomeCarStuff();

我的问题是,我的车库里到底有什么?我试图获得直觉,并理解“幕后”发生了什么。


为了方便以后的读者,以下是对答案的简要总结:

Yes, there's a Car in myGarage[] Being a static typed language, the Java compiler will not lend access to methods/fields that are non-"Vehicle", if accessing those through a data structure based on the Vehicle super class( such as Vehicle myGarage[]) As for how to solve, there are 2 main approaches below: Use type casting, which will ease the compiler's concerns and leave any errors in the design to run time The fact that I need casting says the design is flawed. If I need access to non-Vehicle capabilities then I shouldn't be storing the Cars and Boats in a Vehicle based data structure. Either make all those capabilities belong to Vehicle, or use more specific (derived) type based structures In many cases, composition and/or interfaces would be a better alternative to inheritance. Probably the subject of my next question... Plus many other good insights down there, if one does have the time to browse through the answers.


当前回答

如果你使用Java,可以使用反射来检查一个函数是否可用并执行它

其他回答

你问管家:

吉夫斯,还记得我在爪哇岛的车库吗?去看看第一辆停在那里的车是不是自动挡的。

懒惰的吉夫斯说:

但是,先生,如果这是一辆既不能自动也不能非自动的车呢?

这是所有。

好吧,这还不是全部,因为现实中更多的是鸭子类型而不是静态类型。这就是为什么我说吉夫斯很懒。

这是应用访问者设计模式的好地方。

这种模式的美妙之处在于,您可以在一个超类的不同子类上调用不相关的代码,而不必到处执行奇怪的强制转换,也不必在超类中放入大量不相关的方法。

这是通过创建一个Visitor对象并允许我们的Vehicle类接受()访问者来实现的。

您还可以创建许多类型的Visitor,并使用相同的方法调用不相关的代码,只是不同的Visitor实现,这使得这种设计模式在创建干净的类时非常强大。

举个例子:

public class VisitorDemo {

    // We'll use this to mark a class visitable.
    public static interface Visitable {

        void accept(Visitor visitor);
    }

    // This is the visitor
    public static interface Visitor {

        void visit(Boat boat);

        void visit(Car car);

    }

    // Abstract
    public static abstract class Vehicle implements Visitable {

            // NO OTHER RANDOM ABSTRACT METHODS!

    }

    // Concrete
    public static class Car extends Vehicle {

        public void doCarStuff() {
            System.out.println("Doing car stuff");
        }

        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }

    }

    // Concrete
    public static class Boat extends Vehicle {

        public void doBoatStuff() {
            System.out.println("Doing boat stuff");
        }

        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }

    }

    // Concrete visitor
    public static class StuffVisitor implements Visitor {

        @Override
        public void visit(Boat boat) {
            boat.doBoatStuff();
        }

        @Override
        public void visit(Car car) {
            car.doCarStuff();
        }
    }

    public static void main(String[] args) {
        // Create our garage
        Vehicle[] garage = {
            new Boat(),
            new Car(),
            new Car(),
            new Boat(),
            new Car()
        };

        // Create our visitor
        Visitor visitor = new StuffVisitor();

        // Visit each item in our garage in turn
        for (Vehicle v : garage) {
            v.accept(visitor);
        }
    }

}

如您所见,StuffVisitor允许您在Boat或Car上调用不同的代码,这取决于调用哪个访问实现。您还可以创建Visitor的其他实现,以使用相同的.visit()模式调用不同的代码。

还要注意,使用此方法时,不使用instanceof或任何hack类检查。类之间唯一重复的代码是方法void accept(Visitor)。

例如,如果你想支持3种类型的具体子类,你也可以将该实现添加到Visitor接口中。

你的车库包含车辆,所以编译器静态控制视图中你有一个车辆,因为。auto是一个Car字段,你不能访问它,动态地它是一个Car,所以转换不会产生一些问题,如果它是一个船,你试图将转换为Car会在运行时引发异常。

要回答你的问题,你可以找到你的车库里到底有什么,你可以做下面的事情:

Vehicle v = myGarage[0];

if (v instanceof Car) {
   // This vehicle is a car
   ((Car)v).doSomeCarStuff();
} else if(v instanceof Boat){
   // This vehicle is a boat
   ((Boat)v).doSomeBoatStuff();
}

更新:正如你可以从下面的评论中读到的,这种方法对于简单的解决方案是可以的,但它不是一个好的实践,特别是如果你的车库里有大量的车辆。所以,只有在你知道车库不会太大的情况下才使用它。如果不是这样,在堆栈溢出时搜索“avoid instanceof”,有多种方法可以做到这一点。

如果对基类型进行操作,则只能访问它的公共方法和字段。

如果你想访问扩展类型,但有一个基类型的字段存储它(在你的情况下),你首先必须强制转换它,然后你可以访问它:

Car car = (Car)myGarage[0];
car.doSomeCarStuff();

或更短,没有温度字段:

((Car)myGarage[0]).doSomeCarStuff();

由于您使用的是Vehicle对象,所以只能从它们的基类调用方法,而不能强制转换。因此,对于车库来说,最好区分不同数组(或者更好的列表)中的对象——数组通常不是一个好主意,因为它在处理方面远不如基于collection的类灵活。