Dziwne, prawdopodobnie nieprawidłowe zachowanie programu z implementowanym interfejsem KeyListener.

0

Witam,

Staram się nauczyć programować w Javie, mam problem z pewnym przeraźliwie prostym programem, nie mam pojęcia skąd takie dziwne zachowanie i jak o uniknąć.

Klasa

Zadaniem programu jest szybka reakcja na wciskanie klawiszy, Klasa "Listen" to najprostsza, mała widoczna ramka, na której dokonywany jest cały eksperyment. Ma ona zaimplementowany i dodany KeyListener oraz jedną zmienną integer zdefiniowaną na samym początku klasy, tak, aby globalnie (dla całej klasy i przez gettery dla reszty programu) przechowywała kod wciśniętego klawisza. W ciele są 3 metody KeyListenera zmieniające stan zmiennej keyCode i getter odpowiedzialny za odczyt jej wartości.

Main

W main stworzony jest obiekt typu Listen o nazwie "l", a dalej w nieskończonej pętli program sprawdza, czy wciśnięty został "Esc", jeżeli tak, to zamyka cały program. Wydaje mi się, że to powinno działać, ale sorry.. to nie działa, dopóki w pętle nie wtrynię dodatkowo funkcji wyświetlającej na ekranie kod wciśniętego klawisza... ta funkcja nie powinna mieć znaczenia w działaniu programu.. tak mi się wydawało do tej pory.
Czy to jest jakiś problem z synchronizacją KeyListenera i Programu? Jaaa nie wiem.. Proszę o pomoc, kod jest poniżej.. wszystko piszę w notatniku, bo chcę się nauczyć metod, instrukcji i konstrukcji na pamięć, wszelkie IDE mają podpowiedzi, które mi to utrudniają.

Z góry dziękuję za pomoc.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

//LISTEN
class Listen extends JFrame implements KeyListener{



//VARIABLES
private int keyCode;
//END OF VARIABLES


//CONSTRUCTOR OF LISTEN
Listen(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
addKeyListener(this);


}//END OF CONSTRUCTOR OF LISTEN



//KEYLISTENER METHODS
public void keyPressed(KeyEvent e){
keyCode = e.getKeyCode();
}
public void keyReleased(KeyEvent e){
keyCode = 0;
}
public void keyTyped(KeyEvent e){}
//END OF KEYLISTENER METHODS


//SETTERS & GETTERS
public int getCode(){
return keyCode;
}
//END OF SETTERS & GETTERS

}//END OF LISTEN


//MAIN
public class Program{



//MAIN METHOD
public static void main(String[] args){


Listen l = new Listen();


while(true){
if(l.getCode() == 27){System.exit(1);}
//System.out.println(l.getCode()); /*Wisienka na torcie jest tutaj, wystarczy usunąć 2 ukośniki przed "System" i wszystko chula..*/
}


}//END OF MAIN METHOD

}//END OF MAIN

1

Program prosty, ale zahacza o skomplikowane zagadnienia. Nie można z wątku głównego odwoływać się do elementów należących do GUI. Patrz dokumentacja EventQueue, a w niej odnośnik do AWT Threading Issues.

W praktyce robi się to tak, że funkcji main pozwalasz się zakończyć, a w obsłudze listenera zamykasz okno i robisz System.exit(0).

Gdybyś chciał robić to tak, jak teraz, to musisz użyć jakichś technik do komunikacji między wątkmi. Głównym, z funkcją main i tzw. Event Dispatching Thread, w którym chodzi listener. Pierwsze co przychodzi do głowy to zmienna Object lock, a dalej lock.wait() w mainie i lock.notify() w listenerze.

1
public void keyReleased(KeyEvent e){
keyCode = 0;
}

Ustawiasz kod klawisza na keyPressed a potem zerujesz go na keyReleased.
Zmien to na:

public void keyReleased(KeyEvent e){
        keyCode = e.getKeyCode();
}

I wszystko bedzie dzialac.

EDIT:
Oczywiscie zastosuj rowniez rade powyzej i dodaj :

volatile private int keyCode;
1

Muszę Wam opowiedzieć, że ta historia wyskoczyła w samą porę. Wczoraj miałem rozmowę rekrutacyjną i padło pytanie Co to jest volatile? :) Niestety zostałem zagięty, bo nie umiałem odpowiedzieć prawidłowo na pytanie A jak można sobie poradzić bez tego słówka? W naszym przykładzie, bo do niego się odniosłem. Można np. tak:

public int getCode(){
  synchronized(this) {
    return keyCode;
  }
}

a nawet tak:

while(true){
  if(l.getCode() == 27){System.exit(1);}
  synchronized(Program.class){} // tu mało istotne, co w nawiasie, ale np. new Object() nie działa
}

Opowiada o tym rozdział Memory model (17.4) z Java Language Specification. Prawdopodobnie w środku println jest gdzieś wywołanie synchronized i to załatwia sprawę.

Zaglądam tutaj na forum, żeby się podokształcać i załapać może jakieś kontakty. Okazało się, że warto!

0

Bardzo dziękuję wszystkim za odpowiedzi!

0

In a very, very small nutshell: When you have two threads that are reading and writing to the same 'resource', say a variable named foo, you need to ensure that these threads access the variable in an atomic way. Without the synchronized keyword, your thread 1 may not see the change thread 2 made to foo, or worse, it may only be half changed. This would not be what you logically expect.

Teraz już wszystko jest jasne, dziękuję wszystkim za pomoc i życzę miłego, bezproblemowego programowania. ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1