Tuesday, August 10, 2021

Kotlin

Kotlin Bootcamp for Programmers

https://developer.android.com/courses/kotlin-bootcamp/overview#lesson_2_kotlin_basics

Lesson 2: Kotlin basics

Kotlin does not implicitly convert between number types, so you can't assign a short value directly to a long variable, or a Byte to an Int. 

Null value
By default, variables cannot be null.

Use the question mark operator, ?, after the type to indicate that a variable can be null. Declare an Int? and assign null to it.

var fishFoodTreats = 6
if (fishFoodTreats != null) {
    fishFoodTreats = fishFoodTreats.dec()
}

same as above snippet :
var fishFoodTreats = 6
fishFoodTreats = fishFoodTreats?.dec()

fishFoodTreats = fishFoodTreats?.dec() ?: 0
It's shorthand for "if fishFoodTreats is not null, decrement and use it; otherwise use the value after the ?:, which is 0." If fishFoodTreats is null, evaluation is stopped, and the dec() method is not called.

!! (double-bang), converts any value to a non-null type and throws an exception if the value is null.

Arrays and Lists
val school = listOf("mackerel", "trout", "halibut")
val myList = mutableListOf("tuna", "salmon", "shark")

No mutable version of an Array. 
val mix = arrayOf("fish", 2)
val numbers = intArrayOf(1,2,3)

With a list defined with val, you can't change which list the variable refers to, but you can still change the contents of the list.

With an array defined with val, you can't change which array the variable refers to, but you can still change the contents of the array.

Combine two arrays with the + operator.
val numbers = intArrayOf(1,2,3)
val numbers3 = intArrayOf(4,5,6)
val foo2 = numbers3 + numbers
println(foo2[5])

Nested Array
val numbers = intArrayOf(1, 2, 3)
val oceans = listOf("Atlantic", "Pacific")
val oddList = listOf(numbers, oceans, "salmon")

Array Initialization with it
val array = Array (5) { it * 2 }
println(java.util.Arrays.toString(array))

it refers to the array index, starting with 0.

Because every expression has a value, you can make this code a little more concise. Return the value of the when expression directly.

Lesson 3: Functions

kotlin.Unit
Every function in Kotlin returns something, even when nothing is explicitly specified. So a function like this main() function returns a type kotlin.Unit, which is Kotlin's way of saying no value.

statements and expression
statements don't have a value, an expression has a value
Note : Loops are exceptions to "everything has a value." There's no sensible value for for loops or while loops, so they do not have values. If you try to assign a loop's value to something, the compiler gives an error.

Compact functions
Compact functions, or single-expression functions, are a common pattern in Kotlin. When a function returns the results of a single expression, you can specify the body of the function after an = symbol, omit the curly braces {}, and omit the return.

Examples of Compact Functions)
fun isTooHot(temperature: Int) = temperature > 30
fun isDirty(dirty: Int) = dirty > 30
fun isSunday(day: String) = day == "Sunday"

The default value for a parameter doesn't have to be a value. It can be another function, as shown in the following partial sample:
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = getDirtySensorReading()): Boolean {}

Note : A function used as a default value is evaluated at runtime, so do not put an expensive operation like a file read or a large memory allocation in the function. The operation is executed every time your function is called, which may slow down your program.

filters

eager and lazy filter
eager filter : the result list is created immediately
lazy filter : the result list is created when the list is accessed

In Kotlin, by default, filter is eager, and each time you use the filter, a list is created.
To make the filter lazy, you can use a Sequence.

※ The map() function performs a simple transformation on each element in the sequence

Lambdas
A lambda is an expression that makes a function. But instead of declaring a named function, you declare a function that has no name. Part of what makes this useful is that the lambda expression can now be passed as data. In other languages, lambdas are called anonymous functions, function literals, or similar names.

