可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I want to understand the use-case of setting a parent reference to a child object.
Example: Dog class extends Animal class. (No interfaces, mind it)
I would normally create an object of Dog like this:
Dog obj = new Dog();
Now, since Dog is a subclass of Animal it already has access to all of Animal's methods and variables. Then, what difference does this make:
Animal obj = new Dog();
Please provide a proper use-case with an code snippet of it's use. No theoretical articles about 'Polymorphism' or 'Coding to interfaces' please!
Code:
public class Polymorphism {
public static void main(String[] args){
Animal obj1 = new Dog();
Dog obj2 = new Dog();
obj1.shout(); //output is bark..
obj2.shout(); //output is bark..
}
}
class Animal{
public void shout(){
System.out.println("Parent animal's shout");
}
}
class Dog extends Animal{
public void shout(){
System.out.println("bark..");
}
}
class Lion extends Animal{
public void shout(){
System.out.println("roar..");
}
}
class Horse extends Animal{
public void shout(){
System.out.println("neigh");
}
}
Output is the same for both the cases. Then why do we set parent reference to child object?
回答1:
Let me code some time.
List<String> list = new ArrayList<String>;
list.doThis();
list.doThat();
Oh wait ..I'm gone mad. I want to use LinkedList
instead of ArrayList
List<String> list = new LinkedList<String>;
list.doThis();
list.doThat();
Yup, I have to change only declaration part. No need to touch all of my code. Thanks to programming to interfaces and with super classes.
回答2:
Think generally, you will know java casting/oop concept.
Dog
is a type of Animal
so you can assign it to an animal.
But you can't assign Animal
to a Dog
. Because it can be any other animal like Cat
. If you are sure the object is Dog
, you can caste that to Animal
. If the Animal
is of type Dog
then you cannot magically cast to a Goat
.
回答3:
This is an implementation of a principle which says -
Program to an interface, not to an implementation.
As an example, if you design a method to accept a reference of type Animal
, then in future you can easily pass an= Cat
implementation to it (provided of course that the Cat
is a sub-type of Animal
.
Which means -
public void doSomethingWithAnimal(Animal animal) {
// perform some action with/on animal
}
is much more flexible than -
public void doSomethingWithAnimal(Dog d) {
// your code
}
because for the first method, you can easily do something like -
doSomethingWithAnimal(new Cat());
if you ever decide to create new Cat
type, inheriting from Animal
.
回答4:
Although there are some good answers (among the "meh" ones), it seems like none was acceptable for you. Maybe they are too theoretical or contain details that you are not interested in. So another try:
For the example that you described, it does not matter. If you really only have a two-line method like
void doit()
{
Animal x = new Dog();
x.shout();
}
then you could also have written
void doit()
{
Dog x = new Dog();
x.shout();
}
and this would not have a direct disadvantage.
One could even generalize this statement: For a reference that is only used locally, it does not matter. When you declare the reference in the method, and only use this reference in this method, and do not pass it to other methods, then there is no direct advantage in declaring it as Animal
instead of as Dog
. You can to both.
But...
even if you are not interested in this, I can't omit it:
... using the parent type is part of a best practice:
You should always use the least specific type that is sufficient for what you want to do
This has various technical reasons, regarding abstraction, generalization, flexibility, the application of polymorphism, and one could even go so far to call it a sort of "type hygiene".
And this explicitly also refers to the case where the reference is only used locally: If you don't want to call methods that are specific for the type Dog
, but only want to call methods from the Animal
class, then you should make this clear by declaring the variable as an Animal
- simply because that's the least specific type that you need. So there is an indirect advantage of using the type Animal
in these cases - namely that it is clear that the following code will only use methods of the Animal
class, and none of the Dog
class.
One could continue and go very far with further justifications, use case examples and technical details here. But for this, you may refer to the other answers, or some intermediate or advanced texbooks and tutorials.
回答5:
Okay. I think I got my answer.
public class Polymorphism {
public static void main(String[] args){
Animal obj1 = new Horse();
Horse obj2 = new Horse();
obj1.shout(); //output is neigh..
obj2.shout(); //output is neigh..
obj1.winRaces(); /*But this is not allowed and throws compile time error,
even though the object is of Animal type.*/
}
}
class Animal{
public void shout(){
System.out.println("Parent animal's shout");
}
}
class Horse extends Animal{
public void shout(){
System.out.println("neigh..");
}
public void winRaces(){
System.out.println("won race..");
}
}
So, when we use parent reference for child class object, we cannot access any specific methods in child class (that are not present in parent class) using that object.
回答6:
This would be when you want the code that you're writing to work against the Animal interface instead of the Dog implementation. Creating an object in this way makes your code more robust in the long term.
I frequently use:
List<Object> aList = new ArrayList<>();
This is important when defining class level variables, because you want your whole object to work even if you change an unimportant detail later.
回答7:
When you start with such a simple example, you can't see any benefits because you have tightly coupled the variable to the actual type of object it will hold.
Polymorphism comes into its own only when you consider method declarations where the parameter is of the least specific type needed by the method's implementation: then you can call it with any subtype and the method will know what to do with it, even though it has no knowledge of the actual object type. That's the essence of Liskov substitutability of types.
So imagine you have a method
int getAge(Animal a) {
return Days.toYears(currentDate() - a.dateOfBirth());
}
The method will work against any Animal
, even those you defined after defining the method.
But, if you happen to understand the above, yet ask specifically why one would write
Animal a = new Dog();
then it still often makes sense: you promise up-front that you won't refer to any dog-specific aspects of the instance. Typically you'll see
List<String> strings = new ArrayList<>();
and in this case we know that the rest of the code doesn't rely on the specific choice of ArrayList
as list implementation. This is a much smaller difference than the one decribed above, but it's a combination of brevity, safety, and custom which makes it stick.
回答8:
Looking at the question:-
Polymorphism in java: Why do we set parent reference to child object?
In a method like below(Factory Pattern):-
public Animal doSomething(String str){
if(str.equals("dog")){
return new Dog();
}
else if(str.equals("cat")){
return new Cat();
}
else {
return new Animal();
}
}
You get a type of Animal and actual object of either Dog or Cat so calling a method of Animal will call the method overridden in actual Object of either Dog or Cat if the called method is overridden in base class. It provides you with the flexibility at run time to decide which method to run depending on the actual object and overridden method in base class if any.
The complete example is as under :-
package com.test.factory;
public class Animal{
public void shout(){
System.out.println("Parent animal's shout");
}
}
package com.test.factory;
public class Dog extends Animal{
@Override
public void shout(){
System.out.println("bark..");
}
}
package com.test.factory;
public class Horse extends Animal{
@Override
public void shout(){
System.out.println("neigh");
}
}
package com.test.factory;
public class Lion extends Animal{
@Override
public void shout(){
System.out.println("roar..");
}
}
package com.test.factory;
public class AnimalFactory {
public Animal createAnimal(String str){
if(str.equals("dog")){
return new Dog();
}
else if (str.equals("horse")){
return new Horse();
}
else if(str.equals("lion")){
return new Lion();
}
else{
return new Animal();
}
}
}
package com.test.factory;
package com.test.factory;
public class Polymorphism {
public static void main(String[] args){
AnimalFactory factory = new AnimalFactory();
Animal animal = factory.createAnimal("dog");
animal.shout();
animal = factory.createAnimal("lion");
animal.shout();
animal = factory.createAnimal("horse");
animal.shout();
animal = factory.createAnimal("Animal");
animal.shout();
}
}
Output is :-
bark..
roar..
neigh
Parent animal's shout
The AnimalFactory has a createAnimal method which returns Animal. Since Dog, Lion and Horse are all animals. So we are able to create Dog, Lion and Horse objects by using Animal return type. What we achieved using Animal return type is
Animal animal = new Dog();
Animal animal = new Lion();
Animal animal = new Horse();
which is not possible without the Animal return type.
If I use return type as Dog in createAnimal method then it cannot return Lion or Horse and like wise.