gpt4 book ai didi

java - 如何调试堆栈跟踪未引用我的类的 NullPointerException?

转载 作者:行者123 更新时间:2023-12-02 02:23:24 27 4
gpt4 key购买 nike

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

我最近将谓词放入延迟系统中(完整代码如下),以避免在过滤大型数据集时卡住 UI。

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

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;
});
}
}

之前的版本经过严格测试,没有任何问题。现在,我可以通过在程序启动时快速更改过滤器文本来非常可靠地生成它。我必须假设崩溃是由于此更改的代码而引起的,但由于我的堆栈跟踪根本没有引用我的代码库,所以我不太确定从哪里开始。

编辑:有趣的是,将 sleep 时间从 25 毫秒更改为 50 毫秒似乎可以消除我计算机上的错误。这让我非常紧张,因为我必须想象不同速度系统的“正确”值是不同的。

最佳答案

这绝对是并发问题。

TableView 正在尝试在渲染脉冲期间进行渲染,并且在发生这种情况时支持列表正在更改。由于保存实际元素的 Element 对象已“神秘”消失,因此引发了 NullPointerException

依赖 sleep 时间是一个非常糟糕的主意 - 我相信您也已经意识到这一点。解决这个问题主要有两种方法:

在 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 线程上运行。

不要使用另一个线程,而是在类中创建一个 TimeLine 对象。

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 线程上运行,因此不会出现那些奇怪的异常。

另一个好处是,您不必管理线程。尽管您已将线程设置为守护进程,但该线程仍会每 25 毫秒运行一次 Runnable,直到您的程序完全终止。

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

关于java - 如何调试堆栈跟踪未引用我的类的 NullPointerException?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48144750/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com