코틀린은 다음과 같은 길이를 가지고 있다.

TypeWidth
Long64
Int32
Short16
Byte8
Double64
Float32

테스트를 해보자

val int = 123
val long = 123456L
val double = 12.34
val float = 12.34F
val hexadecimal = 0XAB
val binary = 0b01010101

println( "$int , $long , $double , $float , $hexadecimal , $binary")
==================
123 , 123456 , 12.34 , 12.34 , 171 , 85

String 멀티 라인

val rawString = """
안녕하세요
반갑습니다.
코틀린입니다
"""

print(rawString)

==============

    안녕하세요
    반갑습니다.
    코틀린입니다

Arrays

val array = arrayOf( 1 ,2 ,3)
var perfectSquares = Array(10 , { k -> k * k}) // 0(k)부터 시작해서 10개의 값을 가져온다.

println(Arrays.toString(array))
println(Arrays.toString(perfectSquares))

=====================

[1, 2, 3]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

String 템플릿

  • $변수 , ${변수} 가 가능하다..
var name = "Tommy"
var str = "Hello $name , Your name has ${name.length} characters"

println( name )
println( str )

===========
Tommy
Hello Tommy , Your name has 5 characters

Range

val aToZ = "a".."z";
val isTrue = "c" in aToZ
val oneToNine = 1..9
val isFalse = 11 in oneToNine

println( aToZ )
println( oneToNine )
println( " c is in aToZ = $isTrue , 11 is in oneToNine = $isFalse" )

==========

a..z
1..9
 c is in aToZ = true , 11 is in oneToNine = false

추가적으로 .downTo() .rangeTo()로 지정도 가능하다.

  • downTo()
  • rangeTo()
  • step (띄워넘기)
  • reversed
val counttingDown = 100.downTo(0)
val rangeTo = 10.rangeTo(20)

println( counttingDown )
println( rangeTo )

val oneToFifty = 1..50
val oddNumbers = oneToFifty.step(2)
val isIncludeNegativeNumber = 2 in oddNumbers
val isIncludePositiveNumber = 1 in oddNumbers

println( " 짝수 포함여부 : $isIncludeNegativeNumber , 홀수 여부 $isIncludePositiveNumber" )

val isIncludePositiveNumberReversed = 2 in (2..100).step(2).reversed()

println( " 뒤집었을 경우 짝수 포함여부 : $isIncludePositiveNumberReversed" )

==============

100 downTo 0 step 1
10..20
 짝수 포함여부 : false , 홀수 여부 true
 뒤집었을 경우 짝수 포함여부 : true

Loop

  • for in
  • array.indices -> index 가져올 수 있
val oneToTen = 1..2
for(k in oneToTen){
   for(j in 1..2){
       println(k * j)
   }
}

val array = arrayOf("A" , "B")
for(index in array.indices) { //인덱스를 가져올 수 있다.
   println("Element $index is ${array[index]}")
}
=============
1
2
2
4
Element 0 is A
Element 1 is B

Expression & Scope

  • @ 을 체크
fun testExpression() {
    Building("서울").Reception("02").printAddress()
}

class Building(val address : String) {
    inner class Reception(private val telephone : String) {
        fun printAddress() = println("${this@Building.address} , ${this.telephone} ")
    }
}

===========
서울 , 02

When

  • java 의 switch -> when 으로 사용가능하다.
fun testZeroFromWhen(){
    print(" ${testWhen( Int.MIN_VALUE )} , ${testWhen( Int.MAX_VALUE ) }" )
}

fun testWhen( x : Int) : Boolean{
    return when(x){
        Int.MIN_VALUE -> true
        Int.MAX_VALUE -> true
        else -> false
    }
}
//리스트안에 있는 체크
fun isInNumber(x : Int): Boolean {
    return when(x) {
        in listOf( 1, 2, 3, 4, 5) -> true //  리스트값 체크 가
        else -> false
    }
}
//when 에 인자가 없는 경
fun checkNoWhen( x : Int , y : Int) {
    when {
        x < y -> println("x is less then y")
        x > y -> println("x is greater than y")
        else -> println("x must equal y")
    }
}

