Inheritance and the Any Class

Kotlin, like Java, supports single inheritance so each class has one and only one direct superclass.

Again, as in Java, there is a class that all classes inherit from. This is Object in Java, but in Kotlin it is called Any and it is similar to, but does not implement all the features of Object.

We can create an instance of Any in the same way as we would instantiate any class, using a line specifying Any as the datatype or using the Any class constructor. We have also seen previously how we can have the IDE infer a type of Any for a collection by giving it separate datatypes (such as string and integer) as elements.

The following line of code:

		val x:Any = Any()

declares an object called x which is an instance of Any. This object doesn’t really do anything but if we press control and click on Any, IntelliJ will display the code for the Any class so we can look at its members and this code is shown in figure 108.

			/*
			 * Copyright 2010-2015 JetBrains s.r.o.
			 *
			 * Licensed under the Apache License, Version 2.0 (the "License");
			 * you may not use this file except in compliance with the License.
			 * You may obtain a copy of the License at
			 *
			 * http://www.apache.org/licenses/LICENSE-2.0
			 *
			 * Unless required by applicable law or agreed to in writing, software
			 * distributed under the License is distributed on an "AS IS" BASIS,
			 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
			 * See the License for the specific language governing permissions and
			 * limitations under the License.
			 */

			package kotlin

			/**
			 * The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
			 */
			public open class Any {
				/**
				 * Indicates whether some other object is "equal to" this one. Implementations must fulfil the following
				 * requirements:
				 *
				 * * Reflexive: for any non-null value `x`, `x.equals(x)` should return true.
				 * * Symmetric: for any non-null values `x` and `y`, `x.equals(y)` should return true if and only if `y.equals(x)` returns true.
				 * * Transitive:  for any non-null values `x`, `y`, and `z`, if `x.equals(y)` returns true and `y.equals(z)` returns true, then `x.equals(z)` should return true.
				 * * Consistent:  for any non-null values `x` and `y`, multiple invocations of `x.equals(y)` consistently return true or consistently return false, provided no information used in `equals` comparisons on the objects is modified.
				 * * Never equal to null: for any non-null value `x`, `x.equals(null)` should return false.
				 *
				 * Read more about [equality](https://kotlinlang.org/docs/reference/equality.html) in Kotlin.
				 */
				public open operator fun equals(other: Any?): Boolean

				/**
				 * Returns a hash code value for the object.  The general contract of `hashCode` is:
				 *
				 * * Whenever it is invoked on the same object more than once, the `hashCode` method must consistently return the same integer, provided no information used in `equals` comparisons on the object is modified.
				 * * If two objects are equal according to the `equals()` method, then calling the `hashCode` method on each of the two objects must produce the same integer result.
				 */
				public open fun hashCode(): Int

				/**
				 * Returns a string representation of the object.
				 */
				public open fun toString(): String
			}
Figure 108 - the members of the Any class

It does have an equals() method which will return true if the hashCode() method returns the same value for each of the two values being compared. This method can be overwritten in your class if you want to compare objects in a way that makes more sense for that class.

In addition to the hashCode() method, the Any class also implements a toString() method which will return a string representation of the object but this is not in a format that makes any real sense to a human. For instance, if we print the value of x, which we have declared as an instance of the Any class, the result is something like:

		java.lang.Object@266474c2

So this is showing us the objects ID and type which is not particularly useful. Most classes will override this in order to provide a more useful readout. A data class, for instance, will return a readable string displaying the object’s properties and an implementation of the collections interface will return something useful as well.

Notice that the Any class has been declared as:

		public open class Any

In Java, any class can be a superclass by default unless it is declared as final. Kotlin does this the other way round and only allows you to inherit from a class if it is declared as open. I assume, also, that you only inherit the methods within that class if they are also declared as open.

To demonstrate this, we will create a class called SuperClass in the same file as our current main class.

			open class SuperClass(anInt: Int) {
				protected val _anInt = anInt
			}
Figure 109 - our SuperClass

