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 clear()
or remove()
:
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. int
, long
) and reference types (e.g. String
, Integer
, 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 instancenew Integer(128) == new Integer(128)
results infalse
because 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.equals()
instead of==
. 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 aNullPointerException
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 Long
for 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 Int
and Long
.
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 static
functions 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 static
keyword 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()
function:
require()
takes the boolean result of the parameter check as the first parameter and alazyMessage
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.
So with 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 if
condition, we have a more expressive require()
method call. Third, we do not need to manually throw an IllegalArgumentException
, because require
is 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! 🌴