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

How "Effective Java" influenced Kotlin

Lukas Lechner
September 29, 2017

How "Effective Java" influenced Kotlin

Lukas Lechner

September 29, 2017
Tweet

More Decks by Lukas Lechner

Other Decks in Programming

Transcript

  1. + =

  2. public class NutritionFacts { private final int servingSize; // required

    private final int servings; // required private final int calories; optional private final int fat; // optional private final int sodium; // optional private final int carbohydrate; // optional } • Telescoping Constructor Pattern • JavaBeans Pattern Item 2: “Consider a builder, when faced with many constructor parameters”
  3. The Builder Pattern • Client calls constructor with all of

    the required parameters • Gets a builder object • Client calls setter-like method on the builder object • Client calls build() method to generate the immutable object NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8) .calories(100) .sodium(35) .carbohydrate(27) .build();
  4. class KotlinNutritionFacts( private val servingSize: Int, private val servings: Int,

    private val calories: Int = 0, private val fat: Int = 0, private val sodium: Int = 0, private val carbohydrates: Int = 0) Default Values in Kotlin
  5. Creating an object with named arguments val cocaCola = KotlinNutritionFacts(

    servingSize = 240, servings = 8, calories = 100, sodium = 35, carbohydrates = 27)
  6. Item Summary: • Effective Java recommends to use the builder

    pattern • Boilerplate • Builder Pattern not needed in Kotlin • Default Parameter Values and Named Arguments + =
  7. Bonus Tip: Builder-Style APIs with Kotlin’s apply{ } TextView textView

    = new TextView(context); textView.setId(99); textView.setText("Hello World"); textView.setTextSize(16f); val TextView = TextView(context).apply { id = 99 text = "Hello World" textSize = 16f }
  8. How to create a Singleton in Java public class Elvis

    { private static final Elvis INSTANCE = new Elvis(); private Elvis() { } public static Elvis getInstance() { return INSTANCE; } public void leaveTheBuilding() { System.out.println("Whoa baby, I'm outta here!"); } }
  9. Utility Classes in Java public class Arrays { // Suppresses

    default constructor, ensuring non-instantiability. private Arrays() {} ... public static void sort(int[] a) { ... } ... }
  10. Item Summary: • Non instantiability needs to be enforced because

    every function in Java must belong to a class • Kotlin: Top Level functions, no private constructor needed + =
  11. public class Sum { // Hideously slow program! public static

    void main(String[] args) { Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); } } Slow Java Program
  12. public class Sum { // Hideously slow program! public static

    void main(String[] args) { Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); } } Slow Java Program
  13. public class Sum { // Hideously slow program! public static

    void main(String[] args) { // primitive long instead of the Long reference type long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); } } Fast Java Program
  14. Primitive and Reference Types in Java Primitive Types: int, long,

    float, double, byte, … Reference Types: Integer, Long, Float, Double, String, … • Primitives hold values • Primitives hold only functional values • Primitives are more time- and space-efficient
  15. Be cautious about what Type you use in Java •

    Item 5: “Avoid creating unnecessary Objects” • Item 49: “Prefer primitive types to reference types” ◦ Always use primitives unless you are forced to use reference types in ▪ Collections ▪ Type arguments in generic classes
  16. Types in Kotlin • Int • Long • String =>

    Primitive or reference types?
  17. Kotlin Compiler generates objects of primitive or reference types •

    Most of the times, Java primitive types are generated • Exceptions: ◦ objects in collections, ◦ type arguments in generic classes ◦ nullable types! // So we will still have performance issues if we use Long? var sum: Long? = 0L
  18. Item Summary: • We don’t need to care about which

    kind of type we use • Kotlin Compiler makes that decision • Compiler sticks to advice of Effective Java • We cannot make the mistakes you can make in Java, that Effective Java points out like creating unnecessary objects + =
  19. Bonus Tip: Equality in Java and Kotlin • Java ◦

    == ◦ .equals() • Kotlin ◦ == Structural Equality ◦ === Referential Equality
  20. Chapter 3: Methods Common to All Objects • Item 8

    “Obey the general contract when overriding equals” • Item 9 “Always override hashCode when you override equals” • Item 10 “Always override toString()” • Item 11 “Override clone judiciously” • More than pages 20 pages
  21. Chapter Summary: • the whole third chapter shows us how

    to create proper immutable value objects in Java • how to properly implement toHashCode(), equals() and clone() • in Kotlin, the compiler is doing that for us • data class + =
  22. public class JavaPerson { // don't use public fields in

    public classes! public String name; public Integer age; } • we loose all the benefits of encapsulation • we can’t add additional logic in accessors
  23. public class JavaPerson { private String name; private Integer age;

    public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
  24. class KotlinPerson { var name: String? = null var age:

    Int? = null } kotlinPerson.name kotlinPerson.age = 18
  25. class KotlinPerson { var name: String? = null var age:

    Int? = null set(value) { if (value in 0..120){ field = value } else{ throw IllegalArgumentException() } } }
  26. Item Summary: • in Kotlin we can not make the

    mistake of using public fields • Kotlin uses properties instead of fields as first class language feature + =
  27. public class InstrumentedHashSet<E> extends HashSet<E> { // The number of

    attempted element insertions private int addCount = 0; public InstrumentedHashSet() {} @Override public boolean add(E e) { addCount++; return super.add(e); } @Override public boolean addAll(Collection<? extends E> c) { addCount += c.size(); return super.addAll(c); } public int getAddCount() { return addCount;} public static void main(String[] args) { InstrumentedHashSet<String> s = new InstrumentedHashSet<String>(); s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); System.out.println(s.getAddCount()); } }
  28. public class ForwardingSet<E> implements Set<E> { private final Set<E> s;

    public ForwardingSet(Set<E> s) { this.s = s; } public void clear() { s.clear(); } public boolean contains(Object o) { return s.contains(o); } public boolean isEmpty() { return s.isEmpty(); } public int size() { return s.size(); } public Iterator<E> iterator() { return s.iterator(); } public boolean add(E e) { return s.add(e);} public boolean remove(Object o) { return s.remove(o); } public boolean containsAll(Collection<?> c) { return s.containsAll(c);} public boolean addAll(Collection<? extends E> c) {return s.addAll(c);} public boolean removeAll(Collection<?> c) { return s.removeAll(c); } public boolean retainAll(Collection<?> c) { return s.retainAll(c); } ... } Java Forwarding class to enable Composition
  29. public class ForwardingSet<E> implements Set<E> { private final Set<E> s;

    public ForwardingSet(Set<E> s) { this.s = s; } public void clear() { s.clear(); } public boolean contains(Object o) { return s.contains(o); } public boolean isEmpty() { return s.isEmpty(); } public int size() { return s.size(); } public Iterator<E> iterator() { return s.iterator(); } public boolean add(E e) { return s.add(e);} public boolean remove(Object o) { return s.remove(o); } public boolean containsAll(Collection<?> c) { return s.containsAll(c);} public boolean addAll(Collection<? extends E> c) {return s.addAll(c);} public boolean removeAll(Collection<?> c) { return s.removeAll(c); } public boolean retainAll(Collection<?> c) { return s.retainAll(c); } ... } Java Forwarding class to enable Composition
  30. public class ForwardingSet<E> implements Set<E> { private final Set<E> s;

    public ForwardingSet(Set<E> s) { this.s = s; } public void clear() { s.clear(); } public boolean contains(Object o) { return s.contains(o); } public boolean isEmpty() { return s.isEmpty(); } public int size() { return s.size(); } public Iterator<E> iterator() { return s.iterator(); } public boolean add(E e) { return s.add(e);} public boolean remove(Object o) { return s.remove(o); } public boolean containsAll(Collection<?> c) { return s.containsAll(c);} public boolean addAll(Collection<? extends E> c) {return s.addAll(c);} public boolean removeAll(Collection<?> c) { return s.removeAll(c); } public boolean retainAll(Collection<?> c) { return s.retainAll(c); } ... } Java Forwarding class to enable Composition
  31. public class InstrumentedSet<E> extends ForwardingSet<E> { private int addCount =

    0; public InstrumentedSet(Set<E> s) { super(s); } @Override public boolean add(E e) { addCount++; return super.add(e); } @Override public boolean addAll(Collection<? extends E> c) { addCount += c.size(); return super.addAll(c); } public int getAddCount() { return addCount; } public static void main(String[] args) { InstrumentedSet<String> s = new InstrumentedSet<String>(new HashSet<String>()); s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); System.out.println(s.getAddCount()); } } Correctly prints out 3 !
  32. class InstrumentedSet<E>(val set: MutableSet<E>) : MutableSet<E> by set { var

    addCount = 0 override fun add(element: E): Boolean { addCount++ return set.add(element) } override fun addAll(elements: Collection<E>): Boolean { addCount += elements.size return set.addAll(elements) } } fun main(args: Array<String>) { val s = InstrumentedSet(HashSet<String>()) s.addAll(Arrays.asList("Snap", "Crackle", "Pop")) println(s.addCount) // Correctly prints 3 } Class Delegates in Kotlin
  33. class InstrumentedSet<E>(val set: MutableSet<E>) : MutableSet<E> by set { var

    addCount = 0 override fun add(element: E): Boolean { addCount++ return set.add(element) } override fun addAll(elements: Collection<E>): Boolean { addCount += elements.size return set.addAll(elements) } } fun main(args: Array<String>) { val s = InstrumentedSet(HashSet<String>()) s.addAll(Arrays.asList("Snap", "Crackle", "Pop")) println(s.addCount) // Correctly prints 3 } Class Delegates in Kotlin
  34. class InstrumentedSet<E>(val set: MutableSet<E>) : MutableSet<E> by set { var

    addCount = 0 override fun add(element: E): Boolean { addCount++ return set.add(element) } override fun addAll(elements: Collection<E>): Boolean { addCount += elements.size return set.addAll(elements) } } fun main(args: Array<String>) { val s = InstrumentedSet(HashSet<String>()) s.addAll(Arrays.asList("Snap", "Crackle", "Pop")) println(s.addCount) // Correctly prints 3 } Class Delegates in Kotlin
  35. class InstrumentedSet<E>(val set: MutableSet<E>) : MutableSet<E> by set { var

    addCount = 0 override fun add(element: E): Boolean { addCount++ return set.add(element) } override fun addAll(elements: Collection<E>): Boolean { addCount += elements.size return set.addAll(elements) } } fun main(args: Array<String>) { val s = InstrumentedSet(HashSet<String>()) s.addAll(Arrays.asList("Snap", "Crackle", "Pop")) println(s.addCount) // Correctly prints 3 } Class Delegates in Kotlin
  36. class InstrumentedSet<E>(val set: MutableSet<E>) : MutableSet<E> by set { var

    addCount = 0 override fun add(element: E): Boolean { addCount++ return set.add(element) } override fun addAll(elements: Collection<E>): Boolean { addCount += elements.size return set.addAll(elements) } } fun main(args: Array<String>) { val s = InstrumentedSet(HashSet<String>()) s.addAll(Arrays.asList("Snap", "Crackle", "Pop")) println(s.addCount) // Correctly prints 3 } Class Delegates in Kotlin
  37. Item Summary: • Effective Java recommends to favor composition over

    inheritance • shows how to achieve it with forwarding classes • Kotlin supports composition natively with class delegation • requires zero boiler plate code + =
  38. Java • Classes are open by default • Explicit final

    keyword needed to make it final Kotlin • Classes are final by default • Explicit open keyword needed to make it open
  39. Item Summary: • In Kotlin we have to make the

    explicit choice of making a class subclassable with the open keyword + =
  40. Member Classes in Java public class Outer { private int

    bar = 1; class Nested { public int foo() { return bar; } } } non-static public class Outer { private int bar = 1; static class Nested { public int foo() { return 2; } } } static
  41. Member Classes in Kotlin - static by default class Outer

    { private val bar: Int = 1 class Nested { fun foo() = 2 } } static class Outer { private val bar: Int = 1 inner class Nested { fun foo() = bar } } non static
  42. Item Summary: • Effective Java recommends to favor static member

    classes over non-static • In Kotlin, member classes are static by default + =
  43. Other relevant Chapters & Items • Chapter 5: Generics ◦

    No raw types in Kotlin ◦ Easier way of defining variance • Chapter 6: Enums and Annotations ◦ Consistently use the override annotation
  44. Other relevant Chapters & Items • Chapter 7: Methods ◦

    Check parameters for validity • Chapter 9: Exceptions ◦ Avoid unnecessary use of checked exceptions More Information in my Blogpost Series on Medium: medium.com/@lukleDev
  45. + =