我车库里真的有车吗?

T-R*_*Rex 259 java inheritance

我是Java编程的新手,试图掌握OOP.

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

public abstract class Vehicle{....}
Run Code Online (Sandbox Code Playgroud)

和2个子类:

public class Car extends Vehicle{....}
public class Boat extends Vehicle{....}
Run Code Online (Sandbox Code Playgroud)

Car并且Boat还包含一些不常见的唯一字段和方法(不具有相同的名称,因此我无法在Vehicle中为它们定义抽象方法).

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

Vehicle[] myGarage= new Vehicle[10];
myGarage[0]=new Car(2,true);
myGarage[1]=new Boat(4,600);
Run Code Online (Sandbox Code Playgroud)

在我尝试访问Car独有的一个字段之前,我对多态性非常满意,例如:

boolean carIsAutomatic = myGarage[0].auto;
Run Code Online (Sandbox Code Playgroud)

编译器不接受.我使用cast来解决这个问题:

boolean carIsAutomatic = ((Car)myGarage[0]).auto;
Run Code Online (Sandbox Code Playgroud)

这有效......但它对方法没有帮助,只是字段.意思是我做不到

(Car)myGarage[0].doSomeCarStuff();
Run Code Online (Sandbox Code Playgroud)

所以我的问题是 - 我的车库里到底有什么?我试图获得直觉以及了解"幕后"发生了什么.


为了将来的读者,请简要总结以下答案:

  1. 是的,有一个CarmyGarage[]
  2. 作为一种静态类型语言,如果通过基于Vehicle超类的数据结构访问那些方法/字段,Java编译器将不会访问非"Vehicle"的方法/字段(例如Vehicle myGarage[])
  3. 至于如何解决,有以下两种主要方法:
    1. 使用类型转换,这将减轻编译器的顾虑,并将设计中的任何错误留给运行时
    2. 我需要铸造的事实说设计是有缺陷的.如果我需要访问非车辆功能,那么我不应该将汽车和船只存储在基于车辆的数据结构中.要么使所有这些功能属于Vehicle,要么使用更具体(派生)类型的结构
  4. 在许多情况下,组合和/或接口将是继承的更好替代方案.可能是我下一个问题的主题......
  5. 如果有人有时间浏览答案,那么还有许多其他好的见解.

Jea*_*art 147

如果您需要之间的区别Car,并Boat在你的车库,那么你应该将它们存储在不同的结构.

例如:

public class Garage {
    private List<Car> cars;
    private List<Boat> boats;
}
Run Code Online (Sandbox Code Playgroud)

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

为什么会有多态?

让我们说Vehicle是这样的:

public abstract class Vehicle {
   protected int price;
   public getPrice() { return price; }
   public abstract int getPriceAfterYears(int years);
}
Run Code Online (Sandbox Code Playgroud)

每个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));  
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

多态的兴趣是能够getPriceAfterYearsVehicle 关心实现的情况下调用.

通常,向下倾斜是设计有缺陷的标志:如果您需要区分其实际类型,请不要将您的车辆全部存放在一起.

注意:当然这里的设计很容易改进.这只是一个展示要点的例子.

  • 在这种情况下,我不认为向下转换有什么问题.如果你的车库有这么多的海湾,而且每个海湾都可以被汽车或船只占用,那么OP的设计就非常有意义.这里的设计,以及单独的汽车和船只列表意味着,无论何时添加新型车辆(摩托车,拖车等),您都必须更新代码,这肯定比铸造更糟糕. (30认同)
  • 为什么会有多态?谁说我们应该?我家里的车肯定不知道它的价值多少.那将是某些CarMarketApp的任务.我想说的价格可能不是最好的例子,也许我们应该使用构图而不是继承. (12认同)
  • 使用继承和多态的主要原因:你大多**不关心汽车和船之间的区别.(如果不是这样,那么继续使用单独的列表) (9认同)
  • @Gabe:`公共功能canDrive(车辆车辆){for(技能技能:技能){if(vehicle.canBeDrivenWith(skill)){return true; 返回false; }`.现在只有车辆需要知道它的传输.而现在你的皮卡车代客可以将拖车列为一项技能,你*不必完全做其他事情 - 当它到来的时候,他们只会在拖车/船上使用他们的牵引技术而不是试图启动并驱动它. (4认同)