라벨 정하기

fun printUntilStop(){
    val list = listOf( "a" , "b" , "stop" , "d")

    list.forEach stop@{
        if(it == "stop") return@stop
        else println(it)
    }

    list.forEach {
        if (it == "stop") return@forEach
        else println(it)
    }
}

========
a
b
d <-- 주의
a
b
d <-- 주의

클래스정의시 생성자 넣는 방법

  • require로 조건 체크 가능.. 오류시 -> java.lang.IllegalArgumentException 발생시킴
fun main(args : Array<String>){
    val person1 = Person( "" , "kim" , 20) // java.lang.IllegalArgumentException 발생
    val person2 = Person( "tommy" , "kim" , 20)
    print("person value = ${person1.firstName} , ${person2.lastName} , ${person1.age}")

}

class Person(val firstName : String , val lastName : String , val age : Int?){
    init{
        require( firstName.trim().length > 0 ) { "Invalid firstName argument."}
        require(lastName.trim().length > 0) { "Invalid lastName argument" }

        if(age != null) {
            require( age > 0 && age < 150) { "Invalid age argument" }
        }
    }
}

==============

코틀린에서 값을 가져올때

val person = Person( "tommy" , "kim" , 20)
print("person value = ${person.firstName} , ${person.lastName} , ${person.age}")
//위와 같이 접근이 가능하다.

그럼 자바에서는 코틀린의 Person 값을 어떻게 가져올까?

Person person = new Person("tommy" , "kim" , 26);
System.out.println(String.format("person firstName -> %s" , person.getFirstName()));
// 위와 같이 getter 로 접근 가능하다.

만약 생성자에서 nullSafe 가 있는 경우 어떻게 될까?

  • constructor : this 유
fun main(args : Array<String>){
    val person = Person( "tommy" , "kim" ) // 인자가 2개인걸 유의
    print("person value = ${person.firstName} , ${person.lastName} , ${person.age}")

}

class Person(val firstName : String , val lastName : String , val age : Int?){
    //생성자에서 null로 따로 치환해줘야 한다. ( this(...) )
    constructor( firstName: String , lastName: String) : this(firstName , lastName , null)
    init{
        require( firstName.trim().length > 0 ) { "Invalid firstName argument."}
        require(lastName.trim().length > 0) { "Invalid lastName argument" }

        if(age != null) {
            require( age > 0 && age < 150) { "Invalid age argument" }
        }
    }
}

내부 변수 초기화시 다른 방법으로도 가능하다.

fun main(args : Array<String>){
    val person2 = Person2( "tommy" , "kim" , null)
    print("person value = ${person2.getName()} , ${person2.getAge()}")
}

class Person2(firstName: String , lastName: String , howOld : Int?) {
    private val name : String
    private val age : Int?

    init {
        this.name = "$firstName,$lastName" //이렇게 초기화 가능
        this.age = howOld
    }

    fun getName() : String = this.name //함수 초기화가 이런식이 가능함

    fun getAge() : Int? = this.age
}

중첩 클래스

중첩시 내부 클래스가 가능하다. 내부 클래스에서 외부 클래스의 변수 접근을 위해선 inner를 붙여야 한다.

  • Inner
fun main(args : Array<String>){
    Line("ttt").InnerLine(2).draw()
}

class Line(graphName : String){
    private val name : String

    init{
        name = graphName
    }

    inner class InnerLine(val x : Int) {
        fun draw() : Unit {
            //$name 사용시 inner를 없는 경우 컴파일 오류
            print("Drawing Line from $x , name -> $name") 
        }
    }
}

중첩시 라벨을 통해서 같은 변수를 지정이 가능하다.

