Kotlin II

Joel Ross
Autumn 2018

INFO 448

Update Homework

The starter code for the Kotlin homework has changed. Please update your repos!

# cd into the project director
cd path/to/repo

# add a reference to the updated version
git remote add upstream https://github.com/info448-au18/kotlin-starter

# pull in changes - watch for merge conflicts!
git pull upstream master

Scaffolding for Today

https://github.com/info448-au18/kotlin-demo

Clone the demo repo to practice with me!
(Or use trykotlin, the Android Studio REPL, etc).

Gradle

Main

Compile and execute Kotlin code using the Gradle Wrapper (with a pre-defined build.gradle configuration)

An example main method

# Use the gradlew wrapper to execute the "runDemo" task
# Compiles and runs class specified in build.gradle
./gradlew runDemo -q
fun main(args: Array<String>) {
    println("Hello world!")
}

function

param and type

Array of Strings!

Kotlin Practice

  1. Define an Array of Strings that contains each of the words to the following song lyric (don't include the notes): "everything is awesome everything is cool when you are part of a TEEEEAM"
    • Hint: use the split() string method.
  2. Define a function called abbreviate() that takes in a string as an argument and returns the first letter of that string capitalized and followed by a period (.). For example: "E."
  3. Use the map() function to transform the list of lyric words into a list of abbreviations (technically an initialism). Use a lambda expression version of your abbreviate() function
  4. Use the filter() function to remove each word from the list of words that is 3 or fewer letters in length.
  5. In a single statement, use both the map() and filter() functions to get a list of the initials of the long words in the the words list.

Classes

We define data types by creating Classes (classifications).

A class is a template/recipe/blueprint for individual objects. It defines what data (attributes) and behaviors (methods) they have.

An object is an "instance of" (example of) a class. We instantiate an object from a class.

Classes in Java

public class Person { //class declaration
  private String firstName; //fields (private)
  private int age;

  //Constructor (called with `new`, initializes variables)  
  public Person(String newName, int newAge){
    this.firstName = newName; //assign params to fields
    this.age = newAge;
  }
    
  public String getName() { //a "getter" (accessor)
     return this.firstName; //return own field
  }
  
  public void haveBirthday() {
    this.age++; //access and modify own field
  }

  public void sayHello(Person otherPerson) { //Person param
    //call method on parameter object for printing
    System.out.println("Hello, " + otherPerson.getName());
    System.out.println("I am " + this.age +  " years old");
  }
}
//instantiate objects
Person adaObj = new Person("Ada", 21);
Person bobObj = new Person("Bob", 42);

//call the method ON Ada, and PASS Bob as a param
adaObj.sayHello(bobObj);

Classes in Kotlin

//class with primary constructor defining properties
class Person(val name: String, private var age: Int) {

  fun haveBirthday() {
    this.age++ //`this` can be inferred
  }

  fun sayHello(otherPerson: Person) {
    println("Hello, ${otherPerson.name}") //access fields
    println("I am $age years old");
  }
}
//instantiate objects
val ada = Person("Ada", 21)
val bob = Person("Bob", 42)

//access fields
println(ada.name)

//call methods
ada.haveBirthday()
ada.sayHello(bob)

Properties

Attributes (i.e., fields, instance variable) in Kotlin are referred to as properties, and declared as variables inside the class.

class Dog {

  var name: String = "Fido"   //mutable property
  val breed: String = "Husky" //read-only property
  private var human = "Joel"  //access modifiers like Java

}

Like in Java, can access properties with dot notation:

val doggy = Dog()
println(doggy.breed) //access
doggy.name = "Good Boy" //modify

Constructors

The primary constructor for a class is part of the class declaration. Additional code can be placed in init blocks.

Secondary constructors are defined with the constructor keyword (like JavaScript!)

class Dog(name:String) { //primary constructor is here
  var name = "$name the dog" //assign constructor param
  var toy:String? = null //additional field

  init {
    println("Aww, a puppy!")
  }

  constructor(name:String, toy:String) : this(name) {
    this.toy = toy //assign 
    println("$name has a $toy")
  }
}

//...
val doggy = Dog("fido", "squeaky bone") //use secondary

Getters and Setters

Class properties can be given custom accessors and mutators, included as part of the property declaration. If you need to reference the property as part of the accessor, you can refer to its backing field with the field keyword.

class Dog(toy:String?) {
  var toy:String? = null //property
    get() {
      if(field == null) return "none"
      return field
    }
    set(value) {
      //only change if doesn't have toy already
      if(field == null) field = value
      else {
        println("I won't drop my $toy!!")
      }
    }
}

//...
val doggy = Dog(null)
println(doggy.toy) //call getter
doggy.toy = "squeaky bone" //call setter
println(doggy.toy)
doggy.toy = "yarn cat" //call setter

Companion Objects

Kotlin doesn't have static methods. Instead, you can make a static nested class by giving a class a companion object. This is an object expression: an anonymous class instance.

class Dog(breed:String) {
  val breed: String = breed
  companion object Breeder {
    private var puppyCount = 0 //shared variable
    fun breed(first:Dog, second:Dog): Dog {
      puppyCount++
      return Dog("${first.breed}-${second.breed} mix")
    }
  }

  fun countPuppies(): Int { //only Dogs can access the shared variable
    return puppyCount
  }
}

//...
val doggy = Dog("Husky")
val doggo = Dog("Lab")
val puppyA = Dog.Breeder.breed(doggy, doggo)
println(puppyA.breed) // Husky-Lab mix
val puppyB = Dog.Breeder.breed(doggo, doggy)
println(doggy.countPuppies()) //2, shared variable

optional name

Data Classes

If your class just holds data values (like a struct in C) with no methods, you can use a data class as a shorthand:

//a data class with two properties
data class ChewToy(val name: String, val cost: Double)

//instantiate as normal
val bone = ChewToy("squeaky bone", 6.99)

//data classes have pre-defined toString() methods!
println(bone) //ChewToy(name=squeaky bone, cost=6.99)

Inheritance

Inheritance is the idea that some classes are refinements of others. These classes "inherit" properties (attributes and methods) from their parents.

Inheritance in Kotlin

Inherit from a class by putting the parent class's constructor after a colon (:). The derived class inherits the parent's properties and methods, but can also define its own. Base classes and overridden methods must be explicitly open

open class Dog(val name: String) { //base class
    fun wagTail() { println("$name wags his tail") }
    open fun bark() { println("$name the dog barks!") }
}

class Husky(name:String ) : Dog(name) { //derived class
    //new method
    fun pullSled() { println("mush!") }

    //overrides parent function
    override fun bark() { println("$name woofs") }
}


//...
val dubs = Husky("Dubs")
dubs.wagTail() //inherit methods
dubs.pullSled() //new method
dubs.bark() //overridden method

Referencing the Parent

As in Java, you can call a method from the parent class using the super reference. This is useful for "delegating" to a base class--passing the details "up the tree".

open class Pet(val name: String) { //base class
    open fun play(toy:String) {
      println("$name plays with $toy")
    }
}

class Cat(name:String ) : Pet(name) { //derived class
    override fun play(toy:String) {
      println("$name waits until you aren't looking")
      super.play(toy) //pass argument to parent to handle
    }
}

//...
val fluffy = Cat("Fluffy")
fluffy.play("yarn")

Polymorphism

Inheritance represents an is-a relationship. Which means a class is a instance of its parent's type. The relationship applies to the whole hierarchy (a class is a instance of grandparent's type).

