Оператор If..Else ведет себя непредсказуемо (неопределенно?) В программе JFrame - PullRequest
2 голосов
/ 05 июля 2019

Редактировать: пользователь отметил, что мой вопрос является возможной копией этого вопроса: " Какое ключевое слово volatile полезно для ", заголовок которого "Для чего полезно ключевое слово volatile?".Я прочитал вопрос, но не понимаю, как он относится к моему вопросу.


Вот программа, написанная в двух файлах .java.Мой вопрос касается if..else .. в методе main.

Обратите внимание, что в приведенном ниже коде единственная строка внутри else {..} закомментирована.Я назову эту «версию 1» программы и назову программу с этой строкой, закомментированной обратно в «версии 2».

// -------------
// The code below is in IfElseBugProgram.java
public class IfElseBugProgram {

    public static void main(String[] args) {

        MyJFrame terminal = new MyJFrame();

        while (true) {
            String keyReleased = terminal.getKeyReleased();

            if (! keyReleased.equals("") )
            {
                System.out.print("@" + keyReleased);
            }
            else
            {
                // System.out.print("!" + keyReleased);
            }

        } 
    }
}



// ----- 
//The code below is in file MyJFrame.java

import javax.swing.JFrame;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import java.util.ArrayList;
import java.util.List;


public class MyJFrame extends JFrame implements KeyListener
{
    private List<KeyEvent> keyEventQueue;

    public MyJFrame()
    {
        keyEventQueue = new ArrayList<KeyEvent>();

        this.addKeyListener(this);
        pack();

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

    public void keyPressed(KeyEvent e)
    {
    }

    public void keyTyped(KeyEvent e)
    {
    }

    public void keyReleased(KeyEvent keyEvent)
    {
        keyEventQueue.add(keyEvent);
        System.out.println("some key was released!" + keyEventQueue.size());
    }


    public String getKeyReleased()
    {
        int i = keyEventQueue.size();

        if (i == 0)
        {
            return ("");
        }
        else
        {
            return ("" + i);
        }
    }

}

Я ожидаю, что код в if {...} для запуска, как только я нажму клавишу на клавиатуре.То есть я ожидаю, что код System.out.print("@" + keyReleased); будет запущен, как только я нажму клавишу.

В версии 1 I никогда , похоже, не запустится System.out.print("@" + keyReleased);;в консоли никогда не печатается "@ 1", "@ 2" или "@ 3" и т. д.

С версией 2 (то есть с кодом в блоке else {..}, закомментированном назадв),

  • , что обычно происходит в том, что оператор print, который печатает "!"запускается несколько раз, пока я не нажму клавишу.В этот момент такие вещи, как «@ 1» или «@ 2» и т. Д., Неоднократно печатаются.
  • что-то происходит, что я не получаю «!»ни "@ 1", ни "@ 2" не распечатаны!(С тем же исходным кодом!)

Вопрос: Почему строка System.out.print("@" + keyReleased); в блоке if {..} не работает в версии 1, но (обычно) работает в версии 2?


Дополнительные замечания:

  1. Оператор печати System.out.println("some key was released!" + keyEventQueue.size()); в MyJFrame # keyReleased () всегда печатает, независимо от того, как я изменяю код.

  2. Я подумал, что, возможно, что-то не так с моей консолью в Netbeans.Но я попытался запустить другой код в блоке if {..}, например строку кода java.awt.Toolkit.getDefaultToolkit().beep();, которая издает звук.Я также попытался сделать некоторые графические изображения в самом JFrame.Результат тот же: с закомментированной строкой кода в else {...} код в блоке if {..} не выполняется;но если я закомментирую строку кода в else {..} обратно, код в блоке if {...} (включая создание звука или отображение чего-либо в JFrame) действительно запустится.

  3. Я почти уверен, что условие if, if (! keyReleased.equals("") ) правильное.Я пытался добавить одну строку кода System.out.println("?" + (! keyReleased.equals("")) ); чуть выше if (! keyReleased.equals("") ), и последовательно на моей консоли я неоднократно печатал «? False», пока не нажал клавишу, и в этот момент я неоднократно напечатал «? True»,Но также: странно, если вставить эту одну строку кода (System.out.println("?" + (! keyReleased.equals("")) );) над if в версии 1 , то строки кода в блоке if {..} теперь будут выполняться?!


(необязательно) Справочная информация:

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

Я пытаюсь создать простой класс под названием MyJFrameчто может помочь мотивировать друга научиться программировать.Идея состоит в том, чтобы позволить ему узнать о переменных визуально мотивированным способом, программируя чрезвычайно простые игры, как я узнал, изучая бейсик в детстве.Я хочу, чтобы он мог написать программу, полностью содержащуюся в одном методе main ();Я хочу, чтобы у него был простой способ нарисовать строку на экране и простой способ получить пользовательский ввод.Пример программы может выглядеть следующим образом:

int playerLocationX = 3;
int playerLocationY = 4;

MyJFrame terminal = new MyJFrame();

while(true)
{
  // erase player that was drawn during the previous iteration of this loop
  terminal.write(" ", playerLocationX, playerLocationY);

  // get a key the user last pressed (if the user pressed a key) and update 
  // player location

  String keyReleased = terminal.getKeyReleased();

  if (keyReleased.equals("LEFT"))
  {
    playerLocationX = playerLocationX - 1;
  }
  else if (keyReleased.equals("RIGHT"))
  {
    playerLocationY = playerLocationY + 1;
  }

  // draw player again, using most recent player location
  terminal.write("@", playerLocationX, playerLocationY);

}

Я не хочу, чтобы он нуждался в написании своего собственного метода keyReleased () (т. Е. Реализации интерфейса KeyListener), потому что это требует знания написания вашегособственные методы;это также требует знания объектов, потому что метод keyReleased () не имеет способа изменить локальные переменные playerLocationX и playerLocationY, хранящиеся в main ().

Ответы [ 2 ]

1 голос
/ 05 июля 2019

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

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

private volatile List<KeyEvent> keyEventQueue;

Как говорит JLS :

Запись в энергозависимое поле (§8.3.1.4) происходит перед каждым последующим чтением этого поля.

Я не знаю, гарантированно ли работает ваш V2 в соответствии с JLS, но System.out PrintStream будет синхронизироваться при каждой записи, ограничивая оптимизацию, которую может выполнять ВМ.

1 голос
/ 05 июля 2019

Основная причина, по которой вы испытываете такое непредсказуемое поведение, заключается в том, что вы фактически создали многопоточную программу, в которой вы обращаетесь к компонентам Swing из потока, отличного от потока диспетчеризации событий .

В частности, строка:

MyJFrame terminal = new MyJFrame();

запускает поток диспетчеризации событий Swing, но строка (например):

String keyReleased = terminal.getKeyReleased();

обращается к terminal (компоненту Swing) из основного потока.

Из документации пакета Swing :

Как правило, Swing не безопасен для потоков. Все компоненты Swing и связанные с ними классы, если не указано иное, должны быть доступны в потоке диспетчеризации событий.

Прежде чем продвигаться к тому, чтобы заставить этот код работать, я бы предложил пройти урок Урок: параллелизм в Swing .

...