Higher-order functions ?? (https://kotlinlang.org/docs/lambdas.html)
You can create a higher-order function by passing a lambda to another function. In the previous task, you created a higher-order function called filter. You passed the following lambda expression to filter as the condition to check: {it[0] == 'p'}
Similarly, map is a higher-order function, and the lambda you passed to it was the transformation to apply.

For lambdas, the parameters (and their types, if needed) go on the left of what is called a function arrow ->. The code to execute goes to the right of the function arrow. Once the lambda is assigned to a variable, you can call it just like a function.


var dirtyLevel = 20
val waterFilter = { dirty : Int -> dirty / 2}
println(waterFilter(dirtyLevel))

val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
Here's what the code says:
. Make a variable called waterFilter.
. waterFilter can be any function that takes an Int and returns an Int.
. Assign a lambda to waterFilter.
. The lambda returns the value of the argument dirty divided by 2.

higher-order function
fun updateDirty(dirty: Int, operation: (Int) -> Int): Int {
   return operation(dirty)
}
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
println(updateDirty(30, waterFilter))

The function you pass doesn't have to be a lambda; it can be a regular named function instead. To specify the argument as a regular function, use the :: operator. This way Kotlin knows that you are passing the function reference as an argument, not trying to call the function.

fun increaseDirty( start: Int ) = start + 1
println(updateDirty(15, ::increaseDirty))

Kotlin prefers that any parameter that takes a function is the last parameter. When working with higher-order functions, Kotlin has a special syntax, called the last parameter call syntax, which lets you make the code even more concise.

last parameter call syntax?  https://kotlinlang.org/docs/lambdas.html#passing-a-lambda-to-the-last-parameter

Lesson 4: Classes and objects

Terminology about Class
. Classes are blueprints for objects. For example, an Aquarium class is the blueprint for making an aquarium object.
. Objects are instances of classes; an aquarium object is one actual Aquarium.
Properties are characteristics of classes, such as the length, width, and height of an Aquarium.
. Methods, also called member functions, are the functionality of the class. Methods are what you can "do" with the object. For example, you can fillWithWater() an Aquarium object.
. An interface is a specification that a class can implement. For example, cleaning is common to objects other than aquariums, and cleaning generally happens in similar ways for different objects. So you could have an interface called Clean that defines a clean() method. The Aquarium class could implement the Clean interface to clean the aquarium with a soft sponge.
. Packages are a way to group related code to keep it organized, or to make a library of code. Once a package is created, you can import the package's contents into another file and reuse the code and classes in it.

class Aquarium {
    var width:Int=20
    var height:Int=40
    var length:Int=100

    fun printSize(){
        println("Width : $width cm "+
                "Length : $length cm "+
                "Height : $height cm ")
    }
}

Constructor
In Kotlin, you define the constructor directly in the class declaration itself, specifying the parameters inside parentheses as if the class was a method. As with functions in Kotlin, those parameters can include default values.

class Aquarium(length:Int=100, width:Int=20,height:Int=40) {
    var width:Int=width
    var height:Int=height
    var length:Int=length

    fun printSize(){
        println("Width : $width cm "+
                "Length : $length cm "+
                "Height : $height cm ")
    }
}

compact form of contructor:
class Aquarium(var length:Int=100, var width:Int=20, var height:Int=40) {   
    fun printSize(){
        println("Width : $width cm "+
                "Length : $length cm "+
                "Height : $height cm ")
    }
}

Init blocks
If your constructor needs more initialization code, it can be placed in one or more init blocks.
  Notice that the init blocks are executed in the order in which they appear in the class definition, and all of them are executed when the constructor is called.(multiple init blocks are available)

Kotlin coding style says each class should have only one constructor, using default values and named parameters. This is because using multiple constructors leads to more code paths, and the likelihood that one or more paths will go untested. Before writing a secondary constructor, consider whether a factory function would work instead, to keep the class definition clean.

factory function? https://kotlinlang.org/docs/coding-conventions.html#factory-functions

secondary constructors
Every secondary constructor must call the primary constructor first, either directly using this(), or indirectly by calling another secondary constructor. This means that any init blocks in the primary will be called for all constructors, and all the code in the primary constructor will be executed first.

getter
Kotlin automatically defines getters and setters when you define properties, but sometimes the value for a property needs to be adjusted or calculated.

val volume: Int
    get() = width * height * length / 1000  // 1000 cm^3 = 1 l

setter
var volume: Int
    get() = width * height * length / 1000
    set(value) {
        height = (value * 1000) / (width * length)
    }

visibility modifiers https://kotlinlang.org/docs/visibility-modifiers.html
By default, everything in Kotlin is public, which means that everything can be accessed everywhere, including classes, methods, properties, and member variables.
  In Kotlin, classes, objects, interfaces, constructors, functions, properties, and their setters can have visibility modifiers:
. public means visible outside the class. Everything is public by default, including variables and methods of the class.
. internal means it will only be visible within that module. A module is a set of Kotlin files compiled together, for example, a library or application.
. private means it will only be visible in that class (or source file if you are working with functions).
. protected is the same as private, but it will also be visible to any subclasses.

subclasses and inheritance
In Kotlin, by default, classes cannot be subclassed. Similarly, properties and member variables cannot be overridden by subclasses (though they can be accessed).

You must mark a class as open to allow it to be subclassed. Similarly, you must mark properties and member variables as open, in order to override them in the subclass. The open keyword is required, to prevent accidentally leaking implementation details as part of the class's interface.

Note: Subclasses must declare their constructor parameters explicitly.

open class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
class TowerTank (override var height:Int, var diameter:Int):Aquarium(height=height,width=diameter,length=diameter)

Abstract Class and Interface
. Neither an abstract class nor an interface can be instantiated on its own, which means you cannot create objects of those types directly.
. Abstract classes have constructors.
. Interfaces can't have any constructor logic or store any state.

Note: Abstract classes are always open; you don't need to mark them with open. Properties and methods of an abstract class are non-abstract unless you explicitly mark them with the abstract keyword. That means subclasses can use them as given. If properties or methods are abstract, the subclasses must implement them.


interface delegation
Interface delegation is an advanced technique where the methods of an interface are implemented by a helper (or delegate) object, which is then used by a class. This technique can be useful when you use an interface in a series of unrelated classes: you add the needed interface functionality to a separate helper class, and each of the classes uses an instance of the helper class to implement the functionality.

singleton class https://en.wikipedia.org/wiki/Singleton_pattern
Kotlin lets you declare a class where you can only create one instance of it by using the keyword object instead of class.

data class
A data class is similar to a struct in some other languages—it exists mainly to hold some data—but a data class object is still an object.

You could have used == to check whether d1 == d2 and d3 == d2. In Kotlin, using == on data class objects is the same as using equals() (structural equality). If you need to check whether two variables refer to the same object (referential equality), use the === operator. Read more about equality in Kotlin in the language documentation.

Destructuring is a useful shorthand. The number of variables should match the number of properties, and the variables are assigned in the order in which they are declared in the class.

singletons, enums, and sealed classes

singleton : a class which has only one instance of it.
ex)
object GoldColor : FishColor {
   override val color = "gold"
}