UI에서 뷰 변수를 지정시 많이 사용한다고 한다.

  • @A , @B 를 유의해서 보자.
fun main(args : Array<String>){
    A().B().foo("")
}

class A {
    private val somefield : Int = 1
    inner class B {
        private val somefield : Int = 2
        fun foo(s : String) {
            println("Field <somefield from B : " + this.somefield)
            println("Field <somefield from B : " + this@B.somefield)
            println("Field <somefield from A : " + this@A.somefield)
        }
    }
}
=============
Field <somefield from B : 2
Field <somefield from B : 2
Field <somefield from A : 1

Enum

Enum은 기본형태는 자바와 같다.

fun testEnum(){
    println(Day.valueOf("MONDAY"))
    println( Arrays.toString(Day.values()))
}

enum class Day(val weather : String) {
    MONDAY("good") , TUESDAY("rainy")
}
=============
MONDAY
[MONDAY, TUESDAY]

Interface로 확장도 가능하다.

fun testEnum(){
    val w = Word.HELLO
    w.print()
}

interface Printable {
    fun print()
}

enum class Word : Printable {
    HELLO {
        override fun print() {
            println("Word is HELLO")
        }
    },
    BYE {
        override fun print() {
            println("Word is BYE")
        }
    }
}
==========
Word is HELLO

Static 으로 접근

자세한 설명은 해당 링크로 가자.

fun main(args : Array<String>) {
    print(Student.create("tommy").name)
}

interface StudentFactory {
    fun create(name : String) : Student
}

class Student private constructor(val name : String) {
    companion object : StudentFactory{
        override fun create(name: String): Student {
            return Student(name)
        }
    }
}


Interface

fun main(args : Array<String>) {
    val pngImage = PNGImage()
    pngImage.save()
}

open class Image{
    open fun save() {
        println("Image save called")
    }
}

interface VendorImage{
    fun save() {
        println("VendorImage save called")
    }
}
class PNGImage : Image() , VendorImage {
    override fun save(){
        super<VendorImage>.save() //제너릭으로 해당 부모를 지정하는 것에 유의
        super<Image>.save() //제너릭으로 해당 부모를 지정하는 것에 유의
    }
}

확장함수

함수를 통해서 원하는 클래스에 추가적으로 붙일 수 있다.

우선 확장 안된 함수를 보자.

fun main(args : Array<String>){
    val items = listOf( 1 , 3,  7 , 9, 100)
    val newItems = drop( 2 , items)
    println( newItems )
}

fun <E> drop(k : Int , list : List<E>) : List<E> {
    val resultSize = list.size - k
    when {
        //resultSize 가 0보다 작으면 빈 리스트 변환
        resultSize <= 0 -> return emptyList<E>()
        //그렇지 않을 경우 k번째 아이템을 제거하고 새로운 리스트를 리턴받는다.
        else -> {
            val newList = ArrayList<E>(resultSize)
            for(index in k..list.size - 1) {
                newList.add(list[index])
            }
            return newList
        }
    }
}
==========
[7, 9, 100]

실행시 별도의 drop으로 함수를 호출해서 해야 한다.

그럼 이걸 이제 확장 함수로 바꿔보자.

fun main(args : Array<String>){
    val items = listOf( 1 , 3, 7 , 9, 100)
    val newItems = items.drop( 2 , items) // 컬렉션 변수뒤에 붙는 걸 유의해서 보자.
    println( newItems )
}

fun <E> List<E>.drop(k : Int , list : List<E>) : List<E> {
    val resultSize = list.size - k
    when {
        //resultSize 가 0보다 작으면 빈 리스트 변환
        resultSize <= 0 -> return emptyList<E>()
        //그렇지 않을 경우 k번째 아이템을 제거하고 새로운 리스트를 리턴받는다.
        else -> {
            val newList = ArrayList<E>(resultSize)
            for(index in k..size - 1) {
                newList.add(this[index]) //this를 주의
            }
            return newList
        }
    }
}
==========
[7, 9, 100]

