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

3. Classes at Work: Properties and Functions

Peter Späth1 
(1)
Leipzig, Germany
 

After reading Chapter 2 about classes and objects, it is now time shine more light on properties and their types, and also on the options we have to declare functions and what can be done from inside functions. This chapter talks about property and function declarations, but also about an important feature of object-oriented languages, inheritance, through which properties and functions of some class can be altered and redefined by other classes. We also learn about visibility and encapsulation, which help us to improve program structure.

Properties and Their Types

Properties are data holders or variables that define the state of an object. A property declaration inside a class uses the optional visibility type, optional modifiers, the keyword val for immutable (nonchangeable) variables or var for mutable (changeable) variables, the name, the type, and an initial value:
[visibility] [modifiers] val propertyName:PropertyType = initial_value
[visibility] [modifiers] var propertyName:PropertyType = initial_value

Apart from this, any property from a class’s constructor prepended by val or var directly and automatically goes into a corresponding hidden property using the same name. In the following paragraphs we discuss all the possible options for properties given inside the class body.

Simple Properties

Simple properties do not provide a visibility nor any modifiers, so their declaration reads
val propertyName:PropertyType = initial_value
var propertyName:PropertyType = initial_value
respectively, for immutable and mutable variables. Here are some additional rules:
  • If inside the class or singleton object or companion object a value gets assigned inside the init{ } block, the = initial_value can be omitted.

  • If Kotlin can infer the type by the initial value given, the :PropertyType can be omitted.

Such simple properties can be accessed from outside via instanceName.propertyName for classes and ObjectName.propertyName for singleton objects. Inside the class or singleton object, just use the propertyName to access it.

Let’s add two simple properties to the GameUser class from the NumberGuess project from Chapter 2. We know the first and last name from the constructor, so it might be interesting to derive a property for initials and one for full name as follows:
class GameUser(val firstName:String,
           val lastName:String,
           val userName:String,
           val registrationNumber:Int,
           val birthday:String = "",
           val userRank:Double = 0.0) {
    val fullName:String
    val initials:String
    init {
        fullName = firstName + " " + lastName
        initials = firstName.toUpperCase() +
                   lastName.toUpperCase()
    }
}
Here you can see that for fullName and initials we have only vals, so it is not possible to reassign values to them. Because we first assign them inside init{ }, though, it is possible to omit the = initial value in the property declaration. Also, because all the constructor parameters have a val prepended, all of them get transported to corresponding properties, so all of them are properties: firstName, lastName, userName, registrationNumber, birthday, and userRank. To access them we use, for example:
val user = GameUser("Peter", "Smith", "psmith", 123, "1988-10-03", 0.79)
val firstName = user.firstName
val fullName = user.fullName
A user.firstName = "Linda" for assigning a value is not possible, though, because we have immutable vals . If we had vars instead this would be allowed:
class GameUser(var firstName:String,
           var lastName:String,
           var userName:String,
           var registrationNumber:Int,
           var birthday:String = "",
           var userRank:Double = 0.0) {
    var fullName:String
    var initials:String
    init {
        fullName = firstName + " " + lastName
        initials = firstName.toUpperCase() +
                   lastName.toUpperCase()
    }
}
// somewhere inside a function in class MainActivity
val user = GameUser("Peter", "Smith", "psmith",
        123, "1988-10-03", 0.79)
user.firstName = "Linda"
console.log(user.fullName)

Can you guess the output? This short program prints Peter Smith, although we changed the first name to Linda. The answer to the question of how this can be is that the full name gets calculated inside init{ }, and init{ } does not get invoked again after we alter the first name, so we’d have to take care of that.

Note

For example, you would introduce a new function like setFirstName() and update the first name, the full name, and the initials accordingly. A possibly cleaner variant is a function that calculates the full name on the fly, without using a separate property for it: fun fullName() = firstName + " " + lastName

This is one of the reasons you should prefer vals over vars wherever possible; it is just easier to avoid corrupted states.

Exercise 1

What is wrong with the following code?
class Triangle(color: String) {
    fun changeColor(newColor:String) {
        color = newColor
    }
}

Property Types

In the example code snippets, we’ve already seen a couple of types you can use for properties. Here is an exhaustive list.
  • String: This is a character string. Each character from the Basic Multilingual Plane (the original Unicode specification) is of type Char (see later). Supplementary characters use two Char elements. For most practical purposes and in the majority of languages, assuming each string element to be a single Char is an acceptable approach.

  • Int: This is an integer number. Values range from −2,147,483,648 to 2,147,483,647.

  • Double: This is a floating-point number between 4.94065645841246544 · 10-324 and 1.79769313486231570 · 10+308 for both positive and negative signs. Formally it is a 64-bit floating-point value from the IEEE 754 specification.

  • Boolean: This is a boolean value that can be true or false.

  • Any class: Properties can hold instances of any class or singleton object. This includes built-in classes, classes from libraries (software built by others you use), and your own classes.

  • Char: This is a single character. Characters in Kotlin use the UTF-16 encoding format (characters from the original Unicode specification) to store them.

  • Long: This is an extended integer number with values between −9,223,372,036,854,775,808 and 9,223,372,036,854,775,807.

  • Short: This is an integer number with a reduced value range. Values are from –32,768 to 32,767. You won’t see this often, because for most practical use cases an Int is the better choice.

  • Byte: This is an integer number from the very small range from –128 to 127. This type is frequently used for low-level operating system function calls. You will probably not use this type often, with the exception that it frequently gets used if you perform input/output (I/O) operations with files.

  • Float: This is a lower precision floating-point number. Ranges are from 1.40129846432481707 · 10−45 to 3.40282346638528860 · 10+38 for both positive and negative signs. Formally it is a 32-bit floating-point value from the IEEE 754 specification. Unless storage space or performance is a major issue, you would usually prefer Double over Float.

  • [Any class]: You can use any class or interface as a type, including those built in as provided by Kotlin, from other programs you use, and from your own programs.

  • [Enumeration]: Enumerations are data objects with possible values from a set of unordered textual values. See Chapter 4 for further details.

