Kotlin Fundamentals

The article is about the fundamental concept of Kotlin and my comprehension about those concepts mainly based on the official documentation and other sources.

1. if expression

the if-condition statement can be reduced into an expression to assigned a variable or constant value. The else branches couldn’t be omitted.

var a = if (b > c) d else e

2. safe call operator

The safe call operator is used when the receiver might be null type. Once it is null type, the operation on the receiver would cause NullPointerExceptions (NPE) in Java, so Kotlin develops a safe call operator to avoid NPE. If the receiver is null, the return value of that operation is null. Thus, it is SAFE to call an object with safe call operator.

/** str: String? **/
// without safe call operator
var l: Int? = if (str != null) str.length else null
// with safe call operator
var l: Int? = str?.length

3. Let function

Let function usually works with safe call operator. It execute the function if the object is not null.

val withNullList: List<String?> = listOf("Kotlin", null, "Great")
for (item in withNullList) {
	item.let { print(item) } // Kotlin Great
}

4. Elvis Operator

Elvis operator is actually an if expression, which returns the left hand side expression if not null, or the right hand side operation.

/** str: String? **/
// use if expression
var l: Int? = if (str != null) str.length else -1
// use Elvis operator
var l: Int? = str?.length ?: -1

5. is and in keywords and == operator

is keyword is to check if a variable is the specific type. If the variable is null type, the is keyword checks the actual type of the variable.

