0

0

解决Java Swing绘图应用仅显示最后一个图形的问题:理解引用传递与对象拷贝

聖光之護

聖光之護

发布时间:2025-09-24 11:18:34

|

283人浏览过

|

来源于php中文网

原创

解决java swing绘图应用仅显示最后一个图形的问题:理解引用传递与对象拷贝

本文探讨了Java Swing绘图应用中一个常见问题:程序仅显示最后绘制的图形。核心原因在于Point对象的引用传递机制导致所有图形实例共享并更新相同的坐标数据。解决方案是确保在创建图形对象时,为每个图形实例分配独立的Point对象副本,避免引用共享,同时建议在构造器中进行防御性拷贝以增强代码健壮性。

理解问题:为何只有最后一个图形可见?

在基于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对象实例,而不是复用或修改现有的Point对象。

步骤一:在鼠标事件中创建新的Point实例

在Painter类的mousePressed和mouseReleased方法中,不再仅仅更新startPoint和endPoint的坐标,而是直接创建新的Point对象。

修改后的Painter类相关代码:

Type
Type

生成草稿,转换文本,获得写作帮助-等等。

下载
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类(修正示例代码中的冗余绘制)

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 primitives = new ArrayList();

    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绘图应用程序将能够正确地显示所有绘制的图形,而不仅仅是最后一个。

核心要点回顾:

  1. 理解引用传递: 在Java中,对象变量存储的是对象的引用。当一个对象作为参数传递或赋值给另一个变量时,传递的是引用本身,而不是对象的副本。
  2. 创建独立实例: 当你需要每个逻辑实体(如本例中的每个Line或Circle)拥有其独立的数据状态时,务必创建新的对象实例,而不是复用或修改共享的实例。
  3. 防御性拷贝: 在对象的构造器中对传入的可变对象参数进行拷贝,是一种良好的编程习惯,可以有效防止外部修改对内部状态造成意外影响,提高代码的健壮性。
  4. paintComponent的正确使用: paintComponent方法应该只包含绘制逻辑,避免执行耗时操作(如System.out.println)或修改应用程序状态。始终先调用super.paintComponent(g)。

遵循这些原则,你将能更好地管理Java Swing应用程序中的对象状态,创建出功能更稳定、行为更符合预期的图形界面。

相关文章

Windows激活工具
Windows激活工具

Windows激活工具是正版认证的激活工具,永久激活,一键解决windows许可证即将过期。可激活win7系统、win8.1系统、win10系统、win11系统。下载后先看完视频激活教程,再进行操作,100%激活成功。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
java值传递和引用传递有什么区别
java值传递和引用传递有什么区别

java值传递和引用传递的区别:1、基本数据类型的传递;2、对象的传递;3、修改引用指向的情况。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

108

2024.02.23

go语言引用传递
go语言引用传递

本专题整合了go语言引用传递机制,想了解更多相关内容,请阅读专题下面的文章。

159

2025.06.26

location.assign
location.assign

在前端开发中,我们经常需要使用JavaScript来控制页面的跳转和数据的传递。location.assign就是JavaScript中常用的一个跳转方法。通过location.assign,我们可以在当前窗口或者iframe中加载一个新的URL地址,并且可以保存旧页面的历史记录。php中文网为大家带来了location.assign的相关知识、以及相关文章等内容,供大家免费下载使用。

226

2023.06.27

clawdbot ai使用教程 保姆级clawdbot部署安装手册
clawdbot ai使用教程 保姆级clawdbot部署安装手册

Clawdbot是一个“有灵魂”的AI助手,可以帮用户清空收件箱、发送电子邮件、管理日历、办理航班值机等等,并且可以接入用户常用的任何聊天APP,所有的操作均可通过WhatsApp、Telegram等平台完成,用户只需通过对话,就能操控设备自动执行各类任务。

19

2026.01.29

clawdbot龙虾机器人官网入口 clawdbot ai官方网站地址
clawdbot龙虾机器人官网入口 clawdbot ai官方网站地址

clawdbot龙虾机器人官网入口:https://clawd.bot/,clawdbot ai是一个“有灵魂”的AI助手,可以帮用户清空收件箱、发送电子邮件、管理日历、办理航班值机等等,并且可以接入用户常用的任何聊天APP,所有的操作均可通过WhatsApp、Telegram等平台完成,用户只需通过对话,就能操控设备自动执行各类任务。

16

2026.01.29

Golang 网络安全与加密实战
Golang 网络安全与加密实战

本专题系统讲解 Golang 在网络安全与加密技术中的应用,包括对称加密与非对称加密(AES、RSA)、哈希与数字签名、JWT身份认证、SSL/TLS 安全通信、常见网络攻击防范(如SQL注入、XSS、CSRF)及其防护措施。通过实战案例,帮助学习者掌握 如何使用 Go 语言保障网络通信的安全性,保护用户数据与隐私。

8

2026.01.29

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

567

2026.01.28

包子漫画在线官方入口大全
包子漫画在线官方入口大全

本合集汇总了包子漫画2026最新官方在线观看入口,涵盖备用域名、正版无广告链接及多端适配地址,助你畅享12700+高清漫画资源。阅读专题下面的文章了解更多详细内容。

209

2026.01.28

ao3中文版官网地址大全
ao3中文版官网地址大全

AO3最新中文版官网入口合集,汇总2026年主站及国内优化镜像链接,支持简体中文界面、无广告阅读与多设备同步。阅读专题下面的文章了解更多详细内容。

350

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.9万人学习

Java 教程
Java 教程

共578课时 | 52.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号