编程语言
首页 > 编程语言> > java – 使用几个自定义表模型,避免重复代码

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

作者:互联网

我正在一个项目中,我们有几个域类来建模业务数据.这些类是简单的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<String> columnNames;
    private final List<Customer> 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 < 0 || 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<String> columnNames) {
        if (columnNames != null) {
            this.columnNames.clear();
            this.columnNames.addAll(columnNames);
            fireTableStructureChanged();
        }
    }

    public List<String> 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<Customer> 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<Customer> getCustomers() {
        return Collections.unmodifiableList(this.customers);
    }

    public void clearTableModelData() {
        if (!this.customers.isEmpty()) {
            int lastRow = customers.size() - 1;
            this.customers.clear();
            fireTableRowsDeleted(0, lastRow);
        }
    }
}

到现在为止一切都很好.但是这种方法至少有两个问题:

>由于我必须为每个类实现一个表模型,然后我将生成大量重复代码,基本上做三件事:定义适当的表头,向/从底层结构添加/删除对象(列表),覆盖两者setValueAt()和getValueAt()方法用于处理用户定义的对象.
>假设我拥有完全相同的客户列表,但我必须在两个不同的表中提供此表,其中包含不同的标题或数据.我必须子类化我的表模型并覆盖它需要被覆盖的任何东西才能满足这个要求.它根本不是优雅的.

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

解决方法:

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

该表模型将具有以下特殊功能:

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

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

>如果我们需要显示一个只读表,那么我们必须覆盖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. 
 * <p />
 * 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.
 * <p />
 * 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 <T> The class handled by this TableModel.
 * @author dic19
 */
public abstract class DataObjectTableModel<T> extends AbstractTableModel {

    private final List<String> columnNames;
    private final List<T> data;

    public DataObjectTableModel() {
        this.data = new ArrayList<>();
        this.columnNames = new ArrayList<>();
    }

    public DataObjectTableModel(List<String> 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<String> columnNames) {
        if (columnNames != null) {
            this.columnNames.clear();
            this.columnNames.addAll(columnNames);
            fireTableStructureChanged();
        }
    }

    public List<String> 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<T> 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<T> getDataObjects(int firstRow, int lastRow) {
        List<T> subList = this.data.subList(firstRow, lastRow);
        return Collections.unmodifiableList(subList);
    }

    public List<T> 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<Customer> 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实体吗? 只要实体拥有公共吸气剂和制定者,它就会这样做.与JPA实现不同,此表模型不适用于反射,因此我们必须使用类的公共接口访问对象属性以实现getValueAt()和setValueAt()方法. 它适用于JDBC吗? 不,不.我们必须将结果集包装到域类中,并使用上面提到的类提供的接口. 它适用于Java默认类吗? 是的,它确实.再一次,使用类’提供的接口.例如,让我们拿java.io.File类,我们可以有以下表模型实现:

String[] header = new String[] {
    "Name",
    "Full path",
    "Last modified",
    "Read",
    "Write",
    "Execute",
    "Hidden",
    "Directory"
};

DataObjectTableModel<File> model = new DataObjectTableModel<File>(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);
        }
    }
};

标签:tablemodel,java,swing,jtable,abstracttablemodel
来源: https://codeday.me/bug/20190722/1505949.html