代码之家  ›  专栏  ›  技术社区  ›  Grumblesaurus

如果stacktrace没有引用我的类,如何调试NullPointerException?

  •  1
  • Grumblesaurus  · 技术社区  · 7 年前

    我有一个表,它的行根据文本输入进行过滤。

    我最近将谓词放在一个延迟系统中(下面是完整的代码),以避免在过滤大数据集时冻结UI。

    我可以在程序启动时通过垃圾发送过滤器输入文本框来生成以下异常。正如您将看到的,整个异常都发生在Oracle的代码库中。我在stacktrace上没有看到我的任何项目类。

    Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
        at javafx.collections.transformation.SortedList$Element.access$200(SortedList.java:272)
        at javafx.collections.transformation.SortedList.get(SortedList.java:170)
        at javafx.scene.control.TableColumn.getCellObservableValue(TableColumn.java:562)
        at javafx.scene.control.TableCell.updateItem(TableCell.java:644)
        at javafx.scene.control.TableCell.indexChanged(TableCell.java:468)
        at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:116)
        at com.sun.javafx.scene.control.skin.TableRowSkinBase.requestCellUpdate(TableRowSkinBase.java:659)
        at com.sun.javafx.scene.control.skin.TableRowSkinBase.lambda$init$0(TableRowSkinBase.java:159)
        at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:137)
        at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
        at javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(ObjectPropertyBase.java:105)
        at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
        at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
        at javafx.scene.control.Cell.setItem(Cell.java:403)
        at javafx.scene.control.Cell.updateItem(Cell.java:670)
        at javafx.scene.control.TableRow.updateItem(TableRow.java:259)
        at javafx.scene.control.TableRow.indexChanged(TableRow.java:225)
        at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:116)
        at com.sun.javafx.scene.control.skin.VirtualFlow.setCellIndex(VirtualFlow.java:1957)
        at com.sun.javafx.scene.control.skin.VirtualFlow.addTrailingCells(VirtualFlow.java:1344)
        at com.sun.javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1197)
        at com.sun.javafx.scene.control.skin.VirtualFlow.setCellCount(VirtualFlow.java:231)
        at com.sun.javafx.scene.control.skin.TableViewSkinBase.updateRowCount(TableViewSkinBase.java:567)
        at com.sun.javafx.scene.control.skin.VirtualContainerBase.checkState(VirtualContainerBase.java:113)
        at com.sun.javafx.scene.control.skin.VirtualContainerBase.layoutChildren(VirtualContainerBase.java:108)
        at com.sun.javafx.scene.control.skin.TableViewSkinBase.layoutChildren(TableViewSkinBase.java:696)
        at javafx.scene.control.Control.layoutChildren(Control.java:578)
        at javafx.scene.Parent.layout(Parent.java:1087)
        at javafx.scene.Parent.layout(Parent.java:1093)
        at javafx.scene.Parent.layout(Parent.java:1093)
        at javafx.scene.Parent.layout(Parent.java:1093)
        at javafx.scene.Parent.layout(Parent.java:1093)
        at javafx.scene.Parent.layout(Parent.java:1093)
        at javafx.scene.Parent.layout(Parent.java:1093)
        at javafx.scene.Parent.layout(Parent.java:1093)
        at javafx.scene.Scene.doLayoutPass(Scene.java:552)
        at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2397)
        at com.sun.javafx.tk.Toolkit.lambda$runPulse$3(Toolkit.java:355)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
        at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
        at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
        at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
        at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:319)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
        at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
        at com.sun.glass.ui.gtk.GtkApplication.lambda$null$5(GtkApplication.java:139)
        at java.lang.Thread.run(Thread.java:748)
    

    我最近添加的代码是这样的,它取代了对表进行过滤的常规方法。基本思想是避免应用不必要的谓词。

    import java.text.Normalizer;
    import java.util.ArrayList;
    
    import javafx.collections.transformation.FilteredList;
    import net.joshuad.hypnos.Album;
    
    public class ThrottledAlbumFilter {
        private String requestedFilter = "";
        private long timeRequestMadeMS = 0;
    
        private Thread filterThread;
        private boolean interruptFiltering = false;
    
        private String currentAppliedFilter = "";
    
        private FilteredList <Album> filteredList;
    
        public ThrottledAlbumFilter ( FilteredList <Album> filteredList ) {
            this.filteredList = filteredList;
    
            filterThread = new Thread ( () -> {
                while ( true ) {
                    String filter = requestedFilter;
    
                    if ( !filter.equals( currentAppliedFilter ) ) {
                        if ( System.currentTimeMillis() >= timeRequestMadeMS + 100 ) {
                            interruptFiltering = false;
                            setPredicate( filter );
                            currentAppliedFilter = filter;
                        }
                    }
    
                    try { Thread.sleep( 25 ); } catch ( InterruptedException e ) {} 
                }
            });
    
            filterThread.setDaemon( true );
            filterThread.start();
        }
    
        public void setFilter ( String filter ) {
            if ( filter == null ) filter = "";
            timeRequestMadeMS = System.currentTimeMillis();
            this.requestedFilter = filter;
            interruptFiltering = true;
        }
    
        private void setPredicate ( String filterText ) {
            filteredList.setPredicate( album -> {
                if ( interruptFiltering ) return true;
                if ( filterText.isEmpty() ) return true;
    
                ArrayList <String> matchableText = new ArrayList <String>();
    
                matchableText.add( album.getAlbumArtist().toLowerCase() );
                matchableText.add( album.getYear().toLowerCase() );
                matchableText.add( album.getFullAlbumTitle().toLowerCase() );
    
                matchableText.add( Normalizer.normalize( album.getFullAlbumTitle(), Normalizer.Form.NFD )
                    .replaceAll( "[^\\p{ASCII}]", "" ).toLowerCase() 
                );
    
                matchableText.add( Normalizer.normalize( album.getYear(), Normalizer.Form.NFD )
                    .replaceAll( "[^\\p{ASCII}]", "" ).toLowerCase()
                );
    
                matchableText.add( Normalizer.normalize( album.getAlbumArtist(), Normalizer.Form.NFD )
                    .replaceAll( "[^\\p{ASCII}]", "" ).toLowerCase() 
                );
    
                String[] lowerCaseFilterTokens = filterText.toLowerCase().split( "\\s+" );
                for ( String token : lowerCaseFilterTokens ) {
                    boolean tokenMatches = false;
                    for ( String test : matchableText ) {
                        if ( test.contains( token ) ) {
                            tokenMatches = true;
                        }
                    }
    
                    if ( !tokenMatches ) {
                        return false;
                    }
                }
    
                return true;
            });
        }
    }
    

    之前的版本经过了大量测试,没有问题。现在,我可以在程序启动时快速更改过滤器文本,从而非常可靠地生成它。我不得不假设崩溃是由于这些更改的代码引起的,但由于我的堆栈跟踪根本没有引用我的代码库,我真的不确定从哪里开始。

    编辑:有趣的是,将睡眠时间从25ms改为50ms似乎可以消除我电脑上的错误。这让我非常紧张,因为我不得不想象不同速度系统的“正确”值是不同的。

    1 回复  |  直到 7 年前
        1
  •  3
  •   Jai    7 年前

    这绝对是并发问题。

    这个 TableView 正在尝试在渲染脉冲期间进行渲染,并且发生此情况时备份列表正在更改。 NullPointerException 正在抛出,因为 Element 持有实际元素的对象“神秘地”消失了。

    依赖睡眠时间是一个非常糟糕的主意——我相信你也意识到了这一点。有两种主要方法可以解决此问题:

    在UI线程(即JavaFX应用程序线程)上修改UI

    您也可以这样做,只是要包装 filteredList.setPredicate() 请来 Platform.runLater() .

    换句话说,它应该是这样的:

    final Predicate<Album> predicate = album -> {
        // Whatever you have
    };
    
    Platform.runLater(() -> filteredList.setPredicate(predicate));
    

    执行此操作将卸载 Predicate 在后台线程中,而实际更新是在UI线程上完成的。我想说的是,这将导致大量的处理被转移回UI线程,但这可能是不可避免的。

    但是,您仍然可以跳过一些谓词更改,因为您在 Runnable 线程中的对象。我假设这满足了“避免应用不必要的谓词”的要求

    使用时间线

    JavaFX有一个非常方便的类,叫做 TimeLine 这就像计时器一样,在UI线程上运行。

    创建一个 时间线 对象。

    private String filter;
    
    private final Timeline timeline = new Timeline(
        new KeyFrame(Duration.millis(100),
                     ae -> setPredicate()
        ));
    
    public void setFilter ( String filter ) {
        if ( filter == null ) filter = "";
        if ( !this.filter.equals( filter ) ) {
            this.filter = filter;
            this.timeline.playFromStart();
        }
    }
    
    private void setPredicate() {
        final String filterText = this.filter;
    
        // The rest remains pretty much the same.
    }
    

    使用这种方法会导致所有代码都在UI线程上运行,所以不会出现这些奇怪的异常。

    另一个好处是,您不必管理线程。虽然已将线程设置为守护程序,但该线程仍将运行 可运行的 每25ms一次,直到程序完全终止。

    最后,这提供了从过滤器字符串中的最后一次更改开始的统一延迟。这将提供更好的用户体验。