Java 我的车库里真的有车吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24883075/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Do I really have a car in my garage?
提问by T-Rex
I'm a newbie to Java programming, trying to get the hang of OOP.
我是 Java 编程的新手,试图掌握 OOP 的窍门。
So I built this abstract class:
所以我构建了这个抽象类:
public abstract class Vehicle{....}
and 2 subclasses:
和 2 个子类:
public class Car extends Vehicle{....}
public class Boat extends Vehicle{....}
Car
and Boat
also hold some unique fields and methods that aren't common (don't have the same name, so I can't define an abstract method for them in Vehicle).
Car
并且Boat
还包含一些不常见的独特字段和方法(没有相同的名称,因此我无法在 Vehicle 中为它们定义抽象方法)。
Now in mainClass I have setup my new Garage:
现在在 mainClass 我已经设置了我的新车库:
Vehicle[] myGarage= new Vehicle[10];
myGarage[0]=new Car(2,true);
myGarage[1]=new Boat(4,600);
I was very happy with polymorphism until I tried to access one of the fields that are unique to Car, such as:
我对多态非常满意,直到我尝试访问 Car 独有的字段之一,例如:
boolean carIsAutomatic = myGarage[0].auto;
The compiler doesn't accept that. I worked around this issue using casting:
编译器不接受。我使用强制转换解决了这个问题:
boolean carIsAutomatic = ((Car)myGarage[0]).auto;
That works... but it doesn't help with methods, just fields. Meaning I can't do
那行得通……但它对方法没有帮助,只是对字段有帮助。意思是我做不到
(Car)myGarage[0].doSomeCarStuff();
So my question is - what do I really have in my garage? I'm trying to get the intuition as well as understand what's going on "behind the scenes".
所以我的问题是 - 我的车库里到底有什么?我试图获得直觉并了解“幕后”发生了什么。
for the sake of future readers, a short summary of the answers below:
为了将来的读者,以下答案的简短摘要:
- Yes, there's a
Car
inmyGarage[]
- 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.
- 是的,有一个
Car
在myGarage[]
- 作为一种静态类型语言,如果通过基于 Vehicle 超类的数据结构(例如
Vehicle myGarage[]
)访问非“Vehicle”的方法/字段,Java 编译器将不会提供访问 - 至于如何解决,主要有以下2种方法:
- 使用类型转换,这将减轻编译器的顾虑,并将设计中的任何错误留给运行时
- 我需要铸造的事实表明设计有缺陷。如果我需要访问非车辆功能,那么我不应该将汽车和船只存储在基于车辆的数据结构中。要么让所有这些功能都属于 Vehicle,要么使用更具体的(派生的)基于类型的结构
- 在许多情况下,组合和/或接口将是继承的更好替代方案。可能是我下一个问题的主题......
- 再加上许多其他很好的见解,如果有人有时间浏览答案的话。
回答by Leonid
You garage contains Vehicles, so the compiler static control view that you have a Vehicle and as .auto is a Car field you can't access it, dynamically it is a Car so the cast don't create some problem, if it will be a Boat and you try to make cast to Car will rise an exception on runtime.
你的车库包含车辆,所以编译器静态控制视图你有一个 Vehicle 并且因为 .auto 是一个 Car 字段你不能访问它,动态它是一个 Car 所以演员不会产生一些问题,如果它会的话a Boat 并且您尝试将 cast 转换为 Car 将在运行时引发异常。
回答by Alexander Rühl
If you operate on the base type, you can only access public methods and fields of it.
如果对基类型进行操作,则只能访问其公共方法和字段。
If you want to access the extended type, but have a field of the base type where it's stored (as in your case), you first have to cast it and then you can access it:
如果你想访问扩展类型,但有一个存储它的基本类型的字段(如你的情况),你首先必须转换它,然后你可以访问它:
Car car = (Car)myGarage[0];
car.doSomeCarStuff();
Or shorter without temp field:
或者更短的没有临时字段:
((Car)myGarage[0]).doSomeCarStuff();
Since you are using Vehicle
objects, you can only call methods from the base class on them without casting. So for your garage it may be advisable to distinguish the objects in different arrays - or better lists - an array is often not a good idea, since it's far less flexible in handling than a Collection
-based class.
由于您使用的是Vehicle
对象,因此您只能从它们的基类中调用方法而无需强制转换。因此,对于您的车库,最好区分不同数组中的对象 - 或者更好的列表 - 数组通常不是一个好主意,因为它在处理上远不如Collection
基于类的灵活。
回答by HamoriZ
You defined that your garage will store vehicles, so you do not care what type of vehicles you have. The vehicles have common features like engine, wheel, behavior like moving. The actual representation of these features might be different, but at abstract layer are the same. You used abstract class which means that some attributes, behaviors are exactly the same by both vehicle. If you want to express that your vehicles have common abstract features then use interface like moving might mean different by car and boat. Both can get from point A to point B, but in a different way (on wheel or on water - so the implementation will be different) So you have vehicles in the garage which behave the same way and you do not car about the specific features of them.
您定义了您的车库将存放车辆,因此您不关心您拥有什么类型的车辆。这些车辆具有发动机、车轮、移动等行为等共同特征。这些特征的实际表示可能不同,但在抽象层是相同的。您使用了抽象类,这意味着两种车辆的某些属性、行为完全相同。如果你想表达你的车辆具有共同的抽象特征,那么使用像移动这样的界面可能意味着汽车和船的不同。两者都可以从 A 点到达 B 点,但方式不同(在轮子上或在水上 - 因此实施方式会有所不同)所以您车库中的车辆行为方式相同,并且您不会关注特定功能其中。
To answer the comment:
要回答评论:
Interface means a contract which describes how to communicate with the outer world. In the contract you define that your vehicle can move, can be steered, but you do not describe how it will actually work, it is described in the implementation.By abstract class you might have functions where you share some implementation, but you also have function which you do not know how it will be implemented.
接口是指描述如何与外部世界进行通信的契约。在合同中你定义你的车辆可以移动,可以转向,但你没有描述它的实际工作方式,它在实现中描述。通过抽象类,你可能有共享一些实现的功能,但你也有您不知道它将如何实现的功能。
One example of using abstract class:
使用抽象类的一个例子:
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();
}
}
You will use the same steps by each vehicle, but the implementation of the steps will differ by vehicle type. Car might use GPS, boat might use sonar to identify where it is.
您将对每辆车使用相同的步骤,但步骤的实施将因车辆类型而异。汽车可能使用 GPS,船可能使用声纳来识别它的位置。
回答by danizmax
To answer your question you can find out what exactly is in your garage you do the following:
要回答您的问题,您可以执行以下操作,找出车库中到底有什么:
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();
}
UPDATE: As you can read from the comments below, this method is okay for simple solutions but it is not good practice, particularly if you have a huge number of vehicles in your garage. So use it only if you know the garage will stay small. If that's not the case, search for "Avoiding instanceof" on stack overflow, there are multiple ways to do it.
更新:正如您从下面的评论中所读到的,这种方法适用于简单的解决方案,但不是一个好习惯,特别是如果您的车库中有大量车辆。所以只有当你知道车库会保持很小的时候才使用它。如果不是这种情况,请在堆栈溢出时搜索“Avoiding instanceof”,有多种方法可以做到。
回答by Jean Logeart
If you need to make the difference between Car
and Boat
in your garage, then you should store them in distinct structures.
如果您需要在车库之间Car
和Boat
车库中进行区分,那么您应该将它们存放在不同的结构中。
For instance:
例如:
public class Garage {
private List<Car> cars;
private List<Boat> boats;
}
Then you can define methods that are specific on boats or specific on cars.
然后您可以定义特定于船只或特定于汽车的方法。
Why have polymorphism then?
那为什么会有多态呢?
Let's say Vehicle
is like:
让我们说Vehicle
就像:
public abstract class Vehicle {
protected int price;
public getPrice() { return price; }
public abstract int getPriceAfterYears(int years);
}
Every Vehicle
has a price so it can be put inside the Vehicle
abstract class.
每个Vehicle
都有一个价格,所以它可以放在Vehicle
抽象类中。
Yet, the formula determining the price after n years depends on the vehicle, so it left to the implementing class to define it. For instance:
然而,确定 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));
}
}
The Boat
class may have an other definition for getPriceAfterYears
and specific attributes and methods.
该Boat
班可能有一个其他的定义getPriceAfterYears
和具体的属性和方法。
So now back in the Garage
class, you can define:
所以现在回到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();
}
The interest of polymorphism is to be able to call getPriceAfterYears
on a Vehicle
withoutcaring about the implementation.
多态的好处是能够调用getPriceAfterYears
aVehicle
而无需关心实现。
Usually, downcasting is a sign of a flawed design: do not store your vehicles all together if you need to differenciate their actual type.
通常,向下倾斜是设计有缺陷的标志:如果您需要区分它们的实际类型,请不要将您的车辆全部存放在一起。
Note: of course the design here can be easily improved. It is just an example to demonstrate the points.
注意:当然这里的设计可以很容易地改进。这只是一个例子来证明要点。
回答by badp
Your problem here is at a more fundamental level: you built Vehicle
in such a way that Garage
needs to know more about its objects than the Vehicle
interface gives away. You should try and build the Vehicle
class from the Garage
perspective (and in general from the perspective of everything that's going to use Vehicle
): what kind of things do they need to do with their vehicles? How can I make those things possible with my methods?
你的问题是在一个更基本的层面上:你Vehicle
以这样一种方式构建,Garage
需要了解更多关于它的对象而不是Vehicle
接口所提供的信息。您应该尝试Vehicle
从以下Garage
角度构建类(并且通常从将要使用的所有事物的角度来看Vehicle
):他们需要对他们的车辆做什么样的事情?我怎样才能用我的方法使这些事情成为可能?
For example, from your example:
例如,从你的例子:
bool carIsAutomatic = myGarage[0].auto;
Your garage want to know about a vehicle's engine for... reasons? Anyway, there is no need for this to be just exposed by Car
. You can still expose an unimplemented isAutomatic()
method in Vehicle
, then implement it as return True
in Boat
and return this.auto
in Car
.
您的车库想知道车辆的发动机是出于什么原因?无论如何,没有必要仅通过Car
. 您仍然可以在 中公开未实现的isAutomatic()
方法Vehicle
,然后将其实现为return True
inBoat
和return this.auto
in Car
。
It would be even better to have a three-valued EngineType
enum (HAS_NO_GEARS
, HAS_GEARS_AUTO_SHIFT
, HAS_GEARS_MANUAL_SHIFT
), which would let your code reason on the actual characteristics of a generic Vehicle
cleanly and accurately. (You'd need this distinction to handle motorbikes, anyway.)
拥有一个三值EngineType
枚举 ( HAS_NO_GEARS
, HAS_GEARS_AUTO_SHIFT
, HAS_GEARS_MANUAL_SHIFT
)会更好,它可以让您的代码Vehicle
清晰准确地推理泛型的实际特征。(无论如何,您需要这种区别来处理摩托车。)
回答by Sylvain Leroux
I'm a newbie to Java programming, trying to get the hang of OOP.
我是 Java 编程的新手,试图掌握 OOP 的窍门。
Just my 2 cents — I will try to make it short as many interesting things have already been said. But, in fact, there is two questions here. One about "OOP" and one about how it is implemented in Java.
只是我的 2 美分——我会尽量缩短它,因为已经说过很多有趣的事情了。但实际上,这里有两个问题。一个是关于“OOP”的,另一个是关于它是如何在 Java 中实现的。
First of all, yes, you havea car in your garage. So your assumptions are right. But, Java is a statically typedlanguage. And the type system in the compiler can only "know" the type of your various object by their corresponding declaration. Not by their usage. If you have an array of Vehicle
, the compiler only knows that. So it will check that you only perform operation allowed on anyVehicle
. (In other words, methodsand attributesvisible in the Vehicle
declaration).
首先,是的,你的车库里有一辆车。所以你的假设是正确的。但是,Java 是一种静态类型语言。并且编译器中的类型系统只能通过它们相应的声明“知道”各种对象的类型。不是他们的用法。如果您有一个 数组Vehicle
,则编译器只知道这一点。因此它会检查您是否只对任何Vehicle
. (换句话说,方法和属性在Vehicle
声明中可见)。
You can explain to the compiler that "you in fact know this Vehicle
is a Car
", by using an explicit cast (Car)
. the compiler will believe you -- even if in Javathere is a check at run-time, that might lead to a ClassCastException
that prevent further damages if you lied(other language like C++ won't check at run-time - you have to know what you do)
您可以使用显式强制转换向编译器解释“您实际上知道这Vehicle
是一个Car
”(Car)
。编译器会相信你——即使在 Java 中在运行时有一个检查,ClassCastException
如果你撒谎,这可能会导致进一步的损害(其他语言,如 C++ 不会在运行时检查——你必须知道你做什么)
Finally, if you really need, you might rely of run-time type identification (i.e.: instanceof
) to check the "real" type of an object before attempting to cast it. But this is mostly considered as a bad practice in Java.
最后,如果您真的需要,您可能会依赖运行时类型识别(即instanceof
:)在尝试强制转换之前检查对象的“真实”类型。但这在 Java 中通常被认为是一种不好的做法。
As I said, this is the Java way of implementing OOP. There is whole different classfamilyof languages broadly known as "dynamic languages", that only check at run-timeif an operation is allowed on an object or not. With those languages, you don't need to "move up" all the common methods to some (possibly abstract) base class to satisfy the type system. This is called duck typing.
正如我所说,这是实现 OOP 的 Java 方式。有完全不同班级广泛称为“动态语言”的语言家族,仅在运行时检查是否允许对对象进行操作。使用这些语言,您不需要将所有通用方法“上移”到某个(可能是抽象的)基类以满足类型系统。这称为鸭子类型。
回答by einpoklum
You asked your butler:
你问你的管家:
Jeeves, remember my garage on the Isle of Java? Go check whether the first vehicle parked there is automatic.
Jeeves,还记得我在爪哇岛上的车库吗?去检查停在那里的第一辆车是否是自动的。
and lazy Jeeves said:
懒惰的吉维斯说:
but sir, what if it's a vehicle that can't be automatic or non-automatic?
但是先生,如果是不能自动或非自动的车辆怎么办?
That's all.
就这样。
Ok, that's not really all since reality is more duck-typed than statically typed. That's why I said Jeeves is lazy.
好吧,这还不是全部,因为现实比静态类型更像鸭子类型。这就是为什么我说Jeeves很懒。
回答by jimbobmcgee
I'm really just pooling the ideas of the others here (and I'm not a Java guy, so this is pseudo rather than actual) but, in this contrived example, I would abstract my car checking approach into a dedicated class, that only knows about cars and only cares about cars when looking at garages:
我真的只是在这里汇集其他人的想法(我不是 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;
}
}
Point is, you've already decided you only care about cars when you ask about the car's transmission. So just ask the CarInspector. Thanks to the tri-state Enum, you can now know whether it is automatic or even if it is not a car.
重点是,当您询问汽车的变速箱时,您已经决定只关心汽车。因此,只需询问 CarInspector。多亏了三态 Enum,您现在可以知道它是自动的还是不是汽车。
Of course, you'll need different VehicleInspectors for each vehicle you care about. And you have just pushed the problem of which VehicleInspector to instantiate up the chain.
当然,您需要为您关心的每辆车配备不同的 VehicleInspector。您刚刚将哪个 VehicleInspector 实例化链的问题推到了链上。
So instead, you might want to look at interfaces.
因此,您可能需要查看接口。
Abstract getTransmission
out to an interface (e.g. HasTransmission
). That way, you can check if a vehicle has a transmission, or write an TransmissionInspector:
抽象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;
}
}
Now you are saying, you only about transmission, regardless of Vehicle, so can ask the TransmissionInspector. Both the bus and the car can be inspected by the TransmissionInspector, but it can only ask about the transmission.
现在你是说,你只关心传输,而不管车辆,所以可以问传输检查员。公共汽车和汽车都可以通过TransmissionInspector进行检查,但它只能询问传输。
Now, you might decide that boolean values are not all you care about. At that point, you might prefer to use a generic Supported type, that exposes both the supported state and the value:
现在,您可能认为布尔值并不是您关心的全部。此时,您可能更喜欢使用通用的 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;
}
}
Now your Inspector might be defined as:
现在您的 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]));
}
}
As I've said, I'm not a Java guy, so some of the syntax above may be wrong, but the concepts should hold. Nevertheless, don't run the above anywhere important without testing it first.
正如我所说,我不是 Java 人,所以上面的一些语法可能是错误的,但这些概念应该成立。尽管如此,请不要在没有先测试的情况下在任何重要的地方运行上述内容。
回答by Jay Harris
CreateVehicle level fields that will help make each individual Vehicle more distinct.
创建车辆级别字段,这将有助于使每个单独的车辆更加独特。
public abstract class Vehicle {
public final boolean isCar;
public final boolean isBoat;
public Vehicle (boolean isCar, boolean isBoat) {
this.isCar = isCar;
this.isBoat = isBoat;
}
}
Setthe Vehicle level fields in the inheriting class to the appropriate value.
将继承类中的 Vehicle 级别字段设置为适当的值。
public class Car extends Vehicle {
public Car (...) {
super(true, false);
...
}
}
public class Boat extends Vehicle {
public Boat (...) {
super(false, true);
...
}
}
Implementusing the Vehicle level fields to properly decipher the vehicle type.
实现使用车用级字段正确解码车型。
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();
}
Since your telling your compiler that everything in your garage is a Vehicle, your stuck with the Vehicle class level methods and fields. If you want to properly decipher the Vehicle type, then you should set some class level fields e.g. isCar
and isBoat
that will give you the programmer a better understanding of what type of Vehicle you are using.
由于您告诉编译器您车库中的所有东西都是 Vehicle,因此您坚持使用 Vehicle 类级别的方法和字段。如果您想正确解读 Vehicle 类型,那么您应该设置一些类级别的字段,例如isCar
,isBoat
这将使程序员更好地了解您正在使用的 Vehicle 类型。
Java is a type safe language so its best to always type check before handling data that has been casted like your Boat
s and Car
s.
Java 是一种类型安全的语言,因此最好在处理像Boat
s 和Car
s一样已转换的数据之前始终进行类型检查。