- Example 12: This example creates arrays of
String, Int, and
Boolean.
fun main( ) {
// Create and print array of String.
var stringArray : Array<String> =
arrayOf("C#", "Java", "Kotlin", "Python")
for(s in stringArray) {
print("$s ")
}
println("\nstringArray[1]: ${stringArray[1]}\n")
// Create and print array of String.
var intArray : Array<Int> =
arrayOf(236, 740, 981, 227)
for(n in intArray) {
print("$n ")
}
println("\nintArray[3]: ${intArray[3]}")
// Create and print array of Boolean.
var boolArray : Array<Boolean> =
arrayOf(true, false, true, true)
for(b in boolArray) {
print("$b ")
}
println("\nboolArray[0]: ${boolArray[0]}")
}
- Duck typing works for arrays. The statement
var stringArray : Array<String> =
arrayOf("C#", "Java", "Kotlin", "Python")
can also be written as
var stringArray = arrayOf("C#", "Java", "Kotlin", "Python")
- Printing an array directly without using a loop does not give useful information:
println(stringArray)
// Output:
[Ljava.lang.String;@b684286
- As in Example 12, an array can be printed with a Kotlin for
loop resembling a
traditional Java for loop:
for (i in 0..stringArray.size - 1) {
print("${stringArray[i]} ")
}
// Output:
C++ Java Kotlin Python
- Like a Java array, the size of a Kotlin array cannot be changed.
- Also like a Java array, a Kotlin array is mutable, which means that its
elements can be changed:
stringArray[4] = "JavaScript"
- Like a Java class, a Kotlin class contains instance variables and instance methods.
- Unlike Java, getters and setters can be created automatically by the compiler.
- Example 14: create a Dog class defined by this UML diagram:
+----------------------------+
| Dog |
+----------------------------+
| - name : String {get} |
| - breed : String {get} |
| - age : Int {get, set} |
| - weight : Int { get, set} |
+----------------------------+
| + haveBirthday( ) |
| + bark( ) : String |
+----------------------------+
- Here is the Kotlin code for the Dog class and a
main method to test it:
//--------------------------------------
// Example 14.
// Source code file: Dog.kt
// The fields name and breed cannot be changed, so
// they are defined with val; age and weight can be
// changed, so they are defined with var.
class Dog(val name : String, val breed : String,
var age : Int, var weight : Int) {
fun haveBirthday( ) {
age++;
}
fun bark( ) : String {
if (weight >= 20) {
return "Woof"
}
else {
return "Yip"
}
}
override fun toString( ) : String {
return "$name;$breed;$age;$weight"
}
}
//--------------------------------------
// Example 14.
// Source code file: Test1.kt
fun main( ) {
var dog1 = Dog("Sparky", "Dachshund", 3, 12)
println(dog1)
println("Name: ${dog1.name}")
println("Breed: ${dog1.breed}")
println("Age: ${dog1.age}")
println("Weight: ${dog1.weight}")
dog1.haveBirthday( )
println(dog1.age)
println(dog1.bark( ))
dog1.weight = 22
println(dog1.bark( ))
}
- Example 15. Another example is the Point class.
It contains the dist method that measures the distance from the
current Point object (x, y) to the origin
(0, 0). The equals method
returns true, which indicates that the two points
this and other are equal if
this.x == other.x
and
this.y == other.y.
The toString method of the
Point class outputs
the point in ordered pair notation, for example
(3.2, 9.0).
// Example 15
// Source code file: Point.kt
class Point(var x : Float, var y : Float) : Comparable<Point> {
fun dist( ) : Float {
var x2 : Double = (x * x).toDouble( )
var y2 : Double = (y * y).toDouble( )
return Math.sqrt(x2 + y2).toFloat( )
}
override fun compareTo(other : Point) : Int {
if(this.dist( ) > other.dist( )) return 1
else if(this.dist( ) < other.dist( )) return -1
else return 0
}
fun equals(other : Point) : Boolean {
return this.x == other.x && this.y == other.y
}
override fun toString( ) : String {
return "($x, $y)"
}
}
----------------------------------------
// Example 15.
// Source code file: Test1.kt
fun main( ) {
// Create array of four Point objects. The f suffix
// makes the constants of type Float.
var arr = arrayOf(Point(3.4f, 7.5f), Point(1.9f, 2.6f),
Point(0.3f, 0.7f), Point(1.5f, 6.1f))
// Sort points in order from closest to the origin
// to furthese from origin.
arr.sort( )
// Print sorted points.
for(p in arr) {
print("$p ${p.dist( )} ")
}
}
- Example 16. This example is inspired by Griffiths and Griffiths, Head First Kotlin, A
Brain Friendly Guide, 2019, O'Reilly
Sometimes we want to include instance variables
in our class that are not initialized with constructor parameters; sometimes we want to include
custom getters and setters in our class. Here is an example that does this:
//--------------------------------------
// Example 16.
// Source code file: Dog.kt
// Parameters defined in the class header are passed
// in to a class object via the constructor, but they
// do not immediately define instance variables because
// val or var are not used.
class Dog(name_param: String, breed_param: String,
weight_param: Int) {
// Define name field to be read only; ensure that
// it is it non-empty.
var name = if (name_param.equals("")) "Unknown" else name_param
// Define breed field. Define getter to return
// the breed in all caps.
val breed = breed_param
get( ) = field.uppercase( )
// Define weight field. Ensure that weight
// is non-negative.
var weight = if (weight_param > 0) weight_param else 0
set(value) {
field = if (value > 0) value else 0
}
// toString method
override fun toString( ) : String {
return "$name;$breed;$weight"
}
}
//--------------------------------------
// Example 16.
// Source code file: Test1.kt
fun main( ) {
var dog1 = Dog("Bentley", "Bernese Mountain Dog", 115)
println(dog1)
println(dog1.name + " " + dog1.breed + " " + dog1.weight)
var dog2 = Dog("", "Cocker Spaniel", -20)
println(dog2)
println(dog2.name + " " + dog2.breed + " " + dog2.weight)
dog2.weight = 85
println(dog2.weight)
}
- Why is wrong with these lines of code for defining a setter?
var weight = weight_param
set(value) {
if (value > 0) weight = value
}
}
- To the Dog class in Example 16, add and test this property that is not
initialized with a constructor parameter:
var vaccinated = false;
When a Dog object is created, it is initially unvaccinated. To vaccinate the dog, the
use the setter for vaccinated:
dog1.vaccinated = true
- The Map and MutableMap collections:
// Example 18
// Source code file: Test1
// Once a MutableMap collection is created,
// it cannot be modified.
fun main( ) {
var m1 : Map<String, String> = mapOf("C001" to "Green Beans",
"C002" to "Quinoa Salad", "C003" to "Garlic Hummus")
println(m1)
println(m1.getValue("C002"))
for ((key, v) in m1) {
println("$key: $value")
}
// Show that these statements cause errors:
// m1.put("C004", "Frozen Spinach")
// m1.remove("C001")
// m1.clear( )
var m2 : MutableMap<String, String> = mutableMapOf(
"C001" to "Green Beans", "C002" to "Quinoa Salad",
"C003" to "Garlic Hummus")
println(m2)
println(m2.getValue("C002"))
for (key, v) in m2) {
println("$key: $v")
}
m2.put("C004", "Frozen Spinach")
println(m2)
m2.remove("C001")
println(m2)
m2.clear( )
println(m2)
}
- The Set and MutableSet
collections are like the List and
MutableList collections, with the difference that Set
and MutableSet cannot contain duplicate items.
// Example 19
// Source code file: Test1.kt
fun main( ) {
// Create s1 as a set of prime numbers.
var s1 : Set<Int> = setOf(2, 3, 5, 7, 11, 13, 17)
println(s1)
println(s1.elementAt(2))
println(s1.indexOf(13))
// Create s2
var s2 : Set<Int> = setOf(2, 4, 6, 8, 10)
println(s2)
println(s1.intersect(s2))
println(s1.union(s2))
// For Set objects a and b, a == b returns
// true of a and b contain the same objects,
// not necessarily in the same order.
// To see if a and b are the same object,
// use the === operator.
var s3 : Set<Int> = setOf(2, 4, 8, 6, 10)
println("${s1 == s2} ${s2 == s3}")
var s4 : MutableSet<Int> = mutableSetOf(2, 3, 5, 7, 9)
println(s4)
for(item in s4) {
print("$item, ")
}
s4.add(11)
s4.remove(2)
println("\n" + s4)
s4.clear( )
println(s4)
- Notice that the keys used to insert items into a Map or
a MutableMap form a set because
duplicate keys are not allowed.
- An anonymous function is a function without a name.
- A lambda function is an anonymous function defined using arrow notation.
- Start with this Kotlin function:
fun makeGreeting(name : String) : String {
return "Hello, $name, how are you?"
}
- The same function written as an anonymous function
assigned to the variable f:
var f = fun(name : String) : String {
return "Hello, $name, how are you?"
}
Test the function f like this:
println(f("Alice"))
- The function f can also be written as a lambda function:
var f : (String) -> String = { name : String -> "Hello, $name, how are you?" }
Using duck typing, this definition of f can be shortened in several ways:
// One parameter: Version 2
var f = { name : String -> "Hello, $name, how are you?" }
// One parameter: Version 3
var f : (String) -> String) = { name -> "Hello, $name, how are you?" }
We can try shortening the definition of f even more
// Version 4 does not compile
var f = { name -> "Hello, $name, how are you?" }
However, Version 3 does not compile because the compiler needs to
know the datatype of name.
- If a lambda function has no parameters, f is defined
like this:
var f : ( ) -> String = { -> "Hello, how are you?" }
// Using duck typing, f can be simplified:
var f = { "Hello, how are you?" }
- If a function has exactly one parameter, it can
stand in for that parameter:
var f : (String) -> String = { "Hello, ${it}, how are you?" }
println(f("Alice"))
The type of f is specified by
(String) -> String. This is needed for the compiler to
know that the datatype of it is String.
- Now consider this function with two parameters:
fun makeGreeting(greetWord : String, name : String) : String {
return "$greetWord, $name, how are you?"
}
fun main( ) {
println(makeGreeting("Hello", "Alice")
}
Let's rewrite this makeGreeting function as a lambda function:
// Two parameters: version 1
var f : (String, String) -> String =
{ greetWord : String, name : String -> "$greetWord, $name, how are you?" }
// Two parameters: version 2
var f : (String, String) -> String =
{ greetWord, name -> "$greetWord, $name, how are you?" }
// Two parameters: version 3
var f = { greetWord : String, name : String -> "$greetWord, $name, how are you?" }
- Lambda functions can be passed as arguments to other functions. Here is
an example:
// Example 21
// Source Code File: TestConvert.kt
// Test the convert function.
fun main( ) {
println(convert(2.0, inToCm))
println(convert(20.0, celToFahr))
}
// Function to convert inches to centimeters:
var inToCm = { inches : Double -> 2.54 * inches }
// Function to convert Celsius temperature to Fahrenheit
var celToFahr = { cel : Double -> (9.0/5.0) * cel + 32 }
// The convert function accepts a function as a parameter
var convert = fun(value : Double, f: (Double) -> Double) : Double {
return f(value)
}
- We can simplify the main function by using
anonymous functions instead of inToCm and
celToFahr:
fun main( ) {
// If the last argument of a function is an anonymous function,
// enclose the body of the anonymous function with braces and
// denote the argument by it.
println(convert(2.0) { 2.54 * it })
println(convert(20.0) { (9.0/5.0) * it + 32.0 })
}
- For the previous main method to work, the anonymous function must be the last
argument of the convert method: the anonymous function's body is enclosed in braces, the other
arguments (in this case the value to convert) are inside the parentheses.
- Kotlin has several built in methods that accept lambda functions as arguments.
Here is a partial list:
List methods: filter filterNot forEach groupBy
max min maxBy minBy
Try out the Kotlin function repeat, which accepts two arguments with two types:
Int and (Int) -> Unit