The future of Java is to absorb Scala features
We’re less than a week away now from the official release of Java 17, the first Long Term Support (LTS) release of Java since Java 11. On September 14, 2021, the Java 17 runtime environment (JRE) and development kit (JDK) will be available for download and installation.
Reading over the features of Java 17, I become more convinced that the future of Java is to slowly and gradually absorb Scala features.
As you already know, Scala is a programming language for the Java Virtual Machine (JVM). The Scala compiler compiles Scala source to JVM bytecode just like the Java compiler.
Scala was created by Martin Odersky, who was ahead of his time as he worked on the Java 1.3 compiler. He wanted the Java programming language to adopt generics. Finally added in Java 5, generics enable us to use data structures with type flexibility while maintaining strong type checking (with caveats).
For example, in Scala, we might write
var transactions: List[Transaction] = List()
whereas in Java we might write
List<Transaction> transactions = new ArrayList<>();
In either case, the compiler, and by extension the integrated development environment (IDE) if you use one, will warn you if you inadvertently try to add an element of the wrong type to such a list.
Even though we’re not fully protected from type problems at runtime, the compile-time type checking is still a big help.
Scala had generics from the beginning. Odersky wanted Java to have generics starting with Java 1.3, but that had to wait to Java 5. Of course Odersky understood that the evolution of the Java programming language was constrained by backwards compatibility, and that’s still the case.
It is safe to predict that Java will slowly add more Scala features, because it has been adding Scala features already.
Let’s look at a fairly minor but much advertised feature: the addition of var
in Java 10. One thing that gives Java a reputation for verbosity is the perceived redundancy of type declarations. For example,
SomeReallyLongClassName obj = new SomeReallyLongClassName(param);
Auto-complete in an integrated development environment (IDE) like IntelliJ IDEA really helps, but still, having to repeat long class identifiers has tended to make Java programmers give instances very short names like obj
and param
, or worse, single letters.
With var
, the example above becomes
var obj = new SomeReallyLongClassName(param);
Well, in my opinion, that’s not such a big deal. Much more significant was the addition of lambdas and functional interfaces in Java 8, which brings Java closer to Scala’s idea of “functions as first class citizens.”
For example, let’s say that in your local Scala REPL, you have an IndexedSeq[Int]
containing integers of the form n² + n + 1, such as 3, 7, 13, 21, 31, 43, 57, 73, 91, etc., as res13
.
You want to know which of them are prime and you have a Boolean function, isPrime()
, defined for just that purpose.
Then it’s a simple matter of filtering with isPrime()
as the “predicate” (a fancy math term for a Boolean function). Practically writes itself:
scala> res13.filter(isPrime)
res14: IndexedSeq[Int] = Vector(3, 7, 13, 31, 43, 73, 157, 211, 241, 307, 421, 463, 601, 757, 1123, 1483, 1723, 2551, 2971, 3307, 3541, 3907, 4423, 4831, 5113, 5701, 6007, 6163, 6481, 8011, 8191, 9901)
If you don’t have a local Scala REPL, you can try it out in a Scastie snippet like the one I’ve posted.
By the way, the JDK for Java 9 added JShell, a local REPL for Java. You can obtain the same result in JShell without lambdas or functional interfaces, but it would be very clunky. Since those were added in Java 8, they’re certainly available in JShell. It would go something like this:
res13.stream().filter(new Predicate<Integer> { @Override
public boolean test(Integer v) {
return isPrime(v);
}});
There’s a much better way, but it just wasn’t as obvious to me as Scala’s filter(predicate)
syntax (many thanks to Julian, see his September 17 comment for a couple of better options).
A more mundane use of filtering would be to put together a list of accounts with a balance below the minimum balance.
val minBalAccounts = accounts.filter(_.balance < MINIMUM_BALANCE)
I’m picturing _.balance
and MINIMUM_BALANCE
as being of an appropriate type specifically designed for money amounts, and not floating point numbers. I don’t think Java will ever have operator overloading (other than the special case of String
concatenation), but I could be wrong about that.
In any case, there are plenty of other Scala features that Java programmers might feel a greater need for, like more sophisticated pattern matching for Switch-Case statements.
If you looked at the Scastie snippet I linked earlier, you would have seen how I defined the isPrime()
function:
def isPrime(num: Int): Boolean = Math.abs(num) match {
case 0 => false
case 1 => false
case n => (2 to
Math.floor(Math.sqrt(n)).toInt) forall (p => n % p != 0)
}
In Java, you might write that with a Switch-Case with a Return from each Case, and then you wouldn’t have to worry about unintentional Switch-Case fall-through from neglecting to put in Breaks.
You can write more sophisticated Match-Case statements in Scala, such as Cases that match based on type. For example,
urlConn match {
case httpsConn: HttpsURLConnection => // TODO: Process HTTPS
case httpConn: HttpURLConnection => // TODO: Process HTTP
}
You can also put “guards” in Cases. Of course you’ll have to be careful to arrange your Cases from narrow to broad. Your IDE might alert you to unreachable Cases, if the patterns are not too complicated.
It has been announced that Java 17 will add pattern matching to Switch-Case statements, as well as Case guards like in Scala.
Java 17 is also expected to add sealed classes and interfaces, which neatly correspond to sealed classes and traits in Scala. I’ll have to elaborate on this another time.
As for the much maligned semicolons, they will probably stay in Java, since adding semicolon inference is probably a much bigger challenge to backwards compatibility than things like var
.
I believe that Java will continue to be used as long as we continue to use electronic computers. All the while pundits will keep forecasting the impending demise of Java.
Maybe Martin Odersky isn’t too interested in the JVM garbage collector improvements (I know I’m not), but perhaps most of his ideas about how the Java programming language should evolve will all come to fruition, long after he first put those ideas forth in the Scala language.