Kotlin

Das moderne Java

28.01.2019

Niklas Lochschmidt

Software Entwickler bei                   

        @Niklas_L

Mehr dazu auf bryter.io

Agenda

Photo by Sarita Rungsakorn

präzise und kompakt

Concise

Typinferenz, vals, keine Semikolons

val hello = "Hello World"
println(hello)
final String hello = "Hello World"
System.out.println(hello);

Kotlin

Java

final var hello = "Hello World";
System.out.println(hello);

Java 10

Klassen und Methoden

class UserProfile(
   val firstName: String,
   val lastName: String) {

   fun fullName() = "$firstName $lastName"

}
public final class UserProfile {
  private final String firstName;
  private final String lastName;

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

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public String fullName() {
    return firstName + " " + lastName;
  }
}

Kotlin

Java

new UserProfile("John", "Doe")
UserProfile("John", "Doe")

Mächtige API für Collections

listOf(1, 2, 3, 4, 5)
   .map { it + 1 }
   .filter { it % 2 == 0 }
   .take(2)
List.of(1, 2, 3, 4, 5)
  .stream()
  .map(it -> it + 1)
  .filter(it -> it % 2 == 0)
  .limit(2)
  .collect(toList());

Kotlin

Java

Mächtige API für Collections

listOf(1, 2, 3, 4, 5)
   .asSequence()
   .map { it + 1 }
   .filter { it % 2 == 0 }
   .take(2)
   .toList()
List.of(1, 2, 3, 4, 5)
  .stream()
  .map(it -> it + 1)
  .filter(it -> it % 2 == 0)
  .limit(2)
  .collect(toList());

Kotlin

Java

Extension Functions

fun Int.seconds() = Duration.ofSeconds(this.toLong())

fun String.toUUID() = UUID.fromString(this)

Kotlin

val timeout = 60.seconds()

val userId = "dc9b21f2-986d-438f-80a6-e6afe430e6e8".toUUID()

Kotlin

val text = java.io.File("trykotlin.txt").readText()

val price = "39.90".toBigDecimalOrNull()

Kotlin

Eigene  Extension Functions definieren

Kotlin Stdlib

Konkretes Beispiel:
 

  • Kundennummer
  • Name
  • Von anderem Kunden geworben (optional)
    • Kunden können sich nicht selbst werben
  • Beginn der Kundenbeziehung
  • Datum der letzten Bestellung (optional)
  • Gesamtvolumen der Bestellungen
    • 0 € zu Beginn
    • Kann niemals weniger als 0 € werden
                               Customer
customerNr: Int
name: String
referredBy: Int [0..1]
becameCustomerAt: Instant  = now()
lastOrderAt: Instant [0..1]
totalOrderValue: Double = 0.0
setTotalOrderValue(Double)

Kunde im Kontext eines Kundenbindungsprogrammes

Customer customer = new Customer(1, "Peter", 2, Instant.now(), null 0.0);

Fragen:

  • Welcher Parameter ist referredBy?
  • Welche Parameter sind notwendig, welche optional?
  • Wenn ein Parameter hinzugefügt wird müssen wir hier nochmal ran

Java

Kurzer Exkurs

Notwendigkeit von Buildern in Java

public final class Customer {
  private final int customerNr;
  private String name;
  private final Integer referredBy;       // Builder default: null
  private final Instant becameCustomerAt; // Builder default: Instant.now()
  private Instant lastOrderAt;            // Builder default: null
  private double totalOrderValue;         // Builder default: 0.0

 private Customer(Builder builder) {
    if (builder.customerNr == builder.referredBy) {
      throw new IllegalArgumentException("Customers can not refer themselves");
    }

    customerNr = builder.customerNr;
    name = builder.name;
    referredBy = builder.referredBy;
    becameCustomerAt = builder.becameCustomerAt;
    lastOrderAt = builder.lastOrderAt;

    setTotalOrderValue(builder.totalOrderValue);
  }

