[코틀린의 고급 문법 2]

고급 문법 마지막 시간입니다, 힘내자구요~~ 화이팅!

 

람다식

람다식이란 함수를 간결하게 표시한 형태입니다. 익명 클래스나 익명 함수를 간결하게 표현할 수 있어 편리합니다.

코드가 간결해져서 좋긴하지만, 남발할 경우 가독성이 떨어져 디버깅이 힘들어 질 수도 있습니다.

아래 세 가지 표현은 모두 같은 의미입니다. (코딩 간결화의 변천 과정이라고 생각해도 됨)

 

① 일반 함수
fun add(x: Int, y: Int) : Int { return  x + y }

② 문법적으로 허용된 생략을 한 일반 함수
fun add(x: Int, y: Int) = x + y

③ 람다식
var add = {x: Int, y: Int -> x + y }      // { } 부분이 람다식임.  '{ 인수들 -> 함수본문 }' 형태임

사용 예)
println(add(3+4))        // 결과: 7

 

 

 

SAM 변환

JAVA에서 메소드가 하나뿐인 인터페이스는 인터페이스를 구현하는 대신 함수로 구현할 수 있는데,
이것을 'SAM변환'이라고 합니다.  SAM (Simple Abstract Method ; 간단한 추상메소드)

코틀린 함수는 매개변수로 한 개의 추상메소드를 받는 형태를 취할 수 있는데,
이 때는 자바에서 작성된 인터페이스인 경우에 한해,
자바의 SAM변환과 유사하게 함수로 구현된 매개변수를 전달할 수 있습니다.

예를 들어 버튼의 클릭 이벤트를 구현할 때, View.OnClickListener 인터페이스를 구현하는데,
 (이 View.OnClickListener는 onClick() 이라는 추상메소드 한 개만을 가지고 있음)
이 인터페이스를 함수로, 더 나아가 익명메소드 형태로 구현해서 전달해 봅시다.

button.setOnClickListener(object : View.OnClickListener {    //OnClickListener는 OnClick메소드 하나만 포함
   override fun onClick(v: View?) {                        // 익명메소드 형태
       // 클릭시 처리 코드
   }
}
)

그리고 위 익명메소드는 람다식으로 표현할 수 있으므로,

button.setOnClickListener({ v: View? ->                // OnClickListener가 람다식화된 형태
      // 클릭시 처리 코드(람다 블럭)
})


코틀린에서 메소드 호출 시 제일 뒤의 매개변수가 람다식인 경우에는,
가독성을 위해 람다식을 ( ) 밖으로 빼서 작성할 수 있습니다.

button.setOnClickListener( ) { v: View? ->
     // 클릭시 처리 코드(람다 블럭)
}

그리고 람다식이 어떤 메소드의 유일한 매개변수인 경우에는 메소드의 ( ) 를 생략할 수 있습니다. 게다가 컴파일러가 자료형을 추론할 수 있다면 자료형을 생략할 수 있는 것이므로, 최종적으로 다음과 같이 되겠네요.

button.setOnClickListener{ v ->
     // 클릭시 처리 코드(람다 블럭)
}

※ 만약에 '클릭시 처리 코드'에서 v라는 매개변수를 사용하지 않는다면,
v를 _ 기호로 대치할 수도 있습니다.

button.setOnClickListener{ _ ->
       // 클릭시 처리 코드(람다 블럭)
}

※ 또 만약에 그리고 람다식에서 매개변수가 하나 뿐인 경우라면,
매개변수를 아예 생략할 수도 있습니다.

button.setOnClickListener{
    // 클릭시 처리코드(람다 블럭)
}

그리고 이 때 람다블럭내에서는 매개변수를 it로 접근할 수 있습니다.

예를들면,
button.setOnClickListener{
    it.visiblity = view.GONE             // 여기서 it는 View? 형 v를 의미함 ----- (K)
}


위에서 SAM변환을 이용한 익명메소드에서 람다식화, 그리고 코틀린의 편리한 생략 표현 기법들을 총동원해 코드를
간결화시켜봤고 코드들 모두 같은 의미입니다. 어쨌거나 가장 가독성이 뛰어난 형태는 (K)의 코드죠. 앞으로 종종 등장합니다.

 
주의해야 할 점은 코틀린에서의 SAM변환은 자바에서 작성한 인터페이스일 때에만 동작한다는 것~!.


 

확장 함수

코틀린에서는 .연산자를 이용해 이미 정의된 클래스에 메소드를 쉽게 추가할 수 있습니다.
(보통 Java나 C#에서는 final로 상속이 봉인되어 있어서 메소드를 추가하지 못하는 경우가 많은데 편리한 기능인 것 같네요)
(확장 함수 내부에서 해당 객체로의 접근은 this를 사용)

예) Int 클래스에 isEven()을 추가해 보자.
fun Int.isEven( ) = this % 2 == 0    // 'this % 2 == 0'의 결과인 부울 값이 함수의 결과 값이 됨

