Java: Body Mass Index (BMI)

BMI window

The Body Mass Index program is in one file. Sometimes the main program is put in a different file, but it's common to put it in the same class as the JFrame subclass.

The BMI calculator

  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 
// File:   gui/components/bmi/BMI.java
// Description: Compute Body Mass Index = kg/(m*m)
//         BMI is regarded as a measure of "fatness", with
//         numbers between 19 and 25 considered good.
//         Although commonly used, it's quite inaccurate.
//         Weight must be in kilograms and height in meters.
// Author: Fred Swartz - 2006-11-07
// Other : Level     : Introductory.
//         Structure : Subclass JFrame, include main in that class.
//         Components: JButton, JTextArea, JLabel.
//         Containers: JFrame.  JPanel for content pane.
//         Layout    : FlowLayout
//         Listeners : ActionListener as named inner class.
//         Naming    : Underscore prefix on instance variables.
//         Problems  : No check for legal numbers on input.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

////////////////////////////////////////////////////////////// class BMI
class BMI extends JFrame {
    //=============================================== static method main
    public static void main(String[] args) {
        BMI window = new BMI();
        window.setVisible(true);
    }

    //=============================================== instance variables
    // Declare and initialize instance variables that are
    // referred to when the program is running.
    private JTextField _mField   = new JTextField(4);  // height
    private JTextField _kgField  = new JTextField(4);  // weight
    private JTextField _bmiField = new JTextField(4);  // BMI

    //====================================================== constructor
    public BMI() {
        //... Create button and add action listener.
        JButton bmiButton = new JButton("Compute BMI");
        bmiButton.addActionListener(new BMIListener());

        //... Set layout and add components.
        JPanel content = new JPanel();
        content.setLayout(new FlowLayout());
        content.add(new JLabel("Weight in kilograms"));
        content.add(_kgField);
        content.add(new JLabel("Height in meters"));
        content.add(_mField);
        content.add(bmiButton);
        content.add(new JLabel("Your BMI is"));
        content.add(_bmiField);

        //... Set the window characteristics.
        setContentPane(content);
        setTitle("Body Mass Index");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();                          // Do layout.
        setLocationRelativeTo(null);     // Center window.
    }

    //////////////////////////////////////////// inner class BMIListener
    // Inner class is used to access components.
    // BMI is converted to int to eliminate excess "accuracy".
    private class BMIListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            double kilograms = Double.parseDouble(_kgField.getText());
            double meters    = Double.parseDouble(_mField.getText());
            int    bmi       = (int)computeBMI(kilograms, meters);
            _bmiField.setText("" + bmi);
        }
    }

    //=========================================== logic method computeBMI
    public static double computeBMI(double weight, double height) {
        return weight / (height * height);
    }
}

Notes

Typical pattern for simple programs

The BMI program shows a typical pattern used to make a one-window program with components. This is built from Example - Generic Calc prototype by adding fields, renaming, and changing the calculation.

BMI class is a subclass of JFrame

JFrame is the Java class that implements a window, which contains a content pane. The content pane is where we can put our components for display. The idea of a subclass (BMI) is that it can do everything its parent class (JFrame) can do, and more. Our subclass inherits all the capabilities of the default, empty, window and we then add our extra features.

Instance/field variables

Variables that are declared in the class, but not in a method or constructor are called instance, field, or member variables, depending on the author. The lifetime of these variables is the lifetime of the object, unlike local variables which disappear when the method or constructor returns. It's necessary to use instance variables for anything you need to refer to by name during execution (ie, in the listeners). The convention of prefixing instance variables with an underscore ("_") is used in this program.

Declare instance variables for things that will be referred to when the program is running, ie after the constructor has built the interface. Typically these include:

The button listener

The button listener gets the strings the user typed in the text fields, converts them to numbers, computes a result, and displays the result, converted to string, in another text field.

Happy Trails programming

A problem with this code is that it doesn't check for legal values. Checking isn't difficult, but it would make this simple example more complicated. In industrial strength programs checking for valid input and error conditions can take a surprisingly large percent of the total code. Student examples often omit error checking, but see Strings to Numbers for how to check for legal number conversions.

Separating Logic and Presentation

Ideally, the code that implements the core model of the problem (the BMI computation in this case) should be split from the graphical user interface code. If the logical model for the computation required saving values (its state), it would be better to put it in its own class. The computation of BMI can be done as a short method (computeBMI() - defined at the bottom of the BMIPanel class. A good rule of thumb is to structure the model code so that it could easily be used by a command line mode interface or called by another program. If you always make some effort to separate logic and presentation, your programs will be much easier to work with.

OPTIONAL - Formatting the double value

If you want to display the BMI value as a floating point number, you will discover that the default conversion to string sometimes produces many digits after the decimal point. The solution is to use a java.util.DecimalFormat object to format the output to one (or however many you wish) decimal place. The DecimalFormat object has a format() method which takes a number and returns a String formatted as requested. See Number to String Conversion.

Changes to BMIPanel

// Add this import to get the DecimalFormat class.
import java.text.DecimalFormat;
    . . .
    // Create this instance variable
    DecimalFormat m_resultFormat = new DecimalFormat("0.0");
    . . .
        // Assign result to a double without casting to int.
        double bmi = computeBMI(kilograms, meters);
        // Format the double with the resultFormat object.
        m_bmiField.setText(m_resultFormat.format(bmi));
. . .

How should resultFormat be declared?

The declaration for m_resultFormat above was as an instance variable, but there is nothing "variable" about it. Because it never changes, it should be declared as a constant using final. Because there only needs to be one copy, it should be declared static, and because we want to be able to change this without affecting other programs, we should make it private. Ie,

private static final DecimalFormat RESULT_FORMAT = new DecimalFormat("0.0");

Of course, it's inconceivable that another program will use this class, so declaring it private is overkill, but it's a good habit to establish.

OPTIONAL - Catching errors with try...catch

Catching conversion errors

Most student programs don't check for most error conditions. Perhaps this is appropriate because there is only limited time in a course. However, it isn't the way "real" programs should be written. If a user types input that isn't a number into one of the BMI text fields, the program should produce an error message, not crash. This suggested improvement is a good illustration of how exceptions are used for error checking.

The try...catch statement (See Exceptions) in the button listener catches the NumberFormatException that Double.parseDouble() throws if the input is not something that can be converted to double.

Changes to BMIPanel

. . .
    private class BMIListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            //... Enclose code that might throw an exception in try clause.
            try {
                double kilograms = Double.parseDouble(_kgField.getText());
                double meters    = Double.parseDouble(_mField.getText());
                int bmi = (int)computeBMI(kilograms, meters);
                _bmiField.setText("" + bmi);
            } catch (NumberFormatException ex) {
                _bmiField.setText("Bad Input");
            }
        }
   }
. . .

There are a number of ways so indicate an error to the user: Put an error message in an input or output field as above, beep, show an error dialog, ...

Extensions to the BMI program

The following are possible extensions to the BMI program.

  1. Change to English units. Use English units (inches and pounds) for input..

    Note: BMI must be calculated in metric units, so English input values must be converted to metric units to solve the problem. Assume there are 0.454 kilograms per pound and 0.0254 meters per inch.

  2. Give health advice. Based on the BMI values, make some commentary.

    Write another method which takes a BMI value and returns a string comment on the number. Call this method after calling the computeBMI method. Use a JOptionPane to deliver the message. See JOptionPane - Simple Dialogs.

  3. Add error checking. Use if statements to test for unreasonable and illegal values.