Thursday, January 29, 2015

Java vs Groovy : switch blocks

This post is about highlighting the difference between Java's switch statement and the one that's in Groovy

Just as a reminder, according to The Java Tutorials' article titled The switch Statement: "A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types, the String class, and a few special classes that wrap certain primitive types: Character, Byte, Short, and Integer."

Both Groovy and Java are case sensitive when it comes to matching a String in a switch block.
@Test
public void case_statements_are_case_sensitive() {
   String str = "Hello";

   switch(str) {
      case "hElLo":
         break;
      case "Hello":
         return;
   }

   Assert.fail();
}

According to Groovy's documentation on Logical Branching, when it comes to switch statements "[the default case] must go at the end of the switch/case...” Actually this is not entirely true as this simple example demonstrates:
@Test
public void default_block_with_body_allowed_at_start_of_switch_block() {
   switch("Hello") {
      default:
         return;
      case "World":
         break;
   }

   Assert.fail();
}
What you're not allowed to do is have a default case with an empty body at the start of the switch block. (It is allowed in Java though.)
@Test
public void default_block_with_empty_body_allowed_at_start_of_switch_block() {
   // this does not work in Groovy, but does in Java

   switch("Hello") {
      default:
      case "Hello":
         return;
   }
}

Groovy allows you to specify a regular expression for the case to match.
@Test
public void regular_expression_allowed_as_case_statement() {
   switch ("aaaab") {
      case ~/a*b/:
         return;
   }

   Assert.fail();
}
See Also: Groovy's documentation on Regular Expressions

Groovy allows you to specify a Collection for the case and it will check if the Collection contains the item specified by the switch value:
@Test
public void can_check_if_item_is_in_collection() {
   String str = "Hello";

   Collection<String> collection = new ArrayList<string>();
   collection.add(str);

   switch(str) {
      case collection:
         return;
   }

   Assert.fail();
}
You cannot, however, specify a Collection as the switch value and then check if it contains a given item.
@Test
public void can_check_if_collection_contains_item() {
   // this does not work in either Groovy or Java

   String str = "Hello";

   Collection<String> collection = new ArrayList<String>();
   collection.add(str);

   switch(collection) {
      case str:
         return;
   }

   Assert.fail();
}
If you tried to do either one of the above in Java you would get a compiler error stating that the switch and case statements have incompatible types.

Groovy allows you to both test if the switch value is in a range...
@Test
public void can_test_if_switch_value_in_range() {
   switch(2) {
      case 1..10:
         return;
   }

   Assert.fail();
}
...and if a specific case value is within the range of the switch value.
@Test
public void can_test_if_specific_case_is_within_given_range() {
   switch(1..10) {
      case 2:
         return;
   }

   Assert.fail();
}
Java does not have a built in syntax for working with ranges. Therefore the above two examples would not work in Java.

Groovy allows you to test if the case value's type is an instance of the type used for the switch value.
package stuff;

public class Parent {
}
package stuff;

public class Child extends Parent {
}
@Test
public void can_test_if_case_is_instance_of_specific_type() {
   switch(new Child()) {
      case Parent:
         return;
   }

   Assert.fail();
}
The above would result in several compiler errors in Java.

Groovy allows you to specify differing types for the test cases.
@Test
public void can_specify differing_types_for_test_cases() {
   // this works in Groovy, but not Java

   String str = "Hello World";

   switch(str) {
      case 12:
      case 1.23:
      case 'A':
      case “Hello”:
      default:
             return;
   }

   Assert.fail();
}

Groovy allows you to specify a variable for both the switch value and the case statement.
@Test
public void both_case_and_switch_can_have_variable() {
   int i = 300;

   switch (i) {
      case i:
         return;
   }

   Assert.fail();
}
You can even specify a variable that isn't set. That is, it doesn't issue a compiler error or throw an exception when you do so.
@Test
public void both_case_and_switch_can_have_variable_that_is_not_set() {
   String str;

   Assert.assertTrue(str == null);

   switch(str) {
      case str:
      default:
         return;
   }
}
Java, on the other hand, only allows a variable to be specified for the switch value and the variable must be set to an initial value. You cannot specify a variable as the case to match on. Java only allows a constant expression to specified for the case.

You can do something like the following in both Java and Groovy.
@Test
public void case_statements_with_constant_expression() {
  switch(5) {
    case 2 + 3:
      return;
  }

  Assert.fail();
}


