❓[TIL] 2023. 12. 11

Gahyeon Lee·2023년 12월 11일
0

TIL

목록 보기
23/47
post-thumbnail

🙌 유용한 기능

1. 자료형의 변환

▶️ 일반 자료형간의 변환 예시

  • 숫자 자료형끼리는 to자료형() 메소드를 활용할 수 있다.
    ex) toInt(), toDouble()
  • 문자열을 숫자로 변경할때에는 별도의 메소드가 필요하다.
fun main() {
    var num1 = 20
    var num2 = 30.2

    var num3 = num2.toInt()
    var num4 = num1.toDouble()

    var strNum5 = "10"
    var strNum6 = "10.21"

    var num5 = Integer.parseInt(strNum5)    // 숫자 형태의 문자열은 싱글턴으로 선언된 Integer라는 클래스에 parseInt()라는 메소드를 부르고, 그 파라미터에 문자열 형태의 숫자인 변수를 넣어주면 숫자로 바꿔서 리턴해준다.
    var num6 = strNum6.toDouble()           // 실수 형태의 문자열은 변수에 바로 .toDouble()을 하면 double로 바꿔서 리턴해주는 메소드를 사용.

    println("num3: $num3")
    println("num4: $num4")
    println("num5: $num5")
    println("num6: $num6")
}

/* 결과
num3: 30
num4: 20.0
num5: 10
num6: 10.21
*/

▶️ 객체 자료형간의 변환 예시

  • 객체 자료형간의 변환은 상속관계에서 가능하다.
  • OOP 5대 특징 중 다형성에 해당하는 내용으로 정말 중요하고 유용한 개념!
  • 다운 캐스팅보다는 주로 업 캐스팅을 많이 활용하게 될 것이다.
  • 업 캐스팅(자식클래스를 부모클래스의 자료형으로 객체 생성)
fun main() {
    println("몇 마리를 생성하시겠습니까?")
    var count = readLine()!!.toInt()
    var birds = mutableListOf<Bird>()   // birds에는 Bird 클래스만 들어갈 수 있다고 제한을 둠.

    for(idx in 0..count-1) {      // 예를 들어, count에 2를 입력했다면 2-1해서 0,1 즉 2마리를 생성할 수 있는 for문
        println("조류의 이름을 입력해주세요")
        var name = readLine()!!

        // as Bird는 생략가능(as Bird는 업캐스팅으로 만들겠다는 것. 그런데 위에서 변수 birds에는 Bird 클래스만 들어갈 수 있다고 제한을 뒀기 때문에 Sparrow를 바로 넣을 수 없는데, Bird클래스로써 들어갈 수 있다고 업캐스팅하는 것.)
        birds.add(Sparrow(name) as Bird)  // Sparrow 클래스의 객체를 만듦. Sparrow로 만들었지만, 상속관계에 있는 Bird로써 birds 리스트에 들어갈 수 있다.
    }
    println("============조류 생성완료============")
    for(bird in birds) {
        bird.fly()        // 참새나 비둘기 객체를 따로따로 만들어서 fly()하는 것이 아니라, Bird 안에 리스트를 만들고, 업캐스팅을 통해 객체를 만들어서 sparrow.fly()나 pigeon.fly() 말고 bird.fly()하면 된다.
    }
}

open class Bird(name: String) {
    var name: String

    init {
        this.name = name
    }

    fun fly() {
        println("${name}이름의 조류가 날아요~")
    }
}

class Sparrow(name: String): Bird(name) {    // 생성자에 위에서 입력한 name를 넘기고, 또 상속 받은 Bird 클래스의 name에 넘김.

}

/* 결과
몇 마리를 생성하시겠습니까?
2
조류의 이름을 입력해주세요
참새
조류의 이름을 입력해주세요
비둘기
============조류 생성완료============
참새이름의 조류가 날아요~
비둘기이름의 조류가 날아요~
*/
  • 다운 캐스팅(부모클래스를 자식클래스의 자료형으로 객체 생성)
    • 다운 캐스팅 오류 사례