enum : Enums are a bit like singletons—there can be only one, and only one of each value in the enumeration.
ex)
enum class Color(val rgb: Int) {
   RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}

Color.RED.name, Color.RED.ordinal, Color.RED.degrees

sealed class : A sealed class is a class that can be subclassed, but only inside the file in which it's declared. If you try to subclass the class in a different file, you get an error.
  Because the classes and subclasses are in the same file, Kotlin will know all the subclasses statically. That is, at compile time, the compiler sees all the classes and subclasses and knows that this is all of them, so the compiler can do extra checks for you.
ex)
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()
fun matchSeal(seal: Seal): String {
   return when(seal) {
       is Walrus -> "walrus"
       is SeaLion -> "sea lion"
   }
}

※ Compile time vs. RunTime

Lesson 5.1: Extensions

Pairs and Triples
Pairs and triples are premade data classes for 2 or 3 generic items. This can, for example, be useful for having a function return more than one value.

val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")

val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())

Destructure some pairs and triples
val equipment = "fish net" to "catching fish"
val (tool, use) = equipment
println("$tool is used for $use")

val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")

Comprehend collections
Lists and Mutable lists
main functions and purposes
add(element: E) : Add an item to the mutable list.
remove(element: E) : Remove an item from a mutable list.
reversed() : Return a copy of the list with the elements in reverse order.
contains(element: E) : Return true if the list contains the item.
subList(fromIndex: Int, toIndex: Int) : Return part of the list, from the first index up to but not including the second index.

val list = listOf(1, 5, 3, 4)
println(list.sum())

val list2 = listOf("a", "bbb", "cc")
println(list2.sum()) Error

