Classes

Constructors

In Kotlin you have one primary constructor and one or more secondary constructors. Primary constructors are only used to pass required fields to the class. Primary constructors cannot contain any code. To run code after the primary constructor use init-blocks.

Primary Constructor

// java
public class User{

    public User(String firstName, String lastName){
        this.firstName = firstName;
        this.lastName = lastName;
    }

}
// kotlin

class User(var firstName: String, var lastName: String)

This is especially useful when creating data classes. As you can see you don’t need curly brackets {} for the body if you only define class properties. If you still want some code in your primary

Secondary Constructor

Secondary constructors can be created via the constructor keyword.

// kotlin

class User(
    var firstName: String,
    var lastName: String
){
    // using one parameter for both
    constructor(name: String){
        firstName = name
        lastName = name
    }
}

Default Values / Default Constructor

When you work with frameworks like Spring you may come across some errors where Spring cannot instantiate a class because it is missing a default constructor. You can either do this by creating a secondary constructor without parameters or by assigning default values to you properties.

// kotlin

class User(
    var firstName: String = "",
    var lastName: String = ""
)

But in Kotlin aren’t strings not-null anyway? Why do I have to set a string to its default value altought it already is this value?

That’s correct. Kotlin does not allow null values for String (unlike String?). Kotlin needs these default values to determine whether the properties shall be an optional parameter or not (see Optional Parameters).

Data Classes

In Kotlin data classes can be simplified and need no additional overhead when declaring.

// java

public final class User {

    private long id;

    @NotNull
    private String username;

    @NotNull
    private String password;

    public User(long id, @NotNull String username, @NotNull String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public long getId() {
        return id;
    }

    @NotNull
    public String getUsername() {
        return username;
    }

    @NotNull
    public String getPassword() {
        return password;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id &&
                Objects.equals(username, user.username) &&
                Objects.equals(password, user.password);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, username, password);
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
// kotlin

data class User(
    val id: Long,
    val username: String,
    val password: String
)

The data keyword causes the compiler to create the given properties and the usual equals, hashCode, toString methods. These are important to differenciate between objects of the same type.

Additionally the method copy is created with then can be used to copy an object easily. The copy method accepts every property of the data class as optional parameter to easily manipulate the copied object.

Static methods

In Kotlin static methods are visually divided from the instance members.

// java

public class User{
    private String firstName;
    private String lastName;

    public User(String firstName, String lastName){
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getDisplayName(){
        return firstName + lastName;
    }

    public static getDisplayName(User user){
        return user.firstName + user.lastName;
    }
}

// kotlin

class User(
    var firstName: String,
    var lastName: String
){
    fun getDisplayName() = firstName + lastName
}

fun getDisplayName(user: User) = firstName + lastName

As you can see static methods get declared outside of the class body to visually divide instance and static methods.

Sealed Classes

In Kotlin there are so called sealed classes which are useful when using when expressions to check if an object is of a given type. At compile time the compiler knows what types are inheriting the sealed class and know what you can check in an when expression. For example:

// Kotlin
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
// Kotlin
fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // the `else` clause is not required because we've covered all the cases
}

Sealed classes are similar to enum values except that enum values are unique and cannot be instantiated whereas (subclasses of) sealed classes can be insantiated. Sealed classes itself are abstract.

Also the Reddit user sebaslogen explained it pretty well.

Not to be confused with C#s sealed keyword, which is equal to non-open classes or final classes in java.

Inline Classes

best explaination can be found here