Kotlin Classes - mariamaged/Java-Android-Kotlin GitHub Wiki

Kotlin - Basic Classes

Basic Classes

Classes in Kotlin are very similar to Java and Ruby counterparts:

  • They primarily contain:
    • Properties.
    • Functions that operate on those properties.
  • A class does not have to contain any of that.
  • This is a valid Kotlin class definition.
class Foo {

}
  • So is this.
class Foo
  • Having a class with no body seems odd.
  • However, in Kotlin, you will occasionally see that sort of thing, particularly with data classes and sealed classes.

Creating Instances of Classes

  • Java and Ruby both use new to create instances of classes, whether as a keyword:
Java
Foo foo = new Foo();
Ruby
foo = Foo.new
  • JavaScript has a few flavors of creating objects, including one using the new keyword, much as you would with Java.
  • In Kotlin, though, you treat the class name as a function name, and just call it:
class Foo

val foo = Foo()

Packages

  • Kotlin makes use of packages the same way that Java does:
    • As a namespace for classes.
  • And, as with Java, by default a class is not in a package.
    • Most Kotlin files are in an actual project - as opposed to little REPL experiments - will be in a package.
  • What differs, compared to Java, is just the lack of the semicolon that the equivalent Java package statement would require.

Common Contents

Functions

  • Declaring an ordinary function is a matter of having the fun be inside the body of a class:
class Foo {
	fun something() {
		println("Hello, world!")
}
}
  • Calling an object's function works pretty much like Java, by using a dot (.) notation.
class Foo {
	fun something() {
		println("Hello, world!")
}
}

val foo = Foo()
foo.something()

Properties

  • Just as classes can have functions, they can have properties:
class Foo {
	var count = 0;

	fun something() {
		count += 1
		println("something() was called $count times")
}
}

val foo = Foo()
foo.something()
foo.something()
foo.something()
  • Here, we have a count property, initially set to 0, that we increment on each call to something().
  • That count is then used in the printed output, courtesy of a bit of string interpolation.
something() was called 1 times
something() was called 2 times
something() was called 3 times

this

  • As with Java, Kotlin uses as a pseudo property to refer to the instance of the object in whose function you happen to be running.
  • You can use that as a prefix for:
    • Property references.
    • Or function calls.
    • Such as this.count.
class Foo {
	var count = 0

	fun something() {
	this.count += 1
	println("something() was called $count times") 
}
}

val foo = Foo()

foo.something() 
foo.something()
foo.something()
  • Or you could use it on its own to refer to the current instance, such as for supplying that object as a parameter to a function:
class Foo {
	var count = 0

	fun printMe() {
		println(this)
}
}

val foo = Foo()
foo.printMe()

Constructors

  • As with Java and Ruby, a Kotlin class does not necessarily need an explicitly-declared constructor.
  • By default, you get a zero-parameter constructor, as Foo did in the preceding example.
  • Many of your Kotlin classes will not need any custom constructors...but a few will need one, and on occasion they might need more than one.

What You Normally See

  • In Java and Ruby, declaring constructors is a matter of having a specific method (or thing that looks like a method) declared on a the class.
  • In Java, that pseudo-method has the name of the class.
Java
class Foo {
	final private int count;

	Foo(int count) {
		this.count = count;
}
}
Ruby
  • We use the initialize method.
class Foo
	def initialize(count)
		@count = count
	end
end	
JavaScript
  • Similarly, JavaScript uses a function named after the class, at least in some approaches of implementing objects in JavaScript.
Kotlin
  • The way you will normally see Kotlin classes have a constructor is by making the actual class name itself take the constructor parameters.
  • Here, we have one constructor parameter, count.
  • The var keyword in front of it means that we can use it like a var property, in this case changing the value via the increment (+=) operator.
class Foo(var count: Int) {

	fun something() {
		count += 1
		println("something() was called $count times")
}
}

val foo = Foo(0)

foo.something()
foo.something()
foo.something()
something was called 1 times
something was called 2 times
something was called 3 times

The Formal Approach

  • The constructor syntax shown above is really a shorthand for using the constructor keyword.
  • For most classes, with zero or one constructors, you will not see the constructor keyword used.
  • It becomes more important when you have multiple constructors, including private constructors.
class Foo constructor(var count: Int) {
	fun something() {
		count += 1
		println("something() was called $count times")
}
}

