Hello to this new post! This is part 3 of my blog post series about “How the book Effective Java may have influenced the design of Kotlin.” I wrote the first and second part about half a year ago and thought that I figured out every aspect of how Effective Java could have possibly influenced the programming language Kotlin.
A few months ago however, I bought and read, what is, in my opinion, the best book on Kotlin: Kotlin in Action. It is written by Dmitry Jemerov and Svetlana Isakova, who are Kotlin core developers working at JetBrains. They definitely know what they are talking about. If you want to advance your Kotlin Knowledge to the next level, I definitely recommend you to read Kotlin in Action 📘!
While reading the book, I discovered new language features and design choices that were also likely to have been influenced by Effective Java.
11. First class support for composition over inheritance
“Favor composition over inheritance” is an often stated principle in the software development field. In Item 16, Effective Java demonstrates how to achieve composition with Java. In the book, an example of an
InstrumentedHashSet is shown. It should count how many times an element got added to the set using
addCount as a counter.
This code is incorrect, however, because it prints 6 as the result instead of 3. That is because the
HashSet implementation internally calls the
add()method three times in
addAll(). What we see here is that with inheritance, we always have a dependency on the super class. If the super class changes in the future, it is possible that the implementation of the subclass will no longer work properly. This is especially true in cases where the superclass is not designed for inheritance (see Item 17). Inheritance leads to fragile implementations.
We can fix this problem with composition by creating a “Forwarding Class”:
ForwardingSet holds an instance of type
Set<E> and delegates calls to this instance. This pattern is known as the Delegation Pattern. The class that does the instrumentation by increasing the
addCount then has to override the relevant methods:
InstrumentedSet works as expected and is independent of the implementation details of the super class of
Set ! One drawback of composition in Java, as we can see in the forwarding class
ForwardingSet, is that a lot of boilerplate code needs to be written. That is definitely work that the Kotlin compiler can do for us!
Composition with Kotlin
In Kotlin, the
InstrumentedSet can be as simplified as shown below:
We don’t need to create a forwarding class. We can delegate method calls to instances by using the
by keyword. The compiler will then generate all the methods of
InstrumentedSet that forward to
set. This feature is called Class Delegation.
Tip: If you want to find out what the compiler is generating under the hood, you can use the “Kotlin Byte Code Viewer” of IntelliJ IDEA or Android Studio. This viewer can show you the resulting Java byte code of a Kotlin file and can also decompile this byte code to pure Java code. When we perform this on
InstrumentedSet.kt , we see the generated forwarding methods like
In a nutshell, it is generally preferable to use composition over inheritance. We can do this in Java by writing a lot of boilerplate code. Kotlin, on the other hand, supports composition with the delegator pattern natively with the class delegation language feature and therefore requires zero boilerplate.
12. No primitive and reference type confusion
In Java, there is a distinction between primitive types (e.g.
long) and reference types (e.g.
Long). There are basically three differences between them:
- Primitive types contain values. Reference types hold references to a memory location of an object. It’s a very common mistake to use the
==operator on reference types if you want to check if the two objects have the same value. For instance
new Integer(128) == new Integer(128)results in
falsebecause you are checking if the two objects point to the same memory address, which they do not because they are two distinct objects. The right solution would be to use
==. With primitive types, on the other hand, the correct way to evaluate equivalent values is to use
2. Primitives only have functional values, whereas reference types also have a non-functional value:
null . As a result, it is possible to produce a
NullPointerException during runtime by using reference types.
3. Primitives are more time- and space-efficient. By using reference types in loops for instance instead of primitive types, you can get performance issues very easily:
The previous example is taken from Item 5 of Effective Java, that states “Avoid creating unnecessary objects”. It took about 25 seconds to execute this code on my machine. Why does it take so long? The reason is simple. By using the reference Type
sum, a new object of type
Long gets created every time
sum += i is performed. A small change to the primitive type
long will drastically improve the performance. After this change the code gets executed in about 2.5 seconds.
Release 1.5 of Java contained the features
autoboxing (creating a reference type from a primitive under the hood) and
auto-unboxing (creating a primitive from a reference type under the hood). This should make it easier to use the two types with each other. The problem is that it is not obvious when Java is performing these features, which often confuses developers and results in nasty bugs.
Long story short: In Java you have to be very cautious about which type you use and when some kind of boxing is performed. As a general guideline, you should always use primitive types except when you are forced to use reference types (like in collections and type arguments of a generic class). Item 49 also recommends to “Prefer primitive types to boxed primitives”.
Types in Kotlin
What about types in Kotlin? Is there a distinction between primitive and reference types? Simple Answer: No! There are no different types of e.g. Long or Integer, there is just
You might ask yourself if types in Kotlin are rather like primitive types or like reference types and the answer here is: It depends!
We know that the Kotlin compiler produces Java byte code. So ultimately the compiler is the one that generates the right kind of type. The compiler is smart and wants to create efficient byte code. So most of the time, primitive types are generated, but not always.
When stored in collections and with type arguments in generic classes, the compiler generates reference types because there is no way around doing so. The interesting thing is that a reference type is also generated when you use a nullable type like
Long? . The reason behind this is simple if you think about it. A primitive type cannot hold
null , whereas a reference type can. So our code example above would also have performance issues in Kotlin when you declare:
var sum: Long? = 0L .
For equality checks, you can always use
== in Kotlin to compare values. If you want to check if two references point to the same object in memory, called “Structural equality” in Kotlin, you use
=== . That’s a much simpler and less error-prone way than in Java.
In summary, because there is no distinction between primitive and reference types in Kotlin in the language itself, it’s not possible to make some of the mistakes that Effective Java recommends avoiding. However, you have to be aware that Kotlin generates reference types from nullable types in the byte code, which might cause performance issues.
13. Functions outside of classes
In Java, every function needs to be defined inside a class. Classes with helper functionality that do not contain any state but only contain
public staticfunctions are called “Utility Classes” (e.g.
java.util.Arrays , a utility class that helps with e.g. sorting arrays).
Effective Java proposes in Item 4 to enforce noninstantiability with these classes by implementing a private constructor so that clients cannot create instances of such helper classes:
In Kotlin, a function does not need to be associated with a class. Functions outside of a class are called “Top Level Functions” and are well suited for “Utility Functions”. The
Array.java file would look like this:
With Top Level Functions, we do not need to make any class noninstantiable, because the functions are not part of any class.
14. Static member classes by default
Effective Java suggests in Item 22 to “Favor static member classes over nonstatic.” Nonstatic member classes have an implicit reference to their enclosing classes and therefore cause a lot of trouble (e.g. they use a lot of memory space, are a common cause of memory leaks). Member classes are nonstatic by default in Java. You have to explicitly provide the
statickeyword to make them (… guess what?) static.
Nested classes in Kotlin do not hold an implicit reference by default:
You have to explicitly add the keyword
inner to get an implicit reference to the enclosing class:
The designers of Kotlin followed the recommendation of Item 15 to “Favor static member classes over nonstatic” and made member classes, in contrast to Java, static by default.
15. Nice way of checking input
Chapter 7 in Effective Java is all about Methods. Item 38 recommends to “Check parameters for validity,” as the following Java code example demonstrates:
The Kotlin team provides nice utility functions for parameter checks in its standard library, more precisely in the file Preconditions.kt. Let’s take a look at the
require() takes the boolean result of the parameter check as the first parameter and a
lazyMessage function type as the second parameter. Only in cases where the first parameter is
false, will the code of
lazyMessage be executed (that’s why it’s called lazy), and an
IllegalArgumentException with the
.toString() result of
lazyMessage will be thrown.
require(), that’s how the validation code looks like:
The first thing to notice is that there is no null check anymore. That’s because the method only allows its clients to pass non-nullable instances of
List<Int> . Second, the code is much more readable. Instead of an
ifcondition, we have a more expressive
require() method call. Third, we do not need to manually throw an
IllegalArgumentException , because
requireis doing that for us.
Summary: Kotlin knows about the importance of input validation and therefore provides nice utility functions in its standard library.
That’s it! Thank you for reading! If you enjoyed this post and maybe learned a thing or two, I would really appreciate it if you could share this article 👏. If you never want to miss a post from me, feel free to follow me on twitter 😃. I regularly blog about new stuff I am learning (mostly Kotlin and Android related). Have a nice day! 🌴