我是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;
编译器不接受。我使用cast来解决这个问题:
boolean carIsAutomatic = ((Car)myGarage[0]).auto;
这有效......但它对方法没有帮助,只是字段。意思是我不能做
(Car)myGarage[0].doSomeCarStuff();
所以我的问题是 - 我的车库里到底有什么?我试图获得直觉以及了解“幕后”发生了什么。
为了未来的读者,请简要总结以下答案:
Car
myGarage[]
Vehicle myGarage[]
)访问那些方法/字段,Java编译器将不会访问非“Vehicle”的方法/字段。 答案 0 :(得分:147)
如果您需要在车库中区分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
上调用Vehicle
,而无需关注实施。
通常,向下转向是设计有缺陷的标志:如果您需要区分其实际类型,请不要将您的车辆全部存放在一起。
注意:当然这里的设计很容易改进。这只是一个证明要点的例子。
答案 1 :(得分: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();
}
更新:正如您可以从下面的评论中读到的,这种方法对于简单的解决方案是可以的,但这不是一个好的做法,特别是如果你的车库里有大量的车辆。所以只有当你知道车库会保持小的时候才使用它。如果情况并非如此,请搜索&#34;避免实例&#34;在堆栈溢出时,有多种方法可以做到。
答案 2 :(得分:22)
如果您使用基本类型,则只能访问它的公共方法和字段。
如果你想访问扩展类型,但是有一个存储它的基本类型的字段(如你的情况),你首先必须强制转换它然后你可以访问它:
Car car = (Car)myGarage[0];
car.doSomeCarStuff();
在没有临时场的情况下更短:
((Car)myGarage[0]).doSomeCarStuff();
由于您使用的是Vehicle
个对象,因此您只能在不使用强制转换的情况下从基类调用方法。因此,对于你的车库,建议区分不同阵列中的对象 - 或者更好的列表 - 数组通常不是一个好主意,因为它在处理方面的灵活性远远低于基于Collection
的类
答案 3 :(得分: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();
}
}
您将使用每辆车的相同步骤,但步骤的实施因车辆类型而异。汽车可能会使用GPS,船可能会使用声纳识别它的位置。
答案 4 :(得分:13)
我是Java编程的新手,试图掌握OOP。
仅仅是我的2美分 - 我会尝试缩短它,因为已经说过许多有趣的事情。但事实上,这里有两个问题。一个关于&#34; OOP&#34;以及如何用Java实现它。
首先,是的,你在你的车库里有一辆车。所以你的假设是正确的。但是,Java是一种静态类型语言。并且编译器中的类型系统只能知道&#34;各种对象的类型由相应的声明组成。不是他们的用法。如果您有一个Vehicle
数组,编译器只知道它。因此,它会检查您是否仅在任何 Vehicle
上执行操作。 (换句话说,Vehicle
声明中可见方法和属性。
你可以向编译器解释&#34;你实际上知道这个Vehicle
是Car
&#34; ,通过使用显式转换{{ 1}}。编译器会相信你 - 即使 in Java 在运行时进行检查,这可能会导致(Car)
阻止进一步损坏,如果你撒谎 (像C ++这样的其他语言在运行时不会检查 - 你必须知道你做了什么)
最后,如果你真的需要,你可以依靠运行时类型识别(即:ClassCastException
)来检查&#34;真实的&#34;尝试强制转换之前的对象类型。但这在Java中被认为是一种不好的做法。
正如我所说,这是实现OOP的Java方式。有完全不同的 class 系列语言,通常称为&#34;动态语言&#34; ,仅在运行时检查-time 如果对象是否允许操作。使用这些语言,您不需要向上移动&#34;一些(可能是抽象的)基类的所有常用方法,以满足类型系统。这称为duck typing。
答案 5 :(得分:9)
你问过你的管家:
Jeeves,还记得我在爪哇岛的车库吗?检查停在那里的第一辆车是否是自动的。
懒惰的Jeeves说:
但先生,如果它是一辆不能自动或非自动的车辆怎么办?
这就是全部。
好吧,这并不是全部,因为现实比静态打字更像鸭子。这就是我说Jeeves懒惰的原因。
答案 6 :(得分:8)
您的问题处于更基础的层面:您构建Vehicle
的方式Garage
需要了解更多关于其对象的信息,而不是Vehicle
接口。您应该尝试从Vehicle
角度构建Garage
类(并且通常从将要使用的所有内容Vehicle
的角度来看):它们是什么类型的东西需要与他们的车辆?如何用我的方法使这些事情成为可能?
例如,从您的示例:
bool carIsAutomatic = myGarage[0].auto;
你的车库想要了解车辆发动机的原因吗?无论如何,Car
没有必要公开这个。您仍然可以在isAutomatic()
中公开未实现的Vehicle
方法,然后将其return True
中的Boat
和return this.auto
中的Car
实现。
拥有一个三值EngineType
枚举(HAS_NO_GEARS
,HAS_GEARS_AUTO_SHIFT
,HAS_GEARS_MANUAL_SHIFT
)会更好,这会让你的代码推理实际特征一般的Vehicle
干净准确。 (无论如何,你需要这种区别来处理摩托车。)
答案 7 :(得分:7)
你的车库包含车辆,所以你有车辆的编译器静态控制视图和.auto是一个你无法访问它的Car字段,动态地它是一辆Car所以演员不会创建一些问题,如果它将是一个船,你试图使铸造到汽车将在运行时上升异常。
答案 8 :(得分:7)
这是应用Visitor
设计模式的好地方。
这种模式的优点是你可以在超类的不同子类上调用不相关的代码,而不必在任何地方进行奇怪的转换,或者将大量不相关的方法放入超类中。
这可以通过创建Visitor
对象并允许我们的Vehicle
班级到accept()
访问者来实现。
您还可以使用相同的方法创建多种类型的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
上调用不同的代码,具体取决于调用visit
的实现。您还可以创建访问者的其他实现,以使用相同的.visit()
模式调用不同的代码。
另请注意,使用此方法时,不会使用instanceof
或任何hacky类检查。类之间唯一重复的代码是方法void accept(Visitor)
。
例如,如果您想支持3种类型的具体子类,您也可以将该实现添加到Visitor
接口中。
答案 9 :(得分: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;
}
}
重点是,当你询问汽车的变速器时,你已经决定只关心汽车了。所以请问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;
}
}
现在你说,你只关于传输,无论车辆,所以可以问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;
}
}
现在您的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]));
}
}
正如我所说,我不是Java人,所以上面的一些语法可能是错误的,但概念应该成立。尽管如此,如果没有先测试,请不要将上述内容放在任何重要位置。
答案 10 :(得分:2)
创建车辆级别字段,有助于使每个车辆更加独特。
public abstract class Vehicle {
public final boolean isCar;
public final boolean isBoat;
public Vehicle (boolean isCar, boolean isBoat) {
this.isCar = isCar;
this.isBoat = isBoat;
}
}
将继承类中的“车辆级别”字段设置为。
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是一种类型安全的语言,因此在处理像Boat
和Car
s一样的数据之前,最好始终键入check。
答案 11 :(得分:2)
如果您使用Java,可以使用反射来检查函数是否可用并执行它
答案 12 :(得分:0)
对要在程序中呈现的对象进行建模(为了解决某些问题)是一回事,编码是另一回事。在您的代码中,我认为基本上使用数组建模车库是不合适的。数组通常不应被视为对象,尽管它们出现,通常是为了自包含语言的完整性并提供一些熟悉,但数组作为一种类型实际上只是一个特定于计算机的东西,恕我直言,尤其是在Java中,你无法扩展数组。
我知道正确建模一个类来代表一个车库将无助于回答你的“车库中的汽车”问题;只是一条建议。
回到代码。除了对OOP有所了解之外,一些问题对于创建一个场景会有所帮助,从而更好地理解你想要解决的问题(假设有一个问题,而不仅仅是“挂起”):
carIsAutomatic
?carIsAutomatic
,执行doSomeCarStuff
的人或者是什么?可能是一些检查员,或者只知道如何驾驶自动变速车等的人,但从车库的角度来看,它只知道它拥有一些车辆,因此(在这个模型中)它是责任这名检查员或司机告诉他们这是一辆汽车还是一艘船;此时,您可能想要开始创建另一组类来表示场景中类似类型的* actor * s。取决于要解决的问题,如果你真的需要,你可以把车库建模成一个超级智能系统,所以它的行为就像一个自动售货机,而不是一个普通的车库,有一个按钮说“汽车”,另一个说“船”,以便人们可以按下按钮来获得他们想要的汽车或船只,这反过来使这个超级智能车库负责告诉应该向用户呈现什么(汽车或船);为了遵循这种即兴创作,车库在接受车辆时可能需要一些簿记,有人可能需要提供信息等,所有这些责任都超出了简单的 Main 类。
说了这么多,当然我理解所有麻烦,以及样板,编写一个OO程序,特别是当它试图解决的问题非常简单时,OO确实是解决许多其他问题的可行方法。根据我的经验,通过一些输入提供用例,人们开始设计场景如何相互交互,将它们分类为类(以及Java中的接口),然后使用类似 Main 的内容用于引导世界的类。