val foo = Foo(0)
foo.something()
foo.something()
foo.something()

Init Blocks

Compared with Java and Ruby constructors, though, our Kotlin constructors are missing something: a body.

All we are doing is declaring a list of parameters that can be supplied to the instance of the class.

  • Kotlin instead uses an init block.
  • Syntax-wise, an init block resembles a Java static block: a set of braces-wrapped statements preceded by a keyword.
     
  • In Java, the static block is executed once for the class and is used to initialize complex static fields.
  • In Kotlin, an init block is executed once for each instance and serves as a baseline constructor body.
     
  • Here, inexplicably, we are passing in a String representation of an initial value to use for the count, rather than passing in the var count as we did before.
  • The init block is used to initialize count from sillyCount as part of setting up our instance of instance of Foo.
class Foo(val sillyCount: String) {
	var count = 0

	init {
		count = sillyCount.toInt()
   }

	fun something() {
			count += 1
			println("something() was called $count times")
   }
}

val foo = Foo("7")

foo.something()
foo.something()
foo.something()

Parameters Sans var Or val

Sometimes, you will see constructor parameters that do not have the var or val keyword.

  • They just have the parameter name and type.
  • They are still constructor parameters, but they are more limited in scope.
    • You cannot refer to them from the functions of your class.
    • You can refer to them from init blocks, though.
class Foo(sillyCount: String) {
	var count = 0

	init {
		count = sillyCount.toInt()
	}

	fun something() {
		count += 1
		println("something was called $count times")
	}
}

val foo = Foo("7")
foo.something()
foo.something()
foo.something()

Having Multiple Constructors

Some Kotlin classes have no constructor, using only the default zero-parameters constructor.

Some Kotlin have one constructor.

And a handful of classes will have more than one constructor.

  • The primary constructor appears in the class declaration itself, as we have seen.
  • Other constructors, if they exist, are called "secondary constructors", and they somewhat resemble ordinary functions.
class Foo(var count: Int) {
	constructor(sillyCount: String): this(sillyCount.toInt()) {
		// could have code here if needed
}

fun something() {
	count += 1
	println("something() was called $count times")
}
}

val foo = Foo("7")
foo.something()
foo.something()
foo.something()
  • A primary constructor has no function body - any such code goes to init block.
  • Secondary constructors, though, can have bodies, the way that functions do, but only if needed.
     
  • Secondary constructors MUST arrange to call the primary constructor.
  • This is handled by having a this() call after the constructor parameter list, separated from the list via a colon.
    • The this() call needs to match the parameter list of the primary constructor.
    • In the example shown above, the secondary constructor needs to provide an Int to the primary constructor.

Default Parameter Values

Having multiple constructors is comparatively common in a language like Java, as through Java 8, there was no support for default parameter values.

  • Hence, if you wanted to allow for both simple and complex object instantiation:
    • You needed to declare all of the necessary constructors.
    • Perhaps chaining from one other.
  • For example,
    • Android Developers who have created custom View classes might be familiar with the four View constructors.
    • Here, each of the constructors simply adds on another possible parameter to be supplied.
    • And the constructors chain to other constructors to eliminate duplication of initialization logic.
public View(Context context) {
	// A ton of code.
}

public View(Context context, @Nullable AttributeSet attrs) {
	this(context, attrs, 0);
}

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
	this(context, attrs, defStyleAttr, 0)
}

public View(Context context, @Nullable AttributeSet attra, int defStyleAttr, int defStyleRes) {
	this(context);

	// Another ton of code.
}
  • Kotlin, like Ruby, dispenses with this, by allowing you to define default values for your constructor parameters...just as you can on ordinary functions.
class View (
	context: Context,
	attrs: AttributeSet? = null,
	defStyleAttr: Int = 0,
	defStyleRes: Int = 0
) {

init {
	// Tons of code.
}
}
  • Here, the latter three parameters all have default values defined, giving us the ability to call the constructor:
    • With one parameter.
    • All four parameters.
    • Combinations in between.
       
  • The above code snippet contains newlines in between the parameters .
  • This is merely a matter of code formatting.
  • Typical Kotlin will split long parameter lists up this way, with one parameter per line.

Inheritence

As with many object-oriented classes, Kotlin supports class inheritance.