open class Dog(val name: String) { //base class
    open fun bark() { println("$name the dog barks!") }
}

class Husky(name:String ) : Dog(name) { //derived class
    //overrides parent function
    override fun bark() { println("$name woofs") }

    //new method
    fun throwFootball() { println("Go long!") }
}

//...
val dubs: Dog = Husky("Dubs")
dubs.bark() //uses Husky implementation
dubs.throwFootball() //compiler error: dogs can't throw!

if(dubs is Husky) { //smart casting!
  dubs.throwFootball()
}

The specific properties (data and functions) that an abstraction makes available for interaction.

Defines an explicit annotation (contract)

Programming Interfaces

Kotlin Interfaces

Kotlin supports interfaces just like Java. A class implements an interface by including it after the colon in the class declaration, and fulfilling the contract (providing the required methods)

interface Huggable {
  fun hug() //no body needed
}

class Dog : Huggable {  //implements interface, fulfills the contract
  override fun hug() { println("Hug the puppy!") }
}

class TeddyBear {  //has method, but doesn't implement interface!
  fun hug() { println("Hug the bear!") }
}

//...
var squeezy: Huggable = Dog() //interaces enable polymorphism
squeezy.hug() 

var snuggleParty: MutableList<Huggable> = mutableListOf(squeezy)
snuggleParty.add(TeddyBear()) //compile error: isn't explicitly huggable

