The only category I would say is objectively "better" is Java (and other JVM languages like Kotlin) has the best IDEs out of _any_ language. There is nothing on-par for Python, Ruby, C/Cpp, etc as far as autocompletion, type inference, automated refactoring, partial recompiles, hot code swapping, unit test integration, Tomcat or server integration, code formatting, etc. Java is great because of it's IDEs. If we were writing Java in notepad.exe it'd be a pretty clunky experience, but nobody serious does that.
Kotlin was made by the people behind IntelliJ. I'm not sure I understand your point.
Makes switching to vscode feeling going from an framing nailer to beating in rusty iron nails with a rock.
One feature that comes to mind is rearrange methods. I haven’t used Java in years so someone who actually uses both can find more cases where Kotlin support is worse than Java’s.
Valhalla might. There's been a lot of time put into nulls and primitives for valhalla. How that manifests seems to be up in the air still. But I would not be surprised to find non-nullable types in the future (like `int`).
I don't think this is something that will only apply to null safety objects. Maybe initially, but I don't see there being major restrictions to extending this to other classes.
And that, in turn, answers the question of where Kotlin is different enough from Java that it might be worth switching.
Java is very strict on backwards compatibility. That has its advantages. But it also has a lot of downsides. Kotlin doesn't want/need to be backwards compatible, and it can afford to break semantics enough to make null not allowable(*) in non-nullable types.
It's a trade-off, of course. But it explains why some people prefer Kotlin.
(*) Save for Java interop.
When targeting Java 8, Kotlin creates helper classes for the default methods on interfaces as that is not supported in Java 8, whereas on Java 9+ it uses Java default functions. However, you can target multiple Java versions, as I've done that with my projects for 11/17 support.
(Plus if you're going to go to the trouble of using a non-Java language it would be a real waste to use Kotlin over Scala)
I'm not sure that's a reason to pick Java over Kotlin though – streams are just generally cumbersome, even in Java, compared to Kotlin's sequences. By choosing Java you're getting more exposure to them whereas in Kotlin you can mostly avoid them in favor of sequences.
- Compile speed - Kotlin is still painfully slow - about 4-5 times slower than equivalent Java functionality (rough estimate)
- Kotlin is completely proprietary, no community or openness, no alternative to Jetbrain's IDE
- Kotlin (for me) has passed from being a simpler/more terse/attractive version of Java into being a complex language with lots of strange corner cases. Particularly largish Kotlin code-bases can be hell to read as Kotlin supports and encourages a DSL like approach.
For Android? Kotlin all the way.
Servers? Java is much closer to the JVM and Kotlin is an abstraction over it which honestly does not provide much extra compared to Java 21.
Kotlin has several design decisions which are neater 80% of time, but Java is the better general purpose language for those 20%. OC, much is a matter of taste. And in Kotlin will find yourself in awkward positions where you have to use Java Collections and you lose all the advantages of Kotlin (HashMaps not having non-nullable values is one of them).
Java has come a long way in recent years and it's far better than it was in the past. Kotlin stands on the great baseline of features that Java has established and there are certainly many advantages to (mostly ergonomics), to using Kotlin over Java at least for the time being.
Not sure about that example since it’s easy to do
class MapWithDefault<K, V>(
private val map: Map<K, V>,
private val defaultValue: V
) {
fun get(k: K) = map.get(k) ?: defaultValue
// …
}
Edit: or I’m sure there’s something that does this already.<R> R collect (Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
The fact that you see a type like Comparable<T> and can implement it with an anonymous function is unique to Java. In other languages you would have Function<Int, T, T> which simply baffles the mind. Java has a powerful and performant model which also benefits from static and default methods (e.g. functional composition).
Granted, the collect endpoint looks pretty wild but in 99% of cases you will simply use ".toList" or Collectors.toSet().
(T) -> R
vs
Function<T,R>
Maybe Kotlin lacks implementing interfaces functionally but in practice, I can't remember it being a pain point. Funtional programming in Java to me, still feels very clunky compared to scala and kotlin.
When WebAssembly/gc becomes generally available, it will be exciting to see which languages will successfully target it. Kotlin has quite good chances of succeeding as a full-stack language, because of its growing ecosystem of multi-platform libraries that work in the JVM, in a browser, and on a native system. Note that the Java standard library is great, but it does not interoperate well with the JavaScript ecosystem. The same goes for C# and Rust.
As a personal pet project, and to check if the multi-platform concept actually works, I have written an emulator for an old computer in Kotlin. Initial development was done in the JVM, because I was familiar with JavaFX and sound output in Java. I then later compiled it to JavaScript, which was trivial and required no code changes, except for an implementation of canvas and audio rendering.
A serious problem is the IDE experience though. Interactive web development in VSCode with TypeScript is a breeze, with a subsecond edit-compile-run cycle. Kotlin compilation is still clunky, and fixing a typo requires at least 10 seconds of my patience, even on a decent workstation. Work is underway to improve this, but only time will tell if things succeed.
Or perhaps I'm missing some sarcasm here :)
Then there's other people like me who come from other ecosystems. I used to write Ruby and I just can't stand how verbose and convoluted Java can be. It makes reading code a real pain. But with Kotlin I found a language that struck a good balance between expressivity and compile-time safety.
Maybe Kotlin should do more to convert people from other ecosystems rather than trying to cater to the kinds of people who were never offended by the dullness of writing Java.
I understand that, but for starting new projects the equation can be different.
> many of us don’t struggle with null
Sorry, but I don't believe there's anybody like that. I guess many people accepted/internalized the struggle.
Anything relying on that is brittle. I work on a 20 years old code base with the majority of the base classes being 15+ years old. This is exactly where Java should shine, and it's not doing bad, but it's very hard to enforce top-notch coding standards across ~200 devs committing during that time frame.
As a result, there isn't much of an information on the nullability of return types, and you end up defensively handling nulls everywhere which is just noise.
> Last time I saw uncaught NPE in server logs was a long time ago
Luckily these are rare also for us, I guess most are caught during development and then on some test environment / CI, but catching these anytime later than compilation is way too late. The cost of a bug is directly connected with time between introduction and fixing it.
I don't think for new projects the equation is any different. Why would I throw away a language and ecosystem that I understand, that I'm productive in and with language designers that I trust to do the correct thing for the future? Why go learn another language and ecosystem when the things it brings to the table are of no value _to me_ ?
I honestly do not struggle with null. If you actually read the code you call and test the code you write you won't have a problem.
The language is conceptually very similar and ecosystem is shared to a large degree.
> I honestly do not struggle with null. If you actually read the code you call and test the code you write you won't have a problem.
Looks like you work on small projects only. If you can just read the code you call, then you don't need much of a type system at all.
In my case, the call I make often executes 10,000s of lines of code and figuring out if it can return null in some cases can take hours or days.
Ideally everything should be non-nullable, with optionally specified nullability.
Of course, in the type system.
Just like we're "documenting" types themselves in the type system and not in JavaDocs.
Don’t be dismissive. I could easily just say “looks like you’re just a bad dev if you can’t handle null.”
I work on large code bases too. There’s nothing special about them.
If a function in a null-safe language is declared to return "String", then it signals that, effectively, you don't have to check for null. So, yes, compiler-enforced null safety does reduce the amount of nulls you need to check.
The trick, of course, is to not make everything nullable, but as few things as possible.
In Kotlin, of course, this is only true up to the point where Java interop might interfere with it (because Kotlin might infer a Java return type to be non-nullable when it's in fact nullable).
A notable exception is some kind of generated objects, from say, wsdls getting transformed to different dtos, but in this case MapStruct is a godsend, and is overall less error-prone than doing it even in a null-safe language. (Manually copying fields won’t tell you if a new field is added that should also be copied, for example).
Also, all hail `val`; `final var` is objectively worse
In Kotlin one can get finality via the much nicer "val" keyword, and seems to be idiomatically the default, versus Java's mutable-by-default stance
Saying either language "won't catch type errors" is not something I can agree with
final var addressMap = getAddresses();
final Tuple args = Tuple.of("a", 200L, x);
So you may as well inline the method call and give up on any type checking.Another example:
final long limit = getLimit();
for (var i = 0; i < limit; i++) {}
The wrapping behavior is determined by whether there is an L (or lowercase, l) after the 0. If limit happens to be greater than Integer.MAX_VALUE, that L is the difference between an infinite loop and a well-behaved routine. Some junior programmer could easily remove the L accidentally. And you wouldn't run into the bug until limit gets big enough. Which could happen at runtime in production. All because you wanted to save one character by using var instead of long.This is just what I could think of off the top of my head. I'm sure there are other examples. You are absolutely giving up some compile-time sanity checks with var. If it's just some throwaway script, then fine. But if so why use Java in the first place?
But that’s a completely useless code. Here is a real example:
var list = getSomeFancyLongAssNestedGenericTypedList();
list.append(new HashMap<…>());
If I change the return type of that function to something that doesn’t have a compatible append method, your method will fail. In fact, you implicitly encode all its usage in that var, so it will be maximally safe, while not being more restrictive than needed (e.g. changing to another object that has append may be fine).Also, var is not a must-use, there are useful cases where it gives more clear code and the type is not important (say, when the right side is a constructor call), and there are cases where you really should be annotating your types. But it will be type safe either way.
Sure, you might append to the list. But in the world of increasing immutability, more likely the return value of getAddresses would be immutable. You don't want to rely on "well hopefully there's a method call that catches the error before it goes into the args list."
What did I gravely misunderstand? And if I gravely misunderstood it, then why can't you point out what's wrong with both of my examples instead of running as fast as possible to your own toy example? I'm actually laughing at your totally shameless pivot.
>maximally safe
You don't know the definition of maximum. Maximum means there exist no levels which exceed it. You have amnesia about the type checks that I just demonstrated which do not occur with var that would have prevented the runtime problems I just explained.
If your argument is that, meh, var is "good enough" type checking, then make that argument. Don't pretend like you can't read basic Java code.
I gave a more realistic example where you actually make use of your objects
Secondly, it's not unrealistic. It's not even realistic. It's real. R-E-A-L real. It's in production systems right now, as I said. Modern startups. If you think you can design it better, by all means. "Put up or shut up" is the phrase, I think.
Thirdly, any time you have an API that accepts a supertype of the instance in question, this will happen. Run away and hide from the truth. Or man up and admit you oversold your incorrect theory of var.
Fourthly, you didn't even address the readability issue. Which of course you wouldn't because... var. Like literally VAR. lol.
Fifthly, you still haven't come up with an answer as to what I misunderstood, specifically. Sad!
That’s a feature, not a bug.
> We could eliminate suspend modifiers from pure-Kotlin code, but should we? I’m inclining to answer no. Having to mark asynchronous functions with suspend modifier is a small price to pay, but in return you get better insight into your code. You immediately see which functions are allowed to perform potentially long communications and which are supposed to complete quickly.
I'm not sure what's on the Kotlin roadmap / whether JetBrains see value in making major improvements to the language.
1) Cloud is better than data centers without sacrifices
2) Meal kits are better than grocery shopping without sacrifices
3) Online shopping is better than going to store without sacrifices
4) SAAS is better than locally installable software without sacrifices
I would not start Kotlin or learn it unless you go back 5 or more years.