val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })

hash maps
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
println(cures.get("white spots"))

Constants
What's the difference between const val and val? The value for const val is determined at compile time, where as the value for val is determined during program execution, which means, val can be assigned by a function at run time.
  That means val can be assigned a value from a function, but const val cannot.
val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // Error

In addition, const val only works at the top level, and in singleton classes declared with object, not with regular classes. You can use this to create a file or singleton object that contains only constants, and import them as needed.

Companion object
Kotlin does not have a concept of class level constants.
  To define constants inside a class, you have to wrap them into companion objects declared with the companion keyword. The companion object is basically a singleton object within the class.

The basic difference between companion objects and regular objects is:
. Companion objects are initialized from the static constructor of the containing class, that is, they are created when the object is created.
. Regular objects are initialized lazily on the first access to that object; that is, when they are first used.

Extension Function
Extension functions allow you to add functions to an existing class without having to access its source code.

Extension functions only have access to the public API of the class they're extending. Variables that are private can't be accessed.

class AquariumPlant(val color: String, private val size: Int)
fun AquariumPlant.isRed() = color == "red"    // OK
fun AquariumPlant.isBig() = size > 50         // Error

open class AquariumPlant(val color: String, private val size: Int)
class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)
fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")
val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print()  // what will it print?

plant.print() prints GreenLeafyPlant. You might expect aquariumPlant.print() to print GreenLeafyPlant, too, because it was assigned the value of plant. But the type is resolved at compile time, so AquariumPlant gets printed.

the type is resolved at compile time의 의미??


// How about this?
open class Parent{
    open fun print(){
        println("parent\n")
    }
}
class Child : Parent(){
    override fun print() {
        println("child\n")
    }
}
var p1:Parent= Parent()
var p2:Parent= Child()
p1.print()
p2.print()

Extension Property
val AquariumPlant.isGreen: Boolean
   get() = color == "green"

Nullable receivers
The class you extend is called the receiver, and it is possible to make that class nullable. If you do that, the this variable used in the body can be null, so make sure you test for that. You would want to take a nullable receiver if you expect that callers will want to call your extension method on nullable variables, or if you want to provide a default behavior when your function is applied to null.

fun AquariumPlant?.pull() {
   this?.apply {
       println("removing $this")
   }
}
val plant: AquariumPlant? = null
plant.pull()

Lesson 5.2: Generics

Introduction to generics
Kotlin, like many programming languages, has generic types. A generic type allows you to make a class generic, and thereby make a class much more flexible.

class MyList<T> {
    fun get(pos: Int): T {
        TODO("implement")
    }
    fun addItem(item: T) {}
}

generic constraint
class Aquarium<T>(val waterSupply: T)
class Aquarium<T: Any?>(val waterSupply: T) // The same as above line
class Aquarium<T: Any>(val waterSupply: T) // Not allowing passing null
In this context, Any is called a generic constraint. It means any type can be passed for T as long as it isn't null.

What you really want is to make sure that only a WaterSupply (or one of its subclasses) can be passed for T. Replace Any with WaterSupply to define a more specific generic constraint.
class Aquarium<T: WaterSupply>(val waterSupply: T)

In and Out Types
An in type is a type that can only be passed into a class, not returned. 
An out type is a type that can only be returned from a class.
  There aren't any methods that take a value of type T as a parameter (except for defining it in the constructor). Kotlin lets you define out types for exactly this case, and it can infer extra information about where the types are safe to use. Similarly, you can define in types for generic types that are only ever passed into methods, not returned. This allows Kotlin to do extra checks for code safety.

The in and out types are directives for Kotlin's type system.

val and var are about the VALUES of variables. val protects the variable value from being changed.

in and out are about the TYPES of variables. in and out make sure that when working with generic types, only safe types are passed in and out of functions.

Typically, making a generic function is a good idea whenever the function takes an argument of a class that has a generic type.

Sometimes out or in is too restrictive because you need to use a type for both input and output. You can remove the out requirement by making the function generic.

You can use generic functions for methods too, even in classes that have their own generic type.

function vs. method ?

Note: Generic types are normally only available at compile time, and get replaced with the actual types. To keep a generic type available until run time, declare the function inline and make the type reified.

