Обновление JTable в Swing приводит к исключению

#java #multithreading #swing #exception #jtable

#java #многопоточность #swing #исключение #jtable

Вопрос:

Поэтому я, по сути, пытаюсь обновлять свою JTable каждый раз при нажатии клавиши или при обновлении со стороны наблюдателя. Иногда я получаю замороженное приложение и эту трассировку стека…

Исключение в потоке «AWT-EventQueue-0» java.lang.Исключение ArrayIndexOutOfBoundsException: 5 > = 5 в java.util.Vector.ElementAt (неизвестный источник) в javax.swing.table.DefaultTableColumnModel.getColumn(неизвестный источник) в javax.swing.plaf.synth.SynthTableUI.paintCells(неизвестный источник) в javax.swing.plaf.synth.SynthTableUI.paint (неизвестный источник) в javax.swing.plaf.synth.SynthTableUI.update (неизвестный источник) в javax.swing.JComponent.paintComponent (неизвестный источник) в javax.swing.JComponent.paint (неизвестный источник) в javax.swing.JComponent.paintToOffscreen (неизвестный источник) в javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(неизвестный источник) в javax.swing.RepaintManager$PaintManager.paint (неизвестный источник) в javax.swing.RepaintManager.paint (неизвестный источник) в javax.swing.JComponent._paintimediately(неизвестный источник) в javax.swing.JComponent.Paintimediately(неизвестный источник) в javax.swing.RepaintManager.paintDirtyRegions (неизвестный источник) в javax.swing.RepaintManager.paintDirtyRegions (неизвестный источник) в javax.swing.RepaintManager.seqPaintDirtyRegions(неизвестный источник) в javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run (неизвестный источник) при java.awt.event.InvocationEvent.dispatch (неизвестный источник) в java.awt.EventQueue.dispatchEvent (неизвестный источник) в java.awt.EventDispatchThread.pumpponeeventforfilters(неизвестный источник) в java.awt.EventDispatchThread.pumpEventsForFilter (неизвестный источник) в java.awt.EventDispatchThread.pumpeventsforchierarchy(неизвестный источник) в java.awt.EventDispatchThread.pumpEvents (неизвестный источник) в java.awt. EventDispatchThread.pumpEvents(Неизвестный источник) в java.awt.EventDispatchThread.run (Неизвестный источник) Исключение в потоке «AWT-EventQueue-0» java.lang.Исключение ArrayIndexOutOfBoundsException: 4 > = 4 в java.util.Vector.ElementAt (неизвестный источник) в javax.swing.table.DefaultTableColumnModel.getColumn(неизвестный источник) в sun.swing.SwingUtilities2.convertColumnIndexToModel(неизвестный источник) в javax.swing.JTable.convertColumnIndexToModel(неизвестный источник) в javax.swing.JTable.getColumnClass(неизвестный источник) в javax.swing.plaf.synth.SynthTableUI$SynthTableCellRenderer.getTableCellRendererComponent(неизвестный источник) в javax.swing.JTable.prepareRenderer (неизвестный источник) в javax.swing.plaf.synth.SynthTableUI.paintCell(неизвестный источник) в javax.swing.plaf.synth.SynthTableUI.paintCells(неизвестный источник) в javax.swing.plaf.synth.SynthTableUI.paint (неизвестный источник) в javax.swing.plaf.synth.SynthTableUI.update (неизвестный источник) в javax.swing.JComponent.paintComponent (неизвестный источник) в javax.swing.JComponent.paint (неизвестный источник) в javax.swing.JComponent.paintToOffscreen (неизвестный источник) в javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(неизвестный источник) в javax.swing.RepaintManager$PaintManager.paint (неизвестный источник) в javax.swing.RepaintManager.paint (неизвестный источник) в javax.swing.JComponent._paintimediately(неизвестный источник) в javax.swing.JComponent.Paintimediately(неизвестный источник) в javax.swing.RepaintManager.paintDirtyRegions (неизвестный источник) в javax.swing.RepaintManager.paintDirtyRegions (неизвестный источник) в javax.swing.RepaintManager.seqPaintDirtyRegions(неизвестный источник) в javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(неизвестный источник) на java.awt.событие.InvocationEvent.dispatch (неизвестный источник) в java.awt.EventQueue.dispatchEvent (неизвестный источник) в java.awt.EventDispatchThread.pumpponeeventforfilters(неизвестный источник) в java.awt.EventDispatchThread.pumpEventsForFilter (неизвестный источник) в java.awt.EventDispatchThread.pumpeventsforchierarchy(неизвестный источник) в java.awt.EventDispatchThread.pumpEvents (неизвестный источник) в java.awt. EventDispatchThread.pumpEvents(Неизвестный источник) в java.awt.EventDispatchThread.run (Неизвестный источник)

