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
- GitHub jvm-language-examples
- JEP-420, Pattern Matching for switch (Second Preview)
- Kotlin: when expression
- Scala: Pattern matching
- JEP-478, Text Blocks
- Examining the Switch Statement and the Factory Pattern in Three JVM Languages, Foojay.io, 2022