println(5.isEven())        // 결과: false


 

형 변환

val a = 10L

b = a.toInt()
c = a.toDouble()
d = a.toString()

e = Integer.parseInt(d)      // 문자열 형을 숫자로 변환


※ 일반 클래스 간 형 변환 (as 키워드)
open class Human()
class Man: Human()

val man = Man()
val human = man as Human     // Human 형으로 형 변환


형 체크

val st = "hello"
if (st is String) { println(st.toUpperCase() }



고차 함수

함수를 매개 변수로 전달하거나 함수 형으로 반환할 수 있습니다. 이렇게 사용되는 함수를 '고차 함수'하고 합니다.

fun add( x: Int, y: Int, callback: (sum: Int) -> Unit) {   // 두 개의 Int 매개변수와 한 개의 익명함수 매개변수
                                                 // 매개 변수로 쓰인 익명함수는 한 개의 Int 매개변수를 받고 리턴 값은 없음
    callback( x + y )
}

add(3,4, {println(it)})      // 결과: 7    함수를 { }로 감싸고, 이 함수 내부에서는 반환 값을 it로 접근한 예임



동반 객체

'팩토리 메소드'
코틀린에서는 클래스를 객체화 하는 것과는 별개로 메소드를 이용해 객체를 생성하는 코딩 패턴을 지원하는데, 이를 '팩토리 메소드'라고 합니다.
(나중에 다루게 될 프래그먼트 컴포넌트는 특수한 제약 때문에 팩토리 메소드로만 객체를 생성할 수 있음)

코틀린에서는 타 언어에서 정적인 메소드를 만들 때 사용하는 static 키워드 같은 게 없습니다. 그 대신 '동반 객체 (companion object)'라는 것을 통해 이를 구현합니다. (companio : 동반자, 동료, 친구)

다음은 newInstance() 정적 메소드를 사용해서 Fragment 객체를 생성하는 팩토리 패턴을 구현한 것입니다.
여기서 동반 객체 내부의 메소드는 Fragment 클래스와 아무 관계가 없는 정적인 존재입니다.

class Fragment {
   companion object {
      fun newInstance(): Fragment {    // shs: 함수의 반환 형이 Fragment 형이라... 
         println("생성됨")
      }
   }
}

val fragment = Fragment.newInstance()   // shs: 이거 정적 Fragment 안의 newInstance 멤버메소드를 액세스 하는 것과 비슷한데요...



<<< 코틀린 기본 라이브러리에서 유용한 함수들 >>>

let()

블럭에 자기 자신을 인수로 전달(it로 참조)하고 실행 결과를 반환합니다. 
'안전한 호출' 즉, str이 null이 아닐때만 호출 되도록 ?.연산자를 이용하면 더 좋습니다.

val result = str?.let {            //   결과는 Int형
    Integer.parseInt(it)
}
// fun <T, R> T.let(block: (T) -> R): R


with()

객체를 매개변수로 받고 블럭에 리시버 객체형으로 전달(this로 참조)해 준 후, 실행 결과를 반환합니다.
단, '안전한 호출'이 불가능하기 때문에 반드시 str이 null아닐 때에만 호출해야 합니다.

with(str) {
    println(toUpperCase())     // this.toUpperCase()에서 this.를 생략할 수도 있다.
}
// fun <T, R> with(receiver: T, block T.() -> R): R


apply()

블럭에 객체 자신이 리시버 객체형으로 전달되고 그 객체형으로 반환됩니다.
(주로 객체의 상태를 변경해서 반환할 때 사용함)

val result = car?.apply {
   car.setColor(Color.BLACK)
   car.setPrice(1000000)
}
// fun  T.apply(block: T.() -> Unit): T


run()

run() 함수는 익명 함수처럼 쓰는 법과 객체에서 호출하는 법 모두를 제공합니다.

i) 익명 함수처럼 쓸 때
블럭의 결과를 반환합니다.
블럭 안에 선언된 변수들은 모두 임시로 잠깐 사용하는 변수들인데, 임시 변수를 많이 사용하는 복잡한 계산에 유용하겠네요.

val avg = run{
   val kor = 90
   val eng = 80
   val math = 90

   (kor + eng + math) / 3.0f
}
// fun  run(block: () -> R): R


ii) 객체에서 호출 할 때
객체를 블럭의 리시버 객체로 전달하고 결과를 반환합니다.
안전한 호출이 가능하므로 with()보다 더 유용합니다~ 

str?.run {
   println(toUpperCase())
}
// fun <T, R> T.run(block: T.() -> R): R

 

 

 

 

 

 

<<< 이전 글 보기      다음 글 보기 >>>

 

 

 

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

+ Recent posts