Если это поможет, вот мой код для обновления таблицы:

 public synchronized void refreshTable()
{
    Customer cust = custManager.getCustomer(phoneNumber.getText());

    if (cust == null)
    {
        table.setModel(new DefaultTableModel(new Object[][] {}, tableHeader) {
                @SuppressWarnings("rawtypes")
                Class[] columnTypes = new Class[] {Integer.class, String.class,
                                                   Object.class, Object.class, 
                                                   Object.class, Object.class};
                @SuppressWarnings({ "unchecked", "rawtypes" })
                public Class getColumnClass(int columnIndex) {
                    return columnTypes[columnIndex];
                }

                public boolean isCellEditable(int row, int column) {
                    return false;
                }
        });
        table.getColumnModel().getColumn(0).setPreferredWidth(40);
        table.getColumnModel().getColumn(1).setPreferredWidth(120);
        return;
    }

    Object[][] grid = new Object[cust.getOrderHistory().size()][6];

    SimpleDateFormat sdf = new SimpleDateFormat("MMM/dd/yyyy HH:mm");
    NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();

    int i = 0;
    for (Entry<GregorianCalendar, Order> orderEntry : cust.getOrderHistory())
    {
        Order order = orderEntry.getValue();

        grid[i][0] = order.getOrderID();
        grid[i][1] = sdf.format((order.getProcessedTimestamp().getTime()));
        grid[i][2] = currencyFormat.format(order.getSubTotal()/100.00);
        grid[i][3] = currencyFormat.format(order.getTaxedAmount()/100.00);
        grid[i][4] = currencyFormat.format(order.getTotal()/100.00);
        grid[i][5] = order.getOrderStatus();

        i  ;
    }

    DefaultTableModel dft = new DefaultTableModel(grid, tableHeader) {
            @SuppressWarnings("rawtypes")
            Class[] columnTypes = new Class[] {Integer.class, String.class, 
                                               Object.class, Object.class, 
                                               Object.class, Object.class};
            @SuppressWarnings({ "unchecked", "rawtypes" })
            public Class getColumnClass(int columnIndex) {
                return columnTypes[columnIndex];
            }

            public boolean isCellEditable(int row, int column) {
                return false;
            }
        };

    table.setModel(dft);
    table.getColumnModel().getColumn(0).setPreferredWidth(40);
    table.getColumnModel().getColumn(1).setPreferredWidth(120);
}
  

Для справки, я пытался обернуть его в try / catch, и нет, это не поможет. Я также пытался изменить обработчик исключений по умолчанию, и это также не увенчалось успехом.

Комментарии:

1. Это происходит только в том случае, если вы печатаете очень быстро? То есть, если вы осторожно нажимаете по одной клавише за раз, ведет ли она себя так, как вы ожидаете? Или проблема возникает независимо от скорости ввода?

2. Это произошло, когда я быстро набрал текст, и это происходит, когда экземпляр получает вызов «update». Я бы склонен согласиться, что это условие гонки, но при этом оно было просто странным.

Ответ №1:

