© Peter Späth 2019
Peter SpäthLearn Kotlin for Android Developmenthttps://doi.org/10.1007/978-1-4842-4467-8_4

4. Classes and Objects: Extended Features

Peter Späth1 
(1)
Leipzig, Germany
 

This chapter covers some extended object orientation features that are not necessary for a program to work, but nevertheless improve readability and expressiveness. The chapter presumes that you have read Chapter 2. We also use the NumberGuess sample app from Chapter 2.

Anonymous Classes

Inside your coding in some instances you might want to create a one-time implementation of an interface or a one-time subclass of some class. While in Kotlin, it is possible to write
class A : SomeInterface {
    // implement interface functions ...
}
val inst:SomeInterface = A()
// use inst ...
or
open class A : SomeBaseClass() {
    // override functions ...
}
val inst:SomeBaseClass = A()
// use inst ...
Inside functions, there is a more concise way to create and use such one-time instances:
val inst:SomeInterface = object : SomeInterface {
    // implement interface functions ...
}
// use inst ...
or
val inst:SomeBaseClass = object : SomeBaseClass() {
    // override functions ...
}
// use inst ...

If you extend some superclass as in the latter listing, this one might also be abstract. It is then necessary, however, to implement all abstract functions, as is usually the case for an instantiation to be possible. Because the name of the interface implementation or subclass is neither specified nor needed, such classes are called anonymous classes. Inside the class body between the curly braces you can write anything that you could also write inside a named class’s body.

Note

The object : inside the declaration suggests that there is just a one-time instantiation. It is not possible to have several instances of anonymous classes.

We know that this refers to the actual instance. This is also the case from inside anonymous classes, where this refers to the one instance of the anonymous class. There is an extension of this that allows us to get the instance of the enclosing class: Just append @ClassName to this. For example in
interface X {
    fun doSomething()
}
class A {
    fun meth() {
        val x = object : X {
            override doSomething() {
                println(this)
                println(this@A)
            }
        }
    }
}

the first this refers to the anonymous class, and this@A refers to the instance of class A.

Inner Classes

Classes and singleton objects can also be declared inside other classes or singleton objects, and even inside functions. They then can be accessed from inside their scope, so if a class or singleton object gets declared inside some class A, it can be instantiated inside A. If it is declared inside a function, it can be used from the point of its declaration until the end of the function.
class A {
    class B { ... }
    // B can now be instantiated from
    // everywhere inside A
    fun meth() {
        ...
        class C { ... }
        // C can now be used until the end of
        // the function
        ...
    }
}
Classes and objects inside other classes or other objects can be addressed from outside using a path specification similar to packages: If X as a class or an object gets declared inside A (a class or a singleton object), you can write A.X to access it from outside. You should probably do this, however, only if the inner class provides some kind of interface to the enclosing class, to avoid breaking encapsulation principles.
class A {
    class B { ... }
}
fun main(args:Array<String>) {
    val ab = A.B()
    // do something with it ...
}

Functions and Properties Outside Classes

In your project you can have Kotlin files that do not contain a single class, interface, or object declaration but nevertheless show val and var properties and functions. Whereas at first sight we seem to work outside object orientation if we use such files, in fact the Kotlin compiler implicitly and secretly creates a singleton object based on the package name and puts such properties and functions into this object.

Note

For very small projects it is acceptable to not use explicit classes and singleton objects. If a project gets bigger, it is still possible to only use such nonclass files, but you’ll then risk having chaotic and unreadable code in the end.

Derived from that fact, the following rules apply for such properties and functions:
  • It does not matter where in the file you declare val and var properties and functions; they will be usable from everywhere in the file.

  • Such properties and functions are visible to other classes or singleton objects you write in other files using import the.package.name.name where the last name refers to the property or function name.

  • You can have several files of that type inside a package. The Kotlin compiler then just sequentially parses all the files and gathers all the functions and properties that are neither from inside a class nor a singleton object. The file names play no role here.

  • If you have several such files in different packages (as defined by the package declarations at the top of the files), name clashes do not cause a problem. You could have properties and functions using the same name. Nevertheless, you should avoid this to keep your code readable.

  • It is possible to add classes, interfaces, and singleton objects to such files. You can use such structure units from the place of their declaration until the end of the file.

Additionally, it is possible to import properties and functions from all files of that kind inside a particular package using the wildcard notation import the.package.name.*. This comes in very handy to avoid a lengthy import list.

