Почему затенение данных переменными экземпляра не работает в моей программе?

#java #swing #inheritance #scope

#java #swing #наследование #область видимости

Вопрос:

У меня есть три класса, которые показаны ниже, для графического интерфейса игры:-

 //this is the parent class.
import javax.swing.*;
import java.awt.*;
public class GameGui extends JFrame{
      public void decorateButton(JButton aBut,Color forg,Color back){
         Font afont = new Font(Font.SANS_SERIF,Font.PLAIN,18);
         aBut.setFont(afont);
         aBut.setBackground(back);
         aBut.setForeground(forg);
      }
      public void setFrameDefault(){
         this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         this.setSize(400, 475);
         this.setLocationRelativeTo(null);
         this.setResizable(false);
     }
     public void setConstraints(int x,int y,int weightx,int weighty,GridBagConstraints gbc){
         gbc.weighty=weighty;
         gbc.weightx=weightx;
         gbc.gridx=x;
         gbc.gridy=y;
     }
}

//this class is for result to be shown for the game.

import javax.swing.*;
import java.awt.*;
class Result extends GameGui{
      JPanel mainPanel = new JPanel();
      JLabel backImage = new JLabel();//I want this variable to be shadowed by the subclass variable,but it is not happening. 
      JButton continueGame = new JButton("continueGame");
      JButton exitGame = new JButton("exitGame");
      public Result(){
            this.setFrameDefault();
            backImage.setLayout(new BorderLayout());
            this.setContentPane(backImage);
            mainPanel.setLayout(new GridBagLayout());
            decorateButton(continueGame,Color.green,Color.white);
            decorateButton(exitGame,Color.green,Color.white);
            setGui();
      }
      public void setGui(){
            GridBagConstraints gbc = new GridBagConstraints();
            mainPanel.setOpaque(false);
            gbc.gridy=200;
            gbc.gridx=0;
            gbc.insets=new Insets(410,0,0,130);
            mainPanel.add(continueGame,gbc);
            gbc.gridx=GridBagConstraints.RELATIVE;
            gbc.insets = new Insets(410,0,0,0);
            mainPanel.add(exitGame,gbc);
            setFrameDefault();
            this.getContentPane().add(mainPanel);
      }

 }

 //this class is for showing the result for a Win.

 import javax.swing.*;
 import java.awt.*;
 public class Win extends Result{
        JLabel backImage = new JLabel(new ImageIcon("C:\Users\BSK\Desktop\win.png"));//Problem is here as i have declared the same named JLabel as in Result class but iam not getting the image as background.
        public static void main(String[] args) { //this main method is for testing.
            Win w = new Win();
            w.setVisible(true);
        }
 }
  

Мне нужны два класса в конце иерархии, которые являются Win и Defeat (второй я не реализовал).Мне это нужно, потому что я хочу, чтобы фрейм победы и фрейм поражения отличались только изображением.
Итак, мой вопрос заключается в том, что, хотя я объявил JLabel с тем же именем, что и backImage в обоих классах Result и Win , почему я не получаю изображение в фоновом режиме?Я протестировал это, поместив изображение в класс JLabel backImage of Result , и тогда это работает!Но я хочу воспользоваться преимуществом затенения данных, потому что в моем Defeat классе (который также расширяет Result) я буду использовать имя, JLabel имеющее то же имя, что и backImage , но с другим изображением, установленным для него.Я надеюсь, вы понимаете, так каков же выход?
Заранее спасибо.

ПРИМЕЧАНИЕ пожалуйста, протестируйте с вашим изображением.

Комментарии:

1. Похоже, что вы злоупотребляете наследованием здесь: вы используете наследование там, где композиция была бы лучше.

2. @HovercraftFullOfEels если вы можете предоставить подробный ответ ……много-много-много раз спасибо

3. @HovercraftFullOfEels, пожалуйста, если вы можете описать…. жду вашего ответа.

Ответ №1:

Затенение влияет на то, к какой переменной относится имя. То есть, поскольку подкласс Win определяет свою собственную backImage переменную экземпляра, методы Win , которые ссылаются на backImage , будут ссылаться на переменную экземпляра в Win (и, следовательно, на ее значение), а не на переменную в суперклассе, Result .

Затенение не заменяет объект, на который указывают переменные и другие объекты. То есть суперкласс Result по-прежнему определяет свою собственную backImage переменную экземпляра, а Result методы по-прежнему ссылаются на эту переменную (и, следовательно, на ее значение). Итак, Win#backImage тени Result#backImage , но это не меняет того, как работает результат.

Также обратите внимание, что строки инициализации, подобные JLabel backImage = ... , выполняются как часть конструктора класса, а конструктор подкласса Win начинается с запуска его конструктора суперкласса Result . Итак, если подкласс не объявлял другой, backImage а его конструктор присвоил новое значение унаследованной переменной экземпляра Result#backImage , это произошло бы после того, как Result конструктор создал панель содержимого, поэтому отображение не изменилось бы.

Вы могли бы изменить содержимое backImage объекта:

 public class Win extends Result {
  public Win() {
    super();
    backImage.setIcon(new ImageIcon("C:\Users\BSK\Desktop\win.png"));
  }

  ...
  

чтобы изменить значок обратного изображения для Win подкласса.

Комментарии:

1. пожалуйста, уточните свою первую строку .. если можете… Спасибо

2. Доработано. Коду также может потребоваться запросить ретрансляцию окна после изменения значка метки.

3. вы говорите, что Win класс будет ссылаться на свой собственный JLabel, но тогда почему результат не соответствует ожиданиям?

4. Нет. Класс Win не должен объявлять свою собственную переменную backImage или создавать другой экземпляр JLabel. Просто измените значок существующего JLabel, который уже связан с деревом компонентов.

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

Ответ №2:

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

Один из них (я определенно не говорю, что лучший!) заключается в том, чтобы сделать вашу Result игру абстрактной с помощью абстрактного getIcon() метода или чего-то еще и вызвать это из вашего setGui() метода, тогда ваши классы Win / Lose будут просто возвращать разные значки там.

Комментарии:

1. но это не переопределение, и как Result класс будет использовать другие свои методы, если мы не JLabel определили их там?

2. JLabel все равно будет удален внутри Result класса, и метод getIcon() также будет объявлен там, просто инициализируйте свой background в setGui() , но вместо определенного значка используйте свой getIcon() метод. Затем переопределите это в вашем Win классе, чтобы вернуть значок, который вы действительно хотите.

3. Хотя Win можно переопределить Result#getIcon() метод, обычно это плохая идея переопределять метод, вызываемый конструктором, потому что он вызывается до запуска конструктора подкласса. Это можно исправить, выполнив работу по созданию пользовательского интерфейса после конструктора.

4. aaa, верно, я хотел добавить предупреждение о том, что это ужасная идея вызывать переопределяемые методы из конструктора (из собственного опыта), я не заметил, что setGui() был вызван из конструктора.