  public void setName(String value) { 
    this.name = Objects.requireNonNull(value); 
  }
  
  public void setLastOrderAt(Instant value) { 
    this.lastOrderAt = value; 
  }

  public void setTotalOrderValue(double value) {
    if (value < 0.0) { 
      throw new IllegalStateException("Can not set totalOrderValue to less than 0.0"); 
    }
    this.totalOrderValue = value;
  }

Java

Wichtig: Setter benutzen sonst findet keine Prüfung statt

  ...
  public int getCustomerNr() { 
    return customerNr; 
  }

  public String getName() { 
    return name; 
  }
  
  public int getReferredBy() { 
    return referredBy; 
  }
  
  public Instant getBecameCustomerAt() { 
    return becameCustomerAt; 
  }
  
  public Instant getLastOrderAt() { 
    return lastOrderAt; 
  }

  public double getTotalOrderValue() { 
    return totalOrderValue; 
  }

  @Override public String toString() {
    return String.format("Customer(customerNr = %d, name = %s ...)", customerNr, name);
  }

  @Override public boolean equals(Object other) {
    if (other instanceof Customer) {
      return ((Customer) other).customerNr == customerNr;
    }
    return false;
  }

  @Override public int hashCode() { 
    return Integer.hashCode(customerNr); 
  }
  ...

Java

  ...
  public static class Builder {
    // Required
    private final int customerNr;
    private final String name;
    // Optional
    private Integer referredBy = null;
    private Instant becameCustomerAt = Instant.now();
    private Instant lastOrderAt = null;
    private double totalOrderValue = 0.0;

    public Builder(int customerNr, String name) {
      this.customerNr = Objects.requireNonNull(customerNr);
      this.name = Objects.requireNonNull(name);
    }

    public Builder referredBy(int value) {  
      this.referredBy = value; 
      return this; 
    }

    public Builder becameCustomerAt(Instant value) { 
      this.becameCustomerAt = value; 
      return this; 
    }

    public Builder lastOrderAt(Instant value) { 
      this.lastOrderAt = value; 
      return this; 
    }
    
    public Builder totalOrderValue(double value) { 
      this.totalOrderValue = value; 
      return this; 
    }

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

Java

Customer customer = new Customer(2, "Peter", 1, Instant.now(), null 0.0)
Customer customer = new Customer.Builder(2, "Peter")
  .referredBy(1)
  .build()

Vorher

Nachher

Ungelöste Probleme

  • Parameter customerNr und name werden nicht benannt
  • Builder und build() ist Rauschen

Java

Java

public final class Customer {
  private final int customerNr;
  private String name;
  private final Integer referredBy;       // Builder default: null
  private final Instant becameCustomerAt; // Builder default: Instant.now()
  private Instant lastOrderAt;            // Builder default: null
  private double totalOrderValue;         // Builder default: 0.0

 private Customer(Builder builder) {
    if (builder.customerNr == builder.referredBy) {
      throw new IllegalArgumentException("Customers can not refer themselves");
    }

    customerNr = builder.customerNr;
    name = builder.name;
    referredBy = builder.referredBy;
    becameCustomerAt = builder.becameCustomerAt;
    lastOrderAt = builder.lastOrderAt;

    setTotalOrderValue(builder.totalOrderValue);
  }

  public void setName(String value) { 
    this.name = Objects.requireNonNull(value); 
  }
  
  public void setLastOrderAt(Instant value) { 
    this.lastOrderAt = value; 
  }

  public void setTotalOrderValue(double value) {
    if (value < 0.0) { 
      throw new IllegalStateException("Can not set totalOrderValue to less than 0.0"); 
    }
    this.totalOrderValue = value;
  }

  public int getCustomerNr() { 
    return customerNr; 
  }

  public String getName() { 
    return name; 
  }
  
  public int getReferredBy() { 
    return referredBy; 
  }
  
  public Instant getBecameCustomerAt() { 
    return becameCustomerAt; 
  }
  