Exercise 1

You have a utility singleton object
package com.example.util
object Util {
    fun add10(a:Int) = a + 10
    fun add100(a:Int) = a + 100
}
and a client
package com.example
import com.example.util.Util
class A(q:Int) {
    val x10:Int = Util.add10(q)
    val x100:Int = Util.add100(q)
}

Could you think of a way to rewrite the Util.kt file to not use the object { } declaration? What will the client code look like?

Importing Functions and Properties

Functions and properties from singleton objects can be imported via import statements like these
import package.of.the.object.ObjectName.propertyName
import package.of.the.object.ObjectName.functionName

at the top of the file, after the package declaration and together with the other import statements for importing classes and singleton objects. It is then possible to directly use the function or property by using just its name, without a prepending ObjectName.

Note

There is no wildcard for importing all properties and functions of a singleton object. You have to put each of them into its own import line.

Exercise 2

Given that Math.log() calculates the logarithm of a number, and with Math residing inside package java.lang, rewrite
package com.example
class A {
  fun calc(a:Double) = Math.log(a)
}

such that Math. is no longer needed.

Data Classes

Classes that only contain properties and no or very few functions most likely are data classes, the purpose of which is to provision a bracket around a couple of properties. They thus serve as a kind of container gathering a range of properties. Think of a Person class which for a person gathers the name, birthday, place of birth, SSN, and so on. Kotlin has a special notation for such classes: Prepend data as in
data class ClassName([constructor])
This doesn’t look very different from normal classes, but in contrast to them, prepending data leads to the following outcomes:
  • The class automatically gets a specially tailored toString() function based on the properties; you don’t have to write your own.

  • The class automatically gets sensible equals() and hashCode() functions based only on the properties. We’ll talk about object equality later; for now, this is what you need to know: The equality check relation a == b for two instances of data classes yields true only if the instances belong to the same data class, and all their properties need to be equal as well.

Data classes come handy if you need a function to return structured or compound data. In other languages you’d frequently have to use full-fledged classes, arrays, or lists for that purpose, which makes this task feel a little bit clumsy. In Kotlin you can instead concisely write, for example:
data class Point(val x:Double, val y:Double)
fun movePoint(pt:Point, dx:Double, dy:Double):Point =
    Point(pt.x + dx, pt.y + dy)
// somewhere in a function ...
val pt = Point(0.0, 1.0)
val pt2 = movePoint(pt, 0.5, 0.5)

You can see that with the single data class line at the top, we can make the function movePoint() return a structured datum.

Exercise 3

With the data classes
data class Point2D(val x:Double, val y:Double)
data class Point3D(val x:Double, val y:Double, val z:Double)
at hand, which of the following is true (the == stands for equals)?
  1. 1.

    Point2D(0, 1) == Point2D(1, 0)

     
  2. 2.

    Point2D(1, 0) == Point3D(1, 0, 0)

     
  3. 3.

    Point2D(1, 0).x == Point3D(1, 0, 0).x

     
  4. 4.

    Point2D(1, 0) == Point2D(1.0, 0)

     
  5. 5.

    Point2D(1, 0) == Point2D(1, 0)

     

Describe why or why not.

Exercise 4

Which classes of the NumberGuess game are considered data classes? Perform the conversion.

Enumerations

The enumeration type is basically a nonnumeric data type with values from a given set. Basically here means that internally the type gets handled by an integer by default, but in basic usage scenarios you don’t have to be concerned about that. The term set is used in a mathematical sense, which means that values must be unique and do not have a sort order.

Enumerations in Kotlin are a specialized form of a class:
enum class EnumerationName {
    VALUE1, VALUE2, VALUE3, ...
}

where for EnumerationName you can use any camelCase name and VALUEx is any string from the character set A-Z0-9_ starting with a letter or _.

Note

For the values, technically more characters are available, but by convention you should use a combination of the characters shown here.

