Como vimos no post passado, um JTable utiliza um objeto TableModel para manipular seus valores e no exemplo que vimos utilizamos a classe DefaultTableModel que funciona bem para muitos casos mas se quisermos uma maior personalização quando estamos manipulando os dados da JTable devemos criar nosso próprio TableModel.

Para começarmos devemos criar uma classe que estenda a classe abstrata AbstractTableModel, que irá nos obrigar a implementar os seguintes métodos:

public int getRowCount(); 
public int getColumnCount(); 
public Object getValueAt(int i, int i1);

Estes métodos devem retornar respectivamente o numero de linhas, o numero de colunas e o valor da célula correspondente aos valores de linha e coluna fornecidos por parâmetro.

Mas lembrando que estamos criando uma classe para armazenar os dados de uma JTable então devemos ter uma estrutura para armazenar estes valores, eu particularmente gosto do List, mas o que vamos armazenar, se vocês se lembram do exemplo do outro post que usamos o DefaultTableModel, podíamos pegar os dados da tabela e ele nos devolvia em uma estrutura que era um vetor(Vector) sendo cada elemento deste uma linha que também era um vetor e que cada de seus elementos seriam as células da linha. No nosso caso pretendemos que cada linha seja as informações de um objeto(no caso Pessoa) então nada mais natural que os valores serem armazenados em um List que contenha objetos do tipo Pessoa, então antes de tudo vamos criar a nossa classe Pessoa.

public class Pessoa {
     
    private String nome;
    private String sobrenome;
    private int idade;
    private double altura;
     
    //gets e sets
}

Agora podemos começar a criar nosso TableModel:

public class PessoaTableModel extends AbstractTableModel{
     
    private List<Pessoa> dados;
    private String[] colunas = {"Nome" , "Sobrenome" ,"Idade","Altura"};
     
    public PessoaTableModel(){
        dados = new ArrayList<Pessoa>();
    }
     
    public void addRow(Pessoa p){
        this.dados.add(p);
        this.fireTableDataChanged();
    }
 
    public String getColumnName(int num){
        return this.colunas[num];
    }
 
    @Override
    public int getRowCount() {
        return dados.size();
    }
 
    @Override
    public int getColumnCount() {
        return colunas.length;
    }
 
    @Override
    public Object getValueAt(int linha, int coluna) {
        switch(coluna){
            case 0: return dados.get(linha).getNome();
            case 1: return dados.get(linha).getSobrenome();
            case 2: return dados.get(linha).getIdade();
            case 3: return dados.get(linha).getAltura();
        }  
        return null;
    }
}

Na linha definimos uma tributo List que irá armazenar os valores da tabela, na linha 4 criamos uma array de string que será os nomes das colunas do nosso model(ele será utilizados no método getColumnName definido na linha 15) que será chamado pelo JTable quando for mostrar os nomes das colunas no topo.

Na linha 6 temos o construtor que instancia o ArrayList também poderíamos sobrecarregar este construtor para receber por parâmetro o ArrayList já populado.

Seguindo na linha 10 temos o método addRow para adicionar um valor a tabela, nele chamamos o método add do nosso ArrayList e logo em seguida chamamos o método fireTableDataChanged herdado de AbstractTableModel que faz com que os valores da JTable sejam recarregados, este método deve ser chamado toda vez que fizermos alguma modificação nos dados da tabela.

Nas linhas 20 e 25 simplesmente retornamos o número de linhas e o número de colunas.

Agora vamos ao método getValueAt(int,int), na linha 30, que é responsável por devolver o valor da célula, como cada coluna de nossa tabela corresponde a um atributo de nosso objeto Pessoa iremos utilizar um switch para, dependendo do número da coluna passado por parâmetro, retorne o atributo apropriado, e utilizamos o índice passado para linha para pegar o objeto em nosso ArrayList através do método get(int).

Também iremos precisar de um método que remova uma linha da tabela:

public void removeRow(int linha){
    this.dados.remove(linha);
    this.fireTableRowsDeleted(linha, linha);
}

Como vemos o código é bem simples, apenas removemos a linha recebida por parâmetro através do método remove(int) do nosso ArrayList e depois chamamos o método fireTableRowsDeleted para atualizar a tabela e retirar o intervalo de linhas passados, no nosso caso somente uma.

Outro método interessante para termos seria um que devolva um objeto Pessoa dado o número da linha;

public Pessoa get(int linha){
    return this.dados.get(linha);
}

Se você deve lembrar quando utilizávamos o DefaultTableModel podíamos alterar a tabela simplesmente dando um duplo clique em cima e alguma célula e ela permitiria a edição. Isso acontecia porque no DefaultTableModel o método isCellEditable(int linha, int coluna) que é chamado para saber se uma célula é editável sempre retornava true, mas no AbstractTableModel ele retorna sempre false, então devemos sobrescreve-lo:

public boolean isCellEditable(int linha, int coluna) {
    return true;
}

O código acima irá permitir a edição mas o valor não ficará fixo pois não foi criado o método setValueAt(Object valor, int linha, int coluna) que é responsável por colocar um valor em uma célula.

public void setValueAt(Object valor, int linha, int coluna){
        if( valor == null) return;
         
        switch(coluna){
            case 0:  dados.get(linha).setNome( (String) valor);break;
            case 1:  dados.get(linha).setSobrenome( (String) valor);break;
            case 2:  dados.get(linha).setIdade( Integer.parseInt( (String) valor) );break;
            case 3:  dados.get(linha).setAltura( Double.parseDouble( (String) valor) );break;
        }
        this.fireTableRowsUpdated(linha, linha);
    }

O código acima irá altera o valor no ArrayList mas se você precisar que os dados também sejam imediatamente alterados no banco de dados por exemplo, você vai precisar adicionar um TableModelListener ao seu model que executará o método tableChanged toda vez que os dados da JTable forem alterados. Adicionaremos eles no construtor:

public PessoaTableModel(){
       dados = new ArrayList<Pessoa>();
        
       this.addTableModelListener(new TableModelListener() {
 
           @Override
           public void tableChanged(TableModelEvent tme) {
               int linha = tme.getFirstRow();
               Pessoa p = dados.get(linha);
               // aqui você atualiza no banco ou em outro lugar qualquer
           }
       } );
   }

Era isso pessoal, espero que tenha dado para entender alguma coisa, t++