设计题目
解析一个文件中的价格数据,并计算平均价格,比如该文件的内容如下:
商品列表:
电视机,2567元/台
洗衣机,3562元/台
冰箱,6573元/台
扩展要求:设计窗口,点击“获取”按钮,将文件内容显示在一个文本区中,点击“计算”按钮,计算平均价格并显示在一个文本框中。显示出总价格和平均价格。
类的思想
创建图形用户界面(GUI)应用程序 —— 价格分析工具,用于从文件中读取包含产品价格信息的数据,然后对这些价格数据进行分析,计算出总价格和平均价格并展示给用户。1. 用户界面设计
- 界面布局:采用了 BorderLayout、GridLayout 等布局管理器来组织界面组件。BorderLayout 用于主面板 mainPanel,将界面划分为北、中、南三个主要区域,分别用于放置结果面板、文本区域(带滚动条)和按钮面板。GridLayout 用于结果面板 resultPanel,以 2 行 2 列的形式整齐排列 “总价格:”“平均价格:” 标签及其对应的文本字段。这种布局方式使得界面结构清晰,各个功能区域划分明确,方便用户理解和操作。
- 组件创建:创建了 JTextArea(文本区域)用于显示从文件读取的内容或其他相关信息;JButton(按钮)如 “获取” 和 “计算” 按钮,用于触发相应的操作;JTextField(文本字段)用于展示计算得到的总价格和平均价格,并且设置为不可编辑状态以防止用户误操作。通过合理选择和组合这些 Swing 组件,构建出一个满足价格分析工具功能需求的基本界面框架。
2. 数据读取与处理
- 文件读取:readFileContents 方法实现了从指定文件读取内容的功能。它使用 BufferedReader 逐行读取文件内容,并将每行存储到一个 ArrayList 中。如果读取过程中出现 IOException,则通过弹出 JOptionPane 对话框显示错误信息给用户。这样设计可以方便地获取文件中的数据,并且在出现问题时及时告知用户,保证程序的健壮性。
- 价格分析计算:alculateTotalPrice 和 calculateAveragePrice 方法分别负责计算总价格和平均价格。它们都遍历从文件读取的每行数据(以逗号分隔),提取出价格部分并进行相应的计算。在提取价格时,需要去除一些可能存在的格式修饰(如 “元 / 台”)并转换为 double 类型。对于无法正确解析价格的行,采取了忽略的处理方式,以避免程序因个别数据格式问题而中断。通过这种方式,可以准确地从文件数据中提取有效价格信息并进行统计分析,满足对价格数据处理的需求。
3. 事件处理与交互逻辑
- 按钮点击事件处理:实现了 ActionListener 接口的 actionPerformed 方法用于处理按钮的点击事件。当用户点击 “获取” 按钮时,首先检查默认文件是否存在,如果存在则直接读取其内容并显示在文本区域;如果不存在,则弹出文件选择对话框让用户选择其他文件,然后读取并显示所选文件的内容。
- 当点击 “计算” 按钮时,先从文本区域获取当前显示的内容(可能是之前从文件读取的),经过处理提取出有效行数据,再分别计算总价格和平均价格,并将结果显示在对应的文本字段中。如果在计算过程中出现异常,同样通过 JOptionPane 对话框显示错误信息。这种事件处理机制使得用户能够通过点击按钮方便地完成获取数据和进行价格分析计算的操作,实现了良好的用户交互逻辑。
4. 程序启动与线程安全
主方法与线程安全:在 main 方法中,使用了 SwingUtilities.invokeLater 来启动应用程序。这是因为 Swing 组件的创建和更新必须在事件调度线程(Event Dispatch Thread,EDT)中进行,以确保线程安全和正确的界面显示效果。通过这种方式,创建了 PriceAnalyzerApp 实例并使其可见,从而启动整个价格分析工具应用程序。
设计原理
- 布局管理器的运用:BorderLayout:主面板(mainPanel)采用 BorderLayout 布局,这是一种将容器划分为五个区域(北、南、东、西、中)的布局方式。在本程序中,主要利用了其中的北、中、南三个区域,分别用于放置结果面板(展示计算结果)、文本区域(显示文件内容等)和按钮面板(放置操作按钮)。这种布局方式使得界面的主要功能区域划分清晰,用户可以很容易地理解不同区域所承担的功能。
- GridLayout:结果面板(resultPanel)采用 GridLayout 布局,设置为 2 行 2 列。通过这种布局,能够整齐地排列 “总价格:”“平均价格:” 等标签及其对应的文本字段(用于显示具体的价格数值)。GridLayout 保证了这些组件在布局上的规整性和对称性,提升了界面的美观度和可读性。组件功能与交互设计:JTextArea:文本区域(textArea)用于多种用途。一方面,它用于显示从文件中读取的原始数据内容,让用户能够直观地查看文件中的信息;另一方面,在进行价格计算时,它所显示的内容也作为计算的数据源,从中提取出有效的价格数据行进行分析。
- JButton:“获取” 按钮(getButton)和 “计算” 按钮(calculateButton)是用户与程序进行交互的关键触发点。用户通过点击这些按钮来启动相应的操作流程。例如,点击 “获取” 按钮会触发从指定文件或用户选择的文件中读取内容并显示在文本区域的操作;点击 “计算” 按钮则会依据文本区域当前显示的内容进行价格分析计算,并将结果展示在相应的文本字段中。JTextField:总价格文本字段(totalPriceField)和平均价格文本字段(averagePriceField)用于展示最终的计算结果。将它们设置为不可编辑状态,是为了确保计算结果的准确性和稳定性,防止用户误操作对结果进行修改,从而保证了结果展示的可靠性。
- 文件读取机制:在readFileContents方法中,采用了BufferedReader结合FileReader的方式来读取文件内容。BufferedReader提供了高效的缓冲读取功能,可以逐行读取文件内容,减少了对文件系统的频繁访问,提高了读取效率。通过循环读取每一行,并将其添加到一个ArrayList中,最终形成一个包含文件所有行内容的列表。这样的设计使得程序能够方便地获取文件中的全部数据,为后续的价格分析处理提供了完整的数据基础。
- 数据解析:在calculateTotalPrice和calculateAveragePrice方法中,对于从文件读取的每行数据,都采用了以逗号为分隔符进行拆分的方式(假设文件中的数据格式是每行以逗号分隔不同字段)。这是因为通常在存储价格数据的文件中,可能会将产品的其他信息(如产品名称、规格等)与价格信息放在同一行,通过逗号分隔可以清晰地提取出价格部分。
- 价格提取与转换:在拆分后的数组中,通过判断数组长度大于 1 来确定该行包含价格信息,然后对价格部分进行处理。首先去除价格部分前后的空格,再去掉可能存在的格式修饰(如 “元 / 台”),最后将其转换为double类型以便进行数学计算。这种处理方式能够准确地从文件数据中提取出有效的价格数据,排除了格式因素对计算的干扰。
- 按钮点击事件处理流程:“获取” 按钮:当用户点击 “获取” 按钮时,首先会检查默认文件(指定路径下的文件)是否存在。如果存在,就直接读取该文件的内容并通过String.join("\n", fileContents)将其转换为字符串形式,然后设置到文本区域(textArea)中供用户查看。如果默认文件不存在,则弹出一个文件选择对话框(通过JFileChooser实现),让用户自行选择一个文件,然后读取所选文件的内容并同样设置到文本区域中。这种处理方式既考虑了用户可能已经有一个固定的默认文件用于存储价格数据的情况,又为用户提供了在默认文件不存在时选择其他文件的灵活性。
- “计算” 按钮:当用户点击 “计算” 按钮时,首先会从文本区域(textArea)当前显示的内容中提取出有效行数据。具体做法是通过textArea.getText().split("\n")将文本区域的内容按行拆分,然后对每行进行判断,去除空行后将有效行添加到一个新的列表中。接下来,分别计算总价格和平均价格。计算总价格时,调用calculateTotalPrice方法;计算平均价格时,先通过遍历有效行数据统计出有效价格数据的数量,再根据总价格和有效价格数据的数量计算出平均价格。最后,将计算得到的总价格和平均价格分别设置到总价格文本字段(totalPriceField)和平均价格文本字段(averagePriceField)中,以展示给用户。在整个过程中,如果出现任何异常情况,如计算过程中出现错误,都会通过弹出JOptionPane对话框来显示错误信息,保证了用户能够及时了解到程序出现的问题。
设计原理
设计源码——PriceAnalyzerApp.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
// 价格分析应用程序类,继承自JFrame并实现ActionListener接口
// 用于创建一个具有图形用户界面的价格分析工具
public class PriceAnalyzerApp extends JFrame implements ActionListener {
// 用于显示文件内容的文本区域
private JTextArea textArea;
// 用于触发获取内容操作的按钮
private JButton getButton;
// 用于触发计算价格操作的按钮
private JButton calculateButton;
// 用于显示总价格的文本字段
private JTextField totalPriceField;
// 用于显示平均价格的文本字段
private JTextField averagePriceField;
// 价格分析应用程序的构造函数
public PriceAnalyzerApp() {
// 设置窗口标题
setTitle("价格分析工具");
// 设置窗口大小
setSize(800, 600);
// 设置窗口关闭时的默认操作,这里是退出应用程序
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 创建主面板,并设置其布局为边界布局
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
// 创建文本区域实例
textArea = new JTextArea();
// 将文本区域添加到滚动面板中,以便在内容过多时可以滚动查看
JScrollPane scrollPane = new JScrollPane(textArea);
// 创建按钮面板
JPanel buttonPanel = new JPanel();
// 创建“获取”按钮,并为其添加动作监听器(当前类自身作为监听器)
getButton = new JButton("获取");
getButton.addActionListener(this);
// 创建“计算”按钮,并为其添加动作监听器(当前类自身作为监听器)
calculateButton = new JButton("计算");
calculateButton.addActionListener(this);
// 将“获取”按钮和“计算”按钮添加到按钮面板中
buttonPanel.add(getButton);
buttonPanel.add(calculateButton);
// 创建结果面板,并设置其布局为网格布局(2行2列)
JPanel resultPanel = new JPanel();
resultPanel.setLayout(new GridLayout(2, 2));
// 在结果面板中添加“总价格:”标签
resultPanel.add(new JLabel("总价格:"));
// 创建用于显示总价格的文本字段,并设置为不可编辑状态
totalPriceField = new JTextField();
totalPriceField.setEditable(false);
// 将总价格文本字段添加到结果面板中
resultPanel.add(totalPriceField);
// 在结果面板中添加“平均价格:”标签
resultPanel.add(new JLabel("平均价格:"));
// 创建用于显示平均价格的文本字段,并设置为不可编辑状态
averagePriceField = new JTextField();
averagePriceField.setEditable(false);
// 将平均价格文本字段添加到结果面板中
resultPanel.add(averagePriceField);
// 将滚动面板添加到主面板的中心区域
mainPanel.add(scrollPane, BorderLayout.CENTER);
// 将按钮面板添加到主面板的南部区域
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
// 将结果面板添加到主面板的北部区域
mainPanel.add(resultPanel, BorderLayout.NORTH);
// 将主面板添加到窗口中
add(mainPanel);
// 设置窗口相对于屏幕居中显示
setLocationRelativeTo(null);
}
// 读取文件内容的方法,接收一个文件对象作为参数
private List<String> readFileContents(File file) {
// 创建一个用于存储文件每行内容的列表
List<String> lines = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
// 逐行读取文件内容
String line;
while ((line = br.readLine())!= null) {
// 将读取到的每行内容添加到列表中
lines.add(line);
}
} catch (IOException e) {
// 如果读取文件过程中出现错误,弹出错误消息对话框显示错误信息
JOptionPane.showMessageDialog(this, "读取文件出错: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
}
// 返回存储文件内容的列表
return lines;
}
// 计算总价格的方法,接收一个包含文件内容行的列表作为参数
private double calculateTotalPrice(List<String> lines) {
// 初始化总价格为0
double totalPrice = 0;
// 遍历文件内容的每一行
for (String line : lines) {
// 以逗号为分隔符将每行内容拆分成数组
String[] parts = line.split(",");
// 如果拆分后的数组长度大于1,说明包含价格信息
if (parts.length > 1) {
try {
// 提取价格部分,去除前后空格并去掉“元/台”字样,然后转换为double类型
double price = Double.parseDouble(parts[1].trim().replace("元/台", ""));
// 将该价格累加到总价格中
totalPrice += price;
} catch (NumberFormatException e) {
// 如果无法解析价格,忽略这一行(可能格式不正确)
// 这里只是简单地跳过,不做进一步处理
}
}
}
// 返回计算得到的总价格
return totalPrice;
}
// 计算平均价格的方法,接收一个包含文件内容行的列表作为参数
private double calculateAveragePrice(List<String> lines) {
// 初始化价格总和为0
double sum = 0;
// 初始化有效价格数据的数量为0
int validCount = 0;
// 遍历文件内容的每一行
for (String line : lines) {
// 以逗号为分隔符将每行内容拆分成数组
String[] parts = line.split(",");
// 如果拆分后的数组长度大于1,说明包含价格信息
if (parts.length > 1) {
try {
// 提取价格部分,去除前后空格并去掉“元/台”字样,然后转换为double类型
double price = Double.parseDouble(parts[1].trim().replace("元/台", ""));
// 将该价格累加到价格总和中
sum += price;
// 有效价格数据的数量加1
validCount++;
} catch (NumberFormatException e) {
// 如果无法解析价格,忽略这一行(可能格式不正确)
// 这里只是简单地跳过,不做进一步处理
}
}
}
// 如果存在有效价格数据,计算并返回平均价格;否则返回0
return validCount > 0? sum / validCount : 0;
}
// 实现ActionListener接口的方法,用于处理按钮的点击事件
@Override
public void actionPerformed(ActionEvent e) {
// 如果点击的是“获取”按钮
if (e.getSource() == getButton) {
// 创建一个默认文件对象,指向指定路径下的文件
File defaultFile = new File("E:\\java idea\\java_homework\\src\\products.txt");
// 如果默认文件存在
if (defaultFile.exists()) {
// 读取默认文件的内容
List<String> fileContents = readFileContents(defaultFile);
// 将文件内容设置到文本区域中,每行之间用换行符连接
textArea.setText(String.join("\n", fileContents));
} else {
// 如果默认文件不存在,弹出提示消息对话框告知用户,并提示选择其他文件
JOptionPane.showMessageDialog(this, "默认文件products.txt.txt不存在,请选择其他文件。", "提示", JOptionPane.WARNING_MESSAGE);
// 创建文件选择器实例
JFileChooser fileChooser = new JFileChooser();
// 显示文件选择对话框,并获取用户选择的结果
int result = fileChooser.showOpenDialog(this);
// 如果用户选择了文件(点击了确定按钮)
if (result == JFileChooser.APPROVE_OPTION) {
// 获取用户选择的文件对象
File selectedFile = fileChooser.getSelectedFile();
// 读取用户选择文件的内容
List<String> fileContents = readFileContents(selectedFile);
// 将文件内容设置到文本区域中,每行之间用换行符连接
textArea.setText(String.join("\n", fileContents));
}
}
}
// 这里下面的代码重复了上面点击“获取”按钮的部分逻辑,你选择下面部分代码便可以直接选择其他文件
// if (e.getSource() == getButton) {
// JFileChooser fileChooser = new JFileChooser();
// int result = fileChooser.showOpenDialog(this);
// if (result == JFileChooser.APPROVE_OPTION) {
// File selectedFile = fileChooser.getSelectedFile();
// List<String> fileContents = readFileContents(selectedFile);
// textArea.setText(String.join("\n", fileContents));
// }
// }
// 如果点击的是“计算”按钮
else if (e.getSource() == calculateButton) {
// 创建一个空列表,用于存储从文本区域获取的有效行内容
List<String> lines = new ArrayList<>();
// 遍历文本区域中的每一行内容
for (String line : textArea.getText().split("\n")) {
// 如果该行内容去除前后空格后不为空,说明是有效行,添加到列表中
if (!line.trim().isEmpty()) {
lines.add(line);
}
}
// 初始化总价格、平均价格和有效价格数据的数量
double totalPrice = 0;
double averagePrice = 0;
int validCount = 0;
try {
// 计算总价格
totalPrice = calculateTotalPrice(lines);
// 遍历从文本区域获取的有效行内容
for (String line : lines) {
// 以逗号为分隔符将每行内容拆分成数组
String[] parts = line.split(",");
// 如果拆分后的数组长度大于1,说明包含价格信息
if (parts.length > 1) {
try {
// 提取价格部分,去除前后空格并去掉“元/台”字样,然后转换为double类型
double price = Double.parseDouble(parts[1].trim().replace("元/台", ""));
// 有效价格数据的数量加1
validCount++;
} catch (NumberFormatException ignored) {
}
}
}
// 如果存在有效价格数据,计算并设置平均价格;否则设置为0
averagePrice = validCount > 0? totalPrice / validCount : 0;
// 将总价格设置到总价格文本字段中
totalPriceField.setText("总价格: " + totalPrice);
// 将平均价格设置到平均价格文本字段中
averagePriceField.setText("总价格: " + averagePrice);
} catch (Exception ex) {
// 如果在计算价格过程中出现错误,弹出错误消息对话框显示错误信息
JOptionPane.showMessageDialog(this, "计算价格时出错: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
}
}
}
// 应用程序的主方法,程序入口点
public static void main(String[] args) {
// 使用SwingUtilities的invokeLater方法确保Swing组件在事件调度线程中创建和更新
SwingUtilities.invokeLater(() -> {
// 创建价格分析应用程序实例
PriceAnalyzerApp app = new PriceAnalyzerApp();
// 设置应用程序窗口可见
app.setVisible(true);
});
}
}
public void actionPerformed(ActionEvent e) {
// 如果点击的是“获取”按钮
if (e.getSource() == getButton) {
// 创建一个默认文件对象,指向指定路径下的文件
File defaultFile = new File("E:\\java idea\\java_homework\\src\\products.txt");
// 如果默认文件存在
if (defaultFile.exists()) {
// 读取默认文件的内容
List<String> fileContents = readFileContents(defaultFile);
// 将文件内容设置到文本区域中,每行之间用换行符连接
textArea.setText(String.join("\n", fileContents));
} else {
// 如果默认文件不存在,弹出提示消息对话框告知用户,并提示选择其他文件
JOptionPane.showMessageDialog(this, "默认文件products.txt.txt不存在,请选择其他文件。", "提示", JOptionPane.WARNING_MESSAGE);
// 创建文件选择器实例
JFileChooser fileChooser = new JFileChooser();
// 显示文件选择对话框,并获取用户选择的结果
int result = fileChooser.showOpenDialog(this);
// 如果用户选择了文件(点击了确定按钮)
if (result == JFileChooser.APPROVE_OPTION) {
// 获取用户选择的文件对象
File selectedFile = fileChooser.getSelectedFile();
// 读取用户选择文件的内容
List<String> fileContents = readFileContents(selectedFile);
// 将文件内容设置到文本区域中,每行之间用换行符连接
textArea.setText(String.join("\n", fileContents));
}
}
}
// 这里下面的代码重复了上面点击“获取”按钮的部分逻辑,你选择下面部分代码便可以直接选择其他文件
// if (e.getSource() == getButton) {
// JFileChooser fileChooser = new JFileChooser();
// int result = fileChooser.showOpenDialog(this);
// if (result == JFileChooser.APPROVE_OPTION) {
// File selectedFile = fileChooser.getSelectedFile();
// List<String> fileContents = readFileContents(selectedFile);
// textArea.setText(String.join("\n", fileContents));
// }
// }
这一部分你们可以自行设计自己强行打开的文件,设置相关的路径即可
products.txt.
商品列表:
电视机,2567元/台
洗衣机,3562元/台
冰箱,6573元/台
注意:文件的路径需要自行修改,每个人的配置路径不一致,我这选用的是直接路径不是相对路径