7 April 2022

Mastering command-line with factory pattern in three JVM languages

The goal of this blog post is to examine a program selection control mechanism and the command-line program execution. Yes, only the command-line is used for the purpose of this post. Let us start from the beginning. What is a selection control mechanism? It allows a value, expression or variable type to influence the program flow execution. This concept is commonly used by developers. A good example for this is a widely used design pattern. A design pattern that belongs to the family of creational patterns and is used for an object creation without exposing the entire logic.

A careful reader may already have identified which pattern. Yes, it’s the Factory design pattern and it will be used in the example code. Let us consider a simple factory that produces different vehicle types. The used types are “Car”, “Bus” and “Truck”. Each inherits from the interface “Vehicle” (Img 1.)


Img 1.:: Producting an desired vehicle instances with naive vehicle factory example

Let us implement such a naive vehicle factory in 3 different JVM languages:

  • Java 18 with enabled preview feature “Pattern Matching for switch” (Ref 2., Example 1.)
  • Kotlin 1.6.10 (Example 2.)
  • Scala 2.13.8. (Example 3.)

The comparison allows us to observe the verbosity of the implementation in those languages. To reduce the displayed code we only show the method “createVehicle” as this method is in focus for this blog post. The post follows a trivial flow in all three cases. The code is compiled and executed by the command line. We observe the result of the method “createVehicle”. Let us explore it case by case.

1. Java “switch” statement implementation

# compilation
$ javac --release 18 --enable-preview -g -classpath out -sourcepath java -d out ./java/*java
# execution
$ java --enable-preview -cp out VehicleFactory

Example 1.: Compile and execute the “VehicleFactory” code, related to Example 2.

private static Vehicle createVehicle(String type) {
  return switch(type){
     case"CAR"->new Car();
     case"BUS"->new Bus();
     case"TRUCK"->new Truck();
     default-> throw new IllegalArgumentException("""
               illegal type: %s """.formatted(type));
     };
}

Example 2.: “VehicleFactory”, method “createVehicle” in Java - “switch” statement

2. Kotlin “when” control expression

# compilation 
$ kotlinc ./kotlin/*.kt -include-runtime -d ./out/vehicle_factory.jar
# execution
$ kotlin -classpath ./out/vehicle_factory.jar KVehicleFactoryKt

Example 3.: Compile and execute the “KVehicleFactory“ code, related to Example 4.

fun createVehicle(type: String): KVehicle = when(type){
   "CAR" -> KCar()
   "BUS" -> KBus()
   "TRUCK" -> KTruck()
   else -> throw IllegalArgumentException("illegal type:$type")
}

Example 4.: “VehicleFactory”, method “createVehicle” in Kotlin - “when” expression

3. Scala “match” statement

# compilation
$ scalac -d out ./scala/*.scala
# execution
$ scala -cp out SVehicleFactory

Example 5.: Compile and execute the “SVehicleFactory” code, related to Example 6.

def createVehicle(vehicleType: String): SVehicle = vehicleType match {
   case"CAR" => new SCar
   case"TRUCK" => new STruck
   case"BUS" => new SBus
   case _ => throw new IllegalArgumentException(f"illegal type: $vehicleType")
}

Example 6.: “VehicleFactory”, method “createVehicle” in Scala - “match” mechanism

Conclusion

The vehicle factory has been implemented in three different JVM languages: Java, Kotlin and Scala (see examples 2., 4., 6.). It seems that commonly used critique about the verbosity in Java is not really valid in this example. Actually, each of three different implementations look very similar.

The example shows the usage of Java TextBlocks (Ref 5.), delivered in the Java 15 release. We can actually notice that Kotlin or Scala provide quite neat constructions for the texts, Java is a bit behind as to this aspect. On the other hand, it is fair to say that Exceptions in Java are a bit more specific than in Kotlin or Scala. Java produces a much more compact StackTrace (Example 4.) which may influence the application performance.

Working example and additional information is available on this GitHub account (Ref 1.) - GitHub

// force 'VehicleFactory' to cause an exception
vehicle = createVehicle("BLA")

1. Java StackTrace
Java 18, vehicle factory example...
Exception in thread "main" java.lang.IllegalArgumentException: illegal type: BLA
  at VehicleFactory.createVehicle(VehicleFactory.java:37)
  at VehicleFactory.main(VehicleFactory.java:26)


2. Kotlin StackTrace
Kotlin, vehicle factory example
Exception in thread "main" java.lang.IllegalArgumentException: illegal type:BLA
   at KVehicleFactory$Companion.createVehicle(KVehicleFactory.kt:24)
   at KVehicleFactoryKt.main(KVehicleFactory.kt:36)
   at KVehicleFactoryKt.main(KVehicleFactory.kt)
   at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
   at java.base/java.lang.reflect.Method.invoke(Method.java:577)
   at org.jetbrains.kotlin.runner.AbstractRunner.run(runners.kt:64)
   at org.jetbrains.kotlin.runner.Main.run(Main.kt:176)
  at org.jetbrains.kotlin.runner.Main.main(Main.kt:186)


3. Scala StackTrace
Scala, vehicle factory example
java.lang.IllegalArgumentException: illegal type: BLA
    at SVehicleFactory$.createVehicle(ScalaVehicleFactory.scala:35)
    at SVehicleFactory$.main(ScalaVehicleFactory.scala:27)
    at SVehicleFactory.main(ScalaVehicleFactory.scala)
   at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
   at java.base/java.lang.reflect.Method.invoke(Method.java:577)
   at scala.reflect.internal.util.RichClassLoader$.$anonfun$run$extension$1(ScalaClassLoader.scala:101)
   at scala.reflect.internal.util.RichClassLoader$.run$extension(ScalaClassLoader.scala:36)
   at scala.tools.nsc.CommonRunner.run(ObjectRunner.scala:30)
   at scala.tools.nsc.CommonRunner.run$(ObjectRunner.scala:28)
   at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:45)
   at scala.tools.nsc.CommonRunner.runAndCatch(ObjectRunner.scala:37)
   at scala.tools.nsc.CommonRunner.runAndCatch$(ObjectRunner.scala:36)
   at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:70)
   at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:91)
   at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:103)
   at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:108)
   at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

Example 4.: Vehicle factory causes exception with “StackTraces

Note: This article is an extension to the already published article (Ref 6.)

References

  1. GitHub jvm-language-examples
  2. JEP-420, Pattern Matching for switch (Second Preview)
  3. Kotlin: when expression
  4. Scala: Pattern matching
  5. JEP-478, Text Blocks
  6. Examining the Switch Statement and the Factory Pattern in Three JVM Languages, Foojay.io, 2022

Miro Wengner

Miro is a member of the JCP program for very long time. He contributes actively to the OpenJDK, Java Mission Control/Flight Recorder project. His focus is on java performance and maintainability. Miro has also contributed/is contributing to various another open-source project such as OpenTracing, Pi4J and etc. He is also co-author of Robo4j project which has been awarded by DukeChoice Award 2017. Miro has been recognized as JavaChampion, RockStar speaker. Aside of his daily duties as a Principal Engineer at OpenValue he shares his knowledge over conferences (JavaOne, CodeOne, Devoxx, GeeCON etc.) and blogging. Miro has been Elected to Java Community Process (JCP) - Executive Committee to help guide the evolution of Java technologies.