Niklas Lochschmidt
Backend Developer at BRYTER GmbH
Software Entwickler bei
@Niklas_L
Mehr dazu auf bryter.io
Photo by Sarita Rungsakorn
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
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")
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
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
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
Customer |
---|
customerNr: Int name: String referredBy: Int [0..1] becameCustomerAt: Instant = now() lastOrderAt: Instant [0..1] totalOrderValue: Double = 0.0 |
setTotalOrderValue(Double) |
Customer customer = new Customer(1, "Peter", 2, Instant.now(), null 0.0);
Fragen:
referredBy
?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
customerNr
und name
werden nicht benanntBuilder
und build()
ist RauschenJava
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:
Kotlin
Kotlin
"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
NullPointerExceptions verhindern?
In Java ist alles "Nullable by default"
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
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
fun findCustomerByNr(customerNr: Int): Customer? { ... }
Kotlin
Ensure exclusive access to any mutable components
keine Referenz auf interne Daten rausgeben wenn diese veränderbar sind
Item 17 aus "Effective Java"
data class OrderReceived(
val orderNr: Int,
val customerNr: Int,
val totalValue: Double,
val receivedAt: Instant = Instant.now(),
)
val
sind nicht veränderbardata class
kann nicht geerbt werdencopy(...)
liefert ein neues Objekt zurücktoString()
equals()
und hashCode()
Kotlin
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
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
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
Frameworks und Tools mit Kotlin Support
Entwickler von Bibliotheken bieten Erweiterungen für Kotlin an
Jackson
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
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
Photo by David Bartus
Vielen Dank für Ihre Aufmerksamkeit
By Niklas Lochschmidt
Das moderne Java