Experiments in Java


Session J4: Boolean Expressions and Conditionals

Let us turn our attention to the creation of a simple class to represent dates. We will call this class SimpleDate. You may have created such a class in a previous lab. In case you haven't (or in case you are not confident in your code), here is a sample one.


/**
 * A very simple implementation of dates using Gregorian-style 
 * calendars (with year, month, and day).
 *
 * @author Samuel A. Rebelsky
 * @version 1.2 of September 1998
 */
public class SimpleDate {

  // +--------+--------------------------------------------------
  // | Fields |
  // +--------+

  /** The year. */
  protected int year;

  /** The month.  Use 1 for January, 2 for February, ...  */
  protected int month;

  /** The day in the month.  */
  protected int day;


  // +--------------+--------------------------------------------
  // | Constructors |
  // +--------------+

  /**
   * Build a new date with year, month, and day.  The month should be
   * between 1 and 12 and the day between 1 and the number of days in
   * the month.
   */
  public SimpleDate(int y, int m, int d) {
    this.year = y;
    this.month = m;
    this.day = d;
  } // SimpleDate(int,int,int)


  // +------------+----------------------------------------------
  // | Extractors |
  // +------------+

  /**
   * Get the year.
   */
  public int getYear() {
    return this.year;
  } // getYear()

  /**
   * Get the month.
   */
  public int getMonth() {
    return this.month;
  } // getMonth()

  /**
   * Get the day.
   */
  public int getDay() {
    return this.day;
  } // getDay()

  /**
   * Convert to a string (American format: MM/DD/YYYY)
   */
  public String toString() {
    return this.month + "/" + this.day + "/" + this.year;
  } // toString()

} // class SimpleDate


At present, this class is relatively primitive. All we can do is create new dates, check on their various components, and convert dates to strings. Good object design dictates that we think about some other things we might want to do with dates, such as check whether the current year is a leap year or determine the number of days in the month.

In Experiment J4.1 you will extend this SimpleDate class.

Boolean expressions

While there are a number of different methods we might add to the SimpleDate class, we will begin with some relatively basic ones (in terms of the complexity of implementation) and move on to more complicated ones.

To begin with, we'll look at a very simple question: ``Does this date occur in January?'' While it is not a very sophisticated question, it will help us get started with more advanced questions. It might even be used in support of other questions. For example, in order to determine whether a date is in winter, we might want to check if it's in January.

How do we determine if the date occurs in January? Fairly easily: all we need to do is compare the month field to the value we use for January (1).

Now, how might we express this as a Java method? By this point, you should know that every method begins with the keyword public, a return type, a method name, and a parameter list. Each method also has a body that describes how to implement the method.

What name should we give our method that checks whether the current date falls in January? Perhaps inJanuary.

What type should inJanuary return? It shouldn't return a number, since numbers aren't used to answer questions like ``Is this day in January?'' (If you don't understand this, think about how you'd react to ``four'' as an answer to ``Is it sunny today?'') Strings seem like they might be appropriate, since "Yes" and "No" are valid responses. However, these are really the only responses that we would accept (e.g., "Zebra" is not a response we would be very happy with). Because there are many cases in which we accept only two answers, Java provides a primitive type, boolean, which only has two legal values. The two legal values are true (corresponding to ``yes'') and false (corresponding to ``no''). The type is named boolean after George Boole, a logician who developed a system of reasoning about these two values.

What parameters do we send to inJanuary? None, it turns out. While you may be tempted to give inJanuary a SimpleDate as a parameter, in the object-oriented perspective, we will ask each date whether it occurs in January. A small difference, but an important one.

Putting it all together, we get the following:

  /** 
   * Determine whether the current date falls in January.
   */
  public boolean inJanuary() {
    ...
  } // inJanuary()

If the month field has the value 1, then it is January. The ``is'' operator in Java is written == and, as you might guess, returns a boolean value. We write this as

    return this.month == 1;

In addition to == for ``is'', Java also supports

