생각정리

파트0) 코틀린 복습 및 공부 본문

코틀린

파트0) 코틀린 복습 및 공부

공부중초보에요 2023. 1. 8. 02:42
728x90

https://play.kotlinlang.org/#eyJ2ZXJzaW9uIjoiMS44LjAiLCJwbGF0Zm9ybSI6ImphdmEiLCJhcmdzIjoiIiwibm9uZU1hcmtlcnMiOnRydWUsInRoZW1lIjoiaWRlYSIsImNvZGUiOiIvKipcbiAqIFlvdSBjYW4gZWRpdCwgcnVuLCBhbmQgc2hhcmUgdGhpcyBjb2RlLlxuICogcGxheS5rb3RsaW5sYW5nLm9yZ1xuICovXG5mdW4gbWFpbigpIHtcbiAgICBwcmludGxuKFwiSGVsbG8sIHdvcmxkISEhXCIpXG59In0=

 

Kotlin Playground: Edit, Run, Share Kotlin Code Online

 

play.kotlinlang.org

여기 웹에서도 코틀린 코드를 작성 가능하다.


코틀린 특징: 제트브레인에서 만든 언어. 자바의 약점을 보완. 객체 지향이면서도 함수형언어의 특징도 가짐.

JVM 위에서 돌아가므로 자바와 호환 가능.

 

실용성, 간결성, 안정성, 상호운용성을 가짐.

 

디컴파일  기능을 유용하게 사용하자.


부생성자는 주생성자를 무조건 호출하는 방식임. 즉, 부생성자에서는 프로퍼티 선언이 불가능하다. => 안그러면 부생성자를 뭘 사용하는지에 따라 프로퍼티가 달라지게 된다.

 

init블록이 부생성자보다 먼저 호출된다.

init블록은 프래그먼트 클래스 생성 시 먼저 실행되어야 하는 것들을 담을 수 있다.  프래그먼트 초기화 시 사용 가능하다. 

 

자바와 코틀린의 if-else 차이는 코틀린에서는 식으로 사용될 수 있다.

즉, if-else라는 식의 결과 값을 변수에 대입 가능함.

 

// 그냥 구문 처럼 사용할 때는 else가 필수가 아니지만, 값 대입을 위한 식으로 사용될 때는 else가 필수다.
val result = when (dayOfWeek) {
    "월",
    "화",
    "수",
    "목",
    "금" -> false
    else -> true
}
val result = when (dayOfWeek) {
    "월",
    "화",
    "수",
    "목",
    "금" -> if(dayOfWeek=="금") "좋아" else "너무 좋아"
    else -> true
}

=> 이렇게도 when안에 if문을 배치시키는 방식으로 사용 가능하다. 

또한, when문의 ()에서 지역변수 선언도 가능하다.

 

val result = when (dayOfWeek) {
    "월",
    "화",
    "수",
    "목",
    "금" -> if (dayOfWeek == "금") "좋아" else "너무 좋아"
    in "a".."z" -> "알파벳"
    in listOf("ㅋㅋ", "ㅎㅎ") -> "웃음"
    else -> true
}

=> 이렇게 범위 연산자 혹은 배열을 사용할 수도 있다.


for문 사용 시에는 .. 말고  downTo를 사용할 수도 있다. 이걸 사용하면 양쪽 포함관계로 감소하게 되는데, 이때 step(2) 와 같이 지정해주면 2씩 감소하게 된다. 주의할 점은 step에는 양의 정수만 들어갈 수 있다는 것이다. 

 


println(list.joinToString(","))

=> 리스트의 모든 원소들을 ,콤마로 구분 지어서 출력한다. 이 함수는 모든 컬렉션에 적용 가능함. 맵도 가능함.

 

 

val diverseList = listOf(1,"안녕",1.78,true)

=> 코틀린의 리스트는 별도로 타입 지정을 안한다면, 다양한 타입을 한 리스트에 넣어줄 수 있음. 

 

val map = mapOf((1 to "안녕"),(2 to "hello"))
//map.put()
val map1= mutableMapOf((1 to "안녕"),(2 to "hello"))
map1.put(3,"응")
map1[100]="이런 것도 되지롱" //맵에 인덱스 연산자 사용 가능.

=> 코틀린의 map은 이런 식으로 키-값 쌍으로 구성된 원소들을 담는 컬렉션임.

 

 

val list1 = listOf(1, 2, 3, 4)
//list1.add() => Immutable 리스트는 변경이 불가능하다.
//리스트 사용시, 데이터의 특정 프로퍼티 값으로 원소를 찾고자할 때
//list1.first{it.id==10} //이런 확장함수로 사용 가능함. 의미는 처음으로 id가 10인 원소를 찾는 듯함.
val test7 =
    listOf(Test7(1, "명진"), Test7(5, "소정"), Test7(10, "은하"), Test7(15, "소진"), Test7(3, "광희"))
