
在java swing应用程序中,尤其是在自定义绘图组件时,一个常见的问题是,即使我们向绘图列表中添加了多个图形对象,最终屏幕上却只显示最后绘制的那一个,或者所有图形都重叠在同一个位置。这通常不是绘图逻辑本身的错误,而是由于对java中对象引用传递机制的误解。
以本案例为例,Painter 类中定义了两个 Point 类型的成员变量 startPoint 和 endPoint:
public class Painter implements ActionListener, MouseListener, MouseMotionListener {
// ...
Point startPoint = new Point(); // 初始化的Point对象
Point endPoint = new Point(); // 初始化的Point对象
// ...
}在鼠标事件处理方法 mousePressed 和 mouseReleased 中,这些 Point 对象的坐标会被更新:
@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) {
canvas.addPrimitive(new Line(startPoint, endPoint, temp)); // 将startPoint和endPoint传递给Line对象
}
// ...
}问题就出在这里。startPoint 和 endPoint 在 Painter 类的生命周期中只被初始化了一次。每次鼠标按下或释放时,setLocation() 方法仅仅是修改了这两个 现有 Point 对象的内部坐标值,而不是创建新的 Point 对象。
当 canvas.addPrimitive(new Line(startPoint, endPoint, temp)) 被调用时,Line 类的构造函数接收的是 Painter 类中 startPoint 和 endPoint 这两个 相同 Point 对象的引用。这意味着,无论你创建多少个 Line 或 Circle 对象,它们内部存储的 startPoint 和 endPoint 引用都指向 Painter 类中那两个唯一的 Point 实例。
立即学习“Java免费学习笔记(深入)”;
因此,每次用户绘制新图形时,Painter 类的 startPoint 和 endPoint 会被更新到最新的鼠标位置。由于所有先前创建的 Line 或 Circle 对象都引用着这两个相同的 Point 实例,当 paintComponent 方法被调用并遍历 primitives 列表进行绘制时,所有图形都会根据 startPoint 和 endPoint 的 当前 值(即最后一次鼠标释放时的值)进行绘制,从而导致所有图形都重叠在最后绘制的位置。
要解决这个问题,核心思想是确保每个绘制的图形对象都拥有其自己独立的坐标数据,而不是共享同一个 Point 实例。这可以通过两种方式实现:
最直接的解决方案是在 mousePressed 和 mouseReleased 方法中,每次都创建新的 Point 对象来存储当前的鼠标位置,而不是修改现有的 startPoint 和 endPoint 成员变量。
修改 Painter 类中的 mousePressed 和 mouseReleased 方法如下:
public class Painter implements ActionListener, MouseListener, MouseMotionListener {
// ...
// startPoint 和 endPoint 仍然可以是成员变量,但现在它们将在每次事件中被重新赋值为新对象
Point startPoint;
Point endPoint;
// ...
@Override
public void mousePressed(MouseEvent e) {
// 创建一个新的 Point 实例来存储鼠标按下的位置
startPoint = new Point(e.getPoint());
}
@Override
public void mouseReleased(MouseEvent e) {
// 创建一个新的 Point 实例来存储鼠标释放的位置
endPoint = new Point(e.getPoint());
if (object == 0) {
// 现在传递给 Line 构造器的是新创建的、独立的 Point 对象
canvas.addPrimitive(new Line(startPoint, endPoint, temp));
}
if (object == 1){
// 同样,Circle 也会接收到独立的 Point 对象
canvas.addPrimitive(new Circle(startPoint, endPoint, temp));
}
canvas.repaint();
}
// ...
}通过这种修改,每次鼠标事件发生时,startPoint 和 endPoint 都会指向一个新的 Point 对象。当这些新的 Point 对象被传递给 Line 或 Circle 的构造器时,每个图形实例将拥有其自己独立的坐标数据,不再受后续鼠标事件的影响。
作为一种防御性编程实践,即使 Painter 类已经创建了新的 Point 实例,在 Line(以及 Circle)的构造器中对传入的 Point 对象进行“防御性拷贝”也是一个好习惯。这意味着,Line 对象不会直接存储传入的 Point 引用,而是创建一个新的 Point 对象,并用传入 Point 的值进行初始化。
这样做的好处是,即使外部代码(例如 Painter 类)不小心修改了传递给 Line 构造器的 Point 对象,Line 实例内部存储的坐标也不会受到影响,从而保证了图形的独立性和数据完整性。
修改 Line 类(以及 Circle 类)的构造器如下:
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");
g.drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
}
@Override
public String toString() {
return "Line";
}
}通过这种修改,Line 对象内部持有的 startPoint 和 endPoint 将是其私有的副本,与外部任何 Point 对象都无关。这提供了更强的封装性和健壮性。
为了清晰起见,以下是整合了上述两种修改方案的关键代码片段:
Painter 类 (部分修改)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.ArrayList; // 假设 PaintingPanel 在同一文件或可访问
public class Painter implements ActionListener, MouseListener, MouseMotionListener {
// ... 其他成员变量
Color temp = Color.RED;
int object = 0; // 0 = line, 1 = circle
PaintingPanel canvas;
// 不再在声明时初始化,而是在 mousePressed/Released 中赋值新对象
Point startPoint;
Point endPoint;
Painter() {
// ... 构造器中的其他UI初始化代码
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
// ... 其他面板和按钮设置
canvas = new PaintingPanel();
// ... 将 canvas 添加到 holder
// 注册监听器
// ... 按钮监听器
canvas.addMouseListener(this); // 将鼠标监听器添加到 canvas 上
// ...
frame.setContentPane(holder);
frame.setVisible(true);
}
// ... actionPerformed, mouseDragged, mouseMoved, mouseClicked, mouseEntered, mouseExited 方法
@Override
public void mousePressed(MouseEvent e) {
// 关键修改:每次按下鼠标时创建新的 Point 对象
startPoint = new Point(e.getPoint());
}
@Override
public void mouseReleased(MouseEvent e) {
// 关键修改:每次释放鼠标时创建新的 Point 对象
endPoint = new Point(e.getPoint());
if (object == 0) {
canvas.addPrimitive(new Line(startPoint, endPoint, temp));
}
if (object == 1){
canvas.addPrimitive(new Circle(startPoint, endPoint, temp));
}
canvas.repaint(); // 通知 PaintingPanel 重新绘制
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new Painter()); // 推荐在事件分发线程中创建UI
}
}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);
// 关键修改:在构造器中进行防御性拷贝
this.startPoint = new Point(start);
this.endPoint = new Point(end);
}
public void drawGeometry(Graphics g) {
// System.out.println("draw geo called"); // 调试信息可以移除
g.drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
}
@Override
public String toString() {
return "Line";
}
}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(); // 可以在 Painter 中统一调用,或者在这里调用,确保UI更新
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 始终调用父类的 paintComponent 来清空背景等
for (PaintingPrimitive shape : primitives) {
// g.drawLine(0,0,100,100); // 这行调试代码可以移除
shape.draw(g); // 调用每个图形的 draw 方法
}
}
}只显示最后一个图形的问题,通常是由于Java中对对象引用传递机制理解不足导致的。通过在鼠标事件处理器中每次创建新的 Point 实例,以及在图形构造器中进行防御性拷贝,我们可以确保每个图形对象都拥有独立的坐标数据,从而正确地在 JPanel 上显示所有绘制的图形。掌握这些概念对于开发健壮和可维护的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号