Figure 109 shows the basic code for our SuperClass. Note the following:

	•	It is declared as open so we can extend this with another class
	•	It has a single integer value called anInt
	•	This integer value is protected and as in Java, this means that it is accessible only to the class or any descendant class

Back in our main function, we can create an instance of SuperClass with an initial value of 42 but if we print it out, we will see the same unhelpful format as we saw with the Any class. So, the code we put in the main class is:

	
	val sup = SuperClass(42)
	println(sup)

and the output we get when printing sup is:

			java.lang.Object@266474c2
			SuperClass@6f94fa3e
			override fun toString(): String {
				return "SuperClass(anInt: ${_anInt})"
			}
Figure 110 - the overridden toString() method in our SuperClass class

Figure 110 shows the method we have created to override the toString() method in order to produce something more readable when an instance of SuperClass is printed.

So, the SuperClass was declared as open so that we could inherit from it (or extend it). Let’s see how to do that.

We will start by declaring our class, which we will call SubClass and we will give it the same integer value in the primary constructor as we did with SuperClass. If it were not extending SuperClass, the class declaration would look something like that shown in figure 111.

			class SubClass(anInt: Int) {

			}
Figure 111 - the declaration for SubClass as it would look if we were not specifiying a SuperClass

In Java, we would use the keyword extends to indicate the class we want to inherit from but in Kotlin, we put a colon after the parameter list followed by the name of the parent class. So our class declaration is changed to the code shown in figure 112.

			class SubClass(anInt: Int): SuperClass(anInt) {

			}
Figure 112 - the declaration of SubClass which now has SuperClass as its parent class

Note that when we create an instance of SubClass, we will pass it an integer as required by the constructor but the SuperClass constructor also requires an integer and this is why we pass this integer on to SuperClass.

We can also override the toString function we inherit from SuperClass, otherwise when we print an instance of SubClass it will say SuperClass followed by the integer value. The toString() function created for SubClass is shown in figure 113.

			override fun toString(): String {
				return "SubClass (anInt: ${_anInt}"
			}
Figure 113 - the toString() function created in SubClass

Notice that we did not declare the variable called _anInt which is used in the returned string but this, of course, has been inherited from SuperClass. Now, we can create an instance of SubClass and if we print it, we will see that the integer is being displayed with the appropriate toString() method.

Another point to make is that we have two toString() methods, one in SuperClass and one in SubClass.

			val sup = SuperClass(42)
			println(sup)

			val sub = SubClass(53)
			println(sub)
Figure 114 - Code to create and print instances of SuperClass and SubClass

Take a look at the code in figure 114 and note that the toString() method is being called when an instance of either class is printed. The actual method called is determined by the instance that is making the call so when we print an instance of SuperClass we are using the SuperClass toString() method and when we print an instance of SubClass we are using the SubClass toString() method. This means that the decision regarding which method to call is made statically (that is, it is made at compile-time rather than at run-time).

The practical implication of this is that we are using two methods that do the same thing with the only difference being the class of the object being printed.

If we remove the toString() method from SubClass, we can amend the method in SuperClass so that it makes this decision dynamically and therefore we don’t need an additional copy of the method in SubClass.

			override fun toString(): String {
				return "${this::class.simpleName} (anInt: ${_anInt})"
			}
Figure 115 - the amended toString() method in SuperClass