Both Groovy and Java allow switch statements to work with enums. In Java, you don't prefix the element name in the case part with the name of the enum.
package stuff;

public enum DaysOfWeek {
   SUDNAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}
@Test
public void java_requires_only_name_of_enum_element() {
   // this works in Java, but not Groovy

   switch(DaysOfWeek.TUESDAY) {
      case TUESDAY:
         return;
   }

   Assert.fail();
}
Groovy, on other hand, requires you to specify both the name of the enum and the name of the element you're matching on.
@Test
public void groovy_requires_both_enum_and_element_name() {
   // this works in Groovy, but not Java

   switch(DaysOfWeek.TUESDAY) {
      case DaysOfWeek.TUESDAY:
         return;
   }

   Assert.fail();
}
Something that Groovy's switch can do that Java's can't is match on the name of an enum element.
@Test
public void can_match_name_of_enum_element() {
   DaysOfWeek day = DaysOfWeek.TUESDAY;
   String name = day.name();

   switch(day) {
      case name:
         return;
   }

   Assert.fail();
}
You cannot, however, match the ordinal value of the enum element. The following is still valid code in Groovy but it fails the test.
@Test
public void can_match_ordinal_value_of_enum() {
   // this does not work in Groovy

   DaysOfWeek day = DaysOfWeek.TUESDAY;
   int ordinal = day.ordinal();

   switch(day) {
      case ordinal:
         return;
   }

   Assert.fail();
}

As pointed out in Groovy's documentation on Logical Branching, all case blocks in a Java switch statement share the same scope.
@Test
public void all_cases_share_same_scope_in_java() {

   // this is valid in Java, but not Groovy

   switch("Hello") {
      case "Bonjour":
         int x = 1;
         break;
      case "Salut":
         x = 2;
         System.out.println("x = " + x);
         break;
      case "Hello":
         x = 3;
         System.out.println("x = " + x);
         break;
   }
}
Groovy is different in that each case block has its own scope. So if you tried to do the above in Groovy it would result in a thrown MissingPropertyException. The following is the correct way to do this in Groovy.
@Test
public void each_case_is_given_own_scope_in_groovy() {

   switch("Hello") {
      case "Bonjour":
         int x = 1;
         break;
      case "Salut":
         int x = 2;
         System.out.println("x = " + x);
         break;
      case "Hello":
         int x = 3;
         System.out.println("x = " + x);
         break;
   }
}

Groovy allows you to use a closure for the specific case you're trying to match on. The closure in the following example is looking for an Integer with an even value.
@Test
public void can_use_closure_as_case_statement() {
   switch (28) {
      case { it instanceof Integer && it % 2 == 0 }:
         return;
   }

   Assert.fail();
}

If the case is a map Groovy will check if it has a key equal to the switch value.
@Test
public void can_specify_key_for_switch_value() {
   def map = ["Ohio": "Columbus", "Kentucky": "Frankfort"];

   switch("Ohio") {
      case map:
         return;
   }

   Assert.fail();
}
You cannot, however, do the opposite. You cannot specify a map for the switch value and then specify a key for the case to match on.
@Test
public void can_specify_key_for_case_statement() {
   // this does not work in Groovy
   def map = ["Ohio": "Columbus", "Kentucky": "Frankfort"];

   switch(map) {
      case "Ohio":
         return;
   }

   Assert.fail();
}

Suppose you had the following user defined class.
class Klass {
}
If you tried to use that class in switch statement it would not work in Groovy.
@Test
public void case_can_be_custom_class() {
   // this does not work in Groovy

   Klass klass = new Klass();

   switch("Hello") {
      case klass:
         return;
   }

   Assert.fail();
}

@Test
public void switch_value_can_be_custom_class() {
   // this does not work in Groovy

   Klass klass = new Klass();

   switch(klass) {
      case "Hello":
         return;
   }

   Assert.fail();
}
There is a way, however, to allow the user defined class to be used as the case value. All you need to do is override the isCase method like so
class Klass {

   public boolean isCase(Object candidate) {
      return (candidate instanceof String);
   }
}
Note: Don't annotate the method with @Override.

Now the following will work in Groovy.
@Test
public void case_can_be_custom_class() {

   GKlass klass = new GKlass();

   switch("Hello") {
      case klass:
         return;
   }

   Assert.fail();
}

No comments:

Post a Comment