for(item in snuggleParty) item.hug() //hug them all!

Kotlin Interfaces

Thanks to Java 8, Kotlin supports properties and default implementations in interfaces. This can cause confusion if multiple interfaces define the same method.

//from Kotlin docs

interface A {
    fun foo() { print("A") }
    fun bar()
}

interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}

class C : A {
    override fun bar() { print("bar") }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }

    override fun bar() {
        super<B>.bar()
    }
}

Abstract Classes

As in Java, methods can be declared abstract, indicating that they are not yet implemented (and will need to be overridden and completed by a subclass). Any class with an abstract method needs to be declared as abstract. Interfaces are automatically abstract.

abstract class Animal(name:String) {
  val name = name
  abstract fun speak() //will need to define this
}

class Dog(name: String) : Animal(name) {
  override fun speak() { println("bark bark bark") }
}

//...
val animal: Animal = Animal("Alfred") //compile error, can't instantiate

val animal: Animal = Dog("Dubs") //create concrete class
animal.speak() //bark!

Delegation Pattern

Unless you're working with a framework (like Android), you should favor composition over inheritance -- use instance variables instead of inheritance to share behavior. One way to do this is to use a delegate: an instance variable that does the work for you.

class Quadruped {
  fun walk() { println("walk on 4 legs") }
}

class Dog : Quadruped {
  //inherits walk
}

class Cat : Quadruped {
  //inherits walk
}

//...
val dog = Dog()
dog.walk() //walk on 4 legs
interface Locamotion {
  fun walk()
}

class Quadruped : Locamotion {
  override fun walk() { println("walk on 4 legs") }
}

class Dog(val motion: Locamotion) {
  fun walk() { motion.walk() } //delegate to property
}

//...
val dog = Dog(Quadruped())
dog.walk() //walk on 4 legs

Kotlin Delegates

Kotlin has built-in support for delegation, for both classes and properties, specified with the by keyword.

interface Locamotion {
  fun walk()
}

class Quadruped : Locamotion {
  override fun walk() { println("walk on 4 legs") }
}

class Dog(motion:Locamotion) : Locamotion by motion {
  //inherits method!
}

//...
val dog = Dog(Quadruped())
dog.walk() //walk on 4 legs

constructor
param

Delegate Properties

You can also delegate a property and let another class handle the getter/setter logic. That class needs to provide two specific methods: getValue() and setValue()

class NameDelegate { //class that handles naming schemes
  
  var name:String = "nobody" //extra data about the name
  var askCount = 0

  operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    askCount++ //keep track of who has accessed
    return if(askCount >= 3) "I've already told you" else name
  }

  operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    name = value.toUpperCase() //save modified version
  }
}

class Dog(name:String) {
  var name:String by NameDelegate() //delegate the property
  init {
    this.name = name //(initialize it though)
  }
}

//...
val dog:Dog = Dog("Fido")
println(dog.name)
dog.name = "Rover"
println(dog.name)
println(dog.name)

Standard Delegates

Kotlin also provides a few common property delegation classes "built-in". For example, lazy is a delegate that will execute a lambda the first time a getter is called.

//From Kotlin docs

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}

Action Items!

  • Kotlin Homework due Tuesday
     

No office hours tomorrow (Joel out of town)


Next Time: back to Android (Resources)

info448au18-kotlin-2

By Joel Ross

info448au18-kotlin-2

  • 904