println(test7.first { it.id == 10 })
test7.first { it.id==5 }.name = "소정2"
println(test7.joinToString(" | "))

=> 이런식으로 확장함수로 찾은 원소의 값을 수정하면 기존 객체에 반영된다는 것을 알 수 있다. 

 

 

fun cast(a: Any) {
    val result = a as? String
        ?: "실패" // a를 String으로 타입변환함. 만약 타입 변환이 안된다면 널을 반환함. 엘비스 오퍼레이터 ?: 를 사용하면 디폴트 객체를 지정 가능함.
    println(result)
}

// 코틀린은 if 문에서 is로 타입을 확인하면 그냥 그 블록 안에서는 별도의 캐스팅 as 과정없이 그냥 사용 가능함.
fun smartCast(a: Any): Int {
    return if (a is String) {
        a.length
    } else if (a is Int) {
        a.dec()
    } else {
        -1
    }
}

=> casting에서 as가 아니라 as? 를 사용하면 좀 더 안정적으로 캐스팅이 가능하다.

 

 

// 람다에 대해 배운다. 코틀린의 함수형 언어 특징이 여기서 나타난다. if-else가 조건식으로 값을 반환하는 것도 함수형 언어의 특징임.
fun main() {
    // 1. 익명 함수 : 함수에 이름이 없다.
    // 2. 함수의 매개변수가 함수일 수 있다. 혹은 함수가 리턴타입이 될 수도 있다.
    // 3. 한 번 사용되고, 재사용되지 않는 함수에 람다가 사용된다.
    val a = fun() { println("hello") } // 익명함수이므로 이름이 없다. 이걸 변수처럼 사용 가능함.

    // (인풋)->아웃풋, 함수의 인풋과 아웃풋의 타입들을 지정해주면 된다. 그게 바로 함수의 타입임.
    val b: (Int, Int) -> Int = { a, b ->
        a * b * 10 //이게 리턴값임.
    }
    // 인풋 요소가 한 개이면 그냥 it을 활용하면 된다.
    println(b)
    println(b(3, 5))

    // 이렇게 함수 구현부 내에 인풋 요소들을 지정해줘도 된다. 리턴 값 요소는 알아서 추론한다.
    val d = { a: Int, b: Int ->
        a * b * 100
    }

    // 안쓰는 인풋 요소들은 _로 생략 가능하다.
    val f: (Int, String, Boolean) -> String = { _, b, _ ->
        b
    }

    println(hello(7,b)(1,5))  // 7*20*10 = 1400 나옴. 그리고 여기서 리턴받은 함수를 활용해서 또 호출한다.



}

fun hello(a: Int, b: (Int,Int) -> Int):(Int,Int)->Int {
    println(a)
    println(b(a,20))
    return b
}

=>  람다에 대한 공부 내용

 


코틀린 스코프 함수(=범위 지정함수) 

- 코틀린 표준 라이브러리에서 제공하는 확장함수

- 간결성, 명료성, 유지보수 용이성을 위해 사용한다. 

- 객체의 컨텍스트 내에서, 실행 가능한 코드 블럭을 만드는 함수

   호출 시, 임시 범위가 생성되며, 이 범위 안에서는 이름 없이 객체에 접근이 가능하다. 

   context: 문맥, 맥락, 전후 사정, => 쉽게 말해서 객체가 지닌 특징벡터 같은 느낌임.

- 수신 객체(리시버): 확장 함수가 호출되는 대상이 되는 값(객체)

- 수신 객체 지정 람다(lamda with receiver): 수신 객체를 명시하지 않고, 람다의 본문 안에서 해당 객체의 메서드를 호출할 수 있게 하는 것

- 차이점

   1. 수신 객체 접근 방법: this, it

   2. Return 값: 수신객체, 마지막 행(lamda result)

 

코틀린 범위 지정 함수

스코프 함수

1. let

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

=> 수신객체를 T로 받음. 그리고 let함수는 매개변수로 람다식을 받음. 그 람다식은 인풋이 한개이고 타입이 T임. 그리고 아웃풋의 타입은 R임. 그리고 이 let함수의 반환 타입도 R임. 

 

 

 

 

 

 

//초기화 지연: lateinit과 lazy
// 목적: 메모리 효율성을 위한 널-세이프 방법
// lateinit은 var에 사용하는 키워드. 프리미티브 타입인 Int는 안됨. Integer를 사용해야 함. 초기화되기 전에 사용하려고 하면 Exception 발생.
// lazy는 val에 붙는 키워드. 프리미티브 타입 가능. 사용 시 초기화됨.

=> 액비비티나 데이터바인딩, 뷰모델 초기화 등에서 많이 사용함. 

 


데이터 클래스: 데이터를 담기 위한 클래스

toString(), hashCode(), equals(), copy() 메서드가 자동으로 생성되어있는 클래스임. 얘네를 오버라이드해서 원하는대로 사용할 수도 있다.