Once a type is reified, you can use it like a normal type—because it is a real type after inlining. That means you can do is checks using the type.

If you don't use reified here, the type won't be "real" enough for Kotlin to allow is checks. That's because non-reified types are only available at compile time, and can't be used at runtime by your program. This is discussed more in the next section.

Concept: Reified types and type erasure
In the earlier example, you had to mark the generic type as reified and make the function inline, because Kotlin needs to know about them at runtime, not just compile time.
  All generic types are only used at compile time by Kotlin. This lets the compiler make sure that you're doing everything safely. By runtime all the generic types are erased, hence the earlier error message about checking an erased type.
  It turns out the compiler can create correct code without keeping the generic types until runtime. But it does mean that sometimes you do something, like is checks on generic types, that the compiler can't support. That's why Kotlin added reified, or real, types.
  You can read more about reified types and type erasure in the Kotlin documentation.

Lesson 6: Functional manipulation

Annotations
Annotations are a way of attaching metadata to code, and are not something specific to Kotlin. The annotations are read by the compiler and used to generate code or logic.
  Many frameworks, such as Ktor and Kotlinx, as well as Room, use annotations to configure how they run and interact with your code. You are unlikely to encounter any annotations until you start using frameworks, but it's useful to be able to know how to read an annotation.
  There are also annotations that are available through the Kotlin standard library that control the way code is compiled. They're really useful if you're exporting Kotlin to Java code, but otherwise you don't need them that often.

Use ::class to get information about a class at runtime.

Labeled breaks

Simple lambdas
val waterFilter = { dirty: Int -> dirty / 2 }

Higher-order function
Passing a lambda or other function as an argument to a function creates a higher-order function. The filter above is a simple example of this. filter() is a function, and you pass it a lambda that specifies how to process each element of the list.

Writing higher-order functions with extension lambdas is one of the most advanced parts of the Kotlin language. It takes a while to learn how to write them, but they are really convenient to use.

data class Fish (var name: String)
fun fishExamples() {
    val fish = Fish("splashy")  // all lowercase
}
fun main () {
    fishExamples()  // No Output Yet
}

The with() function lets you make one or more references to an object or property in a more compact way. Using this. with() is actually a higher-order function, and in the lamba you specify what to do with the supplied object.

fun fishExamples() {
    val fish = Fish("splashy")  // all lowercase
    with (fish.name) {
        this.capitalize()
    }
}

※ When function returns nothing, the return type is specified with Unit

fun myWith(name: String, block: String.() -> Unit) {}

Inside myWith(), block() is now an extension function of String. The class being extended is often called the receiver object. So name is the receiver object in this case.


The with() extension lambda is very useful, and is part of the Kotlin Standard Library. Here are a few of the others you might find handy: run(), apply(), and let().
※ Kotlin Standard Library : https://kotlinlang.org/api/latest/jvm/stdlib/

Inline functions
Lambdas and higher-order functions are really useful, but there is something you should know: lambdas are objects. A lambda expression is an instance of a Function interface, which is itself a subtype of Object. Consider the earlier example of myWith().

myWith(fish.name) {
    capitalize()
}

The Function interface has a method, invoke(), which is overridden to call the lambda expression. Written out longhand, it would look something like the code below.

// actually creates an object that looks like this
myWith(fish.name, object : Function1<String, Unit> {
    override fun invoke(name: String) {
        name.capitalize()
    }
})

Normally this isn't a problem, because creating objects and calling functions doesn't incur much overhead, that is, memory and CPU time. But if you're defining something like myWith() that you use everywhere, the overhead could add up.

Kotlin provides inline as a way to handle this case to reduce overhead during runtime by adding a bit more work for the compiler. Marking a function as inline means that every time the function is called, the compiler will actually transform the source code to "inline" the function. That is, the compiler will change the code to replace the lambda with the instructions inside the lambda.

inline myWith(fish.name) {
    capitalize()
}

// with myWith() inline, this becomes
fish.name.capitalize()

Single Abstract Methods
Single Abstract Method just means an interface with one method on it. They are very common when using APIs written in the Java programming language, so there is an acronym for it, SAM. Some examples are Runnable, which has a single abstract method, run(), and Callable, which has a single abstract method, call().



























No comments:

Post a Comment