
java源码使用教程:新手完成第一张作品的方法
第一次接触 Java 源码,容易把注意力全放在语法和开发工具上,最后却没有一个看得见的结果。更适合入门的方式,是先完成一个范围明确的小作品:它能运行、有交互,最后还能保存下来。
本文会带你用 Java 自带的 Swing 和 Java2D 做一个简易绘图板。你可以选择画笔颜色,在画布上拖动绘画,清空画布,再把作品导出为 PNG 图片。程序只有一个 Java 源文件,不依赖第三方库,适合用来熟悉源码、事件监听、图形绘制和文件输出。
一、先明确这份示例适合做什么
这个绘图板适合学习 Java 桌面图形程序的基本结构,也适合做简单的手绘草图、涂鸦或课堂演示。它使用 Swing 创建窗口和按钮,用 Java2D 在内存中的图片上画线,再通过 ImageIO 输出 PNG。
它不是专业绘图软件,也不适合作为大型绘画项目的基础版本。当前实现没有图层、撤销、缩放、压力感应、自动保存或矢量编辑功能;画笔宽度固定,画布尺寸固定,作品以 PNG 位图保存。理解这个边界很重要:先完成可运行的小作品,再决定是否要增加功能。
二、运行前需要准备什么
需要安装 Java Development Kit(JDK),而不只是运行 Java 程序所需的运行环境。建议使用仍在维护的 JDK 版本,例如 JDK 17 或更高版本。Swing 和 Java2D 属于 JDK 提供的桌面图形相关 API,不需要另外下载绘图库。
安装完成后,在终端或命令提示符中检查:
java -version
javac -version
如果系统提示找不到命令,通常是 JDK 没有安装成功,或 JDK 的 bin 目录没有配置到系统 PATH。也可以先不调整 PATH,直接使用 JDK 安装目录中的 java 和 javac 可执行文件。
本文的程序需要图形桌面环境。若在没有图形界面的服务器、容器或纯命令行环境中运行,Swing 窗口可能无法显示;这不代表绘图代码本身一定有问题。
三、创建源文件并粘贴代码
新建一个名为 FirstArtwork.java 的文件。文件名要与下面的 public class FirstArtwork 一致,大小写也要一致。将以下代码完整复制进去:
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileNameExtensionFilter;
public class FirstArtwork {
private static final int CANVAS_WIDTH = 900;
private static final int CANVAS_HEIGHT = 600;
private static final float BRUSH_WIDTH = 8.0f;
private final DrawingPanel drawingPanel = new DrawingPanel();
private Color brushColor = Color.BLACK;
private void createAndShowWindow() {
JFrame frame = new JFrame("我的第一张作品");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.LEFT));
JButton colorButton = new JButton("选择颜色");
colorButton.addActionListener(event -> {
Color selected = JColorChooser.showDialog(
frame, "选择画笔颜色", brushColor);
if (selected != null) {
brushColor = selected;
}
});
JButton clearButton = new JButton("清空画布");
clearButton.addActionListener(event -> drawingPanel.clear());
JButton saveButton = new JButton("保存 PNG");
saveButton.addActionListener(event -> saveArtwork(frame));
toolbar.add(colorButton);
toolbar.add(clearButton);
toolbar.add(saveButton);
frame.add(toolbar, BorderLayout.NORTH);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void saveArtwork(JFrame frame) {
JFileChooser chooser = new JFileChooser();
chooser.setDialogTitle("保存作品");
chooser.setFileFilter(
new FileNameExtensionFilter("PNG 图片 (*.png)", "png"));
if (chooser.showSaveDialog(frame) != JFileChooser.APPROVE_OPTION) {
return;
}
File selectedFile = chooser.getSelectedFile();
String fileName = selectedFile.getName();
if (!fileName.toLowerCase().endsWith(".png")) {
selectedFile = new File(
selectedFile.getParentFile(), fileName + ".png");
}
try {
boolean written = ImageIO.write(
drawingPanel.getImage(), "png", selectedFile);
if (written) {
JOptionPane.showMessageDialog(frame, "作品已保存到:"
+ selectedFile.getAbsolutePath());
} else {
JOptionPane.showMessageDialog(frame,
"当前环境没有可用的 PNG 写入器。",
"保存失败", JOptionPane.ERROR_MESSAGE);
}
} catch (IOException ex) {
JOptionPane.showMessageDialog(frame,
"无法保存文件:" + ex.getMessage(),
"保存失败", JOptionPane.ERROR_MESSAGE);
}
}
private class DrawingPanel extends JPanel {
private final BufferedImage image;
private int previousX;
private int previousY;
DrawingPanel() {
setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
image = new BufferedImage(
CANVAS_WIDTH, CANVAS_HEIGHT, BufferedImage.TYPE_INT_RGB);
clear();
MouseAdapter mouseHandler = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent event) {
previousX = event.getX();
previousY = event.getY();
drawLine(previousX, previousY, previousX, previousY);
}
@Override
public void mouseDragged(MouseEvent event) {
int currentX = event.getX();
int currentY = event.getY();
drawLine(previousX, previousY, currentX, currentY);
previousX = currentX;
previousY = currentY;
}
};
addMouseListener(mouseHandler);
addMouseMotionListener(mouseHandler);
}
private BufferedImage getImage() {
return image;
}
private void drawLine(int x1, int y1, int x2, int y2) {
Graphics2D g2 = image.createGraphics();
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(brushColor);
g2.setStroke(new BasicStroke(
BRUSH_WIDTH,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
g2.drawLine(x1, y1, x2, y2);
g2.dispose();
repaint();
}
private void clear() {
Graphics2D g2 = image.createGraphics();
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
g2.dispose();
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() ->
new FirstArtwork().createAndShowWindow());
}
}
四、编译并运行
在终端进入 FirstArtwork.java 所在目录,执行:
javac FirstArtwork.java
java FirstArtwork
第一条命令将源码编译为 FirstArtwork.class;第二条命令启动程序。成功后会出现一个固定尺寸的窗口。按住鼠标左键并拖动即可绘画,单击“选择颜色”可以更换画笔颜色,“清空画布”会把当前内容全部清除,“保存 PNG”会打开文件选择窗口。
使用支持 Java 的集成开发环境也可以运行:创建普通 Java 项目,将代码放进名为 FirstArtwork.java 的源文件,再运行 main 方法即可。若项目使用包声明,例如 package demo;,文件应放在对应的目录中;本文为了方便直接运行,没有添加包声明。
五、代码是怎样工作的
1. 窗口和程序入口
main 方法通过 SwingUtilities.invokeLater 创建界面。Swing 通常要求界面创建和更新在事件派发线程上进行。按钮点击和鼠标事件也由界面事件机制处理,所以示例把窗口搭建、绘画交互都留在这套机制内。
JFrame 是主窗口,工具栏放在上方,绘图区放在中间。pack() 根据组件的首选尺寸调整窗口大小;setResizable(false) 禁止用户调整窗口尺寸。这个限制是为了让固定大小的画布和图片保持一致,并不是 Swing 程序必须不可缩放。
2. 为什么要用 BufferedImage
绘图板把作品保存在 BufferedImage 中。鼠标移动时,程序在这张内存图片上画线;窗口需要重绘时,paintComponent 再把图片显示出来。
不能只在鼠标事件里直接对窗口的临时画面作画。窗口被遮挡、最小化后恢复,或系统要求重新绘制时,临时画面可能消失。将内容保存在图片对象中,重绘时再显示,才能保留已有笔迹。这里使用 TYPE_INT_RGB,意味着图片以 RGB 颜色存储,不包含透明通道;清空操作用白色填充,所以导出的背景也是白色。
3. 鼠标拖动怎样变成连续笔迹
mousePressed 记录按下位置,mouseDragged 每次收到拖动事件时,都将上一个位置与当前位置连成一条线,然后更新上一个位置。多个短线段连起来,就形成了连续的笔迹。
按下鼠标时还会绘制一个起点。因为按下和拖动之间不一定已经产生移动事件,如果不单独处理起点,轻点画布时就可能什么也看不到。示例通过从同一坐标画到同一坐标,并使用圆形笔帽,显示一个近似圆点。
4. 颜色、抗锯齿与画笔参数
JColorChooser.showDialog 会打开颜色选择器。用户取消选择时返回值为 null,因此代码只有在 selected != null 时才更新颜色。忽略这个判断可能导致取消选择后程序把空颜色传给绘图逻辑。
BRUSH_WIDTH 控制画笔粗细,示例设为 8.0f。BasicStroke.CAP_ROUND 和 JOIN_ROUND 分别让线段末端和连接处更圆润;抗锯齿设置用于改善边缘观感。它们是绘制效果选项,并不代表所有屏幕和缩放比例下都会呈现完全一致的像素效果。
5. PNG 保存流程
保存时先由 JFileChooser 让用户选择位置。示例若发现文件名没有以 .png 结尾,就自动补上扩展名,然后调用 ImageIO.write 写入 PNG。该方法可能因为没有可用的对应格式写入器而返回 false;文件操作也可能抛出 IOException,所以代码分别处理了这两种情况。
导出的图片尺寸是画布图片的尺寸,即本例中的 900×600 像素。窗口边框、工具栏和按钮不会保存进作品。PNG 是位图格式,放大查看时会看到像素细节;它并非可无限缩放的矢量图。
六、新手最容易遇到的错误
文件名和类名不一致
如果公共类名是 FirstArtwork,源码文件必须叫 FirstArtwork.java。在大小写敏感的系统中,firstartwork.java 和 FirstArtwork.java 不是同一个文件名。
在错误目录编译
执行 javac FirstArtwork.java 时,当前目录必须包含该文件。先用 cd 进入源码所在目录,或在编译命令中写出正确路径。编译报“找不到文件”时,先检查目录和文件扩展名;有些系统会隐藏已知扩展名,文件实际可能变成 FirstArtwork.java.txt。
JDK 和命令行环境不匹配
如果 javac 无法识别,可能只安装了 JRE,也可能 PATH 指向了旧版 Java。检查 java -version 与 javac -version,确认实际使用的是 JDK。IDE 中的项目 JDK 设置也可能与终端中的 JDK 不同。
窗口能打开,但保存失败
检查所选目录是否存在、当前用户是否有写入权限,以及目标文件是否正被其他程序占用。选择目录而不是文件、磁盘空间不足,或者目录权限受限,都可能导致保存失败。示例会把异常信息显示出来,但不会自动创建任意目录。
画布内容消失或尺寸不对
当前程序通过固定画布尺寸和禁止缩放避免图片与组件尺寸不一致。如果后续删除 setResizable(false) 并允许窗口调整大小,绘图区可能大于或小于底层图片,显示区域就会出现空白或截断。要支持缩放窗口,需要额外设计画布尺寸变化时如何扩展、裁切或缩放图片,不能只去掉这一行就认为功能完整。
七、参数边界与安全注意事项
CANVAS_WIDTH和CANVAS_HEIGHT是图片的像素尺寸。创建图片时尺寸必须是有效的正整数;尺寸变得很大时会占用更多内存,分配失败并非不可能。示例使用 900×600,适合普通入门练习,不代表所有电脑上都适合无限增大。BRUSH_WIDTH是浮点数画笔宽度,示例使用正值。将其做成用户可输入的参数时,应验证输入是否为可接受的有限正数,并避免不合理的大值;本文没有实现参数输入界面。- 保存位置由用户通过文件选择器指定。程序会向所选位置写入文件,因此仍应选择自己有权写入的目录,并确认目标文件是否需要覆盖。不要把来源不明、未经检查的路径字符串直接交给程序批量写入。
- 该程序只绘制用户的鼠标操作并保存图片,不会上传作品,也没有网络通信逻辑。但这不等同于经过安全审计的软件;运行任何源码前,都应理解其实际操作,并从可信来源获取代码。
- 保存成功消息中的路径来自用户选择的文件名。若将程序改成服务端应用,不要把桌面文件选择器的行为误当成服务端文件安全方案;服务端需要单独设计身份验证、路径限制、文件大小限制和访问控制。
八、完成第一张作品后,可以怎样扩展
建议每次只加一个小功能,并在修改后重新编译运行。例如,增加几种预设颜色按钮;提供画笔粗细选项;增加“新建画布”确认框;或者加入撤销功能。撤销不能简单地从 PNG 中还原线条,通常要额外保存绘画操作记录,或保存若干历史画布状态。
还可以把画布宽高做成可配置参数,或在保存时弹出确认对话框。但扩展功能时应重新考虑输入边界、内存使用和异常处理。比如允许用户输入任意画布尺寸,就要避免不合理的大尺寸造成内存压力;允许自由指定文件路径,也要明确程序写入的范围和覆盖行为。
结语
这份小程序虽然简单,却覆盖了完成第一个 Java 桌面作品所需的一条完整路径:创建源文件、编译运行、响应鼠标事件、保存绘图状态、导出图片。先亲手运行,再逐项修改画笔宽度、窗口标题或颜色选择逻辑,比一次性堆叠很多功能更容易定位问题。完成并保存自己的第一张作品后,你就已经开始从“看懂代码”走向“用代码做东西”了。