To declare a type of an enumeration you write, as for any other class,
val e1: EnumerationName = ...
var e2: EnumerationName = ...
where for the right side of the assignment you use EnumerationName. appending any of the enumeration values. For example, an enumeration with fruits as values would be declared and and used with this result:
enum class Fruit {
    BANANA, APPLE, PINEAPPLE, GRAPE
}
val f1 = Fruit.BANANA
val f2 = Fruit.BANANA
val f3 = Fruit.APPLE
var fx:Fruit? = null
// you can check for equality:
val b1:Boolean = f1 == f2  // -> true
val b2:Boolean = f1 == f3  // -> false
// you can reassign vars:
fx = Fruit.APPLE
fx = Fruit.BANANA
// toString() gives the textual value name
val s = fx.toString() // -> "BANANA"
Note that == is the equivalent of equals. This is a boolean expression we have not yet formally introduced. If you like, you can define the internal data type of enumeration values yourself: Just add a primary constructor to the enum class and use it for the values:
enum class Fruit(val fruitName:String) {
    BANANA("banana"),
    APPLE("apple"),
    PINEAPPLE("pineapple"),
    GRAPE("grape")
}
You can then use the name of the property you introduced to fetch this custom internal value:
val f1 = Fruit.BANANA
var internalVal = f1.fruitName // -> "banana"
An interesting built-in function for enumeration classes is the dynamic lookup function valueOf(): If you need to get a value dynamically from a string, write
val f1 = Fruit.valueOf("BANANA")
//     <- same as Fruit.BANANA
Use
EnumerationName.values()
to fetch all values for an enumeration (e.g., for loops). The enumeration values themselves also have two built-in properties:
  • Use enumVal.name to get the value’s name as a string.

  • Use enumVal.ordinal to get the value’s index in the enumeration values list.

Exercise 5

Add a Gender enumeration to the GameUser class from the NumberGuess game app. Allow values M, F, and X. Add a corresponding constructor parameter gender to the GameUser constructor parameters with default value X.

Custom Property Accessors

We know that a var property basically gets declared by writing
var propertyName:PropertyType = [initial_value]

We also know that to get the var we write object.propertyName and to set it we write object.propertyName =...

In Kotlin it is possible to change what is happening when you get or set the property. To adapt the getting process, you write this:
var propertyName:PropertyType = [initial_value]
    get() = [getting_expression]
Inside the [getting_expression] you can write what you like, including accessing functions and other properties. For more complicated cases you can also provide a body, as in
var propertyName:PropertyType = [initial_value]
    get() {
        ...
        return [expression]
    }
To instead change the setting process that applies for propertyName = ... you write
var propertyName:PropertyType = [initial_value]
    set(value) { ... }

Inside the set body you can access all the functions and all the other properties of the object. In addition, you can use the special field identifier to refer to the datum corresponding to the property.

You can, of course, do both; that is, adapt the getting and the setting processes:
var propertyName:PropertyType = [initial_value]
    get() = [getting_expression]
    set(value) { ... }
You can fine-tune the visibility of both the getters and setters of a property. Just write
[modifier] var propertyName:PropertyType = ...
    private get
    private set

or any of the other visibility modifiers. To make the getter private, though, the property itself must be declared to be private as well. Making the setter private for a public property is a valid option instead.

Interestingly, it is possible to define properties that do not have corresponding data in the class or singleton object. If you define both the setter and getter of a property and specify neither an initial value nor use field inside the setter code, no data field will be generated for that property.

Exercise 6

Can you guess what can be done with val instead of var properties?

Exercise 7

Write an str property that does the same as the toString() function (so it is possible to write obj.str instead of obj.toString()).

Exercise 8

Recall the NumberGuess game app:
data class GameUser(var firstName:String,
             var lastName:String,
             var userName:String,
             var registrationNumber:Int,
             var gender:Gender = Gender.X,
             var birthday:String = "",
             var userRank:Double = 0.0) {
    enum class Gender{F,M,X}
    var fullName:String
    var initials:String
    init {
      fullName = firstName + " " + lastName
      initials = firstName.toUpperCase() +
                 lastName.toUpperCase()
    }
}
We had the problem that with a later firstName change the fullName gets corrupted.
val u = GameUser("John", "Smith", "jsmith", 123)
u.firstName = "Linda"
val x = u.fullName // -> "John Smith" WRONG!

Find a way to avoid such corrupted states. Hint: Afterward an init{ } block is no longer needed. Update your code accordingly.

Kotlin Extensions

In Kotlin, it is possible to “dynamically” add extensions to classes. We need to put that dynamically in quotation marks because the usage of such extensions must be defined in your code prior to execution. It is not possible in Kotlin to decide during runtime whether or not, and if so, which extensions get used. Computer language designers usually refer to such features as static features.

