The Programmers Guide To Kotlin: Advanced Functions
The Programmers Guide To Kotlin: Advanced Functions
Written by Mike James   
Monday, 11 September 2017
Article Index
The Programmers Guide To Kotlin: Advanced Functions
this & Extension Functions
Infix Functions

Named Parameters

The problem with specifying default parameter values with only positional parameters leads us on to the next variation - named parameters. You don't have to do anything extra, you simply use the parameters as if they were variables within the function call.

For example:

myFunction(p3=MyClass(),p1=1)

This sets values for the first and third parameter and the second takes its default value.

Using named parameters in this way not only makes it possible to be selective about which parameters use their defaults, it also makes function calls more understandable, but only if you give parameters sensible meaningful names – which is usually a difficult task.

Variable parameters

The final parameter trick that Kotlin has for us is variable argument lists.

The basic idea is that one of the parameters can be marked as a vararg. You can only have one vararg parameter and it is usually the last. The reason is that when the vararg parameter begins accepting values it continues until the closing parenthesis is reached. This means that the only way to set any parameters beyond the vararg is by name. The values stored in the vararg are treated as an array of the indicated type. Notice that varargs can only be of a single type and its sub-types. You can think of vararg as an instruction to pack all of the arguments that follow into an array.

For example:

 fun myFunction(vararg va:Int){
    println(va[0])
    println(va[1])
  }

Of course it is up to you to make sure that you don't try and use an array element that doesn't exist. You can use generics with vararg but of course this restricts what you can do with the parameters. If you want to allow mixed parameters then you can also use Any and casts:

fun myFunction(vararg va:Any){
    println(va[0] )
    println(va[1])
  }
myFunction(1,"abc",3,4)

This & Methods

When you call a method:

myObject.myMethod(parameters)

this is an alternative way of writing:

myMethod(myObject,parameters)

That is, the object instance the method is called on is conceptually the same as an additional parameter. The additional parameter is usually named this and it is refereed to as the receiver or the call context and of course, it isn't passed as parameter, it is just made available within the object.

In most cases you don't have to use this within the method to refer to the instance, because it is assumed that your references are to the current instance.

For example if you are writing a class:

class MyClass{
 var myProperty:Int=0
 fun myMethod(){
  myProperty=1
 }
}

The reference to myProperty in myMethod is shorthand for:

this.myProperty

but as it within the class declaration it is assumed. Sometimes it is a good idea to explicitly write this to distinguish what is and what is not a member of the class.

For example:

class MyClass{
 var myProperty:Int=0
 fun myMethod(myProperty:Int){
  this.myProperty=myProperty
 }
}

You have to write this.myProperty to distinguish it from the myProperty parameter. In most cases this isn't necessary.

Qualified this

As already stated you can think of this as an extra parameter that is set to reference the object that the method is currently working with. This is standard across most object oriented programming languages but Kotlin goes a little further.

In Kotlin you can have inner classes and this means that in a method you can have an inner and an outer context. The same applies to extension functions and function literals with a receiver – see the next chapter.

Normally this refers to the innermost enclosing scope i.e. the object you would most expect it to refer to. You can make this reference an outer scope by using a label. By default classes have labels that use the same name as the class.

A simple example is difficult to construct because at the least you need is one inner class:

class MyOuterClass(var myProperty: Int = 0) {
 inner class MyInnerClass(var myProperty: Int = 1) {
    fun myInnerMethod() {
      println(this.myProperty)
      println(this@MyInnerClass.myProperty)
      println(this@MyOuterClass.myProperty)
     }
    }
    fun MyOuterMethod(){
        val myInnerObject=MyInnerClass()
        myInnerObject.myInnerMethod()
    }
}

In this case we have an outer class with an inner class which has a property of the same name as the outer class and a single method that prints this.myProperty, this@MyInnerClass.myProperty and this@MyOuterClass.myProperty.

To try this out we need an instance of the outer class:

var myObject = MyOuterClass()
myObject.MyOuterMethod()

What you see is 1,1,0 corresponding the inner class's property twice, followed by the outer class’s property.

This is a potentially very confusing but occasionally useful feature of the language.

