Running on Java 24-ea+25-3155 (Preview)
Home of The JavaSpecialists' Newsletter

017aSimulating Switch Statements on Object Handles with try-catch

Author: Dr. Heinz M. KabutzDate: 2001-04-26Java Version: 1.1Category: Language
 

Abstract: The try-catch construct can be used for program flow control. In this newsletter we abuse it to simulate the switch statement.

 

Welcome to the 17th issue of The Java(tm) Specialists' Newsletter, after a few very busy days in the Mauritian Paradise. Mauritius is a wonderful place to go to, with extremely friendly people all around, treating you like kings even in business. It's definitely worth a vacation, I wish I had gotten one while I was there ;-)

Please remember to forward this newsletter to anyone who might be interested, friends and foe.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Simulating Switch Statements on Object Handles with try-catch

This week I will talk about a really completely useless idea, how to use switch/case statements on type-safe constants in Java. This idea occurred to me while I was talking to a bunch of programmers about TCP/IP programming, I don't think my topic of conversation had anything to do with the hare-brained idea presented in this newsletter. My listeners saw my eyes take on a distant gaze and I muttered "hmmm, I wonder if..." so here you go.

First I need to bore you with a monologe of why switch statements are bad and why you should never use them. Switch statements herald from a time before we used Object Orientation, Encapsulation and Polymorphism, and were mostly used to write methods which would do different things depending on the type of record we had passed the method. For example, say we had a CAD system, with a triangle, rectangle and circle, we could say:

public interface Constants {
  int TRIANGLE_SHAPE = 0;
  int RECTANGLE_SHAPE = 1;
  int CIRCLE_SHAPE = 2;
}

Without encapsulation, we would then have a struct or class without methods, looking like this:

public class Shape {
  public int type;
  public java.awt.Color color;
}

We would then have a CAD system for drawing these shapes, such as

public class CADSystem implements Constants {
  public void draw(Shape shape) {
    switch(shape.type) {
      case TRIANGLE_SHAPE: // some code which draws a triangle
        System.out.println("Triangle with color " + shape.color);
        break;
      case RECTANGLE_SHAPE: // some code which draws a rectangle
        System.out.println("Rectangle with color " +shape.color);
        break;
      case CIRCLE_SHAPE: // some code which draws a circle
        System.out.println("Circle with color " + shape.color);
        break;
      default: // error only found at runtime
        throw new IllegalArgumentException(
          "Shape has illegal type " + shape.type);
    }
  }
}

This was the old procedural way of writing such code. The result was code where it was extremely challenging to add new types. In addition, in Java such code is very dangerous because we don't have enumerated types and you cannot switch on object references (well, have a look further down on how you actually "can"). You therefore could not be sure at compile time if you had defined the method for all the various types in your CADSystem.

The answer in OO is to use inheritance, polymorphism and encapsulation, the above example would thus be written as:

public abstract class Shape {
  private final java.awt.Color color;
  protected Shape(java.awt.Color color) { this.color = color; }
  public java.awt.Color getColor() { return color; }
  public abstract void draw();
}

public class Triangle extends Shape {
  public Triangle(java.awt.Color color) { super(color); }
  public void draw() {
    System.out.println("Triangle with color " + getColor());
  }
}

public class Rectangle extends Shape {
  public Rectangle(java.awt.Color color) { super(color); }
  public void draw() {
    System.out.println("Rectangle with color " + getColor());
  }
}

public class Circle extends Shape {
  public Circle(java.awt.Color color) { super(color); }
  public void draw() {
    System.out.println("Circle with color " + getColor());
  }
}

public class CADSystem {
  public void draw(Shape shape) {
    shape.draw();
  }
}

Now if we forget to implement one of the draw methods, we'll immediately get a compile-time error. Of course, if we extend Rectangle and forget to implement the draw method we'll get the wrong shape, so a certain level of diligence in testing is still required.

It is possible to take a switch statements and transform it to polymorphism using various refactorings. In previous newsletters I mentioned the book "Refactoring" by Martin Fowler. In case you hadn't noticed, I am a fan (of the book, that is). In that book you can find refactorings to transform a switch/case statement to polymorphism or polymorphism back to a switch/case statement.