위와 같이 내부적으로 this를 사용해서 해당 컬렉션을 가져오는 걸 볼수 있다.

순서와 매개변수에 따른 함수 호출

같은 이름과 같은 매개변수의 함수의 경우 ordering이 다를 수 있다. 다음의 예를 보자.

class Apple {
    fun eat() : Unit {
        println("Apple eat")
    }

    fun drop() : Unit {
        println("Apple drop")
    }
}

fun Apple.eat(): Unit {
    println("another Apple eat");
}

fun Apple.drop(where : String) : Unit {
    println("apple drop at $where")
}

실행시
val apple = Apple()
    apple.eat() //<- 처음 함수만 실행
    apple.drop()
    apple.drop("school")

===========
Apple eat
Apple drop 
apple drop at school

Null 오브젝트의 확장함수

null safe 오브젝트의 확장함수는 다음과 같다.

val test : String? = "222"
val test2 : String = "222"
var test3 : String? = null

val isEqual = test?.safeEquals(test2)
val isEqual2 = test3?.safeEquals(test2)
println(isEqual)
println(isEqual2)



//확장 함수
fun Any?.safeEquals(other : Any?) : Boolean {
    if (this == null && other == null) return true
    if (this == null) return false
    return this.equals(other)
}

===============
true
null

확장함수내에 맴버 변수 사용하기

확장함수를 사용시 클래스 내에 맴버변수를 같이 연동시 어떻게 하는 지 살펴보자.

  • 내부적으로 사용되는 hascode() 를 통해서 어떤 객체의 값을 가져오는지를 판별하자.
fun main(args : Array<String>){
    val mapping = Mappings()
    val inputStr = "Add one"
    println("hasCode #1-1 ${inputStr.hashCode()}")
    mapping.add(inputStr)
    mapping.add2("Add two")
    println("hasCode #2-1 ${mapping.hashCode()}")
    print(mapping.get(mapping.hashCode()))
}

class Mappings {
    private val map = hashMapOf<Int, String>()
    //확장 함수내부에 맴버변수를 사용가능하다.
    private fun String.stringAdd() : Unit {
        val hasCode = hashCode() //<- String 값에 대한 hascode
        println("hasCode #1-2 $hasCode")
        map.put( hasCode, this)
    }

    private fun String.stringAdd2() : Unit {
        val hasCode = this@Mappings.hashCode() //Mappings 클래스에 대한 hascode
        println("hasCode #2-2 $hasCode")
        map.put( hasCode , this)
    }

    fun add(str : String) : Unit = str.stringAdd()
    fun add2(str : String) : Unit = str.stringAdd2()
    fun get(key : Int) : String? = map.get(key)
}
=============
hasCode #1-1 514527815
hasCode #1-2 514527815 
hasCode #2-2 1360875712
hasCode #2-1 1360875712
Add two

다중 반환 값

  • Pair 에 대해서 알아보자
fun main(args : Array<String>) {
    val positiveRootVal = positivieRoot(4)
    val negativeRootVal = negativeRoot(4)
    print("$positiveRootVal , $negativeRootVal");
}

fun positivieRoot(k : Int) : Double {
    require( k >= 0)
    return Math.sqrt(k.toDouble())
}

fun negativeRoot(k : Int) : Double {
    require( k >= 0)
    return -Math.sqrt(k.toDouble())
}
=========================
// 2.0 , -2.0

위 내용을 보면 주어진 값의 제곱근을 양수와 음수로 보여준다.

중복된 내용이 많이 보인다. 이걸 다시 리펙토링 하면

fun main(args : Array<String>) {
    val roots = roots(5)
    print("positive -> ${roots[0]} , ${roots[1]}")
}

fun roots(k: Int) : Array<Double> {
    require( k >= 0 )
    val root = Math.sqrt( k.toDouble() )
    return arrayOf(root , -root)
}
================
//2.23606797749979 , -2.23606797749979