For example, you might check if x is negative with x<0. Similarly, you might check if x is non-negative with x>=0.

In Experiment J4.2 you will develop some simple boolean methods.

Operations on Booleans

As you might guess, we can be somewhat limited if all we can do is one comparison. In particular, you may want to use the results of a comparison and you may want to do multiple comparisons. We will examine the second issue first and write boolean expressions that involve multiple comparisons. In particular, we will look at three new questions we might ask about a date:

These questions have some other appropriate aspects. Our previous question (``Does this date occur in January?'') is at least partially involved in the answer to two of these questions (``Does this date occur in winter?'' and ``Is this date the first day of the year?''). The last two questions also have simple approximate answers (e.g., ``It's winter if it's December, January, or February'') and more complex correct answers (e.g., ``It's winter if it's after the winter solstice and before the vernal equinox''). Note also that these questions are used to answer more complex questions, like ``How many days are there in this year?'' and ``How likely is it to be cold on this day?''

How do we know that a day is the first day of the year? It's the first day of the year if it's in January and it's the first day of the month. You'll note that we have two tests we want, and we'd like both of them to hold. Just as we said and in the sentence above, we use an and operator in Java. Surprisingly, and is written && in Java. Using that operator, we can write

  /**
   * Is this day the first day of the year?
   */
   public boolean isFirstDay() {
     return (this.month == 1) && (this.day == 1);
   } // isFirstDay

Note that we could also have used inJanuary() in place of (this.month == 1). Note also that we used parentheses to gather together the subexpressions. Such parentheses aren't always necessary, but we recommend that you use them as they keep your code clearer.

Is isFirstDay strictly necessary? Probably not, as we could just use the test that forms the one line of its body. However, using isFirstDay can make our code more readable, shorter, and more supportive of change. For example, if it were somehow decreed that July 31 were the first day of the year, we would only need to change isFirstDay and not hundreds or thousands of lines of code.

Now let us move on to the next method. How do we determine if a date occurs in winter? We'll use the simple view that a month is in winter if it is in December, January, or February. Note that here we used the word or. Java includes an or operator that holds if either argument holds. In Java, or is written ||. Using that operator, we can write the simple isWinter as follows.

  /**
   * Is this day in winter?
   */
  public boolean isWinter() {
    return (this.month == 12) ||
           (this.month == 1) ||
           (this.month == 2);
  } // isWinter

You might want to think about how to write a more sophisticated version of this method that only returns true when it's between the winter solstice and the vernal equinox.