Property Value Assignment

Properties can have values assigned at four places. The first place is at the property’s declaration, as in
class TheClassName {
    val propertyName1:PropertyType1 = initial_value
    var propertyName2:PropertyType2 = initial_value
    ...
}
object SingletonObjectName {
    val propertyName1:PropertyType1 = initial_value
    var propertyName2:PropertyType2 = initial_value
    ...
}
class TheClassName {
    companion object {
        val propertyName1:PropertyType1 = initial_value
        var propertyName2:PropertyType2 = initial_value
        ...
    }
}

where initial_value is any expression or literal that can be converted to the expected property type. We will talk about literals and type conversion later in this chapter.

The second place where values can be assigned is inside init{ } blocks:
// we are inside a class, a singleton object, or
// a companion object
init {
    propertyName1 = initial_value
    propertyName2 = initial_value
    ...
}

This is only possible if the property was previously declared, either in the class, singleton object, or companion object, or as a var in the primary constructor declaration.

Only if properties have a value assigned to them inside an init{ } block can you omit the initial value assignment in the property declaration. It is therefore possible to write
// we are inside a class, a singleton object, or
// a companion object
val propertyName1:PropertyType1
var propertyName2:PropertyType2
init {
    propertyName1 = initial_value
    propertyName2 = initial_value
    ...
}
The third place where values can be assigned to properties is inside functions. Obviously this is only possible for mutable var variables. Those variables must have been previously declared using var propertyName:PropertyType = …, and for the assignment you must omit the var.
// we are inside a class, a singleton object, or
// a companion object
var propertyName1:PropertyType1 = initial_value
...
fun someFunction() {
    propertyName1 = new_value
    ...
}
The fourth place where values can be assigned is from outside the class, singleton object, or companion object. Use instanceName. or ObjectName. and append the property name, as shown here:
instanceName.propertyName = new_value
ObjectName.propertyName = new_value

This is obviously possible only for mutable vars.

Exercise 2

Create a class A with one property var a:Int. Perform assignments: (a) set it to 1 inside the declaration, (b) set it to 2 inside an init{ } block, (c) set it to 3 inside function fun b(){}, and (d) set it to 4 inside a main function.

Literals

Literals express fixed values you can use for property assignments and inside expressions. Numbers are literals, but so are strings and characters. Here are some examples:
val anInteger = 42
val anotherInteger = anInteger + 7
val aThirdInteger = 0xFF473
val aLongInteger = 700_000_000_000L
val aFloatingPoint = 37.103
val anotherFloatingPoint = -37e-12
val aSinglePrecisionFloat = 1.3f
val aChar = 'A'
val aString = "Hello World"
val aMultiLineString = """First Line
    Second Line"""
Table 3-1 lists all possible literals you can use for your Kotlin programs.
Table 3-1.

Literals

Literal Type

Description

Enter

Decimal

Integer

An integer 0,   ±  1,   ±  2,  …

0, 1, 2, …, 2147483647,

–1, –2, …, –2147483648

If you like you can use underscores as the thousands separator, as in 2_012

Double

Precision

Float

A double precision floating- point number between

4.94065645841247.10-324

and

1.79769313486232.10+308

with a positive or negative sign

Dot notation:

[s]III.FFF

where [s] is nothing or a + for positive values, – for negative values; III is the integer part (any number of digits), and FFF is the fractional part (any number of digits)

Scientific notation:

[s]CCC.FFFe[t]DDD

where [s] is nothing or a + for positive values, – for negative values, CCC.FFF is the mantissa (one or more digits; the .FFF can be omitted if not needed), [t] is nothing or a + for positive exponents, – for negative exponents, and DDD is the (decimal) exponent (one or more digits)

Char

A single character

Use single quotation marks, as in val someChar = 'A'. There are a number of special characters: write for a Tab,  for a Backspace, for a newline, for a carriage Return, ' for a single quote, \ for a backslash, and $ for a dollar sign. In addition, you can write uXXXX for any unicode character XXXX (hex values); for example, u03B1 is an α

String

A string of characters

Use double quotation marks, as in val someString = "Hello World". For the characters inside, the same rules as for Chars apply, except that for a single quotation mark you don’t use a preceding backslash, but for a double quotation mark you use one:

"Don't say "Hello"". In Kotlin there are also multiline raw string literals: Use triple double quotation marks as in """ Here goes multiline contents""". Here the escaping rules for the characters inside no longer apply (that is where the name raw comes from).

Hexadecimal

Integer

An integer 0,  ± 1,  ± 2, … using the hexadecimal basis

0x0, 0x1, 0x2, …, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x10, …, 0x7FFFFFFF, –0x1, –0x2, …, –0x80000000

Long

Decimal

Integer

A long integer 0,  ± 1,  ± 2, … with extended limits

0, 1, 2, …, 9223372036854775807, –1, –2, …, –9223372036854775808

If you like you can use underscores as thousands separator, as in 2_012L

Long

Hexadecimal

Integer

An integer 0,  ± 1,  ± 2, … with extended limits, using the hexadecimal basis

0x0, 0x1, 0x2, …, 0x9, 0xA, 0xB, …, 0xF, 0x10, …, 0x7FFFFFFFFFFFFFFF, –0x1, –0x2, …, –0x8000000000000000

Float

A single precision float

Same as double precision float, but add an f at the end; e.g., val f = 3.1415f

Note

