Examples of Kotlin making your Java code better

Examples of Kotlin making your Java code better

Andy Balaam
artificialworlds.net/blog

Kotlin

  • Very Java-compatible
  • Familiar to Java programmers
  • Less code
  • Better code

Kotlin

  • Very Java-compatible
  • Familiar to Java programmers
  • Less code
  • Better code

Things that are good

  • Separate values and algorithms
  • Immutability
  • Errors caught at compile time
  • Declarative style
  • Less code

Gentle nudges

  • Easy to do the right thing
  • Bad things are more visible

Good patterns

  • Kotlin codifies patterns that we have learned make good Java
  • ... and makes them easy

Examples

Image by MRCZMT [CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0)], from Wikimedia Commons

Value objects

public class Thing { private final String name; private final String desc; public Thing(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } // ...

Value objects

public class Thing { // ... @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((desc == null) ? 0 : desc.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } // ...

Value objects

public class Thing { // ... @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Thing other = (Thing) obj; if (desc == null) { if (other.desc != null) { return false; } } else if (!desc.equals(other.desc)) { return false; } if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } return true; } // ...

Value objects

public class Thing { // ... @Override public String toString() { return "Thing(name=" + name + ", desc=" + desc + ")"; } // ...

Value objects

  • 14 mentions of the word "name"
  • Each essentially means "this object has a property called name"

Value objects

data class ThingK(val name: String, val desc: String)
  • Same meaning as the Java
  • More likely to be correct

Parameters

public class Message { final String id; final String customer; final String to; final String from; final String body; public Message(String id) { this(id, null, null, null, null); } public Message forCustomer(String customer) { return new Message(... } public Message withTo(String to) { // ...

Parameters

Message m = new Message("234") .withTo("012") .forCustomer("Acme");

Parameters

data class MessageK( val id: String, val customer: String? = null, val to: String? = null, val from: String? = null, val body: String? = null )

Parameters

val m = MessageK("234", to="012", customer="Acme")
  • Direct expression of what we meant
  • More likely to be correct

Null safety

public class Scorer { static final Map<String, Integer> wordValues = new HashMap<>(); static { wordValues.put("purple", 3); wordValues.put("lavender", 5); wordValues.put("amethyst", 50); } // ...

Null safety

public class Scorer { // ... final Map<String, Integer> scores = new HashMap<>(); void scoreRequest(String user, String path) { scores.merge( user, wordValues.get(path), Integer::sum ); } int total(String user) { return scores.get(user); }

Null safety

@Test public void Different_users_are_independent() { Scorer s = new Scorer(); s.scoreRequest("user1", "purple"); s.scoreRequest("user2", "purple"); s.scoreRequest("user1", "lavendar"); // CRASH assertThat(s.total("user1"), equalTo(8)); assertThat(s.total("user2"), equalTo(3)); }

Null safety

private val wordValues = mapOf( "purple" to 3, "lavender" to 5, "amethyst" to 50 )

Null safety

class ScorerK { val scores = mutableMapOf<String, Int>() fun scoreRequest(user: String, path: String) { scores.merge( user, wordValues[path] ?: 0, Integer::sum ) } fun total(user: String): Int { return scores[user] ?: 0 } }

Null safety

class ScorerK { val scores = mutableMapOf<String, Int>() fun scoreRequest(user: String, path: String) { scores.merge( user, wordValues[path] ?: 0, Integer::sum ) } fun total(user: String): Int { return scores[user] ?: 0 } }

Null safety

  • If this were the only thing in Kotlin, it would be worth it.

Immutability

public class Emitter { public void handleMessage(// ... long start, end; // ... start = System.currentTimeMillis(); String url = urls.get(m.to); if (url == null) { // ... } for(start = 0; start < 3; start++) { // ... } end = System.currentTimeMillis(); System.out.println(m.id + ": " + (end-start));

Immutability

public class Emitter { public void handleMessage(// ... final long start, end; // ... start = System.currentTimeMillis(); String url = urls.get(m.to); if (url == null) { // ... } for(start = 0; start < 3; start++) { ERROR! // ... } end = System.currentTimeMillis(); System.out.println(m.id + ": " + (end-start));

Immutability

class EmitterK { fun handleMessage(// ... val start: Long val end: Long // ... start = System.currentTimeMillis() val url = urls[m.to] if (url == null) { // ... } for(start in 0..3) { // ... } end = System.currentTimeMillis() println(m.id + ": " + (end - start))

Immutability

class EmitterK { fun handleMessage(// ... val start: Long val end: Long // ... start = System.currentTimeMillis() val url = urls[m.to] if (url == null) { // ... } for(start in 0..3) { // Warning: name shadowed // ... } end = System.currentTimeMillis() println(m.id + ": " + (end - start))

Immutability

  • Sane by default

Handling cases

long reattemptTs = NEVER; switch (code) { case 451: case 2020: reattemptTs = now + (ONE_HOUR * tries); break; case 711: case 1522: reattemptTs = now + ONE_MINUTE; break; case 2: default: reattemptTs = NEVER; break; } return reattemptTs;

Handling cases

return when (code) { 451, 2020 -> now + (ONE_HOUR * tries) 711, 1522 -> now + ONE_MINUTE 2 -> NEVER else -> NEVER }
  • Harder to make mistakes

Handling cases

sealed class Response(val code: Long); class Ok : Response(0) class FatalError(code: Long) : Response(code) class FastError(code: Long) : Response(code) class SlowError(code: Long) : Response(code) // ... return when (response) { is SlowError -> now + (ONE_HOUR * tries) is FastError -> now + ONE_MINUTE is FatalError -> null is Ok -> null }
  • Language supports meaningful constructs

Handling cases

return when (code) { 451, 2020 -> now + (ONE_HOUR * tries) 711, 1522 -> now + ONE_MINUTE 2 -> NEVER else -> NEVER }
  • Say what you mean (declarative style)

Constructors

class Requester { Requester(String scheme, String host, HttpClient client) { this.scheme = scheme; this.host = host; this.client = client; } Requester( String user, String pass, String host, boolean useSsl) { this(useSsl, host, new SimpleClient(user, pass)); } Requester(boolean useSsl, String host, HttpClient client) { this(useSsl ? "https" : "http", host, client); }

Constructors

class RequesterK( val scheme: String, val host: String, val client: HttpClient ) { constructor( user: String, pass: String, host: String, useSsl: Boolean) : this(useSsl, host, SimpleClient(user, pass)) constructor( useSsl: Boolean, host: String, client: HttpClient) : this(if (useSsl) "https" else "http", host, client)

Constructors

  • Primary constructor explains the object's fundamental nature
  • Primary is often the one needed in tests
  • Nudges towards good style

Streams

static final List<String> hotDrinks = Arrays.asList("tea", "coffee"); public List<String> hotCustomers() { return customers.stream() .filter( cust -> hotDrinks.stream().anyMatch( drink -> cust.drink.contains(drink) ) ) .map(Customer::getName) .collect(Collectors.toList()); }

Streams

val hotDrinks = listOf("tea", "coffee") fun hotCustomers(): List<String> { return customers .filter { cust -> hotDrinks.any { cust.drink.contains(it) } } .map {it.name} }

Summary

  • Common patterns are easy to do right, e.g.
    • Value objects, immutable variables, streams
    • Construction and parameters
  • Null handling is:
    • explicit and mandatory,
    • easy
  • Say what you mean, e.g.
    • Case expressions
    • Declarative style

Wish list

  • Marking functions pure
  • Deep immutability




Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.