So, in the unfortunate case (haha, pun intended) that you want to use a switch-type of construct but you don't want to worry about the anonymity of using int's as type identifiers, how do you do it?

We demonstrate by using a TransactionType class which defines the transaction isolation levels you find in most enterprise systems. The isolation types are None, Read Uncommitted, Read Committed, Repeatable Read and Serializable. The point of this newsletter is not to describe transaction isolations, so I won't go into what they all mean. Rumour has it though, that if you use them without knowing what they mean, you will get a system which doesn't work, HA.

We define a TransactionType superclass with a private constructor, so that it is not possible to construct instances of these types or to subclass it, except from within the type. The constructor takes a name as a description, which can be returned via the toString() method. The reason why the type class has to be Throwable will become clear in the example.

We then make public static inner classes for each of the types, again with private constructors, and make public static final instances of these types in each of the inner classes. The reason why we need classes and instances will also become clearer in the example.

//: TransactionType.java
public class TransactionType extends Throwable {
private final String name;
private TransactionType(String name) {
  this.name = name;
}
public String toString() { return name; }
public static class None extends TransactionType {
  public static final TransactionType type = new None();
  private None() { super("None"); }
}
public static class ReadUncommitted extends TransactionType {
  public static final TransactionType type =
    new ReadUncommitted();
  private ReadUncommitted() { super("ReadUncommitted"); }
}
public static class ReadCommitted extends TransactionType {
  public static final TransactionType type =
    new ReadCommitted();
  private ReadCommitted() { super("ReadCommitted"); }
}
public static class RepeatableRead extends TransactionType {
  public static final TransactionType type =
    new RepeatableRead();
  private RepeatableRead() { super("RepeatableRead"); }
}
public static class Serializable extends TransactionType {
  public static final TransactionType type =
    new Serializable();
  private Serializable() { super("Serializable"); }
}
}

How does such a type help us to make safe types which we can switch on? The answer is that we use a construct which is not really meant to be used as a switch, but which acts as one nevertheless, namely the throw-catch construct. We simply throw the type, which can be a handle to a TransactionType object, and the exception handling mechanism sorts out which catch to call. Yes, I can hear you all groaning now with pearls of sweat caused by fear, but this really does work. For syntactic sugar, we can import the inner classes using "import TransactionType.*" after which we can refer to the inner class simply by their name "ReadCommitted". We can of course also use the full name such as Transaction.None instead of importing the inner classes.

//: SwitchingOnObjects.java
import TransactionType.*;
public class SwitchingOnObjects {
  public static void switchStatement(TransactionType transact) {
    try {
      throw transact;
    } catch(TransactionType.None type) {
      System.out.println("Case None received");
    } catch(ReadUncommitted type) {
      System.out.println("Case Read Uncommitted");
    } catch(ReadCommitted type) {
      System.out.println("Case Read Committed");
    } catch(RepeatableRead type) {
      System.out.println("Case Repeatable Read");
    } catch(TransactionType type) {
      System.out.println("Default");
    }
  }
  public static void main(String[] args) {
    switchStatement(TransactionType.None.type);
    switchStatement(ReadUncommitted.type);
    switchStatement(ReadCommitted.type);
    switchStatement(RepeatableRead.type);
    switchStatement(Serializable.type);
  }
}

Try it out, the exception handling mechanism works quite well for this. There are a few pointers you have to follow if you want to use this:

  1. Don't ever catch "Throwable" as the default case. You should rather catch the type base class, such as TransactionType. Otherwise you run the risk of catching RuntimeException and Error classes, such as OutOfMemoryError.
  2. Make sure that ALL the types are public inner classes of the type base class.
  3. Make sure that all the constructors are private.
  4. Lastly, rather use polymorphism to achieve this effect. Switch/Case code is really messy to maintain and very error- prone.

I've already donned my asbethos suit for the criticisms from hard-nosed C/C++ programmers who think switch/case is great and from weeny Java purists who think that switch/case is completely unacceptable. Flame away...

Until next week, and please remember to forward this newsletter in its entirety to as many Java users as you know.

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '24 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...