While using kotlin, it summarizes differences from java and sample codes.
0. The main function does not have to be inside a class.
If the main function is defined without a class, a class is created with the file name and the main function is put into it.
fun main(args: Array<String>) { | |
println("Hello World!") | |
} | |
//// java code | |
//public final class MainKt { | |
// public static final void main(@NotNull String[] args) { | |
// System.out.println("Hello World!"); | |
// } | |
//} |
1. All primitive types are classes.
Basic types are not very different from Java, but they are all in the form of classes.
// Base type is class | |
fun main(args: Array<String>) { | |
val a: Byte = 0 // 8 | |
val b: Short = 0 // 16 | |
val c: Int = 0 // 32 | |
val d: Long = 0 // 64 | |
val e: UByte = 0u | |
val f: UShort = 0u | |
val g: UInt = 0u | |
val h: ULong = 0u | |
val i: Float = 0F | |
val j: Double = 0.0 | |
val k: Float = 0F | |
val l: Double = 0.0 | |
val bool: Boolean = true | |
val ch: Char = 'a' | |
val str: String = "abc" | |
} |
2. At compile time, a check is made for the scope of the base type.
An error occurs when initializing a value beyond the maximum value, such as val a: Byte = 500.
You can prevent overflow for an initial wrong type.
// If out of the range according to the type, an error occurs. | |
// The type is automatically set according to the given value. | |
fun main(args: Array<String>) { | |
// val a: Byte = 500 // compile error | |
val c = 0 | |
val bool = true | |
val ch = 'a' | |
val str = "abc" | |
c.toString() // has method | |
} |
3. Add fun to function definitions, val and var to variable definitions.
fun foo(a: Int): Int // fun function_name(parameter): return_type
Add val or var in front of a variable, where val means that does not change to value, and var means that changes to variable.
// define function | |
fun foo(a: Int): Int { | |
return 0 | |
} | |
// val : value, var : variable | |
fun main(args: Array<String>) { | |
val a = foo(0) | |
// val, var | |
val c = 0 | |
c = 1 // compile error | |
var d = 0 | |
d = 1 | |
} |
4. Using a for loop
Can be used in multiple styles.
// for loop | |
fun main(args: Array<String>) { | |
for (x in 1..5) { | |
// 1, 2, 3, 4, 5 | |
} | |
for (x in 1 until 5) { | |
// 1, 2, 3, 4 | |
} | |
for (x in 5 downTo 1) { | |
// 5, 4, 3, 2, 1 | |
} | |
for (x in 1..5 step 2) { | |
// 1, 3, 5 | |
} | |
for (item in args) { | |
} | |
for (item in args.indices) { | |
} | |
} |
5. if, when
You can use an if statement as a value, like val c = if (a == 5) "a is 5" else "a is not 5". In this case, there should always be an else.
You can think of using when instead of switch. Use else instead of default.
The values used in when are used from basic types to class objects,
Multiple values can be grouped into one item using ','.
// if, when | |
class Cat(n: String, w: Int) { | |
val name = n | |
var weight = w | |
} | |
fun main(args: Array<String>) { | |
val a = 5 | |
val b = "abc" | |
val c = if (a == 5) "a is 5" else "a is not 5" | |
println(c) | |
when (a) { | |
1 -> println("1") | |
2,3,4 -> { println("2,3,4") } | |
5 -> println("5") | |
else -> println("else") | |
} | |
when (b) { | |
"abc" -> println("1") | |
else -> println("else") | |
} | |
val cat = Cat("a", 0) | |
val cat1 = Cat("a", 0) | |
// val cat1 = cat | |
when (cat) { | |
cat1 -> println("cat 1") | |
else -> println("cat else") | |
} | |
} |
6. Defining member variables
If the class name parameter is defined as val or var, it becomes a member variable. It is mainly used in "data class" types.
Member variables can be initialized with the passed parameters, and can also be initialized using the init block.
In the case of var variables, you can use lateinit to delay initialization (initialize them before use).
lateinit cannot be used on val variables. val is immutable and must be initialized at creation time.
// class | |
class Dog(val name: String, var weight: Int) { | |
} | |
class Cat(n: String, w: Int) { | |
val name = n | |
var weight = w | |
} | |
class Wolf(n: String, w: Int) { | |
val name: String | |
var weight: Int | |
init { | |
name = n | |
weight = w | |
} | |
} | |
class Duck(n: String, w: Int) { | |
val name = n | |
// lateinit var weight: Int // lateinit - Not applicable to basic types | |
var weight: Int = 0 | |
lateinit var dog: Dog | |
fun foo() { | |
weight = 0 | |
dog = Dog("aaa", 0) | |
} | |
} |
7. Defining getters and setters
In Java, you need to define get~, set~ methods, but in kotlin, you can attach get(), set(value) under member variables.
Getters and setters are simply defined, and set and get are automatically called when values are assigned or used as if using variables.
// getter setter | |
class Cat() { | |
val name: String | |
get() { return "abc" } | |
var weight: Int = 0 | |
get() { | |
return if (field < 0) | |
-1 | |
else | |
field | |
} | |
set(value) { | |
println("Hello") | |
field = value | |
} | |
} |
8. Using Inheritance
A class in Kotlin is a final class in Java. not inherited
Kotlin's open class is Java's class. inheritance is possible
Functions that can be overridden are prefixed with "open". When override it, add "override" in front.
Abstract classes are defined using the "abstract" keyword.
// open class | |
open class Animal() { | |
open val name: String = "" | |
open fun print() { | |
println("Animal $name") | |
} | |
private fun print2() { | |
println("Animal2 $name") | |
} | |
} | |
class Cat(): Animal() { | |
override val name: String = "cat" | |
override fun print() { | |
println("Cat $name") | |
} | |
fun print2() { | |
println("Cat2 $name") | |
} | |
} | |
fun main(args: Array<String>) { | |
val cat = Cat() | |
val animal: Animal = cat | |
cat.print() | |
cat.print2() | |
animal.print() | |
animal.print2() | |
} | |
// =================================================== | |
// abstract class | |
abstract class Animal() { | |
abstract val name: String | |
abstract fun print() | |
private fun print2() { | |
println("Animal2 $name") | |
} | |
} | |
class Cat(): Animal() { | |
override val name: String = "cat" | |
override fun print() { | |
println("Cat $name") | |
} | |
fun print2() { | |
println("Cat2 $name") | |
} | |
} | |
fun main(args: Array<String>) { | |
val cat = Cat() | |
val animal: Animal = cat | |
cat.print() | |
cat.print2() | |
animal.print() | |
// animal.print2() | |
println("${animal is Cat}") | |
val animal2 = cat as Animal | |
} |
9. Null Stability - 1
When defining a variable, add "?" to determine whether to use null.
var testName: String? = null
If you attach "!!" when using it, a null pointer exception occurs when it is a null value, as when using it in java.
If you use it with "?", it doesn't do anything if it is a null value.
dog.printName() // compile error
cat2.printName()
Both of them did a null check in the if statement, but one generated a compilation error and the other did not.
The difference is whether the value of the variable can be changed after the if statement.
If dog is a member variable and operates in multi-thread, it may change in another thread immediately after passing the if statement.
cat2 is a local variable that cannot be changed within the function. After checking for null, "." It is possible to use only
// null safety - 1 | |
class Dog(n: String, w: Int) { | |
val name = n | |
var weight = w | |
var testName: String? = null | |
fun printName() { | |
testName = "aa" | |
} | |
} | |
class Cat(n: String, w: Int) { | |
val name = n | |
var weight = w | |
var dog: Dog? = null | |
fun printName() { | |
dog = Dog("a", 5) | |
} | |
fun testPrintName() { | |
if (dog != null) { | |
// dog.printName() // compile error | |
} | |
dog!!.printName() // if null, exception | |
dog?.printName() | |
} | |
} | |
fun testPrint1() { | |
var cat2: Cat? = null | |
if (cat2 != null) { | |
cat2.printName() | |
} | |
} |
10. Null Stability - 2
dog?.let defines a block that operates only when dog is not null and accesses dog using "it".
The elvis operator "?:" simplifies the if statement.
// null safety - 2 | |
class Dog(n: String, w: Int) { | |
val name = n | |
var weight = w | |
} | |
class Cat(n: String, w: Int) { | |
var dog: Dog? = null | |
fun testPrintName() { | |
var num: Int? = null | |
if (dog != null) { | |
println("dog is not null") | |
} | |
dog?.let { | |
println("dog is not null ${it.name}") | |
} | |
val aa = if (num != null) { | |
num | |
} else { | |
-1 | |
} | |
val bb = num ?: -1 | |
} | |
} |
11. data class
It is defined by attaching data in front of the class name. If the defined value is the same in the constructor parameter, true is returned during == operation.
Use "===" to check whether objects are the same.
The data class internally defines several methods such as copy and equals. (Recommended to convert and compare with java code)
data class Book(val title: String, val isNew: Boolean) | |
class BookNotData(val title: String, val isNew: Boolean) | |
fun main(args: Array<String>) { | |
val nr1 = BookNotData("Snow White", false) | |
val nr2 = BookNotData("Snow White", false) | |
// val nr3 = nr1.copy(title = "Cinderella") // compile error | |
println("nr1 hash code : ${nr1.hashCode()}") | |
println("nr2 hash code : ${nr2.hashCode()}") | |
println("nr1 toString : ${nr1.toString()}") | |
println("nr2 toString : ${nr2.toString()}") | |
println("nr1 == nr2? ${nr1 == nr2}") | |
println("nr1 === nr2? ${nr1 === nr2}") | |
println("#####################################################") | |
val r1 = Book("Snow White", false) | |
val r2 = Book("Snow White", false) | |
val r3 = r1.copy(title = "Cinderella") | |
println("r1 hash code : ${r1.hashCode()}") | |
println("r2 hash code : ${r2.hashCode()}") | |
println("r3 hash code : ${r3.hashCode()}") | |
println("r1 toString : ${r1.toString()}") | |
println("r2 toString : ${r2.toString()}") | |
println("r3 toString : ${r3.toString()}") | |
println("r1 == r2? ${r1 == r2}") | |
println("r1 === r2? ${r1 === r2}") | |
println("r1 == r3? ${r1 == r3}") | |
val (title, isNew) = r1 | |
println("title is $title and it is new $isNew") | |
} |
12. Using Arrays
Returns an array whose values are set through arrayOf.
The value of the array can be changed (you can see what it means by comparing it to List).
There is also a way to define the number other than value setting, and it is possible to set an initial value like "IntArray(10) { index -> index }".
In the case of arrays related to numbers like Int type, frequently used operation functions such as sum and average are added.
fun mainTest1(args: Array<String>) { | |
val array = arrayOf(1, 3, 2) | |
val array2 = arrayOf('a', 'b', 'c') | |
val array3 = arrayOf("ab", "bc", "ca") | |
var array4: Array<String?> = arrayOfNulls(2) | |
val array5 = IntArray(10) | |
val array6 = IntArray(10) { index -> index } | |
print("array5 : ") | |
for (item in array5) { | |
print("$item, ") | |
} | |
println() | |
print("array6 : ") | |
for (item in array6) { | |
print("$item, ") | |
} | |
println() | |
} | |
fun mainTest2(args: Array<String>) { | |
val array = arrayOf(1, 3, 2) | |
val size = array.size | |
for (item in array) { | |
print("$item, ") | |
} | |
println() | |
array.reverse() | |
for (item in array) { | |
print("$item, ") | |
} | |
println() | |
val isIn = array.contains(1) | |
val sum = array.sum() | |
val average = array.average() | |
println("$sum, $isIn, $average") | |
array.sort() | |
for (item in array) { | |
print("$item, ") | |
} | |
println() | |
} |
13. List, MutableList
The main data storage types include a List with order, a Set without duplicate values, and a Map connected with a key.
These types cannot be changed in value, and if you want to change it, define it as a type with Mutable attached. (MutableList...)
fun main(args: Array<String>) { | |
val muList = mutableListOf("tea", "egg") | |
val list = listOf("tea", "egg") | |
// list[0] = "a" | |
// list.add("test") | |
val list2 = muList.toList() | |
muList.add("apple") | |
val list3 = muList.toList() | |
print("list2 : ") | |
for (item in list2) { | |
print("$item, ") | |
} | |
println() | |
print("list3 : ") | |
for (item in list3) { | |
print("$item, ") | |
} | |
println() | |
muList[0] = "milk" | |
print("muList : ") | |
for (item in muList) { | |
print("$item, ") | |
} | |
println() | |
println("muList size : ${muList.size}") | |
muList.remove("apple") | |
println("muList size : ${muList.size}") | |
muList.removeAt(0) | |
println("muList size : ${muList.size}") | |
for (item in muList) { | |
print("mu $item, ") | |
} | |
println() | |
} |
14. Lambda expression
These days, when you look at development languages, you often see support for lambda expressions (c++, java, etc.).
It is a useful development method if you know and use it.
This expression is used when you want to access the created object's variable from another object and create the desired result.????
As I said, it is not easy to explain, but I recommend that you understand it by looking at several sample codes.
In the example code, { num -> list.contains(num) } is created as an internal object and its reference is passed to LambdaTest.
It's important to understand where lambda expressions act as inner objects.
class LambdaTest(val test: (Int) -> Boolean) { | |
var checkNum = 1 | |
fun isOk(): Boolean { | |
return test(checkNum) | |
} | |
} | |
class LambdaUser(arg: List<Int>) { | |
var list = arg | |
fun test() { | |
val lambdaTest1 = LambdaTest() { num -> list.contains(num) } | |
println("test : ${lambdaTest1.isOk()}") | |
val lambdaTest2 = LambdaTest() { num -> list.contains(num) == false } | |
println("test : ${lambdaTest2.isOk()}") | |
} | |
} | |
fun main(args: Array<String>) { | |
val lambdaUser1 = LambdaUser(listOf(1, 2, 3, 4, 5)) | |
lambdaUser1.test() | |
println("#######") | |
val lambdaUser2 = LambdaUser(listOf(2, 3, 4, 5)) | |
lambdaUser2.test() | |
} |
15. Coroutines are not multi-threaded.
When I first saw it, I understood it as a multi-threaded concept because it said light concurrent work.
The operation is completely different because it is a concurrent operation that is performed without context switching.
When calling delay, which is used for coroutines, the control passes to scope management.
I think each coroutine will be called according to the delay time, but unlike multi-threading, the guarantee that it will be called is weak.
If the coroutine execution is short and a delay is called to move to scope management, you may get the desired result.
If coroutine execution is long, coroutines within the same scope are not guaranteed to be executed in time.
If you put sleep, other coroutines do not work until sleep ends.
It is used when asynchronous execution with short execution time is required.
import kotlinx.coroutines.* | |
import java.lang.Thread.sleep | |
import java.time.Instant | |
import java.time.ZoneOffset | |
import java.time.format.DateTimeFormatter | |
fun main() = runBlocking { | |
printTime() | |
doWorld() | |
printTime() | |
println("Done") | |
} | |
fun printTime() { | |
println( | |
DateTimeFormatter | |
.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS") | |
.withZone(ZoneOffset.UTC) | |
.format(Instant.now()) | |
) | |
} | |
suspend fun doWorld() = coroutineScope { | |
launch { | |
delay(2000L) | |
printTime() | |
println("World 2 ") | |
} | |
launch { | |
delay(1000L) | |
printTime() | |
println("World 1") | |
// sleep(3000) | |
} | |
launch { | |
printTime() | |
println("Hello") | |
} | |
} |
No comments:
Post a Comment