Java实现多功能科学计算器:从界面设计到表达式解析

1. 从零开始:为什么用Java Swing做计算器是个好主意

很多Java初学者在学完基础语法后,都会面临一个灵魂拷问:我能用Java做出什么像样的东西?课本上的例子太简单,企业级项目又太复杂,这时候,一个功能完整的科学计算器就成了绝佳的练手项目。我自己当年就是这么过来的,从只会写控制台程序,到做出第一个带界面的计算器,那种成就感至今难忘。

你可能觉得计算器很简单,不就是几个按钮加加减减吗?但真要自己动手实现一个多功能科学计算器,你会发现里面涉及的知识点非常全面。首先,你需要用Java Swing来构建图形界面,这能让你掌握事件驱动编程的核心思想。其次,计算器最核心的“大脑”——表达式计算,会逼着你去学习数据结构与算法,特别是栈(Stack)的应用。最后,为了让计算器足够“聪明”,能处理各种用户输入错误,你还得设计一套健壮的错误处理机制

这个项目就像一块试金石,能检验你对Java基础、面向对象、GUI编程和算法的综合掌握程度。而且,它的成果是可视化的,你可以直接拿给朋友用,或者放到简历里,比干巴巴地写“熟悉Java语法”要有说服力得多。接下来,我就带你一步步拆解这个项目,从界面布局到核心算法,把每个环节的“坑”和技巧都讲清楚。

2. 界面设计:用Swing打造一个“看得见”的计算器

做计算器,第一步当然是让它“长得像”一个计算器。我们选择Java Swing这个经典的GUI工具包,它足够轻量,也完全能满足我们的需求。原始文章里用setBounds手动定位每个按钮,这确实是最直观的方式,尤其对于计算器这种按钮排列整齐、但布局又有点特殊的界面。用布局管理器当然也可以,但GridLayoutGridBagLayout在应对不同大小的按钮(比如那个超长的“=”键)时,反而会更麻烦。

我在这里踩过的坑是:不能直接把组件加到JFrame上。如果你把JFrame的布局设为null,然后直接往里面加按钮和文本框,很可能会发现界面一片空白或者显示异常。正确的做法是创建一个JPanel面板,把面板的布局设为null,然后把所有按钮、文本框都加到面板里,最后把这个面板加到JFrame的内容窗格中。这是因为JFrame本身有复杂的内部结构,直接操作容易出问题。

关于窗口尺寸,这里有个小细节:你设计的面板大小是600x400,但实际设置JFrame的大小时,需要额外加上边框和标题栏的像素。通常宽度加15,高度加35左右比较保险,也就是setSize(615, 435)。原始代码里是614x437,可能是根据特定系统主题微调的结果,大家在实际运行时可以根据自己电脑的效果再调整。

为了让界面更专业,我建议在原始代码基础上做两点优化。一是字体选择,计算器的显示屏通常使用等宽字体,比如Monospaced,这样数字对齐更美观。按钮上的文字可以用无衬线字体,如SansSerif,更清晰。二是颜色搭配,可以给数字键、运算符键、功能键(如sin、cos)设置不同的背景色,让用户一眼就能区分。比如,数字键用浅灰色,运算符用橙色,功能键用蓝色。这只需要在创建按钮后,调用button.setBackground(Color.ORANGE)即可实现。

下面是一个改进后的界面初始化代码片段,增加了颜色区分:

// 在创建按钮的循环中,根据按钮类型设置不同颜色
for (int i = 0; i < buttons.length; i++) {
    buttons[i] = new JButton(strings[i]);
    buttons[i].setFont(f);
    buttons[i].setFocusable(false);
    
    // 根据按钮文本内容设置颜色
    String text = strings[i];
    if (text.matches("[0-9.]")) {
        buttons[i].setBackground(new Color(240, 240, 240)); // 数字键浅灰色
    } else if (text.matches("[+\\-*/=]")) {
        buttons[i].setBackground(new Color(255, 159, 0)); // 运算符橙色
        buttons[i].setForeground(Color.WHITE);
    } else if (text.matches("[()]")) {
        buttons[i].setBackground(new Color(200, 200, 200)); // 括号中灰色
    } else {
        buttons[i].setBackground(new Color(70, 130, 180)); // 科学函数键蓝色
        buttons[i].setForeground(Color.WHITE);
    }
}