Here is what we mean by extensions: Wouldn’t it be nice if we could add functions and custom properties to any class? This could be very useful, for example, if we want to add extra functionality to classes and functions provided by others. We know we can use inheritance for that purpose, but depending on the circumstances, this might not be possible or the implementation could feel clumsy.

Caution

The extension mechanism is extremely powerful. Be cautious not to overuse it. You can write very elegant code using extensions that no one understands without time-consuming research of the extension definitions.

Extension Functions

Consider we’d like to have a hasLength(l:Int): Boolean function inside the built-in String class. You might think that this is what inheritance is used for. However, it is not possible to extend the String class, because it is forbidden to extend String by design, so we can’t use inheritance for that aim. Still, the Kotlin extension mechanism helps us here. We can write
package the.ext.pckg
fun String.hasLength(len:Int) = this.length == len

inside some file fileName.kt (the file name doesn’t play a role here, so use whatever you like) inside some package the.ext.pckg. Remember the == checks for equality.

We can now use that extension function inside any class or singleton object and write
import the.ext.pckg.*
// anywhere inside a function ...
val hasLen10:Boolean = someString.hasLength(10)

The same process is possible for any other class, including your own classes, and companion objects. For the latter case write fun SomeClass.Companion.ext() { } to define a new extension function ext. The Companion here is a literal identifier used to address the companion object.

Note

If extension functions have the same name and function signature (parameter set) as already existing functions, the latter take priority.

Extension Properties

A similar procedure works for properties. Say you want to add an l property to String that does the same as .length() and calculates the string length. You can do that via some construct like this:
package the.ext.pckg
val String.l get() = this.length
Note that we can’t use val String.l = this.length because for technical reasons extension properties are not allowed to actually create real data fields. An initialization is thus not possible, because in fact there is nothing that can be initialized. As for getters we can write what we want, so we can directly refer to .length. Now it is possible to write
import the.ext.pckg.*
// anywhere inside a function ...
val len1 = someString.length
val len2 = someString.l // this is the same

Extensions with Nullable Receivers

Note

Receiver refers to the class or singleton object that is being extended.

It is possible to catch null values for the extension. If you prepend a question mark as in
fun SomeClass?.newFunction(...) { ... }

you can check whether this == null inside the body and appropriately react in such cases to do the right thing. You can then write instance.newFunction(...) even if instance is null and even then get into the extension function.

Encapsulating Extensions

If you want to encapsulate extensions inside particular classes, singleton objects, or companion objects, it is possible to write something like this:
class SomeClass {
    fun SomeOtherClass.meth() {
        ...
    }
}
Here SomeOtherClass receives the extension function, but that function can only be used from inside SomeClass. For the hasLength() extension for the String class, the encapsulated version thus reads
class SomeClass {
    fun String.hasLength(len:Int) = this.length == len
    fun function() {
        ...
        // we can use hasLength() here
        val len10:Boolean = someString.hasLength(10)
        ...
    }
}
class SomeClass2 {
    // we can't use String.hasLength() here
}
A similar procedure allows us to encapsulate extension properties. The notation for such properties reads
class SomeClass {
    val SomeOtherClass.prop get() = ...
}
and the encapsulated version of the String.l extension for the string length is thus
class SomeClass {
    val String.l get() = this.length
    fun function() {
        ...
        // we can use .l here
        val len = someString.l
        ...
    }
}

The obvious advantage of encapsulated extensions is that we don’t have to import extension files. If we want to define extensions that are usable for many classes, the nonencapsulated variant would be the better choice.

Functions with Tail Recursion

Recursive functions call themselves. This happens once in a while for certain algorithms. For example, the factorial function n! = n(n1)(n2)...21 can be implemented as
fun factorial(n:Int):Int {
    return if(n==1) n else n * factorial(n-1)
}

Note that the if() expression returns the part before or after the else, depending on whether the argument evaluates to true or false (we’ll be talking about branching later in the book).

For proper application functioning, the runtime engine needs to keep track of function calls, so internally a call to factorial() will look like factorial( factorial( factorial (...) ) ) If this recursion depth is not too high, this is not a problem. If it gets really high, though, we’ll run into trouble concerning memory usage and performance. However, provided the recursion happens in the last statement of such a function, it can be converted to a tail recursion function and then internally an overuse of system resources won’t happen.