Remember that in the decimal system 214 means 2 · 102 + 1 · 101 + 4 · 100. In the hexadecimal system we accordingly have 0x13D mean 2 · 162 + 3 · 161 + 13 · 160. The letters A, B, …, F correspond to 10, 11, …, 15.

As for type compatibility, you can assign normal integers to long integer properties, but not the other way around. You can also assign reduced precision floats to double properties, but not the other way around. Disallowed assignments require you use a conversion (see Chapter 5).

To assign literals to Short and Byte properties, use integers, but make sure the limits are not exceeded.

Both single and triple double quotation mark String literal representations exhibit a feature called string templates . That means that an expression starting with a dollar sign and followed by an expression surrounded by curly brackets gets executed and its result is passed to the string. Therefore "4 + 1 = ${4+1}" evaluates to the string "4 + 1 = 5". For simple expressions built from just a single property name, the curly braces can be omitted, as in "The value of a is $a".

Exercise 3

Find a shorter way to write
val a = 42
val s = "If we add 4 to a we get " + (a+4).toString()

avoiding the string concatenation "" + ""

Property Visibility

Visibility is about which parts of your program can access which functions and properties from other classes, interfaces, objects, or companion objects. We talk about visibility in depth in the section “Visibility of Classes and Class Members” later in this chapter.

Null Values

The special keyword null designates a value you can use for any nullable property. The null as value means something like uninitialized, not yet decided, or undefined. Any property can be nullable, but in the declaration you have to add a question mark to the type specifier:
var propertyName:PropertyType? = null
This is possible for any type, including classes, so you can write, for example:
var anInteger:Int? = null
var anInstance:SomeClass? = null
For mutable nullable var properties you can also assign null values at any time:
var anInteger:Int? = 42
anInteger = null

Other languages like Java allow nullability for any object type, which frequently leads to problems because null has neither any property nor function. Consider, for example, someInstance.someFunction(), which behaves nicely if someInstance points to a real object. If, however, you set someInstance = null, a subsequent someInstance.someFunction() is not possible and thus leads to an exceptional state. Because Kotlin draws a distinction between normal properties and nullable properties, such state inconsistencies can more easily be avoided by the Kotlin compiler.

We already used the so-called dereferencing operator (.) a lot to access functions and properties. To improve stability, Kotlin disallows the . operator for nullable variables (or expressions). Instead there is a safe-call variant “?.” you have to use in this case—the dereferencing then happens only if the value on the left side of the operator is not null. If it is null, the operator calculates to null itself. Look at this example:
var s:String? = "Hello"
val l1 = s?.length() // -> 5
s = null
val l2 = s?.length() // -> null

Exercise 4

Which of the following is true?
  1. 1.

    You can perform an assignment val a:Int = null.

     
  2. 2.

    It is possible to write val a:Int? = null; val b:Long = a.toLong().

     
  3. 3.

    It is possible to write val a:Int? = null; val b:Long? = a.toLong().

     
  4. 4.

    It is possible to write val a:Int? = null; val b:Long? = a?.toLong().

     

Property Declaration Modifiers

You can add the following modifiers to your property declaration:
  • const: Add const as in

const val name = ...
to the declaration to convert the property into a compile time constant. The property must be of type Int, Long, Short, Double, Float, Byte, Boolean, Char, or String for this to work. You can use this to avoid having to put a constant into the companion object. Other than that, concerning usage there is no difference between using and not using const.
  • lateinit: If you add lateinit as in

lateinit var name:Type
where Type is a class, interface, or String (none of Int, Long, Short, Double, Float, Byte, Boolean, Char) you tell the Kotlin compiler to accept the var being or not being null. You can thus write
class TheClass {
    lateinit var name:String
    fun someFunction() {
        val stringSize = name.length
    }
}

This leads to a runtime error but not a compile time error, and thus thwarts the Kotlin nullability check system. Using lateinit makes sense if variables get initialized in a way the Kotlin compiler cannot detect (e.g., by reflection). Do not use lateinit unless you really know what you want do. By the way, it is possible to check whether a lateinit var has been initialized or not by using ::name.isInitialized.

Member Functions

Member functions are elements of classes, singleton objects, and companion objects responsible for accessing them. Inside functions, the state of the structure unit gets queried, altered, or both. A calculation based on the state could happen, by taking an input and producing an output dependent on that input and the state. Functions can also be purely functional without using the state, which means given some particular set of input parameters they always produce the same output. Figure 3-1 illustrates the various possibilities.
../images/476388_1_En_3_Chapter/476388_1_En_3_Fig1_HTML.jpg
Figure 3-1.

Functions

Depending on the terminology used, functions are also sometimes called operations or methods .

Functions Not Returning Values

To declare a function not returning anything, in Kotlin you write inside the body of a class, a singleton object, or a companion object.
[modifiers]
fun functionName([parameters]) {
    [Function Body]
}

Inside the function body, you can have any number of return statements exiting the function. A return at the end of the body is allowed as well, but not needed.

Functions might or might not have input parameters. If they don’t, just write fun functionName() {}. If input parameters exist, they will be declared like this:
parameterName1:ParameterType1,
parameterName2:ParameterType2, ...

Note

In Kotlin, function arguments cannot be reassigned inside the function body. This is not a disadvantage, as reassigning function parameters inside the function is considered bad practice anyway.

Functions also can have variable argument lists. This feature is called varargs and we discuss it later. Another feature we cover later is default arguments . Such arguments allow for the specification of a default value that will be used if a parameter is not specified in the function invocation.

