Перегрузка метода Java + двойная диспетчеризация

Кто-нибудь может подробно объяснить, почему перегруженный метод print(Parent parent) вызывается при работе с экземпляром Child в моем тестовом фрагменте кода?

Какие-либо особенности виртуальных методов или перегрузки/разрешения методов в Java здесь задействованы? Любая прямая ссылка на Java Lang Spec? Какой термин описывает это поведение? Большое спасибо.

public class InheritancePlay {

    public static class Parent {        
        public void doJob(Worker worker) {
            System.out.println("this is " + this.getClass().getName());

            worker.print(this);
        }
    }

    public static class Child extends Parent {
    }

    public static class Worker {
        public void print(Parent parent) {
            System.out.println("Why this method resolution happens?");
        }

        public void print(Child child) {
            System.out.println("This is not called");
        }
    }

    public static void main(String[] args) {
        Child child = new Child();
        Worker worker = new Worker();

        child.doJob(worker);
    }
}

person Max    schedule 08.05.2010    source источник


Ответы (2)


JLS указывает в §8.4.9 Перегрузка:

  1. Когда метод вызывается (§15.12), количество фактических аргументов (и любых явных аргументов типа) и типы аргументов времени компиляции используются во время компиляции для определения подписи метода. метод, который будет вызываться (§15.12.2).
  2. Если метод, который должен быть вызван, является методом экземпляра, фактический вызываемый метод будет определен во время выполнения с использованием динамического поиска метода (§15.12.4).

Итак, в вашем случае:

  1. Аргумент метода (this) имеет тип времени компиляции Parent, поэтому вызывается метод print(Parent).
  2. Если бы класс Worker был подклассом, и подкласс переопределял бы этот метод, а экземпляр worker принадлежал этому подклассу, то вызывался бы переопределенный метод.

Двойной отправки в Java не существует. Вы должны имитировать это, например. с помощью шаблона посетителя. В этом шаблоне, в основном, каждый подкласс реализует метод accept и вызывает посетителя с this в качестве аргумента, а this имеет тип времени компиляции этого подкласса, поэтому используется желаемая перегрузка метода.

person Christian Semrau    schedule 08.05.2010
comment
Кристиан, спасибо за исчерпывающий ответ! Итак, мы имеем дело здесь с типами времени выполнения и времени компиляции. Я буду копаться в этой теме. (Здесь упоминается двойная отправка, потому что я столкнулся с этим вопросом, изучая тот самый шаблон Visitor :) ). Максимум - person Max; 08.05.2010

Причина в том, что doJob реализован в Parent и не перегружен в Child. Он передает this методу print работника, поскольку this относится к типу Parent, будет вызван метод Worker::print(Parent).

Чтобы вызвать Worker::print(Parent), нужно перегрузить doJob в Child:

public static class Child extends Parent {
    public void doJob(Worker worker) {
        System.out.println("from Child: this is " + this.getClass().getName());

        worker.print(this);
    }
}

В приведенном выше коде this.getClass() в Child эквивалентно Child.class.

person rsp    schedule 08.05.2010
comment
rsp, спасибо за указание на это, но я знаю, что переопределение doJob в Child заставляет прогу работать. Дело в том, что я не понимаю, почему :). Давайте посмотрим на исходный код. Когда мы передаем объект Child в Parent.doJob, отражение Java внутри Parent.doJob доказывает, что мы имеем дело с this типа Child, так почему же разрешение перегруженного метода не удается? - person Max; 08.05.2010
comment
@Max, внутри метода тип this всегда является типом класса, в котором находится метод. Компилятор не может знать, что класс будет унаследован в будущем, он должен использовать знания, которые у него есть на тот момент. . this.getClass() — это информация о времени выполнения, а не о времени компиляции. - person rsp; 08.05.2010