To convert a function to a tail recursion function, just prepend tailrec to fun, as in
tailrec fun factorial(n:Int) {
    return if(n==1) n else n * factorial(n-1)
}

Infix Operators

Infix operators are used for operations notated by
operand1    OPERATOR    operand2
We know a lot of such infix operators: Think of multiplication (3 * 4), addition (3 + 4), and more. In Kotlin, many such infix operators are predefined, but it is also possible to define your own infix operators. To do so, write
infix operator
fun SomeClass1.oper(param:SomeClass2) = ...
where oper is the name of the operator (use your own) and the ... performs any calculation using this (the instance of SomeClass1) and param. You can then write
[expression1] oper [expression2]
where the type of [expression1] is SomeClass1 and the type of [expression2] is SomeClass2. For more complicated calculations you can also use a function body as usual:
infix operator
fun SomeClass1.oper(param:SomeClass2):ResultType {
    ...
    return [result_expression]
}
For example, to allow a string to be repeated n times using a new operator TIMES, we write
infix operator fun String.TIMES(i:Int) =
    (1..i).map { this }.joinToString("")
(The second line is a functional construct; we’ll be talking about functional design later.) We can then write
val s = "abc" TIMES 3 // -> "abcabcabc"
We can do this more cleverly if we take account of Kotlin having textual counterparts of the standard operators. The textual representation of *, for example, is times, so we can write
operator fun String.times(i:Int) =
     (1..i).map { this }.joinToString("")
which then allows us to use the asterisk for the same operation:
val s = "abc" * 3 // -> "abcabcabc"

The infix could be omitted here, because Kotlin knows that * belongs to an infix operation.

Using the standard operators for defining custom calculation is called operator overloading. In the following section we learn more about that, using a list and the textual representation of all the standard operators.

Operator Overloading

Operators take one or two expressions and produce a single output from that using the following notation:
[OPER] expression
[expression] [OPER] [expression]

Handling one expression is referred to as a unary operation and the operator accordingly gets called a unary operator. Likewise, handling two expressions gives us binary operations and binary operators.

From math we know a lot of operators like –a, a + b, a * b, a / b, and so on. Kotlin, of course, has many such operators built in for its data types, so 7 + 3 and 5 * 4 and so on do the expected things. We’ll be talking about operator expressions in detail later in this book, but for now we want to pay some attention to operator overloading, the capability of Kotlin that lets you define your own operators using standard operator symbols for your own classes.

Say, for example, you have a Point class designating a point (x, y) in space, and a Vector class designating the direct connection between two points. From what we have already learned, we know that we can declare both of them concisely via
data class Point(val x:Double, val y:Double)
data class Vector(val dx:Double, val dy:Double)
Now from math we know it is possible to write for the vector from point P1 to point P2 the expression $$ overrightarrow{v}={P}_2-{P}_1 $$. The calculation goes dx = p2.x − p1.x and dy = p2.y − p1.y. Wouldn’t it be nice if we could just write v = p2 − p1 to perform that operation, as in
val p1 = Point(1.0, 1.0)
val p2 = Point(4.0, -2.0)
val v:Vector = p2 - p1
Using operator overloading we can do exactly that. It is easy: First, we need the textual representation of the − operator, which happens to be minus. Second we write
data class Point(val x:Double, val y:Double) {
  operator fun minus(p2:Point) =
       Vector(p2.x-this.x, p2.y-this.y)
}

That is it. The val v:Vector = p2 - p1 now works, so whenever the compiler sees a - between two Point instances it calculates the vector combining them.

For unary operators the procedure is the same, but you don’t specify a parameter in the operator function. For example, if you want -Vector(1.0, 2.0)) to work, giving the reversed vector, you just add
operator fun unaryMinus() = Vector(-this.dx, -this.dy)

to the Vector class.

You can do the same for all operators Kotlin knows. The textual representation for all of them is shown in Table 4-1.
Table 4-1

Operators

Symbol

Arity

Textual

Standard Meaning

+

U

unaryPlus

Reproduces data (e.g., +3).

U

unaryMinus

Negates data (e.g., −7).

!

U

not

Logically negates data (e.g., !true == false).

++

U

inc

