三个Java入门项目

发布时间 2023-10-02 23:49:23作者: Enid_Lin

Java实现简单计算器

参考链接?:https://www.bilibili.com/video/BV1d54y1s7uC?p=1&vd_source=cf21268954e139179e71f046bac01e56

  • 设计思路
  • 创建容器框架
  • 创建组件和组件布局方式
  • 组件的测试
  • 数字按钮和功能按钮的实现
  • 设置事件监视器
  • 运算功能和清空功能的实现
  • 代码

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    public class Calculator extends JFrame implements ActionListener {
        private JPanel jp_north = new JPanel(); //北面面板
        private JTextField input_text = new JTextField(); //输入框
        private JButton c_button = new JButton("C"); //清除按键
    
        private JPanel jp_south = new JPanel(); //南面面板
    
    
        public Calculator() throws HeadlessException{
            this.init();
            this.addNorthComponent();
            this.addSouthButton();
        }
    
        //初始化窗口
        public void init() {
            this.setTitle("计算器");
            this.setSize(300, 300);
            this.setLayout(new BorderLayout());
            this.setResizable(false); //设置窗口不可拉伸
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            this.setLocationRelativeTo(null); //使窗口打开时默认在屏幕中央
        }
    
        //添加北面的控件
        public void addNorthComponent() {
            this.input_text.setPreferredSize(new Dimension(230, 30));
            jp_north.add(input_text);
            this.c_button.setForeground(Color.RED);
            this.c_button.setBackground(Color.ORANGE);
            jp_north.add(c_button);
    
            c_button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    input_text.setText("");
                    firstInput = null;
                }
            });
    
            this.add(jp_north, BorderLayout.NORTH);
        }
    
        //添加南面的按钮
        public void addSouthButton() {
            String button_text = "123+456-789x.0=÷";
            this.jp_south.setLayout(new GridLayout(4, 4));
            for (int i = 0; i < 16; i++) {
                String temp = button_text.substring(i, i + 1);
                JButton button = new JButton(temp);
                if (temp.equals("=")) {
                    button.setBackground(Color.CYAN);
                } else {
                    button.setBackground(Color.white);
                }
                if (temp.matches("[\\+\\-x÷=]")) {
                    button.setFont(new Font("粗体", Font.BOLD, 16));
                }
                button.addActionListener(this::actionPerformed);
                jp_south.add(button);
            }
            this.add(jp_south, BorderLayout.CENTER);
        }
    
        public static void main(String[] args) {
            Calculator calculator = new Calculator();
            calculator.setVisible(true);
        }
    
        private String firstInput = null;
        private String secondInput = null;
        private String operator = null;
        private boolean calculated = false;
        @Override
        public void actionPerformed(ActionEvent e) {
            String clickStr = e.getActionCommand();
            if (calculated) {
                this.input_text.setText("");
                calculated = false;
            }
            if (".0123456789".indexOf(clickStr) != -1) {
                if (firstInput != null) {
                    if (secondInput == null) {
                        secondInput = clickStr;
                    } else {
                        secondInput += clickStr;
                    }
                }
                this.input_text.setText(input_text.getText() + clickStr);
                this.input_text.setHorizontalAlignment(JTextField.RIGHT);
                //JOptionPane.showMessageDialog(this, clickStr);
            } else if (clickStr.matches("[\\+\\-x÷]{1}")) {
                operator = clickStr;
                secondInput = null;
                firstInput = this.input_text.getText();
                this.input_text.setText(input_text.getText() + clickStr);
            } else if (clickStr.equals("=")) {
                Double a = Double.valueOf(firstInput);
                Double b = Double.valueOf(secondInput);
                Double result = null;
                calculated = true;
                switch (operator) {
                    case "+":
                        result = a + b;
                        break;
                    case "-":
                        result = a - b;
                        break;
                    case "x":
                        result = a * b;
                        break;
                    case "÷":
                        if (b != 0) {
                            result = a / b;
                        }
                        break;
                }
                this.input_text.setText(result.toString());
            }
        }
    }
    
  • 运行结果

image-20230902195520619



Eclipse 实现 Java 编辑器

