Для прослушивания нажатий клавиш, существует специальный интерфейс KeyListener:

interface KeyListener {
	//клавиша нажата, но не отпущена
	public void keyPressed(KeyEvent event);

	//клавиша отпущена
	public void keyReleased(KeyEvent event);

	//клавиша нажата и отпущена
	public void keyTyped(KeyEvent event);
}

Каждый метод, реализованный интерфейсом KeyListener, вызывается определенным событием, вместе с которым передается экземпляр KeyEvent. KeyEvent содержит в себе всю информацию о нажатой клавише и о модификаторах, таких как Alt, Ctrl, Shift:

int keyCode = event.getKeyCode(); //цифровой код нажатой клавиши
boolean isAltDown = event.isAltDown();
boolean isControlDown = event.isControlDown();
boolean isShiftDown = event.isShiftDown();

Дополнительно про KeyEvent можно почитать тут.

У каждой клавиши есть свой цифровой код, например, код пробела 32, клавиша вправо имеет код 39. Всегда можно посмотреть эти коды, выполнив команду:

System.out.println(event.getKeyCode());

Кроме того, класс KeyEvent содержит коды всех клавиш в статических переменных, все они начинаются на VK_

KeyEvent.VK_SPACE; //32
KeyEvent.VK_RIGHT; //39

Можно и нужно использовать эти переменные для сравнения:

If (event.getKeyCode()==KeyEvent.VK_SPACE) {
	System.out.println(Нажат пробел);
}

Короткие нажатия

Когда дело касается только обработки нажатой клавиши, достаточно поместить необходимый код в метод keyTyped() интерфейса KeyListener.

@Override
public void keyTyped(KeyEvent event) {
	//клавиша нажата и отпущена
}

Длинные одновременные нажатия

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

private boolean isLeft = false;
private boolean isRight = false;
private boolean isUp = false;
private boolean isDown = false;

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

@Override
public void keyPressed(KeyEvent event) {
	if (e.getKeyCode()==KeyEvent.VK_LEFT) isLeft = true;
	if (e.getKeyCode()==KeyEvent.VK_RIGHT) isRight = true;
	if (e.getKeyCode()==KeyEvent.VK_UP) isUp = true;
	if (e.getKeyCode()==KeyEvent.VK_DOWN) isDown = true;
}

@Override
public void keyReleased(KeyEvent event) {
	if (e.getKeyCode()==KeyEvent.VK_LEFT) isLeft = false;
	if (e.getKeyCode()==KeyEvent.VK_RIGHT) isRight = false;
	if (e.getKeyCode()==KeyEvent.VK_UP) isUp = false;
	if (e.getKeyCode()==KeyEvent.VK_DOWN) isDown = false;
}

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

Подключаем слушатель

Остается только повесить наш класс слушателя нажатий на какой-нибудь компонент Swing, например на JFrame:

frame.addKeyListener(keyListener);

Живой пример

Перед вами код, реализующий отрисовку змейки. Голова управляется "стрелками".

Змейка на Java Swing в 100 строк кода Змейка на Java Swing в 100 строк кода

Листинг RunKeybord.java:

package ru.jcup.education.graphics.swing;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.JFrame;

public class RunKeyboard extends JFrame implements KeyListener{

	private Thread thread;
	private static Random random = new Random();
	private static final int DIR_STEP = 2;
	private boolean isLeft = false;
	private boolean isRight = false;
	private boolean isUp = false;
	private boolean isDown = false;
	private int x, y;
	
	public RunKeyboard(int width, int height) {
		this.setSize(width, height);
		x = width/2;
		y = height/2;
		this.addKeyListener(this);
		thread = new MoveThread(this);
		thread.start();
	}
	
	//Start point
	
	public static void main(String... string) {
		JFrame frame = new RunKeyboard(500,500);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}
	
	//Listener
	
	@Override
	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode()==KeyEvent.VK_LEFT) isLeft = true;
		if (e.getKeyCode()==KeyEvent.VK_RIGHT) isRight = true;
		if (e.getKeyCode()==KeyEvent.VK_UP) isUp = true;
		if (e.getKeyCode()==KeyEvent.VK_DOWN) isDown = true;
	}

	@Override
	public void keyReleased(KeyEvent e) {
		if (e.getKeyCode()==KeyEvent.VK_LEFT) isLeft = false;
		if (e.getKeyCode()==KeyEvent.VK_RIGHT) isRight = false;
		if (e.getKeyCode()==KeyEvent.VK_UP) isUp = false;
		if (e.getKeyCode()==KeyEvent.VK_DOWN) isDown = false;
	}

	@Override
	public void keyTyped(KeyEvent arg0) {}
	
	//Graphics
	
	@Override
	public void paint(Graphics gr) {
		Graphics2D g2d = (Graphics2D)gr;
		int r = random.nextInt(256);
		int g = random.nextInt(256);
		int b = random.nextInt(256);
		g2d.setColor(new Color(r,g,b));
		g2d.setStroke(new BasicStroke(4f));
		g2d.drawOval(x-25, y-25, 50, 50);
	}
	
	public void animate() {
		if (isLeft) x-=DIR_STEP;
		if (isRight) x+=DIR_STEP;
		if (isUp) y-=DIR_STEP;
		if (isDown) y+=DIR_STEP;
		this.repaint();
	}
	
	//Engine thread
	
	private class MoveThread extends Thread{
		RunKeyboard runKeyboard;
		
		public MoveThread(RunKeyboard runKeyboard) {
			super("MoveThread");
			this.runKeyboard = runKeyboard;
		}
		
		public void run(){
			while(true) {
				runKeyboard.animate();
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

}