fun main() {
    println("몇 마리를 생성하시겠습니까?")
    var count = readLine()!!.toInt()
    var birds = mutableListOf<Bird>()

    for(idx in 0..count-1) {
        println("조류의 이름을 입력해주세요")
        var name = readLine()!!

        birds.add(Sparrow(name) as Bird)
    }
    println("============조류 생성완료============")
    for(bird in birds) {
        bird.fly()
    }
    // 다운캐스팅 오류
    // var s1:Sparrow = birds.get(0)
    // 윗줄의 주석을 풀면 오류가 난다. birds.get(0)을 Sparrow에 넣는 것은 불가능하기 때문이다. birds는 Sparrow보다 크고, 더 많은 정보를 가진 Bird 타입이고, Sparrow는 Bird가 가진 정보를 모두 가지고 있지 않기 때문에 Sparrow로의 다운캐스팅은 안된다. 하지만 자식인 Sparrow는 부모 Bird로는 갈 수 있다.
}

open class Bird(name: String) {
    var name: String

    init {
        this.name = name
    }

    fun fly() {
##         println("${name}이름의 조류가 날아요~")
    }
}

class Sparrow(name: String): Bird(name) {

}

2. 자료형의 타입을 확인

  • 코틀린은 is 키워드를 활용해서 자료형의 타입을 확인할 수 있다.
fun main() {
    var name = "aa"    // 안에 있는 값을 보는 게 아니라 변수가 어떤 자료형인지를 보는 것.
    if(name is String) {
        println("name은 String 타입입니다")
    } else {
        println("name은 String 타입이 아닙니다")
    }
}

3. 여러 인스턴스를 리턴하기(복수 데이터 리턴)

  • 메소드는 기본적으로 하나의 데이터를 리턴한다.
  • 두 개 이상의 데이터를 포함하는 데이터클래스를 설계하고 인스턴스를 리턴하면 가능하지만 매번 불필요한 클래스를 만드는 행위는 비효율적이다.(값 하나씩을 리턴하는 것이 아니라, 객체로 여러 가지 프로퍼티(정보)들을 채워서 객체를 리턴하면 여러 개를 리턴할 수 있다는 건데 비효율적)
  • 코틀린은 데이터를 두 개, 세 개 리턴할 수 있는 클래스 PairTriple을 제공한다. 이것도 결국에는 하나의 값을 리턴하는 것이긴 한데, 객체 형태로 쉽게 리턴할 수 있게 하는 클래스들이 있다는 것!

▶️ Pair를 활용해서 두 개의 인스턴스 리턴

fun main() {
    var chicken = Chicken()         // Chicken 클래스 만들고 인스턴스
    var eggs = chicken.getEggs()
    var listEggs = eggs.toList()    // 리스트화([]형태로 출력됨)

	//first, second로 관리하는 경우
	//var firstEgg = eggs.first
	//var secondEgg = eggs.second

    // 리스트로 관리하는 경우
    var firstEgg = listEggs[0]      // 달걀
    var secondEgg = listEggs[1]     // 맥반석

    println("달걀의 종류는 ${eggs} 입니다.")
    println("리스트 달걀의 종류는 ${listEggs} 입니다.")
    println("첫번째 달걀의 종류는 ${firstEgg} 입니다.")
    println("두번째 달걀의 종류는 ${secondEgg} 입니다.")
}


class Chicken {
    fun getEggs(): Pair<String, String> {    // 리턴 타입이 Pair이고, 첫 번째, 두 번째 파라미터가 String 타입.
        var eggs = Pair("달걀", "맥반석")       // Pair라는 객체는 달걀, 맥반석이라는 두 개의 값을 가지고, 그걸 eggs 변수에 넣음.
        return eggs
    }
}

/* 결과
달걀의 종류는 (달걀, 맥반석) 입니다.
리스트 달걀의 종류는 [달걀, 맥반석] 입니다.
첫번째 달걀의 종류는 달걀 입니다.
두번째 달걀의 종류는 맥반석 입니다.
*/

▶️ Triple를 활용해서 세 개의 인스턴스 리턴

fun main() {
    var chicken = Chicken()
    var eggs = chicken.getThreeEggs()
    var listEggs = eggs.toList()

    //first, second, third로 관리하는 경우
    //var firstEgg = eggs.first
    //var secondEgg = eggs.second
    //var eggTime = eggs.third

    // 리스트로 관리하는 경우
    var firstEgg = listEggs[0]
    var secondEgg = listEggs[1]
    var eggTime = listEggs[2]

    println("달걀의 정보는 ${eggs} 입니다.")
    println("리스트 달걀의 정보는 ${listEggs} 입니다.")
    println("첫번째 달걀의 종류는 ${firstEgg} 입니다.")
    println("두번째 달걀의 종류는 ${secondEgg} 입니다.")
    println("달걀은 ${eggTime}에 나왔습니다.")
}