As an example, two simple function declarations with and without parameters look like this:
fun printAdded(param1:Int, param2:Int]) {
    console.log(param1 + param2)
}
fun printHello() {
    console.log("Hello")
}
Inside an interface—remember that we use interface to describe what needs to be done, but not how it needs to be done—functions do not have an implementation and thus declaring a body is not allowed. For functions not returning anything, the function declaration in interfaces thus reads like this:
fun functionName([parameters])
The optional [modifiers] you can prepend to the function declaration for fine-tuning a function’s behavior are as follows:
  • private, protected, internal, and public: These are visibility modifiers. Visibility is explained in the section “Visibility of Classes and Class Members” later in this chapter.

  • open: Use this to mark a function in a class as overridable by subclasses. See the section “Inheritance” later in this chapter for details.

  • override: Use this to mark a function in a class as overriding a function from an interface or from a superclass. See the section “Inheritance” later in this chapter for details.

  • final override: This is the same as override, but indicates that further overwriting by subclasses is prohibited.

  • abstract: Abstract functions cannot have a body, and classes with abstract functions cannot be instantiated. You must override such functions in a subclass to make them concrete (which means “unabstract” them). See the section “Inheritance” later in this chapter for details.

You cannot freely mix modifiers. Particularly for the visibility modifiers, only one is allowed. You can, however, combine any of the visibility modifiers with any combination of the other modifiers listed here. If you need more than one modifier, the separator to use is a space character.

Note that declarations in interfaces usually don’t have and don’t need modifiers. Visibility values other than public, for example, are not allowed here. Functions in interfaces are public by default, and because they have no implementations in the interfaces themselves, you can consider them abstract by default, so explicitly adding abstract is unnecessary.

Exercise 5

What is wrong with the following function?
fun multiply10(d:Double):Double {
    d = d * 10
    return d
}

Exercise 6

What is wrong with the following function?
fun printOut(d:Double) {
    println(d)
    return
}

Functions Returning Values

To declare a value-returning function in Kotlin inside a class, a singleton object, or a companion object, inside its body you add : ReturnType to the function header and write
[modifiers]
fun functionName([parameters]): ReturnType {
    [Function Body]
    return [expression]
}
The function parameters are the same as for functions not returning values, and so are the modifiers discussed previously. For the value or expression returned, Kotlin must be able to convert the expression’s type to the function return type. An example for such a function would be as follows:
fun add37(param:Int): Int {
    val retVal = param + 37
    return retVal
}

It is possible to have more than one return statement inside the body, but they all must return a value of the anticipated type.

Note

Experience says that for improved code quality it is better to always use just one return statement at the end.

It is also possible to replace the body by a single expression if this is possible:
 [modifiers]
 fun functionName([parameters]): ReturnType = [expression]
The : ReturnType can be omitted here if the type the expression yields to is the anticipated function return type. Kotlin can therefore infer from
fun add37(param:Int) = param + 37

that the function return type is Int.

Again for interfaces, functions do not have an implementation and the function declaration in this case reads
fun functionName([parameters]): ReturnType

Note

Actually Kotlin internally lets all functions return a value. If a returned value is not needed, Kotlin assumes a special void type that it calls Unit. If you omit : ReturnType and the function does not return a value, or if the function body does not have a return statement at all, Unit is assumed. If, for whatever reason, it helps to improve the readability of your program, you can even write fun name() : Unit {} to express that a function does not return any interesting value.

Exercise 7

Is the following true?
fun printOut(d:Double) {
    println(d)
}
is the same as
fun printOut(d:Double):Unit {
    println(d)
}

Exercise 8

Create a shorter version of the following class:
class A(val a:Int) {
    fun add(b:Int):Int {
        return a + b
    }
    fun mult(b:Int):Int {
        return a * b
    }
}

Exercise 9

Create an interface AInterface describing all of class A from Exercise 8.

Accessing Masked Properties

In case of name clashes, function parameters may mask class properties. Say, for example, a class has a property xyz and a function parameter has the very same name xyz, as in
class A {
    val xyz:Int = 7
    fun meth1(xyz:Int) {
        [Function-Body]
    }
}
The parameter xyz is then said to mask the property xyz inside the function body. This means if you write xyz inside the function, the parameter gets addressed, not the property. It is still possible, though, to also address the property by prepending this. to the name:
class A {
    val xyz:Int = 7
    fun meth1(xyz:Int) {
        val q1 = xyz // parameter
        val q2 = this.xyz // property
        ...
    }
}

The this refers to this current object, so this.xyz means property xyz from this current object, not xyz as made visible by the function specification.

Note

Some people use the term shadowed instead of masked for such properties. Both mean the same thing.

Exercise 10

What is the output of
class A {
    val xyz:Int = 7
    fun meth1(xyz:Int):String {
        return "meth1: " + xyz +
              " " + this.xyz
    }
}
fun main(args:Array<String>) {
    val a = A()
    println(a.meth1(42))
}

Function Invocation

Given an instance, a singleton object, or a companion object, you invoke functions as follows:
instance.functionName([parameters]) // outside the class
functionName([parameters]) // inside the classObject.functionName([parameters]) // outside the objectfunctionName([parameters]) // inside the object

To call the function of a companion object from inside the class you also just use functionName([parameters]). From outside the class, you’d use ClassName.functionName([parameters]) here.

Exercise 11

Given this class
class A {
    companion object {
        fun x(a:Int):Int { return a + 7 }
    }
}

describe how to access function x() with 42 as a parameter from outside the class in a println() function.

Function Named Parameters

For a function invocation you can use the argument names to improve readability:
 instance.function(par1 = [value1], par2 = [value2], ...)
or
 function(par1 = [value1], par2 = [value2], ...)

from inside the class or object. Here the parN are the exact function parameter names as in the function’s declaration. As an additional benefit of using named parameters, you can use any parameter sort order you like, because Kotlin knows how to properly distribute the parameters provided. You can also mix unnamed and named parameters, but it is then necessary to have all named parameters at the end of the parameter list.