3. 核心算法:中缀表达式如何“变身”为后缀表达式

界面做好看了,接下来就是最核心、也最考验逻辑的部分:怎么让计算机理解我们输入的算式?我们人类习惯的写法,比如 3 + 5 * (2 - 1),在计算机科学里被称为中缀表达式。它的特点是运算符在操作数中间。但这种形式对计算机来说并不友好,因为运算符有优先级(乘除高于加减),还有括号会改变计算顺序。

为了让计算机能按正确的顺序计算,我们需要把中缀表达式转换成后缀表达式(也叫逆波兰表达式)。后缀表达式的特点是运算符在操作数后面,并且没有括号,计算顺序完全由表达式的顺序决定。比如,上面的中缀表达式转换成后缀表达式就是 3 5 2 1 - * +

这个转换过程,栈(Stack) 数据结构就派上大用场了。栈的特点是“后进先出”,正好适合处理这种有优先级和括号的表达式。转换算法我总结为以下几个步骤,你可以对照着原始文章的houZhui方法来看:

  1. 初始化两个栈:一个运算符栈(fuZhan),一个输出列表(list2,其实用列表或队列都可以)。
  2. 从左到右扫描中缀表达式的每个元素(数字或运算符)。
  3. 遇到数字:直接加入输出列表。
  4. 遇到运算符
    • 如果栈为空,或栈顶是左括号(,或当前运算符优先级高于栈顶运算符,则直接入栈。
    • 如果当前运算符优先级低于或等于栈顶运算符,则不断将栈顶运算符弹出并加入输出列表,直到栈为空、栈顶为左括号、或栈顶运算符优先级低于当前运算符,然后再将当前运算符入栈。
  5. 遇到左括号(:直接入栈。
  6. 遇到右括号):不断将栈顶运算符弹出并加入输出列表,直到遇到左括号(,然后将左括号弹出(丢弃,不加入输出列表)。
  7. 扫描结束后:将运算符栈中剩余的所有运算符依次弹出并加入输出列表。

原始代码里用adv方法定义了运算符优先级,加减是1,乘除是2,乘方、函数等是4。这里有个可以优化的点:优先级定义可以更精细。比如,乘方^是右结合的(2^3^2应该计算为2^(3^2)),而加减乘除是左结合的。原始代码将所有高级运算符(^!gloct)都设为4,没有区分结合性。在实际更复杂的计算器中,我们需要为^单独设置优先级,并在遇到它时,只有优先级高于栈顶才入栈(对于右结合运算符),而左结合运算符是优先级高于或等于栈顶才入栈。不过对于初学者项目,原始代码的简化处理已经足够。

4. 后缀表达式计算:栈的第二次华丽登场

得到后缀表达式后,计算就变得异常简单了,栈再次成为主角。计算规则如下:

  1. 初始化一个操作数栈。
  2. 从左到右扫描后缀表达式。
  3. 遇到数字,就压入栈。
  4. 遇到运算符,就从栈中弹出两个操作数(注意顺序,对于减法和除法,先弹出的是右操作数),进行运算,将结果压回栈中。
  5. 扫描结束后,栈中剩下的那个数字就是最终结果。

原始文章的math方法就是这个逻辑。它处理了加减乘除、乘方、阶乘、开方、对数、三角函数等运算。这里我想重点说一下错误处理的细节。一个好的计算器不能一遇到错误就崩溃,而是要给出明确的提示。原始代码在math方法里做了几处关键检查:

  • 除法时判断除数是否为零。
  • 阶乘时判断是否为自然数(非负整数)。
  • 自然对数ln和以2为底的log判断参数是否大于零。
  • 正切tan判断参数是否会导致cos(x)为零(即π/2 + kπ)。

这些都是非常必要的。我建议在此基础上,还可以增加一些健壮性检查,比如在弹出栈元素进行计算前,先判断栈里是否有足够的操作数。对于单目运算符(如阶乘!、开方g、三角函数等),只需要弹出一个操作数。原始代码在遇到!gl等时,只弹出了一个数,这是正确的。但我们可以增加一个判断,如果栈为空,则说明表达式不合法,应报错。

5. 输入验证:给计算器穿上“防弹衣”

用户输入是不可预测的,他们可能会输入2++3sin(2))5/0或者12.34.56这样的非法表达式。如果不对输入进行校验,程序很可能在解析或计算时抛出异常,甚至崩溃。所以,在将字符串交给中缀转换和计算之前,我们必须先进行一轮严格的语法检查

原始文章的estimate方法做了大量工作,列出了13条规则来检查输入合法性。这些规则非常细致,覆盖了开头结尾、运算符前后、数字格式、括号匹配等多个方面。我们来分析一下其中的核心思想:

  • 状态机思想:本质上,它是在遍历字符串时,检查每一个字符及其前后字符的组合是否合法。这就像是一个简单的状态机,根据当前字符的类型(数字、运算符、括号、函数等)和上一个字符的类型,来判断下一个字符是否允许出现。
  • 边界检查:特别关注字符串的第一个和最后一个字符,以及一些特殊字符(如小数点.、数字0)的上下文环境。
  • 括号匹配:在zhanDui方法里,通过统计左右括号的数量和顺序来检查括号是否成对且嵌套正确。

在实际开发中,这种基于规则的检查虽然有效,但代码会显得比较冗长和难以维护。一个更优雅、更强大的方法是使用正则表达式来验证整个表达式的语法结构。我们可以先定义一个描述合法数学表达式的正则表达式模式,然后在用户点击等号时,用这个模式去匹配整个输入字符串。不过,正则表达式对于处理复杂的嵌套括号会有点力不从心。另一种更高级的方法是使用递归下降解析语法分析器生成工具,但这对于计算器项目来说就有点杀鸡用牛刀了。

对于初学者,我建议理解并借鉴原始文章的检查思路,但可以尝试重构代码,使其更清晰。例如,可以将不同的检查规则封装成独立的方法,如checkOperatorSequencecheckNumberFormatcheckParentheses等,这样主方法estimate的逻辑会更清晰。

6. 事件处理:让按钮“活”起来

界面画好了,算法也准备好了,现在需要把两者连接起来,这就是事件监听器(ActionListener) 的工作。原始文章为33个按钮分别添加了监听器,每个监听器里更新显示文本框(jTextField1)和内部表达式字符串(str)。这是最直接的方式。

但这里有一个设计模式上的优化点:使用命令模式。我们可以为每一种计算器操作(数字输入、运算符输入、函数输入、等号、清空等)定义一个命令类。每个按钮绑定一个具体的命令对象。这样做的好处是,将用户界面的动作(点击按钮)与具体的业务逻辑(更新字符串、执行计算)解耦。如果以后要增加新的按钮功能,或者改变按钮的布局,只需要创建新的命令类或修改配置,而不需要去动那一大串actionPerformed方法。

不过,对于第一个版本的计算器,像原始文章那样为每个按钮写匿名内部类也是完全可以接受的,逻辑清晰直观。需要注意的是,在事件处理中,除了更新显示,更重要的是维护好内部状态。原始文章用str变量来记录用户输入的“原始”表达式,用jTextField1来显示“美化后”的表达式(比如str里存的是g,但显示的是)。这是一个很好的做法,将内部逻辑表示和外部显示分离。

另一个细节是等号(=)按钮的处理。它触发了整个计算流程:先调用estimate()进行输入验证,再调用zhanDui()进行括号匹配和计算。如果计算成功,结果会同时显示在主显示屏(jTextField1)和副显示屏(jTextField2)上。这里可以增加一个功能:历史记录。每次成功计算后,可以把表达式和结果保存到一个列表里,然后提供一个“历史”按钮来查看,这会让计算器更实用。

7. 功能扩展:让你的计算器更“科学”

原始文章的计算器已经实现了三角函数、对数、阶乘、开方、乘方等科学计算功能,这已经很棒了。但如果你想挑战自己,或者让项目更出彩,这里有几个扩展方向

  • 角度与弧度切换:这是科学计算器的标配。可以增加一个“Deg/Rad”切换按钮,用一个布尔变量记录当前模式。在计算sincostan时,如果处于角度制(Deg),需要先将输入值乘以π/180转换为弧度,再调用Math.sin等函数。
  • 常数与存储器:增加eπ按钮是第一步。还可以增加内存功能,如MC(内存清除)、MR(内存读取)、M+(加到内存)、M-(从内存减)。这需要额外定义一个double memory变量来存储值。
  • 表达式历史与回退:实现一个栈或列表来存储之前计算过的表达式和结果。增加“上箭头”按钮可以调出上一次的输入或结果,这在连续计算时非常方便。
  • 错误提示的友好性:原始代码在出错时只是简单显示“输入错误!”。可以做得更细致,比如提示“括号不匹配”、“除数不能为零”、“ln的参数必须大于0”,甚至用对话框高亮显示出错的大概位置。
  • 键盘支持:让用户除了用鼠标点击,还可以用键盘输入数字和常用运算符(如+, -, *, /, Enter作为等号),这会极大提升使用体验。这需要为JFrame添加KeyListener

实现角度切换的代码示例可能如下:

// 在类中增加一个成员变量
private boolean isDegreeMode = true; // 默认角度制

// 在初始化按钮时,增加一个切换按钮
JButton degRadButton = new JButton("Deg");
degRadButton.addActionListener(e -> {
    if (isDegreeMode) {
        degRadButton.setText("Rad");
        isDegreeMode = false;
    } else {
        degRadButton.setText("Deg");
        isDegreeMode = true;
    }
});

// 在计算三角函数的方法中,根据模式进行转换
private double computeSin(double value) {
    if (isDegreeMode) {
        value = Math.toRadians(value); // Java内置的转换方法,等同于 value * Math.PI / 180
    }
    return Math.sin(value);
}
// 同理修改cos, tan的计算方法

8. 项目总结与避坑指南

走完整个开发流程,我们再回头看看这个项目带给我们的收获。它绝不仅仅是一个计算器,而是一个综合性的Java练手项目。你实践了Swing GUI开发、事件处理、数据结构(栈)的应用、字符串处理、算法(中缀转后缀)、以及基本的软件工程思想(如错误处理、模块分离)。

结合我自己的经验,这里有几个容易踩的“坑”和调试建议

  1. 布局错乱:如果界面显示不正常,首先检查是否将组件添加到了正确的容器(JPanel)中,以及JFrame的尺寸是否包含了边框。可以用setBackground(Color.RED)临时给面板上色,看看它到底显示在哪儿、有多大。
  2. 表达式解析错误:这是最头疼的。建议在开发zhongZhui(中缀分割)和houZhui(后缀转换)方法时,多使用System.out.println打印中间结果。比如,每扫描一个字符,就打印当前字符和解析出的列表;在转换后缀时,打印运算符栈的状态和输出列表。对比手动计算的步骤,很容易找到逻辑错误。
  3. 计算精度问题:Java的double类型有精度限制,对于连续的浮点数运算,可能会产生极微小的误差。比如0.1 + 0.2的结果可能不是精确的0.3。对于显示,可以使用DecimalFormat格式化输出,限制小数位数。对于需要高精度计算的场景(如金融),应该考虑使用BigDecimal类。
  4. 事件响应问题:如果点击按钮没反应,首先检查addActionListener是否被正确调用,以及监听器里的代码逻辑(特别是对str和文本框的更新)是否正确。确保没有在事件监听中阻塞了事件分发线程(EDT)。
  5. 代码复用与清晰度:原始文章的代码将所有功能写在一个大类里,对于学习是清晰的。但当功能扩展后,代码会变得很长。可以考虑将界面构建表达式解析与计算事件处理分离到不同的类中,比如CalculatorViewExpressionEvaluatorCalculatorController,这样符合MVC模式,更利于维护。

最后,把完整的代码跑起来,看到自己亲手打造的计算器能正确计算 sin(30) + log(8) / 2 这样的复杂表达式时,那种感觉是非常棒的。这个项目可以作为你Java学习路上的一个里程碑,它所涉及的思想和技能,在你未来学习更复杂的框架和系统时,依然会发挥重要作用。

下载代码方式:https://pan.quark.cn/s/957405011bdf 在计算机视觉技术中,轮廓提取与中心识别被视为两项核心的技术,它们对于图像处理及模式识别领域扮演着不可或缺的角色。本文将深入剖析这两种算法,并围绕"轮廓提取(中心识别)算法"这一核心主题,同时结合所提供的压缩包文件"Contour",对相关知识点进行详尽的阐述。 轮廓提取算法,主要功能在于识别并分离图像中的不同对象。该算法通过探测物体边缘,构建出明确的边界线,从而实现图像内部各组成部分的区分。在色位图环境下,不同的色彩可能象征着不同的对象或区域,因此,一个性能优越的轮廓提取算法应当具备处理此类复杂场景的能力。常见的轮廓提取算法包括Canny边缘检测、Sobel算子、Laplacian算子和Hough变换等。这些算法各自具备独特的优缺点,究竟选择何种方法,需要依据具体的应用情境以及性能要求来决定。 1. Canny边缘检测:由John F. Canny所研发,这是一种自适应的级边缘检测方法。Canny算法借助高斯滤波器来降低噪声干扰,随后运用强度梯度和非极大值抑制技术来定位最显著的边缘,最终通过双阈值检测来区分边缘与噪声。 2. Sobel算子:Sobel算子是一种基于梯度的边缘检测工具,它通过计算图像在水平和垂直方向上的梯度来探测边缘。此方法操作简便且效率较高,但可能对图像中的噪声较为敏感。 3. Laplacian算子:Laplacian算子是一种二阶导数算子,能够迅速识别图像中的尖峰(即边缘)。然而,它容易受到噪声的影响,因此通常需要与其他技术结合使用,例如Gaussian滤波。 4. Hough变换:Hough变换是一种在参数空间中进行边缘检测的方法,它能够检测出任意...
【重要提示】本资源设置为0积分下载,若非0积分请勿轻易下载 亲爱的CSDN用户: 首先感谢你点进这个资源页面。我需要提前说明一个重要情况: **本资源原本已设置为“0积分下载”**,即作者希望完全免费共享。但CSDN平台有时会根据文件的下载热度、文件大小、用户权限等因素,**自动将部分资源的积分调整为非0数值**(如1积分、2积分、5积分等)。这是平台系统的自动行为,而非作者本人的设定。 **因此,如果你当前看到该资源的下载所需积分不是0(例如显示为1、2、3……),请谨慎决定是否下载。** 如果你按照非0积分支付并下载后发现资源内容不符合预期、链接失效,或者实际上该资源本应是免费的,作者无法为此承担积分损失或退还操作。**强烈建议:仅在页面显示为0积分时进行下载。** 另外,本资源描述中**并未直接提供具体的下载地址或外部链接**,因为它本身是一个通过CSDN官方上传通道提交的文件/内容包。如果你看到描述中没有外部网盘地址,这是正常的——资源文件应通过CSDN内置的“下载”按钮获取。若因平台积分显示异常导致你支付了积分,请优先联系CSDN客服咨询积分退还政策,作者没有权限修改平台自动设定的积分值。 感谢你的理解与支持。技术分享本应开放,但受限于平台规则,特此提醒如上。祝习进步!
源码链接: https://pan.quark.cn/s/432c36f9786d ### 解决Android Studio 导入项目停滞在“building gradle project info”阶段的问题 #### 问题背景 在运用Android Studio进行开发活动时,开发者常常会遭遇在导入项目的过程中出现停滞现象,特别是在“building gradle project info”这一阶段。该问题的存在显著降低了开发效率,给开发者带来了诸困扰。本文将深入阐述该问题的成因、应对策略及其内在机制。 #### 问题概述 当我们在Android Studio中尝试启动一个项目时,集成开发环境(IDE)会自动执行一系列初始化操作,其中包括构建Gradle项目信息(即“building gradle project info”)。这一过程主要是为了加载项目的依赖关系并解析项目结构,以便让开发者能够顺利开展后续的编码任务。然而,在某些特定情形下,这个过程可能会因为种因素而陷入停滞状态,导致开发者无法继续进行工作。 #### 原因分析 1. **Gradle版本不匹配**:如果项目的`build.gradle`文件中指定的Gradle版本与Android Studio所支持的版本不一致,可能会引发兼容性问题。 2. **网络连接障碍**:构建过程中需要下载远程依赖或Gradle分发包,如果网络环境不稳定或传输速度过慢,则可能导致构建过程长时间处于等待状态。 3. **配置文件配置不当**:如`gradle-wrapper.properties`文件中的`distributionUrl`配置存在错误,或者文件缺失等。 4. **系统资源瓶颈**:例如内存不足...
【重要提示】本资源设置为0积分下载,若非0积分请勿轻易下载 亲爱的CSDN用户: 首先感谢你点进这个资源页面。我需要提前说明一个重要情况: **本资源原本已设置为“0积分下载”**,即作者希望完全免费共享。但CSDN平台有时会根据文件的下载热度、文件大小、用户权限等因素,**自动将部分资源的积分调整为非0数值**(如1积分、2积分、5积分等)。这是平台系统的自动行为,而非作者本人的设定。 **因此,如果你当前看到该资源的下载所需积分不是0(例如显示为1、2、3……),请谨慎决定是否下载。** 如果你按照非0积分支付并下载后发现资源内容不符合预期、链接失效,或者实际上该资源本应是免费的,作者无法为此承担积分损失或退还操作。**强烈建议:仅在页面显示为0积分时进行下载。** 另外,本资源描述中**并未直接提供具体的下载地址或外部链接**,因为它本身是一个通过CSDN官方上传通道提交的文件/内容包。如果你看到描述中没有外部网盘地址,这是正常的——资源文件应通过CSDN内置的“下载”按钮获取。若因平台积分显示异常导致你支付了积分,请优先联系CSDN客服咨询积分退还政策,作者没有权限修改平台自动设定的积分值。 感谢你的理解与支持。技术分享本应开放,但受限于平台规则,特此提醒如上。祝习进步!
【重要提示】本资源设置为0积分下载,若非0积分请勿轻易下载 亲爱的CSDN用户: 首先感谢你点进这个资源页面。我需要提前说明一个重要情况: **本资源原本已设置为“0积分下载”**,即作者希望完全免费共享。但CSDN平台有时会根据文件的下载热度、文件大小、用户权限等因素,**自动将部分资源的积分调整为非0数值**(如1积分、2积分、5积分等)。这是平台系统的自动行为,而非作者本人的设定。 **因此,如果你当前看到该资源的下载所需积分不是0(例如显示为1、2、3……),请谨慎决定是否下载。** 如果你按照非0积分支付并下载后发现资源内容不符合预期、链接失效,或者实际上该资源本应是免费的,作者无法为此承担积分损失或退还操作。**强烈建议:仅在页面显示为0积分时进行下载。** 另外,本资源描述中**并未直接提供具体的下载地址或外部链接**,因为它本身是一个通过CSDN官方上传通道提交的文件/内容包。如果你看到描述中没有外部网盘地址,这是正常的——资源文件应通过CSDN内置的“下载”按钮获取。若因平台积分显示异常导致你支付了积分,请优先联系CSDN客服咨询积分退还政策,作者没有权限修改平台自动设定的积分值。 感谢你的理解与支持。技术分享本应开放,但受限于平台规则,特此提醒如上。祝习进步!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值