Java: Example - Simple Calculator

Calculator image Here is the source for the simple calculator shown at the left. It's divided into three source files.
  • Main (Calc.java) - A simple main program.
  • User interface (CalcGUI.java) - This is implemented as a subclass of JFrame. It both builds the interface the user sees (the "view), and handles the events coming from the buttons (the "controller").
  • Model (CalcLogic.java) - This is where the actual calculations take place. Altho this simple example doesn't show the full power of separating the business logic (often called the "model") from the user interface, there are many advantages in larger programs.
    • It is simpler for the developer to work with.
    • It can be used with many kinds of interfaces without changes. Eg, a GUI interface, a command-line interface, or a web-based interface.
    • The model can be changed (eg, to work with BigInteger) without changing the user interface. Of course, some changes may require interface changes, but the separation makes this easier.

The main program

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
// calc-ui-model/Calc.java -- Fred Swartz
//     Level     : Intermediate.
//     Structure : Three files: main, GUI (subclass of JFrame), logic.
//     Components: JButton, JTextField (right justified).
//     Containers: JFrame, several JPanels.
//     Layouts   : BorderLayout to put the other panels together.
//                 Two GridLayout panels for the buttons.
//     Listeners : One ActionListener which is shared by all 
//                 numeric key buttons.  Similarly share
//                 an ActionListener for all operator buttons.
//                 ActionListener for Clear button.
//     Other     : Use Font to enlarge font for components.
//               : try...catch for NumberFormatExceptions.

// Possible enhancements: 
//               Check for zero before division.
//               Additional operations: mod, square root, sign change, ...
//               Make this work with doubles, BigInteger, or ...
//               Format double results with DecimalFormat
//               Add keyboard listener.
//               Change to RPN (Reverse Polish Notation)

import javax.swing.*;

///////////////////////////////////////////////////////////// class Calc
/** Calc.java - A simple calculator.
  @author  Fred Swartz
  @version 2004-06-22 Rodenbach
*/
class Calc {
    //====================================================== method main
    public static void main(String[] args) {
        JFrame window = new CalcGUI();
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setVisible(true);
    }//end main
}//endclass Calc

The user interface

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157 
158 
159 
160 
161 
162 
/** calc-ui-model/CalcGUI.java - A GUI for the calculator.
    @author Fred Swartz
    @version 2004-04-20 Rodenbach
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

////////////////////////////////////////////////////////// class CalcGUI
class CalcGUI extends JFrame {
    //======================================================= constants
    private final Font BIGGER_FONT = new Font("monspaced", Font.PLAIN, 20);
    
    //=============================================== instance variables
    //--- Component referenced during execution
    private JTextField m_displayField;       // display result / input.
    
    //--- Variables representing state of the calculator
    private boolean   m_startNumber = true;  // true: num key next
    private String    m_previousOp  = "=";   // previous operation
    private CalcLogic m_logic = new CalcLogic(); // The internal calculator.

    //====================================================== constructor
    public CalcGUI() {
        //--- Display field
        m_displayField = new JTextField("0", 12);
        m_displayField.setHorizontalAlignment(JTextField.RIGHT);
        m_displayField.setFont(BIGGER_FONT);

        //--- Clear button
        JButton clearButton = new JButton("CLEAR");
        clearButton.setFont(BIGGER_FONT);
        clearButton.addActionListener(new ClearListener());

        //--- One listener for all numeric keys.
        ActionListener numListener = new NumListener();
        
        //--- Layout numeric keys in a grid.  Generate the buttons
        //    in a loop from the chars in a string.
        String buttonOrder = "789456123 0 ";
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new GridLayout(5, 3, 5, 5));
        for (int i = 0; i < buttonOrder.length(); i++) {
            String keyTop = buttonOrder.substring(i, i+1);
            if (keyTop.equals(" ")) {
                buttonPanel.add(new JLabel(""));
            } else {
                JButton b = new JButton(keyTop);
                b.addActionListener(numListener);
                b.setFont(BIGGER_FONT);
                buttonPanel.add(b);
            }
        }
        
        //--- One ActionListener to use for all operator buttons.
        ActionListener opListener = new OpListener();
        
        //--- Create panel with gridlayout to hold operator buttons.
        //    Use array of button names to create buttons in a loop.
        JPanel opPanel = new JPanel();
        opPanel.setLayout(new GridLayout(5, 1, 5, 5));
        String[] opOrder = {"+", "-", "*", "/", "="};
        for (int i = 0; i < opOrder.length; i++) {
            JButton b = new JButton(opOrder[i]);
            b.addActionListener(opListener);
            b.setFont(BIGGER_FONT);
            opPanel.add(b);
        }

        //--- Layout the top-level panel.
        JPanel content = new JPanel();
        content.setLayout(new BorderLayout(5, 5));
        content.add(m_displayField, BorderLayout.NORTH );
        content.add(buttonPanel   , BorderLayout.CENTER);
        content.add(opPanel       , BorderLayout.EAST  );
        content.add(clearButton   , BorderLayout.SOUTH );
        
        //--- Finish building the window (JFrame)
        this.setContentPane(content);
        this.pack();
        this.setTitle("Simple Calculator");
        this.setResizable(false);
    }//end constructor
    

    //======================================================== action_clear
    /** Called by Clear btn action listener and elsewhere.*/
    private void action_clear() {
        m_startNumber = true;         // Expecting number, not op.
        m_displayField.setText("0");
        m_previousOp  = "=";
        m_logic.setTotal("0");
    }

    /////////////////////////////////////// inner listener class OpListener
    /** Listener for all op buttons. */
    class OpListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            // The calculator is always in one of two states.
            // 1. A number must be entered -- an operator is wrong.
            // 2. An operator must be entered.
            if (m_startNumber) { // Error: needed number, not operator
                action_clear();
                m_displayField.setText("ERROR - No operator");
            } else {
                m_startNumber = true;  // Next thing must be a number
                try {
                    // Get value from display field, convert, do prev op
                    // If this is the first op, m_previousOp will be =.
                    String displayText = m_displayField.getText();
    
                    if (m_previousOp.equals("=")) {
                        m_logic.setTotal(displayText);
                    } else if (m_previousOp.equals("+")) {
                        m_logic.add(displayText);
                    } else if (m_previousOp.equals("-")) {
                        m_logic.subtract(displayText);
                    } else if (m_previousOp.equals("*")) {
                        m_logic.multiply(displayText);
                    } else if (m_previousOp.equals("/")) {
                        m_logic.divide(displayText);
                    }
    
                    m_displayField.setText("" + m_logic.getTotalString());
    
                } catch (NumberFormatException ex) {
                    action_clear();
                    m_displayField.setText("Error");
                }
    
                //--- set m_previousOp for the next operator.
                m_previousOp = e.getActionCommand();
            }//endif m_startNumber
        }//endmethod
    }//end class
    

    //////////////////////////////////// inner listener class ClearListener
    /** Action listener for numeric keys */
    class NumListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            String digit = e.getActionCommand(); // Get text from button
            if (m_startNumber) {
                // This is the first digit, clear field and set
                m_displayField.setText(digit);
                m_startNumber = false;
            } else {
                // Add this digit to the end of the display field
                m_displayField.setText(m_displayField.getText() + digit);
            }
        }
    }
    
    
    //////////////////////////////////// inner listener class ClearListener
    class ClearListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            action_clear();
        }
    }
}//endclass CalcGUI

