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))
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
}
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