JAVA 中的动态实例化优势

Dynamic instantiation advantages in JAVA

我有 2 个 class。动物和马。动物是超级动物class,马延伸动物

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");
    }
}

来自另一个 class,我正在这些 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

    }
}

理论上,aa.tail() 在编译时仍然是 Animal 类型,这就是它抛出错误的原因,因为它看不到方法。但是,在运行时间里,它被实例化为一匹马,它应该有利用马方法的能力。

如果不是,声明的意义何在

Animal aa = new Horse();

这样做有什么好处?

But, during run time, it is instantiated as a horse, and it should have the ability to take advantage of the horse method.

如果在运行时,您的代码喜欢这样做,它仍然可以将实例类型转换为

Animal aa = new Horse(); // or, received as an argument

if (aa instanceof Horse) {
  // Let's do Horse things
  Horse hh = (Horse) aa;
  hh.tail();
}

编译器不能假定 Animal 引用会指向 Horse 实例,因此将 aa.tail() 标记为错误。

What are the advantages of doing this?

多态性,如您自己的代码所示

aa.legs(); // prints the Horse version

对象是一个Horse

引用类型是Animal

编译器不会尝试预测稍后在运行时,Animal 引用将指向 Horse 实例。

尽可能使用超类 Animal 类型的一个优点是,它使代码更 可重用 用于其他种类的动物。

声明 Animal aa = new Horse(); 不仅仅存在于 classes 或 subclasses;它也适用于接口:

List<Animal> animalPen = new LinkedList<>();

使用语法的优势取决于您的左手 class 是什么。

如果您使用的是纯继承,那么只要该方法在父 class 中定义,并且父 class 被视为引用(在这种情况下,它' d 为 Animal),那么您将受益于 virtual method invocation,并且 Java 将对分配给引用的正确实例使用正确的方法。

如果你有不同类型的动物,并且 Animal class 有一个 tail 方法,所有其他 classes 覆盖 tail,那么将使用子classes的tail方法代替父class.

如果您在 LHS 上有一个接口,那么您就符合该接口的编译时契约。您是在说,只要使用的任何实现符合我关心的接口,特定于实现的部分就不那么重要了。

形式上,规则在 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.