09. 테이블 컴포넌트
JTable은 데이터를 행(Row)과 열(Column)로 구성된 표 형식으로 보여주고 편집할 수 있는 강력한 컴포넌트입니다.
💡 비주얼 개념 잡기: JTable과 TableModel의 관계
JTable과 TableModel의 구조는 마치 진열장과 장부의 관계와 같습니다.

- JTable (진열장 / 격자판): 데이터를 화면에 격자 모양으로 보여주기만 하는 나무 틀(진열장)입니다. 스스로 데이터를 보관하지 않습니다.
- TableModel (원본 장부): 실제 데이터가 한 땀 한 땀 기록되어 있는 원본 노트(장부)입니다. 데이터의 추가, 삭제, 수정은 모두 이 장부에서 일어나며, JTable은 단지 이 장부를 바탕으로 화면을 그릴 뿐입니다.
- Cell Renderer (선물 포장지): 텍스트뿐만 아니라 체크박스, 아이콘, 배경색 등 셀을 예쁘게 포장해서 보여주는 역할을 합니다.
1. JTable 구조
테이블은 컬럼 헤더(Column Header), 행(Row), 셀(Cell)로 구성됩니다.
- 컬럼(Column): 열. 각 컬럼은 동일한 데이터 타입을 가집니다.
- 행(Row): 가로 줄.
- 셀(Cell): 행과 열이 만나는 데이터 표시 단위.
2. 기본 생성 방법
가장 간단한 생성 방법은 컬럼 이름 배열(String[])과 데이터 배열(Object[][])을 사용하는 것입니다.
String[] columnNames = { "이름", "나이" };
Object[][] rowData = {
{ "춘삼월", 25 },
{ "하여름", 23 },
{ "하바다", 26 }
};
JTable table = new JTable(rowData, columnNames);
주의사항
JTable은 스크롤을 자동으로 지원하지 않으므로JScrollPane에 넣어서 사용해야 합니다.- 컬럼 폭 조절:
table.getColumn("이름").setPreferredWidth(100);
3. 예제: 기본 JTable
- 실습 예제 파일: JTableBasicExample.java (경로:
sample/sec09/exam01_jtable/JTableBasicExample.java) - 실행 방법:
# 1. 09_table_component 디렉토리로 이동 후 컴파일 javac -d sample sample/sec09/exam01_jtable/JTableBasicExample.java # 2. 실행 java -cp sample sec09.exam01_jtable.JTableBasicExample
package sec09.exam01_jtable;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
public class JTableBasicExample extends JFrame {
private JTable jTable;
public JTableBasicExample() {
this.setTitle("JTableBasicExample");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(300, 200);
// JScrollPane에 JTable 추가
this.getContentPane().add(new JScrollPane(getJTable()), BorderLayout.CENTER);
}
public JTable getJTable() {
if(jTable == null) {
String[] columnNames = { "이름", "나이" };
Object[][] rowData = {
{ "춘삼월", 25 },
{ "하여름", 23 },
{ "하바다", 26 },
{ "추가을", 22 },
{ "동겨울", 27 },
{ "동장군", 15 }
};
jTable = new JTable(rowData, columnNames);
// 컬럼 폭 설정
jTable.getColumn("이름").setPreferredWidth(100);
jTable.getColumn("나이").setPreferredWidth(50);
}
return jTable;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JTableBasicExample jFrame = new JTableBasicExample();
jFrame.setVisible(true);
});
}
}
실행 결과 화면

