4

How “Effective Java” may have influenced the design of Kotlin – Part 1

 2 years ago
source link: https://www.lukaslechner.com/how-effective-java-influenced-the-design-of-kotlin-part-1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
How “Effective Java” may have influenced the design of Kotlin – Part 1screwdrivers-1073515_1920.jpg?resize=1024%2C685&ssl=1

Java is a great programming language but has some known flaws, common pitfalls and not-so-great elements that have been inherited from its early days (1.0 got released in 1995). A well-respected Book on how to write good Java code, avoid common coding mistakes and deal with its weaknesses is Joshua Bloch’s “Effective Java.” It contains 78 sections, called “Items”, that give the reader valuable advice on different aspects of the language.

Creators of modern programming languages have a big advantage because they are able to analyze the weaknesses of established languages and make things better themselves. Jetbrains, a company that has created several very popular IDEs, decided in 2010 to create a programming language Kotlin for their own development. The goal of it was to be more concise and expressive while eliminating some of the weaknesses of Java. All the previous code for their IDEs has been written in Java, so they needed a language that is highly interoperable with Java and compiles down to Java bytecode. They also wanted to make it very easy for Java developers to jump into Kotlin. With Kotlin, Jetbrains wanted to build a better Java.

While re-reading “Effective Java,” I found out that a lot of these Items are not necessary for Kotlin and the idea of this blog post series about how the content of this book may have influenced Kotlin’s design was born.

1. No more builders needed with Kotlin’s default values

When you have a lot of optional parameters for a constructor in Java, the code becomes verbose, hard to read and error-prone. To help with that, Item 2 of Effective Java describes how to effectively use the Builder Pattern. A lot of code is needed to construct such an object like the nutrition facts object in the code samples below. It has two required parameters (servingSize, servings) and four optional parameters (calories, fat, sodium, carbohydrates):

public class JavaNutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate;

public static class Builder { // Required parameters private final int servingSize; private final int servings;

// Optional parameters - initialized to default values private int calories = 0; private int fat = 0; private int carbohydrate = 0; private int sodium = 0;

public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; }

public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public Builder sodium(int val) { sodium = val; return this; }

public JavaNutritionFacts build() { return new JavaNutritionFacts(this); } }

private JavaNutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } }

Instantiating an object with Java looks like this:

final JavaNutritionFacts cocaCola = new JavaNutritionFacts.Builder(240,8) .calories(100) .sodium(35) .carbohydrate(27) .build();

With Kotlin you don’t need to use a builder pattern at all because there is the feature of default parameters, which allow you to define default values for each optional constructor argument:

class KotlinNutritionFacts( private val servingSize: Int, private val servings: Int, private val calories: Int = 0, private val fat: Int = 0, private val sodium: Int = 0, private val carbohydrates: Int = 0)

Creating an object in Kotlin looks like this:

val cocaCola = KotlinNutritionFacts(240,8, calories = 100, sodium = 35, carbohydrates = 27)

For better readability, you can also name the required parameters servingSize and servings:

val cocaCola = KotlinNutritionFacts( servingSize = 240, servings = 8, calories = 100, sodium = 35, carbohydrates = 27)

Like Java, the object created here is immutable.

We reduced the lines of code needed from 47 in Java to 7 in Kotlin, resulting in a huge productivity boost. Not bad!

not_bad

Tip: If you want to create the KotlinNutrition object in Java, you can do that of course, but you are forced to specify a value for each optional parameter. Fortunately, if you add the @JvmOverloads annotation, multiple constructors are generated. Note that if you want to use an annotation, we need the keyword constructor too:

class KotlinNutritionFacts @JvmOverloads constructor( private val servingSize: Int, private val servings: Int, private val calories: Int = 0, private val fat: Int = 0, private val sodium: Int = 0, private val carbohydrates: Int = 0)

2. Easy creation of singletons

Item 3 of “Effective Java” shows how to design a Java object so that it behaves as a singleton, which is an object where only a single instance can be instantiated. The code snippet below shows  a “monoelvistic” universe, where only one Elvis can exist:

public class JavaElvis {

private static JavaElvis instance;

private JavaElvis() {}

public static JavaElvis getInstance() { if (instance == null) { instance = new JavaElvis(); } return instance; }

public void leaveTheBuilding() { } }

Kotlin has the concept of object declarations that gives us the behavior of a singleton out of the box:

object KotlinElvis {

fun leaveTheBuilding() {} }

No need to manually construct your singletons anymore!

3. equals() and hashCode() out of the box

A good programming practice that has its origins in functional programming and simplifies the code is to mainly use immutable value objects. Item 15 contains the advice that “Classes should be immutable unless there’s a very good reason to make them mutable.” Immutable value objects are very tedious to create in Java, because for every object you have to override the equals() and hashCode() functions yourself. It took Joshua Bloch 18 pages to describe how to obey to the explicit general contract of these two methods in Item 8 and 9. For example, if you override equals() you have to make sure that the contracts of reflexivity, symmetry, transitivity, consistency and nun-nullity are all fulfilled. Sounds more like math rather than programming.

In Kotlin, you can simply use data classes instead, where the compiler automatically derives methods like equals(), hashCode() and many more. This is possible because the standard functionality can be mechanically derived from the properties of your object. Simply enter the keyword data in front of your class and you are done. No 18 pages of description needed here.

Tip: Lately, AutoValue for Java became popular. This library generates immutable value classes for Java 1.6+.

4. Properties instead of fields

public class JavaPerson {

// don't use public fields in public classes! public String name; public Integer age; }

Item 14 recommends the use of accessor methods instead of public fields in public classes. If you do not stick to this advice you could get into trouble, because fields are then directly accessible and you lose all the benefits of encapsulation and flexibility. This means that in the future you will be unable to change the internal representation of your class without changing its public API. For instance, you cannot limit values for a field, such as the age of a person, later. That is one of the reasons why we always create those verbose default getters and setters in Java.

This best practice is enforced by Kotin because it uses properties with auto-generated default getters and setters instead of fields.

class KotlinPerson {

var name: String? = null var age: Int? = null }

Syntactically you access those properties like public fields in Java with person.name or person.age.  You can add custom getters and setters later without changing the API of the class:

class KotlinPerson {

var name: String? = null

var age: Int? = null set(value) { if (value in 0..120){ field = value } else{ throw IllegalArgumentException() } } }

Long story short: with Kotlin’s properties, we get more concise classes with greater flexibility out of the box.

5. Override as mandatory keyword instead of optional annotation

Annotations were added to Java in release 1.5. The most important one is @Override, which marks that a method is overriding a method of a superclass. According to Item 36, this optional annotation should be constantly used to avoid nefarious bugs. The compiler then throws an error when you think you are overriding a method from a superclass but you actually do not. This works as long as you don’t forget to use the @Override annotation.

In Kotlin, override is not an optional annotation but a mandatory keyword. So there is no more chance for these types of nasty bugs. The compiler will alert you about these beforehand. Kotlin sticks to making these kinds of things explicit.

That’s it

Thanks for reading! This was part 1 of how “Effective Java” may have influenced the design of Kotlin.




📬 Get notified whenever I publish new content! 🚀

No Spam! Unsubscribe at any time!


Leave this field empty if you're human:
Share this Article!

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK