@title[Kotlin vs Java]
Modern, Less code, More readable, Smarter
@title[Kotlin fixes]
Kotlin fixes a series of issues that Java suffers from
@ul
- Null references are controlled by the type system
- No raw types
- Arrays in Kotlin are invariant
- Kotlin has proper function types, as opposed to Java's SAM-conversions
- Use-site variance without wildcards
- Kotlin does not have checked exceptions
@ulend
@ul
- Null-safety
- Smart casts
- Singletons + Companion objects
- Extension functions
- Range expressions
- Data classes
- String templates
- Other cool stuff 🍿
@ulend
- Lambda expressions + Inline functions
- First-class delegation
- Type inference for variable and property types
- Declaration-site variance & Type projections
- Operator overloading
- Separate interfaces for read-only and mutable collections
- Coroutines
@ul
- Checked exceptions
- Primitive types that are not classes
- Static members
- Non-private fields
- Wildcard-types
- Ternary-operator a ? b : c
@ulend
@title[Syntax]
Kotlin
fun main(args: Array<String>) {
println("Hello, ${args[0]}")
}
Java
public static final void main(String[] args) {
System.out.println(String.format("Hello, %s", args[0]))
}
@title[Kotlin variable syntax]
val & var Properties
// Explicit
const val hello: String = "World" // Compile time constant
val hello: String = "World" // Runtime constant
var android: String = "Kotlin"
// Implicit
val hello = "World" // Runtime constant
var android = "Kotlin"
// Nullable ?
var name: String? = null
// Lateinit
lateinit var name: String // Assign later in code, can't be null
@title[Kotlin vs Java syntax]
Dry, clean, readable
Kotlin
val file = File()
Java
public static final File file = new File()
class Person(val name: String) { // Primary constructor in header
constructor(name: String, parent: Person) : this(name) { // Secondary constructor
...
}
}
Classes and functions are by default always final and must have open modifier to be inherited or overridden.
open class A {
open fun foo(i: Int = 10) { }
}
class B : A() {
override fun foo(i: Int) { } // no default value allowed
}
fun multiply(multiplicator: Int, multiplicand: Int): Int {
return multiplicator * multiplicand
}
Usage
multiply(1,2)
multiply(multiplicator = 1, multiplicand = 2)
multiply(multiplicand = 2, multiplicator = 1)
@title[Null safe quote]
"I call it my billion-dollar mistake. It was the invention of the null reference in 1965."
Sir Tony Hoare
public String getLastFour(Employee employee) {
if(employee != null) {
Address address = employee.getPrimaryAddress();
if(address != null) {
ZipCode zip = address.getZipCode();
if(zip != null) {
return zip.getLastFour()
}
}
}
throw new Exception("Missing data");
}
public String getLastFour(Optional<Employee> employee) {
return employee.flatMap(employee -> employee.getPrimaryAddress())
.flatMap(address -> address.getZipCode())
.flatMap(zip -> zip.getLastFour())
.orElseThrow(() -> new FMLException("Missing data"));
}
Can be as one line single expression
fun getLastFour(employee: Employee?) =
employee?.address?.zip?.lastFour
?: throw Exception("Missing data")
var a: String = "abc"
a = null // compilation error
var b: String? = "abc"
b = null // ok
Usage
val l = a.length // ok
val l = b.length // error: variable 'b' can be null
Explicitly check if b is null
val l = if (b != null) b.length else -1
Safe call operator, ?.
b?.length
?:
val name = bob?.department?.head?.name ?: "Unknown"
Throws an NPE if b is null
val l = b!!.length
Safe casts that return null
val aInt: Int? = a as? Int
Smart cast, no ((String)x).length
fun demo(x: Any) {
if (x is String) {
print(x.length) // x is automatically cast to String
}
}
MySingleton.kt
object MySingleton { // Note object
fun add(number: Int): Int = 10 + number
}
MySingleton+Extensions.kt
fun MySingleton.multiply(multiplicand: Int, multiplicator: Int) -> Int {
return multiplicand * multiplicator
}
Usage
val sum = MySingleton.add(2)
val product = MySingleton.multiply(1, 2)
for (i in 1..4) print(i) // prints "1234"
for (i in 4..1) // prints nothing
for (i in 4 downTo 1) // prints "4321"
for (i in 1..4 step 2) // prints "13"
for (i in 4 downTo 1 step 2) // prints "42"
for (i in 1 until 10) // i in [1, 10), 10 is excluded
We frequently create classes whose main purpose is to hold data. In such a class some standard functionality and utility functions are often mechanically derivable from the data.
data class Person(val name: String, val age: Int)
equals(), hashCode(), toString(), copy()
+++
val jane = Person("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"
+++
data class Person(val name: String, val age: Int)
public class Person {
private String name;
private int age = 0;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (name != null ? !name.equals(person.name) : person.name != null) return false;
if (age != 0 ? age != person.age : person.age != 0) return false;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
Traversing a map/list of pairs
for ((k, v) in map) {
println("$k -> $v")
}
+++
Execute if not null
val value = ...
value?.let {
... // execute this block if not null
}
+++
Return on when statement
fun transform(color: String): Int {
return when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
}
or
fun transform(color: String) = when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
+++
'if' expression
fun foo(i: Int) {
val result = if (i == 1) {
"one"
} else if (i == 2) {
"two"
} else {
"three"
}
}
+++
Typealias
typealias JsonData = String
typealias StatusCode = Int
typealias Error = String
typealias Success = (JsonData) -> Unit
typealias Error = (StatusCode, Error) -> Unit
+++
Generics
inline fun <reified T : Any> loadStyle(inputStream: InputStream): T {
return Gson().fromJson(InputStreamReader(inputStream), object : TypeToken<T>() {}.type)
}
Java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
}
+++
Kotlin
No findViewById or Butterknife needed
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
}
}
@title[Links]
Frost Android Kotlin Style Guide
@title[The End]
Ok 👋