Increments data (e.g., var  a  =  6; a++; // -> a == 7). The operator must not change the object on which it gets invoked! The assignment of the incremented value happens under the hood.

−−

U

dec

Decrements data (e.g., var a = 6; a−−; // -> a == 5). The operator must not change the object on which it gets invoked! The assignment of the decremented value happens under the hood.

+

B

plus

Adds two values.

B

minus

Subtracts two values.

B

times

Multiplies two values.

/

B

div

Divides two values.

%

B

rem

Remainder of a division (e.g., 5 % 3 = 2).

..

B

rangeTo

Creates a range (e.g., 2..5 -> 2, 3, 4, 5)

in

!in

B

contains

Checks whether the right side is contained or not contained in the left side.

[ ]

B+

get / set

Indexed access. If on the left side of an assignment like q[5] = ... the set() function gets used with the last parameter designating the value to set. The get() and set() functions allow more than one parameter, which then corresponds to several comma-separated indexes inside []; for example, q[i]q.get(i), q[i,j]q.get(i, j), and q[i,j] = 7q.set(i, j, 7)

( )

B+

invoke

Invocation. Allows more than one parameter, which then corresponds to several comma-separated parameters inside (); for example, q(a)q.invoke(a) and q(a, b)q.invoke(a, b).

+ =

B

plusAssign

Same as plus(), but assigns the result to the instance at which the operator is invoked.

=

B

minusAssign

Same as minus(), but assigns the result to the instance at which the operator is invoked.

=

B

timesAssign

Same as times(), but assigns the result to the instance at which the operator is invoked.

/ =

B

divAssign

Same as div(), but assigns the result to the instance at which the operator is invoked.

% =

B

remAssign

Same as rem(), but assigns the result to the instance at which the operator is invoked.

==

B

equals

Checks for equality. The ! = stands for unequals and corresponds to equals() returning false.

<

>

<=

>=

B

compareTo

Compares two values. The compareTo() function is supposed to return −1, 0, +1, depending on whether the argument is less than, equal to, or greater than the value to which the function is applied.

Note

Because in the operator function body or expression you can calculate what you want, you can let the operator do strange stuff. Just bear in mind that your class users expect a certain behavior when operators are used, so be reasonable with what you calculate there.

By the way, if you prefer to overload operators in an extension file, there is no magic required. Just write operator fun TheClass.[operator_name]( [parameters] ) = ... For the earlier points and vectors example, this reads
operator fun Point.minus(p2:Point) =
    Vector(p2.x-this.x, p2.y-this.y)

Don’t forget to import the extension file, just like you would for any other extension.

Exercise 9

Add - and + operators to the Vector class. The calculation consists of adding or subtracting the dx and dy members: Vector(this.dx + v2.dx, this.dy + v2.dy) and Vector(this.dx - v2.dx, this.dy - v2.dy) if v2 is the operator function parameter.

Delegation

We learned that inheritance via class TheClass : SomeInterface { ... } lets TheClass implement functions the interface only declares in an abstract manner. The implementation code enters the overridden functions in TheClass. Delegation is similar to inheritance; it starts the same way: class TheClass : SomeInterface ... . The difference is where the implementing code resides: For delegation it is presumed that an object is at hand that already implements the interface and TheClass primarily delegates the work to this object. Using the constructs we already know, this could be written as:
interface TheInterface {
    fun someMethod(i:Int):Int
    ...more functions
}
class Implementor0 : SomeInterface {
    override fun someMethod(i:Int):Int = i*2
    ...implement the other functions
}
class Implementor : TheInterface {
    val delegate = Implementor0()
    override fun someMethod(i:Int):Int = delegate(i)
    ...do the same for the other functions
}
The method someMethod() in the Implementor class delegated to the delegate, but it could also add some extra work, as in
override fun someMethod(i:Int):Int = delegate(i-1) + 1
Kotlin has a concise notation for the delegation basic pattern. You simply write
class Implementor : TheInterface by Implementor0()
// or
val impl0 = Implementor0()
class Implementor : TheInterface by impl0
The Kotlin compiler then automatically implements all the interface methods by forwarding the work to the delegate. You still can change any function by overriding it:
class Implementor : TheInterface by Implementor0() {
    override fun someMethod(i:Int):Int = i * 42
}
If you explicitly need the delegate object, you must add it to the constructor as in
val b = Implementor0()
class Implementor(val b:TheInterface) :
        TheInterface by b {
    override
    fun someMethod(i:Int):Int = b.someMethod(i-1) + 1
}
val instance = Implementor(b)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.86.134