The logic/model

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
// calc-ui-model/CalcLogic.java - The logic of a calculator.  No user interface.
// Fred Swartz - 2004-11-17 

/**
 * CalcLogic is the logic (model) of a hand calculator. 
 * Separating the model (logic) from the interface has advantages.
 * In this program the model is small, so it may not be as obvious,
 * but in larger programs the advantages can be substantial.
 * 1. It is simpler for the developer to work with.
 * 2. It can be used with many kinds of interfaces without changes.  Eg,
 *    a GUI interface, a command-line interface, or a web-based interface.
 * 3. The model can be changed (eg, to work with BigInteger) without
 *    changing the user interface.  Of course, some changes require
 *    interface changes, but the separation makes this easier.
 *
 * Improvements:
 * <ul>
 * <li>Calculations are done as ints; use double.</li>
 * <li>Add error checking (eg, division by zero checking.</li>
 * <li>Additional operations - change sign, mod, square root, ...</li>
 * </ul>
 * @author  Fred Swartz
 */
public class CalcLogic {
    
    //-- Instance variables.
    private int m_currentTotal;     // The current total is all we need to remember.
    
    /** Constructor */
    public CalcLogic() {
        m_currentTotal = 0;
    }
    
    public String getTotalString() {
        return ""+m_currentTotal;
    }
    
    public void setTotal(String n) {
        m_currentTotal = convertToNumber(n);
    }
    
    public void add(String n) {
        m_currentTotal += convertToNumber(n);
    }
    
    public void subtract(String n) {
        m_currentTotal -= convertToNumber(n);
    }
    
    public void multiply(String n) {
        m_currentTotal *= convertToNumber(n);
    }
    
    public void divide(String n) {
        m_currentTotal /= convertToNumber(n);
    }
    
    private int convertToNumber(String n) {
        return Integer.parseInt(n);
    }
}