Extension Functions

In Kotlin you can add a method to any class without needing access to the class declaration. This seems to be almost magic but if you keep in mind the way that methods and this works it seems simple.

An extension method discovers the instance it is to work with via this which is set to reference the instance or receiver when the method is called. Methods get their this parameter by virtue of being part of a class, but any function that has access to a parameter that provides a reference to the current instance can look like a class member even if it isn't.

Kotlin provides the this mechanism to any function even if it isn't a member of the class – it simply has to be declared in association with the class.

For example:

fun MyClass.myExtension(){
 this,myProperty=1
 this.myMethod()
}

myExtension is not part of MyClass but it can now be called as if it was:

val myObject=MyClass()
myObject.myExtension()

It looks as if myExtension is a method of myObject but of course it isn't. The call:

myObject.myExtension()

is equivalent to:

myExtension(myObject)

where this is the first parameter of the function call. This is a simple syntactic change.

Notice that this implies that the extension method has no access to the internal workings of the class, only its public methods and properties.

You might think that this would limit the usefulness of extension methods but the ability to add methods to classes that you don't have access to including built-in classes such as Int and String is so useful that the Kotlin standard library has lots of extension methods.

Extension methods can, of course, be Generic.

Now we come to the question of how the compiler knows that there is a suitable extension?

Suppose we have two extensions:

fun MyClassA.myExtension(){...}

and:

fun MyClassB:myExtension{...}

and you write:

val myObjectB=MyClassB()
myObjectB.myExtension()

The compiler knows that there is an extension method called myExtension for ClassA and ClassB and obviously it selects the one defined on MyClassB to call for myObjectB.

The important point is that it is the type of the variable at compile time that determines which extension function is called.

That is, extensions are resolved statically at compile type rather than dynamically at runtime.

To see why this matters consider the situation where MyClassB is a subclass of MyClassA. Now if we try:

var myObject:MyClassA
myObject=MyClassB()

which is fine, as MyClassB can be treated as if it was a MyClassA. We now have a variable of type MyClassA referencing an object of type MyClassB and the question is, which extension method will be called:

myObject.myExtension()

The answer is, the one that was defined on MyClassA because this is the type of the variable, and extension methods are resolved statically on the type of the variable they are called on.

Notice that this is true of a generic extension method:

fun <T>  mutableList<T>.myExtension(){..} myObject.myExtension()

will call the extension with <Int> if the myObject variable is of type mutableList<Int> irrespective of any variance.

There are also some general points to keep in mind: 

  • Extension methods cannot be local, but they can be methods of another class. In this case this resolves both to the instance of the class that the extension is a method of, and to the instance of the class it extends as required. If there is any doubt about which this is to be used you can use a qualified this this@MyClass or this@MyExtensionClass. Extension methods can be overridden in subclasses, and which extension method is used is dynamic i.e. virtual on the class the extension is a member of, and static on the class it extends.

  • Also, if a class has a member function with the same signature, then it is called in preference to the extension function. You can define extension functions that have the same name as a member but the signatures have to be different.

  • If you are extending a nullable type you have to make sure to check for this being null in the extension.

  • As a property is just a special case for a function member, a property is just a pair of get and set functions, you can have extension properties. However, as extension functions do not have access to the class they cannot have backing fields, and hence cannot be initialized. An extension property is therefore limited to computing a value using other functions and other properties of the class. For example:
    val String.lastChar:Char
    get()= this[length-1]

    This retrieves the last character in any string and you can use it as if it was a normal String property, e.g:
    val myString="abcde"
    println(myString.lastChar)

  • You can also define extension methods for companion objects i.e. static extension methods. You define the method using the name of the companion object:
    MyClass.Companion.myExtension(){}
    and call it as if it was a normal "static" method of the class:
    MyClass.myExtension()

 

<ASIN:1871962536>



Last Updated ( Monday, 11 September 2017 )
 
 

   
Banner
Banner
RSS feed of all content
I Programmer - full contents
Copyright © 2017 i-programmer.info. All Rights Reserved.
Joomla! is Free Software released under the GNU/GPL License.