I have 2 classes. Animal and Horse. Animal is the super class, horse extends animal.
public class Animal
{
public void legs()
{
System.out.println(this + " Some animals can have 3 legs, some have 4");
}
}
public class Horse extends Animal
{
public void legs()
{
System.out.println(this + " Horses always have 4 legs");
}
public void tail()
{
System.out.println("A horse definitely has a tail");
}
}
From another class, I am doing some functions on these classes.
public class SCJP_Practice
{
public static void main (String args[])
{
Animal a = new Animal();
Horse h = new Horse();
a.legs();
h.legs();
Animal aa = new Horse();
aa.legs(); // THIS PRINTS THE HORSE VERSION OF LEGS() AND RIGHTLY SO
aa.tail(); // THIS IS THROWING A COMPILATION ERROR
}
}
Theoretically, aa.tail(), while it compiles is still an Animal type, which is why it throws an error, since it can't see the method. But, during run time, it is instantiated as a horse, and it should have the ability to take advantage of the horse method.
If not, what is the point in declaring
Animal aa = new Horse();
What are the advantages of doing this?
解决方案
The declaration Animal aa = new Horse(); doesn't just exist for classes or subclasses; it also exists for interfaces, too:
List animalPen = new LinkedList<>();
The advantages of using the syntax depend on what your left-hand class are.
If you're using pure inheritance, then so long as the method is defined in the parent class, and the parent class is treated as the reference (in this case, it'd be Animal), then you would benefit from virtual method invocation, and Java would use the right method for the right instance assigned to the reference.
If you had different types of animals, and the Animal class had a tail method, and all of those other classes override tail, then the subclasses' tail method would be used instead of the parent class.
If you've got an interface on the LHS, then you're conforming to the interface's compile-time contract. You're stating that, so long as whatever implementation is used conforms to the interface I care about, the implementation-specific portions aren't as crucial.
Formally, the rules are spelled out in the JLS, section 15.12.2.1:
The class or interface determined by compile-time step 1 (§15.12.1) is
searched for all member methods that are potentially applicable to
this method invocation; members inherited from superclasses and
superinterfaces are included in this search.
In addition, if the method invocation has, before the left
parenthesis, a MethodName of the form Identifier, then the search
process also examines all member methods that are (a) imported by
single-static-import declarations (§7.5.3) and static-import-on-demand
declarations (§7.5.4) within the compilation unit (§7.3) within which
the method invocation occurs, and (b) not shadowed (§6.4.1) at the
place where the method invocation appears, to determine if they are
potentially applicable.
A member method is potentially applicable to a method invocation if
and only if all of the following are true:
The name of the member is identical to the name of the method in the method invocation.
The member is accessible (§6.6) to the class or interface in which the method invocation appears.
Whether a member method is accessible at a method invocation depends on the access modifier (public, none, protected, or private)
in the member's declaration and on where the method invocation
appears.
If the member is a variable arity method with arity n, the arity of the method invocation is greater or equal to n-1.
If the member is a fixed arity method with arity n, the arity of the method invocation is equal to n.
If the method invocation includes explicit type arguments, and the member is a generic method, then the number of type arguments is equal
to the number of type parameters of the method.
If the search does not yield at least one method that is potentially applicable, then a compile-time error occurs.