The amended toString() method is shown in figure 115 and if we run the code with these changes, bearing in mind that we are still executing the lines of code shown in figure 114 in our MainKT() method, we get exactly the same output and this is shown in figure 117.

			SuperClass (anInt: 42)
			SubClass (anInt: 53
Figure 116 - the programs output

In both cases, we are now calling the same toString() method, but when it is called, the method uses the keyword this to refer to the instance that called the method. It is then obtaining the class name and this is being used in the print statement.

So now, the determination on which method to call is still being made statically because it is the same method regardless of the class of the instance being printed. However, the determination of which class name to print is being made dynamically (at run-time).

We can also create other functions in SuperClass which will be inherited by SubClass provided they, like the class itself, are declared as open. To demonstrate, we will create a function in SuperClass called multiply that accepts one integer as argument (we will call this factor) and returns the result of multiplying factor by _anInt. This method is shown in figure 117.

			open fun multiply(factor: Int): Int {
				return factor * _anInt
			}
Figure 117 - the multiply function created in SuperClass and inheritable by SubClass

To test this, we will add the following lines of code to our MainKT function.

		println(sup.multiply(100))
		println(sub.multiply(100))

This should return the values 4200 and 5300 respectively and print them out and when we run the code, this is exactly what happens.

We can override this multiply function in SubClass by typing override fun m and then selecting the method from the drop down box so IntelliJ is actually generating this method for us as effectively and this method is shown in figure 118.

			override fun multiply(factor: Int): Int {
				return super.multiply(factor)
			}
Figure 118 - the overridden multiply function in SubClass

As you can see, this function is making a call to the function in SuperClass but we can change it as shown in figure 119.

			override fun multiply(factor: Int): Int {
				return super.multiply(factor) * factor
			}
Figure 119 - the amended version of the overridden multiply function in SubClass

If we run our code again, we see that the printout is the same when we make the function call with sup, but when we make the call with sub we see that an_Int has been multiplied by factor when the SuperClass function was called and then multiplied by factor again by the multiply function in SubClass so rather than 5300, we see 530000 as the output.

To summarise, a class cam have one and only one direct super class and both the super class and any function you want to override must be declared as open.

However, you cannot extend a data class so you will see an error if you try to declare a class of this type as open and this kind of makes sense because you only need to extend a class to access its functionality. For a data class, if you need an object with additional attributes, you can simply add these to the data class itself rather than creating a subclass to add the additional attributes to.

Define and Implement an Interface

Interfaces in Kotlin follow many of the same rules that Java interfaces follow. A class can implement many interfaces and as we have seem in recent versions of Java, an interface can have both abstract properties and functions and also fully implemented functions.

To demonstrate this, we will create an interface called Dog and we will implement this directly in our MainKt file. This is to keep things simple although you could also place it in a separate file if required. We will start by giving it a single abstract function called speak and any class that implements Dog will be required to provide an appropriate implementation.

			interface Dog {
				fun speak()
			}
Figure 120 - the code for our Dog interface

You might notice from the code for the Dog interface, which is shown in figure 120, that we have not declared the speak function as abstract. This is not necessary since this can be inferred from the fact that there is no implementation body.

Next, we will create a concrete class that implements Dog and must therefore provide an implementation for speak. The skeleton for this class is shown in figure 121.

			class Retriever: Dog {

			}
Figure 121 - the outline for our Dog class

Note that this gives us an error since we are contractually obliged (due to the fact that Retriever implements Dog) to provide an implementation of the Speak function. We can do that via an intention action which we can then adapt to provide the required functionality and this gives us:

			override fun speak() {
				println("Woof!")
Figure 122 - the implementation in the Retriever class of the speak function specified (but not implemented) in the Dog class

Now, in our main function, we can create an instance of the retriever class and execute the speak function with the lines:

		val buster = Retriever()
		buster.speak()

and as expected, the word “Woof!” is printed. We could also , if required, specify the interface as we would a data type which gives us:

		val buster:Dog = Retriever()
		buster.speak()

but in most cases this is not necessary.

This is useful, but the real power of interfaces lies in the fact that you can pass classes that implement the interface to functions that expect them.

We can demonstrate this by creating a new function called makeItTalk and this is shown in figure 123.

			fun makeItTalk(dog: Dog) {
				dog.speak()
			}
Figure 123 - the makeItTalk function

Here, we are passing one parameter to the makeItTalk function and that is an instance of Dog. Note, of course, that the Dog class cannot be directly instantiated because it is an interface so in this case, the instance of Dog is buster which is an instance of the Retriever class.

Now, if we create any instance of Dog, we are instantiating a class that implements Dog so we know that the instance will have access to some form of the speak method. Therefore, when we pass that instance to makeItTalk, this function then calls the Dog’s own speak method.

If we amend the code in our main method to:

		val buster = Retriever()
		makeItTalk(buster)

we get exactly the same result. This is interesting because we now have a reusable method and we can pass any instance of Dog to it and it will call the speak method particular to that instance of Dog. So we have a polymorphic function!

Another example is a function we will call reportBreed which takes two parameters:

	•	A string, name
	•	An instance of the class Dog

This function is shown in figure 124.

			fun reportBreed(name: String, dog: Dog) {
				println("$name is a ${dog::class.simpleName}")
			}
Figure 124 - the reportBreed function

We can call this from the main function with:

	reportBreed("Buster", buster)

So we are calling this with an instance of Dog, buster, which is actually an instance of the class Retriever which implements Dog. This gives us the output:

		Buster is a Retriever

So we are defining the function with an abstract class, but when we get the class name back, we are getting the name of the concrete class.

Like functions, the properties defined in an interface are assumed to be abstract. To illustrate this, we will add a property called fur to the Dog interface as follows:

		var fur: String

We immediately see that this creates an error condition in our Retriever class because it has not yet implemented the fur property and we can use an intention action in the class to add the property and this also generates explicit getter and setter methods.

We will amend the getter so that it returns the string “golden”.

We can call this function from the main method with:

		println("This dog's fur is ${buster.fur}")

or we can add it to the reportBreed function as:

		println("This dog's fur is ${dog.fur}")

It was mentioned previously that a class can implement multiple interfaces and most of the times you will know what interface you are working with based on the function signatures. However, if more than one of the interfaces define the same abstract function, this can lead to a conflict and it is up to the implementing class to resolve it.

To demonstrate this, we will create another interface called Cat which also has the fur property and the speak method. We will also amend both the interfaces so that they implement the speak method with a “Woof!” for the Dog interface and a “Meow!” for the Cat interface.

We could now remove the speak function from Retriever or we can be a bit more explicit and have it call the function from the interface. The amended implementation in Retriever is shown in figure 125.

			override fun speak() {
				super.speak()
			}
Figure 125 - the amended speak function in the Retriever class

This is straightforward, but let’s now change Retriever so that it implements both Dog and Cat and we now have a conflict. We are specifying that we want to use the speak function from the super class but we have two super classes, both of which implement speak.

Actually, IntelliJ tells us how to resolve the conflict and we do that by placing the name of the interface in angled brackets after the keyword super so that the compiler knows which method from the super classes to call. Figure 126 shows the correctly amended method from figure 125.

			override fun speak() {
				super<Dog>.speak()
			}
Figure 126 - the amended speak function in the Retriever class with the conflict over which super class function to call now resolved

We can call both of the super class methods if we need to simply by calling the super function twice, once specifying the Dog interface and once specifying the Cat interface. This is shown on figure 127.

			override fun speak() {
				super<Dog>.speak()
				super<Cat>.speak()
			}
Figure 127 - the speak function in the Retriever class calling the speak function from both the Dog and Cat interfaces

We can also amend the makeItTalk method to accept an instance of Retriever. If the type for buster has explicitly been specified as Dog, it will have to be removed (or changed to Retriever) and when we run the code we will see that buster both barks and meows!

As with Java, we don’t need to use interfaces in Kotlin but they are useful for enforcing programming contracts in complex applications and in frameworks such as Android development, understanding how to implement existing interfaces is critical.

Use Anonymous Interface Implementations

Many programming environments use the concept of callback methods to react to various events and there are a few syntax styles for doing this in Kotlin. One of these is anonymous objects which either extend a super class or, more commonly, implements an interface.

The reason an object is referred to as anonymous is because you don’t have to create it as a named variable. If you have a function that requires a particular type of object to be passed to it as an argument, you can simply define the object when you are passing it to the function.

Typically, the function is a member of a stateful class (DZone Java Zone has an interesting article on Stateful or Stateless Classes) that holds on to that reference and can call the function whenever it is needed.

Let’s say we want to handle a click event, minimally we will need to know the x and y co-ordinates of the click and we can store this information in a class called ClickEvent. This will have a primary constructor which takes two integers, x and y as parameters.

Next, we will create an interface which we call ClickListener. This will one abstract function called onClick which will accept an event as a parameter and the type will be ClickEvent.

We now create a class called StatefulClass which will define an instance of that ClickListener interface and its primary constructor will receive a listener object which will be an instance of ClickListener. In order for it to keep a reference to the listener for the lifetime of the class, we will create a backing object called _clickListener which will be an instance of ClickListener whose value can be null and we will set this to the listener object passed to it.

Finally, we will add a function to StatefulClass called clickMe which takes two integers, x and y, as parameters and then uses _clickListener to call the onClick function. To make this function call (note that since _clickListener can have a null value, this needs to be a null-safe call), we will create an anonymous instance of ClickEvent and pass it the values for x and y.

So now we have a complete callback system and StatefulClass has a persistent object that is an implementation of the ClickListener interface.

When the mouse is clicked inside the user interface, we will receive the x and y co-ordinates and these will be processed by calling the interface’s implementation of the onClick function.

In the main function, we can now create an instance of StatefulClass which we will call stateful. We will pass it an anonymous object of type ClickListener. We now have an error condition on object because it does not implement the onClick function and we can use an intention action to correct this. For the implementation we will simply print the location of the click using a string template.

Then we will output something to the console to confirm that everything has been set up.

As things stand, we are not doing anything that will call the ClickListener object but we can run the code just to show that everything is in place.

Now, we can use the callback function like this.

	stateful.clickMe(5,18)
	stateful.clickMe(45, 57)

Now, we can see that the callback method is being called successfully. The anonymous object is being passed to it and that object is persisting within StatefulClass and the function implementation is being called whenever it is triggered.

All of the code for these examples is shown in figure 128 in addition to being in the appendices (note that the code in figure 128 is the code I typed into IntelliJ whilst the code in the appendix is from the course GitHub page so these are not guaranteed to be identical.

			fun main(args: Array<String>) {
				val stateful = StatefulClass(object: ClickListener {
					override fun onClick(event: ClickEvent) {
						println("Click at ${event.x}, ${event.y}")
					}

				})
				println("Listener initialised")

				stateful.clickMe(5,18)
				stateful.clickMe(45, 57)
			}

			class ClickEvent(val x: Int, val y: Int)

			interface ClickListener {
				fun onClick(event: ClickEvent)
			}

			class StatefulClass(listener: ClickListener) {
				private var _clickListener: ClickListener? = listener

				fun clickMe(x: Int, y: Int) {
					_clickListener?.onClick(ClickEvent(x, y))
				}
			}
Figure 128 - the code demonstrating the use of an anonymous object to implement an event handler

So this is one way to implement event listeners in Kotlin and it is a very common pattern in Android Development.

The View class in Android is the super class of all the visual widgets you place on the screen and it supports a variety of events. You can create anonymous objects that implement the required interfaces in order to react to those events. But you can also use a more concise bit of syntax called a lambda expression to handle these interfaces and we will look at those next.

Pass Functions as Lambda Expressions

A Kotlin function is its own object. When you define a function, you can create it as a reference variable.

To illustrate this, we will create an immutable variable called helloWorld and instantiate it with a pair of braces. This creates something called a Unit (which has its own functions) and in this case, we will have our function simply output the string, “Hello world!”. This variable declaration is shown in figure 129.

			val helloWorld: () -> Unit = {
				println("Hello World!")
			}
Figure 129 - the functional helloWorld variable

We can now call this function in the usual way:

		helloWorld()

In addition, the Unit class has a function called invoke and we would use this as:

		helloWorld.invoke()

Now, this is all more complex than simply using the println statement defined within the function as a single statement. However, the concept of a function as an object is the first step in understanding lambda expressions.

Let’s say we have a function called sayHello which we will, again, declare as a variable with a pair of braces, thereby creating an instance of Unit. The function receives a string parameter and when the function is called, that string is passed to the function implementation and that is denoted with the arrow operator (->). Everything after the arrow operator is function implementation.

In this case, the implementation is a print statement that prints the word ‘hello’ followed by the user parameter. The completed function is shown in figure 130.

			val sayHello :(String) -> Unit = {
				user: String -> println("Hello, $user")
			}
Figure 130 - the completed sayHello function

Again, we can call it using a simple statement such as:

		sayHello("Philip")

and, of course, we have to provide a string parameter. As with the helloWorld function, sayHello is an instance of Unit so we can, again, use Unit’s invoke function and the syntax for this is:

		sayHello.invoke("Philip")

As with any function, we can have multiple parameters in a comma separated list. To demonstrate this, we will amend sayHello to take a second parameter which will be an integer called age. This revised function is shown in figure 131.

			val sayHello :(String, Int) -> Unit = {
				user: String, age: Int -> println("Hello, $user.  You are $age years old.")
			}
Figure 131 - the revised function with multiple arguments

There are many functions in the Kotlin library where you can use lambda expressions and one of these is when managing collections.

We will create an array as follows:

	val states = arrayOf("New York", "New Hampshire", "Vermont", "Rhode   Island", "Nebraska")

We want to sort this array and we will create a new array called sorted and we will then call a function, sortedBy, on states and assign the result to sorted. The sortedBy function expects an implementation of the comparable interface and we could do this long-hand using an anonymous object that implements the interface. However, the lambda expression makes this a lot easier.

We can pass it a variable called state and the comparable interface will operate on one element of the array at a time. We will then use an arrow operator and indicate the value that we want to sort on. This gives us:

		val sorted = states.sortedBy { state -> state.length }

Finally, we will use a loop to iterate through sorted, just to make sure it has worked and this loop is shown in figure 132.

			for (state in sorted) {
				println(state)
			}

We can also make this even more concise. Rather than using

		{ state -> state.length }

(where we are effectively saying take each element which is expected by the comparable interface, call it state and then get the length of state) we can replace the references to state with the keyword it.

Hence, the whole statement becomes:

		val sorted = states.sortedBy { it.length }

The result is the same in either case, but this second version is a little bit more concise and readable.

We can also add a second function to the end of this line called filter and again, we will use the keyword it in the lambda expression and again this represents each element of the list or each element expected by the interface.

With it we will use the function startsWith and pass it two arguments, the letter ‘n’ and the word true. The first argument indicates that only elements that start with this letter or string should be returned and the second indicates that case should be ignored.

So in this case, we will display all elements that start with an n and we have three of those. We could, as an example, replace n with new and then we would only display elements that started with the word new such as New York.

You can use lambda expressions instead of anonymous objects in most cases and if you are developing Android apps in Kotlin, you can replace your anonymous object event listeners with lambda expressions. They are usually more readable and once you understand the syntax, they eliminate a lot of unnecessary code.

Use Abstract and Sealed Classes

It was mentioned previously that a data class cannot be declared as open and therefore cannot act as a superclass. However, there may be occasions when you want to create a strict hierarchy with a single superclass and a clearly defined set of subclasses. In this case, we can designate our superclass as sealed.

To illustrate this, we will return to our ClothingItem class so to start with, we have our main class, a package called model and a data class inside that package called ClothingItem.

In the main function, we have two lines of code, one to create an instance of ClothingItem and one to display it. We want to turn this data class into a super class with subclasses such as pants, shirts, hats and so on.

To convert the data class to a sealed class, we just need to add the declaration, sealed, in much the same way as we declared a class to be a data class. In fact, since we are converting from a data class to a sealed class in this case, we simply change the word sealed to data.

Now, you might recall that ClothingItem had three parameters in its primary constructor and these were style, size and price. In our class hierarchy, each subclass will represent a style so we will amend this so that the primary constructor has one parameter, style and the other two parameters will become properties of the class.

The reason for this may not be obvious but essentially, we want the subclasses to inherit these properties (size and price) but each subclass will define its own style (so to speak) and this will become clearer when we see some more code.

When we move the parameters into the body of the class, we will see an error condition which tells us that these properties must be either initialised or abstract and we will resolve this by declaring them as abstract and now we have the class as shown in figure 133.

			package model

			sealed class ClothingItem(val type: String) {
				abstract val size: String
				abstract val price: Double
			}
Figure 133 - the ClothingItem class re-written as a sealed class

The rule for creating subclasses of a sealed class is that they must be in the same file. They can be nested within the parent class or written as separate classes provided they are part of the same file.

The subclasses can be data classes and that gives you all of the advantages of a data class including an automatically generated toString function.

Next, we will create a data class called Shirt. This will call the ClothingItem constructor and pass it the string “Shirt” as a parameter.

Now, since the data class is extending ClothingItem, we need to implement the properties, size and price. We will do this by placing both properties in the primary constructor for the class Shirt and using the override keyword and this gives us the class definition shown in figure 134.

			data class Shirt (override var size: String, 
							  override var price: Int): ClothingItem ("Shirt")
Figure 134 - the data class, Shirt which extends the ClothingItem class

So now, when we instantiate Shirt, we will pass it a size and price.

We can use the Shirt data class as a template to create additional subclasses by simply changing both the class name and the string passed to the ClothingItem constructor and we will do that, creating a new class called Pants.

So now we have a complete class hierarchy and we could create as many additional subclasses as required.

To see how this works, we will now go back to the main class and recall that we have a statement here creating an instance of ClothingItem. This now shows an error condition since ClothingItem has been converted to a sealed class and cannot be instantiated. Instead, we will create an instance of the Shirt class with:

		val shirt = Shirt("XL", 19.99)

Now, this will give us an error condition because we have imported ClothingItem, but not Shirt. We can add a further import statement for Shirt but we would also need to do this for Pants and any additional subclasses we might create. A better way is to use a wildcard so our import statement becomes:

		import model.*

Actually, an additional note on this is that we do not actually need to import ClothingItem since we are not directly instantiating it form within the main function.

Notice how the syntax for creating an instance of Shirt is a little bit tidier than our previous statement for creating an instance of ClothingItem since we no longer need to specify the type. In fact, we are implicitly specifying type with the name of the subclass we are creating an instance of.

Also, the output is a little bit neater for similar reasons. Previously, we would have seen something like ClothingType followed by the values of its three properties and now we see:

	Shirt(size=XL, price=19.99)

Now, I will create a second item, this time a pair of pants in order to investigate one of the more powerful benefits of creating a class hierarchy like this.

We want to compare the two items we have created in order to determine which is the most expensive. To do this, we first create a variable called mostExpensive and give its type as ClothingItem and assign it a value. The value we assign is gong to be determined by a conditional statement and this declaration, together with the conditional assignment statement are shown in figure 135.

			val mostExpensive: ClothingItem =
				if (item1.price > item2.price) item1 else item2
Figure 135 - the mostExpensive variable along with its conditional assignment statement

We can now use mostExpensive in a when statement which will provide instructions for use for whichever item has the higher price.

			val instructions = when (mostExpensive) {
					is Shirt -> "Button it!"
			}
Figure 136 - the when statement

This is shown in figure 136 and there are several notable points to make here.

	•	In this case, we are using the when statement to assign a value to instructions.  Depending on the value of mostExpensive, it will return a string corresponding to the item type, which is “Button it!” if mostExpensive is a Shirt.
	•	We are making use of an  operator we haven’t seen before which is.  This is equivalent to the Java operator instanceOf and if we use it in conjunction with the Shirt class as we have here, it will return true if the object is an instance of Shirt.
	•	The Pants item was deliberately omitted in the code example in order to illustrate the essential point we are making here.  As things stand, we will see that we have an error condition on the when statement which occurs because our list of options is not exhaustive (in other words because of the fact that we have omitted the Pants item.
	
	If we now add an additional line of code to return a string, “Buckle it!” if the item is an instance of Pants, the error code disappears.  This is important because it helps to ensure none of the possible items are omitted.  So if we had, say, 30 subclasses of ClothingItem, this error would persist until we had accounted for all 30 items in the when statement so it is helping to ensure that our code is complete.

To summarise, the sealed class architecture allows us to create all possible subclasses of a particular super class and the rest of the code, our evaluation of the various possibilities can be concise and easily readable.