我是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的人,所以这是伪的而不是实际的),但是,在这个人为的例子中,我将把我的汽车检查方法抽象为一个专门的类,它只知道汽车,只关心查看车库时的汽车:

abstract class Vehicle { 
    public abstract string getDescription() ;
}

class Transmission {
    public Transmission(bool isAutomatic) {
        this.isAutomatic = isAutomatic;
    }
    private bool isAutomatic;
    public bool getIsAutomatic() { return isAutomatic; }
}

class Car extends Vehicle {
    @Override
    public string getDescription() { 
        return "a car";
    }

    private Transmission transmission;

    public Transmission getTransmission() {
        return transmission;
    }
}

class Boat extends Vehicle {
    @Override
    public string getDescription() {
        return "a boat";
    }
}

public enum InspectionBoolean {
    FALSE, TRUE, UNSUPPORTED
}

public class CarInspector {
    public bool isCar(Vehicle v) {
        return (v instanceof Car);
    }
    public bool isAutomatic(Car car) {
        Transmission t = car.getTransmission();
        return t.getIsAutomatic();
    }
    public bool isAutomatic(Vehicle vehicle) {
        if (!isCar(vehicle)) throw new UnsupportedVehicleException();
        return isAutomatic((Car)vehicle);
    }
    public InspectionBoolean isAutomatic(Vehicle[] garage, int bay) {
        if (!isCar(garage[bay])) return InspectionBoolean.UNSUPPORTED;
        return isAutomatic(garage[bay]) 
             ? InspectionBoolean.TRUE
             : InspectionBoolean.FALSE;
    }
}

重点是,当你问汽车的传动装置时你已经决定只关心汽车了。所以只要问问CarInspector就行了。多亏了三状态枚举,你现在可以知道它是自动的,甚至它不是一辆车。

当然,你需要为你关心的每辆车使用不同的vehicleinspector。你已经把哪个VehicleInspector实例化的问题推到了链上。

因此,您可能想要查看接口。

抽象getTransmission到接口(例如hastrtransmission)。这样,你就可以检查车辆是否有变速器,或者写一个TransmissionInspector:

abstract class Vehicle { }

class Transmission {
    public Transmission(bool isAutomatic) {
        this.isAutomatic = isAutomatic;
    }
    private bool isAutomatic;
    public bool getIsAutomatic() { return isAutomatic; }
}

interface HasTransmission { 
    Transmission getTransmission(); 
}

class Car extends Vehicle, HasTransmission {
    private Transmission transmission;

    @Override
    public Transmission getTransmission() {
        return transmission;
    }
}

class Bus extends Vehicle, HasTransmission {
    private Transmission transmission;

    @Override
    public Transmission getTransmission() {
        return transmission;
    }
}

class Boat extends Vehicle { }

enum InspectionBoolean {
    FALSE, TRUE, UNSUPPORTED
}

class TransmissionInspector {
    public bool hasTransmission(Vehicle v) {
        return (v instanceof HasTransmission);
    }
    public bool isAutomatic(HasTransmission h) {
        Transmission t = h.getTransmission();
        return t.getIsAutomatic();
    }
    public bool isAutomatic(Vehicle v) {
        if (!hasTranmission(v)) throw new UnsupportedVehicleException();
        return isAutomatic((HasTransmission)v);
    }
    public InspectionBoolean isAutomatic(Vehicle[] garage, int bay) {
        if (!hasTranmission(garage[bay])) return InspectionBoolean.UNSUPPORTED;
        return isAutomatic(garage[bay]) 
             ? InspectionBoolean.TRUE
             : InspectionBoolean.FALSE;
    }
}

现在你说,你只关心变速器,而不考虑车辆,所以可以问变速器检查员。TransmissionInspector可以检查公共汽车和汽车,但它只能询问有关变速器的信息。

现在,您可能认为布尔值并不是您所关心的全部。在这一点上,你可能更喜欢使用泛型的Supported类型,它同时暴露了受支持的状态和值:

class Supported<T> {
    private bool supported = false;
    private T value;

    public Supported() { }
    public Supported(T value) { 
        this.isSupported = true;
        this.value = value; 
    }

