我是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.


当前回答

你在这里的问题是在一个更基本的层面上:你以这样一种方式构建了Vehicle,车库需要了解更多关于它的对象的信息,而不是Vehicle接口所提供的信息。您应该尝试从Garage透视图来构建Vehicle类(通常从所有将使用Vehicle的东西的透视图来构建):他们需要用他们的车辆做什么样的事情?我怎样才能用我的方法实现这些呢?

例如,从你的例子:

bool carIsAutomatic = myGarage[0].auto;

你的车库想知道一辆车的发动机…原因吗?不管怎样,没有必要只是通过Car来曝光。你仍然可以在Vehicle中暴露一个未实现的isAutomatic()方法,然后在Boat中实现它为return True并返回this。Car中的auto。

如果有一个三值的EngineType enum (HAS_NO_GEARS, HAS_GEARS_AUTO_SHIFT, HAS_GEARS_MANUAL_SHIFT)会更好,这会让你的代码清晰而准确地推断出通用车辆的实际特征。(无论如何,你需要区分摩托车。)

其他回答

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

这只是我的观点——我将尽量简短,因为很多有趣的事情已经说过了。但实际上,这里有两个问题。一个是关于“面向对象”的,一个是关于它如何在Java中实现的。

首先,是的,你的车库里有辆车。所以你的假设是对的。但是,Java是一种静态类型语言。而编译器中的类型系统只能通过相应的声明来“知道”各种对象的类型。不是他们的习惯。如果你有一个Vehicle数组,编译器只知道它。所以它会检查你只在任何车辆上执行允许的操作。(换句话说,方法和属性在Vehicle声明中可见)。

你可以通过显式类型转换(Car)向编译器解释“你实际上知道这个Vehicle是Car”。编译器会相信你——即使在Java中有一个在运行时的检查,这可能会导致一个ClassCastException,以防止进一步的损害,如果你撒谎(其他语言,如c++不会在运行时检查-你必须知道你在做什么)

最后,如果你真的需要,你可以依靠运行时类型标识(即:instanceof)在尝试强制转换之前检查对象的“真实”类型。但在Java中,这通常被认为是一种糟糕的实践。

正如我所说,这是实现OOP的Java方式。有完全不同的类语言家族,通常被称为“动态语言”,它们只在运行时检查是否允许对对象进行操作。使用这些语言,您不需要将所有公共方法“上移”到某些(可能是抽象的)基类来满足类型系统。这就是所谓的鸭子打字。

你的车库包含车辆,所以编译器静态控制视图中你有一个车辆,因为。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和Boat,那么你应该将它们存储在不同的结构中。

例如:

public class Garage {
    private List<Car> cars;
    private List<Boat> boats;
}

然后,您可以定义特定于船或特定于汽车的方法。

那为什么要多态呢?

假设Vehicle是这样的:

public abstract class Vehicle {
   protected int price;
   public getPrice() { return price; }
   public abstract int getPriceAfterYears(int years);
}

每个Vehicle都有一个价格,所以它可以放在Vehicle抽象类中。

然而,决定n年后价格的公式取决于车辆,因此留给实现类来定义它。例如:

public Car extends Vehicle {
    // car specific
    private boolean automatic;
    @Override
    public getPriceAfterYears(int years) {
        // losing 1000$ every year
        return Math.max(0, this.price - (years * 1000));  
    }
}

Boat类可能对getPriceAfterYears和特定的属性和方法有其他定义。

现在回到Garage类,你可以定义:

// car specific
public int numberOfAutomaticCars() {
    int s = 0;
    for(Car car : cars) {
        if(car.isAutomatic()) {
            s++;
        }
    }
    return s;
}
public List<Vehicle> getVehicles() {
    List<Vehicle> v = new ArrayList<>(); // init with sum
    v.addAll(cars);
    v.addAll(boats);
    return v;
}
// all vehicles method
public getAveragePriceAfterYears(int years) {
    List<Vehicle> vehicules = getVehicles();
    int s = 0;
    for(Vehicle v : vehicules) {
        // call the implementation of the actual type!
        s += v.getPriceAfterYears(years);  
    }
    return s / vehicules.size();
}

多态性的兴趣在于能够调用车辆上的getPriceAfterYears而不关心实现。

通常,下倾是设计缺陷的标志:如果需要区分车辆的实际类型,请不要将所有车辆存储在一起。

注:当然这里的设计可以很容易地改进。这只是一个例子来证明这一点。

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