Exercise 12

Given this class
class Person {
    var firstName:String? = null
    var lastName:String? = null
    fun setName(fName:String, lName:String) {
        firstName = fName
        lastName = lName
    }
}

create an instance and use named parameters to set the name to John Doe.

Caution

Using named parameters in function calls greatly improves code readability. However, be careful if you use code from other programs, as with new program versions the parameter names might change.

Function Default Parameters

Function parameters might have defaults that apply if omitted in the function invocation. To specify a default you just use
parameterName:ParameterType = [default value]
inside the function declaration. A function parameter list may have any number of default values, but they all must be at the end of the parameter list:
fun functionName(
    param1:ParamType1,
    param2:ParamType2,
    ...
    paramM:ParamTypeM = [default1],
    paramM+1:ParamTypeM+1 = [default2],
    ...) { ... }

To let the defaults apply you then just omit them in the invocation. If you omit x parameters at the end of the list, the x rightmost parameters take their default values. This sorting order dependency makes using default parameters a little cumbersome. If you mix named parameters and default parameters, though, using defaults adds versatility to functions.

Exercise 13

To the function declaration
fun set(lastName:String,
    firstName:String,
    birthDay?:String,
    ssn:String?) { ... }

add as defaults lastName = "", firstName = "", birthDay = null, ssn = null. Then invoke the function using named parameters, specifying just lastName = "Smith" and ssn = "1234567890".

Function Vararg Parameters

We learned functions exist to take input data and alter an object’s state from that, possibly producing some output data. So far we have learned about fixed parameter lists, covering a big subset of all possible use cases. What about lists of unknown, potentially unlimited size, though? Such lists are called arrays or collections, and any modern computer language needs to provide a way to handle such data in addition to types holding single data elements. We cover arrays and collections in greater detail in Chapter 9, but for now you should know that arrays and collections are fully fledged types and you can use them for single constructor and function parameters, as in …, someArray:Array<String>, ….

There is, however, a construct that sits between using many different single-valued parameters and one array or collection parameter: varargs. The idea is as follows: As the last element in the parameter list of a function declaration, add a vararg qualifier as in
fun functionName(
    param1:ParamType1,
    param2:ParamType2,
    ...
    paramN:ParamTypeN,
    vararg paramV:ParamTypeV) { ... }
The result is a function that is able to take N + x parameters, where x is any number from 0 to infinity. This is provided, however, that all vararg parameters are of the type specified by ParamTypeV. Of course, N might be 0, so a function can have a single vararg parameter:
fun functionName(varargs paramV:ParamTypeV) {
    ...
}

Note

Kotlin actually allows vararg parameters to appear anywhere earlier in the parameter list. Kotlin then, however, can distribute passed-in parameters during function invocation only if the next parameter after the vararg has a different type. Because this complicates call structures, it is better to avoid such vararg constructs.

To invoke such a function, provide all non-vararg parameters in the call, then any number of vararg parameters (including zero):
functionName(param1, param2, ..., paramN,
    vararg1, vararg2, ...)
As a simple example we create a function that takes a date as String, then any number of names, again as Strings. The declaration reads:
fun meth(date:String, vararg names:String) {
    ...
}
The following invocations are now possible:
meth("2018-01-23")
meth("2018-01-23", "Gina Eleniak")
meth("2018-01-23", "Gina Eleniak",
      "John Smith")
meth("2018-01-23", "Gina Eleniak",
      "John Smith", "Brad Cold")

You can extend the name list at will.

The question is now this: How can we handle the vararg parameter inside the function? The answer is that the parameter is an array of the specified type, and it has all the features we describe in Chapter 9, including a size property and the access operator [] to get elements as in [0], [1], and so on. If we therefore use the example function with parameters (date:String, vararg names:String) and invoke it via
meth("2018-01-23", "Gina Eleniak",
      "John Smith", "Brad Cold")
inside the function you’ll have date = "2018-01-23" and for the vararg parameter:
names.size = 3
names[0] = "Gina Eleniak"
names[1] = "John Smith"
names[2] = "Brad Cold")

Exercise 14

Build a Club class and add a function addMembers with the single vararg parameter names. Inside the function, use
println("Number: " + names.size)
println(names.joinToString(" : "))

to print the parameter. Create a main(args:Array<String>) function outside the class, instantiate a Club, and invoke its addMembers() function with three names “Hughes, John”, “Smith, Alina”, and “Curtis, Solange”.

Abstract Functions

Functions inside classes can be declared without body and marked abstract. This transforms the class into an abstract class as well, and Kotlin requires the class to be marked abstract to be compilable.
abstract class TheAbstractClass {
    abstract fun function([parameters])
    ... more functions ...
}

Abstract classes are something between interfaces and normal classes: They provide implementations for some functions and leave other functions abstract (unimplemented) to allow for some variety. Abstract classes thus frequently serve for some kind of “basis” implementation, leaving the details to one or more classes implementing the abstract functions.

Abstract functions also make functions behave like interface functions, including the fact that classes with such functions cannot be instantiated. You have to create a subclass from such an abstract class implementing all functions to have something that can be instantiated.
abstract class TheAbstractClass {
    abstract fun function([parameters])
    ... more functions ...
}
// A subclass of TheAbstractClass ->
class TheClass : TheAbstractClass() {
    override fun function([parameters]) {
        // do something...
    }
}

Here TheClass can be instantiated, as it implements the abstract function. For more details about subclassing see the section “Inheritance” later in this chapter.

Polymorphism

Inside a class, a singleton object, a companion object, or an interface, you can have several functions using the same name with different parameters. There is no magic to that, but this feature in object orientation theory has its own name: polymorphism.