A subclass inherits from a superclass, and what it "inherits" from are all of the:

  • Properties.
  • Functions.
  • Constructors.

What Happens By Default

  • The default base class for a Kotlin class is Any.

Extending a Class

Java
  • We use the extends keyword.
class Foo extends Bar{


}
Ruby
  • We use the < symbol.
class Foo < Bar

end
Kotlin
  • We use the : symbol.
class Foo(var count: Int) : Any() {
	fun something() {
		count += 1
		println("something() was printed $count times")
	}
}
val foo = Foo(0)

foo.something()
foo.something()
foo.something()

Chaining to the Superclass Constructors

  • The () at the end of Any() is actually a call to the superclass constructor.
  • You will need to supply parameters to that call, if the superclass requires them.
    • Base has a constructor requiring a tag parameter.
    • Foo needs to supply that parameter when it calls the Base() constructor.
    • Bu then Foo can reference the tag property that it inherits, including it as part of the printed output.
open class Base(val tag: String)

class Foo(var count: Int) : Base("Foo example") {
	fun something() {
		count += 1
		println("$tag: something() was called $count times")
	}
}

val foo = Foo(0)
foo.something()
foo.something()
foo.something()

Open classes

You will notice that the declaration of Base has something beyond a constructor parameter: it is prefixed with the open keyword.

  • In Java, by default, any class can be extended by a subclass.
  • To prevent a class from being extended, you have to mark it as final.
     
  • Kotlin goes the opposite route.
  • Kotlin classes cannot be extended by default.
  • You have to mark classes that can be extended using open.
  • Failure to do that results in an error.

So, if we remove the open from the previous example.

class Base(val tag: String)

class Foo(var count: int) : Base("Foo Example") {
	fun something() {
		count += 1
		println("something() was called $count times")
 }
}

val foo = Foo(0)
foo.something()
foo.something()
foo.something()

We get a compilation error.

enter image description here

This has an important side effect:

  • You cannot create subclasses of arbitrary classes that you do not control.
  • Only if the implementer of that other class is willing to support subclasses will the implementer use the open keyword, and only those classes you can extend.
  • This approach is uncommon in major programming languages.
  • Basically, Kotlin recognizes that inheritance is a potentially fragile relationship between two classes.
  • Ideally, the classes adhere to the Liskov substitution principle.
    • This basically states that instances of a subclass should be usable everywhere that instances of the superclass can be used.

Overriding Functions

As you might expect, a subclass can call functions that it inherits from its superclass:

open class Base(val tag: String) {
	fun gimmeTheTag() = tag
}

class Foo(var count: int) : Base("Foo Example")  {
	fun something() {
		count += 1
		println("${gimmeTheTag()}: something() was called $count times")
	 }
}

val foo = Foo(0)

foo.something()
foo.something()
foo.something()
  • And, as you might expect, a subclass can override functions of its superclasses.
    • At least, if those too are marked as open.
  • By default, a Kotlin function is
    • Not open.
    • Cannot be overriden.
  • Not only must the superclass function be marked as open, but the overriding function must be marked with override.
open class Base(val tag: String) {
	open fun gimmeTheTag() = tag
}

class Foo(var count: Int) : Base("Foo expression") {
	override fun gimmeTheTag() = "Not the Original Tag"

	fun something() {
		count += 1
		println("${gimmeTheTag()}: something() was called $count times")
	}
}

val foo = Foo(0)

foo.something()
foo.something()
foo.something()
Not the Original Tag: something() was called 1 times 
Not the Original Tag: something() was called 2 times 
Not the Original Tag: something() was called 3 times
  • By default, open is transitive.
  • Once a function is marked as open, it is open for all subclasses down the hierarchy, regardless of whether intervening classes override the function or not.
open class Base(val tag: String) {
	open fun gimmeTheTag() = tag
}

open class Intermezzo(val thing: String) : Base(thing) 

class Foo(var count: Int) : Intermezzo("Foo example") {
	override fun gimmeTheTag() = "Not the Original Tag"

	fun something() {
		count += 1
		println("${gimmeTheTag()}: something() was called $count times")
	}
}

val foo = Foo(0)
foo.something()
foo.something()
foo.something()
Not the Original Tag: something() was called 1 times 
Not the Original Tag: something() was called 2 times 
Not the Original Tag: something() was called 3 times
  • However, if Intermezzo makes its version of that function as final.