var str: String? = "Kotlin"
if (str is String) {
	print("String")
else {
	print("not String")
} // String
// if str = null, it will print("not String")

in keyword is to check if a variable is in the range, set, list, map or something.

if (3 in 1..6) print("yes") // yes

== operator is to check if two variable is equal

if (3 == 3) print(3)

Their not type keyword and operator are !is, !in and !=, respectively.

6. Progression

Progression is a special type. For example, type of Int progression is IntProgression, it has three essential properties - first, step and step.

val pro = 1..6 step 2
print("${pro.first}, ${pro.step}, ${pro.last}") // 1, 2, 5

7. When Statement and When Expression

When statement is a kind of statement like C-switch-case statement, but there is a distinct difference that once the first branch is tested true in the when statement and expression, the very branch is executed and jump over the statement, which means that one and only one branch is executed.

var x: Int = 3
when (x) {
	in 1..2 -> print("0")
	in 1..5 -> print("1")
	in 1..8 -> print("2")
	else -> print("else")
} // output: 1

When statement and expression could be a replacement of if statement and if expression

8. it parameters

If there is only one input argument in the function, it can be used to replace the argument

val fruits: List<String> = listOf("apple", "banana", "pear")
fruits.forEach { println(it) }

9. Collection

Collection
Collection offers two kinds of collection - (immutable) collection and mutable collection. Immutable collection means that it only support Read-only operation (retrieving, …) and mutable collection offers write operation. Note that immutability does not require that the elements in the very collection could not change, it just suggests that the reference of the collection would not change. Sequently, mutable collection does not request var declaration. In other words, if a mutable collection is claimed value, one can change its element but can not reassign it.

val list: MutableList<Int> = mutableListOf(1,2,3)
println(list) // [1, 2, 3]
list.add(4)
println(list) // [1, 2, 3, 4]
//list = mutableListOf(1,2,3) // error: val cannot be reassigned

Collection Type and Their Mutability

declaration classinitialization functionmutability
Collection<T>immutable
MutableCollection<T>mutable
List<T>listOf()immutable
MutableList<T>mutableListOf()mutable
Set<T>setOf()immutable
MutableSet<T>mutableSetOf()mutable
Map<K, V>mapOf()immutable
MutableMap<K, V>mutableMapOf()mutable

Note: Map is not an inheritor of Collection, but it is included in the collection type in Kotlin.

10. Class

In a class, primary constructor and property are regarded as the initializer, and primary constructor delegates to secondary constructors, so the code in initializer blocks would be executed before that of secondary constructors.

class C() {
	constructor() {
		println("secondary constructor")
	}
	init {
		println("initializer blocks")
	}
}
fun main() {
	val c: C = C() // initializer blocks
				   // secondary constructor
}

10.1 Nested Class

A nested class is a class declared in a class. There are two types of nested classes, which are (common) nested class and inner (nested) class. The difference between them is that the inner class which marked as inner keyword is able to access the properties of the outer class, but (common) nested class are not.

// common nested class
class A {
	val a: Int = 1
	class B {
		// fun foo() = a // error: unresolved reference: a
		fun foo1() = 2 // it is ok
	}
}
// inner nested class
class C {
	val b: Int = 2
	inner class D {
		val c: Int = 3
		inner class E {
			fun foo() = b + c // it is OK
		}
	}
}
fun main() {
	println(A.B().foo1()) // 2
	println(C().D().E().foo()) // 5
}

Notice the invoking form of the methods in nested class above; compared with Python syntax, for the (common) nested class - A.B().foo1(), it seems that the nested class, B, is a “global property” of class A, so it is accepted to access B through A.B. However, for inner class D and E, the invoking form is C().D().E(), it suggests that D is the “actual property” of C and E is the “actual property” of C. If C.D.E().foo() is compiled, an error occurs such that “error: constructor of inner class E can be called only with receiver of containing class”. It means that class C contain class C and class D contains class E.

10.2 open and final

open declares a class or a member is able to be inherited or overriden, and “final” claims the opposite.

10.3 Class, Abstract Class, Interface and Object

class in Kotlin is the same as those in other programming languages. It is worth noting that the class and its members by default are final, which means that the specific class could not be inherited and the properties and functions could not be overridend, so if we want to define a class as a super class, it could be done by using a reserved keyword open before class.

abstract class is essentially a class. An abstract class is literally a class that defined abstractly of something. As described in the official documentation,

A class and some of its members may be declared abstract. An abstract member does not have an implementation in its class. Note that we do not need to annotate an abstract class or function with open – it goes without saying.

An abstract class is by default “open” and members of an abstract class are non-abstract, suggesting that they could not be overridend. To offer overriding, keyword “abstract” is needed. There are some rules about abstract class:

  1. Instantiation of an abstact class is not allowed.
  2. All the members in abstract class are by default non-abstract. Annotate it with open to allow overriding.
  3. Members with abstract annotation is open, but initializations and definitions of those abstract members are not allowed, meaning that no value is assigned to values or variables and no body is attached to abstract function. Additionally, overriding of those abstract memebers is needed in the class inherited from the abstract class.

An abstract class can inherited from an open class.

interface is a term kind of similar to abstract class, but its members by default are abstract and open. In principle, interface cannot hold state.

object, explained in the official documentation, is just an object of an annonymous class. In another word, A variable declared with an object type is an object, which it do not need to instantiate and do not offer a constructor.

WHEN TO USE ABSTRACT CLASS AND INTERFACE?
Abstract class is an abstract of something, so it describes things in the same family. However, interface, as its name shown, is an interface, so it contains many class, as long as they do the same thing. For example, Person is an abstract class and Students, Presidents, Artists are its children. Is Drivers a child of Person? Not really true. It is because student can be a driver and president can also be a driver only if they get a license.

abstract class Person(val name: String) {
	/* code */
}
class Students(name: String): Person(name) {
	/* code */
}
class Presidents(name: String): Person(name) {
	/* code */
}
class Artists(name: String): Person(name) {
	/* code */
}
/* and other classes */
interface Driver {
	fun getLicense()
}

11. Field and Property

A field in Java is a variable in class. In Kotlin, property with accessor is used. To create a Java field,

String name = "Goodman";

However, in Kotlin,

var name: String = "Goodman"

The equivalent code to perform Kotlin property in Java

String name = "Goodman";
public String getName() {
	return name;
}
public void setName(String name) {
	this.name = name;
}

In Kotlin, accessor is used

var name: String
	set(value) {
		if (value.isNotBlank()) {
			field = value
		}
	}

12. Delegation

It is hard to understand delegation when reading the documents. Referring to Widipedia and blog, and based on my understanding, delegation is literally what it tells us that an objects (usually subclasses) delegates some operations (functions, classes) to another objects (classes, usually super classes). For example, there is a open class Person, and final class Male and Female are its subclasses.

open class Person(val name: String) {
	fun getname() = name
}
class Male(name: String): Person(name)
class Female(name: String): Person(name) 

Besides, we want to have a function that returns the name of each object. Since there are three types, overload (functions own the name, but with different types or number of arguments) might be used in C++ (not sure). Equivalent code in C++ is shown (which might be a wrong statement)

/* 
class Person;
class Male;
class Female;
*/
void hello(Person p) {
	std::cout << "hello " << p.getname() << std::endl;
}
void hello(Male m) {
	std::cout << "hello " << m.getname() << std::endl;
}
void hello(Female f) {
	std::cout << "hello " << f.getname() << std::endl;
}

In Kotlin, delegation could be used to simplify the code, because Male and Female are inherited from Person and they can delegate to Person. Kotlin code is shown

fun hello(p: Person) = println("hello " + p.getname)
fun main(args: Array<String>) {
    val p: Person = Person("James")
    val m: Male = Male("Jones")
    val f: Female = C("Janes")
    hello(p) // hello James
    hello(m) // hello Jones
    hello(f) // hello Janes
}

In face, delegation in Kotlin is kind of like dynamic binding in C++.
NOTE: Do not use getProperty and setProperty as a member function name, such as getName() in the above example. Once doing that, the compiler would complain, “platform declaration clash”, because the declarations have the same JVM signature, as shown in the above section, Field and Property, where Java uses getProperty() and setProperty() as the accessor in Kotlin. If we use those name, name clashes in different platforms when Kotlin translates it into Java.

Taking three examples to compare the results and deliberating why those results are returned.

// example 1 (included in the official tutorial)
fun main(args: Array<String>) {
    val b: BaseImpl = BaseImpl(10)
    Derived(b).print() // 10
}
interface Base {
    fun print() = println("Base")
}
class BaseImpl(val x: Int) : Base {
    override fun print() = print(x) 
}
class Derived(val b: Base) : Base by b
//example 2
fun main(args: Array<String>) {
    val b: BaseImpl = BaseImpl(10)
    Derived(b).print() // Base
}
interface Base {
    fun print() = println("Base")
}
class BaseImpl(val x: Int) : Base {
    override fun print() = print(x) 
}
class Derived(val b: Base) : Base
// example 3
fun main(args: Array<String>) {
    val b: BaseImpl = BaseImpl(10)
    Derived(b).print() // 10
}
interface Base {
    fun print() = println("Base")
}
class BaseImpl(val x: Int) : Base {
    override fun print() = print(x) 
}
class Derived(val b: Base) : Base {
	override fun print() = b.print()
}

In example 2, Derived inherited from Base does not override the print function, so it keeps print(“Base”) as the return of print(). However, example 1 and example 3 seems to do the same thing and they actually do the same thing, the difference is that overriding (in example 3) is done by the compiler (in example 1). The code in example for compiler is something like

fun main(args: Array<String>) {
    val b: BaseImpl = BaseImpl(10)
    Derived(b).print() // 10
}
interface Base {
    fun print() = println("Base")
}
class BaseImpl(val x: Int) : Base {
    override fun print() = print(x) 
}
class Derived(val b: Base) : Base by b{
	//override fun print() = b.print()
}

With using by-clause, the overriding could be omitted. Literally, “Base by b” suggests that Base (decided/defined by) b (the parameters), so it is delegation. the article How to Work with Delegation in Kotlin considers example 1 as implicit delegation and example 3 as explicit delegation.

There are two types of delegation:
Explicit delegation, which can be implemented in any object-oriented language.
Implicit delegation, which requires language-level support for the delegation pattern.

12.1 Delegated Properties

13.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值