
在基于Java Swing构建的绘图应用程序中,一个常见的问题是,当用户绘制多个图形(如线条或圆形)时,屏幕上却只显示最后绘制的那个图形。这通常是由于对Java中对象引用传递机制的误解和不当使用造成的。
问题的根源在于,程序中用于记录图形起始点和结束点的Point对象被多个图形实例共享。当用户完成一次绘图操作后,新的图形对象被创建并添加到绘制列表中。然而,如果这些新图形对象接收的是对同一个Point实例的引用,那么当用户再次开始新的绘图操作并更新这些Point实例的坐标时,所有先前创建的图形对象也会“看到”这些更新后的坐标,从而导致它们全部重绘到新的位置,最终只显示最后一个图形。
让我们通过原始代码片段来具体分析这个问题。
原始代码中的问题所在:
立即学习“Java免费学习笔记(深入)”;
在Painter类中,startPoint和endPoint被声明为类的成员变量:
public class Painter implements ActionListener, MouseListener, MouseMotionListener {
// ...
Point startPoint = new Point();
Point endPoint = new Point();
// ...
}在mousePressed和mouseReleased方法中,这两个Point对象的location被更新:
@Override
public void mousePressed(MouseEvent e) {
startPoint.setLocation(e.getPoint()); // 更新现有startPoint的坐标
}
@Override
public void mouseReleased(MouseEvent e) {
endPoint.setLocation(e.getPoint()); // 更新现有endPoint的坐标
if (object == 0) {
// 将startPoint和endPoint的引用传递给Line构造器
canvas.addPrimitive(new Line(startPoint, endPoint, temp));
}
// ...
}当Line(或Circle)对象被创建时,它接收的是Painter类成员变量startPoint和endPoint的引用。这意味着,Line对象内部的startPoint和endPoint字段实际上指向了与Painter类中相同的Point对象。因此,每次鼠标释放时,startPoint和endPoint的location被更新,所有之前创建并添加到primitives列表中的Line或Circle对象,由于它们持有相同的Point对象引用,它们的绘制位置也会随之改变,最终所有图形都显示在最后一次绘制的位置上。
要解决这个问题,核心在于确保每个绘制的图形对象都拥有自己独立的、不可变的起始点和结束点坐标。这意味着在每次创建新的图形时,都应该创建新的Point对象实例,而不是复用或修改现有的Point对象。
在Painter类的mousePressed和mouseReleased方法中,不再仅仅更新startPoint和endPoint的坐标,而是直接创建新的Point对象。
修改后的Painter类相关代码:
public class Painter implements ActionListener, MouseListener, MouseMotionListener {
// ...
// 可以将startPoint和endPoint初始化为null或在mousePressed中直接赋值
Point startPoint;
Point endPoint;
// ...
@Override
public void mousePressed(MouseEvent e) {
// 直接创建一个新的Point对象,而不是修改现有对象的location
startPoint = new Point(e.getPoint());
}
@Override
public void mouseReleased(MouseEvent e) {
// 同样,创建一个新的Point对象
endPoint = new Point(e.getPoint());
if (object == 0) {
// 现在传递的是新创建的、独立的Point对象引用
canvas.addPrimitive(new Line(startPoint, endPoint, temp));
}
if (object == 1){
canvas.addPrimitive(new Circle(startPoint, endPoint, temp));
}
canvas.repaint();
}
// ...
}通过startPoint = new Point(e.getPoint());和endPoint = new Point(e.getPoint());,我们确保了每次鼠标按下和释放时,都会创建全新的Point对象,这些对象包含了当前鼠标事件的准确坐标。当这些新的Point对象被传递给Line或Circle的构造器时,每个图形实例都将持有其独特的坐标信息,不再受后续绘图操作的影响。
尽管步骤一已经解决了核心问题,但为了代码的健壮性和防止未来可能出现的意外行为(例如,如果Point对象在其他地方被意外修改),最佳实践是在图形(如Line或Circle)的构造器中也进行一次“防御性拷贝”。这意味着即使传入的Point对象在外部被修改,图形实例内部的坐标也不会受到影响。
修改后的Line类构造器:
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Color;
public class Line extends PaintingPrimitive{
Point startPoint; // 无需初始化,将在构造器中赋值
Point endPoint; // 无需初始化,将在构造器中赋值
public Line(Point start, Point end, Color c) {
super(c);
// 对传入的Point对象进行防御性拷贝,确保Line实例持有独立的Point对象
this.startPoint = new Point(start);
this.endPoint = new Point(end);
}
public void drawGeometry(Graphics g) {
System.out.println("draw geo called"); // 注意:在实际应用中应避免在paintComponent中进行System.out.println,会影响性能
g.drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
}
@Override
public String toString() {
return "Line";
}
}通过在Line构造器中使用new Point(start)和new Point(end),即使Painter类中startPoint和endPoint成员变量的引用在未来被不当地复用或修改,已经创建的Line对象也不会受到影响,因为它拥有自己独立的坐标副本。
PaintingPanel类中的paintComponent方法负责实际的绘制工作。原始代码中包含了一行g.drawLine(0,0,100,100);,这行代码会在每次重绘时额外绘制一条从(0,0)到(100,100)的固定线条。这通常不是期望的行为,应将其移除。
修正后的PaintingPanel类:
import java.util.ArrayList;
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.Color;
public class PaintingPanel extends JPanel {
ArrayList<PaintingPrimitive> primitives = new ArrayList<PaintingPrimitive>();
PaintingPanel() {
setBackground(Color.WHITE);
}
public void addPrimitive(PaintingPrimitive obj) {
primitives.add(obj);
this.repaint(); // 添加新图形后立即请求重绘
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 必须调用父类的paintComponent来清空背景等
// 遍历并绘制所有存储的图形基元
for (PaintingPrimitive shape : primitives) {
// 移除不必要的固定线条绘制
// g.drawLine(0,0,100,100);
shape.draw(g);
}
}
}通过以上修改,你的Java Swing绘图应用程序将能够正确地显示所有绘制的图形,而不仅仅是最后一个。
核心要点回顾:
遵循这些原则,你将能更好地管理Java Swing应用程序中的对象状态,创建出功能更稳定、行为更符合预期的图形界面。
以上就是解决Java Swing绘图应用仅显示最后一个图形的问题:理解引用传递与对象拷贝的详细内容,更多请关注php中文网其它相关文章!
Windows激活工具是正版认证的激活工具,永久激活,一键解决windows许可证即将过期。可激活win7系统、win8.1系统、win10系统、win11系统。下载后先看完视频激活教程,再进行操作,100%激活成功。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号