How do we determine whether it's a leap year? For the modern Gregorian calendar, it involves a relatively simple calculation. You might think that this calculation is ``a year is a leap year if the year is divisible by 4''. However, the designers of the Gregorian calendar realized that such a calculation would lead to some inaccuracy as the centuries marched on. In fact, years divisible by 100 are not leap years, unless they are also divisible by 400. So, 1900 is not a leap year, but 2000 is. Is such precision necessary in a computer program? Certainly. For example, computer programs are responsible for computing interest on bank accounts, and you wouldn't want the bank to pay you one less day of interest in some years (and, conversely, the bank wouldn't want to pay all of its customers for an extra day of interest). More importantly, if you claim that a leap year isn't a leap year, then you'll misidentify which day of the week most days of the year fall on. So, we can say that

a year is a leap year if is divisible by 400 or if it is divisible by 4 and not divisible by 100.

So, how do we determine if a year is evenly divisible by 4 (or 100 or 400). We can use the modulus or remainder operator, %. To express ``the remainder after dividing x by 4'' in Java, we would write x%4. A number is evenly divisible by 4 if the remainder after dividing by 4 is 0, hence year is divisible by 4 if (year%4 == 0).

Putting it all together, we get

  /**
   * Determine if the current year is a leap year.
   */
  public boolean isLeapYear() {
    return (this.year % 400 == 0) ||
      ((this.year % 4 == 0) && (this.year % 100 != 0))
  } // isLeapYear

Note that we used the ``is not'' operator, !=. We can also use the boolean not operator, which is written !. So, we could also write ``this year is not divisible by 100'' as

!(this.year % 100 == 0)

In Experiment J4.3 you will develop additional boolean methods for the SimpleDate class.

Conditionals

By this point, you may have noted that we are not doing much with the results of all the methods we have developed. If you have done the experiments, you will have noted that you can use them to print true or false, but not much else. So, how do we use boolean variables and methods? Most typically, we use them in conditional expressions. For example, if someone asked how many days there are in the year, you might say to yourself ``If it is a leap year, then there are 366 days; otherwise, there are 365 days''. Java provides an if statement that does something similar.

The if statement has the form

    if (condition) {
      statement-list;
    }
    else {
      statement-list;
    }

To execute an if statement, Java first evaluates the condition (a boolean expression). If the condition holds (evaluates to true), Java then executes the statements in the first statement list. If the condition does not hold, Java then executes the statements in the second list.

For example, to print meaningful text about whether or not a day falls in winter, we might write

    if (day.isWinter()) {
      out.println(day.toString + " falls in winter.");
    }
    else {
      out.println(day.toString + " does not fall in winter.");
    }

Similarly, to write a function that returns the number of days in the current year we might write

  /**
   * Determine the number of days in the current year.
   */
  public int daysInYear() {
    if (this.isLeapYear()) {
      return 366;
    }
    else {
      return 365;
    }
  } // daysInYear()

Note that you do not need to include the else portion of the if statement. If you leave out the else and the condition fails, Java simply goes on to the next statement. For example, if you only want to print a comment about winter days, you might write

    // Say something about cold winter days.
    if (day.isWinter()) {
      out.println("Brr ... I'll bet it's cold on " + 
                  day.toString() + ".");
    } // if the day is in winter

We are now ready to consider a more complicated method, one that determines whether or not the current date precedes another date. This method requires us to do a sequence of tests. One might say

Note that if we wrote this using the indentation method given above, we would indent fairly far. Hence, custom dictates that in a series of nested ifs, you maintain the same indentation, as in

  /** 
   * Determine if the current date precedes the parameter.
   */
  public boolean precedes(SimpleDate other) {
    if (this.year < other.year) {
       return true;
    }
    else if (this.year > other.year) {
     return false;
    }
    else if (this.month < other.month) {
      return true;
    }
    else if (this.month > other.month) {
      return false;
    }
    else if (this.day < other.day) {
      return true;
    }
    else if (this.day > other.day) {
      return false;
    }
    else {
      return false;
    }
  } // precedes(SimpleDate)

In Experiment J4.4 you will improve the output from this class. In Experiment J4.5 you will investigate comparison of dates.

The switch statement

Let us now consider how one might compute the number of days until the end of the month. If we had a daysInMonth function, we might simply write

  /**
   * Compute the number of days until the end of the month.
   */
  public int daysLeftInMonth() {
    return this.daysInMonth() - this.day;
  } // daysLeftInMonth()

If you've done Experiment J4.5, you may even have started to write such a method. However, we will assume that no such method is available. What should we do? It seems like a series of nested if statements is necessary, as in

  /**
   * Compute the number of days until the end of the month.  Return
   * -1 if it is not possible to determine this value.
   */
  public int daysLeftInMonth() {
    if (this.month == 1) {
      return 31 - this.day;
    } // January
    else if (this.month == 2) {
      if (this.isLeapYear()) {
        return 29 - this.day;
      } // A leap year
      else {
        return 28 - this.day;
      } // Not a leap year
    } // February
    
    ...
    
    else if (this.month == 11) {
      return 30 - this.day;
    } // November
    else if (this.month == 12) {
      return 30 - this.day;
    } // December
    else { // Aagh!  Not a month I recognize.
      return -1;
    } // Troubles
  } // daysLeftInMonth()

Such a solution is rather cumbersome. To handle some of these cumbersome cases, Java provides the switch statement, which has the following form

    switch (expression) {
      case val-1: 
        statement-list-1;
        break;
      case val-2:
        statement-list-2;
        break;
      ...
      case val-n:
        statement-list-n;
        break;
      default:
        statement-list;
        break;
    } // switch

This statement has the meaning ``Evaluate the expression. If it is equal to val-1, then execute statement-list-1. Otherwise, if it is equal to val-2, then execute statement-list-2. If the expression is not equal to any of the values, execute the default statement list.''

Using this statement, we can rewrite daysLeftInMonth as

  /**
   * Compute the number of days until the end of the month.  Return
   * -1 if it is not possible to determine this value.
   */
  public int daysLeftInMonth() {
    int daysLeft;
    switch (this.month) {
      case 1:  // January
        daysLeft = 31 - this.day;
        break;
      case 2:  // February
        if (this.isLeapYear()) {
          daysLeft = 29 - this.day;
        } 
        else {
          daysLeft = 28 - this.day;
        }
        break;
    ...
    
      case 12:  // December
        daysLeft = 31 - this.day;
        break;
      default:
        daysLeft = -1;
        break;
    } // switch(this.month)
    return daysLeft;
  } // daysLeftInMonth()

Note that you can only use switch statements with primitive types, like integers, boolean values, and real numbers.

It is possible to use multiple labels (the lines that read case) for each statement sequence. If any of the corresponding values match, then the statement is executed. Using this feature, we can rewrite daysLeftInMonth a final time.

  /**
   * Compute the number of days until the end of the month.  Return
   * -1 if it is not possible to determine this value.
   */
  public int daysLeftInMonth() {
    int daysLeft;
    switch (this.month) {
      case 1:  // January
      case 3:  // March
      case 5:  // May
      case 7:  // July
      case 8:  // August
      case 10: // October
      case 12: // December
        daysLeft = 31 - this.day;
        break;
      case 4:  // April
      case 6:  // June
      case 9:  // September
      case 11: // November
        daysLeft = 30 - this.day;
        break;
      case 2:  // February
        if (this.isLeapYear()) {
          daysLeft = 29 - this.day;
        } 
        else {
          daysLeft = 28 - this.day;
        }
        break;
      default:
        daysLeft = -1;
        break;
    } // switch(this.month)
    return daysLeft;
  } // daysLeftInMonth()

We will not cover many subtleties to the switch statement. For example, you can leave out the break statements, in which case you ``fall through'' to the next bit of code.

In Experiment J4.6 you will investigate the switch statement.

Applet parameters

This section is optional and is intended for classes emphasizing applets or pursuing a simultaneous discussion of applications and applets.

As you may have noticed, the applets that we've built so far are limited. If you want an applet to do something slightly different (e.g., display a different piece of text), you need to change the source code of the applet. Is this the only way to configure an applet? No. You can also use the HTML page to set other attributes of the applet. These are often called the applet's parameters. What can the parameters be? Anything you think is appropriate: colors, text, more general instructions, and so on and so forth.

How do you set a parameter? With a param tag (another HTML tag). The param tag goes between your applet tags. For example, to indicate that the message should be ``Reconfigured'', one might write:

<param name="message" value="Reconfigured">

How can you access that parameter? With a getParameter method. The getParameter method always returns a string (even if you want a number). If the HTML code does not include a param tag with the appropriate name, getParameter returns the special null value.

Here is the paint method of a simple applet that draws a selected string in green.

  public void paint(Graphics paintBrush) {
    String msg;
    msg = getParameter("message");
    paintBrush.setColor(Color.green);
    paintBrush.drawString(msg, 10, 14);
  } // paint(Graphics)

If the message parameter is set to ``Hello World'', then this will display ``Hello World''. If the message parameter is set to ``Java Rules'', then this will display ``Java Rules''.

But what if the page does not set a parameter? Then msg will by null. We don't know what drawString does with null messages, but it's unlikely to be pleasant. Hence, we should specify a default message that the applet uses when no parameter is supplied. How do we know when to use the default? Conditionals help us tell.

    String msg = getParameter("message");
    if (msg == null) {
      msg = "No message selected";
    }

Conditionals can also help you help you convert strings to other objects. As an example of conversion, consider how one might allow the Web page to set the color that text is displayed in. As before, getParameter will return a string. Presumably, the string for the color will be ``blue'', ``red'', ``yellow'', or other colors you wish to support. Yet setColor needs a color. Hence, we compare the string for the color to members of a fixed set of strings, and when we find which it matches, we select the appropriate color. For example,

    Color color = Color.black;	// Default
    String colorName = getParameter("color");
    if ("blue".equals(colorName)) { color = Color.blue; }
    else if ("red".equals(colorName)) { color = Color.red; }
    else if ("yellow".equals(colorName)) { color = Color.yellow; }

Note that we compare strings with the equals method rather than ==.

What about numeric parameters? Since your applet receives the parameter as a string, you need a way to convert strings to numbers. Unfortunately, while Java includes a standard conversion mechanism, that mechanism is somewhat confusing for novices. Conversion involves the use of the Integer.parseInt method along with a try/catch clause. For example,

    int fontSize;
    String sizeParam = getParameter("Size"); 
    try { 
      fontSize = Integer.parseInt(sizeParam);
    }
    catch (Exception e) {
      fontSize = 12;
    }

What does this say? As you might guess, the first line is a declaration of the variable fontSize and the second line reads in the string used for the font size. The line that reads fontSize = Integer.parseInt(sizeParam); does the conversion.

So, what is a try/catch clause? It's a kind of conditional. Java cannot convert all strings to integers. For example, what integer should it use for the string "Hello"? Hence, you'd like to write a conditional that says something like ``if the conversion fails, use a default''. The try indicates that you're doing something that might fail. The catch is the ``if it fails, do this''.

In effect, the code above says ``try to convert sizeParam to an integer and then assign it to fontSize; if that fails, assign 12 to fontSize''.

In a subsequent lab, you will consider try/catch clauses in more depth. For now, just model your code on this example. You are now ready to build parameterized applets. In Experiment J4.7 you will consider the basics of applet parameters. In Experiment J4.8 you will use conditionals to improve applet customization.

In both labs, you will use and extend the following customizable applet.


import java.applet.Applet;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;

/**
 * A simple illustration of applet parameters.
 * Will eventually support the following parameters:
 *   message: the message to print
 *   font: the base font (Serif, SansSerif, etc.)
 *   size: the size of the font
 *   style: the style (plain, bold, italic, bolditalic)
 *   color: the color of the message
 *   shadow: the color of a shadow behind the message
 * The initial version supports only the message parameter.
 *
 * @author Samuel A. Rebelsky
 * @version 1.0 of August 1999
 */
public class ConfigurableGreeting 
  extends Applet
{
  /** Paint the greeting. */
  public void paint(Graphics paintBrush) {
    // Begin with reasonable defaults
    String message = "A configurable message";
    String fontName = "Serif";
    int fontSize = 14;
    int fontStyle = Font.PLAIN;
    Color fontColor = Color.red;
    Color shadowColor = Color.gray;

    // Read information from the parameters.
    message = getParameter("message");
    
    // Set the font in preparation for writing.
    paintBrush.setFont(new Font(fontName, fontStyle, fontSize));

    // Paint the shadow first.  The vertical offset is based on 
    // the size of the font.  This allows the message to appear
    // at the top of the applet. 
    paintBrush.setColor(shadowColor);
    paintBrush.drawString(message, 12, fontSize+6);

    // Paint the message.
    paintBrush.setColor(fontColor);
    paintBrush.drawString(message, 10, fontSize+4);
  } // paint(Graphics)
} // class ConfigurableGreeting



Copyright (c) 1998 Samuel A. Rebelsky. All rights reserved.

Source text last modified Tue Oct 26 13:02:40 1999.

This page generated on Tue Oct 26 15:38:20 1999 by Siteweaver.

Contact our webmaster at rebelsky@math.grin.edu