有没有一种使用 set/getPreferredSize、set/getMinimumSize 和 set/getMaximumSize(或绕过它们的方法)的好方法,而无需不时分配 Dimension 对象?(除了扩展 JComponents)
我的应用程序在鼠标拖动时大量使用此方法来调整大小/移动,我担心周围的所有 Dimension 对象。
我知道 set/getBounds 之类的方法可以与传递给它们的已分配对象一起使用,我似乎找不到其他方法的等价物。
有没有一种使用 set/getPreferredSize、set/getMinimumSize 和 set/getMaximumSize(或绕过它们的方法)的好方法,而无需不时分配 Dimension 对象?(除了扩展 JComponents)
我的应用程序在鼠标拖动时大量使用此方法来调整大小/移动,我担心周围的所有 Dimension 对象。
我知道 set/getBounds 之类的方法可以与传递给它们的已分配对象一起使用,我似乎找不到其他方法的等价物。
有没有好的合作方式
set/getPreferredSize, set/getMinimumSize, and set/getMaximumSize
是的:不要使用它们!改用适当的LayoutManager
。LayoutManager 负责计算这些值,因此最好的方法是将其委托给LayoutManager
. BorderLayout
并且GridBagLayout
已经为布局提供了一个很好的起点。您可能不时必须使用FlowLayout
和/或GridLayout
. 如果你愿意使用第三方库,MigLayout
也是一个有效的LayoutManager
.
考虑阅读关于是否应该避免在 Java Swing 中使用 set[Preferred|Maximum|Minimum]Size 方法的答案?
无论如何,Dimension
对象的内存占用非常小,并且通常在实例化后很快就会被垃圾收集,因此您不必担心它们。
如果您觉得自己遇到了性能问题,请使用分析器来帮助您找到它们。
您必须每秒至少分配数千个这样的对象才能开始担心。HotSpot 的 GC 方案专门针对短寿命对象的低成本进行了优化。它们在 Eden 空间中被分配,如果 Eden 填满时没有幸存者,则只需更新指针即可释放它们。所以最好的建议是先关注其他性能问题;这个优先级应该很低。
“我担心周围的所有 Dimension 对象。”
不要担心几千个微型物体会很快死去。GC 可以处理这个问题。如果您遇到任何性能问题,它们将无法通过避免创建一些 Dimension 来解决。您应该知道,像 setPreferredSize() 这样的调用在内部的成本比作为参数传递的微不足道的维度要高得多(查看 JRE 源代码,它在内部做的更多)。
我对 JComponent 的 get/setPreferredSize 机制和分配行为感兴趣
注意:我不感兴趣:默认布局管理器是如何工作的。让我们考虑一下,我将 preferredSize 值用于与默认布局管理器无关的 X 目的。
官方文档没有给出解决方案,所以我们看一下JRE源码:
这个叫超...
//JComponent.java (1642 ~ 1663)
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
Dimension size = null;
if (ui != null) {
size = ui.getPreferredSize(this);
}
return (size != null) ? size : super.getPreferredSize();
}
这个也叫超...
//JComponent.java (1628 ~ 1639)
public void setPreferredSize(Dimension preferredSize) {
super.setPreferredSize(preferredSize);
}
JComponent 扩展了 Container,我们来看看:
//Container.java (1751 ~ 1800)
public Dimension getPreferredSize() {
return preferredSize();
}
@Deprecated
public Dimension preferredSize() {
Dimension dim = prefSize;
if (dim == null || !(isPreferredSizeSet() || isValid())) {
synchronized (getTreeLock()) {
prefSize = (layoutMgr != null) ?
layoutMgr.preferredLayoutSize(this) :
super.preferredSize();
dim = prefSize;
}
}
if (dim != null){
return new Dimension(dim);
}
else{
return dim;
}
}
正如我所想,它正在返回一个新实例。但是让我们看看在 Component.java 中的 setter 会发生什么(因为 Container 扩展了 Component 但没有覆盖该方法):
//Component.java (2560 ~ 2585)
public void setPreferredSize(Dimension preferredSize) {
Dimension old;
if (prefSizeSet) {
old = this.prefSize;
}
else {
old = null;
}
this.prefSize = preferredSize;
prefSizeSet = (preferredSize != null);
firePropertyChange("preferredSize", old, preferredSize);
}
这只是引用我传递的对象!, 完美的!(确实很危险,但它适合我目前的需求!)
我得出结论,我正在设置我的组件持有和使用的实际对象实例,所以(至少对于这个 JRE 版本)没有必要再次用同一个对象重复调用 setPreferredSize (但不这样做确实很危险因为这个源代码可以在不同的实现中改变),即使这样setPreferredSize 也不会产生垃圾,如果我们用同一个 Dimension 实例调用它。另一方面,调用 getPreferredSize 显然会分配一个新的 Dimension 对象来返回值。
答案是:在某个地方跟踪您的首选尺寸,以尽量减少 getPreferredSize 的使用,并且为了对 API 有礼貌,调用 setPreferredSize,但使用相同的 Dimension 实例来避免垃圾。
Dimension pref = null;
void getNewPrefOnlyWhenNeeded() {
pref = component.getPreferredSize();
}
void calledEveryMillisecond() {
//Here or on the Constructor, MouseListener or wherever
if (pref == null || someCondition) getNewPrefOnlyWhenNeeded();
// do stuff with pref...
...
//setPreferredSize can actually be skipped if we call our layout manager after all is resized. It would be dirty tho...
component.setPreferredSize(pref);
}
经过进一步分析,最大和最小尺寸方法共享这种机制,所以一切都解决了。