4. TableModel
JTable은 데이터 관리를 위해 TableModel을 사용합니다. getModel() 메서드로 모델을 얻을 수 있습니다.
주요 메서드 (TableModel)
| 메서드 | 설명 |
| :———————————————- | :————————— |
| int getColumnCount() | 전체 컬럼 수 반환 |
| String getColumnName(int col) | 해당 인덱스의 컬럼 이름 반환 |
| int getRowCount() | 전체 행 수 반환 |
| Object getValueAt(int row, int col) | 해당 셀의 데이터 반환 |
| void setValueAt(Object val, int row, int col) | 해당 셀의 데이터 변경 |
데이터의 동적 추가/삭제가 필요하다면 DefaultTableModel을 사용하는 것이 좋습니다.
addRow(Object[] data): 행 추가removeRow(int row): 행 삭제
5. 예제: TableModel 사용 및 셀 렌더러
셀 렌더러 (Cell Renderer)
기본적으로 텍스트로 표시되지만, 이미지나 체크박스 등 다른 컴포넌트로 셀을 표시하고 싶을 때 사용합니다. TableCellRenderer 인터페이스를 구현해야 합니다.
// 예: 조건에 따라 아이콘을 다르게 표시하는 렌더러
public class MyCellRenderer extends JLabel implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
// ... 컴포넌트 설정 (this 반환)
return this;
}
}
종합 예제 Code (JTableCustomExample)
- 실습 예제 파일: JTableCustomExample.java (경로:
sample/sec09/exam03_cellrenderer/JTableCustomExample.java) - 실행 방법:
# 1. 09_table_component 디렉토리로 이동 후 컴파일 javac -d sample sample/sec09/exam03_cellrenderer/JTableCustomExample.java # 2. 실행 (실행 전 같은 패키지 폴더 내의 key.gif 및 start.gif 리소스가 필요합니다) java -cp sample sec09.exam03_cellrenderer.JTableCustomExample
package sec09.exam03_cellrenderer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
public class JTableCustomExample extends JFrame {
private JTable jTable;
public JTableCustomExample() {
this.setTitle("JTableCustomExample");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(400, 300);
this.getContentPane().add(new JScrollPane(getJTable()), BorderLayout.CENTER);
}
public JTable getJTable() {
if (jTable == null) {
String[] columnNames = { "이름", "나이", "선택" };
Object[][] rowData = {
{ "춘삼월", 25, false },
{ "하여름", 26, true },
{ "추가을", 22, false },
{ "동겨울", 27, true }
};
// 데이터 수정이 용이한 DefaultTableModel 사용
DefaultTableModel model = new DefaultTableModel(rowData, columnNames);
jTable = new JTable(model);
// 1. 이름 컬럼 렌더러 (중앙 정렬 + 노란 배경 선택)
jTable.getColumn("이름").setCellRenderer(new DefaultRenderer());
// 2. 나이 컬럼 렌더러 (조건부 아이콘 표시)
jTable.getColumn("나이").setCellRenderer(new AgeRenderer());
// 3. 선택 컬럼 렌더러 (체크박스)
jTable.getColumn("선택").setCellRenderer(new CheckBoxRenderer());
// 마우스 클릭 이벤트
jTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
int row = jTable.getSelectedRow();
int col = jTable.getSelectedColumn();
if (row != -1 && col != -1) {
Object value = jTable.getValueAt(row, col);
System.out.println("선택된 값(" + row + "," + col + "): " + value);
}
}
});
}
return jTable;
}
// 기본 렌더러 (중앙 정렬)
class DefaultRenderer extends JLabel implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
setText(value.toString());
setHorizontalAlignment(JLabel.CENTER);
setOpaque(true);
setBackground(isSelected ? Color.YELLOW : Color.WHITE);
return this;
}
}
// 나이 렌더러
class AgeRenderer extends JLabel implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
int age = (Integer) value;
if (age <= 25) {
setIcon(new ImageIcon(getClass().getResource("key.gif")));
} else {
setIcon(new ImageIcon(getClass().getResource("start.gif")));
}
setText(value.toString());
setHorizontalAlignment(JLabel.CENTER);
setOpaque(true);
setBackground(isSelected ? Color.YELLOW : Color.WHITE);
return this;
}
}
// 체크박스 렌더러
class CheckBoxRenderer extends JCheckBox implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
setSelected((Boolean) value);
setHorizontalAlignment(CENTER);
setBackground(isSelected ? Color.YELLOW : Color.WHITE);
return this;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JTableCustomExample jFrame = new JTableCustomExample();
jFrame.setVisible(true);
});
}
}
실행 결과 화면