If you have several functions with the same name, Kotlin decides by looking at the parameters. An invoking code specifies which function to actually use. This dispatching procedure usually works and you won’t see any problems, but with complicated classes and lots of possibilities for a certain class, perhaps including complex parameter lists with default arguments, interfaces, and varargs, the decision of which function to call is not unambiguous. In such cases the compiler issues an error message and you have to redesign the function call or your class for everything to work correctly.

Use cases for polymorphism are manifold; as a simple example consider a class with several add() functions allowing for an Int parameter, a Double parameter, or a String parameter. Its code could read:
class Calculator {
    fun add(a:Int) {
        ...
    }
    fun add(a:Double) {
        ...
    }
    fun add(a:String) {
        ...
    }
}

If you now call calc.add() with some argument, Kotlin takes the argument’s type to find out which of the functions to call.

Caution

Be careful with function naming: Polymorphism (i.e., several functions with the same name) should not happen by accident or for just technical reasons. Instead, all functions using one particular name should serve the same purpose from a functional perspective.

Local Functions

In Kotlin, functions can be declared inside functions. Such functions are called local functions and they can be used from the point of their declaration until the end of the enclosing function.
fun a() {
    fun b() {
        ...
    }
    ...
    b()
    ...
}

Inheritance

In real life, inheritance means leaving one’s belongings to someone else. In object-oriented computer languages like Kotlin, the idea is similar. Given a class A, writing class B : A indicates that we give all the assets from class A to class B. What is this good for, beyond having some kind of renamed copy of A? The magic is that class B can overrule or override parts of the assets it inherits from class A. This can be used to alter some aspects of the class it inherits from to introduce new behavior.

Although this overriding of functions and properties somewhat deviates from the real-life analogy of inheritance, inheriting classes and overriding particular functions and properties is one of the central aspects of any object-oriented language.

Classes Inheriting from Other Classes

The precise syntax for inheritance is
open class A { ... }
class B : A() {
    [overriding assets]
    [own assets]
}
if A has an empty default constructor, and
open class A([constructor parameters]) { ... }
class B : A([constructor parameters]) {
    [overriding assets]
    [own assets]
}
otherwise. Class B may, of course, have its own constructor:
open class A([constructor parameters]) { ... }
class B([own constructor parameters]) :
      A([constructor parameters])
{
    [overriding assets]
    [own assets]
}

The open in the class declaration is a Kotlin specialty. Only classes marked with open can be used for inheritance.

Note

This is a somewhat odd design decision from the makers of Kotlin. It basically disables inheritance unless you add open to all possible classes that could be used for inheritance. In real life, developers will likely forget to add open to all their classes or even refuse to add open everywhere because it feels like a nuisance, so inheritance most likely is broken if your program uses classes from other programs or libraries. Unfortunately, there is no way out, so we have to live with that. You can, of course, add open wherever needed in your own classes.

In relation to each other, the class used as a basis for inheritance is also called a superclass and the class inheriting from it is a subclass. In the preceding code, therefore, A is the superclass of B, and B is the subclass of A.

In our NumberGuess example you can see, for instance, that our MainActivity class inherits from AppCompatActivity. This subclassing of built-in activity classes is important for any app to work with Android.

Constructor Inheritance

At the very beginning of the subclass construction the superclass’s constructor including the init{ } block will be called. If the superclass provides secondary constructors, for a subclass it is also possible to call one of the secondary constructors instead. This happens by simply using the secondary constructor’s parameter signature:
open class A([constructor parameters]) {
    constructor([parameters2]) { ... }
}
class B : A([parameters2]) {
    ...
}

Because we know that secondary constructors always also invoke the primary constructor, inheritance by design in any case always invokes the superclass’s primary constructor and init{ } block. This is also true if the subclass provides its own init{ } block, which then gets called second in line. Beginners tend to forget this fact, but if you keep this in mind you could avoid some difficulty.

In Kotlin, subclasses can steal properties from the superclass’s constructor. To do so, the val or var needs to be prepended with open, as in this example:
open class A(open val a:Int) {
}
A subclass can then override the parameter in question:
open class A(open val a:Int) {
}
class B(override val a:Int) : A(42) {
    ...
}

Such an overridden property will then be addressed by any code from the superclass that formerly used its own original version of the property.

Exercise 15

What will be the output of
open class A(open val a:Int) {
    fun x() {
        Log.d("LOG",
              "A.x() -> a = ${a}")
    }
    fun q() {
        Log.d("LOG",
              "A.q() -> a = ${a}")
    }
}
class B(override val a:Int) : A(37) {
    fun y() {
        Log.d("LOG",
              "B.y() -> a = ${a}")
        q()
    }
}
// inside some activity function:
val b = B(7)
b.y()

Note that Log.d("TAG",) prints the second argument to the console.

Overriding Functions

To override functions of a superclass, in the subclass you have to use the override modifier and write
open class A {
    open fun function1() { ... }
}
class B : A() {
    override
    fun function1() { ... }
}
Again we have to add open to the function in the superclass to make it eligible for inheritance. The function could, of course, have a parameter list and parameter types must be the same in the superclass and in the subclass for overriding to work correctly. The overridden function gets a new version in the subclass, but the original version is not lost altogether. It is possible to address the original function by writing
super.functionName(param1, param2, ...)

in the subclass.

Overriding Properties

Kotlin has a special feature not found in other object-oriented languages. Not only is it possible to override functions, but properties can also be overridden. For this to work, such properties need to be marked open in the superclass, as in
open class A {
    open var a:Int = 0
}
A class that inherits from this superclass can then override the property by declaring
class B : A() {
    override var a:Int = 0
}

Using this notation, any usage of the property from inside class B and A is then covered by the property as declared in class B. The property behaves as if the declaration in class A didn’t exist any longer, and functions in A formerly using “their” version of this property will use the property from class B instead.

