Java: Body Mass Index (BMI)
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:
- Components such as text fields or text areas that contain information that you will
get or set. Normally, you would not declare labels or buttons as instance variables; they can be local
variables or even just expressions in the constructor if your program never
refers to them after the interface has been constructed.
Altho local variables disappear when a constructor or method returns, buttons and labels have been added to panel, which saves their values. If you need to refer to a button to enable or disable it, it should also be an instance variable.
- Variables that hold values that are needed as part of the problem. These are values that must remain between interactions with the user, not just temporary calculations.
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.
- 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.
- 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 aJOptionPane
to deliver the message. See JOptionPane - Simple Dialogs. - Add error checking.
Use
if
statements to test for unreasonable and illegal values.