Type safety

Consider the method findByDate that executes a query to find something by the year and month. One way of implementing this method in Java can be with two parameters year & month, both of type int. But this has some problems with passing valid arguments to methods and statically type checking.

public function findByDate(int year, int month) {
  // do stuff
}

When a method like this (by accident) is called as findByDate(10, 2014); the code will compile without problem but the executing of will yield no result or fails with some kind of runtime error. Finding the exact cause can be a problem because there are no signs that something is wrong.

Argument checking

To solve this the method could implement some argument checks to check if the year and month makes some sense to use in a query. A year and month check can be implemented like this with an IllegalArgumentException.

public function findByDate(int year, int month) {
   if (year < 1900 || month > 3000) {
      throw new IllegalArgumentException("Invalid year " + year);
   }

   if (month <= 0 || month > 12) {
      throw new IllegalArgumentException("Invalid month " + month);
   }

   // Rest of the code
}

When we execute this method with this check at runtime an exception will be thrown and when this exception is not catched the program will terminate. This will indicate a problem to the developer. But this solution has a few problems in validation, readability and changeability:

  • The findByDate method has to implement the validating logic to check if the year and month are valid.
  • For every of those “query” methods this check has to be executed.
  • When a parameter is changed all implemented validation code has to be changed, this is a direct result of copying the logic around.

Safe type

Defining a special Year/Month type can tackle this problem. We define a different class YearMonth that defines this type and implements the special checking for the year and month.

public class YearMonth { 
   private final int MIN_MONTH = 0;
   private final int MAX_MONTH = 12;

   // Default year / month
   private int year = 1900; 
   private int month = 1;

   public void setMonth(int month) {
      if (month <= MIN_MONTH || month > MAX_MONTH ) {
         throw new IllegalArgumentException("Invalid month " + month);
      }
   }

   // Set year etc.. 
}

The class YearMonth will never accept a month out of the reach, and when a invalid month is supplied an Exception will be thrown indicating something is wrong.

This makes the implementation of findByDate a lot more simply, the method findByDate can now rely on the fact that ym is always a valid YearMonth instance. In a sense the constraints make sure the class invariant will be enforced.

public function findByDate(YearMonth ym) {
   // Rest of the code
}

Other languages

The subtype declaration of Ada solves this problem by explicitly defining the range of the type and naming this subtype. The compiler will statically check if the use of this type will satisfy this type everywhere.

subtype Month is Integer range 1 .. 12;
subtype Year is Integer range 1900 .. 3000;

Conclusion

With introducing a special type instead of a primitive type in method calls we can ensure the arguments passed to a method are valid and within range. This simplifies method definitions because a lot of argument checking is placed in the special type class and the method can rely on receiving valid arguments.