class Chicken {
    fun getTwoEggs(): Pair<String, String> {
        var eggs = Pair("달걀", "맥반석")
        return eggs
    }

    fun getThreeEggs(): Triple<String, String, Int> {
        var eggs = Triple("달걀", "맥반석", 20230101)
        return eggs
    }
}

/* 결과
달걀의 정보는 (달걀, 맥반석, 20230101) 입니다.
리스트 달걀의 정보는 [달걀, 맥반석, 20230101] 입니다.
첫번째 달걀의 종류는 달걀 입니다.
두번째 달걀의 종류는 맥반석 입니다.
달걀은 20230101에 나왔습니다.
*/

4. 자기 자신의 객체를 전달해서 효율적으로 처리하기

  • 코틀린에서는 Scope Functions(범위 지정 함수)들을 제공한다.
  • 범위 지정 함수는 특정 범위 안에서 객체의 이름을 생략하고 코드를 실행시켜주는 함수이다. 코드의 간결성과 유지보수의 용이함 때문에 쓰인다.
  • 객체를 사용할때 임시로 Scope를 만들어서 편리한 코드 작성을 도와준다.

[ Kotlin의 Scope Function 종류]

▶️ let function의 활용

  • 중괄호 블록안에 it으로 자신의 객체를 전달하고 수행된 결과를 반환
fun main() {
    var strNum = "10"
                                          // let {}은 현재 내 객체를 it으로 넘겨서 {} 안에서 처리하고 리턴해서 result에 대입해 준다.
    var result = strNum?.let {    		  // strNum("10")이 null인지 검사하고, let{}을 이용할 때 {} 안에서는 it으로 strNum을 대체해서 쓸 수 있다.
        // 중괄호 안에서는 it으로 활용함
        Integer.parseInt(it)              // it은 strNum을 대체해서 쓰였고, it을 넘겼을 때 문자열 형태의 숫자를 숫자로 바꿔서 result에 저장.
    }

    println(result!!+1)                   // result가 null이 아님을 보장하고 1을 더했을 때의 값을 출력
}

// 결과 --> 11

▶️ with function의 활용

  • 중괄호 블록안에 this로 자신의 객체를 전달하고 코드를 수행한다.
  • this는 생략해서 사용할 수 있으므로(생략가능!) 반드시 null이 아닐때만 사용하는게 좋다.(생략해서 사용할 수 있기 때문에 언제든 바로 사용해도 된다는 보장이 있으므로 null이 아닌 것이 확실할 때만 사용하자.)❓
fun main() {
    var alphabets = "abcd"    // 값이 들어있으니 null이 아닌 게 확실!

    with(alphabets) {
//      var result = this.subSequence(0, 2)
        var result = subSequence(0, 2)        // this 생략해도 alphabets.subSequence를 부르는구나 알 수 있음.
        println(result)
    }
}

// 결과 --> ab

▶️ also function의 활용

  • 중괄호 블록안에 it으로 자신의 객체를 전달하고 객체 자체를 반환해준다.
  • apply와 함께 자주 사용한다.
fun main() {
    var student = Student("참새", 10)

    var result = student?.also {    // 객체 student가 null이 아닐 때만 실행하게 하고 also 실행
        it.age = 50                 // {} 안에 student를 it으로 넘김 -> resultdp 50 들이(age가 바뀐 새로운 student 객체가 result에 대입 됨
    }
    result?.displayInfo()                    
    student.displayInfo()
}

class Student(name: String, age: Int) {
    var name: String
    var age: Int

    init {
        this.name = name
        this.age = age
    }

    fun displayInfo() {
        println("이름은 ${name} 입니다")
        println("나이는 ${age} 입니다")
    }
}

/* 결과
이름은 참새 입니다
나이는 50 입니다
이름은 참새 입니다
나이는 50 입니다
*/

▶️ apply function의 활용

  • 중괄호 블록안에 this로 자신의 객체를 전달하고 객체를 반환해준다.
  • 주로 객체의 상태를 변화시키고 바로 저장하고 싶을때 사용한다.