dan*_*max 84

要回答您的问题,您可以了解您的车库中究竟是什么,您可以执行以下操作:

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();
}
Run Code Online (Sandbox Code Playgroud)

更新:正如你可以从下面的评论中读到的,这种方法对于简单的解决方案是可以的,但这不是一个好的做法,特别是如果你的车库里有大量的车辆.所以只有当你知道车库会保持小的时候才使用它.如果不是这种情况,请在堆栈溢出时搜索"避免instanceof",有多种方法可以执行此操作.

  • 这里有一个重要的注意事项:如果你发现自己经常使用很多`instanceof`,你应该退一步看看你是否可以用更多态的方式重写你的代码. (104认同)
  • @T-Rex如果汽车`doSomeCarStuff`和船`doSomeBoatStuff`,难道你不能说`车的全部`doSomeStuff`?你知道它是一种车辆,每种类型的车辆都可以做(也可能是东西).你不一定需要知道什么,`车辆`可以解决这个问题.一个车库并不关心汽车驶出而船被拖走,它只知道现在有一个空位. (5认同)
  • 在任何情况下,我都建议至少使用`else {throw new UnsupportedVehicleError(v); 最后那里,除非你确定*什么都不做才是处理除汽车或船只以外的任何和所有车辆的正确方法. (4认同)
  • @ T-Rex:你已经得到了@Keppil的答案 - 不,不是.如果可能,避免使用`instanceof`.上面的代码有助于证明这一点,但不适合常用. (2认同)

Ale*_*ühl 22

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

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

Car car = (Car)myGarage[0];
car.doSomeCarStuff();
Run Code Online (Sandbox Code Playgroud)

或者没有温度场时更短:

((Car)myGarage[0]).doSomeCarStuff();
Run Code Online (Sandbox Code Playgroud)

由于您正在使用Vehicle对象,因此您只能在不使用强制转换的情况下从基类调用方法.因此,对于你的车库,建议区分不同阵列中的对象 - 或更好的列表 - 数组通常不是一个好主意,因为它的处理灵活性远远低于Collection基于类的.

  • @JuannStrauss:我反对 - 因为toString()不适用于那个用例,所以switch将是一个脆弱的构造,因为它意味着期望每个子类也这样做. (3认同)

Ham*_*riZ 13

您定义了您的车库将存储车辆,因此您不关心您拥有什么类型的车辆.这些车辆具有发动机,车轮,移动等行为等共同特征.这些特征的实际表示可能不同,但在抽象层是相同的.你使用了抽象类,这意味着两种车辆的某些属性,行为完全相同.如果你想表达你的车辆有共同的抽象功能,那么使用移动界面可能意味着汽车和船只的不同.两者都可以从A点到达B点,但是以不同的方式(在轮子上或在水上 - 所以实施方式会有所不同)所以你在车库里的车辆行为方式相同而且你不关心具体功能他们

回答评论:

接口是指描述如何与外部世界通信的合同.在合同中,您定义您的车辆可以移动,可以操纵,但是您没有描述它将如何实际工作,它在实现中描述.通过抽象类,您可能具有共享某些实现的功能,但您也有你不知道它将如何实现的功能.

使用抽象类的一个例子:

    abstract class Vehicle {

    protected abstract void identifyWhereIAm();
    protected abstract void startEngine();
    protected abstract void driveUntilIArriveHome();
    protected abstract void stopEngine();

    public void navigateToHome() {
        identifyWhereIAm();
        startEngine();
        driveUntilIArriveHome();
        stopEngine();
    } 
}
Run Code Online (Sandbox Code Playgroud)

您将使用每辆车的相同步骤,但步骤的实施因车辆类型而异.汽车可能会使用GPS,船可能会使用声纳识别它的位置.


Syl*_*oux 13

我是Java编程的新手,试图掌握OOP.

仅仅是我的2美分 - 我会尝试缩短它,因为已经说过许多有趣的事情.但事实上,这里有两个问题.一个关于"OOP",一个关于如何在Java中实现它.

首先,是的,你的车库里一辆车.所以你的假设是正确的.但是,Java是一种静态类型语言.编译器中的类型系统只能通过相应的声明 "知道"各种对象的类型.不是他们的用法.如果你有一个数组Vehicle,编译器只知道它.因此它会检查您是否只执行任何 操作Vehicle.(换句话说,声明中可见的方法属性Vehicle).

您可以通过使用显式强制转换向编译器解释"您实际上知道这Vehicle是一个Car"(Car).编译器会相信你 - 即使在Java中运行时有一个检查,ClassCastException如果你撒谎 可能会导致阻止进一步损坏(其他语言如C++不会在运行时检查 - 你必须知道你做什么)

最后,如果你真的需要,你可能会依赖于运行时类型识别(即:) instanceof来检查对象的"真实"类型,然后再尝试强制转换它.但这在Java中被认为是一种不好的做法.

正如我所说,这是实现OOP的Java方式.有完全不同 广泛称为"动态语言"的语言系列,仅在运行时检查是否允许对象进行操作.使用这些语言,您不需要将所有常用方法"向上移动"到某些(可能是抽象的)基类以满足类型系统.这叫做鸭子打字.


ein*_*ica 9

你问过你的管家:

Jeeves,还记得我在爪哇岛的车库吗?去检查停在那里的第一辆车是否是自动的.

和懒惰的Jeeves说:

但先生,如果这是一辆不能自动或非自动的车辆怎么办?

就这样.

好吧,这并不是全部,因为现实比静态类型更像是鸭子型.这就是我说Jeeves很懒的原因.


bad*_*adp 8

这里的问题处于一个更基础的层面:你构建Vehicle的方式Garage需要了解更多关于其对象的信息而不是Vehicle接口.你应该尝试VehicleGarage角度来构建这个类(一般来说,从所有将要使用的角度来看Vehicle):他们需要对他们的车辆做什么样的事情?如何用我的方法使这些事情成为可能?

例如,从您的示例:

bool carIsAutomatic = myGarage[0].auto;
Run Code Online (Sandbox Code Playgroud)

你的车库想知道车辆发动机的原因是什么?无论如何,没有必要让它暴露出来Car.您仍然可以在其中公开未实现的isAutomatic()方法Vehicle,然后return True在in Boatreturn this.autoin中实现它Car.

它甚至会更好,有一个三值EngineType枚举(HAS_NO_GEARS,HAS_GEARS_AUTO_SHIFT,HAS_GEARS_MANUAL_SHIFT),这将让我们在一个通用的实际特点代码原因Vehicle干净,准确.(无论如何,你需要这种区别来处理摩托车.)


Leo*_*nid 7

你的车库包含车辆,所以编译器静态控制视图你有一个车辆和.auto是一个你无法访问它的Car字段,动态它是一个Car所以演员不会产生一些问题,如果它将是一个船,你试图使铸造到汽车将在运行时上升异常.


Dav*_* Xu 7

这是应用Visitor设计模式的好地方.

这种模式的优点是你可以在超类的不同子类上调用不相关的代码,而不必在任何地方进行奇怪的转换或将大量不相关的方法放入超类中.

这可以通过创建一个Visitor对象并允许我们的Vehicleaccept()访问者来实现.

您还可以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);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

如您所见,StuffVisitor允许您调用不同的代码BoatCar根据调用的实现visit调用.您还可以创建访问者的其他实现,以使用相同的.visit()模式调用不同的代码.

另请注意,使用此方法时,不会使用instanceof任何hacky类检查.类之间唯一重复的代码是方法void accept(Visitor).

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


jim*_*gee 6

我真的只是在这里汇集其他人的想法(我不是一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

重点是,当你询问汽车的变速器时,你已经决定只关心汽车了.所以请问CarInspector.感谢三态Enum,您现在可以知道它是自动的还是不是汽车.

当然,对于您关心的每辆车,您都需要不同的VehicleInspectors.而你刚刚推出了哪个VehicleInspector实例化链的问题.

相反,您可能希望查看接口.

摘要getTransmission到界面(例如HasTransmission).这样,您可以检查车辆是否有传输,或者写一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你说,你只关于传输,不管是否有车辆,所以可以问一下TransmissionInspector.总线和汽车都可以通过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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在您的Inspector可能被定义为:

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]));
    }
}
Run Code Online (Sandbox Code Playgroud)

正如我所说,我不是Java人,所以上面的一些语法可能是错的,但概念应该成立.尽管如此,如果不首先进