open class Base(val tag: string) {
	open fun gimmeTheTag() = tag
}

open class Intermezzo(val thing: String) : Base(thing) {
		final override fun gimmeTheTag() = "-$thing-"
}

class Foo(var count: Int) : Intermezzo("Foo expression") {
		override fun gimmeTheTag() = "Not the Original Tag"
		fun something() {
			count += 1
			println("${gimmeTheTag()}: something() was called $count times")
	}
}
  • This results in an error.
     

enter image description here

Overriding Properites

  • It is also possible for a class to declare a property as open, allowing classes to override it.
  • Mostly, this is needed for properties declared as constructor parameters.
open class Base(val tag: String) {
	open fun gimmeTheTag() = tag
}

open class Intermezzo(val thing: String) : Base(thing) 

class Foo(var count: Int) : Intermezzo("Foo example") {
	override fun gimmeTheTag() = "Not the Original Tag"

	fun something() {
		count += 1
		println("${gimmeTheTag()}: something() was called $count times")
	}
}

val foo = Foo(0)
foo.something()
foo.something()
foo.something()
  • Notice that the constructor parameter for Base is called tag.
  • While the constructor parameter for Intermezzo is called thing.
  • They happen to be the same value.
  • Suppose, though, that we wanted to name these the same, such as having them both as tag:
open class Base(val tag: String) {
	open fun gimmeTheTag() = tag
}

open class Intermezzo(val tag: String) : Base(tag)

class Foo(var count: Int) : Intermezzo("Foo example") {
	override fun gimmeTheTag() = "Not the Original Tag"
	
	fun something() {
		count += 1
		println("${gimmeTheTag()}: something() was called $count times")
	}
}

val foo = Foo(0)

foo.something()
foo.something()
foo.somthing()
  • This results in a compile error.
     

enter image description here

  • This is because both are DECLARED AS PROPERTIES, and by default you cannot re-declare a property by reusing the name.
     
  • One way to address this is to realize that we do not need the constructor parameter in Intermezzo to be a property.
  • It could be a PLAIN constructor parameter.
open class Base(val tag: string) {
	fun gimmeTheTag() = tag
}

open class Intermezzo(tag: String) : Base(tag)

class Foo(var count: Int) : Intermezzo("Foo example") {
	override fun gimmeTheTag() = "Not the Original Tag"

	fun something() {
		count += 1
		println("${gimmeTheTag()}: something() was called $count times")
	}
}

val foo = Foo(0)

foo.something()
foo.something()
foo.something()
  • What the compiler suggests, though, is that we can make tag in Base be open then override it in Intermezzo:
open class Base(open val tag: String) {
	open fun gimmeTheTag() = tag
}

open class Intermezzo(override val tag: String) : Base(tag)

class Foo(var count: Int) : Intermezzo("Foo example") {
	override fun gimmeTheTag() = "Not the Original Tag"

	fun something() {
		count += 1
		println("${gimmeTheTag()}: something() was called $count times")
	}
}

val foo = Foo(0)

foo.something()
foo.something()
foo.something()

Chaining to Superclass Functions

  • Like Java, Kotlin uses a super . prefix to allow code in a subclass to specifically call implementations in a superclass.
  • Typically, you do this in a function that you overrode.
    • To execute the superclass implementation.
    • As part of the overriden function's implementation.
open class Base(val tag: String) {
	open fun gimmeTheTag() = tag
}

class Foo(var count: Int) : Base("Foo Example") {
	override fun gimmeTheTag() = super.gimmeTheTag() + "-Extended"

	fun something() {
		count += 1
		println("${gimmeTheTag()}: something() was called $count times")
	}
}

val foo = Foo(0)
foo.something()
foo.something()
foo.something()
Foo Example-Extended: something() was called 1 times 
Foo Example-Extended: something() was called 2 times 
Foo Example-Extended: something() was called 3 times

Inheritance Tests

Sometimes, we need to know whether a reference to some supertype is an instance of some subtype.

  • In Java, usually we do this via instanceof.
  • So if we have a class hierarchy like this:
class Animal {


}

class Frog extends Animal {

}

class Axotl extends Animal {


}

... we can use instanceof to see if some Animal is really a Frog:

void something(Animal criter) {
	if(critter instanceof Frog) {
		// Do froggy things.
	}
}
  • In Ruby, kind_of?, or is_a? are the typical ways of accomplishing the same thing.
def something(critter) 
	if critter.is_a? Frog
	# Do froggy things.
	end
end
  • In Kotlin, the same syntax as Java is used, with a shorter keyword: is.
open class Animal

class Frog : Animal()

class Axotl : Animal()

val critter : Animal = Frog()
if(critter is Frog) println("Ribbit!") 
else println("Ummm... whatever noise an axolotl makes!")

Inheritance and when

  • In Kotlin, we can use is not only as part of an expression, but we can use it as a test for a when clause.
  • This is helpful if you have a series of subtypes to check and handle separately.
    • Though you may be better served by using function overloading.
    • And putting the logic in the classes themselves.
    • (e.g., have a makeSound() function that all Animal classes implement, so you can just call makeSound() on any Animal and get the desired result.
open class Animal

class Frog: Animal()
class Axolotl: Animal()

val critter: Animal = Frog()
when(critter) {
	is Frog -> println("Ribbit!")
	else -> println("Umm...whatever noise an axolotl makes!")
}

Casting

Java programmers have a long history with casting objects.

  • This turns a reference to a supertype into a subtype.
  • In Java, we use the subtype name in parentheses to perform the cast.
void something(Animal critter) {
	if(critter instanceof Frog) {
		Frog frog = (Frog) critter;
		  
	    // Do something with the frog.
	}
	else {
		Axolotl axolotl = (Axolotl) critter;
 
		// Do something with the Axolotl.
     }
}
  • In Kotlin, casting gets a bit more interesting, because the compiler tries to help the reduce the number of casts that you need.
  • In fact, well-written Kotlin code rarely involves casting.

Manual

  • In Kotlin, you can use the as keyword.
val critter: Animal = Frog()
val kermit = critter as Frog

Smart Casts

  • In the Java code, in the if block, we cast critter to be a Frog.
  • We know this is safe, because we checked the type validity with instanceof in the if test.

SO, IF WE KNOW THE CRITTER IS A FROG...WHY DOESN'T JAVA?

Presumably, the Kotlin developers asked themselves that sort of question.

Inside of if and when blocks, if we know that a variable is really of some subtype, the compiler lets us skip the casts and allows us to refer to the variable as the subtype, EVEN THOUGH IT WAS DECLARED AS A SUPERTYPE.

open class Animal

class Frog: Animal() {
	fun hop() = println("Hop!")
}

class Axolotl: Animal() {
	fun swim() = println("Swish!")
}

val critter: Animal = Frog()

when(critter) {
	is Frog -> critter.hop()
	is Axolotl -> critter.swim()
}
  • In a Java style world, this would not compile.
     
  • critter is an Animal.
    • Animal has neither hop or swim.
       

However, Kotlin realizes that the is Frog code will only be executed if critter is a Frog, so it allows us to call hop() on critter, as if critter were declared as a Frog instead of an Animal.

  • However, this only works when the compiler is certain of the type.
  • As a result, this subtly-different edition of that code fails with a compile error.

Here, we replace is Axolotl with else.

  • The Kotlin compiler knows that critter is not a Frog, as otherwise we would not be executing the else.
  • However, critter might not be an Axolotl, as it could be an instance of Animal.
  • As a result, Kotlin cannot assume that is safe to call swim() on a critter, as while an Axolotl can swim(), an Animal cannot.
open class Animal 

class Frog : Animal() {
	fun hop() = println("Hop!")
}

class Axolotl : Animal() {
	fun swim() = println("Swish!")
}

val critter : Animal = Frog()
when(critter) {
	is Frog -> critter.hop()
	else -> critter.swim()
}

enter image description here

  • To get it to compile, we need to manually cast the critter to be an Axolotl:
open class Animal

class Frog: Animal() {
	fun hop() = println("Hop!")
}

class Axolotl: Animal() {
	fun swim() = println("Swish!")
}

val critter: Animal = Frog()

when(critter) {
	is Frog -> critter.hop()
	else -> (critter as Axolotl).swim()
}
  • While this compiles, it might crash at runtime giving this error message from Kotlin/JVM.
     

enter image description here
 

Kotlin's "smart cast" - as this automatic casting is called - not only saves you from typing, but it also helps you avoid improper manual casts.