Kotlin 기본3

Seongho·2021년 10월 16일
0

Kotlin

목록 보기
3/6

11. 기본 프로젝트 구조

  • 하나의 프로젝트는 여러 개의 모듈로 구성됨
  • 라이브러리 = 모듈
  • 모듈 안에는 폴더와 파일이 들어가며, 모듈과 관련된 설정, 리소스 파일 등도 포함 된다.
  • 패키지는 개발시에 소스 코드의 '소속'을 지정하기 위한 논리적인 단위
  • 코드를 작성할 때 코드내에서 사용되는 이름이 서로 충돌하지 않도록 유니크한 패키지 이름을 지어야 하며, 일반적으로 서비스 도메인을 거꾸로 붙이고, 그 뒤에 프로젝트 명을 붙이고, 그 아래에 기능별로 세분화하는 방식으로 묶는다.
    서비스도메인이 youtube.com 이라면,
    com.youtube.dimo
    com.youtube.dimo.base
    com.youtube.dimo.kotlin
    com.youtube.dimo.android
    com.youtube.dimo.talk
    이런 식으로 패키지 이름을 짓는다.
    패키지로 어떤 코드 파일을 묶으려면, 상단에 다음과 같이 적어준다.
    package com.youtube.dimo
    package com.youtube.dimo.base // 사용할 외부 패키지
    fun main() {
    	println("Hello!")
    }
    사용할 외부 패키지가 있는 경우 위와 같이 그 패키지 이름을 적어주고, 만약 두 패키지에서 겹치는 클래스, 변수, 상수 등이 있는 경우 해당 클래스, 변수, 상수를 사용할 때 패키지 이름까지 적어주면서 사용해야 제대로 쓸 수 있다.
  • 코틀린은 클래스명과 파일명이 일치하지 않아도 되며, 하나의 파일에 여러 개의 클래스를 넣어도 알아서 컴파일 가능하다.

12. 변수, 함수, 클래스의 접근범위와 접근제한자

12.1 스코프

변수나 함수, 클래스 같은 '멤버'들을 서로 공유하여 사용할 수 있는 범위를 지정해 둔 단위

패키지 스코프

함수 스코프

클래스 스코프

등이 있다.

  • 스코프 외부에서는 스코프 내부의 멤버를 '참조연산자'로만 참조가 가능하다 ex) a.eat() 처럼 클래스 내부의 함수를 외부에서 사용 ex) import com.google.dimo 를 쓰거나 com.google.dimo.A 처럼 사용해야 했음
  • 동일 스코프 내에서는 멤버들을 '공유'할 수 있다 파일에 패키지를 명시하지 않으면 default 패키지이고, 이 상태에서 만드는 모든 멤버들은 같은 패키지 스코프에 포함된다.
    val a = "패키지 스코프"
    
    class B {
        fun print() {
            println(a)
        }
    }
    fun main() {
        println(a) // 패키지 스코프
        B().print() // 패키지 스코프
    }
    변수 a가 class B나 function main에 속해있지는 않지만 동일한 스코프 내에 있기 때문에 사용할 수 있다.
  • 하위 스코프에서는 상위 스코프의 멤버를 재정의할 수 있다. 원래 같은 스코프에서는 같은 이름의 멤버를 만들 수 없다. (conflict error 발생)
    val a = "패키지 스코프"
    
    class B {
        val a = "클래스 스코프"
        fun print() {
            println(a)
        }
    }
    fun main() {
        val a = "함수 스코프"
        println(a) // 함수 스코프
        B().print() // 클래스 스코프
    }

12.2 접근제한자

스코프 외부에서 스코프 내부에 접근할 때 그 권한을 개발자가 제어할 수 있는 기능

  • public
  • internal
  • private
  • protected

위 접근제한자들은 변수, 함수, 클래스 선언시 맨 앞에 붙여서 사용한다.

위 접근자를 패키지 스코프에서 사용할 때

  • public - 어떤 패키지에서도 접근 가능 (기본값)
  • internal - 같은 모듈 내에서만 접근 가능
  • private - 같은 파일 내에서만 접근 가능
  • protected - 사용하지 않음