    public bool isSupported() { return supported; }
    public T getValue() { 
        if (!supported) throw new NotSupportedException();
        return value;
    }
}

现在你的检查器可能被定义为:

class TransmissionInspector {
    public Supported<bool> isAutomatic(Vehicle[] garage, int bay) {
        if (!hasTranmission(garage[bay])) return new Supported<bool>();
        return new Supported<bool>(isAutomatic(garage[bay]));
    }

    public Supported<int> getGearCount(Vehicle[] garage, int bay) {
        if (!hasTranmission(garage[bay])) return new Supported<int>();
        return new Supported<int>(getGearCount(garage[bay]));
    }
}

正如我说过的,我不是一个Java人,所以上面的一些语法可能是错误的,但这些概念应该是成立的。然而,在没有测试之前,不要在任何重要的地方运行上面的代码。

其他回答

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

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”,有多种方法可以做到这一点。

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

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

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

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

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

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

创建车辆级别字段,这将有助于使每个单独的车辆更加独特。

public abstract class Vehicle {
    public final boolean isCar;
    public final boolean isBoat;

    public Vehicle (boolean isCar, boolean isBoat) {
        this.isCar  = isCar;
        this.isBoat = isBoat;
    }
}

将继承类中的Vehicle level字段设置为适当的值。

public class Car extends Vehicle {
    public Car (...) {
        super(true, false);
        ...
    }
}

public class Boat extends Vehicle {
    public Boat (...) {
        super(false, true);
        ...
    }
}

实现使用车辆级别字段正确地破译车辆类型。

boolean carIsAutomatic = false;

if (myGarage[0].isCar) {
    Car car = (Car) myGarage[0];
    car.carMethod();
    carIsAutomatic = car.auto;
}

else if (myGarage[0].isBoat) {
    Boat boat = (Boat) myGarage[0];
    boat.boatMethod();
}

因为你告诉编译器车库中的所有东西都是Vehicle,所以你只能使用Vehicle类级别的方法和字段。如果你想正确地破译车辆类型,那么你应该设置一些类级别的字段,例如isCar和isBoat,这将使你的程序员更好地理解你正在使用的车辆类型。

Java是一种类型安全的语言,所以在处理像Boats和Cars这样已转换的数据之前,最好总是进行类型检查。

Modeling objects you want to present in a program (in order to solve some problem) is one thing, coding is another story. In your code, I think essentially it's inappropriate to model a garage using array. Arrays shouldn't be often considered as objects, although they do appear to be, usually for the sake of self-contained-ness sort of integrity of a language and providing some familiarity, but array as a type is really just a computer-specific thing, IMHO, especially in Java, where you can't extend arrays.

我知道正确地建模一个类来表示车库并不能帮助回答“车库中的汽车”的问题;只是一个建议。

回到代码。除了一些面向对象的问题之外,一些问题将有助于创建一个场景,从而更好地理解你想要解决的问题(假设有一个问题,而不仅仅是“得到一些问题”):

谁或什么东西想了解carIsAutomatic? 给定carIsAutomatic,谁或什么将执行doSomeCarStuff?

It might be some inspector, or someone who knows only how to drive auto-transmission cars, etc., but from the garage's perspective, all it knows is it holds some vehicle, therefore (in this model) it is the responsibility of this inspector or driver to tell if it's a car or a boat; at this moment, you may want to start creating another bunch of classes to represent similar types of *actor*s in the scene. Depends on the problem to be resolved, if you really have to, you can model the garage to be a super intelligent system so it behaves like a vending machine, instead of a regular garage, that has a button says "Car" and another says "Boat", so that people can push the button to get a car or a boat as they want, which in turn makes this super intelligent garage responsible for telling what (a car or a boat) should be presented to its users; to follow this improvisation, the garage may require some bookkeeping when it accepts a vehicle, someone may have to provide the information, etc., all these responsibilities go beyond a simple Main class.

说了这么多,当然我理解编写面向对象程序的所有麻烦,以及样板,特别是当它试图解决的问题非常简单时,但面向对象确实是解决许多其他问题的可行方法。根据我的经验,有了一些提供用例的输入,人们开始设计对象如何相互交互的场景,将它们分类为类(以及Java中的接口),然后使用像Main类这样的东西来引导世界。

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