Static Methods

We will use the ClockTime example. All the source code for this part of the lecture can be found here.

We would like to validate the values passed to ClockTime's constructor. We expect the value given for hours to be between 0 and 23. Similarly we expect the value given for minutes to be between 0 and 59. We would like to check for this conditions inside the constructor method and raise an exception when these conditions are not met.

So we would like to have something like the following skeleton

 ...     
  public ClockTime(int hours, int minutes){   
    if // hours are valid? 
      this.hours=hours; 
    else // exception, the value given for hours is not valid

    if // minutes are valid? 
      this.minutes=minutes; 
    else // exception, the value given for minutes is not valid
  } 
 ...
      

Let's start with the parts we already know how to do; we will write 2 methods one for checking hours and one for checking minutes.

 // to verify that hours is between 0 and 23 
  private boolean validHours(int hours){ 
    return hours >= 0 && hours < 24; 
  } 
 
  // to verify that minutes is between 0 and 59 
  private boolean validMinutes(int minutes){ 
    return minutes >= 0 && minutes < 60; 
  }
      

The preceding method definitions are instance methods. Methods that can be called on an instance (object) of this class. But we want to use these methods inside the constructor. We do not have an instance yet. Notice that for the preceding two methods we do not need this at all. We can make these methods static methods since they do not depend on instance information (i.e. this).

 // to verify that hours is between 0 and 23 
  private static boolean validHours(int hours){ 
    return hours >= 0 && hours < 24; 
  } 
 
  // to verify that minutes is between 0 and 59 
  private static boolean validMinutes(int minutes){ 
    return minutes >= 0 && minutes < 60; 
  }
      

The keyword static after the modifier public indicates that this method is a static method. We can now use this method inside our constructor to check the values given for hours and minutes.

 ...     
  public ClockTime(int hours, int minutes){   
    if (this.validHours(hours))
      this.hours=hours; 
    else // exception, the value given for hours is not valid

    if (this.validMinutes(minutes))
      this.minutes=minutes; 
    else // exception, the value given for minutes is not valid
  } 
 ...
      

If the values given to hours and minutes are within the appropriate ranges then we go ahead and assign these values to the class' fields. But what do we do when the values given are not within the appropriate ranges? Java provides a mechanism for exceptions. Exceptions in Java are objects as well. They have however a special meaning and we can perform special operations on exceptions. One such special operation is the ability to throw an exception. Throwing an exception in Java is a way of indicating that something unexpected happened and we are going to exit from our current computation.

  
  public ClockTime(int hours, int minutes){ 
    if (this.validHours(hours))
      this.hours=hours; 
    else throw new RuntimeException("Invalid hours: " + hours); 

    if (this.validMinutes(minutes))
      this.minutes=minutes; 
    else throw new RuntimeException("Invalid minutes " + minutes); 
  } 

A RuntimeException in Java stops the program's execution and reports the message inside the RuntimeException instance. In our example, when the value given for hours is not within 0 and 23 then we create a new RuntimeException instance with the message "Invalid hours " + hours. This creates a string with the erroneous value for hours. The throw keyword causes the execution of the program to stop and passes the instance of RuntimeException that we just created to the Java runtime.

Here is what you get after running ClockTimeTest. One of our tests causes an exception to be thrown.

Exception in thread "main" java.lang.RuntimeException: Invalid minutes 77
        at ClockTime.(ClockTime.java:12)
        at ClockTimeTest.runTests(ClockTimeTest.java:23)
        at ClockTimeTest.main(ClockTimeTest.java:29)
      

Accessibility Control

All the code for this part of the lecture can be found here.

In Java we can specify which other classes (and objects) can have access to methods and/or fields of our class. We will look at two Java modifiers; public and private.

Recall the class Rat from assignment 4. A rat has a location and a lifespan. After each round a rat's lifespan goes down by one. A rat can increase its lifespan by consuming food. There should be no other way of changing a rat's lifespan. The rat class should have sole control of how to manipulate the rat's lifespan.

 
/* 
   +------------------+ 
   | Rat              | 
   +------------------+
   | int lifespan     |
   | Posn loc         |
   +------------------+
   | Rat starve()     |
   | Rat eat()        |
   | bool isDead()    |
   +------------------+
*/

// to represent a Rat
public class Rat{ 
  private int lifespan; 
  private Posn loc; 

  public Rat(Posn loc){ 
    this(loc,10); 
  }
  private Rat(Posn loc, int lifespan){ 
    this.loc = loc; 
    this.lifespan = lifespan; 
  }

  // produce a hungrier rat. 
  public Rat starve(){ 
    return new Rat(this.loc,this.lifespan-1); 
  }

  // produce a less hungrier rat. 
  public Rat eat(int foodsize){
    return new Rat(this.loc, this.lifespan + foodsize); 
  }

  // the rat is dead when lifespan is 0 
  public boolean isDead(){ 
    return lifespan == 0; 
  } 
} 

Notice that we have two constructors for Rat. One is public and the other is private. The public and private modifiers can be used for a field or method. A public field/method is visible to other classes. A private field/method is only accessible to the class defining the field or method.

The Rat class has two constructors. One is public and one is private. Notice how the public constructor consumes only one argument; the Rat's location. The private constructor consumes a location and a lifespan. Other classes can only use the public constructor. Thus they can only create instances of rats that start with a lifespan of 10 units. The only class that can create rats with arbitrary lifespan is the class rat. This stops other classes from being able to create rats with arbitrary lifespan and gives us one point of control for the creation of rats.

The Temperature Example

All the code for this part of the lecture can be found here.

Lets try this out for our Temperature class.

  
public class Temperature { 
  private double deg; 

  // Create a Temperature given degrees in Fahrenheit
  public Temperature(int degF){
    this.deg = (degF - 32.0)/1.8; 
  } 

  public Temperature(int deg, char c) { 
    this.deg = deg; 
  } 

  //produce temperature in Fahrenheit
  public double getF(){ 
    return (this.deg * 1.8) + 32.0; 
  } 

  //produce temperature in Celcius 
  public double getC() { 
    return this.deg; 
  } 

  public int compareTo(Temperature t){ 
    return (int)this.deg - (int)t.deg; 
  } 
  public String toString(){ 
    return 
      new String("Temperature: " + this.deg + "\n"); 
  } 
 } 

Note: You have already used static methods (or otherwise known as class methods). Recall Math.sqrt(x) where x is an int. The method sqrt is a class method.