Exercise 16

What will be the output of
open class A() {
    private var g:Int = 99
    fun x() {
        Log.d("LOG", "A.x() : g = ${g}")
    }
    fun q() {
        Log.d("LOG", "A.q() : g = ${g}")
    }
}
class B : A() {
    var g:Int = 8
    fun y() {
        Log.d("LOG", "B.y() : g = ${g}")
        q()
    }
}
// inside some activity function:
val b = B()
b.x()
b.y()

Note that Log is provided by the Android libraries automatically included in your project. If you first get an error, place the cursor over it and then press Alt+Enter for a resolution. Can you guess why property g in class A has to be declared private, meaning no other class can see it or use it?

Exercise 17

In Exercise 16, remove the private from the property declaration and make class B override property g from class A. What will be the output?

Accessing Superclass Assets

Even with functions or properties overridden in some subclass, you can access the original versions from the superclass if you prepend a super. So, for example, in
open class A() {
    open var a:Int = 99
    open fun x() {
        Log.d("LOG", "Hey from A.x()")
    }
}
class B : A() {
    override var a:Int = 77
    override fun x() {
        Log.d("LOG", "Hey from A.x()")
    }
    fun show() {
        Log.d("LOG", "Property: " + a)
        Log.d("LOG", "Formerly: " + super.a)
        Log.d("LOG", "Function: ")
        x()
        Log.d("LOG", "Formerly: ")
        super.x()
    }
}
// inside some activity function:
val b = B()
b.show()
the output shows that from the subclass B we can use both the overridden and the original properties and functions:
Property: 77
Formerly: 99
Function:
Hey from B.x()
Formerly:
Hey from A.x()

Local Variables

Local variables are val or var variables that get declared and used inside some function; for example:
class TheClass {
    fun function() {
        ...
        var localVar1:Int = 7
        val localVar1:Int = 8
        ...
    }
}

Such local variables are valid from the place of their declaration to the end of the function; that is why they are called local. They are, of course, allowed to calculate any expression that is necessary to return something from the function, as they will not be destroyed before the return happens.

Local variables should not mask function parameters for code quality reasons. If you have a function parameter xyz of any type, you should not declare a local variable inside the function using the name xyz. The compiler allows that, but it will issue a warning about that shadowing.

Exercise 18

Which of the following classes is valid? For any invalid class, describe what the problem is.
1.    class TheClass {
          var a:Int = 7
          fun function() {
              val a = 7
          }
      }
2.    class TheClass {
          fun function(a:String) {
              val a = 7
          }
      }
3.    class TheClass {
          fun function() {
              println(a)
              val a = 7
          }
      }
4.    class TheClass {
          fun function():Int {
              val a = 7
              return a - 1
          }
      }
5.    class TheClass {
          fun function1():Int {
              val a = 7
              return a - 1
          }
          fun function2():Int {
              a = 8
              return a - 1
          }
      }

Visibility of Classes and Class Members

We have mainly talked about classes, singleton objects, and companion objects (structure units) and their properties and functions thus far in a literally free manner:
class TheName { // or object or companion object
    val prop1:Type1
    var prop2:Type2
    fun function() {
        ...
    }
}
Literally free here means structure units, functions, and properties declared this way can be freely accessed from everywhere. In Kotlin, this kind of accessibility is called public visibility. You can even add the keyword public to all of them this way to describe this public visibility explicitly.
public [class or (companion) object] TheName {
    public val prop1:Type1
    public var prop2:Type2
    public fun function() {
        ...
    }
}

For conciseness, however, you would usually omit that, because public is the default visibility in Kotlin.

In Kotlin, it is possible to impose restrictions on visibility. At first sight it might appear easier if we keep the default public visibility everywhere because anything can be accessed from everywhere and you don’t have to think about restrictions. For any nontrivial project, though, there are good reasons to consider drawing distinctions concerning visibility. The key term connected with that is encapsulation . What do we mean by that? Consider for example an analog clock. It shows the time and it provides a means to adjust the time by some clock control. We could model this by two functions, time() and setTime() :
class Clock {
    fun time(): String {
        ...
    }
    fun setTime(time:String) {
        ...
    }
}
From a user’s point of view, this is all that is needed to “talk” to a clock. What happens inside the clock is a different story: First, to adjust the time the clock needs to add or subtract some amount of time from the time currently shown. This is done by turning the control dial of the clock. Second the current state of the clock is more thoroughly described by the angles of the hour, minute, and second indicators. There is also a technical device that reacts to each second tick. This corresponds to the gear of the clock. We also need a timer that fires events every second, like the spring inside an analog clock does. Finally we also need to add some timer initialization code into an init{ } block. Taking all that into account, we’d have to rewrite our class to read like this:
class Clock {
    var hourAngle:Double = 0
    var minuteAngle:Double = 0
    var secondsAngle:Double = 0
    var timer:Timer = Timer()
    init {
        ...
    }
    fun time(): String {
        ...
    }
    fun setTime(time:String) {
        ...
    }
    fun adjustTime(minutes:Int) {
        ...
    }
    fun tick() {
        ...
    }
}
We now have two types of classes accessing assets: external ones the user cares about, and internal ones the user doesn’t need to know about. Encapsulation precisely takes care of hiding internals from clients by introducing a new visibility class, private. As the name suggests, private properties and functions are private to the structure unit and nobody from outside needs to be concerned with them, or is even allowed to access them. To indicate that a property or function is private, just add the private keyword in front of it. For our Clock class we thus write
class Clock {
    private var hourAngle:Double = 0
    private var minuteAngle:Double = 0
    private var secondsAngle:Double = 0
    private var timer:Timer = Timer()
    init {
        ...
    }
    fun time(): String {
        ...
    }
    fun setTime(time:String) {
        ...
    }
    private fun adjustTime(minutes:Int) {
            ...
    }
    private fun tick() {
            ...
    }
}
Separating functions and properties this way has the following benefits:
  • The client doesn’t need to know details of the internal functioning of a class or an object. It can just ignore anything that is marked private, reducing distractions and making it easier to understand and use the class or object.

  • Because the client only needs to know about public properties and functions, the implementation of the private functions together with all private properties is freely changeable at any time, provided the public properties and functions keep functioning in the expected way. It is thus easier to improve classes or fix deficiencies.

