The JVM is very good and gc is very efficient

– But it’s a bit worse than.NET from a VM perspective.

– Such as tail-recursive optimizations and true generics that do not support cross-recursion.

Of course this is a legacy of history, Java didn’t have generics at first, and then encountered all sorts of inconveniences like Go, so Java 1.5 or 1.6 was added. For compatibility purposes, the JVM is essentially generics free, and generics now rely on runtime strongholds.

For example, Scala, a language on the JVM, writes case X: Vector[Int] when pattern matching to match all vectors. But Java syntax is a long way off, interface is not strong enough, and you can only rely on frameworks like Spring for dependency injection.

There are no language level delegates and properties, so getters and setters are written, not public string Name {get; set }; , generics do not have covariant inverters. You can only write public List<? extends U> map(Function<? super T , ? Extends U> f) this sort of thing.

In addition, there is no Algebra Data Type, and it is difficult to deal with Data types like Option and Tree. Path-dependent type is not supported. X.new xs() is used to instantiate the inner class.

Java has a poor object-oriented design and is static based on classes.

It’s like Simula and SmallTalk and pure-oo.

Static has some limitations, so there is a singleton pattern.

Look next door at Scala’s Object design, where you can override super class methods, to see how bad Java’s object system is.

Also, Java does not support operator overloading, so BigInt/BigDecimal is just a little short of native numeric types.

For example, x.plus(biginteger.zero), Scala can write x + 0.

Also, Java separates primitive types (such as int short byte double) from reference types (defined by class), and generics cannot take arguments to primitive types. Scala is no different and is implemented using native types on the JVM.

So Scala has Vector[Int], and Java uses the wrapper type Integer.

And Java’s == checks for the same address for primitive types and the same address for reference types.

So even if obj1 and obj2 are the same, obj1 == obj2 may yield false.

This results in Java objects using obj.equals(x) to determine equality. Scala just needs obj1 == obj2.

To determine reference equality, use obj1 eq obj2.

Java is abstract by design, but it can’t abstract well.

Interface does not solve the multiple inheritance problem and has many limitations.

Scala solves this problem well with linearization.

There is no ad-hoc polymorphism, no Type class, and no implicit, so the abstraction ability of the language is not enough.

There is no type class in Numeric[T] (Scala), for example, for Stream and IntStream.

So we’re going to have a separate Stream with sum. Scala’s sum is def sum(implicit ev:Numeric[T]) = this.foldleft (ev.zero)(ev.plus).

And Java’s type system isn’t good enough to support HKT, so there’s no such thing as trait Monad [M[_]].

So Java’s abstraction is inadequate, and the last hope of implementing Type class is gone.

The Java ecosystem and community is good. You can experience this for yourself.

But Java 8 Stream is much worse than Scala, without reduceLeft/Right and foldLeft/Right.

Java is not going to die, it will only improve, and may eventually become C#++ on the JVM.

JDK 14’s Switch Expression and Record changes a lot. Make the language more powerful and the JVM will not die, with Kotlin and Scala supporting it, even if Java is in the garbage JVM, it can survive on ecology and other languages.