6. 행 추가, 수정, 삭제 (CRUD)
DefaultTableModel을 사용하면 행을 동적으로 제어할 수 있습니다.
addRow(Object[])/addRow(Vector): 행 추가insertRow(int index, ...): 특정 위치에 삽입removeRow(int index): 특정 행 삭제fireTableDataChanged(): 데이터 변경 통지 (일반적으로setDataVector등을 쓸 때 필요하지만,DefaultTableModel메서드들은 자동 통지됨)
CRUD 예제 Code (JTableEditorExample)
- 실습 예제 파일: JTableEditorExample.java (경로:
sample/sec09/exam04_eventhandling/JTableEditorExample.java) - 실행 방법:
# 1. 09_table_component 디렉토리로 이동 후 컴파일 javac -d sample sample/sec09/exam04_eventhandling/JTableEditorExample.java # 2. 실행 java -cp sample sec09.exam04_eventhandling.JTableEditorExample
package sec09.exam04_eventhandling;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
public class JTableEditorExample extends JFrame {
private JTable jTable;
private JTextField txtName, txtAge;
public JTableEditorExample() {
this.setTitle("JTableEditorExample");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(300, 400);
this.getContentPane().add(new JScrollPane(getJTable()), BorderLayout.CENTER);
this.getContentPane().add(getPSouth(), BorderLayout.SOUTH);
}
public JTable getJTable() {
if(jTable == null) {
String[] columnNames = {"이름", "나이"};
Object[][] rowData = {}; // 초기 데이터 없음
// 데이터 조작을 위해 DefaultTableModel 사용
DefaultTableModel model = new DefaultTableModel(rowData, columnNames);
jTable = new JTable(model);
// 마우스 클릭 시 데이터 텍스트필드에 바인딩
jTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
int row = jTable.getSelectedRow();
if(row != -1) {
String name = (String) jTable.getValueAt(row, 0);
String age = (String) jTable.getValueAt(row, 1);
txtName.setText(name);
txtAge.setText(age);
}
}
});
}
return jTable;
}
public JPanel getPSouth() {
JPanel pSouth = new JPanel(new GridLayout(3, 1));
// 입력 패널
JPanel pInput = new JPanel(new GridLayout(1, 4));
pInput.add(new JLabel("이름", JLabel.CENTER));
txtName = new JTextField();
pInput.add(txtName);
pInput.add(new JLabel("나이", JLabel.CENTER));
txtAge = new JTextField();
pInput.add(txtAge);
pSouth.add(pInput);
// 버튼 패널
JPanel pBtn = new JPanel();
// 추가 버튼
JButton btnAdd = new JButton("추가");
btnAdd.addActionListener(e -> {
String[] data = { txtName.getText(), txtAge.getText() };
((DefaultTableModel) jTable.getModel()).addRow(data);
clearFields();
});
pBtn.add(btnAdd);
// 수정 버튼
JButton btnUpdate = new JButton("수정");
btnUpdate.addActionListener(e -> {
int row = jTable.getSelectedRow();
if (row != -1) {
jTable.setValueAt(txtName.getText(), row, 0);
jTable.setValueAt(txtAge.getText(), row, 1);
clearFields();
} else {
JOptionPane.showMessageDialog(this, "수정할 행을 선택하세요.");
}
});
pBtn.add(btnUpdate);
// 삭제 버튼
JButton btnDelete = new JButton("삭제");
btnDelete.addActionListener(e -> {
int row = jTable.getSelectedRow();
if (row != -1) {
((DefaultTableModel) jTable.getModel()).removeRow(row);
clearFields();
} else {
JOptionPane.showMessageDialog(this, "삭제할 행을 선택하세요.");
}
});
pBtn.add(btnDelete);
pSouth.add(pBtn);
return pSouth;
}
private void clearFields() {
txtName.setText("");
txtAge.setText("");
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JTableEditorExample jFrame = new JTableEditorExample();
jFrame.setVisible(true);
});
}
}
실행 결과 화면