  public Instant getLastOrderAt() { 
    return lastOrderAt; 
  }

  public double getTotalOrderValue() { 
    return totalOrderValue; 
  }

  @Override public String toString() {
    return String.format("Customer(customerNr = %d, name = %s ...)", customerNr, name);
  }

  @Override public boolean equals(Object other) {
    if (other instanceof Customer) {
      return ((Customer) other).customerNr == customerNr;
    }
    return false;
  }

  @Override public int hashCode() { 
    return Integer.hashCode(customerNr); 
  }
  
  public static class Builder {
    // Required
    private final int customerNr;
    private final String name;
    // Optional
    private Integer referredBy = null;
    private Instant becameCustomerAt = Instant.now();
    private Instant lastOrderAt = null;
    private double totalOrderValue = 0.0;

    public Builder(int customerNr, String name) {
      this.customerNr = Objects.requireNonNull(customerNr);
      this.name = Objects.requireNonNull(name);
    }

    public Builder referredBy(int value) {  
      this.referredBy = value; 
      return this; 
    }

    public Builder becameCustomerAt(Instant value) { 
      this.becameCustomerAt = value; 
      return this; 
    }

    public Builder lastOrderAt(Instant value) { 
      this.lastOrderAt = value; 
      return this; 
    }
    
    public Builder totalOrderValue(double value) { 
      this.totalOrderValue = value; 
      return this; 
    }

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

Standardwerte

Eigenwerbung abfangen

Bestellwert niemals negativ

Java

...den Wald vor lauter Bäumen nicht sehen

Softwareentwickler verbringen sehr viel Zeit mit der Suche nach den Stellen die wir verändern oder erweitern wollen

Eine Zeile Code weniger die wir schreiben,

ist eine Zeile Code weniger in der ein Fehler auftreten kann

public final class Customer {
  private final int customerNr;
  private String name;
  private final Integer referredBy;       // Builder default: null
  private final Instant becameCustomerAt; // Builder default: Instant.now()
  private Instant lastOrderAt;            // Builder default: null
  private double totalOrderValue;         // Builder default: 0.0

 private Customer(Builder builder) {
    if (builder.customerNr == builder.referredBy) {
      throw new IllegalArgumentException("Customers can not refer themselves");
    }

    customerNr = builder.customerNr;
    name = builder.name;
    referredBy = builder.referredBy;
    becameCustomerAt = builder.becameCustomerAt;
    lastOrderAt = builder.lastOrderAt;

    setTotalOrderValue(builder.totalOrderValue);
  }

  public void setName(String value) { 
    this.name = Objects.requireNonNull(value); 
  }
  
  public void setLastOrderAt(Instant value) { 
    this.lastOrderAt = value; 
  }

  public void setTotalOrderValue(double value) {
    if (value < 0.0) { 
      throw new IllegalStateException("Can not set totalOrderValue to less than 0.0"); 
    }
    this.totalOrderValue = value;
  }

  public int getCustomerNr() { 
    return customerNr; 
  }

  public String getName() { 
    return name; 
  }
  
  public int getReferredBy() { 
    return referredBy; 
  }
  
  public Instant getBecameCustomerAt() { 
    return becameCustomerAt; 
  }
  
  public Instant getLastOrderAt() { 
    return lastOrderAt; 
  }

  public double getTotalOrderValue() { 
    return totalOrderValue; 
  }

  @Override public String toString() {
    return String.format("Customer(customerNr = %d, name = %s ...)", customerNr, name);
  }

  @Override public boolean equals(Object other) {
    if (other instanceof Customer) {
      return ((Customer) other).customerNr == customerNr;
    }
    return false;
  }

  @Override public int hashCode() { 
    return Integer.hashCode(customerNr); 
  }
  
  public static class Builder {
    // Required
    private final int customerNr;
    private final String name;
    // Optional
    private Integer referredBy = null;
    private Instant becameCustomerAt = Instant.now();
    private Instant lastOrderAt = null;
    private double totalOrderValue = 0.0;