参考链接?:https://blog.csdn.net/qq_52354698/article/details/127657550

https://blog.csdn.net/qq_52354698/article/details/127657836

  • Eclipse

Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

Eclipse 附带了一个标准的插件集,包括Java开发工具(Java Development Kit,JDK)。

  • 设计思路
  • 新建项目
  • 创建包和类
  • GUI 界面的实现
  • 测试类的实现
  • actionPerformed 方法的实现
  • run 方法的实现
  • 简单测试
  • 实现步骤
  1. 在Eclipse中创建一个Java projectimg

  2. 创建包和类

    包:com.java.myedit

    类:FileWindow(主要方法类,用作GUI界面以及逻辑功能的实现)和Main(测试类)

    img

  3. 代码

    FileWindow.java

    package com.java.myedit;
    
    import java.awt.CardLayout;
    import java.awt.Color;
    import java.awt.GridLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.io.*;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JTextArea;
    import javax.swing.JTextField;
    
    public class FileWindow extends JFrame implements ActionListener, Runnable {
    	private static final long serialVersionUID = 1L;
    
        Thread compiler = null;
        Thread run_prom = null;
        boolean bn = true;
        CardLayout mycard = new CardLayout();  //声明布局
        File file_saved = null;
        JButton button_input_txt=new JButton("程序输入区(白色)"),   //按钮的定义
                button_compiler_text=new JButton("编译结果区(粉红色)"),
                button_see_doswin=new JButton("程序运行结果(浅蓝色)"),
                button_compiler=new JButton("编译程序"),
                button_run_prom=new JButton("运行程序");
    
        JPanel panel = new JPanel();  //下方显示区
        JTextArea input_text = new JTextArea(); // 程序输入区
        JTextArea compiler_text = new JTextArea();// 编译错误显示区
        JTextArea dos_out_text = new JTextArea();// 程序的输出信息
    
        JTextField input_file_name_text = new JTextField();
        JTextField run_file_name_text = new JTextField();
    
        public FileWindow() {
            super("Java语言编译器");
            compiler=new Thread(this);
            run_prom=new Thread(this);
    
            panel.setLayout(mycard);//设置卡片布局
            panel.add("input",input_text);//定义卡片名称
            panel.add("compiler", compiler_text);
            panel.add("dos",dos_out_text);
            add(panel,"Center");
    
            compiler_text.setBackground(Color.pink); //设置颜色
            dos_out_text.setBackground(Color.cyan);
            JPanel panel_north=new JPanel();
    
            panel_north.setLayout(new GridLayout(3, 3)); //设置表格布局
            //添加组件
            panel_north.add(new JLabel("输入编译文件名(.java):"));
            panel_north.add(input_file_name_text);
            panel_north.add(button_compiler);
            panel_north.add(new JLabel("输入应用程序主类名:"));
            panel_north.add(run_file_name_text);
            panel_north.add(button_run_prom);
            panel_north.add(button_input_txt);
            panel_north.add(button_compiler_text);
            panel_north.add(button_see_doswin);
            add(panel_north,"North");
    
            //定义事件
            button_input_txt.addActionListener(this);
            button_compiler.addActionListener(this);
            button_compiler_text.addActionListener(this);
            button_run_prom.addActionListener(this);
            button_see_doswin.addActionListener(this);
        }
    
        public void actionPerformed(ActionEvent e) {
        	if(e.getSource()==button_input_txt)
            {    //显示程序输入区
                mycard.show(panel,"input");
            }
            else if(e.getSource()==button_compiler_text)
            {    //显示编译结果显示区
                mycard.show(panel,"compiler");
            }
            else if(e.getSource()==button_see_doswin)
            {    //显示程序运行结果区
                mycard.show(panel,"dos");
            }
            else if(e.getSource()==button_compiler)
            {    //如果是编译按钮,执行编译文件的方法
                if(!(compiler.isAlive()))
                {
                    compiler=new Thread(this);
                }
                try {
                    compiler.start();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
    
                mycard.show(panel,"compiler");
    
            }
            else if(e.getSource()==button_run_prom)
            {    //如果是运行按钮,执行运行文件的方法
                if(!(run_prom.isAlive()))
                {
                    run_prom=new Thread(this);
                }
                try {
                    run_prom.start();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
                mycard.show(panel,"dos");
            }
        }
    
        @Override
        public void run() {
            if(Thread.currentThread()==compiler) {
            	/*如果当前 Thread 是编译,
            	 * 那么会将程序输入区中的代码以.java 文件的形式
            	 * 保存到项目的当前目录下,
            	 * 并通过javac命令执行刚才保存的.java 文件生成.class 文件,
            	 * 编译后的信息会输出到编译结果显示区。*/
    
    	        compiler_text.setText(null);
    	        String temp=input_text.getText().trim();  // trim()方法用于删除字符串的头尾空白符
    	        byte [] buffer=temp.getBytes();
    	        int b=buffer.length;
    	        String file_name=input_file_name_text.getText().trim();
    
    	        try {
    				file_saved = new File(file_name);
    				FileOutputStream writefile = new FileOutputStream(file_saved);
    				writefile.write(buffer, 0, b);
    				writefile.close();
    			} catch (Exception e) {
    				System.out.println("ERROR");
    			}
    
    			try {
    			    //获得该进程的错误流,才可以知道运行结果到底是失败了还是成功。
    				Runtime rt=Runtime.getRuntime();
    				InputStream in=rt.exec("javac "+file_name).getErrorStream(); //通过Runtime调用javac命令。注意:“javac ”这个字符串是有一个空格的!!
    
    			    BufferedInputStream bufIn=new BufferedInputStream(in);
    
    			    byte[] arr=new byte[100];
    			    int n=0;
    			    boolean flag=true; //用于判断编译是否成功
    
    			    //输入错误信息
    			    while((n=bufIn.read(arr, 0,arr.length))!=-1) {
    			        String s=null;
    			        s=new String(arr,0,n);
    			        compiler_text.append(s);
    			        if(s!=null) {           
    			        	flag=false;
    			        }
    			    }
                    //判断是否编译成功
                    if(flag) {
                        compiler_text.append("Compile Succeed!");
                    }
                } catch (Exception e) {
                	System.out.println("ERROR");
                }
            }else if(Thread.currentThread()==run_prom){
            	/*如果当前 Thread 是运行,
            	 * 那么会通过java命令执行编译生成的.class 文件,
            	 * 并将程序结果显示到程序运行结果区中*/
    
            	dos_out_text.setText(null);
    
    		    try {
    		        Runtime rt=Runtime.getRuntime();
    		        String path=run_file_name_text.getText().trim();
    				Process stream=rt.exec("java "+path);//调用java命令
    
    				InputStream in=stream.getInputStream();
                    BufferedInputStream bisErr=new BufferedInputStream(stream.getErrorStream());
                    BufferedInputStream bisIn=new BufferedInputStream(in);
    
    				byte[] buf=new byte[150];
    				byte[] err_buf=new byte[150];
    
    				@SuppressWarnings("unused")
    				int m=0;
    				@SuppressWarnings("unused")
    				int i=0;
    				String s=null;
    				String err=null;
    
    				//打印编译信息及错误信息
    				while((m=bisIn.read(buf, 0, 150))!=-1) {
    					s=new String(buf,0,150);
    					dos_out_text.append(s);
    				}
    				while((i=bisErr.read(err_buf))!=-1) {
    					err=new String(err_buf,0,150);
    		            dos_out_text.append(err);	
    		        }
    		    }catch (Exception e) {
    		    	System.out.println("ERROR");
                }
            }
        }
    }
    

    Main.java

    package com.java.myedit;
    
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    public class Main {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            FileWindow win=new FileWindow();
            win.pack();  //调整框架的大小,使框架的内容等于或大于它们的首选大小
            win.addWindowListener(new WindowAdapter() {  //WindowAdapter是接收窗口事件的抽象适配器类
                public void windowClosing(WindowEvent e)
                {
                    System.exit(0);
                }
            });
            //设置窗口大小
            win.setBounds(200, 180,550,360);
            win.setVisible(true);
        }
    }
    
  • 结果及测试

img

img

img



一本糊涂账

链接?:https://how2j.cn/module/104.html

  • 知识
  • 面向对象,字符串数字,日期
  • 异常,集合,JDBC,反射机制,I/O,Swing,利用TableModel更新数据,图形界面的皮肤
  • 图表chart动态生成,数据库的备份与恢复,自定义圆形进度条
  • 软件设计思想:单例模式,面板类与监听器类松耦合,Entity层设计,DAO层设计,Service层设计
  • 业务常见处理手法:CRUD操作,配置信息,配置信息初始化,报表生成,一对多关系,多对一关系
  • 实现目标

​ 步骤1:消费一览

统计本月的消费总数,今日消费,日均消费,本月剩余,日均可用,距离月末有多少天。

同时使用一个环形进度条,这个环形进度条不是JDK自带的,需要自己设计,并且随着消费用度,颜色从绿色渐变为红色。

消费一览

​ 步骤2:记一笔

记录本日的消费额度, 分类下拉框从 消费分类数据中读取,并且把经常消费的分类放在前面。

日期默认选中今天,也可以手动指定日期。

记一笔

​ 步骤3:消费分类管理

对消费进行经典的CRUD 增删改查管理,同时显示一个分类下的消费次数。
这里涉及到多表关系:

消费记录和消费分类是多对一关系

消费分类管理

​ 步骤4:月度消费报表

使用第三方chart类生成柱状报表,显示本月的消费趋势

月度消费报表

​ 步骤5:设置预算和数据库路径

在消费一览中需要显示本月可用多少金额,都是建立在预算的基础上的。

在设置页面,设置本月的预算金额。

后续的还原和备份,都需要用到数据库的命令mysql和mysqldump,需要在这里配置mysql的安装目录

设置预算和数据库路径

​ 步骤6:备份数据

把数据库中的所有数据,备份到.sql文件中

备份数据

​ 步骤7:恢复数据

根据.sql文件还原数据库

恢复数据

  • 开发流程

    1. 表结构设计
    1. 数据库和表的创建

      1)创建数据库

      create database hutubill;
      use hutubill;
      

      2)确定需要哪些表

      ​ 根据业务上的需要,一共要3个表:

      1. 配置表信息 config
        用于保存每月预算和Mysql的安装路径( 用于备份还原用)
      2. 消费分类表 category
        用于保存消费分类,比如餐饮,交通,住宿
      3. 消费记录表 record
        用于存放每一笔的消费记录,并且会用到消费分类

      3)配置信息表config

      ​ 配置信息表 config有如下字段
      id 主键,每个表都有一个主键 类型是 int
      key_ 配置信息按照键值对的形式出现 ,类型是varchar(255)
      value配置信息的值, 类型是 varchar(255)

      1. 键值对
        进一步解释一下键值对,比如要存放每个月的预算,则需要在config表中增加一条记录,key="budget" value="500",就表示预算是500.

      2. varchar(255) 表示变长字符,如果实际存放只有30个字符,那么在数据库中只占用30的空间,最多占用255

      3. key 是关键字,不适合用于作为字段名,所以在key后面加了一个下划线 key_ 就不会有任何问题了,识别性一样很好,一眼就知道这个字段是干什么用的

      4. ENGINE=InnoDB MySQL有多种存储引擎,MyISAM和InnoDB是其中常用的两种, 他们之间的区别很多。 这里使用ENGINE=InnoDB 是因为后续要使用的外键约束只有在InnoDB中才生效。

      5. DEFAULT CHARSET=utf8; 表示该表按照UTF-8的编码存放中文

      CREATE TABLE config (
        id int ,
        key_ varchar(255) ,
        value varchar(255)
      )  ENGINE=InnoDB  DEFAULT CHARSET=utf8;
      

      4)消费分类表 category

      ​ 消费分类表 category 有如下字段:
      id 主键,每个表都有一个主键 类型是 int
      name分类的名称,类型是varchar(255)

      CREATE TABLE category (
        id int,
        name varchar(255)
      )   ENGINE=InnoDB DEFAULT CHARSET=utf8;
      

      5)消费记录表 record

      ​ 消费记录表 record 有如下字段:
      id 主键,每个表都有一个主键 类型是 int
      spend 本次花费,类型是int
      cid 对应的消费分类表的中记录的id, 类型是int
      comment 备注,比如分类是娱乐,但是你希望记录更详细的内容,啪啪啪,那么就存放在这里。
      date 日期,本次记录发生的时间。

      CREATE TABLE record (
        id int,
        spend int,
        cid int,
        comment varchar(255) ,
        date Date
      )   ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
    2. 表关系

      分类表和记录表之间的关系是 一对多
      又叫ONE TO MANY 1:M

      如何分析表之间的关系

      确定好分类和记录之间的关系之后,就需要在数据库表结构中通过外键表现出来

      外键是加在多表中的,也就是加在Record表中的,在这里就是cid
      record (
      id int,
      spend int,
      cid int,
      comment varchar(255) ,
      date Date
      )
      cid指向了category表的主键 id

    3. 约束

      1)主键

      ​ 所有的表都有一个主键id,但是这只是语意上的(我们认为的),为了让数据库把id识别为主键,需要加上主键约束。

      ​ 主键约束自带非空唯一属性,即不能插入空,也不能重复。

      ​ 对三张表都加上主键约束:

      alter table category add constraint pk_category_id primary key (id);
      alter table record add constraint pk_record_id primary key (id);
      alter table config add constraint pk_config_id primary key (id);
      

      alter table category 表示修改表category
      add constraint 增加约束
      pk_category_id 约束名称 pk 是primary key的缩写,category是表名, id表示约束加在id字段上。约束名称是可以自己定义的,你可以写成abc,但是尽量使用好的命名,使得一眼就能够看出来这个约束是什么意思。 能够降低维护成本。
      primary key 约束类型是主键约束
      (id) 表示约束加在id字段上

      2)设置id为自增长

      ​ 设置id为自增长是常用的插入数据策略。 换句话说,插入消费分类数据的时候,只用提供分类名称即可,不需要自己提供id, id由数据库自己生成。
      ​ 不同的数据库采用的自增长方式是不一样的,比如Oracle使用Sequence来实现,而MySQL就使用AUTO_INCREMENT来实现。

      alter table category change id id int auto_increment;
      alter table record change id id int auto_increment;
      alter table config change id id int auto_increment;
      

      alter table category 表示修改表category
      change id 表示修改字段 id
      id int auto_increment; 修改后的id是 int类型,并且是auto_increment(修改之前仅仅是int类型,没有auto_increment)

      3)外键

      ​ 外键约束的作用是保持数据的一致性(比如增加一条消费记录,金额是500,cid是5。但是cid=5在分类表category中找不到对应的数据,那么这就破坏了数据的一致性,会带来一系列的后续问题,比如根据分类进行统计,就会找不到这条数据。)

      ​ 增加外键约束之前首先确定record表的外键是cid,指向了category表的id主键。
      ​ 所以增加外键之前,必须把category的id字段设置为主键。从而保证cid=5的数据在category中只能找到一条,而不是找到多条。

      alter table record add constraint fk_record_category foreign key (cid) references category(id);
      

      alter table record 修改表record
      add constraint 增加约束
      fk_record_category 约束名称fk_record_category,fk是foreign key的缩写,record_category表示是从record表指向category表的约束。 与主键一样,约束名称也是可以自己定义的,比如写成abc. 不过依然建议使用可读性好的命名方式。
      foreign key 约束类型,外键
      (cid) references category(id) 本表record的字段 cid 指向category表的字段id

    1. Java代码层面

我的GitHub链接?:https://github.com/pop-yasigi/BillManagementSystem

  1. 思考&感悟

在这个项目中主要学习到了以下内容:

  • JDBC的使用,将Java代码与MySQL的连接
  • 各功能代码分类打包的方式
  • Entity、DAO、Service、Listener层的设计方式
  • WorkingPanel抽象类(将所有panel的共同点打包成WorkingPanel,并实现该父类,强制重写几个共同的函数
  • 表结构的设计方式:一对多关系,多对一关系
  • 在点击打开某一板块的时候,数据随之刷新,不会出现UI界面数据和MySQL数据不统一的情况