Vector를 이용한 데이터 다이렉트 수정
DefaultTableModel 내부에서 전체 행 데이터는 Vector<Vector> 형태로 보관됩니다. getDataVector() 메서드를 사용하면 이 벡터 데이터에 직접 접근할 수 있습니다.
Vector(전체 행 관리)
|-- Vector(0 인덱스 행 데이터)
|-- Vector(1 인덱스 행 데이터)
|-- ...
DefaultTableModel에서 전체 행을 관리하는 Vector는 다음과 같이 얻을 수 있습니다.
Vector<Vector> rows = tableModel.getDataVector();
그리고 각 행의 Vector는 행 인덱스를 주어 다음과 같이 가져옵니다.
Vector row = rows.elementAt(행인덱스);
행 Vector의 내부 요소를 변경하기 위해서는 Vector의 set() 메소드를 이용하는데, 첫 번째 매개값은 컬럼의 인덱스 번호이고, 두 번째 매개값은 컬럼의 값입니다.
row.set(0, changeValue); // 0번 컬럼의 값을 changeValue로 수정
row.set(1, changeValue); // 1번 컬럼의 값을 changeValue로 수정
행 Vector를 직접 수정한 경우, JTable이 변경 사항을 다시 그리도록 DefaultTableModel의 fireTableDataChanged() 메소드를 호출해야 합니다.
tableModel.fireTableDataChanged();
Vector 활용 종합 예제 (JTableExample)
- 실습 예제 파일: JTableExample.java (경로:
sample/sec09/exam05_row_add_update_delete/JTableExample.java) - 실행 방법:
# 1. 09_table_component 디렉토리로 이동 후 컴파일 javac -d sample sample/sec09/exam05_row_add_update_delete/JTableExample.java # 2. 실행 java -cp sample sec09.exam05_row_add_update_delete.JTableExample
package sec09.exam05_row_add_update_delete;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
public class JTableExample extends JFrame {
private JTable jTable;
private JPanel pSouth;
private JTextField txtName, txtAge;
private JButton btnInsert, btnUpdate, btnDelete;
// 메인 윈도우 설정
public JTableExample() {
this.setTitle("JTableExample");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.getContentPane().add(new JScrollPane(getJTable()), BorderLayout.CENTER);
this.getContentPane().add(getPSouth(), BorderLayout.SOUTH);
this.setSize(250, 250);
}
// JTable 생성
public JTable getJTable() {
if (jTable == null) {
jTable = new JTable();
final DefaultTableModel tableModel = (DefaultTableModel) jTable.getModel();
tableModel.addColumn("이름");
tableModel.addColumn("나이");
// 행을 선택했을 때 이벤트 처리
jTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
int rowIndex = jTable.getSelectedRow();
if (rowIndex != -1) {
String name = (String) tableModel.getValueAt(rowIndex, 0);
String age = (String) tableModel.getValueAt(rowIndex, 1);
txtName.setText(name);
txtAge.setText(age);
}
}
});
}
return jTable;
}
// 사용자 입력 JPanel 생성
public JPanel getPSouth() {
if (pSouth == null) {
pSouth = new JPanel();
pSouth.setLayout(new GridLayout(3, 1));
JPanel pNameInput = new JPanel();
pNameInput.setLayout(new GridLayout(1, 2));
pNameInput.add(new JLabel("이름", JLabel.CENTER));
pNameInput.add(getTxtName());
pSouth.add(pNameInput);
JPanel pAgeInput = new JPanel();
pAgeInput.setLayout(new GridLayout(1, 2));
pAgeInput.add(new JLabel("나이", JLabel.CENTER));
pAgeInput.add(getTxtAge());
pSouth.add(pAgeInput);
JPanel pButton = new JPanel();
pButton.add(getBtnInsert());
pButton.add(getBtnUpdate());
pButton.add(getBtnDelete());
pSouth.add(pButton);
}
return pSouth;
}
public JTextField getTxtName() {
if (txtName == null) {
txtName = new JTextField();
}
return txtName;
}
public JTextField getTxtAge() {
if (txtAge == null) {
txtAge = new JTextField();
}
return txtAge;
}
// 행 삽입 버튼 생성
public JButton getBtnInsert() {
if (btnInsert == null) {
btnInsert = new JButton();
btnInsert.setText("추가");
btnInsert.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Object[] rowData = { txtName.getText(), txtAge.getText() };
DefaultTableModel tableModel = (DefaultTableModel) getJTable().getModel();
tableModel.addRow(rowData);
txtName.setText("");
txtAge.setText("");
}
});
}
return btnInsert;
}
// 행 수정 버튼 생성
public JButton getBtnUpdate() {
if (btnUpdate == null) {
btnUpdate = new JButton();
btnUpdate.setText("수정");
btnUpdate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedRow = jTable.getSelectedRow();
if (selectedRow != -1) {
DefaultTableModel tableModel = (DefaultTableModel) getJTable().getModel();
Vector<Vector> rows = tableModel.getDataVector();
Vector row = rows.elementAt(selectedRow);
row.set(0, txtName.getText());
row.set(1, txtAge.getText());
tableModel.fireTableDataChanged();
txtName.setText("");
txtAge.setText("");
}
}
});
}
return btnUpdate;
}
// 행 삭제 버튼 생성
public JButton getBtnDelete() {
if (btnDelete == null) {
btnDelete = new JButton();
btnDelete.setText("삭제");
btnDelete.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int rowIndex = getJTable().getSelectedRow();
if (rowIndex != -1) {
DefaultTableModel tableModel = (DefaultTableModel) getJTable().getModel();
tableModel.removeRow(rowIndex);
txtName.setText("");
txtAge.setText("");
}
}
});
}
return btnDelete;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JTableExample jFrame = new JTableExample();
jFrame.setVisible(true);
}
});
}
}
실행 결과 화면
