Kotlin Features I miss most in Java
blog.simon-wirtz.de
blog.simon-wirtz.de
The problem is that while Java is newline-agnostic (newline is just another whitespace), Kotlin has some rather difficult rules to determine the what a newline does.
Granted, the rules are not quite as insane as those of Javascript, but I find it very unsatisfying newlines are sometimes significant, and sometimes not.
And just to make it clear where I'm coming from on this topic, my preferred language is Common Lisp, so I really prefer consistent syntax.
However - occasionally present or absent semicolons means I have to think about each situation - at least to a small degree.
template <class T>
struct Optional {
T value;
bool hasValue;
Optional():empty(false) {}
Optional(const T& value):value(value), empty(true) {}
// Also T move ctor, copy ctor, etc.
};
And I try to make sure that all my structs' "empty" state can be set by memset(0). Pointers are a bit tricky, especially strings, but for them I kind of like this: class MyString {
MyBuffer *buf; // Pointer to refcounted shared storage, i.e. CoW string
public:
const char *cStr() {
if (!buf || !buf->chars) {
return "";
} else {
return buf->chars;
}
}
};
In short,1. Try to design structs such that they can be memset(0).
2. Try not to return NULL pointers, if possible. Maybe keep static empty instances to return instead of NULL pointers.
Of course, the overhead of that approach is not always acceptable, but when it is, I find that it significantly simplifies the code that uses such classes.
And then it fails to mention so much of the very best stuff in Kotlin: Extension methods (No, Java 8's default methods are not a substitute.), unifying the type system, covariance and contravariance instead of those wacky wildcards, kicking checked exceptions to the curb. . .
* unsigned ints. Even Java's 8 bit byte is signed??!
* operator overloading, mostly for math like matrices, vectors, complex numbers
* containers of primitives
* ability to write a "swap" function (pass by reference)
* compiler that turns readable code into good assembly (no verbose code needed due to simpler code introducing stray stringstream objects and things like that, ...)
* destructors that reliably do something at end of scope
* no need to write typename twice to create an object (altho C++ std lib on the other hand requires writing container name twice for begin and end for sort and other algos and has horrificly inconvenient number to string conversions so that part I don't miss)
* ...
There is a library (GNU Trove) that provides specialised containers for primitives. It'd be nice if the language had them built in, but they're only quite rarely needed it seems. Normally there's some wrapper type involved.
Java has scoped 'destruction' (called try-with-resources).
An equivalent for 'auto' is coming to Java, though Kotlin has it already.
Java's try-with-resources (and Kotlin's equivalent, .use()) are a start, but C++'s (and Rust's) ownership model is stronger.
Java's model works fine when the resource doesn't have to escape the try block (and when you never forget to use the try block). But if you have a resource which might or might not escape the block (that is, its ownership might be to be passed to the caller, or to a separate long-living object), you have to do it manually, and if you're not very careful, you can have places where a stray exception leads to a resource leak. (Yes, this bit us recently at work, both the stray exception and the "another programmer didn't call .close()". Workaround for the later: finalize()...)
In "modern" C++ style (which avoids manual new/delete most of the time, instead using unique_ptr and similar), ownership transfer is easier to make reliable: you can move the ownership into the return value or into another object, without leaving any gaps where a stray exception could leak the resource. And the default is to have ownership, so less chance of leaks due to forgetting to use a try block. Rust makes it even easier to avoid such leaks (outside of reference cycles, unsafe code using raw pointers, and the "leak it on purpose" mem::forget function).
This is nicer than manually writing `finally` blocks, but it's nowhere near as nice as RAII-style destructors, which you can't accidentally leave out.
Not everyone is writing C++ as they should.
They took out operator overloading deliberately when they designed Java because in practice people mostly use it to obscure what's happening instead of making things more clear.
It's one of those things that as a developer you'd never use yourself but when you need them they're extremely handy. I'd loved doing matrix math in C++ -- I could translate complex mathematical expressions directly into code with almost no translation.
> * operator overloading, mostly for math like matrices, vectors, complex numbers
> * ability to write a "swap" function (pass by reference)
These were in from the beginning
> * containers of primitives
Generics (in the proper sense, not the Java sense) came in C# 2.0/.NET 2.0 in 2006. With those you can make a List<T> and have it backed by an array of primitives that is consecutive in memory. That is, if you make a List<int> it's not a list of some boxed Integer type.
> * compiler that turns readable code into good assembly (no verbose code needed due to simpler
A bit subjective, but I'll argue that the JIT is getting better and is still nowhere near as good as an ahead-of-time compiler that can spend time optimizing the code. The JIT has to be FAST first and foremost since it produces its assembly WHILE the code is already running.
> * destructors that reliably do something at end of scope
There are no destructors and the only deterministic destruction is disposal (via IDisposable). Objects are not freed at that point however. You only use it to free resources, such as deterministically closing a file. The managed object representing the file will sit in memory until the next GC.
The very bleeding edge version of C# yet to be finished will contain a lot more low level C++ like features which will make it more ergonommical to use stack memory (Google "Span<T>"). That will approach some of the C++ use cases.
> no need to write typename twice to create an object
Locals can be implicitly typed. So
var pet = new Dog();
Is an allowed statement inside a method. This came in C#3 in 2007.They have been around since 2008.
A lot of non .Net people think that expression trees are just lambdas, but they are so much more powerful.
If you have a method that has a signature:
Find(Expression<Func<Employee,bool>> query) {…}
And you call it by
foo.Find(e => e.id == 5);
That expression is treated as “data” not code. Meaning if your Find command is searching for Employees in SQL Server using Entity Framework, it will be translated to sql that runs on the server. If you’re using Mongo, it will be translated into MongoQuery. When you are unit testing you can mock it out, and process any given query against an in memory List<People>.
If you are crazy enough, you can even move it out into a micro service and parse the expression tree yourself into a query object that you send to an API.
That's almost a reason for going back to C++ (or PHP) for me. C# doesn't have it either but interestingly managed C++ does.
The downside is it's entirely opt-in, but it's well known and things like IoC containers can handle it.
1. Its restricted to functions, you cant have an IDisposable member without having to implement IDisposable yourself and there manually close the member. Just allowing an annotation on the variable would have made this so much more ergonomic.
2. The lack of ownership semantics when passing objects as function arguments makes it hard to reason about the program. There is no difference between temporary borrowed reference, ownership transfer and shared ownership so you never know if you are the one that should call close or if one of the functions did or will do in the future.
Kotlin has infix functions which help there without treading the dangerous territory of ambiguousness
Many, maybe most large teams maintaining large code bases end up telling their static analyzers to disallow this sort of thing for this exact reason.
Many ways to do something is easier to write, a single way to do something is easier for future you to read.
This is actually my biggest complaint about Kotlin; it doesn't bring much to the table that is very new. If I'm going to move on from Java I want something more exciting like Ceylon's union & intersection types (which I get to use in... Typescript).
I mean, it's a clever solution and the site/documentation is great. But tossing around annotations everywhere feels hacky as hell, not to mention that it only works with IDEs. I'm sure the combination you mentioned approaches Kotlin in terms of features and QoL improvements, but it's just so much nicer to have these things built into the language. Plus, Lombok is mostly constrained to boilerplate reduction - Kotlin may not be as "crazy" as Scala, but it still has some really nice modern features that java doesn't have/can't have/won't have for a long time.
@Getter @Setter String foo;
attr_accessor :foo
It's unfortunate that Java chose the silly capitalization convention for properties instead of something more structured like C#'s get/set, but that's really an aesthetic complaint.When you're already knee deep in it I suppose you're bound to have a less enticing reason to use the other languages.
That being said, I don't see how you can look at the feature list of Kotlin and play around with it without seeing how it's obviously a much better language.
That's not true. Lombok is an annotation preprocessor. That means it is running with javac which is independent of any build tools or IDEs. As long as it is on the classpath when compiling (-cp flag of javac) it works.
Of course for code completion you need an editor that is aware of Lombok / uses the compiled bytecode for that.
There's a strong need for a language that brings the best ideas recently developed and puts them together in a new language that does not have baggage from the past, (e.g. D).
I've never used Lombok or any other code generator with Java.
I think the important features of Kotlin are:
- Null checking
- Proper lambda/function types
- Not everything has to be a class
- You can specify parameter name when passing to functions: specially useful when constructing objects.
- Simplified constructors (member fields can be specified in the argument list).
These were some of my biggest pet peeves with Java.
That, and the weird stream APIs.
(I used to use Lombok, the checkers framework, and various other pieces. I found Scala gave me all of that and more, for a similar level of effort)
- multiple return values - basically doing what the author is doing here, without introducing a new class for it,
- destructuring - assigning pieces of composite object into several variables (see e.g. destructuring-bind, or what pattern matching does in many languages).
Basically, I often find myself wanting to write this:
return {listWithResults, someBooleanFlag};
or return {listWithOneGroup, listWithOtherGroup};
and then use it like this: List<Foo> matchingResults;
List<Foo> nonMatchingResults;
boolean flag;
{matchingResults, nonMatchingResults, flag} = functionReturningMultipleValues();
or: Foo first;
Foo third;
List<Foo> rest;
{first, _, third | rest} = someList;
etc. def functionReturningMultipleValues() = (List(1), List(2), false)
val (l1, l2, b) = functionReturningMultipleValues
evaluates to: l1: List[Int] = List(1)
l2: List[Int] = List(2)
b: Boolean = false
and val first :: _ :: third :: rest = List(1, 2, 3, 4, 5, 6)
to first: Int = 1
third: Int = 3
rest: List[Int] = List(4, 5, 6)
Of course, if you already are familiar with Common Lisp, Clojure might be a better choice.The fact that Kotlin is a simpler language than Scala is a feature, not a bug.
I haven't really used Kotlin much, so I may be wrong, but that's the impression I get.
Java was described as "blue-collar language". I probably won't call Scala that, but it fits Kotlin fine in my opinion.
another comment on that subject
Unless Scala has radically been pared back in the couple of years since I read about it or I was seriously misinformed at the time, then I'd say it probably a yes.
Scala has the reputation for being at the complex end of the language spectrum. Is that undeserved?
The "feature set" really is smaller than Java or Kotlin (especially if you count in terms of "keywords as features" and ignore things included for Java compatibility).
The flexibility though can be genuinely overwhelming (the name, scala[bility], refers to scalability of the syntax, not performance). Both in terms of the syntax flexibility , e.g. where curly braces are optional vs not or different ways of writing lambdas, and powerful/flexible features like implicit. However there are very rarely special cases to these, it's all very consistent once you know the rules.
Even the infamous underscore is "logically" consistent in it's use, it's just a placeholder in both contexts you typically see it used (context of variables and context of types).
Even my go-to example of a special case in scala, vararg unpacking:
`foo(myArgs: _*)`
Despite being a special case actually reads kind of consistently.. it reads like a type ascription[1] asking for myArgs to be treated as a varargs of whattever type. So consistent with other "type context" usages of underscore despite otherwise being weird.
And the flexibility of the type system and the advanced type inference.
It is fair to say that many Scala codebases are complex and difficult to work on. It's a language that punishes poor choice of libraries much harder than many languages (particularly if people are used to JVM languages - what libraries/frameworks can do in Scala is much closer to what they can do in Python/Ruby/...). I don't think it's "fair" to compare Scala to Kotlin on those grounds, simply because we haven't had time for generations of Kotlin frameworks to build up. But obviously that kind of fairness is not your concern if you're a business making a choice of tools.
If you're choosing a language for a short-lived codebase then Kotlin might be a good choice if it does what you want. What I would say is, if you're trying to choose a language for a codebase that will live for 5-10 years, try to think about what Kotlin is going to look like in 5-10 years. Right now Kotlin is able to be a lot cleaner than Scala (at least on a superficial level) because it doesn't have backwards compatibility with large existing codebases hanging over it. That isn't always going to be the case.
Apart from that I still miss the fact that Iterable doesn't have a `stream()` method, it would make it much more useful in post-java8 world.
Disclaimer: I am a contributor to NullAway. There are plenty of other nullability static analysis tools for Java worth considering (Checker Framework, Eradicate, etc). NullAway's main strengths are a focus on performance and the idea that you should be able to use it as part of every (local) build of your code.
Pragmatics of course necessitate that we sometimes need to tack these static analyzers on after the fact. But over time, the amount of tack ons and bandaids and unnecessary backwards-compatibility-necessitated complexity becomes so high that you're better off using a better language, if you and your team can handle it. For many people, this has already happened with Java. I kind of wish Java the language would just die already and stop adding just enough features to keep people from moving on to the next generation of languages. Science progresses one death at a time.
Sure, but keep in mind switching to a new language also entails a huge investment in tooling. There is definitely a higher cost in switching your build infrastructure, tools and IDE plugins to support a new language (nevermind porting an existing codebase) than the few configuration lines it takes to add an annotation processor. At a certain scale, the tools you need might not have even been built for Kotlin, while they have existed for years for Java.
> For many people, this has already happened with Java.
Yes. And, for many others, it hasn't.
Look, I am not arguing that we should all be writing Java until the heat-death of the universe. Whether it is Kotlin or not, I am sure better ways to do things will pop up. But there are good reasons to start even many kinds of new projects in Java today: maturity/efficiency of the compiler, tooling support (including static analyzers, since nullability type systems are not the only feature you'd ever want to bolt onto a language), etc. Over time those reasons will be less, but even then, existing Java codebases will remain, and it will still be worth making them safer. Is not like C, C++ or even Fortran codebases are not around anymore.
I assume Vert.x 3 is doing a better job there, but the fundamental problem remains: callbacks don't work well with exceptions. Maybe Kotlin helps wrapping this nicer, I don't know. All I know is that I'll be more careful when selecting a framework for productive use now. And maybe that I won't be using Vert.x again.
In general, I think without language-level support for continuations (callbacks, coroutines, promises, async-"colored" functions, whatever), projects like Vert.x are going to cause a lot of friction with regards to exceptions. Having a large synchronous legacy to contend with doesn't help either.
http://vertx.io/blog/vert-x-3-5-0-released/#kotlin-coroutine...
2- it also adds toString and destructuring functions.
for the data layer, 'data' classes are awesome IMO.
Foo foo = bar.foo();
Assertions.assertThat(foo).equalTo(new Foo(1, 2, 3));
instead of: Foo foo = bar.foo();
Assertions.assertThat(foo.getFirst()).equalTo(1);
Assertions.assertThat(foo.getSecond()).equalTo(2);
Assertions.assertThat(foo.getThird()).equalTo(3);Also, do you really never need to call .equals on data objects?
A good example is the anonymous class. They are portly and wasteful of vertical space, often costing 5 lines when one would due. But, the amount thinking per line is greatly reduced. In order to visually afford anonymous classes, logic must be simplified or structured, commonly by splitting up a larger function in order to fit it on a single screen. An arguable misfeature of the language has a convenient side effect of forcing best practices.
See I think this is actually backwards thinking. The more boilerplate you have to sift through, the more you're trying to retain before getting to what the program is actually doing.
> A good example is the anonymous class. They are portly and wasteful of vertical space, often costing 5 lines when one would due. But, the amount thinking per line is greatly reduced
Same applies for this statement. You're trying to absorb so much more code before knowing what is actually happening.
Small bugs in the implementation of boilerplate code have a tendency to sail through code reviews and go without notice until they become production issues, because experienced developers develop a habit of glossing over boilerplate without reading it too carefully.
That being said, I'm a big of Java and the eco-system.
I'm not sure I agree with this. I think every effort should be made to keep your code readable to others with similar knowledge. However, I don't think you need to make everything readable by a junior level coder.
Maybe a decent analogy is that not all books target a 4th grade reading level. The varience of information density isn't just about saving time for those that understand it, but also for keeping you interested (or disinterested if you aren't ready for it yet). For example, reading young adult fiction in your 30s usually wouldn't keep you engrossed.
"However, I don't think you need to make everything readable by a junior level coder." I may not be a junior-level coder, but I may still be 'jr' with your particular language or framework, but have been assigned your code because you've moved on to something else.
If your language generates them behind the scenes, so that nobody can modify them, that doesn’t happen.
Also, if a developer overrides one particular example, it stands out in the source, as it (ideally) is the only code visible in the source.
Ergonomics goes into the mix of tradeoffs for design. Something like LINQ's expression trees, for example; not easy to do in Java not because ASTs can't be constructed, but because their construction isn't as ergonomic.
Python fits nicely in both, but java is a great reading language. If I had to maintain any existing codebase I would prefer a java one.
Scala is the great obfusciator.
As for tedium: Java programmers rely heavily on IDEs to "write" most of the boilerplate for them. The IDE fills in tokens automatically, and hides them visually with +/- boxes to the side.
High verbosity == more lines == more bugs.
Also, I bet 10$ that until you saw some short yet clean and expressive code, you might believe that the choice is either Java verbosity or Perl cryptics.