Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

5 Things You Will Like in Kotlin as a Java Developer

DZone's Guide to

5 Things You Will Like in Kotlin as a Java Developer

On the fence about whether to get into the Kotlin world? Here are five things that could very well make up your mind.

· Java Zone ·
Free Resource

FlexNet Code Aware, a free scan tool for developers. Scan Java, NuGet, and NPM packages for open source security and open source license compliance issues.

The Kotlin language is gaining more and more popularity. It is widely used no longer just in mobile apps development, but also for server-side systems. As you probably know, it is a statically-typed programming language that runs on the JVM. That's why it is often compared with the Java language. One of the main reasons for Kotlin popularity is simplicity. It cleans and removes a lot of the code bloat from Java. However, it is also very similar to Java so that any experienced Java developer can pick up Kotlin in just a few hours.

In this article, I'm going to discuss some interesting Kotlin features used for server-side development in comparison to Java. Here's my personal list of favorite Kotlin features that are unavailable with the Java language.

1. Collections and Generics

I really like Java, but sometimes, working with generic collections may be an unpleasant experience, especially if you have to use wildcard types. The good news is that Kotlin doesn't have any wildcard types. Instead, it provides two other features called declaration-site variance and type projections. Now, let's consider the following class hierarchy.

abstract class Vehicle {

}

class Truck extends Vehicle {

}

class PassengerCar extends Vehicle {

}


I defined a generic repository that contains all objects with a given type.

public class Repository<T> {

  List<T> l = new ArrayList();

  public void addAll(List<T> l) {
    this.l.addAll(l);
  }

  public void add(T t) {
    l.add(t);
  }
}


Now, I would like to store all the vehicles in that repository, so I declare the Repository r = new Repository(). But, invoking the repository method addAll with List as a parameter will receive the following error:

Of course, this situation has a logical explanation. First, generic types in Java are invariant, which, in fact, means that List is not a subtype of  List, although Truck is a subtype of Vehicle. The addAll method takes the wildcard type argument and extends T as a parameter, which indicates that this method accepts a collection of objects of T or some subtype of T — not just T itself.

The List is a subtype of List<? extends Vehicle>, but the target list is still List. I don't want to get into details about this behavior — you can read more about it in Java specification. The important thing for us is that Kotlin is solving this problem using a feature called Declaration-site variance. If we add the out modifier to the MutableList parameter inside the addAll method declaration, the compiler will allow adding a list of Truck objects. The smart explanation of that process is provided on the Kotlin site: 'In "clever words," they say that the class C is covariant in the parameter T, or that T is a covariant type parameter. You can think of C as being a producer of T's, and NOT a consumer of T's."

class Repository<T> {

    var l: MutableList<T> = ArrayList()

    fun addAll(objects: MutableList<out T>) {
        l.addAll(objects)
    }

    fun add(o: T) {
        l.add(o)
    }

}

fun main(args: Array) {
    val r = Repository<Vehicle>()
    var l1: MutableList<Truck> = ArrayList()
    l1.add(Truck())
    r.addAll(l1)
    println("${r.l.size}")
}


2. Data Classes

You probably know Java's POJOs (Plain Old Java Object). If you are following the Java best practices, such a class should implement getters, setters, hashCode, and equals methods, as well as the toString method for logging needs. Such an implementation may take up a lot of space, even for a simple class with only four fields. This is also shown below (methods auto-generated using Eclipse IDE):

public class Person {

  private Integer id;
  private String firstName;
  private String lastName;
  private int age;

  public Person(Integer id, String firstName, String lastName) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    Person other = (Person) obj;
    if (firstName == null) {
      if (other.firstName != null)
        return false;
    } else if (!firstName.equals(other.firstName))
      return false;
    if (id == null) {
      if (other.id != null)
        return false;
    } else if (!id.equals(other.id))
      return false;
    if (lastName == null) {
      if (other.lastName != null)
        return false;
    } else if (!lastName.equals(other.lastName))
      return false;
    return true;
  }

  @Override
  public String toString() {
    return "Person [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
  }

}


To avoid many additional lines of code inside your POJO classes, you may use project Lombok. It provides a set of annotations that can be used on the class to deliver implementations of getters/setters, equals, and hashCode methods. It is also possible to annotate your class with @Data, which bundles all the features of @ToString, @EqualsAndHashCode, @Getter/@Setter, and @RequiredArgsConstructor together. So, with Lombok's @Data, the POJO is going to look like as shown below — assuming you don't require a constructor with parameters.