    public Builder(int customerNr, String name) {
      this.customerNr = Objects.requireNonNull(customerNr);
      this.name = Objects.requireNonNull(name);
    }

    public Builder referredBy(int value) {  
      this.referredBy = value; 
      return this; 
    }

    public Builder becameCustomerAt(Instant value) { 
      this.becameCustomerAt = value; 
      return this; 
    }

    public Builder lastOrderAt(Instant value) { 
      this.lastOrderAt = value; 
      return this; 
    }
    
    public Builder totalOrderValue(double value) { 
      this.totalOrderValue = value; 
      return this; 
    }

    public Customer build() { return new Customer(this); }
  }
}
class Customer(
    val customerNr: Int,
    var name: String,
    val referredBy: Int? = null,
    val becameCustomerAt: Instant = Instant.now(),
    var lastOrderAt: Instant? = null,
    totalOrderValue: Double = 0.0) {

  var totalOrderValue: Double = 0.0
    set(value) {
      require(value >= 0.0) { 
        "Can not set totalOrderValue to less than 0.0" 
      }
      field = value
    }

  init {
    require(referredBy != customerNr) { 
      "Customers can not refer themselves" 
    }
    this.totalOrderValue = totalOrderValue
  }

  override fun toString() = 
      "Customer(customerNr = $customerNr, name = $name)"

  override fun equals(other: Any?) = when (other) {
    is Customer -> other.customerNr == customerNr
    else -> false
  }

  override fun hashCode() = customerNr.hashCode()
}

Java

Kotlin

Setter-Aufruf

val customer = Customer(1, "Peter", referredBy = 2)
val customer = Customer(
  customerNr = 1, 
  name = "Peter", 
  becameCustomerAt = Instant.parse("2019-01-28T11:00:00Z")
)

besser:

 

  • Parametername explizit angegeben
    • erlaubt hinzufügen von neuen Parametern an beliebiger Stelle
    • erlaubt umsortieren von Parametern ohne Aufruf zu ändern

Kotlin

Kotlin

Developer Happiness

Safe

Fehlerklassen vermeiden

"I couldn't resist the temptation to put in a null reference [into ALGOL W], simply because it was so easy to implement."
 

"I call it my billion-dollar mistake"


- Tony Hoare at QCon 2009

Nullable Types

NullPointerExceptions verhindern?

  • JSR 305 (dormant) "Annotations for Software Defect Detection" (@Nonnull)
  • Findbugs
  • Lombok @NonNull
  • JetBrains Annotations (@Nullable und @NotNull)

In Java ist alles "Nullable by default"

 

Nullable Types in Kotlin

Null muss explizit behandelt werden

val hello: String = null
// Error: Null can not be a value of a non-null type String
fun sayHello(name: String) {
  println("Hello $name")
}
val hello: String? = null
// ✓
sayHello(null)            
// Error: Null can not be a value of a non-null type String

Kotlin

Kotlin

Kotlin

Kotlin

fun getReferrer(customer: Customer): Customer? {
  return findCustomerByNr(customer.referredBy) 
}
// Error: Type mismatch. Required Int, Found Int?
fun findCustomerByNr(customerNr: Int): Customer? { ... }

Kotlin

Kotlin

Nullable Types in Kotlin

if (customer.referredBy == null) {
  return null
}

findCustomerByNr(customer.referredBy) // Kompiliert

Der Compiler merkt sich null-Checks

customer.referredBy ?: return null

findCustomerByNr(customer.referredBy)

Der "Elvis" Operator 

Kotlin

Kotlin

Nullable Types in Kotlin

fun findCustomerByNr(customerNr: Int): Customer? { ... }

Kotlin

Minimize mutablilty

  1. Don't provide methods that modify the object's state
  2. Ensure that classes can't be extended
    • Vererbung nur an speziell definierten und dokumentierten Punkten 
  3. Make all fields final
  4. Make all fields private, access through getters
  5. Ensure exclusive access to any mutable components