fun main() {
    var student = Student("참새", 10)

    var result = student?.apply {    // student가 null이 아닐 때 실행해서 apply 실행
        student.age = 50             // this로 들어옴.(왜 this 말고 student 그대로 썼지?) 객체 상태를 변환하고 바로 반환해 줌.
    }
    result?.displayInfo()
    student.displayInfo()
}

class Student(name: String, age: Int) {
    var name: String
    var age: Int

    init {
        this.name = name
        this.age = age
    }

    fun displayInfo() {
        println("이름은 ${name} 입니다")
        println("나이는 ${age} 입니다")
    }
}

/* 결과
이름은 참새 입니다
나이는 50 입니다
이름은 참새 입니다
나이는 50 입니다
*/

▶️ run function의 활용

  • 객체에서 호출하지 않는 경우의 예시
fun main() {
    var totalPrice = run {        // 임시로 영역을 만든 것
        var computer = 10000
        var mouse = 5000

        computer + mouse
    }
    println("총 가격은 ${totalPrice}입니다")
}

// 결과 --> 총 가격은 15000입니다
  • 객체에서 호출하는 경우의 예시 ❓
    • with와 달리 null체크를 수행할 수 있으므로 더욱 안전하게 사용 가능하다.
    • with처럼 쓸 수 있지만 null에 더 안전하므로 with 사용을 추천.
fun main() {
    var student = Student("참새", 10)
    student?.run {  		   // null이 아닐 때만 run을 하라고 할 수 있다.(with 쓰면 with 아래에 student 넣고 써야 함)
        displayInfo()          // this 생략하고 메소드 호출.   student.displayInfo()라는 뜻
    }
}

class Student(name: String, age: Int) {
    var name: String
    var age: Int

    init {
        this.name = name
        this.age = age
    }

    fun displayInfo() {
        println("이름은 ${name} 입니다")
        println("나이는 ${age} 입니다")
    }
}

/* 결과
이름은 참새 입니다
나이는 10 입니다 
*/

5. Scope Functions 정리

  • Scope Functions에는 수신객체와 람다함수간의 긴밀한 관계가 존재한다.
    • Scope Functions은 크게 두 가지로 구분할 수 있다.
      • 명시적으로 수신객체 자체를 람다로 전달하는 방법
      • 수신객체를 람다의 파라미터로 전달하는 방법
    • 수신객체, 람다와의 관계를 자세히 알아보자.
      • T는 수신객체를 의미
      • block: 내부는 람다함수의 소스코드
      • 수신객체는 it으로 사용할 수 있다.
// 수신객체 자체를 람다의 수신객체로 전달하는 방법
public inline fun <T, R> T.run(block: T.() -> R): R
public inline fun <T> T.apply(block: T.() -> Unit): T
public inline fun <T, R> with(receiver: T, block: T.() -> R): R

// 수신객체를 람다의 파라미터로 전달
public inline fun <T> T.also(block: (T) -> Unit): T
public inline fun <T, R> T.let(block: (T) -> R): R
  • ⭐️ 모든 수신객체를 it으로 활용하면 문제가 발생할 수 있다.(it은 Scope Functions을 다중으로 사용할 때 문제가 발생할 수 있다.)
    • Child Function에서 Shadow가 되어서 제대로 참조하지 못할 수 있다.
    • 그래서 it은 다른 이름으로 변경해서 사용하기도 한다.
// Scope Function을 중첩으로 사용할 경우 누가 누구의 범위인지 알수 없다!
// Implicit parameter 'it' of enclosing lambda is shadowed 경고 발생!

data class Person(
	var name: String = "",
	var age: Int? = null,
	var child: Person? = null
)

// 잘못된 예시
Person().also {
	it.name = "한석봉"
	it.age = 40
  val child = Person().also {
	it.name = "홍길동" // 누구의 it인지 모른다!
    it.age = 10 // 누구의 it인지 모른다!
  }
  it.child = child
}

// 수정한 예시
Person().also {
	it.name = "한석봉"					// 외부에 있는 it
	it.age = 40
  val child = Person().also { c ->
	c.name = "홍길동"					// 내부에 있는 c
    c.age = 10
  }
  it.child = child
}

Scope에서 접근방식 thisScope에서 접근방식 it
블록 수행 결과를 반환run(권장), withlet
객체 자신을 반환applyalso
profile
코린이 강아지 집사🐶

0개의 댓글