Одна из самых больших проблем, которые я вижу, заключается в том, что вы не привязываете таблицу к своим данным, а затем обновляете ячейки по мере их изменения, вы меняете всю модель данных из-под таблицы каждый раз, когда нажимаете клавишу.

Я бы рискнул предположить, что вы испытываете состояние «гонки». Вы нажимаете клавишу, и это запускает процесс перестройки вашей таблицы и ее модели. На полпути к этой перестройке (помните, Swing использует потоки, поэтому обновление таблицы все еще происходит, пока вы вводите текст) вы переходите и запускаете другую перестройку, которая выводит модель из-под первого обновления, которое все еще выполняется. Конечно, не видя всего вашего кода, я не могу быть уверен в этом, но я могу предложить несколько вещей, чтобы сделать вещи немного чище и менее подверженными проблемам параллелизма.

Вместо того, чтобы писать код, который берет данные вашей модели и преобразует их в простую табличную модель при каждом нажатии клавиши, вы можете создать свой собственный класс table model и реализовать его для просмотра непосредственно в вашей модели. Таким образом, у вас не возникает проблемы с постоянной повторной передачей данных между вашей моделью и массивом.

Чтобы создать свою собственную табличную модель, вы можете просто расширить AbstractTableModel ( http://download.oracle.com/javase/6/docs/api/javax/swing/table/AbstractTableModel.html ). В оригинальном руководстве по Java есть отличный пример ( http://download.oracle.com/javase/tutorial/uiswing/components/table.html#data ) если вы не уверены, как это сделать. Самое важное для вас — это метод «getValueAt», который принимает строку и столбец и возвращает, какое значение должна содержать эта ячейка. Вот куда вы хотите поместить код, который просматривает ваш объект «Order» и выдает желаемый результат.

Как только у вас появится пользовательский объект TableModel, просто передайте его новый экземпляр в метод «setModel» таблицы при ее первой инициализации.

Как только это будет сделано, вы захотите обработать разумные обновления модели. Чтобы обновить все данные в таблице, вы можете запустить событие «fireTableDataChanged». В качестве альтернативы, вы можете обновлять только те части вашей таблицы, которые действительно необходимо изменить, а не все это каждый раз. Вместо того, чтобы вдаваться в подробности здесь, я просто укажу вам на отличную страницу руководства по Java, посвященную именно этому:http://download.oracle.com/javase/tutorial/uiswing/components/table.html#fire

Комментарии:

1. Вау, это выглядит очень полезным — однако я не совсем понимаю, почему это улучшило бы проблемы с параллелизмом. Просто ли изменения в одной ячейке менее «опасны», чем полное обновление? Думаю, я могу с этим справиться. Я обязательно сообщу вам к завтрашнему утру, работает это или нет, потому что я чертовски устал. Но я действительно ценю ответ и дам вам соответствующую обратную связь. =)

2. Это должно решить проблему параллелизма, которую вы описали, потому что в любое время таблица может определить, что содержат все ячейки. В вашем исходном коде в определенных точках таблица может быть не в состоянии получить доступ к отдельным ячейкам в модели, потому что эта модель была извлечена из-под нее, что вызывает ошибки «вне границ». Вместо этого, если вы используете пользовательские модели и события обновления модели (т. Е. fireTableDataChanged), таблица может обрабатывать и ставить в очередь обновление этих табличных данных. Именно так и предполагалось использовать таблицы, так что это намного безопаснее.

3. @ Erica пожалуйста, измените ссылку для AbstactTableModel с Java 1.4 на текущую Java 1.6, некоторые важные изменения…

4. @mKorbel, готово, спасибо за предупреждение. (В силу привычки я все еще использую 1.4 в своей повседневной работе.) 🙂

Ответ №2:

Синхронизировано? Я боюсь, что вы выполняете вызовы Swing из EDT. Вы позаботились о том, чтобы не нарушать правило однопоточности Swing?

Комментарии:

1. Я пытался — по сути, если статус заказа обновляется в фоновом режиме, то этот класс (который является наблюдателем) должен попытаться обновить таблицу. Я предположил, что синхронизация остановит условия гонки. Пожалуйста, простите меня, если я ошибаюсь. Я довольно начинающий разработчик.

2. @user744526, синхронизация на уровне метода аналогична получению блокировки класса объекта, содержащего метод. Это останавливает параллельный запуск других синхронизированных методов для объектов с тем же типом класса . Это не остановит параллельный запуск кода для других типов объектов, включая объекты JTable или DefaultTableModel. Это также не остановит параллельную обработку несинхронизированных методов, даже для одного и того же объекта. На самом деле вам не следует его использовать, если вы действительно уверены, что знаете, какого сценария вы пытаетесь избежать. В противном случае это просто причинит вам огорчение. 🙂

3. но я согласился с @ Hovercraft Full Of Eels, та же ошибка, которую вы можете …, используя AbstractTabelModel как из defautablemodel, это примерно EDT, как было предложено,

Ответ №3:

Как уже подчеркивалось в других ответах: замораживание является типичным признаком того, что вы вызываете таблицу обновления из потока, который не является EDT — общим правилом является то, что весь доступ к сотрудникам Swing, будь то view или model, должен происходить в EDT.

Самый простой способ (просто для начала) добиться этого — вызывать каждый вызов refreshTable

   public void invokeRefreshTable(final OrderTableModel model) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            refreshTable(model)
        }
    }); 

  }
  

На самом деле, вы, вероятно, не хотите обновлять таблицу, вы хотите обновить ее модель, сохранив конфигурацию таблицы как есть — нет необходимости перенастраивать ширину столбца. (Также уже упоминалось) ваш фрагмент кода выглядит так, как будто вы могли бы извлечь выгоду из пользовательской реализации TableModel: это список заказов. Способ украсить этот список TableModel, что-то вроде:

 public static class OrderTableModel extends AbstractTableModel {
    List<Order> orders = new ArrayList<Order>();
    Class<?>[] columnTypes = new Class[] { Integer.class,
            Timestamp.class, BigDecimal.class, BigDecimal.class, BigDecimal.class,
            Object.class };

    public void setOrders(Customer customer) {
        setOrders(customer != null ? customer.getOrderHistory() : null);
    }

    public void setOrders(Map<?, ?> orderHistory) {
        clear();
        if (orderHistory == null) return;
        for (Entry<?, ?> orderEntry: orderHistory) {
            orders.add(orderEntry.getValue());
        }
        fireTableRowsInserted(0, orders.size() - 1);
    }

    public void clear() {
        int rowCount = orders.size();
        orders.clear();
        if (rowCount > 0 ) {
            fireTableRowsDeleted(0, rowCount -1);
        }
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
       switch (columnIndex) {
           case 0: return orders.get(rowIndex).getOrderID();
           //....

           default:
               break;
       }

        return null;
    }

    @Override
    public int getRowCount() {
        return orders.size();
    }

    @Override
    public int getColumnCount() {
        return columnTypes.length;
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return columnTypes[columnIndex];
    }

}

// the single model instance to re-use always
OrderTableModel model = new OrderTableModel()
// separate initial configuration of the table
JTable table = new JTable(model);
// register custom renderers
table.setDefaultRenderer(BigDecimal.class, new BigDecimalRenderer());
table.setDefaultRenderer(Timestamp.class, new TimeStampRenderer());
// config the table
table.getColumnModel().getColumn(0).setPreferredSize(...)
....

// the refresh method crumpled down to a single line
public void refreshTable(OrderTableModel model) {
    model.setCustomer(customManager.getCustomer(phoneNumber))
}

// if refreshing is needed
invokeRefreshTable(model);
  

Примечание:

  • модель содержит реальные объекты и ссылки на их реальные свойства: форматирование является задачей CellRenderers
  • модель может иметь столько методов, связанных с доменом, сколько потребуется: здесь она может повторно заполнять себя либо из экземпляра Customer, либо из его OrderHistory