데이터 클래스에는 abstract, open, sealed, inner 등의 키워드를 붙일 수 없다.

open키워드가 불가능하므로 즉, 상속이 불가능함.

=> 자바에서 toString()을 그냥 호출하면 기본적으로는 객체의 주솟값이 나왔지만, 데이터 클래스에 기본적으로 구현된 toString은 그 객체의 프로퍼티 내용들이 나온다. 

 

@NotNull
public final Dog copy(@NotNull String name, int age) {
   Intrinsics.checkNotNullParameter(name, "name");
   return new Dog(name, age);
}

// $FF: synthetic method
public static Dog copy$default(Dog var0, String var1, int var2, int var3, Object var4) {
   if ((var3 & 1) != 0) {
      var1 = var0.name;
   }

   if ((var3 & 2) != 0) {
      var2 = var0.age;
   }

   return var0.copy(var1, var2);
}

=> 카피 함수는 위와 같이 구현되어 있는 것을 확인 가능하다. 

println(dog.copy(name = "몽이")) // 이름만 바뀐 카피 객체를 얻을 수 있음.

이렇게 원하는 프로퍼티만 변경한 새로운 객체를 얻을 수 있음.

 

 

데이터 클래스가 다른 클래스의 부모 클래스가 될 수 없는 이유: 이미 구현된 네가지의 메서드들이 어떻게 처리될지 알 수 없다. 모호함.


Sealed 클래스: 추상클래스, 상속받은 자식 클래스의 종류를 제한함. 

컴파일러가 sealed 클래스의 자식 클래스가 어떤 것인지 암. => 이것 덕분에 when을 식으로 사용할 때 else브랜치가 필요없다.

when과 함께 쓰일 때, 장점을 느낄 수 있음. 

 

또한 덕분에, 리사이클러뷰에 들어가게 되는 뷰 타입들을 실드 클래스를 상속받게 만든다면, when문으로 처리할때 새로운 뷰타입이 추가되어도 안전하게 빼먹지 않고 추가 가능하다.


Object: 클래스를 정의함과 동시에 객체를 생성

싱글톤을 쉽게 만들 수 있는 키워드

생성자 사용 불가능

프로퍼티, 메서드, 초기화 블록은 사용 가능

=> 해당 Object객체를 사용하려고 하는 순간에 해당 클래스의 초기화가 된다.

=> 주로 에러코드 만들때 사용한다. 

 

 

Companion Object 동반객체

자바의 static과 동일한 역할 => 완전히 같은건 아님. 해당 클래스를 사용할 때 object클래스를 하나 더 내부적으로 만드는 거임.

클래스 내에 하나만 생성할 수 있다.

=> 참고: 

const val은 값이 '컴파일'시에 결정되는 상수로서, val과는 다른 '불변성'을 가지고 있습니다.

또한 const val은 클래스의 생성자에 할당될 수 없으며, String을 포함한 기본 자료형으로만 선언이 가능하며,

런타임 시 생성되는 다른 클래스의 객체는 const val로 선언할 수 없습니다.

이런 const val은 함수 내의 지역변수나, 클래스의 속성으로 사용할 수 없는데요.

때문에 const val을 함수나 클래스 내에서 사용하려면 companion object이하 중괄호 안에 선언해주셔야 합니다.

 

companion object { //static 의 역할
    const val NAME = "name" // const 는 컴파일 시에 결정되는 상수를 의미함. 그냥 val 은 런타임 시에 정해진다는 차이가 있음.
    // const val 은 스트링을 포함한 기본 자료형에만 적용 가능하다.

    fun builderBook() = Book() // 팩토리 패턴에도 컴패니언 오브젝트를 사용한다.
}

 

 

import com.example.kotlin.part0.chapter2.Book.Companion.NAME
object Companion { //static 의 역할
    const val NAME = "name" // const 는 컴파일 시에 결정되는 상수를 의미함. 그냥 val 은 런타임 시에 정해진다는 차이가 있음.
    // const val 은 스트링을 포함한 기본 자료형에만 적용 가능하다.

    fun builderBook() = Book() // 팩토리 패턴에도 컴패니언 오브젝트를 사용한다.

}

 

 

 

import com.example.kotlin.part0.chapter2.Book.Novel.NAME
companion object Novel{ //static 의 역할
    const val NAME = "name" // const 는 컴파일 시에 결정되는 상수를 의미함. 그냥 val 은 런타임 시에 정해진다는 차이가 있음.
    // const val 은 스트링을 포함한 기본 자료형에만 적용 가능하다.

    fun builderBook() = Book() // 팩토리 패턴에도 컴패니언 오브젝트를 사용한다.
}

=> import를 시켜주면 그냥 바로 NAME으로 접근 가능해진다. 

 

 

728x90

'코틀린' 카테고리의 다른 글

Kotlin-In-Action) 도입부  (0) 2023.02.08
Comments