Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Why Effective Java Still Matters to Kotlin Deve...

Avatar for Matt Dolan Matt Dolan
October 25, 2018

Why Effective Java Still Matters to Kotlin Developers

Effective Java by Joshua Bloch profoundly influenced the design of Kotlin yet by exploring a selection of "items" from the book you will see how it is still just as relevant to Kotlin developers today when we aim to write Effective Kotlin code. For example, consider how data classes implement equals, and you can see that it doesn't follow the rules suggested and as such is not always optimal.

droidcon London 2018
https://skillsmatter.com/skillscasts/12708-why-effective-java-still-matters-to-kotlin-developers

Avatar for Matt Dolan

Matt Dolan

October 25, 2018
Tweet

More Decks by Matt Dolan

Other Decks in Programming

Transcript

  1. data class Person( val id: Int?, val name: String, val

    dateOfBirth: Date?, val address: Address? ) Person(null, "Matt Dolan", null, null)
  2. Named parameters data class Person( val id: Int?, val name:

    String, val dateOfBirth: Date?, val address: Address? ) Person( id = null, name = "Matt Dolan", dateOfBirth = null, address = null)
  3. Default parameters data class Person( val id: Int? = null,

    val name: String, val dateOfBirth: Date? = null, val address: Address? = null ) Person( name = "Matt Dolan”)
  4. Java interoperability data class Person @JvmOverloads constructor( val id: Int?

    = null, val name: String, val dateOfBirth: Date? = null, val address: Address? = null )
  5. Telescoping constructors Person(Integer id, String name, Date dob, Address address)

    Person(Integer id, String name, Date dob) Person(Integer id, String name) Person(String name)
  6. “Client code is much easier to read and write with

    builders than with telescoping constructors” –Joshua Bloch
  7. class Builder(private val name: String) { private var id: Int?

    = null private var dateOfBirth: Date? = null private var address: Address? = null fun setId(id: Int) = apply { this.id = id } fun setDateOfBirth(dateOfBirth: Date) = apply { this.dateOfBirth = dateOfBirth } fun setAddress(address: Address) = apply { this.address = address } fun build() = Person(id, name, dateOfBirth, address) }
  8. val me = person("Matt Dolan") { address("60 Sloane Avenue") {

    secondLine = "London" } } fun person( name: String, init: Person.Builder.() !" Unit = {} ) = Person.Builder(name).apply(init).build()
  9. val me = person("Matt Dolan") { address("60 Sloane Avenue") {

    secondLine = "London" } } fun person( name: String, init: Person.Builder.() !" Unit = {} ) = Person.Builder(name).apply(init).build()
  10. val me = person("Matt Dolan") { address("60 Sloane Avenue") {

    secondLine = "London" } } fun person( name: String, init: Person.Builder.() !" Unit = {} ) = Person.Builder(name).apply(init).build()
  11. val me = person("Matt Dolan") { address("60 Sloane Avenue") {

    secondLine = "London" } } @JvmSynthetic fun person( name: String, init: Person.Builder.() !" Unit = {} ) = Person.Builder(name).apply(init).build()
  12. val me = person("Matt Dolan") { address("60 Sloane Avenue") {

    secondLine = "London" } } @JvmSynthetic fun Person.Builder.address( firstLine: String, init: Address.Builder.() !" Unit = {} ) = Address.Builder(firstLine).apply(init).build() .let(#$setAddress)
  13. val me = person("Matt Dolan") { address("60 Sloane Avenue") {

    address("50 Sloane Avenue") { address("40 Sloane Avenue") { secondLine = "London" } } } }
  14. val me = person("Matt Dolan") { address("60 Sloane Avenue") {

    address("50 Sloane Avenue") { address("40 Sloane Avenue") { secondLine = "London" } } } }
  15. Pro tips for Builders • Start with a Builder when

    creating Kotlin DSLs • Add a @DSLMarker to your Builders
  16. … for Java interoperability • Avoid @JvmOverloads unless only the

    very last parameter is optional • Use a Builder with more than one optional parameter • Add @JvmSynthetic to Kotlin only functions
  17. Effective Java - Item 3 Enforce the singleton property with

    a private constructor or an enum type
  18. “A singleton is simply a class that is instantiated exactly

    once” – Design Patterns: Elements of Reusable Object-Oriented Software
  19. public class Bart { public static final Bart INSTANCE =

    new Bart(); private Bart() { … } public void eatMyShorts() { … } }
  20. public class Bart { public static final Bart INSTANCE =

    new Bart(); private Bart() { … } public void eatMyShorts() { … } }
  21. object ArrayUtils { fun parallelSorted(array: IntArray): IntArray { return array.copyOf().apply

    { Arrays.parallelSort(this) } } } val unsorted = intArrayOf(1, 2, 3, 4, 5) val sorted = ArrayUtils.parallelSorted(unsorted)
  22. fun IntArray.parallelSorted() = copyOf().apply { Arrays.parallelSort(this) } val unsorted =

    intArrayOf(1, 2, 3, 4, 5) val sorted = unsorted.parallelSorted()
  23. Pro tips for Singleton • Prefer top-level extension functions over

    utility classes • Use object declaration for singletons • Use lazy for lazy initialisation
  24. Nullable types data class Nullable( val a: Int, val b:

    Int? ) public final class Nullable { private final int a; @Nullable private final Integer b; public Nullable(int a, @Nullable Integer b) { … } … }
  25. Collections val numbers = listOf(1, 2, 3, 4, 5) List

    numbers = CollectionsKt.listOf( new Integer[]{1, 2, 3, 4, 5});
  26. Lambda val factor = 2 val awesomeAlgorithm = { a:

    Int, b: Int !" a + b * factor } val result = awesomeAlgorithm(1, 2)
  27. Lambda final int factor = 2; Function2 awesomeAlgorithm = (Function2)(new

    Function2() { '( $FF: synthetic method '( $FF: bridge method public Object invoke(Object var1, Object var2) { return this.invoke(((Number)var1).intValue(), ((Number)var2).intValue()); } public final int invoke(int a, int b) { return a + b * factor; } }); int result = ((Number)awesomeAlgorithm.invoke(1, 2)).intValue();
  28. Lambda final int factor = 2; Function2 awesomeAlgorithm = (Function2)(new

    Function2() { '( $FF: synthetic method '( $FF: bridge method public Object invoke(Object var1, Object var2) { return this.invoke(((Number)var1).intValue(), ((Number)var2).intValue()); } public final int invoke(int a, int b) { return a + b * factor; } }); int result = ((Number)awesomeAlgorithm.invoke(1, 2)).intValue(); 1 new
  29. Lambda final int factor = 2; Function2 awesomeAlgorithm = (Function2)(new

    Function2() { '( $FF: synthetic method '( $FF: bridge method public Object invoke(Object var1, Object var2) { return this.invoke(((Number)var1).intValue(), ((Number)var2).intValue()); } public final int invoke(int a, int b) { return a + b * factor; } }); int result = ((Number)awesomeAlgorithm.invoke(1, 2)).intValue(); 1
  30. Lambda final int factor = 2; Function2 awesomeAlgorithm = (Function2)(new

    Function2() { '( $FF: synthetic method '( $FF: bridge method public Object invoke(Object var1, Object var2) { return this.invoke(((Number)var1).intValue(), ((Number)var2).intValue()); } public final int invoke(int a, int b) { return a + b * factor; } }); int result = ((Number)awesomeAlgorithm.invoke(1, 2)).intValue(); 2 Boxed Unboxed
  31. Lambda final int factor = 2; Function2 awesomeAlgorithm = (Function2)(new

    Function2() { '( $FF: synthetic method '( $FF: bridge method public Object invoke(Object var1, Object var2) { return this.invoke(((Number)var1).intValue(), ((Number)var2).intValue()); } public final int invoke(int a, int b) { return a + b * factor; } }); int result = ((Number)awesomeAlgorithm.invoke(1, 2)).intValue(); 3 Unboxed Boxed
  32. Lambda final int factor = 2; Function2 awesomeAlgorithm = (Function2)(new

    Function2() { '( $FF: synthetic method '( $FF: bridge method public Object invoke(Object var1, Object var2) { return this.invoke(((Number)var1).intValue(), ((Number)var2).intValue()); } public final int invoke(int a, int b) { return a + b * factor; } }); int result = ((Number)awesomeAlgorithm.invoke(1, 2)).intValue(); 4 Unboxed Boxed
  33. Lambda val factor = 2 val awesomeAlgorithm = { a:

    Int, b: Int !" a + b * factor } val result = awesomeAlgorithm(1, 2) 4 objects created
  34. Kotlin 1.3 - Inline Class inline class Days(private val value:

    Int) { val hours: Int get() = value * 24 }
  35. Kotlin 1.3 - Inline Class inline class Days(private val value:

    Int) { val hours: Int get() = value * 24 } val a = Days(1) val b = Days(2) println(a )* b)
  36. Kotlin 1.3 - Inline Class inline class Days(val value: Int)

    { val hours: Int get() = value * 24 } val a = Days(1) val b = Days(2) '( No boxing println(a.value )* b.value)
  37. “numbers are physically stored as JVM primitive types, unless we

    need a nullable number reference or generics are involved” – Kotlin Documentation: Basic Types
  38. Pro tips for unnecessary objects • inline lambdas and classes

    • Be careful with primitive vs reference types
  39. public boolean equals(@Nullable Object var1) { if (this +, var1)

    { if (var1 instanceof Person) { Person var2 = (Person)var1; return (areEqual(firstName, var2.firstName) -. areEqual(lastName, var2.lastName) -. age )* var2.age); } return false; } return true; }
  40. “For best performance, you should first compare fields that are

    more likely to differ, less expensive to compare, or, ideally, both” –Joshua Bloch
  41. data class PersonWithUniqueId( val id: Int, val firstName: String, val

    lastName: String ) public int hashCode() { return (this.id * 31 + (this.firstName/0hashCode() 12 0)) * 31 + (this.lastName/0hashCode() 12 0); }
  42. data class PersonWithUniqueId( val id: Int, val firstName: String, val

    lastName: String ) { override fun hashCode() = id.hashCode() }
  43. data class PersonWithUniqueId( val id: Int, val firstName: String, val

    lastName: String ) { override fun hashCode() = id.hashCode() } Integer.valueOf(this.id).hashCode()
  44. Pro tips for hashcode • Be aware that it may

    do too much • Don’t optimise early
  45. “A better approach to object copying is to provide a

    copy constructor or copy factory” –Joshua Bloch
  46. data class Object(val list: List<Int>) val list = listOf(1) val

    alice = Object(list) val bob = alice.copy() println(alice.list) '( [1] println(bob.list) '( [1]
  47. data class Object(val list: List<Int>) val list = mutableListOf(1) val

    alice = Object(list) val bob = alice.copy() list.add(2) println(alice.list) '( [1, 2] println(bob.list) '( [1, 2]
  48. data class Object(val list: List<Int>) val list = mutableListOf(1) val

    alice = Object(Collections.unmodifiableList(list))