09. 테이블 컴포넌트

JTable은 데이터를 행(Row)과 열(Column)로 구성된 표 형식으로 보여주고 편집할 수 있는 강력한 컴포넌트입니다.

테이블 컴포넌트 구조

💡 비주얼 개념 잡기: JTable과 TableModel의 관계

JTable과 TableModel의 구조는 마치 진열장장부의 관계와 같습니다.

JTable 개념도

  • 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);
        });
    }
}

실행 결과 화면

JTable 기본 실행 결과


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);
        });
    }
}

실행 결과 화면

JTable 셀 렌더러 커스텀 실행 결과


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);
        });
    }
}

실행 결과 화면

JTable CRUD 에디터 실행 결과

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이 변경 사항을 다시 그리도록 DefaultTableModelfireTableDataChanged() 메소드를 호출해야 합니다.

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);
            }
        });
    }
}

실행 결과 화면

JTable Vector 데이터 제어 실행 결과

서브목차