Java Выполнение выполнения - переопределенный метод выполняется сначала, чем конструктор

У меня есть следующий код, в том же java файле.

import javax.swing.SwingUtilities;
import java.io.File;

public class MainClass2{
   public static void main(String[] args){
       SwingUtilities.invokeLater(new Runnable(){
             public void run() {
                 javax.swing.JFileChooser jfc = new MyFileChooser();
                     File file = jfc.getSelectedFile();
             }

      });
   }
}

class MyFileChooser extends javax.swing.JFileChooser{
    public MyFileChooser(){
        System.out.println("constructor call");
    }
    @Override
    public java.io.File getSelectedFile(){
        System.out.println("call to getSelectedFile");
        return null;
    }
}

Когда я запускаю его, вывод дает мне

call to getSelectedFile

constructor call

call to getSelectedFile

Не должен быть выход

constructor call

call to getSelectedFile

Я использую java 5.

Ответ 1

Конструктор

MyFileChooser эквивалентен:

public MyFileChooser() {
    super(); // ***
    System.out.println("constructor call");
}

Первый вызов getSelectedFile() выполняется конструктором базового класса MyFileChooser, который неявно вызывается в точке, отмеченной *** выше, перед System.out.println("constructor call").

Вот трассировка стека:

MyFileChooser.getSelectedFile() line: 16    
AquaFileChooserUI.installComponents(JFileChooser) line: 1436    
AquaFileChooserUI.installUI(JComponent) line: 122   
MyFileChooser(JComponent).setUI(ComponentUI) line: 670  
MyFileChooser(JFileChooser).updateUI() line: 1798   
MyFileChooser(JFileChooser).setup(FileSystemView) line: 360 
MyFileChooser(JFileChooser).<init>(File, FileSystemView) line: 333  
MyFileChooser(JFileChooser).<init>() line: 286  
MyFileChooser.<init>() line: 11 

Ответ 2

Конструктор:

public MyFileChooser(){
    System.out.println("constructor call");
}

похоже, не имеет никакого отношения к конструктору базового класса. Однако есть неявный супервызов javax.swing.JFileChooser(), который вызывает вызов getSelectedFile();. Поэтому ваш конструктор на самом деле выглядит так:

public MyFileChooser(){
    super();
    System.out.println("constructor call");
}

Поскольку jfc является объектом MyFileChooser, этот метод:

@Override
public java.io.File getSelectedFile(){
    System.out.println("call to getSelectedFile");
    return null;
}

вызывается. распечатывается "call to getSelectedFile", а затем "вызов getSelectedFile".

Ответ 3

Если вы посмотрите на трассировку стека, вы увидите, что конструктор JFileChooser вызывает setup(FileSystemView view), который вызывает updateUI(), который вызывает setUI() в суперклассе JComponent, который вызывает installUI на платформе- конкретный класс пользовательского интерфейса, этот класс затем вызывает installComponents, который вызывает getSelectedFile снова.

Цитата из Effective Java 2nd Edition:

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

Но, конечно, инструментарий Swing не всегда следует этому совету; -)

Полная трассировка стека:

at MyFileChooser.getSelectedFile(MainClass2.java:27)
    at com.apple.laf.AquaFileChooserUI.installComponents(AquaFileChooserUI.java:1436)
    at com.apple.laf.AquaFileChooserUI.installUI(AquaFileChooserUI.java:122)
    at javax.swing.JComponent.setUI(JComponent.java:670)
    at javax.swing.JFileChooser.updateUI(JFileChooser.java:1798)
    at javax.swing.JFileChooser.setup(JFileChooser.java:360)
    at javax.swing.JFileChooser.<init>(JFileChooser.java:333)