@Data
public class Person {

  private Integer id;
  private String firstName;
  private String lastName;
  private int age;

}


Including and using Lombok with a Java application is quite simple and supported by all the main developer IDEs, but Kotlin solves this issue out-of-the-box. It provides functionality called data classes, which are enabled after adding the keyword data to the class definition. The compiler automatically derives the methods from all properties declared in the primary constructor:

  • toString() method
  • componentN() functions corresponding to the properties in their order of declaration
  • copy() function

Because Kotlin internally generates a default getter and setter for mutable properties (declared as var) and a getter for read-only properties (declared as val), the similar implementation of Person Java POJO in Kotlin will look as shown below.

data class Person(val firstName: String, val lastName: String, val id: Int) {

    var age: Int = 0

}


What's worth mentioning here is that the compiler only uses the properties defined inside the primary constructor for the automatically generated functions. So, the field age, which is declared inside the class body, will not be used by toString, equals, hashCode, and copy implementations.

3. Names for Test Methods

Now, let's implement some test cases to prove that the features described in step twowork properly. The following three tests are comparing two objects with different values of age property, trying to add the same object to the Java HashSet twice and checking if the componentN method of the data class is returning properties in the right order.

@Test fun `Test person equality excluding "age" property`() {
  val person = Person("John", "Smith", 1)
  person.age = 35
  val person2 = Person("John", "Smith", 1)
  person2.age = 45
  Assert.assertEquals(person, person2)
}

@Test fun `Test person componentN method for properties`() {
  val person = Person("John", "Smith", 1)
  Assert.assertEquals("John", person.component1())
  Assert.assertEquals("Smith", person.component2())
  Assert.assertEquals(1, person.component3())
}

@Test fun `Test adding and getting person from a Set`() {
  val s = HashSet()
  val person = Person("John", "Smith", 1)
  var added = s.add(person)
  Assert.assertTrue(added)
  added = s.add(person)
  Assert.assertFalse(added)
}


As you can see on the fragment of code above, Kotlin is accepting to use method names with spaces enclosed in backticks. Thanks to that, I can set a descriptive form of test name, which is then visible during execution, and you know exactly what's going on.

4. Extensions

Let's consider the situation that we have a library that contains class definitions, which cannot be changed, and we need to add there some methods. In Java, we have some choices to implement such an approach. We can just extend the existing class, implement a new method, or, for example, implement it with the Decorator pattern.

Now, let's assume we have the following Java class containing the list of persons and exposing getters/setters.

public class Organization {

  private List<Person> persons;

  public List<Person> getPersons() {
    return persons;
  }

  public void setPersons(List<Person> persons) {
    this.persons = persons;
  }

}


If I would like to have the method for adding a single Person object to the list, I would have to extend the Organization and implement a new method there.

public class OrganizationExt extends Organization {

  public void addPerson(Person person) {
    getPersons().add(person);
  }
}


Kotlin provides the ability to extend a class with a new functionality without having to inherit from the base class. This is done via special declarations called extensions. Here's the similar declaration to Organization Java class in Kotlin. Because Kotlin treats a simple Listclass as immutable, we need to define the MutableList.

class Organization(val persons: MutableList = ArrayList()) {

}


We can easily extend it with the addPerson method, as shown below. Extensions are resolved statically, and they do not modify extended classes.

class OrganizationTest {

    fun Organization.addPerson(person: Person) {
        persons.add(person)
    }

    @Test
    fun testExtension() {
        val organization = Organization()
        organization.addPerson(Person("John", "Smith", 1))
        Assert.assertTrue(organization.persons.size == 1)
    }

}


5. String Templates

Here's a little something to make you happy that is not available in Java.

println("Organization ${organization.name} with ${organization.persons.size} persons")


Conclusion

Of course, there are some other differences between Java and Kotlin. This is only my personal list of favorite features that are unavailable in Java. The sample source code with described samples is available on GitHub.

 Scan Java, NuGet, and NPM packages for open source security and license compliance issues. 

Topics:
kotlin ,java ,comparison ,classes ,strings ,templates ,generics ,collections

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}