클래스 스코프에서 사용할 때

  • public - 클래스 외부에서 늘 접근 가능
  • private - 클래스 내부에서만 접근 가능
  • protected - 클래스 잣니과 상속받은 클래스에서 접근 가능
  • internal - 사용하지 않음

접근제한자는 스코프 외부에서 건드리지 말아야 할 기능이나 값들을 안전하게 제한한다.

13. 고차함수와 람다함수

13.1 고차함수

함수를 마치 클래스에서 만들어낸 인스턴스처럼 취급하는 방법

함수를 parameter로 넘겨줄 수도 있고, 결과값으로 반환받을 수도 있다.

코틀린에서는 모든 함수를 고차함수로 사용 가능하다.

fun main() {
    b(::a) //함수를 고차함수 형태로 넘기려면 함수 이름 앞에 콜론을 두 개 붙여주면 된다.
    // b함수는 받아온 a 함수에 "b가 호출한"이라는 값을 넣어서 호출하였다.
    // print 결과: b가 호출한 함수 a
}

fun a (str: String) {
    println("$str 함수 a")
}
fun b (function: (String) -> Unit){ // 함수를 받을 parameter 이름은 function, 자료형은 a의 함수의 형식을 넘겨받아야 함
	function("b가 호출한")
}
// 함수의 형식은 사용하려는 함수의 인수와 반환형을 이용하여 (자료형, 자료형, ...) -> (자료형) 으로 나타낸다.
// Unit은 값이 없다는 뜻

13.2 람다함수(중요)

패러미터로 넘길 함수에 굳이 이름을 붙여 따로 만들 필요가 없다면, 람다함수를 사용하자.

람다함수는 그 자체가 고차함수이다.

fun main() {
    b(::a)
    val c: (String) -> Unit = { str -> println("$str 람다함수") }
    // str:String -> ...이 기본 형식이지만 함수의 인수부분에 이미 자료형이 들어있어 생략한다.
    val c = {str:String -> println("$str 람다함수")} // 이렇게 쓸 수도 있다.
	b(c)
}	

fun a (str: String) {
    println("$str 함수 a")
}
fun b (function: (String) -> Unit){ // 함수를 받을 parameter 이름은 function, 자료형은 a의 함수의 형식을 넘겨받아야 함
	function("b가 호출한")
}

타입추론을 이용하여 축약하여 사용한 것이 두 번째 val c 이다. 헷갈린다..

  • 고차함수, 람다함수의 사용은 컬렉션의 조작이나 스코프 함수에 도움된다.
  • 람다함수는 여러 줄로 작성이 가능하다
    val calculate: (Int, Int) -> Int = {a, b ->
    	println(a)
        println(b)
        a+b // 마지막 줄의 값이 반환된다.
    }
  • parameter가 없는 람다함수는 실행할 구문들만 나열하면 된다.
    val a:() -> Unit = {println("패러미터가 없어요")}
  • parameter가 하나뿐이라면 it을 사용
    val c: (String) -> Unit = {println("$it 람다함수")}

14. 스코프 함수

함수형 언어의 특징을 좀 더 편리하게 사용할 수 있도록 기본 제공하는 함수들

클래스의 인스턴스를 scope 함수에 전달하면 인스턴스의 속성이나 함수를 좀 더 깔끔하게 불러 쓸 수 있다.

  • apply - 인스턴스를 생성한 후 변수에 담기 전에 초기화 과정을 표현할 때 많이 쓰임
    // 기본 방법
    fun main() {
        var a = Book("디모의 코틀린", 10000)
        a.name = "[초특가]" + a.name
        a.discount()
    }
    
    class Book(var name: String, var price: Int)
    {
        fun discount()
        {
            price -= 2000
        }
    }
    
    // apply 적용 시
    fun main() {
        var a = Book("디모의 코틀린", 10000).apply {
    		// main 함수와 별도의 스코프에서 인스턴스를 조작하게 됨
            name = "[초특가]" + name // 참조연산자 없이 적용 가능 
            discount()
        }
    }
    
    class Book(var name: String, var price: Int)
    {
        fun discount()
        {
            price -= 2000
        }
    }
  • run - 역시 참조연산자를 사용하지 않아도 됨. 마지막 구문의 결과 값을 반환한다. 따라서 이미 인스턴스가 만들어진 후에 인스턴스 내의 함수나 속성을 스코프 내에서 사용해야 할 때 유용하다.
    fun main() {
        var a = Book("디모의 코틀린", 10000).apply {
            name = "[초특가]" + name
            discount()
        }
        a.run { // 만들어진 인스턴스에서 사용, 참조연산자 필요 x
            println("상품명: ${name}, 가격: ${price}원")
        }
    }
    
    class Book(var name: String, var price: Int)
    {
        fun discount()
        {
            price -= 2000
        }
    }
  • with - run과 동일한 기능이지만 사용하는 방법이 다름
    a.run {...}
    with(a) {...} // 차이는 이것 뿐! 인스턴스를 parameter로 받는다.
  • also - 처리가 끝나면 인스턴스를 반환(apply), it을 사용
    fun main() {
        var price = 5000
        var a = Book("디모의 코틀린", 10000).apply {
            name = "[초특가]" + name
            discount()
        }
        a.run {
            // main 함수 내의 price 변수가 instance 내의 price 속성보다 우선시된다.
            println("상품명: ${name}, 가격: ${price}원") // 5000원이 출력된다.
        }
        a.let {
            println("상품명: ${it.name}, 가격: ${it.price}원") // 8000원이 출력된다.
        }
    }
    
    class Book(var name: String, var price: Int)
    {
        fun discount()
        {
            price -= 2000
        }
    }
  • let - 처리가 끝나면 최종값을 반환(run), it을 사용

apply와 also, run과 let은 같은 역할이지만 also와 let은 스코프 내의 속성의 이름이 스코프 바깥의 변수 이름과 중복될 수 있을 때 사용한다.

스코프 함수는 인스턴스의 속성이나 함수를 스코프 내에서 깔끔하게 분리하여 사용할 수 있으므로 코드의 가독성을 향상시킨다.

15. 오브젝트

15.1 오브젝트란?

  • 생성자 없이 객체를 만들어 냄
  • 지금까지 배운 클래스는 인스턴스 객체를 만들기 위한 틀로, 내부에 있는 속성이나 함수를 사용하려면 생성자를 통해 실체가 되는 인스턴스 객체를 만들어야 했다.
    var a = Person("박보영", 1990)
  • 여러 개의 인스턴스 객체가 필요하지 않으며 단 하나의 객체만으로 공통적인 속성과 함수를 사용해야 한다면, 굳이 클래스를 사용할 필요 없이 오브젝트를 사용하면 된다.
  • Singleton Pattern을 언어 차원에서 구현한 것이다.

15.2 예시

코드 아무 곳에서나 호출하여 호출한 횟수를 카운팅하거나 초기화할 수 있는 counter라는 오브젝트를 만들자.

fun main() {
    println(Counter.count) // 0, 객체가 최초 사용시 자동 생성됨
    
    Counter.countUp() // 위에서 생성한 객체를 사용하는 것
    Counter.countUp()
    
    println(Counter.count) // 2
    Counter.clear()
    
    println(Counter.count) // 0
}

object Counter {
    var count = 0
    
   	fun countUp() {
        count++
    }
    fun clear() {
        count = 0
    }
}

이런 식으로, 따로 인스턴스를 만들어주는 과정 없이 사용할 수 있다.

15.3 companion object

클래스의 인스턴스 기능은 그대로 사용하면서, 인스턴스 간에 공용으로 사용할 속성 및 함수를 별도로 만드는 것이다. 기존의 언어들이 가지고 있는 Static 멤버와 비슷하다.

*static member: 클래스 내부에서 별도의 영역에 고정적으로 존재, 인스턴스를 생성하지 않아도 공용으로 사용 가능한 속성이나 함수

fun main() {
    var a = FoodPoll("짜장")
    var b = FoodPoll("짬뽕")
    
    a.vote()
    a.vote()
    
    b.vote()
    b.vote()
    b.vote()
    
    println("${a.name}: ${a.count}") // 짜장 2 
    println("${b.name}: ${b.count}") // 짬뽕 3
    println("총계: ${FoodPoll.total}") // 총계 5
}

class FoodPoll(val name: String) {
    companion object {
        var total = 0
    }
    var count = 0
    
    fun vote() {
        total++
        count++
    }
}

a와 b가 서로 다른 인스턴스임에도 total을 공유한다.

0개의 댓글