Back in the NumberGuess game, we already used private as a visibility specifier. If you look at just the function signatures of the activity class, you will see this:
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?)
    override fun onSaveInstanceState(outState: Bundle?)
    fun start(v: View)
    fun guess(v:View)
    ///////////////////////////////////////////////////
    ///////////////////////////////////////////////////
    private fun putInstanceData(outState: Bundle?)
    private fun fetchSavedInstanceData(
          savedInstanceState: Bundle?)
    private fun log(msg:String)
}

Here you also clearly see that we need onCreate() and onSaveInstanceState() to be public, because the Android runtime needs to access them from outside for life cycle handling. Furthermore, start() and guess() need to be public as well, because they get accessed from outside as a result of button presses. The remaining three functions are accessed from only inside the class, hence the private visibility for those.

Apart from public and private, there are two more visibility modifiers: internal and protected. Table 3-2 describes them together with the two we already know.
Table 3-2.

Visibility

Visibility

Asset

Description

public

Function or property

(Default) The function or property is visible from everywhere inside and outside the structure unit.

private

Function or property

The function or property is visible only from inside the same structure unit.

protected

Function or property

The function or property is visible from inside the same structure unit, and from inside any direct subclass. Subclasses get declared via class TheSubclass-Name : TheClassName {} and they inherit all the public and protected properties and functions of the class from which they inherit.

internal

Function or property

Functions and properties are public only for structure units from the same program. For programs from other compilations, especially for programs from others you include in your software, internal gets treated like private.

public

Class, singleton object, or companion object

(Default) The structure unit is visible from everywhere inside and outside the program.

private

Class, singleton object, or companion object

The structure unit is visible only from inside the same file. For inner classes the structure unit is only visible from an enclosing structure unit. For example

class A {

    private class B {

    ... }

    fun function() {

        val b = B()

    }

}

protected

Class, singleton object, or companion object

The structure unit is visible only from an enclosing structure unit or a subclass of it. For example

class A {

    protected class B {

    ... }

    fun function() {

        val b = B()

    }

}

class AA : A {

// subclass of A

    fun function() {

        val b = B()

    }

}

Note

For small projects, you wouldn’t care about any visibility modifiers apart from the default public one. For larger projects, adding visibility restrictions helps to improve software quality.

Self-Reference: This

Inside any class’s function, the keyword this refers to the current instance. We know that from inside the class, we can refer to functions and properties from the same class by just using their names. If visible, from outside the class we’d instead have to prepend the instance name. You can consider this as the instance name that could be used from inside the class, so, if we are in a function, to refer to a property or function from the same class we could equivalently use
functionName()      -the same as-      this.functionName()
propertyName        -the same as-      this.propertyName

If a function’s argument uses the same name as a property of the same class, we already know that the parameter masks the property. We also know that we still could access the property if we prepend this. In fact, this is the primary use case for using this. Under some circumstances it could also help to improve the readability if you prepend this. to function or property names. For example, in functions that set instance properties, using this helps to express that setting properties is the primary purpose of the function.

Consider this:
var firstName:String = ""
var lastName:String = ""
var socialSecurityNumber:String = ""
...
fun set(fName:String, lName:String, ssn:String) {
    this.lastName = lName
    this.firstName = fName
    this.socialSecurityNumber = ssn
}

It technically also works without the three this. instances, but in that case it is less expressive.

Converting Classes to Strings

In Kotlin, any class automatically and implicitly inherits from the built-in class Any. You don’t have to explicitly state it, and there is no way to prevent it. This super-superclass already provides a couple of functions, one of which has the name and return type toString():String. This function is kind of a multipurpose diagnostic function that frequently gets used to let an instance tell about its state in a textual representation. The function is open, so any class can override this function to let your classes indicate instance state in an informal way.

You are free to do whatever you want inside an overridden toString(), but most of the time one or the other property gets returned, as for example in this case:
class Line(val x1:Double, val y1:Double,
           val x2:Double, val y2:Double) {
{
    override fun toString() =
        "(${x1},${y1}) -> (${x2},${y2})"
}
Often you don’t want to miss what superclasses do in their own toString() implementation, so you might prefer to write something like this:
class Line(val x1:Double, val y1:Double,
           val x2:Double, val y2:Double) {
{
    override fun toString() = super.toString()
        " (${x1},${y1}) -> (${x2},${y2})"
}

Remember the super. addresses unoverridden properties and functions.

Exercise 19

Can you guess what happens if you write this?
class Line(val x1:Double, val y1:Double,
           val x2:Double, val y2:Double) {
{
    override fun toString() = toString() +
        " (${x1},${y1}) -> (${x2},${y2})"
}
Many built-in classes in their toString() implementation already provide some useful output, so in most cases you don’t have to override built-in classes just for the sake of providing a sensible toString() output. What happens for some of the other built-in classes and for any of your classes without their own toString() implementation is that toString() indicates the memory position of the instance. For example:
class A
val a = A()
println(a.toString())

will print something like A@232204a1, which, depending on the circumstances, is not very informative. Therefore for diagnostic output, providing a toString() implementation is a good idea.

..................Content has been hidden....................

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