    • keine Referenz auf interne Daten rausgeben wenn diese veränderbar sind 

Item 17 aus "Effective Java"

Data classes

data class OrderReceived(
  val orderNr: Int,
  val customerNr: Int,
  val totalValue: Double,
  val receivedAt: Instant = Instant.now(),
)
  • Alle Felder mit val sind nicht veränderbar
  • Von einer data class kann nicht geerbt werden
  • Die generierte Methode copy(...) liefert ein neues Objekt zurück
  • Zusätzlich generierte Methoden
    • Getter für jedes Feld
    • toString()
    • equals() und hashCode()

Kotlin

Pattern matching und Smart casts

sealed class InvitationResponse {
  object Available : InvitationResponse()
  data class ConflictWithOtherEvent(val otherEventName: String) : InvitationResponse()
}

fun autoReply(response: InvitationResponse): String {
  return when (response) {
    is Available              -> "Great, see you there"
    is ConflictWithOtherEvent -> "Have fun at ${response.otherEventName}"
  }
}

else wird nicht benötigt, da alle Subklassen bekannt sind

sealed: Subklassen können nur in dieser Datei deklariert werden

Zugriff auf otherEventName ohne expliziten Cast

Kotlin

Pattern matching und Smart casts

sealed class InvitationResponse {
  object Available : InvitationResponse()
  object ConflictWithPrivateEvent : InvitationResponse()
  data class ConflictWithOtherEvent(val otherEventName: String) : InvitationResponse()
}


fun autoReply(response: InvitationResponse): String {
  return when (response) {
    is Available              -> "Great, see you there"
    is ConflictWithOtherEvent -> "Have fun at ${response.otherEventName}"
  }
}

Error: when expression must be exhaustive, add necessary ConflictWithPrivateEvent branch or else branch instead

Neue Subklasse ConflictWithPrivateEvent

Kotlin

Standardmäßig sicher(er)

Interoperable

Von Kotlin zu Java und zurück

Java in Kotlin nutzen

Funktioniert im wesentlichen problemlos

Beispiel wie Kotlin mit Unsicherheit aus Java Code umgeht:

public interface ReplyGenerator {
  public String createReply();
}
fun printer(replyGenerator: ReplyGenerator) {
  val reply = replyGenerator.createReply()
  ...
}

IntelliJ markiert reply als String! da unklar ist ob es String oder String? ist

Kotlin

Java

Bekannte Tools und Frameworks weiternutzen

 

Frameworks und Tools mit Kotlin Support






 

Entwickler von Bibliotheken bieten Erweiterungen für Kotlin an

 

 

 

Jackson

Kotlin Code aus Java aufrufen

class Customer @JvmOverloads constructor(
  val customerNr: Int
  var name: String = "Neuer Kunde"
)
Customer customer = new Customer(1000);
customer.getName(); // "Neuer Kunde"
customer.setName("Meyer");
fun withConnection(
  dbName: String, 
  execute: (Connection) -> Unit): {
  ...
}
fun String.toInstant() = Instant.parse(this)
DBKt.withConnection("customerDb", connection -> {
  ...
});
StringOpsKt.toInstant("2019-01-28T11:00:00Z");

Kotlin Klassen in Java verwenden

Top-level funktion in DB.kt

Extension function in StringOps.kt

Kotlin

Java

Kotlin

Java

Java

Kotlin

Kotlin ist keine Magie

Evolution in Kotlin

1.3

1.4

1.5

1.0

1.1

1.2

1.3

1.0

1.1

1.2

Version

Binär-kompatibel mit

Compiler

Auf den Schultern von Riesen

Getting started

Photo by David Bartus

Kotlin Playground

Kotlin Koans

Automatische Konvertierung in IntelliJ

Weitere Informationsquellen

Vielen Dank für Ihre Aufmerksamkeit

Kotlin

By Niklas Lochschmidt

Kotlin

Das moderne Java

  • 1,241