Подтвердить что ты не робот

Как производный класс может вызвать частный метод базового класса?

public class PrivateOverride {  

    private void f() {  
        System.out.println("private f()");  
    }
}  

public class Derived extends PrivateOverride {  

    public void f() {                         //this method is never run.
        System.out.println("public f()");     
    }  
}  

public static void main(String[] args) {

    // instantiate Derived and assign it to 
    // object po of type PrivateOverride.
    PrivateOverride po = new Derived();  

    // invoke method f of object po.  It
    // chooses to run the private method of PrivateOveride
    // instead of Derived
    po.f();                         
  }  
}  

Итак, вывод этого кода private f(). Теперь возникает вопрос: как может po, являющийся объектом Derived Class, вызывать частный метод PrivateOverride, который является его базовым классом?

4b9b3361

Ответ 1

Потому что вы определили основной метод в классе PrivateOverride. Если вы поместите основной метод в класс Derived, он не будет компилироваться, потому что .f() там не будет отображаться.

po.f() вызов в классе PrivateOverride не является полиморфизмом, потому что класс f() in PrivateOverride имеет значение private, поэтому класс f() в Derived не переопределяется.

Ответ 2

Я действительно не вижу проблемы. Этот метод называется "внутри" " класса, это в значительной степени ожидается. Этот метод не переоценивается вообще, вместо этого он затенен другим.

Ответ 3

Методы в Java отправляются в зависимости от статического типа получателя, который в данном случае является PrivateOverride. Не путайте с тем, что переменная po, проверяя код, может содержать только экземпляр Derived в этой строке: только объявление имеет значение при поиске доступных методов.

И, кстати, вызов f() даже не транслируется в виртуальный вызов в последнем байт-коде, потому что, когда компилятор ищет потенциально применимые методы в классе PrivateOverride, он находит только Object и определение f(), которое видно только потому, что метод main() определен в самом PrivateOverride (см. JLS 15.12)

Ответ 4

При вызове метода JVM должен выяснить, какую часть кода выполнить: иногда это выполняется во время выполнения (например, при переопределении методов); иногда это делается во время компиляции (например, при перегрузке). Как только JVM разрешит, какой бит кода он выполняет фактический экземпляр, на который вы ссылаетесь, на самом деле не более значителен, чем любой другой параметр.

Приведенный пример кода устанавливает сценарий, который может выглядеть как переопределение метода, но не так, поэтому метод заканчивается получением привязки во время компиляции. Модификатор видимости private не нарушается, потому что вызов не касается какого-либо кода Derived.

Глядя на байт-код (который код Java скомпилирован с помощью javac) поучителен -

Скажем, мы немного изменим исходный код на:

public class PrivateOverride {
private void f() {
    System.out.println("private f()");
}

public static void main(String[] args) {
    PrivateOverride po = new Derived();
    po.f();
    Derived d = new Derived();
    d.f();
}
}

class Derived extends PrivateOverride {
public void f() {
    System.out.println("public f()");
}
}

Основной метод компилируется (отредактирован для краткости):

public static main([Ljava/lang/String;)V
  NEW Derived
  DUP
  INVOKESPECIAL Derived.<init>()V
  ASTORE 1
  ALOAD 1
  INVOKESPECIAL PrivateOverride.f()V
  NEW Derived
  DUP
  INVOKESPECIAL Derived.<init>()V
  ASTORE 2
  ALOAD 2
  INVOKEVIRTUAL Derived.f()V
  RETURN

Обратите внимание, что в каждом случае метод вызывается по типу времени компиляции. Обратите внимание также, что второй вызов функции f() использует инструкцию INVOKEVIRTUAL. Это то, что говорит JVM проверять тип времени выполнения и решать, что вызывать на основе этого.

Ответ 5

Я просто просмотрел байт-код скомпилированной версии вышеуказанного класса и получил специальный код Opcode. Этот Opcode был достаточно, чтобы указать причину, по которой фактический вывод очевиден. Invokespecial используется в трех ситуациях, в которых метод экземпляра должен быть вызван в зависимости от типа ссылки, а не от класса объекта. Три ситуации:

1) вызов методов инициализации экземпляра()

2) вызов частных методов

3) вызов методов с использованием ключевого слова super

Вышеприведенный пример лежит во втором сценарии, где мы вызываем частные методы. Таким образом, метод был вызван на основе типа ссылки i.e PrivateOverride, а не типа класса i.e Derived

Итак, возникает вопрос, почему invokespecial? У нас есть другой Opcode, такой как invokevirtual, который вызывается для метода на основе classtype, а не типа ссылки. Поэтому давайте обсудим, почему invokespecial Opcode используется для частных методов. Но мы должны знать разницу между invokevirtual и invokespecial. Invokespecial отличается от invokevirtual главным образом тем, что invokespecial выбирает метод, основанный на типе ссылки, а не на классе объекта. Другими словами, вместо динамической привязки она статичная привязка. В каждой из трех ситуаций, где используется invokespecial, динамическое связывание не даст желаемого результата.

Ответ 6

Это ведет себя так, потому что именно так определяется JVM в таких случаях.

Трудная часть понимает, что происходит и почему.

Вы вызывали частный метод из класса, в котором он был закрыт. Таким образом, троянский конь находится внутри замка, он может играть с частными переменными. Выньте троянский конь из замка, и частный метод больше не виден.

Этот пример может прояснить ситуацию. Рассмотрим эту программу:

public class Bicycle {  
  private void getCost() {  
      System.out.println("200");  
  }  

  public static void main(String[] args) {  
      Bicycle ACME_bike = new ACME_bike();
      ACME_bike.getCost();

      Bicycle mybike = new Bicycle();
      mybike.getCost();

      ACME_bike acme_bike = new ACME_bike();
      acme_bike.getCost();

      //ACME_bike foobar = new Bicycle(); //Syntax error: Type mismatch: 
                                          //cannot convert from 
                                          //Bicycle to ACME_bike
  }  
}  

class ACME_bike extends Bicycle {
  public void getCost(){
      System.out.println("700");
  }
}

Эта программа печатает:

200
200
700

Если вы измените модификатор доступа getCost в Bicycle на public, protected или пакет private (без модификатора), тогда он печатает это:

700
200
700

Ответ 7

В Java существует четыре уровня видимости, на уровне пакетов (подразумевается не использование видимости), public (все могут это назвать), private (только я и внутренние классы, которые видят мои функции как глобальные, могут это назвать), и защищенный (I и любой подкласс, может вызвать это).

Вы можете сделать свой второй класс внутренним классом, а затем вы не переопределяете, а просто вызываете функцию, как если бы она была глобальной (насколько это касается внутреннего класса), но тогда вы действительно не можете создавать экземпляры где угодно вы хотите, потому что это класс, который может использоваться исключительно владельцем (поэтому, если вы хотите использовать его в другом месте, владелец должен быть factory и вернуть его, как если бы он был базовым классом), или вы можете сделать метод а затем расширяемый класс может вызвать метод.