Death to DefaultTableModel! AbstractTableModel Rulez!
Antes que alguém venha dizer: ‘Olha o título, esse cara num sabe nada!’ ou ‘DefaultTableModel extends AbstractTableModel!! Asshole!!!’ eu já vou dizendo: Eu sei que DefaultTableModel herda de AbstractTableModel, a ideia do título é abandonar a DefaultTableModel e criar suas próprias classes utilizando os métodos da AbstractTableModel do jeito que nos servir melhor.
Dito isso, vamos ao assunto de hoje: ‘DefaultTableModel e o porque de não utilizá-la’.
Quem já programou utilizando a API Swing e nunca utilizou a JTable que atire a primeira pedra.
Agora, quem já usou JTable e nunca utilizou a DefaultTableModel pode atirar pedras a vontade!
Aqui no blog mesmo… Eu já utilizei DefaultTabelModel em alguns posts. Mas pois bem, vamos abolir essa má prática. Seguem alguns motivos do porque não devemos utilizar a DefaultTableModel:
1. É mais difícil que escrever seu próprio TableModel;
2. É mais lento (usa classes sincronizadas);
3. Ocupa mais espaço em memória (duplica seus dados);
4. Deixa o código mais confuso e difícil de manter;
5. Usa casts inseguros;
6. Força que você tenha que exibir informações desnecessárias (como o ID) na tabela, ou controlar o ID numa lista separada;
7. Faz a sua mulher te deixar, o leite da sua geladeira azedar, e pessoas apontarem o dedo para você na rua.
Motivos muito bem explicados pelo ViniGodoy neste post do GUJ.
Mas se a DefaultTableModel é tão ruim assim, o que devemos fazer? Tudo se resume a uma classe: AbstractTableModel.
Tudo o que temos que fazer é criar uma nova classe que extenda AbstractTableModel e customizá-la para exibir os dados que queremos. Concordo que em um primeiro momento vai parecer meio desnecessário escrever uma classe para cada tabela do seu sistema (ou uma genérica), mas isso compensa muito na hora de utilizá-la, e compensa mais ainda na hora de reutilizá-la (o famoso reuso da orientação a objetos).
Mas chega de conversa, vamos a prática pois é o melhor jeito de fixar as coisas…
Nosso escopo será o seguinte: uma loja qualquer quer controlar seu estoque. Em algum lugar dentro do software que realizará esta tarefa vai ter uma tela de consulta dos produtos e suas respectivas quantidades disponíveis em estoque.
É nessa tela que nosso tutorial irá decorrer. Uma tela de consulta de produtos com alguns botões só para demonstrar o funcionamento do nosso modelo.
Abra o NetBeans, crie um novo projeto para área de trabalho, apague tudo que ele cria, crie um novo pacote, crie um novo JFrame dentro dele e deixe parecido com este:
O JFrame contém os seguintes componentes:
- 1 JTable (que já vem dentro do seu JScrollPane)
- 5 JButtons
Os textos dos botões já explicam qual será a função de cada um deles certo?
Agora vamos a nossa classe base (da camada de modelo) que vai representar os produtos do nosso controle de estoque. Segue a classe Produto.java:
public class Produto { private int cod; private String nome; private int quant; public Produto() { } public Produto(int cod, String nome, int quant) { this.cod = cod; this.nome = nome; this.quant = quant; } public int getCod() { return cod; } public void setCod(int cod) { this.cod = cod; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public int getQuant() { return quant; } public void setQuant(int quant) { this.quant = quant; } }
Uma simples classe para representar um produto. Coloquei o código também porque geralmente sempre precisamos controlar o código dos registros que estão sendo apresentados na tabela. Utilizando DefaultTableModel nós deveríamos ter uma lista separada para poder controlar esses códigos. Mais a frente veremos como seria com nosso novo modelo para a tabela.
Vamos agora para a parte que importa: criar uma classe que herde de AbstractTableModel. A classe AbstractTableModel define alguns métodos que nós podemos reescrever para criar um modelo bem mais fácil de trabalhar. Seguem os métodos, que iremos utilizar neste exemplo, e suas respectivas funções:
- int getRowCount() -> retorna o número de linhas que a tabela tem;
- int getColumnCount() -> retorna o número de colunas que a tabela tem;
- String getColumnName(int column)-> retorna o nome que será exibido na coluna (o índice da primeira coluna é 0);
- Class<?> getColumnClass(int columnIndex) -> retorna a classe que representa cada coluna. String irá mostrar o texto alinhado à esquerda, Integer irá mostrar o número alinhado à direita, Boolean irá mostrar um checkbox… ;
- Object getValueAt(int rowIndex, int columnIndex) -> método utilizado pela JTable para escrever os valores nas células. Internamente a JTable passa em todas as celulas chamando este método para poder setar os valores;
- ??void setValueAt(Object aValue, int rowIndex, int columnIndex) -> método que a JTable chama quando uma célula é editada;
- boolean isCellEditable(int rowIndex, int columnIndex) -> método para saber se a célula é ou não editável;
- void fireTableDataChanged() -> método que avisa todos os listeners da tabela que houve uma mudança nos dados, um deles é o responsável por desenhar a tabela e ao ser notificado irá redesenhá-la.
Ok, agora que já conhecemos alguns dos métodos que a AbstractTableModel fornece, vamos reescrevê-los. Segue o código do arquivo ProdutoTableModel.java:
public class ProdutoTableModel extends AbstractTableModel { //constantes que vão representar as colunas //(só para facilitar o entendimento do código) private final int COL_NOME = 0; private final int COL_QUANT = 1; //lista dos produtos que serão exibidos private List produtos; public ProdutoTableModel() { produtos = new ArrayList(); } public ProdutoTableModel(List lista) { this(); produtos.addAll(lista); } public int getRowCount() { //cada produto na lista será uma linha return produtos.size(); } public int getColumnCount() { //vamos exibir só Nome e Quantidade, então são 2 colunas return 2; } @Override public String getColumnName(int column) { //qual o nome da coluna if (column == COL_NOME) { return "Nome"; } else if (column == COL_QUANT) { return "Quant. Disp"; } return ""; } @Override public Class getColumnClass(int columnIndex) { //retorna a classe que representa a coluna if (columnIndex == COL_NOME) { return String.class; } else if (columnIndex == COL_QUANT) { return Integer.class; } return String.class; } public Object getValueAt(int rowIndex, int columnIndex) { //pega o produto da linha Produto p = produtos.get(rowIndex); //verifica qual valor deve ser retornado if (columnIndex == COL_NOME) { return p.getNome(); } else if (columnIndex == COL_QUANT) { return p.getQuant(); } return ""; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { //pega o produto da linha Produto p = produtos.get(rowIndex); //verifica qual valor vai ser alterado if (columnIndex == COL_NOME) { p.setNome(aValue.toString()); } else if (columnIndex == COL_QUANT) { p.setQuant(Integer.parseInt(aValue.toString())); } //avisa que os dados mudaram fireTableDataChanged(); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { //no nosso caso todas vão ser editáveis, entao retorna true pra todas return true; }
Até agora eu só reescrevi os métodos que foram citados anteriormente. Agora nós vamos à parte produtiva da coisa: vamos escrever os métodos para inserir um novo produto, para remover produtos, para ordená-los e para embaralhá-los, ou seja, vamos dar vida aos nossos botões e o melhor: ver como isso é muito fácil!
Mais alguns métodos para a nossa classe ProdutoTableModel.java:
public void inserir(Produto p) { produtos.add(p); fireTableDataChanged(); } public void excluir(int pos) { produtos.remove(pos); fireTableDataChanged(); } public void excluir(Produto p) { produtos.remove(p); fireTableDataChanged(); } public void ordenarPorNome() { //ordena pelo nome Collections.sort(produtos, new Comparator() { public int compare(Produto o1, Produto o2) { return o1.getNome().compareTo(o2.getNome()); } }); //avisa que a tabela foi alterada fireTableDataChanged(); } public void ordenarPorQuantidade() { //ordena pelo nome Collections.sort(produtos, new Comparator() { public int compare(Produto o1, Produto o2) { return o1.getQuant() - o2.getQuant(); } }); //avisa que a tabela foi alterada fireTableDataChanged(); } public void misturar() { //mistura a lista Collections.shuffle(produtos); //avisa que a tabela foi alterada fireTableDataChanged(); } public Produto getCliente(int pos) { if (pos < 0 || pos >= produtos.size()) { return null; } return produtos.get(pos); }
Pronto! Veja como é fácil manipular os dados da tabela agora. Basta manipular uma lista!
Acho que os únicos métodos que merecem alguma explicação são os de ordenação, onde, em cada um deles é criado um objeto Comparator que ordena de acordo com a necessidade: se é por ordem alfabética dos nomes, compara os nomes, se é pela quantidade em estoque, compara as quantidades.
OBS: Lembre sempre de chamar o método fireTableDataChanged() para avisar que os dados mudaram e a tabela ser redesenahda.
Voltando para aquela ideia de que esta tela seria uma tela de busca… possívelmente ela poderia ser chamada de uma outra tela (a de entrada de produtos por exemplo) para facilitar a busca de um produto. Como seria a recuperação do código do produto selecionado na tabela? Mais ou menos assim:
public class BuscaProdutos extends JDialog { //.... public Produto retornaProdutoSelecionado() { ProdutoTableModel model = (ProdutoTableModel) jT_TabelaProdutos.getModel(); return model.getCliente(jT_TabelaProdutos.getSelectedRow()); } //... } public class EntradaProdutos extends JDialog { //... public void buscaProduto() { //cria a tela de busca como modal BuscaProdutos tela_busca = new BuscaProdutos(null, true); //exibe tela_busca.setVisible(true); //recupera os dados Produto p = tela_busca.retornaProdutoSelecionado(); if (p != null) { jTF_Codigo.setText(String.valueOf(p.getCod())); jTF_Nome.setText(p.getNome()); jTF_Quantidade.setText(String.valueOf(p.getQuant())); } } //... }
A tabela ficaria na tela de busca. Tal tela seria chamada como modal (só ela poderia ter o foco enquanto estivesse visível) e quando se tornasse invisível, o produto selecionado seria recuperado e usado como necessário.
Quanto trabalho não seria necessário para fazer isso utilizando a DefaultTableModel?
Voltando à nossa aplicação inicial, ainda temos que linkar os botões com as funções do modelo. Vamos ao código da classe Principal.java (JFrame da foto lá em cima):
public class Principal extends JFrame { private ProdutoTableModel model; //variável só para controlar os códigos dos produtos private int ultimoCod; public Principal() { initComponents(); ultimoCod = 1; //cria a lista com os produtos ArrayList<Produto> lista = new ArrayList<Produto>(); lista.add(new Produto(ultimoCod++,"Lapiseira 0.7",30)); lista.add(new Produto(ultimoCod++,"Caneta preta",100)); lista.add(new Produto(ultimoCod++,"Caneta azul",70)); lista.add(new Produto(ultimoCod++,"Caneta vermelha",100)); //cria o modelo de Produto model = new ProdutoTableModel(lista); //atribui o modelo à tabela jTable1.setModel(model); } //Ação do botão Inserir private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { Random r = new Random(); Produto p = new Produto(ultimoCod++, "Produto "+String.valueOf(r.nextInt(100)), r.nextInt(1000)); model.inserir(p); } //Ação do botão Excluir private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) { int selecionados[] = jTable1.getSelectedRows(); if (selecionados.length > 0){ ArrayList<Produto> seraoExcluidos = new ArrayList<Produto>(); for (int i=0; i<selecionados.length; i++) seraoExcluidos.add(model.getCliente(selecionados[i])); for (Produto p : seraoExcluidos) model.excluir(p); }else{ JOptionPane.showMessageDialog(this, "Selecione alguém neh..."); } } //Ação do botão Ordenar por Quantidade private void jButton5ActionPerformed(java.awt.event.ActionEvent evt) { model.ordenarPorQuantidade(); } //Ação do botão Ordenar por Nome private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) { model.ordenarPorNome(); } //Ação do botão Misturar private void jButton4ActionPerformed(java.awt.event.ActionEvent evt) { model.misturar(); } /... }
No construtor é criada uma lista com alguns produtos fictícios e também é instanciado o nosso TableModel.
O botão inserir insere um produto com o nome “Produto i” e quantidade Q, onde ‘i’ e ‘Q’ são números randômicos.
O botão excluir trata a exclusão de vários ítens selecionados ao mesmo tempo. Primeiro todos são guardados em uma lista auxiliar e depois eles vão sendo excluídos um a um (Você sabe por que eu fiz assim? Pense um pouco… qualquer coisa a área de comentários está logo abaixo).
Os botões Ordenar por Nome, Ordenar por Quantidade e Misturar são auto explicativos e fazem exatamente o que o seu nome quer dizer.
Bom, chegamos ao fim de mais um artigo aqui do Java Simples. E fica meu conselho: pare de usar DefaultTableModel e começe agora mesmo a escrever suas próprias TableModels.
O código fonte do projeto está disponível aqui! E se achou interessante compartilhe este artigo utilizando a barra social logo abaixo. =]
[]s e até a próxima,
Saab.
Popularity: 2% [?]







Interessante seu tutorial amigo. Parabens!
Sou iniciante ainda, mas pude compreender o que voce quis passar!
Continue contribuindo com a galera. Obrigado.
Abraços
Olá, desculpe minha ignorância, mas estou tentando usar o seu modelo de AbstractTableModel, no entanto não consigo mudar o tamanho da culuna..Pode me dizer como faço para alterar esse tamanho?
Obrigada.
Oi Isabel,
O tamanho das colunas da tabela não tem relação alguma com o modelo que tal tabela usa.
Para mudar o tamanho de uma coluna vc usa o seguinte comando:
jTable1.getColumnModel().getColumn(INDICE).setPreferredWidth(width);
Onde “INDICE” pode ser um número que represente a coluna (a primeira da esquerda para a direita é a 0 (zero)) ou então uma String que seja igual ao nome da coluna (se na coluna estiver escrito ‘Nome’ passe a String “Nome”, exatamente igual).
Boa sorte.
Muito bom cara, eu sempre me mati com o DefaultTableModel, mas depois que comecei um projeto maior me deparei com problemas, mas apartir de hoje escreverei minhas proprias tablemodel