使用多个自定义表模型,避免重复代码

我正在一个项目中,我们有几个域类来建模业务数据。 这些类是简单的POJO,我必须使用它们显示几个表。 例如,考虑这个类:

public class Customer { private Long id; private Date entryDate; private String name; private String address; private String phoneNumber; public Customer(Long id, Date entryDate, String name, String address, String phoneNumber) { this.id = id; this.entryDate = entryDate; this.nombre = name; this.domicilio = address; this.telefono = phoneNumber; } // Getters and setters here } 

我创建了自己的表格模型,从AbstractTableModel扩展,以便直接使用Customer类:

 public class CustomerTableModel extends AbstractTableModel { private final List columnNames; private final List customers; public CustomerTableModel() { String[] header = new String[] { "Entry date", "Name", "Address", "Phone number" }; this.columnNames = Arrays.asList(header); this.customers = new ArrayList(); } @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return Date.class; case 1: return String.class; case 2: return String.class; case 3: return String.class; default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } @Override public Object getValueAt(int rowIndex, int columnIndex) { Customer customer = getCustomer(rowIndex); switch (columnIndex) { case 0: return customer.getEntryDate(); case 1: return customer.getName(); case 2: return customer.getAddress(); case 3: return customer.getPhoneNumber(); default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { if (columnIndex = getColumnCount()) { throw new ArrayIndexOutOfBoundsException(columnIndex); } else { Customer customer = getCustomer(rowIndex); switch (columnIndex) { case 0: customer.setEntryDate((Date)aValue); break; case 1: customer.setName((String)aValue); break; case 2: customer.setAddress((String)aValue); break; case 3: customer.setPhoneNumber((String)aValue); break; } fireTableCellUpdated(rowIndex, columnIndex); } } @Override public int getRowCount() { return this.customers.size(); } @Override public int getColumnCount() { return this.columnNames.size(); } @Override public String getColumnName(int columnIndex) { return this.columnNames.get(columnIndex); } public void setColumnNames(List columnNames) { if (columnNames != null) { this.columnNames.clear(); this.columnNames.addAll(columnNames); fireTableStructureChanged(); } } public List getColumnNames() { return Collections.unmodifiableList(this.columnNames); } public void addCustomer(Customer customer) { int rowIndex = this.customers.size(); this.customers.add(customer); fireTableRowsInserted(rowIndex, rowIndex); } public void addCustomers(List customerList) { if (!customerList.isEmpty()) { int firstRow = this.customers.size(); this.customers.addAll(customerList); int lastRow = this.customers.size() - 1; fireTableRowsInserted(firstRow, lastRow); } } public void insertCustomer(Customer customer, int rowIndex) { this.customers.add(rowIndex, customer); fireTableRowsInserted(rowIndex, rowIndex); } public void deleteCustomer(int rowIndex) { if (this.customers.remove(this.customers.get(rowIndex))) { fireTableRowsDeleted(rowIndex, rowIndex); } } public Customer getCustomer(int rowIndex) { return this.customers.get(rowIndex); } public List getCustomers() { return Collections.unmodifiableList(this.customers); } public void clearTableModelData() { if (!this.customers.isEmpty()) { int lastRow = customers.size() - 1; this.customers.clear(); fireTableRowsDeleted(0, lastRow); } } } 

到现在为止一切都很好。 然而,这种方法至少有两个问题:

  1. 由于我必须为每个类实现一个表模型,然后我将生成大量重复代码以基本上做三件事:定义适当的表头,向/从底层结构添加/删除对象(列表),覆盖setValueAt()getValueAt()方法用于处理用户定义的对象。

  2. 假设我拥有完全相同的客户列表,但我必须在两个不同的表中提供此表,其中包含不同的标题或数据。 我必须子类化我的表模型并覆盖它需要覆盖的任何内容才能满足此要求。 它根本不觉得优雅。

问题:有没有办法摆脱样板代码,使我的表模型灵活可重用?

与其他Swing模型(即: DefaultComboBoxModel , DefaultListModel )一样,我们可以使用Generics来创建灵活且可重用的表模型,同时提供API以使用用户定义的POJO。

此表模型将具有以下特殊function:

  • 它从AbstractTableModel扩展到利用表模型事件处理。
  • 与上面显示的CustomerTableModel不同,此表模型必须是抽象的,因为它不能覆盖getValueAt()方法:仅仅因为我们不知道此表模型将处理的类或数据类型,覆盖上述方法的任务留给子类。
  • 它从AbstractTableModelinheritance了空的setValueAt()实现。 这是有道理的,因为isCellEditable()也从该类inheritance并始终返回false
  • getColumnClass()默认实现也是inheritance的,并且总是返回Object.class

这些function使得这个表模型非常易于实现,具体取决于我们的要求:

  • 如果我们需要显示一个只读表,那么我们必须重写2个方法top: getValueAt()getColumnClass() (建议使用最后一个但不是必需的)。
  • 如果我们的表需要可编辑,那么我们必须覆盖4个方法top:上面提到的两个方法加上isCellEditable()setValueAt()

我们来看看我们的表模型的代码:

 import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.table.AbstractTableModel; /** * Abstract base class which extends from {@code AbstractTableModel} and * provides an API to work with user-defined POJO's as table rows. Subclasses * extending from {@code DataObjectTableModel} must implement * {@code getValueAt(row, column)} method. * 

* By default cells are not editable. If those have to be editable then * subclasses must override both {@code isCellEditable(row, column)} and * {@code setValueAt(row, column)} methods. *

* Finally, it is not mandatory but highly recommended to override * {@code getColumnClass(column)} method, in order to return the appropriate * column class: default implementation always returns {@code Object.class}. * * @param The class handled by this TableModel. * @author dic19 */ public abstract class DataObjectTableModel extends AbstractTableModel { private final List columnNames; private final List data; public DataObjectTableModel() { this.data = new ArrayList<>(); this.columnNames = new ArrayList<>(); } public DataObjectTableModel(List columnIdentifiers) { this(); if (columnIdentifiers != null) { this.columnNames.addAll(columnIdentifiers); } } @Override public int getRowCount() { return this.data.size(); } @Override public int getColumnCount() { return this.columnNames.size(); } @Override public String getColumnName(int columnIndex) { return this.columnNames.get(columnIndex); } public void setColumnNames(List columnNames) { if (columnNames != null) { this.columnNames.clear(); this.columnNames.addAll(columnNames); fireTableStructureChanged(); } } public List getColumnNames() { return Collections.unmodifiableList(this.columnNames); } public void addDataObject(T dataObject) { int rowIndex = this.data.size(); this.data.add(dataObject); fireTableRowsInserted(rowIndex, rowIndex); } public void addDataObjects(List dataObjects) { if (!dataObjects.isEmpty()) { int firstRow = data.size(); this.data.addAll(dataObjects); int lastRow = data.size() - 1; fireTableRowsInserted(firstRow, lastRow); } } public void insertDataObject(T dataObject, int rowIndex) { this.data.add(rowIndex, dataObject); fireTableRowsInserted(rowIndex, rowIndex); } public void deleteDataObject(int rowIndex) { if (this.data.remove(this.data.get(rowIndex))) { fireTableRowsDeleted(rowIndex, rowIndex); } } public void notifyDataObjectUpdated(T domainObject) { T[] elements = (T[])data.toArray(); for (int i = 0; i < elements.length; i++) { if(elements[i] == domainObject) { fireTableRowsUpdated(i, i); } } } public T getDataObject(int rowIndex) { return this.data.get(rowIndex); } public List getDataObjects(int firstRow, int lastRow) { List subList = this.data.subList(firstRow, lastRow); return Collections.unmodifiableList(subList); } public List getDataObjects() { return Collections.unmodifiableList(this.data); } public void clearTableModelData() { if (!this.data.isEmpty()) { int lastRow = data.size() - 1; this.data.clear(); fireTableRowsDeleted(0, lastRow); } } }

因此,采用此表模型和Customer类,完整的实现将如下所示:

 String[] header = new String[] {"Entry date", "Name", "Address", "Phone number"}; DataObjectTableModel model = new DataObjectTableModel<>(Arrays.asList(header)) { @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return Date.class; case 1: return String.class; case 2: return String.class; case 3: return String.class; default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } @Override public Object getValueAt(int rowIndex, int columnIndex) { Customer customer = getDataObject(rowIndex); switch (columnIndex) { case 0: return customer.getEntryDate(); case 1: return customer.getName(); case 2: return customer.getAddress(); case 3: return customer.getPhoneNumber(); default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { if (columnIndex < 0 || columnIndex >= getColumnCount()) { throw new ArrayIndexOutOfBoundsException(columnIndex); } else { Customer customer = getDataObject(rowIndex); switch (columnIndex) { case 0: customer.setEntryDate((Date)aValue); break; case 1: customer.setName((String)aValue); break; case 2: customer.setAddress((String)aValue); break; case 3: customer.setPhoneNumber((String)aValue); break; } fireTableCellUpdated(rowIndex, columnIndex); } } }; 

我们可以看到,在几行代码(LOC <50)中,我们有一个完整的实现。


它适用于JPA实体吗?

只要实体拥有公共getter和setter,它就会这样做。 与JPA实现不同,此表模型不适用于reflection,因此我们必须使用类的公共接口访问对象属性以实现getValueAt()setValueAt()方法。

它适用于JDBC吗?

不,不。 我们必须将结果集包装到域类中,并使用上面提到的类提供的接口。

它适用于Java默认类吗?

是的,它确实。 再一次,使用类’提供的接口。 例如,让我们拿java.io.File类,我们可以有以下表模型实现:

 String[] header = new String[] { "Name", "Full path", "Last modified", "Read", "Write", "Execute", "Hidden", "Directory" }; DataObjectTableModel model = new DataObjectTableModel(Arrays.asList(header)) { @Override public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return String.class; case 1: return String.class; case 2: return Date.class; case 3: return Boolean.class; case 4: return Boolean.class; case 5: return Boolean.class; case 6: return Boolean.class; case 7: return Boolean.class; default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } @Override public Object getValueAt(int rowIndex, int columnIndex) { File file = getDataObject(rowIndex); switch (columnIndex) { case 0: return file.getName(); case 1: return file.getAbsolutePath(); case 2: return new Date(file.lastModified()); case 3: return file.canRead(); case 4: return file.canWrite(); case 5: return file.canExecute(); case 6: return file.isHidden(); case 7: return file.isDirectory(); default: throw new ArrayIndexOutOfBoundsException(columnIndex); } } }; 

与dic19的答案一样,您可以使用行表模型 ,它也使用generics并提供许多常用方法,允许您动态更新TableModel。

您还需要实现一些方法,因为模型也是抽象的。 JButtonTableModel.java代码显示了如何执行此操作。

另外,如果你想真正想要的话,你可以看看Bean Table Model (上面博客中的链接),它扩展了RowTableModel 。 此模型使用reflection来构建TableModel,因此您不必实现自定义模型。