이렇게 배열로 하나 만들수 있다. 하지만 어느쪽이 양수인지를 알수가 없다.

그러면 Pair로 바꾸면 다음과 같다.

fun main(args : Array<String>) {
    val roots = roots(5)
    print("positive -> ${roots.first} , ${roots.second}")
}

fun roots(k: Int) : Pair<Double , Double> {
    require( k >= 0 )
    val root = Math.sqrt( k.toDouble() )
    return Pair(root , -root)
}

그럼 이걸 좀 더 줄이면

val (pos , neg) = roots(5) //!!
print("positive -> $pos , $neg")

이런식으로 줄일수 있다.

그리고 중위 함수(Infix) 로 바꿀수 있다


연산자

코틀린도 Swift 처럼 연산자를 오버로딩이 가능하다.

fun main(args : Array<String>) {
    val board = Chessboard()
    board[0 , 4] = Piece.Queen
    println(board[0,4])
}

enum class Piece {
    Empty , Pawn, Bishop , Queen
}

class Chessboard() {
    private val board = Array<Piece>(64 , {Piece.Empty})
    operator fun get(rank : Int, file : Int) : Piece = board[cal(rank , file)]

    operator fun set(rank : Int , file: Int , value : Piece) : Unit {
        println("set -> $rank , $file , $value , ${file * 8 + rank}")
        board[cal(rank , file)] = value
        println("check empty board -> ${board[cal( 0,2)]}") // 0, 2를 입력해서 빈값이 나오는지 확인
    }

    fun cal ( rank : Int , file : Int) : Int = file * 8 + rank
}

========
set -> 0 , 4 , Queen , 32
check empty board -> Empty
Queen

배열을 set , get을 하기위한 연산자를 오버로딩이 가능하다.

그리고 함수 호출 이름을 연산자로도 사용이 가능하다.

클래스를 함수처럼 사용도 가능하다.

fun main(args : Array<String>) {
    val longs = RandomLongs(3)
    println("${longs()} , ${longs()} , ${longs()}")
}

class RandomLongs(seed : Long) {
    private val random = Random(seed)
    operator fun invoke() : Long = random.nextLong()
}
========
-4961115986754665064 , 1309571695633557482 , 1238145679872042884

또 다른 예제로는 다음과 같다.

여러개의 함수를(생성자가 아니다) 만들어서 그에 맞는 걸 실행도 가능하다.

fun main(args : Array<String>) {
    val Min1 = Min(1, 2, 3)
    val Min2 = Min(1L, 2L)
    print("$Min1 , $Min2")
}

object Min {
    operator fun invoke(a: Int, b: Int): Int = if (a <= b) a else b
    operator fun invoke(a: Int, b: Int, c: Int): Int = invoke(invoke(a, b), c)
    operator fun invoke(a: Int, b: Int, c: Int, d: Int): Int = invoke(invoke(a, b), invoke(c, d))

    operator fun invoke(a: Long, b: Long): Long = if (a <= b) a else b
    operator fun invoke(a: Long, b: Long, c: Long): Long = invoke(invoke(a, b), c)
    operator fun invoke(a: Long, b: Long, c: Long, d: Long): Long = invoke(invoke(a, b), invoke(c, d))
}

=========
1 , 1

비교 연산 할때도 자주 쓰인다.

fun main(args : Array<String>) {
    val tommy = BingoNumber(30)
    val kim = BingoNumber(24)
    println( tommy < kim )
    println( kim < tommy )
}

class BingoNumber(val age: Int) {
    operator fun compareTo(other : BingoNumber) : Int {
        return when {
            age < other.age -> -1 //age 가 other의 나이보다 적다면 -1
            age > other.age -> 1 //크다면 1
            else -> 0 //기타 0
        }
    }
}

=======
false 
true


+ Recent posts