반응형

이번글은 프로그래밍의 꽃인 함수 입니다. 이미 알고 있으시겠지만 함수(function:기능)란 '특정 기능의 코드가 써있는 영역' 이라고 생각하시면 됩니다.

로그인기능 관련코드들이 써 있다면 'Login함수', 회원가입기능 관련 코드가 있다는 'Signup함수' 같은 식으로 만들어 필요할 때 원하는 기능의 함수들을 적절히 호출하여 전체 프로그램이 동작하도록 하는 것이 프로그래밍 이라고 보시면 됩니다.

 

이번에도 함수문법 자체를 수업하듯이 소개한다기 보다는, 자바와는 다른 코틀린의 함수 문법 위주로 소개를 하겠습니다. 이것도 사실 자바와 표기법의 차이가 많고 함수형프로그래밍 언어의 특징이 도입되어 다소 생소한 문법과 개념이 등장하니 한번에 이해한다기 보다 이런식으로 사용하는 구나 정도로 살펴보고 앞으로의 앱 개발 과정을 통해 익숙해지면 학습해보길 바랍니다.

 

새로운 코틀린 파일 Test05Basic5.kt 를 만들고 문법을 알아보겠습니다.

프로그램의 시작인 main()함수까지 작성하겠습니다.

 

 

7. 함수 function

1) 함수 정의 및 호출

특정 기능을 수행하는 코드를 작성하는 함수를 만드는 것을 함수를 정의한다고도 부르는데요. 이 별도의 함수는 일반적으로는 main()함수 영역의 바깥쪽에 만드는 것이 일반적입니다.

자바의 경우 완전체 객체지향언어를 원하는 언어여서 객체를 설계하는 class {..} 영역 밖에 무엇인가를 작성하는 것을 허용하지 않았습니다. 그래서 자바에서의 모든 기능(함수)은 class.. 객체안에만 존재할 수 있기에 함수라고 부르지 않고 메소드(method)라고 불렀습니다. 즉, 자바에서는 함수 라는 용어가 존재하지 않습니다.

코틀린은 객체지향언어가 아니고 함수형 언어의 특징을 가진 언어여서 객체 밖에 별도의 변수나 함수가 존재할 수 있습니다. 그래서 코틀린에서는 객체안에 있는 것을 메소드method 라고 부르고, 객체 밖에 있는 것을 함수function 이라고 구분하여 부릅니다. 이번글에서는 객체 밖에 있는 함수function만 소개하고자 합니다. 메소드는 향후 진행 될 코틀린의 객체지향 글에서 자세히 소개하겠습니다.

 

자. 그럼 console 화면에 "show function" 이라는 글씨를 출력하는 기능함수 show() 를 만들어 보겠습니다.

코틀린에서는 함수를 정의할 때 리턴타입의 위치에 fun 키워드를 사용합니다.

//시작 함수
fun main(){

}//main함수 .. 영역 끝

//1) 함수의 정의
//함수를 정의할 때 리턴타입의 위치에 fun 키워드를 사용  -- 파라미터와 리턴이 없는 함수
fun show(){
    println("show function")
    println()
}

 

함수를 만들었다고 해서 그 함수안에 작성한 코드가 자동으로 실행되는 것은 아닌것을 알고 있으시죠?

잘 알다시피 프로그램의 시작은 main()함수의 중괄호 {  .. 로부터  ..} 중괄호 닫을 때까지만 실행되기에  main함수{..} 안에서 show()함수를 불러야만 실행됩니다. 이를 함수의 호출이라고 합니다.

 

그럼 main함수 안에서 show()함수를 호출하여 실행되도록 코드를 추가하겠습니다.

//시작 함수
fun main(){

    //함수호출
    show()

}//main함수 .. 영역 끝

//1) 함수의 정의
//함수를 정의할 때 리턴타입의 위치에 fun 키워드를 사용  -- 파라미터와 리턴이 없는 함수
fun show(){
    println("show function")
    println()
}

//**출력**
show function

 

 

2) 파라미터 전달

함수를 호출할 때 특정 값을 전달하면, 함수에 파라미터(매개변수)를 만들어 받아서 사용할 수 있는데 이 매개변수를 만들 때는 변수를 만들때 사용하는 var, val 키워드를 사용하면 안됩니다. 코틀린은 기본적으로 모든 매개변수는 값 변경이 불가능한 val 변수로 만들어집니다.

//시작 함수
fun main(){

    //함수호출 - 파라미터에 값 전달  - 정수, 문자열 전달
    output(100, "Hello")

}//main함수 .. 영역 끝

//파라미터를 가진 함수 ( 파리미터명 : 자료형 ) - var, val 키워드 명시 X , 자동 val로 지정됨 (자료형 생략 불가)
fun output(a:Int, b:String){   //전달된 정수, 문자열을 받기위해 Int, String 변수로 지정
    println(a)
    println(b)    
    println()
}

//**출력**
100
Hello

 

파라미터는 값 변경이 불가능 한 val 변수로 만들어지기에 값을 대입하는 코드를 사용하면 문법적 에러가 발생합니다.

fun output(a:Int, b:String){
    println(a)
    println(b)
    
    //파라미터 값 변경 시도해보기
    a=50 //ERROR - 매개변수는 자동 val
    println()
}

 

 

3) 리턴하는 함수

자바와 다르게 함수를 만들 때 리턴타입을 먼저 작성하지 않고 함수 소괄호() 뒤에 : 후에 작성합니다.

두 정수를 파라미터로 받아 덧셈하여 그 연산 결과를 리턴해주는 함수를 만들고 이를 main()함수에서 호출하여 사용해보겠습니다.

//시작 함수
fun main(){

    //리턴을 하는 함수 호출   - 두 정수 50, 30을 sum함수에 전달하고 연산된 결과 리턴값을 num변수로 받기
    var num= sum(50, 30)
    println("sum함수의 결과값 : $num ")

}//main함수 .. 영역 끝


//리턴하는 함수 [ 리턴타입을 함수()뒤에 : 후에 작성 ]
fun sum(a:Int, b:Int) :Int{  
    return a+b
}

//**출력**
sum함수의 결과값 : 80

 

참고로. 자바와 다르게 함수의 리턴이 없으면 void 타입이 아니고 Unit이라는 타입의 객체를 리턴하게 됩니다. 즉, 리턴값이 없는 게 아니기에 참조변수로 참조하는 것이 가능합니다. 물론. 특별히 이 Unit을 사용하는 것은 아니니 그렇구나 정도로 알아 두시기 바랍니다.

//시작 함수
fun main(){

    //리턴이 없는 함수의 리턴을 받으면?? void가 아니라 Unit이라는 자료형이 됨.
    var x= display()
    println(x)

}//main함수 .. 영역 끝


//리턴타입이 없는 함수
fun display(){
    println("display!!");
}

//**출력**
display!!        //display()함수 안에 있는 println() 에 의해 출력된 글씨
kotlin.Unit      //main()함수의 x 변수의 출력에 의한 글씨.. Unit 타입이라는 것을 알 수 있음.

 

1) ~ 3) 까지, 함수정의 및 호출부터 파라미터, 리턴타입까지 자바와 다른 코틀린의 기본 함수 문법을 알아봤습니다. 

정리하면,

- 함수를 정의할 때는 fun 키워드를 사용 [ function(함수)의 약자 ]

- 자바와 다르게 class 밖에서도 함수를 정의 할 수 있음

- 파라미터(매개변수)를 만들 때 var, val 키워드를 명시하면 에러. [ default : val 변수 ]

- 리턴타입은 함수() 소괄호 다음에 : 콜론 표기 후 명시

 


♣ 코틀린 함수 문법의 특이점들

 

4) 함수선언의 단순화

함수를 정의할 때 return 키워드를 할당 연산자 [ = ] 로 대체하여 간단하게 표기하는 것이 가능합니다. 

 

4.1) 단순화 문법의 표기법을 알아보기 전에 먼저, 기본적인 return 을 가진 함수를 살펴보겠습니다.

//시작 함수
fun main(){

    // getData()함수의 return 값을 받기
    val data= getData()
    println(data)  // 출력 : Hello

}//main함수 .. 영역 끝

//기본적인 return을 가진 함수
fun getData(): String{
    return "Hello"
}

 

4.2) return 값을 할당연산자 = 로 바꾸어 함수 선언해보기

//시작 함수
fun main(){

    // 단순화된 함수의 리턴값 받기  -- 사용법은 일반 return 키워드 함수 사용과 다른 점 없음
    val data2= getData2()
    println(data2)  // 출력 : Hello

}//main함수 .. 영역 끝

fun getData2():String = "Hello"

 

getData()함수를 호출하면 Hello 값이 리턴되니, 호출하는 입장에서는 getData()함수가 Hello값을 가지고 있는 것으로 볼 수도 있으니 그냥 함수가 return 값을 가지고 있다는 형태의 표기법이 어색하지 않으니 중괄호{ .. }와 return 키워드를 생략하여 코드가 아주 간결해 지도록 하는 간편 문법입니다.

 

근데 함수의 리턴 코드가 다소 복잡한 경우도 있겠죠. 이를 알아보겠습니다.

 

4.3) 먼저, 조금 더 복잡한 리턴 코드가 있는 함수 만들어보기

//시작 함수
fun main(){

    //함수 호출하면서 정수값 5를 전달하여 결과 받기 
    val data3= getData3(5)
    println(data2)  // 출력 : Good    

}//main함수 .. 영역 끝

//좀더 복잡한 리턴 코드가 있는 함수  -- 파라미터로 값을 받아 조건에 따라 리턴값이 다른 함수
fun getData3(num:Int): String{
    if( num < 10 ) return "Good"  -- 전달받은 5가 10보다 작기에 Good 리턴
    else return "Bad"
}

 

4.4.) 할당연산자로 조건에 따른 리턴도 가능합니다. 실행문의 마지막 값을 리턴할 수 있는 if표현식을 사용하는 겁니다.

//시작 함수
fun main(){

    //함수 호출하면서 정수값 15를 전달하여 결과 받기 
    val data4= getData4(15)
    println(data4)  // 출력 : Bad    

}//main함수 .. 영역 끝

//할당연산자로 조건에 따른 리턴도 가능   -- if 표현식 이용
fun getData4(num:Int): String = if(num<10){
    "Good"
} else {
    "Bad"
}

 


 

5) 익명함수

함수의 이름이 없는 함수하고 하여 '익명함수' 라고 부릅니다. 함수를 변수에 대입하여 전달할 수 있다는 점에서 함수형 프로그래밍 언어의 특징을 가장 잘 보여주는 문법입니다.

자바의 익명클래스에 대해 익숙하다면 어느정도 받아들이기 어렵지 않겠지만 익숙치 않다면 다소 어렵게 느껴질겁니다. 이해한다기 보다 이런문법이 있다는 정도로 받아들이고 추후 앱개발 과정에서 이벤트 리스너 처리 코드를 작성하면서 익숙해 지도록 하겠습니다.

 

일반 함수와 익명함수의 문법적 차이를 비교하기 위해 먼저, 일반적인 함수 부터 만들어 보겠습니다.

 

5.1) 기본적인 함수

//시작 함수
fun main(){

    aaa()   //일반함수 호출

}//main함수 .. 영역 끝

//기본적인 함수
fun aaa(){
    println("aaa")
}

 

 

5.2) 익명함수

함수를 만들 때 함수명을 지정하지 않는 것이 익명함 수 입니다. 하지만 단순히 일반함수에서 이름만 없으면 호출할 방법이 없으니 문법적 에러입니다.

//익명함수 - 함수의 이름이 없는 함수 [ 당연히 그냥 이름만 지우면 에러 - why? 함수의 기능은 있지만 호출할 이름이 없으니까.
fun (){...}  //ERROR

 

그래서 익명함수는 반드시 어떤 변수에서 참조되고 있어야 합니다. 이렇게 함수를 가진 변수명을 이용하여 함수를 호출할 수 있습니다.

//bbb 변수에 이름없는 함수(익명함수)를 대입
val bbb= fun(){
    println("bbb")
}

 

 

이제 익명함수를 저장하고 있는 변수명 bbb 를 이용하여 익명함수를 호출해서 실행하겠습니다. 변수인 bbb 를 마치 함수이름 인양 호출하시면 됩니다.

//시작 함수
fun main(){

    bbb()   //익명함수를 참조하는 변수명을 이용하여 함수 호출

}//main함수 .. 영역 끝

//bbb 변수에 이름없는 함수(익명함수)를 대입
val bbb= fun(){
    println("bbb")
}

 

그러고 보니 bbb 변수의 자료형은 무엇일까요? bbb는 함수를 저장하고 있으니 함수의 자료형이겠네요. 아주 중요하게 보셔야할 함수의 자료형 표기법을 알아보겠습니다.

 

5.3) 익명함수를 가진 변수의 자료형

모든 변수는 자료형을 가집니다.[명시적이든 자동추론이 되든] , 그럼 함수를 가진 변수의 자료형은? 람다표기법(화살표)을 사용합니다.

[ 익명함수의 자료형 : () -> 리턴타입 ]  ** 리턴타입이 없으면 Unit [자바의 void역할]

//시작 함수
fun main(){

    ccc()   //명시적으로 익명함수의 자료형[()->리턴타입]이 지정된 참조변수.ccc 이름을 통해 익명함수 호출

}//main함수 .. 영역 끝

//[ 익명함수의 자료형 : () -> 리턴타입 ]  ** 리턴타입이 없으면 Unit [자바의 void역할]
val ccc:()->Unit = fun(){
    println("ccc")
}

 

5.4) 익명함수를 축약형으로 쓰고 싶다면?

즉, fun() 키워드가 굳이 없어도 익명함수임을 구별할 수 있을 듯 하여 {..}만으로 생략이 가능합니다.

//시작 함수
fun main(){

    ddd()   //fun()키워드 생략한 익명함수 호출 [ {} 까지는 생략불가 ]

}//main함수 .. 영역 끝

//익명함수 축약표기법
val ddd:()->Unit = {
    println("ddd")
}

 

당연하게도 {...} 까지 생략하는 축약형은 불가능 합니다. 함수의 기능 코드 작성임을 인식하기도 해야하고. {}까지 없애면 실행문이 함수일때 리턴값을 대입하라는 글씨로 오인될 수 있습니다.(함수선언의 단순화). 즉, 익명함수 {}는 생략불가!

val ddd2:()->Unit = println("ddd2") //ERROR

 

5.5) 축약된 익명함수를 참조하는 변수의 자료형을 생략하는 자동 추론도 가능합니다.

//시작 함수
fun main(){

    eee()   //익명함수를 가진 변수의 자료형을 명시하지 않고 추론하도록..하고 사용

}//main함수 .. 영역 끝

//변수의 자료형은 자동 추론이 되니까 익명함수를 참조하는 변수의 자료형은 생략가능
val eee= {
    println("eee")
}

 

 

♣파라미터 있는 익명함수

 

5.6) 파라미터를 전달받는 익명함수

문자열을 전달받아 그 글자의 글자수를 출력해주는 함수를 만들어 보겠습니다. 리턴값은 없는 함수입니다.

//시작 함수
fun main(){

    fff("Hello") //파라미터를 전달받는 익명함수 호출 - 문자열을 전달받아 그 글자의 글자수를 출력해주는 함수 [리턴값은 없는 함수 ]

}//main함수 .. 영역 끝

//파라미터를 전달받는 익명함수 [리턴값은 없는 함수 ]
val fff= fun(s:String){
    println("글자수:" + s.length)
}

//**출력**
글자수:5

 

5.7) 파라미터를 가진 익명함수의 자료형 [ (파라미터 자료형)->리턴타입 ]

//시작 함수
fun main(){

    ggg("Nice")  //익명함수의 자료형을 명시한 참조변수를 이용한 호출
    
}//main함수 .. 영역 끝

//이 익명함수를 가진 참조변수도 명시적으로 자료형을 명시할 수 있음. [ 파라미터:String 1개, 리턴타입 : Unit ]
val ggg:(String)->Unit = fun(s:String){
    println("글자수:" + s.length)
}

//**출력**
글자수:4

 

5.8) 파라미터를 가진 익명함수의 축약표현 

파라미터가 있는 익명함수도 fun()키워드를 생략할 수 있습니다. 다만, 파리미터를 만드는 소괄호()가 생략되었기에 {} 안에 참조변수 명을 쓰고 화살표 -> 로 실행문을 작성합니다. 

자바와 비슷하지만 참조변수명과 화살표-> 가 중괄호 {} 안에 있다는 것이 차이가 있습니다.

//시작 함수
fun main(){

    hhh("God")   //fun()키워드 생략한 익명함수 호출 [ { } 안에서 파라미터 작성하는 코드 필요 ]
    
}//main함수 .. 영역 끝

//파라미터를 전달받는 함수도 fun()키워드를 생략할 수 있음. 단, { }안에 익명함수의 자료형[()->리턴타입] 같은 형식으로 코딩 필요
val hhh:(String)->Unit = {
    s -> println("글자수:" + s.length)  // "s->" 의 s가 이 익명함수의 파라미터 변수이름 ( s:String 처럼 자료형명시 해도 됨 )
}

//**출력**
글자수:3

 

5.9) 파라미터 1개 일때는 파라미터명과 화살표도 생략가능합니다.

//시작 함수
fun main(){

    iii("He")    //파라미터가 1개라면 { }안에서 생략가능 - 단, 만약 그 파라미터를 사용하고자 한다면 익명함수의 특별한 키워드 "it" 사용
    
}//main함수 .. 영역 끝

//혹시 파라미터가 1개뿐이라면 "s->" 생략해도 됨.
val iii:(String)->Unit = {
    println("iii")
    //단, 만약 그 파라미터를 사용하려면.. 익명함수의 특별한 변수 "it" 키워드 사용 [ it변수의 자료형은 참조변수에 명시한 익명함수 자료형(String) 으로 자동 선언됨
    println("글자수:" + it.length)
}

//**출력**
iii
글자수:2

 

단, 익명함수 참조변수의 명시적 타입지정을 안하면 자동으로 파라미터가 없는 ()->Unit 으로 추론되기에 "it" 키워드의 사용이 불가능합니다.

val iii2= {
    println("글자수:"+it.lenght) //ERROR   - it 을 인식하지 못함
}

 

또한, {} 안에서 파라미터를 생략하지 않았더라도 익명함수의 참조변수 자료형을 자동 추론되게 할 수 없습니다. 즉, 파라미터가 있을때는 익명함수의 자료형을 참조변수에 명시거나 파라미터의 자료형을 명시적으로 표시해야 합니다.

//{} 안에서 파라미터를 생략하지 않았더라도 익명함수의 참조변수 자료형을 자동 추론되게 할 수 없음. 즉, 파라미터가 있을때는 익명함수의 자료형을 참조변수에 명시해야함.
val iii3= {
    //s -> println("글자수:" + s.length)  // ERROR
    s:String -> println("글자수:" + s.length)  // OK - 많이 활용되지는 않음.
}

 

5.10) 파라미터가 여러개인 익명함수의 자료형.  [ (자료형1, 자료형2, ...) -> 리턴타입 ]

파라미터가 여러개일때는 it변수만으로 파라미터들을 표현하는 것이 불가능 하기에 파라미터의 생략은 불가능 합니다. 

//시작 함수
fun main(){

    jjj("sam", 20)  //파라미터 여러개인 익명함수 호출 [ "it"키워드 불가 ]
    
}//main함수 .. 영역 끝

//파라미터 여러개도 당연히 가능 [ 파라미터가 여러개일때는 "it" 키워드는 사용 불가 ]
val jjj:(String, Int) -> Unit = {
        name, age -> println("name: $name    age: $age")
}

//**출력**
name: sam   age: 20

 

♣리턴타입이 있는 익명함수

 

5.11) 리턴타입이 있는 익명함수

익명함수의 리턴타입 지정도 일반함수와 마찬가지로 함수 소괄호 () 뒤에 : 콜론 후 지정합니다.

//시작 함수
fun main(){

    val number= kkk()  // 리턴타입이 있는 익명함수 호출  [ number의 타입은 자동추론 ]
    println( number )  // 출력 : 10  
    
}//main함수 .. 영역 끝

//리턴타입이 있는 익명함수 [파라미터 없고 리턴타입이 Int인 익명함수]
val kkk= fun():Int{
    return 10
}

 

5.12) 리턴타입이 있는 익명함수를 참조하는 변수의 자료형을 명시적으로 지정해보기

//시작 함수
fun main(){

    val number2:Int= lll()  // 익명함수의 자료형을 명시한 참조변수를 이용한 호출 [명시적으로 자료형 지정]
    println( number2 )  // 출력 : 20  
    
}//main함수 .. 영역 끝

//리턴타입이 있는 익명함수를 참조하는 변수의 자료형 명시적으로 지정
val lll: ()->Int = fun():Int{
    return 20
}

 

5.13) 리턴타입이 있는 익명함수의 fun()키워드 생략하는 축약 표기법 사용해보기

* 주의! : 람다식처럼 축약 표기법을 사용했다면 return 키워드는 생략 해야만 합니다.

//시작 함수
fun main(){

    val number3:Int= mmm()  // fun()키워드를 생략한 익명함수 호출 - [ return 키워드도 삭제해야 함 ]
    println( number3 )  // 출력 : 30  
    
}//main함수 .. 영역 끝

//리턴타입이 있는 익명함수의 fun()키워드 생략하는 축약형 
val mmm: ()->Int = {
    30     // - [ * 주의 : return 키워드를 제거해야 함 *]
}

 

5.14) 축약 표기법안에 실행문이 여러개 인 경우

만약 축약된 {}안에 실행문이 여러개 인 경우에는 마지막 실행문의 값이 return 되는 값이 됩니다. if표현식과 동일합니다.

//시작 함수
fun main(){

    val number4:Int= nnn()  // fun()키워드를 생략한 익명함수의 {}안에 실행문이 여러줄인 함수 호출 [ 가장 마지막 실행문이 리턴값 ]
    println( number4 )  // 출력 : 50  
    
}//main함수 .. 영역 끝

//만약 {}안에 실행문이 여러개 라면? - 마지막 실행문의 값이 return 값
val nnn: ()->Int = {
    30
    println("중간 글씨") //{}영역은 실행문 작성 영역이므로 당연히 중간에 이렇게 리턴값이 아닌 실행문이 있어도 됨. 단, 이 println()이 마지막 실행문이면 에러. [리턴타입이 안 맞아서]
    40
    50 //이 값이 리턴값
    //println("중간 글씨") //ERROR  -- 이 함수의 리턴타입이 Int여서 println()함수가 마지막 값일 수 없음
}

//**출력**
중간 글씨
50

 

5.15) 파라미터와 리턴타입이 모두 있는 익명함수

두 개의 정수값을 전달받아 덧셈결과 값을 리턴해주는 함수를 익명함수로 만들고 이 함수의 자료형를 명시해 보겠습니다.

//시작 함수
fun main(){

    val add= ooo(5,3)   //파라미터와 리턴타입이 모두 있는 익명함수 호출 [두수를 전달받아 덧셈결과를 리턴하는 함수]
    println(add)    //출력 : 8
    
}//main함수 .. 영역 끝

//파라미터와 리턴타입이 같이 있는 익명함수 [두 수를 전달받아 덧셈결과를 리턴하는 함수 ]
val ooo:(Int, Int) -> Int = fun(a:Int, b:Int):Int{
    return a+b
}

 

5.16) 파라미터와 리턴타입이 모두 있는 익명함수를 축약표현하기 - 람다식처럼

fun()키워드를 생략하였기에 파라미터는 {}안에 자료형없이 기입하고 화살표 -> 후 return 키워드를 생략한 후 결과값을 기입합니다.

//시작 함수
fun main(){

    val add2= ppp(4,6)  //fun()키워드 생략한 파라미터와 리턴타입이 같이 있는 익명함수 호출
    println(add2)   //출력: 10
    
}//main함수 .. 영역 끝

//fun()키워드 생략한 파라미터와 리턴타입이 같이 있는 익명함수 [두 수를 전달받아 덧셈결과를 리턴하는 함수 ] : {}안에 익명함수 타입(Int,Int)->Int 형태로 코딩 , return 키워드 역시 생략
val ppp:(Int, Int) -> Int = {
        a, b -> a+b      // (Int,Int)->Int 형태로 코딩 , return 키워드 역시 생략
}

 

5.17) 파리미터와 리턴타입의 자료형이 다른 익명함수 연습 - 4가지 형태로 연습

 

5.17-1) 파라미터와 리턴타입이 다른 일반함수

//기본적인 함수 [ String 입력을 받아 글자수를  Int 로 리턴해 주는 기능 함수]
fun stringLength(str:String): Int{
    return str.length
}

 

5.17-2) 파라미터와 리턴타입이 다른 익명함수

//익명함수 [함수를 stringLength2라는 변수에 넣는 것 (String) -> Int 는 String를 매개변수로 받아  Int를 리턴해 준다는 표기법 ]
val stringLength2: (String) -> Int = fun(str:String):Int{
    return str.length
}

 

5.17-3) 파라미터와 리턴타입이 다른 축약형 람다표기법  - fun(), return 키워드 생략

//익명함수 축약형 [ fun() , return 키워드 생략 ]
val stringLength3: (String) -> Int = {
        str -> str.length
}

 

5.17-4) 축약형 람다표기법의 파라미터가 1개인 경우 파라미터명 생략 - fun(), return 키워드, 파라미터 생략 [ 숨겨진 it 파라미터 사용 ]

//파라미터가 1개이므로 생략한 익명함수 축약형 [ fun() , return 키워드 및 파라미터 생략 ]
val stringLength4: (String) -> Int = {
    it.length      // 생략된 파라미터 1개를 대체하는 it 변수
}

 

위 4개의 함수를 호출해 사용해 보겠습니다.

//시작 함수
fun main(){

    //파라미터와 리턴타입이 다른 익명함수 사용의 마지막 연습
    val len= stringLength("android")
    println(len)       //출력: 7

    //익명함수로 만든 함수 호출
    val len2= stringLength2("kotlin")
    println(len2)      //출력: 6

    //축약표현 익명함수 호출
    println( stringLength3("nice") )   //출력: 4
    println( stringLength4("web") )    //출력: 3
    
}//main함수 .. 영역 끝

 

지금까지 살펴본 익명함수는 '고차함수'로 이용할 때 많이 사용됩니다. 다음으로 고차함수를 알아보겠습니다.


 

6) 고차함수

함수를 호출할 때 파라미터에 전달되는 값을 '인수 argument' 라고 부릅니다.

코틀린의 함수는 다른 함수를 인수로 전달 받을 수 있습니다. 이렇게 다른 함수를 인수로 사용하는 함수를 고차 함수라고 합니다. 즉, 파라미터에 함수를 받아서 이 함수를 사용한다는 겁니다. 처음 보시는 분들은 다소 어렵게 느껴하는 문법입니다. 이름부터 다소 난이도가 있어 보이기도 합니다. 사실. 고차함수 라는 이름은 크게 중요하지 않습니다. 개발자들도 '고차함수 만들어 봐' 라는 식의 말은 거의 사용하지 않습니다. 그냥 함수를 파라미터로 받는 함수가 고차함수 라는 것을 알고 있을 뿐이니 굳이 고차함수라는 용어에 너무 집착하지 말고 그냥 함수를 받아 사용하는 함수의 문법적 모습만 경험해 보시기 바랍니다. 추후 앱 개발 과정에서 매우 자주 사용하시게 될 겁니다. 그때 익숙해 져 봅시다.

 

고차함수를 알아보기 전에 먼저 함수를 객체처럼 다른 변수에 대입할 수 있다는 것을 먼저 알아보겠습니다.

 

6.1) 익명함수를 참조하는 변수를 다른 변수에 대입하기

//정수를 전달받아 출력하는 익명함수를 저장하는 sss 변수
var sss= fun(a:Int){
    println("sss : $a")
}
var ttt= sss //위에서 만든 익명함수 sss 를 ttt변수가 참조하면 ttt()로 함수호출 가능함.

 

함수를 참조하는 변수 sss와 ttt 모두 같은 함수를 참조하고 있기에 두 변수명 중 어떤 것을 사용해도 호출이 가능합니다.

//시작 함수
fun main(){

    sss(100)
    ttt(200)
    
}//main함수 .. 영역 끝

//**출력**
sss : 100     // sss변수에 의해 호출된 출력
sss : 200     // ttt변수에 의해 호출된 출력

 

6.2) 익명함수를 축약한 람다표기법를 참조하는 변수도 다른 변수에 함수 전달 가능합니다. 즉, 코틀린은 모든 함수를 객체로 다룹니다.

//문자열을 파라미터로 받아 출력해 주는 람다 함수를 저장하는 xxx 변수
var xxx:(String)->Unit= { println(it) }

//xxx 변수가 참조하는 람다함수를 yyy변수에 대입
var yyy= xxx

 

함수를 참조하는 xxx화 yyy변수명을 이용하여 함수를 호출해 보겠습니다.

//시작 함수
fun main(){

    xxx("Hello world")
    yyy("Nice to meet you")
    
}//main함수 .. 영역 끝

//**출력**
Hello world         // xxx변수에 의해 호출된 출력
Nice to meet you    // yyy변수에 의해 호출된 출력

 

위 2가지 예제로 살펴봤듯이 코틀린은 함수를 마치 값처럼 다른 변수에 넘겨주면서 호출하는 것이 가능합니다.

이렇게 다른 변수에 함수를 넘겨줄 수 있다면, 어떤 함수의 파라미터(매개변수)에 다른 함수를 인수 값으로 넘겨주는 것도 가능합니다.

 

6.3) 고차함수 

함수를 다른 변수에 대입할 수 있듯이 함수의 파라미터로 다른 함수를 전달하여 사용하는 것을 고차함수 라고 부릅니다.

첫번째 파라미터로 문자열을 받고, 두번째 파라미터로 함수를 전달받는 고차함수 getLength()라는 함수를 만들어 보겠습니다.

이 함수는 첫번째 파라미터로 전달받은 문자열을 두번째 파라미터로 전달받은 함수의 인수로 전달하는 기능을 구현합니다. 조금 이해가 어렵죠? 주석과 코드를 보면서 이해해 보겠습니다.

//'고차함수' : 함수의 파라미터로 다른 함수를 사용하는 것을 고차함수라고 부름 [다른변수에 함수를 대입할 수 있듯이]
// 두번째 파라미터 aaa는 익명함수를 인자로 받겠다고 표시
fun getLength(str:String, aaa: (String)->Int ): Int{
    return aaa(str) //전달받은 함수(aaa)의 파라미터로 첫번째 파라미터인 str을 전달하면서 함수호출하고 결과 return
}

 

위 고차함수의 두번째 파라미터에 5.17-3)에서 사용했던 문자열의 길이값을 리턴해주는 함수를 전달하여 사용해 보겠습니다.

//시작 함수
fun main(){

    //함수는 다른 함수를 인수로 취할 수 있습니다. 다른 함수를 인수로 사용하는 함수를 고차 함수라고 합니다. 이 패턴은 자바에서 콜백 인터페이스를 사용할 때와 동일한 방식으로 구성요소 간에 통신하는 데 유용합니다.
    val len3= getLength("android",stringLength3) //아래 만든 익명함수를 2번째 파라미터에 전달
    println(len3)  //출력: 7
    
}//main함수 .. 영역 끝


//5.17-3)익명함수 축약형 [ fun() , return 키워드 생략 ]
val stringLength3: (String) -> Int = {
        str -> str.length
}

//'고차함수' : 함수의 파라미터로 다른 함수를 사용하는 것을 고차함수라고 부름 [다른변수에 함수를 대입할 수 있듯이]
// 두번째 파라미터 aaa는 익명함수를 인자로 받겠다고 표시
fun getLength(str:String, aaa: (String)->Int ): Int{
    return aaa(str) //전달받은 함수(aaa)의 파라미터로 첫번째 파라미터인 str을 전달하면서 함수호출하고 결과 return
}

 

고차함수에 전달하는 함수를 이전에 만들었던 함수를 사용하는 것이 아니라 함수를 호출하면서 그 자리에서 바로 정의하여 전달할 수도 있습니다.

//시작 함수
fun main(){

    //함수는 다른 함수를 인수로 취할 수 있습니다. 다른 함수를 인수로 사용하는 함수를 고차 함수라고 합니다. 이 패턴은 자바에서 콜백 인터페이스를 사용할 때와 동일한 방식으로 구성요소 간에 통신하는 데 유용합니다.
    val len3= getLength("android",stringLength3) //아래 만든 익명함수를 2번째 파라미터에 전달
    println(len3)  //출력: 7
    
    //2번째 파라미터 자리에서 익명함수[String받아 Int를 리턴해주는]를 그 자리에서 설계하여 전달해보기
    val len4= getLength("kotlin", { str -> str.length })
    println(len4)  //출럭: 6 
    
}//main함수 .. 영역 끝

 

당연히 위와 다른 기능의 함수를 정의하여 전달 할 수 도 있습니다.

//시작 함수
fun main(){

    //함수는 다른 함수를 인수로 취할 수 있습니다. 다른 함수를 인수로 사용하는 함수를 고차 함수라고 합니다. 이 패턴은 자바에서 콜백 인터페이스를 사용할 때와 동일한 방식으로 구성요소 간에 통신하는 데 유용합니다.
    val len3= getLength("android",stringLength3) //아래 만든 익명함수를 2번째 파라미터에 전달
    println(len3)  //출력: 7
    
    //2번째 파라미터 자리에서 익명함수[String받아 Int를 리턴해주는]를 그 자리에서 설계하여 전달해보기
    val len4= getLength("kotlin", { str -> str.length })
    println(len4)  //출럭: 6 
    
    //전달되니 문자열을 Int 타입으로 형변환하는 함수를 전달받아 기능 다르게 동작하도록 해보기
    val len5= getLength("450",{ str -> str.toInt() }) //이런식으로 원래 의도와 다른 함수를 전달하여 기능이 바뀌게 할 수도 있음.
    println(len5 + 3)  //출력: 453    ~ 450+3
    
}//main함수 .. 영역 끝

 

6.4) 특이한 고차함수 축약 람다표기법

고차함수의 파라미터안에 또 다른 함수를 쓰면 소괄호 () 안에 중괄호 {} 코드가 작성되기에 가독성이 나빠집니다. 이를 위해 람다식 {}를 소괄호 밖으로 빼서 작성하는 축약 표기법이 가능합니다. 처음에는 잘 읽어지지 않지만 익숙해지면 코딩이 너무 편해집니다.

// ** 코틀린문법에서는 람다식으로 표현된 함수의 파라미터를 () 밖에 작성하는 방식을 권장함. [마치 함수정의 하듯이]
val len6= getLength("ios"){ str -> str.length }
println(len6)

// ** 람다함수의 파라미터가 1개이므로 str-> 를 생략하고 it 이라는 특별한 변수명으로 대체 가능함.
val len7= getLength("mobile"){ it.length }
println(len7)

 

이 고차함수가 가장 많이 사용되는 곳은 안드로이드의 클릭리스너 작업에 많이 사용됩니다.

추후 SAM 변환 문법 소개시에 추가로 소개할 예정입니다.

 


 

7) 함수 파라미터의 default value

자바의 경우 함수를 호출할 때 파라미터의 개수만큼 인수를 전달하지 않으면 에러가 발생합니다. 그래서 함수 호출하면서 사용할 값이 아니더라도 반드시 값을 전달해야만 하는 불편함이 있었습니다. 그래서 코틀린은 혹시 값을 전달 받지 않으면 기본값을 지정할 수 있는 문법을 제공합니다.

//시작 함수
fun main(){

    //함수 파라미터의 default value
    zzz(5,3) 
    zzz() //a,b 가 모두 default 값으로 적용됨.
    zzz(10) //b는 default 값으로 적용됨.
    //혹시 특별하게 b에게 10을 적용하고 싶다면.. 함수 파라미터 선택 적용
    zzz(b=5) //호출할때 변수명 지정
    
}//main함수 .. 영역 끝


//함수 파라미터의 default value
fun zzz(a:Int=1000, b:Int=2000){   //a에 값이 전달되지 않으면 1000, b에 값이 전달되지 않으면 2000
    println("a: $a , b: $b")
}

//**출력**
a: 5 , b: 3
a: 1000 , b: 2000
a:10 , b: 2000
a:1000 , b: 5

 

 

함수 파라미터의 지정을 통하여 값 전달의 순서를 변경할 수도 있습니다.

//시작 함수
fun main(){

    //함수 파라미터의 지정을 통하여 값 전달 순서를 변경할 수도 있음.
    xxxxx("korea", "seoul")
    xxxxx(city = "newyork", nation = "usa")
    
}//main함수 .. 영역 끝

//첫번째 파라미터 nation만 default 값 지정
fun xxxxx(nation:String="korea", city:String){
    println(nation)
    println(city)
    println()
}

//**출력**
korea
seoul

usa
newyork

 

반응형
반응형

이번 글에서는 코틀린에서 대량의 데이터를 저장하는 배열 및 컬렉션에 대해 알아보겠습니다.

앱 개발과정에서 가장 많이 사용하는 문법이니 주의깊게 읽어보시기 바랍니다. 기존 자바와 만드는 방식의 차이가 꽤 있습니다.

 

새로운 코틀린 파일 Test04Basic4.kt 를 만들고 문법을 알아보겠습니다.

 

프로그램의 시작인 main() 함수까지 작성하겠습니다.

 

6. 배열 Array & 컬렉션 Collection

자바처럼 코틀린도 대량의 데이터를 저장하는 방법은 크게 2 종류가 있습니다.

요소의 개수 변경이 가능한지에 따라 배열과 컬렉션이 존재합니다. 자바와 차이가 존재하기에 주의깊게 살펴보시기 바랍니다.

 

1) 요소개수의 변경이 불가능한 배열 : Array 

배열 참조변수도 역시 var, val 키워드로 만들게 됩니다. 자바와는 다르게 변수선언할때 타입표시에 []가 없습니다. 대신 이 변수가 배열이라는 것을 명시적으로 알수있는 Array 클래스가 배열의 자료형이며 배열요소의 자료형은 제네릭<>으로 지정합니다.

배열 객체를 생성하는 방법도 new 키워드를 사용하는 것이 아니고 배열객체를 생성해 주는 arrayOf()라는 내장함수를 사용합니다.

 

문법 : [ var 참조변수명 : Array<요소자료형> = arrayOf(값1, 값2, 값3, ....)

var arr:Array<Int> = arrayOf(10,20,30) //정수형 배열요소 3개짜리 배열객체 및 참조변수

 

주의 !! Array<Int> 제네릭 뒤에 대입연산자 = 를 붙여서 작성하면 비교연산자 >= (크거나 같다)와 혼동될 수 있어 문법적 에러로 처리함

 

참조변수를 만들 때 데이터타입 생략을 통한 자동추론도 가능합니다.

var arr = arrayOf(10,20,30) //데이터타입 생략을 통한 자동추론 가능함 Array<Int>

 

1.1) 요소의 값을 사용하는 문법은 자바와 같습니다.  배열참조변수명[인덱스 번호]

println(arr[0])  // arr배열의 0번방 요소 값 출력 : 10
println(arr[1])  // arr배열의 1번방 요소 값 출력 : 20
println(arr[2])  // arr배열의 2번방 요소 값 출력 : 30

 

1.2) 당연하게도 인덱스 번호가 틀리면 문법적 에러로 표시되지는 않지만 실행 하면 실행 도중에 에러(예외)가 발생합니다.

println(arr[3])  //인덱스번호가 틀리면 Exception발생

 

1.3) 배열 요소의 값 변경도 특별할 것 없습니다. 배열참조변수명[인덱스 번호] = 값  

arr[0]= 100        //arr배열의 0번방에 100 대입
println(arr[0])    //출력: 100

 

1.4) 특이한 점은 코틀린의 모든 변수는 객체이므로 당연히 배열도 객체이며 모든 배열은 자동으로 get(), set() 함수와  size변수를 기본으로 가지고 있다는 겁니다. 그래서 값을 읽어올때나 변경할 때 get(), set() 메소드를 이용해도 된다는 겁니다.

//마치 자바의 ArrayList 요소 값 얻어오기 처럼...사용가능   
println(arr.get(0)) //출력 : 100
println(arr.get(1)) //출력 : 20
println(arr.get(2)) //출력 : 30
println()

arr.set(1, 200)     //배열 1번방에 200을 대입
println(arr.get(0)) //출력 : 100
println(arr.get(1)) //출력 : 200
println(arr.get(2)) //출력 : 30

 

get(), set() 이 존재하기는 하지만 코틀린에서는 코드 가독성 면에서 [인덱스번호] 사용을 권장합니다. 당분간은 병행하며 소개하겠습니다.

 

1.5) 배열의 길이 size 프로퍼티(멤버변수)[ 자바에서는 length ]

println("배열의 길이 : ${arr.size}")  //출력 : 배열의 길이 : 3

 

1.6) 출력을 일일이 하는거 짜증나죠? 반복문을 이용한 요소값 순차 접근

for(i in 0 until 3){   // 0..3쓰면 OutOfIndex Exception발생함
    println(arr.get(i))
}

//**출력**
100
200
30

 

for문에 소개 때 사용되었던 0..5는 사실 배열같은 것 입니다. 즉, 요소 6개짜리 배열을 놓은 것과 같습니다.
in키워드는 자바의 확장된 for문(foreach문) 같은 역할을 하게 됩니다. 다시말해 0..5자리에 배열이 놓여지고 i는 각 요소들의 차례로 대입되는 임시 제어변수 같은 것이 되기에 배열의 값을 더 쉽게 접근하도록 코딩하는 것이 가능합니다.

 

1.7) 배열요소에 순차접근하는 foreach문 같은 for문 사용법

//배열요소값 for문으로 출력하기 (Java의 foreach문 같은 코드)
for(n in arr){ //n은 인덱스번호가 아님.... 주의!!! 요소임.
    println(n) 
}

//**출력**
100
200
30

 

훨씬 코드가 간결하고 요소값에 접근하기 위해 get()이나 [인덱스번호]를 사용하지 않기에 실수의 여지도 줄일 수 있습니다. 

제어변수 n이 인덱스 번호가 아니고 요소참조변수 임을 혼동하지 않으시기 바랍니다.

 

1.8) 혹시 그럼에도 인덱스로 얻어오고 싶다면?? [ indices[인디시즈] : index의 복수형 ]

for(i in arr.indices){   //i는 인덱스번호를 저장하는 Int형 제어변수
    println(i)
}

//**출력**
0
1
2

 

1.9) 혹시 인덱스와 값을 동시에 가져오고 싶다면 [ withIndex() ]

for( (i, v) in arr.withIndex() ){    // (인덱스번호, 요소값)  - 순서변경 불가, 소괄호 필수
    println( "[ $i ] : $v ")
}

//**출력**
[ 0 ] : 100
[ 1 ] : 200
[ 2 ] : 30

 

1.10) 배열의 forEach 기능 메소드 

다른 함수형 프로그래밍 언어들의 배열처럼 요소값 각각을 반복적으로 접근할때마다 {}의 코드가 실행되도록 하는 forEach 기능이 있습니다. {}안에서는 생략된 변수 it 이 있으며.. it이 요소의 값을 가지고 있습니다. it의 자료형은  배열요소의 자료형으로 자동 지정됩니다.

추후에 고차함수에 대한 실습으로 {} 표기법에 대한 추가 소개할 예정이고 지금은 {}영역안에 코드가 실행된다고 

arr.forEach {     //배열 요소의 개수만큼 {} 영역이 실행됨
    println(it)   //{}영역 안에서 it 이라는 이름의 숨겨진 변수가 요소값을 가지고 있음
}

//**출력**
100
200
30


 for()문법안에 arr 배열을 지정하여 반복하는 것 보다는 arr배열에게 요소 각각에 대하여 반복하여 {} 영역을 실행하라는 명령구조가 보다 직관적이고 대상이 명확하여 필자가 가장 선호하는 배열 요소의 순차 반복 처리 기능입니다. 

 

 

- 코틀린 arrayOf() 배열의 특이한 점 -


1.11) 서로다른 자료형의 배열요소

배열 각 요소의 자료형을 다르게 하면 각 요소의 타입은 자동 Any타입이 됩니다.

var arr2= arrayOf(10, "Hello", true)  // Int, String, Boolean  ==> Array<Any> 타입으로 추론

 

값 가져오는 것은 특별한 문제 없습니다.

println(arr2[0])   //출력 : 10
println(arr2[1])   //출력 : Hello
println(arr2[2])   //출력 : true

 

각 요소의 값 변경도 특별한 문법 없이 대입연산자로 가능합니다.

arr2[0]= 20           //0번방의 값을 10 -> 20으로 변경
arr2.set(1, "bbb")    //1번방의 값을 "Hello" -> "bbb"로 변경
arr2[2]= 3.14         //기존 자료형을 변경해도 됨. Boolean --> Double [Any타입 이므로]

 

아무 자료형이나 마구 넣어도 되고 기존 자료형을 신경쓰지 않아도 되니 쓰기 편하고 좋아 보이나요? 그럼 배열요소를 <Any> 타입으로 지정하는 것이 아주 편해 보이지만 요소의 타입이 저장된 값의 자료형으로 되어 있지 않기에 곧바로 연산에 사용 못하는 문제가 발생합니다.

println( arr2[0] + 5 ) //ERROR - arr2[0]의 값은 20 이기에 산술 덧셈 + 5 가 가능해 보이지만 본인이 Int가 아니라 Any로 인식하여 산술연산 불가

 

만약 산술연산을 해야 하는 상황이라면 형변환 연산자 as 를 사용하여 Int로 변경한 후 덧셈 연산을 수행해야 합니다. 

println( arr2[0] as Int + 5)        //요소를 as 연산자로 Int로 변환해서 사용해야 함. [ 참조타입의 형변환 연산자 as ]
println( arr2[1] as String + "AAA") //요소를 String으로 변환하여 문자열 결합 "bbb"+"AAA"

 

번거롭네요. 그래서 보통 배열을 사용할때는 타입을 명시하여 같은 자료형만 저장하는 방식을 선호합니다. [원래 배열의 특징]

 

1.12) arrayOf()에 Type을 지정하여 배열을 만들기 [ 제네릭 <> ]

var arr3= arrayOf<Int>(10,20,30) //다른 자료형은 넣으면 error

 

<Int> 제네릭 표기법이 보기 싫다면 기본 타입 배열을 만들어주는 전용함수 이용 [ arrayOf() --> intArrayOf() ]

var arr4= intArrayOf(10,20,30)

 

배열 참조변수만 먼저 만들고 나중에 배열객체 대입하려면 변수만들때 자료형을 표시

var arr5: IntArray        //Int형 배열 Array 참조변수
arr5= intArrayOf(1,2,3)

 

Boolean부터 Double까지의 기초 자료형들만 xxxArrayOf()가 존재합니다. StringArrayOf()는 제공하지 않습니다.

 

1.13) 배열 요소값의 시작이 null값을 가진 배열 만들기 [길이:5]  arrayOfNulls()

val arr6= arrayOfNulls<Double>(5)  //요소 개수가 5개인 null값을 가지고 만들어진 배열
for(t in arr6){
    println(t)
}

//**출력**
null
null
null
null
null

 

주목! 위 코드에서 arr6 배열참조변수를 val 로 만들었기에 다른 배열객체로 변경하여 참조할 수 없습니다.

arr6= arrayOfNulls<Double>(3) //ERROR - val 읽기전용 변수의 참조값 변경 불가능

 

배열 참조변수의 자료형을 명시할 때 null값을을 요소가 가질 수 있도록 제네릭에 ? 가 추가된 nullable 변수로 지정해야 함

val arr7:Array<Float?> = arrayOfNulls<Float>(3); //null배열을 만들때 참조변수의 타입을 명시할때 제네릭 자료형에 ?(nullalbe) 키워드 필요함.

 

 

**배열 기본 문법 정리. 한번 더 소개 ** 참조변수에 배열의 타입을 명시하기 - []표기는 없어짐 ***

//var arrr:Int[] = arrayOf(10,20) //error
var arrr:Array<Int> = arrayOf(10,20) //Array타입을 명시할때는 반드시 <>제네릭을 표시해야함.
//var arrr2:Array<Int>= arrayOf(100,200) // <> 다음에 = 대입연산자를 곧바로 붙이면 에러. 띄어쓰기 필요 [ **주의**]

 

** arrayOf()는 배열의 개수는 변경할 수 없음 ***

 


 

2) 자바의 Collection 과 같은 목적의 클래스들 : Collection [ List, Set, Map ]

대량의 데이터를 어떻게 저장하고 관리하는지에 대해 정리한 이론인 자료구조를 실제로 구현한 클래스들의 모음을 Collection 이라고 부릅니다. 코틀린의 컬렉션도 자바의 컬렉션과 마찬가지로 데이터를 취급하는 방식에 따라 크게 3가지로 구분할 수 있습니다.

 

  * List : 요소가 순서대로 저장됨. 인덱스번호가 자동부여. 중복데이터 허용
  * Set  : 요소가 순서대로 저장되어 있지 않음. 인덱스번호 없음. 중복데이터 불허
  * Map : 요소가 순서대로 저장되어 있지 않음. [키,벨류]쌍으로 요소 저장. 별도 지정 key로 요소 식별. 중복 key 불허, 중복데이터 허용

 

구분 종류는 같지만 사용하는 문법에는 큰 차이가 있습니다.

코틀린의 Collection 들은 자바와 다르게 요소의 추가/삭제 및 변경이 불가한 종류와 가능한 종류로 나뉘어져 있음.
 2.1) 요소개수의 추가/삭제 및 변경이 불가능한 컬렉션 : listOf(), setOf(), mapOf() [배열과 다르게 요소값 변경도 불가능]
 2.2) 요소개수의 추가/삭제 및 변경이 가능한 mutable 컬렉션 : mutableListOf(), mutableSetOf(), mutableMapOf()

 

각각에 대해 알아보겠습니다. 배열을 만들때도 new 키워드 대신에 arrayOf()라는 기능함수를 이용했듯이 컬렉션을 만들때도 listOf() 와 같은 함수로 만들게 됩니다.

 

2.1) 요소개수의 추가/삭제 및 변경이 불가능한 컬렉션 : listOf(), setOf(), mapOf()

 

2.1.1) List - 요소가 순서대로 저장됨. 인덱스번호가 자동부여. 중복데이터 허용

val list:List<Int> = listOf(10,20,30,20) //중복데이터[20] 허용 [*주의* 제네릭 <> 타입에 = 대입연산자가 붙어 있으면 에러. 띄어쓰기 해야함.
for( i in 0..3) {
    println( list.get(i) ) // .get(인덱스번호)메소드를 통해 요소의 값 얻어오기
}

//**출력** 
10
20
30
20    //중복 데이터 존재 확인

    

리스트는 저장된 순서대로 저장되어 있다는 것과 [1]번 방과 [3]번 방에 중복된 값 20이 저장될 수 있다는 것을 확인해 봤습니다.

 

값의 추가/삭제/변경에 관련된 기능메소드는 없습니다.

//값의 추가/삭제/변경에 관련된 기능메소드가 없음
list.add(20)      //error - add()메소드 없음
list.remove(0)    //error - remove()메소드 없음
list.set(1, 200)  //error - set()메소드 없음

 

2.1.2) Set - 요소가 순서대로 저장되어 있지 않음. 인덱스번호 없음. 중복데이터 불허

val set:Set<Double> = setOf(3.14, 5.55, 2.22, 5.55, 1.56) //중복데이터[5.55]는 자동으로 저장안됨.
for( e in set) println(e)  

//**출력**
3.14
5.55
2.22
1.56
//중복 데이터 5.5가 저장되어 있지 않음

 

Set은 중복된 데이터를 저장하지 않도록 저장해야 할때 사용합니다. 앱개발로 예를 든다면 중복되지 않는 식별 key값들을 저장하거나 안드로이드의 블루투스 장치들의 리스트를 받을 때 Set을 사용됩니다. 주변 블루투스 장치들을 찾는 방식이 전파를 쏘고 돌아오는 신호를 수신하여 정보를 취득하는 것인데 전파의 전반사로 인해 같은 기기에 도달한 전파신호가 여러개일 수도 있습니다. 이럴때 같은 기기 정보를 저장하지 않는 가장 좋은 방법이 Set 구조로 저장하는 것입니다. 그래서 실제 android 의 블루투스 디바이스 장치 목록은 Set으로 받아오도록 만들어져 있습니다.

 

2.1.3) Map - 요소가 순서대로 저장 안됨. [키,벨류]쌍으로 요소 저장. 별도 지정 key로 요소 식별. 중복 key 불허, 중복데이터 허용

코틀린의 Map 사용은 키-벨류 쌍을 어떤 방식으로 만드는지에 따라 2가지 방법이 있습니다. 기능은 똑같기에 본인 편하신걸 사용하시면 됩니다.

[1] Pair()객체를 이용한 키-벨류 지정

val map:Map<String, String> = mapOf( Pair("title","Hello"), Pair("msg","nice to meet you.") ); //Pair()객체를 이용하여 [키,벨류] 쌍으로 요소 추가
println("요소개수 : ${map.size}")  //요소 개수 확인해보기
for ( (key, value) in map){      //키와 벨류값을 받아오는 for-in .. (key, value) 소괄호 필수. 순서중요
    println("$key : $value")
}

//**출력**
요소개수 : 2
title : Hello
msg : nice to meet you.

 

[2] Pair()객체 대신에 to 연산자 이용하여 키-벨류 지정

val map2:Map<String, String> = mapOf( "id" to "mrhi", "pw" to "1234" )  // key to value
for( (k, v) in map2) println( "$k : $v")  // (키,벨류)의 변수명은 원하는 단어로 변경가능

//**출력**
id : mrhi
pw : 1234

 

 

2.2) 요소개수의 추가/삭제 및 변경이 가능한 mutable 컬렉션  : mutableListOf(), mutableSetOf(), mutableMapOf()

 

2.2.1) MutableList

val aaaa:MutableList<Int> = mutableListOf(10,20,30)  // 요소개수 3개짜리 리스트객체 생성
println("요소개수 ${aaaa.size}")    //출력: 요소개수 3

 

요소추가  [ 가장 마지막에 추가 - 3번방에 40 추가]

aaaa.add(40)


특정 위치에 추가 가능 [ 0번방에 50 추가 ]

aaaa.add(0,50)


추가된 요소의 개수 확인해보기 

println("요소개수 ${aaaa.size}")  //출력: 요소개수 5

 

특정 요소값 변경

aaaa.set(1,200)   //1번방에 200 설정


코틀린은 리스트의 특정 요소값 변경을 set()메소드 대신에 배열처럼 [] 인덱싱 방법을 권장함

aaaa[1] = 200   //1번방에 200 설정


지금까지 추가하고 변경한 리스트 요소값들을 반복문으로 확인해 보겠습니다.

for( e in aaaa) println(e)

//**출력**
50
200
20
30
40

 

2.2.2) MutableSet

val bbbb:MutableSet<Double> = mutableSetOf<Double>()  //셋의 초기 요소개수를 0개로 생성할 수 있음
println("요소개수 ${bbbb.size}")  //출력: 요소개수 0

 

요소 추가 - 중복데이터도 추가해보기

bbbb.add(5.55)
bbbb.add(3.14)
bbbb.add(5.55) //중복데이터는 자동 무시

println("요소개수 ${bbbb.size}")  //출력: 요소개수 2
for( e in bbbb) println(e)

//**출력**
5.55
3.14

 

2.2.3) MutableMap

val cccc:MutableMap<String, String> = mutableMapOf("name" to "sam", Pair("tel", "01012345678"))  //to연산자와 Pair()를 동시에 사용해도 문제없음
println("요소개수 ${cccc.size}")  //출력: 요소개수 2

 

요소 추가하기 [ 키-"addr", 벨류-"seoul" ]

cccc.put("addr", "seoul")
println("요소개수 ${cccc.size}")  //출력: 요소개수 3

 

반복문으로 키-벨류 를 얻어와서 출력해보기

for( (k,v) in cccc) println( "$k : $v")

//**출력**
name : sam
tel : 01012345678
addr : seoul

 

※ 보통 개발자가 Collection 을 사용한다는 것은 유동적으로 요소값들을 제어하는 경우가 대부분이기에 처음 사용하실 때는 특별한 사유가 없다면mutableXXXOf()로 만드는 것을 추천합니다. 다만, 조금 익숙해지셨다면 추후 서버나 DB 데이터를 load하여 UI로 뿌려 주는 등의 작업을 할때 요소의 개수나 값이 실수로 변경되는 것을 방지하기 위해 mutable이 아닌 리스트를 사용하시는 것이 좋습니다. 추후 관련하게 소개하도록 하겠습니다.

 


 

♣ 별외. mutable이 익숙지 않다면 자바의 ArrayList, HashSet, HashMap 에 대응하는 클래스가 존재합니다.

 

자바개발자들에게 편의를 제공하는 클래스이기에 코틀린을 사용하시겠다면 코틀린 특성에 맞게 설계된 Mutable 컬렉션 사용을 권장합니다. 하여 코드 소개는 가급적 간결하게 주석으로만 소개하여 이런게 있구나 정도로 살펴보고 넘어가도 될 것 같습니다.

// Java의 ArrayList 같은 배열
val arrList:ArrayList<Any> = arrayListOf<Any>(10, "Hello", true)
for( e in arrList) println(e)
println()
/**출력**
10
Hello
true
*******/


//요소 추가 및 삭제
arrList.add(20)
arrList.add(3.14) //원래 있던 자료형이 아닌 것이 추가되면 에러였으나 이제 가능함
arrList.forEach { println(it) } //리스트객체의 forEach 기능사용하기 [한번 해보기]
println()
/**출력**
10
Hello
true
20
3.14
*******/

//특정 위치에 추가하기
arrList.add(0, "Nice")
for( e in arrList){
    println(e)
}
println()
/**출력**
Nice
10
Hello
true
20
3.14
*******/

//인덱스번호로 지우기
arrList.removeAt(0)
for( e in arrList){
    println(e)
}
println()
/**출력**
10
Hello
true
20
3.14
*******/

//요소값으로 지우기
arrList.remove(3.14)
for( e in arrList){
    println(e)
}
println()
/**출력**
10
Hello
true
20
*******/


//요소개수나 set(), get()은 동일하게 존재함
println("요소개수 : ${arrList.size}")
arrList.set(0,20) //코틀린은 set()보다는 아래처럼 []인덱싱을 이용하여 설정하는 방식을 권장함
//arrList[0]=20
println("0번요소의 값 :  ${ arrList.get(0) }") //변수명이 아니라 함수를 호출하는 것도 가능함
//println("0번요소의 값 :  ${ arrList[0] }") //코틀린은 []인덱싱 방식을 권장함
println()
/**출력**
요소개수 : 4
0번요소의 값 :  20
********/


val hashSet: HashSet<Any> = hashSetOf(100, "Good", false)
hashSet.add(200)
hashSet.add("Good") //중복데이터 자동 무시
println("요소개수 : ${hashSet.size}")
for( e in hashSet) println(e)
println()
/**출력**
요소개수 : 4
200
100
Good
false
********/

val hashMap: HashMap<String, String> = hashMapOf("apple" to "사과", Pair("house", "집"))
hashMap.put("car","자동차")
hashMap.put("car","차") //같은 key 를 사용하면 마지막 값으로 변경
for ( (k,v) in hashMap) println("$k : $v")
println()
/**출력**
apple : 사과
house : 집
car : 차
*********/

 

다시 말씀드리지만 자바에만 익숙한 개발자라면 기존의 Collection 클래스로 만들어도 무방합니다.그렇지 않다면 mutableXXX 로 사용하는 것을 권장합니다. (언어의 고유특징에 맞게 설계된 개발방법을 권장함)

 


 

3) 2차원 배열 및 리스트

코틀린의 2차원 배열로 자바처럼 1차원 배열객체 여러개를 요소로 가진 배열로 만들게 됩니다.

val arrays= arrayOf( arrayOf(10,20,30),  arrayOf("aa", "bb"), arrayOf(true, false))  //1차원배열 3개를 가진 2차원배열 객체 1개 생성

 

2차원 배열이기에 요소값에 반복 접근하는 것은 중첩 for 문을 사용해야 합니다.

for( ar in arrays){
    for( e in ar){
        print(e)
        print("   ")
    }
    println()
}
println()

//**출력**
10   20   30   
aa   bb   
true   false

 

리스트도 2차원 리스트가 가능합니다.

val arrays2= mutableListOf<MutableList<Int>>( mutableListOf<Int>(10,20,30), mutableListOf<Int>(100,200,300,400) )
println("2차원배열 arrays2의 사이즈 : ${arrays2.size}")  //2차원배열 arrays2의 사이즈 : 2

 

2차원 리스트 안에 요소인 1차원 리스트의 개수들 확인해보기

for( i in 0 until arrays2.size ){
   
}

or

for ( index in arrays2.indices ){   //인덱스 번호들 얻어오기
   println(" arrays[$index] 리스트요소의 사이즈 : ${arrays2[index].size}  ")
}

//**출력**
 arrays[0] 리스트요소의 사이즈 : 3  
 arrays[1] 리스트요소의 사이즈 : 4

 

새로운 1차원 리스트를 추가해보기

arrays2.add(  mutableListOf(1000,2000) )
for( li in arrays2 ){
    for( e in li){
        print("$e, ")  //1차원 배열의 요소값 출력
    }
    println()
}
println()

//**출력**
10, 20, 30, 
100, 200, 300, 400, 
1000, 2000,

 

 

4) Null 값 저장을 무시하는 Collection : xxxOfNotNull 

- [String? : nullable 자료형은 추후 추가로 소개 : null을 저장할 수 있는 자료형]

val aaaaa:List<String?> = listOf(null, "nice")
for ( e in aaaaa ) println(e)
println()
/**출력**
null
nice
********/

val aaaaa2:List<String?> = listOfNotNull(null, "aaa") //null값 저장이 자동으로 무시됨
for( e in aaaaa2) println(e)
println()
/**출력**
aaa
********/

val bbbbb: Set<String?> = setOfNotNull(null,"good"); //null 무시
for( e in bbbbb) println(e)
println()
/**출력**
good
********/

// map 방식은 NotNull 관련 함수가 없음.
//val ccccc: Set<String?> = mapOfNotNull(); //존재하지 않음

// mutableXXX()도 NotNull 관련 함수가 없음.

 

반응형
반응형

이번 글에서는 코틀린에서 연산자, 조건문, 반복문을 사용하는 문법을 소개하도록 하겠습니다.

자바언어에서 연산자, 조건문, 반복문을 모두 학습하고 사용해 본 상태일테니 가급적 코틀린에서 특이하게 변경된 문법만 살펴보는 형식으로 글을 작성하겠습니다.

 

새로운 코틀린 파일 Test03Basic3.kt 를 만들고 문법을 알아보겠습니다.

프로그램의 시작인 main() 함수까지 작성하겠습니다.

 

3. 연산자 - 코틀린에서 달라진 것들

기본적으로 코틀린은 자바의 문법적 표기법만 함수형언어 형식의 모던 코딩 방식으로 변경한 것인만큼 사용하는 연산자는 별로 차이가 없습니다.

자바와 마찬가지로. 산술연산자, 비교연산자, 논리연산자, 비트연산자, 증감연산자, 복합대입연산자, 형변환연산자를 가지고 있습니다. 단, 자바와 다르게 삼항(조건)연산자는 문법적으로 제공하지 않습니다. 다음에 소개할 if 조건문법이 이를 대체합니다. 

 

이제 연산자를 사용할 때 자바와는 다른 몇가지 특이한 점을 살펴보겠습니다.

 

1) 숫자타입들간의 연산은 자동 형변환이 수행됨[작은것->큰것]

println(10 + 3.14)  //출력: 13.14    - Int + Double ==> 10.0(Dobule) + 3.14(Double)

 

2) 숫자타입이 아닌 자료형과는 자동 형변환이 수행되지 않음. [Boolean, Char]

println(10 + true) //ERROR   - Int + Boolean (X)
println(10 + 'A') //ERROR    - Int + Char (X)  ~ 자바와 다르게 Char타입은 정수 65로 인식되지 않음

 

3) 자료형을 체크해주는 연산자 is

var n4 = 10    //자동추론
if (n4 is Int) {   //Int가 맞으면 true
    println("n4변수는 Int타입 입니다.")
}

var s3: String = "Hello"
if (s3 is String) println("s3변수는 String 타입입니다.")  //String 맞기에 true

 

String 과 String? 는 같은 타입의 종류입니다. 그래서 String? 도 true로 결과를 인식함

if (s3 is String?) println("s3변수는 String 타입입니다.")

 

자료형이 맞지 않는 가를 체크하는 !is 도 있습니다.

if( s3 !is String) println("s3변수는 String 타입이 아닙니다.")

 

변수의 자료형과 다른 자료형을 is연산자로 체크하면 문법적으로 에러를 표시함

if( n4 is String){ } //ERROR  - Incompatible types: String and Int

 

결국 같은 자료형에 대해서만 타입을 체크할 수 있다는 건데요. 그렇다는 건 변수의 자료형을 알고 있다는 것이겠네요.

그럼 큰 의미는 없겠군요?? 
그렇지 않습니다. 사실 is연산자는 Any타입에 대한 식별로 많이 사용됩니다.

var obj:Any    //Any타입이기에 어떤 데이터도 참조 가능

obj= 10   //Int 데이터

// Any타입 변수에 저장된 데이터의 타입을 체크
if(obj is Int) println( "${obj}는 Int입니다.")
if(obj is Double) println( "${obj}는 Double입니다.")

//결과 : 10는 Int입니다.

 

이번에는 obj에게 실수형 데이터를 대입해 보고 체크해 보겠습니다.

obj= 10.5  //Double 데이터 
if(obj is Int) println( "${obj}는 Int입니다.")
if(obj is Double) println( "${obj}는 Double입니다.")

//결과 : 10.5는 Double입니다.

 

Any타입에는 어떤 자료형도 참조하는 것이 가능하기에 실제 참조하는 데이터 또는 객체의 종류에 따라 대응하는 기능을 사용해야 할때 많이 사용하는 연산자 입니다.

 

이 is 연산자는 자바의 instanceof같은 기능으로도 사용이 가능합니다.

 

4) 자바의 instanceof같은 기능으로 사용하는 is 연산자

코틀린에서의 클래스와 객체에 대한 소개는 아직 진행전이지만 연습을 위해 이름(name)과 나이(age)를 저장할 수 있는 Person 클래스를 설계해보겠습니다. 자바와 다른 문법적 차이는 주석으로 간략하게 소개하겠습니다.

class Person{
    //코틀린에서는 멤버변수를 속성[Property]라고 명명함.
    //주의!! 프로퍼티는 반드시 초기화가 되어 있어야 함.
    var name:String = "sam"
    var age= 20
}

 

이제 Person 객체를 생성하고 is 연산자가 사용되는 모습을 살펴 보겠습니다.

var p= Person()   //코틀린에서는 객체를 생성하는 키워드 new 를 명시하지 않음
if(p is Person){
    println( p.name + "  " + p.age )  //출력: sam 20   
}

 

멤버변수의 출력을 문자열탬플릿 으로 하면 출력서식을 만들기 조금 더 용이합니다.

var p= Person()
if(p is Person){
    println( p.name + "  " + p.age )
    println( "이름 : ${p.name}    나이: ${p.age}")
}

//**출력**
이름 : sam   나이: 20

 

is 연산자 사용의 특이한 점이 있습니다. is연산자로 특정 타입이라는 것이 확인되었다면 true인 영역안에서는 Any 타입 변수가 그 타입으로 인식됩니다.


Any타입에 Person객체로 인식하여 사용하기

var obj2:Any
obj2= Person()
obj2.name="aaa" //에러는 아니지만 Android Studio편집기에서 name변수의 존재를 알려주지 않음. [다만, 업캐스팅 되어 있다면 .연산자로 자식객체의 멤버를 강제로 명시하여 사용이 가능함(자동 형변환 - 권장하지 않음)]

 

Android Studio 편집기의 입장에서는 obj2 변수는 Any타입이기에 . 연산자를 썼을때 멤버리스트의 존재를 실제 참조하는 Person객체가 아니라 Any 클래스로 지원합니다. 개발자의 입장에서 객체의 멤버명을 모두 외워서 사용하기 어렵기에 편집기의 코드 어시스턴스 기능을 이용하여 코딩하는 경우가 많기에 Any 참조변수가 참조하는 실제 참조객체의 멤버를 사용할 때는 실제 참조객체의 참조변수를 만들어 다운캐스팅으로 형변환한 후 멤버를 사용합니다. 꽤 번거롭습니다.

if(obj2 is Person){  // is의 참 영역 안에서는 obj2가 Any타입이 아니라 Person타입의 참조변수인것으로 편집기가 인식함.
    println( obj2.name + "  " + obj2.age )
    println( "이름 : ${obj2.name}    나이: ${obj2.age}")
}

 

5) 비트 연산자를 대체하는 메소드와 표기법

코틀린은 비트연산자가 없습니다. 대신 비트연산자에 대응하는 메소드가 있으며 가독성이 좋은 표기법도 제공합니다.

println( 7 | 3)             //ERROR -    | ( OR 비트연산자) 없음
println( 7.or(3) )          // OR 비트연산에 대응하는 메소드가 Int 타입에 존재함
println( 7 or 3 )           // OR 메소드를 가독성 좋게 마치 연산자처럼 표기하는 것이 가능함.

println( true and false )   // & AND 비트연산에 대응하는 and 연산자

//println( 3.14 and 5.55 )  //ERROR - Double 타입은 비트연산 안됨
//println( "aaa" or "bbb" ) //ERROR - String 타입은 비트연산 안됨

 

 

4. 조건문 - 코틀린에서 달라진 것들

코틀린의 조건문 종류 : if, when [ switch 문법이 없음 ]

 

1) 삼항연산자를 대체하는 if 문법

var str= (10>5)? "Hello" : "Nice" //ERROR : 삼항연산자 문법이 없음

 

위 코드는 에러입니다. 코틀린은 삼항(조건) 연산자가 없습니다. 대신 if 문법이 이를 대체합니다.

var str= if(10>5) "Hello" else "Nice"    //참이면 "Hello" 거짓이면 "Nice"
println(str)   //결과: Hello

 

삼항연산자의 ? : 이라는 기호를 쓰지않고 영문자인 if - else 문을 사용하여 보다 가독성이 좋아진 것 같습니다.

 

2)혹시 if나 else문의 실행문이 여러줄이면 마지막 실행문 값이 변수에 대입됨

str= if(10<5){
    "zzzzz"
    "aaaaa"    //참이면 이 문자열이 str에 대입됨
}else{
    "qqqqq"
    println("ggggg") //당연히 이렇게 출력문 같은 코드가 있어도 됨.
    "bbbbb"    //거짓이면 이 문자열이 str에 대입됨
}

println(str)  

//**출력**
ggggg    <-- else{} 영역안에 있는 println("ggggg") 실행문에 의한 출력
bbbbb    <-- 조건문 밖의 println(str) 실행문의 의한 출력

 

str변수의 자료형이 String 이기에 조건문 영역의 마지막 실행문 값이 문자열이 아니면 에러가 발생합니다.

str= if(10>5){
    "zzzzz"
    "aaaaa"
}else{
    "qqqqq"
    println("ggggg") //당연히 이렇게 출력문 같은 코드가 있어도 됨.
    "bbbbb"
    println("ggggg") //ERROR - 마지막이 있으면 당연히 에러 [ str의 자료형이 String이어서 에러임. Any였다면 에러 아님. 참고로 println()함수의 자료형은 Kotlin.Unit 타입임]
}

 

3) if 표현식 - 연산식처럼 특정 결과값을 준다고 하여 if 표현식 이라고 부름

위 코드처럼 if문이 마치 3+5 라는 연산식의 결과값 8을 주듯이 특정 결과값을 준다고 하여 코틀린에서는 if문 대신에 if표현식이라고 부릅니다.  

 

4) switch 문법이 없어지고 when 문법이 이를 대체

var h:Any?= null

//문법이 없어서 switch를 쓰면 에러
//switch(h){   }  //ERROR

 

h=10
when(h){   //h값으로 분기
    10-> println("aaa")   //h가 10일때 실행
    20-> println("bbb")   //h가 20일때 실행
}

 

분기되는 값들의 자료형이 달라도 에러가 아닙니다.(자바와 다름)

h=10
when(h){
    10-> println("aaa")
    20-> println("bbb")
    //자료형이 달라도 상관없음
    "Hello"-> println("Hello")
    true-> println("true")
}

 

분기되는 위치에 변수가 있어도 됩니다.

var nn:Int= 100   //정수형 변수 nn 선언
h=10
when(h){
    10-> println("aaa")
    20-> println("bbb")
    //자료형이 달라도 상관없음
    "Hello"-> println("Hello")
    true-> println("true")

    //변수가 있어도 됨 (첫줄에 있는 변수 nn이 100을 가지고 있음)
    nn-> println("100을 가지고 있습니다.")
}

 

분기되는 위치에 2개 이상의 값을 묶을 수도 있습니다.

var nn:Int= 100   //정수형 변수 nn 선언
h=10
when(h){
    10-> println("aaa")
    20-> println("bbb")
    //자료형이 달라도 상관없음
    "Hello"-> println("Hello")
    true-> println("true")

    //변수가 있어도 됨 (첫줄에 있는 변수 nn이 100을 가지고 있음)
    nn-> println("100을 가지고 있습니다.")
    
    //2개이상의 조건을 묶을 수 있음
    30,40-> println("30 or 40 입니다.")
}

 

어떤 조건값에도 해당하지 않을 때에 사용하는 switch 문의 default 역할은 else 키워드로 적용할 수 있습니다. 실행문을 여러줄 사용하고 싶다면 { .. } 영역표시를 하면 됩니다. 

var nn:Int= 100   //정수형 변수 nn 선언
h=10
when(h){
    10-> println("aaa")
    20-> println("bbb")
    //자료형이 달라도 상관없음
    "Hello"-> println("Hello")
    true-> println("true")

    //변수가 있어도 됨 (첫줄에 있는 변수 nn이 100을 가지고 있음)
    nn-> println("100을 가지고 있습니다.")
    
    //2개이상의 조건을 묶을 수 있음
    30,40-> println("30 or 40 입니다.")
    
    //switch문의 default역할 : 실행할 코드가 여러줄이면 {}로 묶어서...
    else->{
            println("ccc")
            println("end")
    }
}

 

when도 if문처럼 표현식이라서 결과를 변수에 저장하는 것이 가능함

h=20
var result= when(h){
    10->"Hello"
    20->"Nice"
    else->{
        println("else")
        "BAD"
    }
}
println(result)  //출력: Nice

 

when에 is 연산자 키워드 사용도 가능합니다.

when(h){
    is Int -> println("Int 타입입니다.")        //h의 자료형이 Int면 분기되어 실행
    is String -> println("String 타입입니다.")  //h의 자료형이 String이면 분기되어 실행
    else -> println("else")                  //Int, String 이 아닐때 실행
}

 

when을 특정 수식으로 제어하고 싶을 때 사용하는 방법도 있습니다. 주의할 것은 when()에서 ()를 생략하며 분기 대상을 미리 지정하지 않는다는 겁니다.

h=85
when{    //()가 생략되어 있음!!
    h>=90 && h<=1000-> println("A학점 입니다")
    h>=80 -> println("B학점 입니다.")
    h>=70 -> println("C학점 입니다.")
    h>=60 -> println("D학점 입니다.")
    else -> println("F학점 낙제 입니다.")
}

 

range 문법( 점이 3개)으로 위 분기조건 중 && 연산 범위를 간략하게 표현할 수 있습니다. [range ...(점3개) 문법은 반복문을 통해 소개]

h=85
when{
    //h>=90 && h<=100-> println("A학점 입니다")
    h in 90..100 -> println("A학점 입니다") // 90..100 == 90~100 범위
    h>=80 -> println("B학점 입니다.")
    h>=70 -> println("C학점 입니다.")
    h>=60 -> println("D학점 입니다.")
    else -> println("F학점 낙제 입니다.")
}

 

when에 in키워드 연산자를 통해 Collections의 요소들값을 체크할 수도 있습니다. 이는 for문과 배열에 대한 소개 후 소개하도록 하겠습니다.

 

 

5. 반복문 - 코틀린에서 달라진 것들

코틀린의 반목문 종류 : while, for

 

1) while 은 특별히 다른점이 없습니다. if, when 처럼 마지막 값을 주는 표현식도 아닙니다. 기존에 사용하던 방식 그래도 사용하면 됩니다.

 

2) for문은 작성하는 방법이 완전히 다릅니다. 기존 자바문법을 그대로 사용하면 에러입니다.

for(var i=0; i<5; i++){} //ERROR - 이런 문법이 없음

 

코틀린의 반복문의 횟수를 지정하는 방법을 살펴보겠습니다.  [ 문법 :   for( 제어변수 in 범위 ) { .. }  ]

 

3) 0부터 5까지 6번 실행되는 반복문을 만들어 보겠습니다.

for(i in 0..5){ //i변수 앞에 var쓰면 에러
    println(i)
}

//**출력**
0
1
2
3
4
5

 

4) 3~10 까지 반복하기. - for안의 제어변수 i 는 지역변수이기에 위에 사용했던 변수명을 사용해도 문제없습니다.

for(i in 3   ..    10){ //..양옆의 공백은 상관없음  - 3~10
    println(i)
}

//**출력**
3
4
5
6
7
8
9
10

 

근데 자바와 for문에 익숙하시다면 알겠지만 마지막 숫자를 반복에 사용하지 않는 경우가 많지요. 이 때는 range .. 연산자를 사용하지 않습니다.

 

5) 마지막 숫자 전까지 반복하기  [ .. 대신에 until ]

for(i in 0 until 10){ // 0 ~ 9
    println(i)
}

//**출력**
0
1
2
3
4
5
6
7
8
9

 

6) 반복 할 때마다 2씩 증가하기 [ step ]

for(i in 0..10 step 2){ //0~10까지 2씩 증가
    println(i)
}

//**출력**
0
2
4
6
8
10

 

7) 반복 할 때 마다 값을 감소 [ downTo

for(i in 10 downTo 0) println(i) //10~0까지 1씩 감소

//**출력**
10
9
8
7
6
5
4
3
2
1
0

 

8) 반복 할 때 마다 값을 2씩 감소 [ downTo + step ]

for(i in 10 downTo 0 step 2) println(i) //10~0까지 2씩 감소

//**출력**
10
8
6
4
2
0

 

♣ 기타 제어문 - break, continue

기본적인 break, continue 의 사용문법은 자바와 차이가 없습니다.

 

1) 0부터 5까지 반복 중에 3이 되면 반복문 종료(break)

for (n in 0..5){
    if(n==3) break
    print("$n    ")   //반복할 때 마다 제어변수 n 출력 - 줄바꿈 안함
}

//**출력**
0    1    2

 

2) 중첩 반복문의 안쪽 반복문에서 break 사용시 안쪽 반복문만 종료  

for(y in 0..5){         //0~5까지 6번 반복 
    print("$y : ")      //제어변수 y 값 출력 [ 문자열 탬플릿 : 문자열 " "안에서 $y 로 변수 인식 ]
    for(x in 0..10){    //0~10까지 11번 반복
        if(x==6) break  //6이 되면 안쪽 반복문만 멈춤
        print("$x    ") //제어변수 y 값 출력
    }
    println()
}

//**출력** 
0 : 0    1    2    3    4    5
1 : 0    1    2    3    4    5
2 : 0    1    2    3    4    5
3 : 0    1    2    3    4    5
4 : 0    1    2    3    4    5
5 : 0    1    2    3    4    5

 

바깥쪽 for 문 y는 0부터 5까지 변함없이 반복하고 안쪽 for문만 종료되는 것을 볼 수 있습니다. 

근데 경우에 따라 안쪽 for 문의 특정 조건이 되었을 때 바깥쪽 for 문까지 종료하여야 하는 경우도 필요할 수 있습니다. 코틀린은 이런 때 사용하기 위해 종료하는 반복문의 위치를 식별할 수 있도록 표식을 남길 수 있습니다. 이를 Label 이라고 부릅니다.

 

3) 반복 종료 지점을 표시하는 Label : @로 종료위치 선택

KKK@ for(y in 0..5){        //for의 지점에 KKK 라벨 지정
    print("$y : ")
    for(x in 0..10){
        if(x==6) break@KKK  //"KKK" Label의 for문을 break
        print("$x    ")
    }
    println()
}

//**출력**
0 : 0    1    2    3    4    5      //바깥쪽 KKK반복문이 종료되어 전체 반복 종료

 

Label 을 소개해 드리긴 했지만 개발자들은 이를 반복문에서는 잘 사용하지 않습니다. 코드의 순서 흐름을 따라가기 혼란스러워 선호하지 않는 편이고 이런 상황에서는 함수나 return 을 이용하여 해당 기능을 구현하는 것이 일반적입니다.

그럼 Label은 굳이 익힐 필요가 없겠네요? 그렇지 않습니다.

@로 표시하는 라벨은 컴파일러에게 해당 키워드가 누구인가를 지정할 때 매우 많이 사용합니다. 대표적으로 이너클래스에서 아웃터클래스를 지칭할 때 사용던 아웃터클래스명.thisthis@아웃터클래스명 으로 사용합니다. 코틀린 앱 개발 코딩 중에 등장할 때 다시금 살펴보고 지금은 Label 이라는 문법이 특정 위치나 대상을 지칭하는데 사용하는 문법이라는 것만 대략적으로 기억해 두시기 바랍니다.

 

코틀린 for 문법의 또 다른 사용법은 배열이나 컬렉션을 진행하면서 추가로 소개하도록 하겠습니다.

반응형
반응형

♣ **** 화면 출력의 format 만들기 ****

 

- 자바처럼 문자열 결합 연산자 + 사용가능합니다.

println("Hello" + " Kotlin") //출력: Hello Kotlin

 

- 하지만 Number타입에서 String타입으로 자동 형변환은 이루어지지 않습니다.

println( 10 + "Hello") //ERROR

 

- 그래서 Number자료형을 String자료형으로 변환하여 출력해야 합니다. [ 기초타입의 형변환 기능 .toXXX() 사용 ]

println(10.toString() + " Hello")  //"10"+" Hello" -->  출력: 10 Hello

 

- 특이한 점은 문자열이 먼저 있다면 자동 형변환이 되어 덧셈이 가능해 집니다. 특이하죠.

println("Hello" + 10)  //"Hello" + "10"  -->  출력: Hello10

 

- 즉, 두 변수의 값을 덧셈하여 50 + 30 = 80 모양의 출력 코드를 작성해보면.

var nnn1 = 50
var nnn2 = 30
//println( num1 +" + "+ num2 +" = " + num1+num2) //ERROR - 이렇게 작성할 수 없음. [숫자가 처음이라서]
println(nnn1.toString() + " + " + nnn2.toString() + " = " + (nnn1 + nnn2))//이렇게 형변환 하거나
println("" + nnn1 + " + " + nnn2 + " = " + (nnn1 + nnn2)) //이렇게 "" 빈 문자열을 먼저 써서 덧셈 해아함.

 

복잡해 보이네요. 위 2가지 해결 방법 모두 가독성이 떨어지고 실수의 여지가 많습니다. 이를 위해 제공되는 문법이 있습니다.

 

- 문자열 탬플릿 문법 : 문자열 "" 안에서 변수명을 인식하는 문법

println("  $nnn1  +  $nnn2   =   ${nnn1 + nnn2} ") //이렇게 " " 사이에 $키워드로 변수명임을 표시하고 사용하는 방식 사용

 

이렇게  $변수명 을 사용하는 것을 [문자열 탬플릿] 이라고 부름

 

- 즉, 이렇게 코드를 작성하면 코드의 가독성이 더 좋아 질 겁니다.

var name = "sam"
var age = 20
println("이름 : $name \n나이 : $age")

// *출력*
//이름 : sam
//나이 : 20

 

반응형
반응형

2. 자료형과 변수

코틀린언어의 변수는 자바 언어와 만드는 방법이 완전히 다릅니다. 다소 적응하는 시간이 걸릴 수는 있지만 몇번 해보면 나쁘지 않다는 것을 느낄 수 있으실 겁니다.

 

데이터를 저장하는 변수를 만드려면 데이터의 유형에 따른 자료형을 먼저 알아야 겠지요. 즉, 데이터를 음식으로 비유하면 음식의 종류에 따라 어울리는 그릇의 종류를 선택해야합니다. 고등어는 접시에, 물은 컵에 담아야 겠지요. 음식에 맞는 그릇의 종류가 있듯이 데이터의 유형에 따라 이에 맞는 변수의 종류(자료형)을 먼저 알아야 합니다. 

 

코틀린의 자료형도 자바와 마찬가지로 크게 2가지 종류로 나뉩니다. 논리값, 정수, 실수 데이터를 저장하는 목적의 기초(primitive)타입과 객체를 제어하기 위한 참조값을 저장하는 참조(reference)타입이 있습니다.

 

* 코틀린 자료형의 종류
1) 기초 타입 : Boolean, Byte, Char, Short, Int, Long, Float, Double  [기본적으로 Kotlin은 모든 변수가 객체임. 즉, 모두 참조변수임]
2) 참조 타입 : String, Any(Java의 Object와 비슷), Unit ...  그외 Kotlin APIs, Java APIs

                      -Boolean, String, Any, Char은 숫자타입[Number Type] 이 아님!!!

 

자바와 비슷한 듯 약간 차이가 있습니다. 일단 기초타입 자료형의 개수는 8개로 똑같습니다. 논리값부터 실수형 타입까지의 종류도 차이가 없습니다. 근데 자료형의 첫글자가 대문자입니다. 보통 첫글자가 대문자라는 것은 객체의 자료형인 class로 만들어진 참조형 자료형을 의미하지요. 즉 객체라는 거네요?

네. 맞습니다. 코틀린은 자바와 다르게 기초타입도 객체로 만들어서 참조하여 다룹니다. 혹시, 자바의 Wrapper 클래스를 아시나요? int형 정수값을 객체로 감싸주는 Interger, double형 실수값을 객체로 감싸주는 Double 같이 기초타입에 대응되는 8개의 클래스들을 감쌌다고 해서  Wrapper 클래스라고 불렀지요. 문자열을 해당 기초타입으로 분석하여 변환해주는 parseXXX()같은 기능메소드를 보유하고 있으며 List와 같은 Collection의 제네릭타입으로 지정하기 위해 사용하는 클래스가 wrapper클래스 였습니다. 이 wrapper클래스의 참조변수는 정수, 실수 같은 값(value) 데이터를 대입하면 자동으로 Interger객체로 wrapping 해주는 auto boxing 기능이 있으면 반대 기능도 보유하고 있습니다.

코틀린의 기초타입인 Int, Double 타입도 이와 유사한 오토박싱과 언박싱 기능이 존재하여 문자열을 해당 자료형으로 변경해주는 변환 기능메소드도 가지고 있습니다. 이에 대한 내용은 이어지는 변수 사용 문법을 소개하면서 알아보겠습니다.

 

코틀린의 참조타입은 자바에서 제공하던 모든 API 클래스들도 사용할 수 있지만, 자바를 대체하고자 만든 언어인 만큼 Kotlin API 클래스들이 제공됩니다. 문자열 타입의 경우 자바 API 인 java.lang.String 을 대신하여 kotlin.String 이 제공됩니다. 이 두개의 String은 생성문법 및 보유 메소드에서 약간의 차이가 있지만 개발자가 사용하는 대부분의 메소드는 동일하기에 추가로 시간을 들여 학습하기 보다는 필요에 따라 그때그때  검색하면서 습득하기를 추천드립니다. 이렇게 기존 자바의 클래스들을 대체하는 목적의 클래스들이 존재합니다. 코틀린에서 기존 자바의 클래스를 사용해도 동작은 잘 되지만 코틀린언어의 특징을 반영하여 만든 대체용 클래스들을 사용하는 것이 좋겠죠. 앞으로는 가급적 코틀린용 클래스들을 사용해보시기 바랍니다. 또한 Any, Unit 과 같이 몇가지 완전 새로운 타입도 추가되었습니다.

일단 기본 변수선언 문법을 익히기 위해 참조타입은 나중에 차차 소개하고 기초타입의 변수 학습을 진행하면서 소개하도록 하겠습니다.


 

자. 이제 코드를 따라 작성해 보면서 각 문법들의 의미를 습득해 볼까요?

 

새로운 .kt 파일을 만들어서 실습을 해보겠습니다. 이전 글을 통해 android studio에서 코틀린파일을 만드는 것을 해봤으니 하실 수 있겠죠? 혹시나 해서 별도의 파일의 생성하는 new >> Kotlin Class/File 메뉴를 통해 새로운 .kt 파일을 만들어 보겠습니다.

 

2번째 문법 소개이니 Test02Basic2 로 파일명을 정하여 만들겠습니다. 객체지향문법이 아니기에 Class가 아니라 File 입니다.  

 

만들어진 모습은 아래와 같습니다. 패키지명만 써있는 빈 .kt 문서 입니다.

코틀린 프로그램의 시작은 main()함수이니 먼저 작성해 놓고 자료형과 변수에 대한 문법을 알아보겠습니다.

 

새로 만든 Test02Basic2.kt 문서만 따로 실행할 수 있도록 문서의 빈 공간에서 마우스 우클릭하여 나타난 팝업 메뉴에서 해당 문서의 Run메뉴를 실행하면 안드로이드 스튜디오 상단메뉴의 run 메뉴 툴바의 대상이 아래그림처럼 Test02Basic2Kt로 된 것을 확인할 수 있습니다.


 

자료형을 살펴봤으니 변수를 만드는 방법에 대해 학습해 보겠습니다.

 

코틀린에서 변수를 만드는 방법은 2가지 입니다. 값을 변경할 수 있는 var변수와 값을 변경할 수 없는 읽기전용 val변수 입니다.

변수를 만드는 문법은 자바와는 많이 다릅니다. 자바의 경우 [ 자료형 변수명; ex. int age;] 으로 변수를 만들었지요. 사실 문법 자체만을 가만히 보면 age가 변수라는 표시는 명시적으로 없네요. 그냥 변수를 만드는 문법이 자료형과 변수명이라고 알고 쓰는 것 뿐이죠.

이에반해 코틀린은 조금 더 명시적이며 직관적으로 문법을 만든 것 같습니다. 변수를 만들 때 [ 변수(variable) age: 자료형 ]라는 형태로 작성합니다.

문법 var 변수명:자료형   or   val 변수명:자료형

 

예를 들어, 사용자의 이름(문자열)나이(정수)를 저장하는 변수를 자바로 만들었을 때와 코틀린으로 만들었을 때를 각각 살펴보겠습니다.

JAVA String name;      // String 문자열을 저장하는 name
ing age;              // int형 값을 저장하는 age
KOTLIN var name:String      // variable(변수) name 인데 자료형은 String 임
var age:Int               // variable(변수) age 인데 자료형이 Int 임

 

자바와 다르게 코틀린은 '나 변수 만든다!' 라는 표시로서 변수를 의미하는 variable의 앞글자만 따서 var 키워드를 사용하여 만들기에 좀더 변수임을 직관적으로 인식하게 합니다. 여러분들은 한국어를 사용하기에 그 느낌이 조금 덜 할 수 있겠지만 영어권 국가의 입장에서는 변수선언 문법이 매우 직관적으로 느껴질겁니다. 

아직 배우지는 않았지만 나중에 배울 함수 function을 만들 때도 앞글자를 따서 fun 키워드를 사용합니다. move라는 이름의 함수를 만든다면 ' fun move(){ ... } ' 문법으로 정의합니다. '나 함수 만든다!'라는 것을 직관적으로 인식하게 합니다.

개인적으로는 이런 변수 선언방법을 더 선호합니다. 

웹을 공부하신 분들이라면 다루었을 JavaScript와 비슷하긴 하지만 한번 만들어진 변수의 자료형은 변경할 수 없는 정적타입언어입니다. 

 

변수의 2가지 종류를 만들고 사용하는 것에 대해 알아보는 실습을 진행해 보겠습니다.

 

1) var [ 값변경이 가능한 변수 : variable ]

var num:Int = 10
println(num)   //결과 : 10

var num2:Double = 3.14
println(num2)  //결과 : 3.14

 

권장하지는 않지만 변수만 먼저 만들고 나중에 값을 대입해도 됩니다. [지역변수만 가능함 - 추후 추가 설명]

var num3:Float
num3= 5.23f     //float형 실수형 숫자값은 뒤에 f 키워드 필요. 없으면 double 실수 값으로 인식하여 에러
println(num3)   //결과 : 5.23

 

변수이므로 변수가 가지고 있던 값의 변경이 가능합니다. 위에서 만든 변수 num, num2, num3의 값을 대입연산자로 변경하겠습니다.

num = 20;
num2 = 20.5
num3 = 10.88F   //float을 의미하는 F는 대소문자 구분없이 사용가능
println(num)    //결과: 20
println(num2)   //결과: 20.5
println(num3)   //결과: 10.88

 

자료형이 있는 변수이므로 다른 자료형을 대입하면 ERROR 가 발생합니다.

num= 3.14  //ERROR   [Int변수에 Double대입]
num2= 50   //ERROR   [Double변수에 Int대입] - 자동형변환 없음

 

큰 자료형의 값(Double)을 작은 타입의 변수(Int)에 대입하는 것은 당연히 에러입니다.

자바의 경우 자료형이 큰 변수(Double)에 작은 타입의 값(Int)을 대입하면 자동 형변환이 되지만 코틀린은 자동형변환을 지원하지 않아서 에러가 발생하는 부분에 차이가 있습니다. 주의하시기 바랍니다. 

 

하지만 프로그램을 짜다보면 자료형을 변환하여 대입해야 하는 경우가 발생할 수도 있습니다. 이럴때 형변환 type casting 을 하는데 그 방법이 자바와는 다릅니다.

a= (Int)3.14 //ERROR 이런식의 자바 형변환 문법 없음.

 

코틀린에서 명시적으로 형변환 하는 방법은 변수.toXXX() 기능메소드로 변환이 가능합니다. 단, 기초데이터타입의 경우에만 가능합니다. 참조타입의 경우에는 as 라는 형변환 연산자가 따로 있느데 이는 추후 소개하도록 하겠습니다.

num = 3.14.toInt()        // Double --> Int
num2 = 50.toDouble()      // Int --> Double  
println(num)      //결과: 3
println(num2)     //결과: 50.0

 

자바를 해보셨던 분들은 3.14나 50 같은 기초 타입의 값에 객체의 멤버메소드를 사용하는 . 연산자를 사용하는 것이 어색할 겁니다. 

이게 자바와 다른 코틀린 기초타입이 모두 객체로 auto boxing 되어 있다는 것을 확인할 수 있는 점입니다. 지금은 일단 그렇구나 정도로 생각하시면 됩니다.

 

보통 이런 기초타입의 형변환은 문자열을 기초타입으로 변경하는 경우가 많습니다. 이 포스트 글에서는 가급적 기초타입의 변수만으로 소개하려 하였으나 형변환을 소개하기 위해 잠시 문자열 String 변수를 만들어 보겠습니다. 자바와 약간 다른 점이 존재합니다. 지금은 가볍게 보시기 바랍니다.

//문자열 String 객체
var s:String= "Hello"
println(s)      //결과: Hello

var s2:String= String("Hello") //ERROR - 단순 "문자열" 객체를 생성할때 String()생성자를 사용할 수 없음. [String() - String() 생성자는 Buffer나 byte 배열을 String객체로 생성할때만 사용함]

 

이제 문자열 String 을 숫자로 변환 해보겠습니다. [ 문자열.toXXX()로 변환 ]

자바처럼 Integer, ..., Double 같은 wrapper 클래스들을 사용하지 않습니다.

var str:String = "123"    //문자열 "123"
//var n:Int= str //error - Int변수에 String 대입 불가능

var m:Int = str.toInt()  // String --> Int 형변환 
println(m)    //결과: 123


var str2:String = "5.64"   //문자열 "5.64"   
var m2:Double = str2.toDouble()   //String --> Double 형변환
println(m2)   //결과: 5.64

 

 

 

2) val [ 값 변경이 불가능한 변수 - 읽기전용 변수 value ]  # 상수와는 조금 다른 개념입니다. 상수는 const 키워드로 만들게 됩니다. #

val n1:Int= 100
//n1=200 //error - 읽기전용 val변수이기에 값을 변경하려하면 에러발생
println(n1)   //결과: 100

val n2:Boolean= true
//n2= false //error - 읽기전용 val변수이기에 값을 변경하려하면 에러발생
println(n2)   //결과: true

 

권장하지는 않지만 변수를 만들때 값을 지정하지 않으면 한번은 대입 할 수 있습니다. - 그 값으로 정해지고 이후에는 값 변경이 불가능합니다.

val n3:String     //val 변수를 만들면서 초기화를 하지 않음.
n3= "Nice"        //이때 "Nice"로 초기화 됨
//n3= "Good" //error - 값 변경 불가능
println(n3)  //결과: Nice

 

값을 변경할 수 없다는 것을 제외하고는 var 변수와 선언방법은 차이가 없습니다. 이런 특징 때문에 상수로 오해하시는 분도 있는데 엄밀하게는 상수가 아닙니다. val 은 getter는 있지만 setter 가 없는 변수 입니다. setter가 없으니 값을 변경할 수 없는 것이라고 보시면 됩니다. getter, setter에 대한 학습이 되어 있다면 이해하실 수 있는데 학생들이나 초보자들은 쉽게 이해를 잘 못하는 개념이던군요.

지금은 아래 정도만 이해하셔도 앱 개발하는데 문제는 없으실 것 같습니다.

♣ val 과 const val 의 차이

const val : 컴파일시에 값이 설정됨

val : 런타임(실행했을 때) 값이 설정됨
문법적 차이

const val
 name:String = getName()    //ERROR   -- 컴파일 때는 함수 호출이 되기 전 이기에 초기화 불가능
const val name:String= "sam"             //OK          -- 컴파일 때 "sam"으로 초기화

val name:String = getName()               //OK          -- 런타임 때 함수 호출의 리턴 값으로 초기화

 

추후 별도의 포스트로 val 과 const val의 차이를 소개하도록 하겠습니다. 당분간은 값을 변경하지 못하는 val 만 사용하도록 하겠습니다.

 

결론으로 코틀린의 변수를 만들어야 할때, 값이 변경될 수 있는 값을 저장하려면 var 변수로 만들고 값이 변경될 일이 없을 때는 val 키워드로 만든다고 보시면 됩니다. 결국 개발자가 상황에 따라 선택하여 변수를 만들어야 한다는 건데 초보자들은 이렇게 알아서 하라고 하면 더 곤혹스로워 하더군요. "이럴땐 var, 저럴땐 val 을 사용해라." 라고 명확하게 구분되는 것을 혼란스럽지 않은 듯 합니다. 하지만, 애석하게도 이건 상황에 따라 적합성이 다르기에 명확하게 구분하여 드리기 어렵습니다. 

 

대신, 나름의 팁을 드리자면.

데이터를 가지는 변수는 var [ex. name, age, title ... ]  ~ 사용자 데이터는 변경될 여지가 있기에 ~

객체를 참조하는 변수는 val  [ex. TextView, ImageView, NotificationManager ... ] ~ 객체 참조를 변경하는 경우가 많지 않기에 ~

로 사용하시면 크게 무리없이 사용하실 수 있으실 겁니다. 

 

변수를 만드는 2가지 방법을 살펴보았는데 문법을 보면 var 변수명 뒤에 :자료형 을 쓰는게 다소 번거롭고 지저분해 보이기도 합니다. 값을 보면 자료형이 뭔지 구분되는데 굳이 변수를 만들때 자료형을 명시적으로 써야할 필요가 있을까 싶기도 하고요. 그래서 코틀린은 대입된 값에 따라 자료형을 자동으로 추론하는 자동추론 기능이 제공됩니다.

 

 

 

3) 자료형을 생략하며 변수선언이 가능 - 자료형은 자동 추론됨.

var aa=10 //Int
println(aa)

var bb=3.14 //Double
println(bb)

var cc=3.14f //Float
println(cc)

val dd=true //Boolean
println(dd)

val ee= 'A' //Char
println(ee)

val ff= "Hello" //String
println(ff)

 

주의!!! 변수선언시에 자료형 표기가 없지만 값을 대입하면서 자료형이 자동추정되어 지정된 것임. 즉, 변수는 자료형이 있는 것임.

 

자동 추론을 하는 변수를 사용할때는 반드시 선언하면서 값을 대입해야만 함.

var gg  //error   - 자료형을 추론할 수 없기에 오류
gg=10

val hh  //error   - 자료형을 추론할 수 없기에 오류
hh= "aaa"

 

※ 정수값 표기의 특이한 점. [실생활에서 숫자의 3자리마다 , 구분을 사용하듯이 값에 _문자로 구분자사용 가능 :잘 사용하지 않음]

var a3 = 5_000_000
println(a3) //출력: 5000000  출력은 그냥 원래대로 구분자없이 숫자만 보여짐

 

 

 

4) 코틀린만의 자료형 타입 - 최상위클래스

- Any 타입 

어떤 타입이든 된다는 영어표현(any : 무엇이든 종류에 상관없이, 어떤 ~가 됐든) 그래도 어떤 타입의 데이터 값도 대입이 가능합니다. 기초타입도 되고 객체참조도 가능합니다. 타입이 정해져 있지 않은 것이 편하고 좋아보이지만 실제 프로그래머들에게는 실수의 여지가 크기에 선호되지 않습니다.(자료형 예측이 어려움). Any는 타입을 특정하기 어려울 때 활용됩니다. 함수나 메소드의 파라미터 or 리턴값의 타입지정이 어려울 때 '업캐스팅'의 목적으로 사용되는 경우가 많습니다.

Java의 Object 처럼 Kotlin의 최상위클래스 입니다. 자바의 Object 개념을 알고 있다면 이해가 어렵지 않을 겁니다. 모르겠다면 어떤 값이든 참조할 수 있구나 정로로 생각해 주시고 차차 자세한 의미는 파악해 보시기 바랍니다.

var v: Any     //Any타입의 참조변수 선언 - 어떤 값도 대입 가능
v = 10   //Int값 대입 
println(v) //출력: 10

v = 3.14       //Double형 값 대입도 가능
println(v) //출력: 3.14

v = "Hello"    //String 값 대입 가능
println(v) //출력: Hello

 

 

 

5) 변수에 값을 대입할 때 특이점!! - null 안전성 [ 추후 별도로 파일을 만들어 소개할 예정입니다. - 여기서는 대략적인 특성만 소개 - ]

 

코틀린은 null 값을 저장할 수 있는 변수저장할 수 없는 변수가 구분되어 존재합니다.

 

 - 자료형을 명시하면 기본적으로 변수에 null을 저장할 수 없도록 되어 있음. 이를 non nullable 변수라고 부릅니다.

var n2: Int  = null   //ERROR
var s: String = null //ERROR

 

 

  - null값을 가질 수 있는 변수라고 표시할 수 있습니다. 자료형 뒤에 ? 표시를 붙입니다. 이를 nullable 변수라고 부릅니다.

var nn: Int? = null       // Int 일까 ? 라는 물음표를 통해 null 일수도 있다는 표식을 추가
var s2: String? = null    // String 일까 ? 라는 물음표를 통해 null 일수도 있다는 표식을 추가
println(nn)   //출력: null
println(s2)   //출력: null

 

 

 - ?가 붙은 nullable 타입 참조변수는 ?가 없는 일반적인 non nullable 타입 참조변수와 다르게 멤버를 사용할때 특별한 연산자가 필요합니다.

var bbb:String?= "Hello"    // String? 이기에 null값을 저장할 수 있는 non nullable variable
println( bbb.length )   //ERROR - null safety 연산자 필요

 

객체가 만약 null 이면 . 연산자로 접근할 객체가 없기에 그 유명한 null point exception 이 발생합니다. 자바에서는 코딩(컴파일) 과정에서 문법적 에러가 발생하지 않아 실행 중에 앱이 다운되는 문제를 야기합니다. 개발자들에게 악명높은 NPE 의 흔한 케이스 입니다. 이에 대응하기 위해 if() 조건으로 null값을 체크하는 코드나 예외처리 코드를 추가로 작성해야 하는 번거로움이 있습니다. 

 

코틀린은 이를 문법적 에러로 만들어 놓음으로서 개발자가 코딩과정에서 문제가 될 여지를 발견하게 함으로서 null값에 의한 문제를 미연에 방지하여 안전하게 null 값을 다루도록 합니다.

var bbb:String?= "Hello"   // String? 이기에 null값을 저장할 수 있는 non nullable variable
//println( bbb.length ) //ERROR - null safety 연산자 필요
println( bbb?.length )  //OK - null safety 연산자 ?.      출력: 5

 

* nullable 변수를 사용할 때 필요한 특이한 멤버접근 연산자들은 뒤에 따로 소개할 예정입니다.

 

자료형을 명시하지 않아 '자동추론'을 시키면 기본적으로 nullable 타입으로 타입이 추론됩니다.

var t = null  // --------- 자동 추론으로 Any? 로 지정됨
println(t) //출력 : null

 

 

 

♣ **** 화면 출력의 format 만들기 ****

 

- 자바처럼 문자열 결합 연산자 + 사용가능합니다.

println("Hello" + " Kotlin") //출력: Hello Kotlin

 

- 하지만 Number타입에서 String타입으로 자동 형변환은 이루어지지 않습니다.

println( 10 + "Hello") //ERROR

 

- 그래서 Number자료형을 String자료형으로 변환하여 출력해야 합니다. [ 기초타입의 형변환 기능 .toXXX() 사용 ]

println(10.toString() + " Hello")  //"10"+" Hello" -->  출력: 10 Hello

 

- 특이한 점은 문자열이 먼저 있다면 자동 형변환이 되어 덧셈이 가능해 집니다. 특이하죠.

println("Hello" + 10)  //"Hello" + "10"  -->  출력: Hello10

 

- 즉, 두 변수의 값을 덧셈하여 50 + 30 = 80 모양의 출력 코드를 작성해보면.

var nnn1 = 50
var nnn2 = 30
//println( num1 +" + "+ num2 +" = " + num1+num2) //ERROR - 이렇게 작성할 수 없음. [숫자가 처음이라서]
println(nnn1.toString() + " + " + nnn2.toString() + " = " + (nnn1 + nnn2))//이렇게 형변환 하거나
println("" + nnn1 + " + " + nnn2 + " = " + (nnn1 + nnn2)) //이렇게 "" 빈 문자열을 먼저 써서 덧셈 해아함.

 

복잡해 보이네요. 위 2가지 해결 방법 모두 가독성이 떨어지고 실수의 여지가 많습니다. 이를 위해 제공되는 문법이 있습니다.

 

- 문자열 탬플릿 문법 : 문자열 "" 안에서 변수명을 인식하는 문법

println("  $nnn1  +  $nnn2   =   ${nnn1 + nnn2} ") //이렇게 " " 사이에 $키워드로 변수명임을 표시하고 사용하는 방식 사용

 

이렇게  $변수명 을 사용하는 것을 [문자열 탬플릿] 이라고 부름

 

- 즉, 이렇게 코드를 작성하면 코드의 가독성이 더 좋아 질 겁니다.

var name = "sam"
var age = 20
println("이름 : $name \n나이 : $age")

// *출력*
//이름 : sam
//나이 : 20

 

반응형
반응형

코틀린 언어 기초문법

이제 코틀린 언어의 문법을 10단계에 걸쳐서 소개해 보겠습니다.

먼저. 코틀린언어를 작성하기 위해 별도의 파일을 만들어야 겠지요. 별도의 Project를 다시 생성할 필요는 없습니다. 현재 KotlinHello 프로젝트의 MainActivity.kt 파일이 있는 패키지에서 코틀린 파일을 만들어 실습을 진행하겠습니다.

 

1) 마우스 우클릭으로 new >> Kotlin Class/File 선택

자바언어로 개발할때는 새로운 Java 파일을 만드실 때 메뉴명을 자세히 보면 Java Class 만 되어 있는데 코틀린은 Kotlin Class/File 로 되어있을 겁니다. 어떤 차이가 있는 걸까요? 자바의 파일은 반드시 class 를 만들어야 한다는 것이고 코틀린은 class를 가진 파일도 있고 없는 파일도 있다는 겁니다. 자바문법을 이미 알고 있으시다면 이해가 조금 되실텐데 자바는 완전체 객체지향을 꿈꾸는 언어입니다. 그래서 객체만 존재해야 합니다. 그러다 보니 심지어 프로그램의 시작점을 의미하는 main()함수 조차도 별도로 존재 할 수 없어서 class 안에 존재하는 것이죠. 그게 static main() 으로 만들어야만 하는 이유이기도 합니다. 

 

이에반해 코틀린은 자바와 다르게 class 영역 밖에 변수와 함수가 존재할 수 있습니다. main()함수도 class 안에 만들지 않습니다. C언어나 Dart언어처럼요. (실은. 코틀린을 컴파일하면 class 밖에 있는 변수나 함수도 class로 묶어서 .class파일이 되긴 하지만 개발자가 코드를 작성할때는 이 부분을 신경쓰지 않아도 됩니다.)

그러다 보니 안드로이드 스튜디오의 코틀린 파일 생성 메뉴를 누르면 class를 만들것인지 그냥 파일을 만들것인지 선택하게 됩니다.

 

2) 우선 기본 문법 테스트로서 File을 만들어 실습을 진행하겠습니다. File 항목을 선택하고 파일명은 [ Test01Basic] 으로 작성합니다.

파일명을 작성하고 엔터키를 누르면 Test01Basic.kt 파일이 만들어진 것을 볼 수 있습니다. 근데 같은 코틀린파일인 MainActivity 는 뒤에 .kt 확장자가 없네요?

없는게 아니고 생략하여 보여주지 않는 겁니다. 실제 액티비티파일도 MainActivity.kt 로 만들어져 있습니다. 왜 굳이 다르게 표시할까요?

좀전에 설명했듯이 코틀린 파일은 class를 작성한 파일도 있고 없는 파일도 있습니다. 이 구별을 쉽게 하기 위해 안드로이드 스튜디오에서 여러분을 위한 제공하는 편의기능 입니다. 자세히 보면 아이콘의 차이도 확인할 수 있습니다. 클래스를 의미하는 ⓒ 가 클래스를 가진 파일을 의미합니다. 앞으로 .kt 확장자의 유무와 아이콘을 통해 만들어진 코틀린 파일의 역할이 쉽게 구별될 겁니다.

 


만들어진 Test01Basic.kt 파일은 아래 그림처럼 자바파일을 만들었을 때와 다르게 파일명과 같은 이름의 class 도 없이 오직 패키지명만 써있는 빈 문서일 겁니다. 이 빈 문서안에 코틀린 코드를 소개해 보겠습니다.

자. 이제 실습할 준비가 끝났습니다. 이제 코틀린언어의 특징과 기본문법들을 알아보겠습니다.

 

※ 안드로이드 스튜디오 IDE 사용이 아닌 코틀린 연습용 온라인 에디터 사이트를 이용하셔서 실습을 진행해도 됩니다.
[ 코틀린 연습 사이트 : https://try.kotlinlang.org ]
★ 폰이나 테블릿 처럼 안드로이드 스튜디오를 설치하기 어려운 환경에서 연습하고 싶을때 유용할 겁니다.

 

먼저 대략적인 특징을 살펴보고 시작하겠습니다.

kotlin 언어의 문법적 주요 특징!!

A. 문장의 끝을 나타내는 ; 을 사용하지 않는다. 써도 에러는 아니지만 무시된다.

B. 변수를 만들 때 자료형을 먼저 명시하지 않고 var, val 키워드를 사용한다. 단, 자료형은 존재함. 자동 형변환 안됨. 즉, 정적타입 언어임.

C. new 키워드 없이 객체를 생성함. new String() --> String()

D. 안전하게 null 을 다룰수 있는 문법을 제공한다.

E. 코틀린은 함수형 언어다. 즉, 함수를 객체처럼 생각해서 변수에 저장하고 파라미터로 넘겨주는 등의 작업이 가능함. [객체지향프로그래밍 언어가 아님]


 

0. 프로그램의 시작점 main() 함수

코틀린언어도 다른 언어와 마찬가지로 프로그램이 실행되면 가장 먼저 실행되는 main()함수가 존재합니다. 자바와는 다르게 반드시 class 안에 있을 필요가 없습니다.

 

자바와 다르게 함수를 정의할 때 리턴타입 위치에 function(함수)의 약자를 나타내는 'fun'키워드를 사용합니다. 추후 함수에 관련된 글을 작성할 때 자세히 설명하고 지금은 대략 '나 이제 함수 function 만들거다!' 라는 것을 표시하는 키워드로 보시면 됩니다.

package com.mrhi2022.ex100kotlinhello2022

// #. 프로그램의 시작함수인 main함수가 반드시 있어야 함.
// #. 함수를 정의할 때 리턴타입위치에 'fun'키워드(function의 약자) 사용
fun main(){

}

 

 

1. 화면출력 : print(), println()함수

첫번째 배울 문법은 화면출력입니다. 여러분들이 아무리 기가막힌 프로그램을 만들어도 원하는 모습으로 화면에 보여줄 수 없으면 무의미하겠죠. 그래서 일단, 자바언어를 학습할때와 마찬가지로 화면에 문자열, 숫자 등의 데이터를 출력하는 문법을 알아보겠습니다.

코틀린은 자바와 다르게 객체가 아니더라도 기능(함수, 메소드)을 별도로 호출할 수 있습니다. 그래서 출력기능 함수 print() 만 사용하면 됩니다. print()함수의 파라미터에 원하는 데이터나 데이터를 가진 변수를 전달하면 그대로 화면에 출력됩니다.

문법  print( 데이터 or 변수 )

 

간단하게 정수형 숫자, 실수형 숫자, 문자열 하나씩 출력해 보겠습니다. 

package com.kitesoft.kotlinhello

// #. 프로그램의 시작함수인 main함수가 반드시 있어야 함.
// #. 함수를 정의할 때 리턴타입위치에 'fun'키워드(function의 약자) 사용
fun main() {

    // 1. 화면출력 : print()
    print(10)
    print(3.14)
    print("Nice")

}//main function.......

 

너무 간단하네요. 이제 코드를 작성했으니 출력해 봐야 겠죠. 

주의하세요. 우리는 지금 안드로이드 앱을 개발하고 있지 않습니다. 코틀린언어의 문법을 알아보기 위해 Test01Basic.kt 문서만 별도로 만드로 실행해야 합니다. 평소 앱을 실행하기 위해 사용하는 상단메뉴의 [▷] run 아이콘버튼을 클릭하면 안됩니다. 이전 글에서 만들었던 텍스트뷰와 버튼을 보여주 던 MainActivity 화면이 실행됩니다. 우리는 현재 Test01Basic.kt 파일만 실행해야 합니다.

 

안드로이드 스튜디오에서 .kt 코틀린 파일만 별도로 실행하려면 해당 문서의 빈 공간에서에서 마우스 우클릭하거나 사이드 [project]탭의 파일 트리목록에서 Test01Basic.kt 파일에 마우스를 올리고 우클릭하여 팝업되는 메뉴에서 해당파일에 대한 run 메뉴를 실행해야 합니다. 결과는 하단 [run]탭에서 실행되어 보여집니다. 자동으로 탭이 열리면서 보여집니다.

 

실행되면 상단메뉴의 [run] 아이콘쪽이 app 모듈에서 Test01BasicKt 로 변경되어 있을 것을 확인할 수 있습니다.

 

실행되면 아래 그림처럼 하단 run 탭이 열리면서 결과가 출력되는 것을 확인할 수 있습니다.

실행결과  103.14Nice
 Process finished with exit code 0

 

첫번째 줄에 나온 103.14Nice 가 출력되는 것을 볼 수 있습니다. 프로그램을 공부하셨으니 다 아시겠죠. 별도의 줄바꿈 명령을 주지 않았기에 정수 10, 실수 3.14, 문자열 Nice 이 붙어서 출력되는 것을 볼수 있습니다. 알아보기 어렵네요.

두번째 줄의 "Process finished with exit code 0" 는 프로세스의 종료를 나타내는 것이니 신경쓰지 않아도 됩니다.

 

print()는 자동으로 줄바꿈 되지 않아 보기 어렵습니다. 출력 후 자동 줄바꿈을 하고 싶다면 println() [ print + line] 함수를 사용합니다.

문법  println( 데이터 or 변수 )

 

다양한 자료형들을 출력해 보겠습니다.

package com.kitesoft.kotlinhello

// #. 프로그램의 시작함수인 main함수가 반드시 있어야 함.
// #. 함수를 정의할 때 리턴타입위치에 'fun'키워드(function의 약자) 사용
fun main() {

    // 1. 화면출력 : print(), println()함수  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    print(10)
    print(3.14)
    print("Nice")

    // print()는 자동으로 줄바꿈 되지 않음. 자동 줄바꿈을 하고 싶다면 println() [ print + line]
    println()   //줄바꿈
    println("Hello Kotlin")
    println(10)
    println(10.5)
    println('A')
    println(true)
    println(5 + 3)   //수식을 넣으면 결과값이 출력 : 8 

}//main function.......
실행결과 103.14Nice
Hello Kotlin
10
10.5
A
true
8

 

출력이 잘 되고 있네요. 

당연하겠지만 ;  이 없으므로 한줄에 2개의 코드를 작성할 수는 없습니다. [ ;을 사이에 넣으면 구분이 되기는 하지만 권장하지 않습니다. ]

rintln(10)println(10) //ERROR

 

데이터를 가진 변수를 전달하면 변수안에 값이 출력됩니다. 변수 선언문법에 대해서는 저 아래에서 자세히 소개하고 여기서는 대략적인 사용 모습만 보겠습니다. 정수형 변수 a 와 문자열 b 를 만들고 출력해보겠습니다.

// 변수명을 전달하면 변수안에 값이 출력됨 - 변수 선언문법에 대해서는 아래에서 소개
var a:Int=10
println( a )
var b:String="Hello"
println(b)
println()
실행결과 10
Hello

 

반응형
반응형

안드로이드 앱 개발 공식언어 Kotlin 

구글에서 안드로이드의 공식언어를 코틀린(Kotlin)으로 변경하였습니다. 코틀린은 사실 전혀 새로운 프로그래밍 언어는 아닙니다. 코틀린문법으로 작성한 소스코드를 컴퓨터가 인식할 수 있도록 compile 시키면 .class 파일로 만들어 집니다. Java언어의 .class 파일과 같습니다. 즉, 컴퓨터 입장에서는 코틀린으로 개발한거나 Java문법으로 개발한거나 컴파일 시킨 파일이 같기때문에 개발자가 작성하는 문법적 표기법만 차이가 있을 뿐 입니다. 다시말하면 자바로 개발하나 코틀린으로 개발하나 똑같기 때문에 하나의 앱안에서 특정 파일은 코틀린으로 작성하고 다른 파일은 자바로 작성해도 서로 잘 인식합니다. 자바로 만든 클래스를 코틀린에서 객체로 생성해도 된다는 겁니다. 그래서 언어가 바뀌었다고 기존 자바 앱개발 기술이 쓸모 없거나 한건 아닙니다. 여전히 현업에서 20% 정도는 자바언어를 이용하여 앱을 개발합니다. 특히, 웨어러블 의료기기의 시그널을 받아 사용자에게 정보를 제공하는 앱들의 경우에는 자바언어를 이용하여 앱을 개발하는 경우가 많습니다. 그리고 개인적인 견해이기는 하지만 코틀린은 결국 컴파일 하면 자바의 .class파일이 되는 만큼 자바언어에 대한 이해없이 코틀린 언어만 학습하여 앱 개발을 하는 것에 대해서는 긍정적으로 보고 있지는 않습니다. 아. 그렇다고 코틀린언어 학습으로 앱 개발을 시작한다고 해서 잘못 되었다는 것은 아닙니다. 단지, 코틀린이 자바언어를 기반으로 하는 만큼 개발자라면 이런 자바와 코틀린언어의 특징은 알고 시작했으면 하는 겁니다.

 

그래서 필자가 강의를 할때는 보통 자바언어 문법 수업을 먼저 교육하고 안드로이드 수업의 시작은 자바언어로 만들어 봅니다.  TextView 나 Button 같은 기본 View 들을 다루는 정도의 수업을 통해 안드로이드 앱 개발의 기초 개념을 익히는 정도는 자바언어로 만들어보는 경험을 한 후에 코틀린언어의 주요 문법들을 학습하여 나머지 안드로이드 앱 개발 수업을 했을 때 비전공자들이 조금 더 프로그래밍 언어의 차이에 대한 이해를 잘하는 것을 볼 수 있어서 이 학습 순서를 선호합니다. 혹시 안드로이드 앱 개발 공부를 시작하는 입장이시라면 가급적 Java언어의 문법을 살펴보고 난 후 코틀린언어를 배우면서 두 언어의 문법적 차이를 대략적으로 확인하며 공부해 보시면 좋을 겁니다. 문법적 차이가 다소 많이 나는 편이어서 쉽지는 않겠지만 나중에 내실있는 앱 개발자가 될때 도움이 될 겁니다. 

 

이건 추가적인 이야기 이지만, 요사이 앱 개발을 하는 스타트업에서 빠르게 android와 ios 용 앱을 만들고 배포하기 위해 많이 사용하는 크로스플랫폼 개발 툴인 Flutter 의 개발언어는 Dart 라는 언어를 사용하는 데 문법적 표기법은 코틀린보다는 자바언어와 조금 더 비슷하여 자바언어를 전혀 모르는 상태에서 코틀린만으로 앱을 개발했던 초보자들은 쉽게 Dart 문법이 익혀지지 않습니다. 반대로 자바 언어의 문법을 대략적으로라도 알고 있다면 조금 더 Dart언어를 받아들이는 과정이 수월할 겁니다.

여러분도 언젠가 앱 개발자가 되어 실무에서 프로젝트들을 수행하다 보면 디바이스의 고유 언어를 사용하는 네이티브 앱 개발만 하지 않고 웹프론트엔드와 웹뷰를 사용하는 웹앱도 개발하게 되며 Flutter 나 React Native와 같은 크로스 플랫폼 앱 개발도 하게 될 겁니다. 

 

코틀린 언어를 학습하기 전에 가급적 자바언어의 특징과 문법. 객체지향프로그래밍 등에 대한 개념을 살펴보길 바라는 입장해서 글이 길어졌네요. 이제 코틀린 언어의 기본적인 문법을 소개하겠습니다.

 

다만, 이번 코틀린에 대한 글은 자바언어를 대략적으로 학습했다는 전제로 차이점을 위주로 작성하려 합니다. 또한, 코틀린 문법을 A~Z까지 모두 세세하게 학습하기 보다는 안드로이드 앱 개발에 필요한 정도의 수준으로 필요한 부분만 소개하려 한다는 점을 이해하고 읽어주시기 바랍니다. 

 


코틀린 개발 환경 

  1. Kotlin compiler
  2. 코드 편집기 IDE  [ IntelliJ IDEA ]

코틀린언어도 개발자가 영어로 작성하는 언어이고 컴퓨터가 이를 인식하게 하기 위해 기계어로 번역하는 컴파일 과정을 거쳐야 하기 위해 컴파일러가 필요합니다. 이 컴파일러를 Kotlin compiler 라고 부릅니다. 무료 소프트웨어 입니다. 즉, 다운로드 받아 여러분의 컴퓨터에 설치하면 됩니다. 구글 검색을 통해 손쉽게 다운로드 받으실 수 있습니다. 그리고 코드를 작성해야 하니 편집기도 필요하겠죠. 아무래도 코틀린언어를 JetBrains 사에서 만든만큼 편집기도 같은 회사에서 만든 IntelliJ 를 사용하면 됩니다. 

 

하지만, 이 글을 보시는 분들은 이미 안드로이드 개발환경을 모두 설치하신 분들일테니 위 2가지 개발환경을 추가로 설치하실 필요가 없습니다. 안드로이드 개발도구인 Android Studio가 IntelliJ 편집기에 앱개발관련 기능과 메뉴들을 추가해서 만들었으며 Kotlin으로 개발하도록 설계되었기에 Kotlin compiler 도 내장되어 있습니다. 이미 준비가 다 되어있기에 안드로이드 스튜디오로 실습을 진행하겠습니다.

 

문법 소개에 앞서, 코틀린언어 학습이 목표라기 보다 안드로이드 앱 개발을 위해 코틀린언어를 학습하는 만큼 기존 자바언어로 개발했을 때와 코틀린으로 개발했을 때가 문법적으로 어떻게 다른지를 대략적으로 살펴보는 것으로 학습을 시작해 보겠습니다.

  • project name : KotlinHello
  • minimum SDK : api 26
  • language : kotlin
  • build configuration language : kotlin DSL
  • OS : mac
  • ANDROID STUIDO : LadyBug 

 

이미 알고 있듯이 안드로이드 앱 개발의 화면 UI 개발은 XML 마크업 언어를 사용하고 동적인 제어를 JAVA언어에서 KOTLIN 으로 변경하는 것이기에 UI 파일인 activity_main.xml 파일을 똑같고 MainActivity.java 파일만 코틀린 파일인 MainActivity.kt 파일로 변경되어 있습니다. 

 

그래서 MainActivity.kt 만 살펴보겠습니다.

MainActivity.kt
package com.kitesoft.kotlinhello

import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

//대략적인 코틀린 코딩 방식 살펴보기 - 자바와의 차이를 기반하여 소개.

//Kotlin에서 클래스 상속 키워드는 ":" 이며 상속하는 클래스명옆에 주생성자를 호출하는()가 필수임
class MainActivity : AppCompatActivity() {

    //Kotlin 에서의 메소드(함수)는 fun 키워드 사용 - onCreate() 라이프사이클 메소드 오버라이드
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 화면의 최상단 status bar(상태표시줄)와 최하단 navigation(뒤로가기,홈,최신앱목록)버튼들을 가진 네비게이션 영역 까지 액티비티 화면을 모두 사용하는 기능 함수
        enableEdgeToEdge()

        // 화면에 보여줄 뷰를 설정
        setContentView(R.layout.activity_main)

        // enableEdgeToEdge()를 사용 하면 화면 하단의 네비게이션 버튼에 의해 화면이 가려지기에 네비게이션 영역만큼 패딩 설정
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
    }
}

 

아직 코틀린 문법을 정식으로 학습하기 전이기에 주석으로 대략적인 코드만 확인해 봤습니다. 지금은 너무 이해할려고 노력하지 않아도 됩니다. 문법적 표기법이 다소 차이는 있지만 기존 자바파일과 큰 차이는 없다는 것을 알 수 있을 겁니다.


이렇게 기본적으로 만들어진 코드만 살펴보기에 다소 아쉽네요.

코틀린 코드의 특징을 간단하게 확인해보기 위해 글씨를 보여주는 TextView와 Button 하나를 화면에 배치하고 버튼 클릭이벤트로 TextView가 보여주는 글씨를 변경하는 코드를 코틀린 스럽게 코딩해 보겠습니다.

 

LinearLayout을 이용하여 TextView와 Button을 간단하게 수직으로 배치하겠습니다.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello"
        android:padding="8dp"/>
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="change text"
        android:textAllCaps="false"/>

</LinearLayout>

 

XML 레아이웃 파일을 만드는 것은 전혀 변한게 없지요. 이제 이 뷰들을 참조하여 클릭이벤트를 처리하는 코드를 작성해 보겠습니다.

기존 MainActivity.kt 파일에서 새로 추가한 부분은 굵은 글씨로 표시하겠습니다. 

지금은 대략적인 코틀린 코딩스타일을 확인하는 것이라 설명은 주석으로 대체하겠습니다. 깊게 이해하기 보다는 눈에 익혀본다는 느낌으로 가볍게 살펴보시기 바랍니다.

MainActivity.kt
package com.kitesoft.kotlinhello

import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

//대략적인 코틀린 코딩 방식 살펴보기 - 자바와의 차이를 기반하여 소개.

//Kotlin에서 클래스 상속 키워드는 ":" 이며 상속하는 클래스명옆에 주생성자를 호출하는()가 필수임
class MainActivity : AppCompatActivity() {

    //Kotlin 에서의 메소드(함수)는 fun 키워드 사용 - onCreate() 라이프사이클 메소드 오버라이드
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 화면의 최상단 status bar(상태표시줄)와 최하단 navigation(뒤로가기,홈,최신앱목록)버튼들을 가진 네비게이션 영역 까지 액티비티 화면을 모두 사용하는 기능 함수
//        enableEdgeToEdge()  

        // 화면에 보여줄 뷰를 설정
        setContentView(R.layout.activity_main)

        // enableEdgeToEdge()를 사용 하면 화면 하단의 네비게이션 버튼에 의해 화면이 가려지기에 네비게이션 영역만큼 패딩 설정
//        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
//            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
//            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
//            insets
//        }

        //변수는 var 키워드사용 - [ var 변수명 : 타입 ]
        var btn: Button = findViewById(R.id.btn)  //문장의 끝인 ;이 필요없음

        //버튼에 클릭리스너 설정하기 - 자바의 람다식과 비슷한 SAM(Single Abstract Method) 변환 제공
        btn.setOnClickListener {
            clickBtn() //메소드 호출
        }

    }//onCreate method ..

    //Kotlin 에서의 메소드(함수)는 fun 키워드 사용
    fun clickBtn(){
        var tv = findViewById<TextView>(R.id.tv)  //변수선언시에 자료형을 명시하지 않아도 됨.
        //tv.setText("Nice to meet you. Kotlin");

        //Kotlin은 setXXX()메소드를 권장하지 않고 멤버변수에 값 대입을 선호함 (실제 내부적으로는 setter 메소드가 구동됨 - kotlin property 특성)
        tv.text= "Nice to meet you. Kotlin";
    }

    //Override 메소드가 Java에서는 @Override 어노테이션을 사용했지만
    //코틀린에서는 메소드 앞에 override 키워드 삽입
    //Override 메소드는 반드시 fun앞에 'override' 키워드가 있어야 함. 없으면 Error
    override fun onResume() {
        super.onResume()
        Toast.makeText(this, "onResume", Toast.LENGTH_SHORT).show() //kotlin언어에서는 Toast 자동완성 기능을 소문자로 toast를 써야함
    }

}//MainActivity class....

 


실행결과

버튼이 클릭되면 글씨가 변경되는 실행화면 [ Hello --> Nice to meet you ]

 


 

대략적으로 살펴 보았으니 다음 글에서 본격적으로 코틀린 문법에 대해 알아보겠습니다.

 

반응형
반응형

 

이번 글은 데이터바인딩의 3번째 실습예제로서 사용자가 EditText에 글씨를 입력하고 버튼을 클릭하면 입력된 글씨를 TextView에 보여주는 예제입니다. 클릭이벤트 및 뷰의 데이터를 제어하는 가장 기본적이면서 어쩌면 앱 개발과정의 가장 핵심적인 기능구현이라고 생각합니다. 이 기능구현에 데이터바인딩을 적용해 보고자 합니다. 이전 포스트에서 소개했던 데이터바인딩의 특징을 다시한번 글로 정리해 보겠습니다.

 

기존의 방법은 EditText 객체를 찾아와 참조하여 입력된 글씨데이터를 얻어와서 TextView객체 참조변수를 이용하여 setText()로 직접 뷰에 데이터를 설정해주는 방법으로 기능을 구현했습니다. 

데이터 바인딩에서는 뷰객체를 참조하여 제어하지 않습니다. 뷰객체에 연결되어 있는 변수를 제어하여 뷰가 보여주는 값을 갱신하지요.

 

또한, 기존에는 클릭이벤트나 체크상태변경 이벤트, 또는 EditText의 글씨변경 리스너등을 Activity에서 설정하고 이벤트 콜백메소드를 만들어 뷰객체들에게 직접 데이터를 설정하는 등의 코드를 작성하였습니다. 그러다 보니 뷰가 많아지고 이벤트들이 많아지면 액티비티의 코드가 길어지고 거의 모든 코드가 액티비티 자바파일에서 작성되어 나중에는 어디에 어떤 메소드가 있는지 찾는 것도 어렵고 수정할때 그 위치를 찾는 것도 어려워 답답해 지는 경우가 많습니다.

자바의 객체지향을 학습하면서 많이 들어 보셨겠지만 객체지향을 위해 설계되는 class들은 그 이름에 걸맞는 변수와 기능메소드 들로 구성되어야 좋습니다. Random 클래스안에 말도 안되게 키보드 입력같은 기능메소드가 들어가 있거나, String 클래스안에 네트워트 작업 기능 메소드가 들어가 있으면 뭔가 이상하지 않나요? 클래스의 이름과도 어울리지 않고요.

 

안드로이드의 액티비티 class 역시 앱의 화면 UI를 담당하는 목적의 클래스 입니다. 이 안에서 액티비티가 보여주는 뷰를 참조하여 제어하는 코드를 작성하는 것는 것은 특별히 어색하지 않습니다. 그래서 그동안은 그렇게 뷰를 참조하고 뷰에 리스너를 달아주고 이벤트를 처리하였습니다.

하지만 데이터바인딩에서는 뷰를 참조하지 않습니다. xml 레이아웃파일에서 이미 뷰객체에 본인들이 제어할 데이터를 가진 변수와 연결되어 있습니다. 그렇기에 버튼을 클릭하는 콜백메소드를 액티비티가 아니라 데이터를 저장하는 변수가 있는 데이터클래스(User)에서 메소드를 만들었습니다. 데이터를 가진 클래스가 User인데 그 값을 변경하는 코드가 Activity에 있는 것보다는 User클래스에 있는것이 유지보수와 재사용성 면에서 훨씬 효과적일 겁니다. 이전 포스트에서 소개한 내용이지요.

 

이번 실습에서도 마찬가지로 EditText의 글씨가 입력되어 변경되는 것을 듣는 TextWatcher리스너의  콜백메소드인 onTextChanged를 User클래스에 콜백메소드로 만들어 입력된 글씨를 알아내고 그 글씨를 TextView에 곧바로 보여주도록 하겠습니다. 버튼을 클릭했을때 글씨를 얻어오는 코드는 이 다음에 이어서 소개하겠습니다. 즉, EditText에 글씨를 입력할때 마다 그 글씨를 TextView에 보여주도록 하겠습니다. 마치 자동으로 따라 써지는 것처럼요.

 

먼저, User 클래스에 TextView가 보여줄 데이터를 저장할 관찰가능한(Observable) 타입의 String 을 저장하는 msg 라는 이름의 참조변수와 객체를 만들어 주겠습니다. EditText의 글씨를 보여줄 TextView에 연결될 변수입니다.

또한 EditText 의 글씨변경에 반응하는 리스너인 TextWatcher의 콜백메소드인 onTextChanged()에 대응하는 메소드를 추가하겠습니다. 이전 글의 실습에 이어서 작업하겠습니다. 이 클래스의 가장 아래쪽 EditText 주석이 있는 부분부터 보시면 됩니다.

# User.java
import android.view.View;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();


    //constructor
    public User(String name, int age, boolean fav) {
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

    //change name click callback method : onclick속성에 반응하는 액티비티의 콜백메소드 작업을 데이터가 있는 이 클래스에서 코
    public void changeName(View view){
        name.set("ROBIN"); //Observable객체들의 값 변경은 반드시 set()을 해야 자동 값 갱신이 이루어짐 [ ObservableXXX 객체들은 어차피 = 대입연산자 사용 못함]
    }

    //increase age by click .. callback method
    public void increaseAge(View view){
        age.set( age.get() + 1 ); // ++연산이 불가하가이 get()으로 가져온 값에 1을 더한 값을 set()
    }

    //toggle fav by button click... callback method
    public void toggleFav(View v){
        fav.set(!fav.get());
    }

    //onCheckedChanged 일때 User객체의 fav값도 같이 변경하기 - 체크상태 변경 리스너의 콜백메소드를 그대로 만들
    public void changeFav(CompoundButton view, boolean isChecked){
        //fav의 값에 의해 체크박스의 체크상태는 자동반영되지만.. 반대로
        //체크박스의 체크상태를 변경했을때 fav멤버값이 자동으로 변경되지 않기에...직접 set
        fav.set(isChecked);
        //값이 제대로 변경되었는지 확인
        Toast.makeText(view.getContext(), "fav : " + fav.get(), Toast.LENGTH_SHORT).show();
    }


    //callback메소드가 아닌 일반 메소능 - 람다식을 이용하여 onClick에 반
    public void changeName(){
        this.name.set("HONG");
    }



    //EditText의 글씨 변경값을 저장하는 관찰되고 있는 멤버값
    public ObservableField<String> msg= new ObservableField<>();

    //EditText의 TextChanged event callback method... [ TextWatcher 클래스의 콜백메소드 - 파라미터 참고 ]
    public void onTextChanged(CharSequence s, int start, int before, int count){
        //변경될때 마다의 글씨를 TextView와 연결되어 있는 msg 변수에 설정
        msg.set(s.toString());
    }
    
}

 

이제, 레이아웃 xml 파일에서 EditText와 TextView를 추가하겠습니다. 이전 실습의 코드에 이어서 작업하겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"
            android:onCheckedChanged="@{user::changeFav}"/>
            <!-- onCheckedChanged="" : onClick 속성과 비슷하게 onChcekedChagedListener의 콜백메소드를 자동호출하는 속성 :자동완성 안됨 -->

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change name"
            android:onClick="@{user::changeName}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="increase age"
            android:textAllCaps="false"
            android:backgroundTint="@color/black"
            android:onClick="@{user::increaseAge}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="change favorite"
            android:backgroundTint="@color/teal_700"
            android:onClick="@{user::toggleFav}"/>

        <!-- onclick속성에 발동하는 메소드 사용하지 않고 xml에서 바로 람다식으로 콜백메소드를 만들어 일반 메소드 호출가 -->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:backgroundTint="#FFFF3300"
            android:text="change name by lamda functioin"
            android:onClick="@{ (v)-> user.changeName() }"/>


        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="enter text"
            android:inputType="text"
            android:onTextChanged="@{user::onTextChanged}"/>
        <!-- onTextChanged=""속성- 자동완성은 안되지만 직접 작성하면 TetWatcher의 콜백메소드 호출 가능함  -->

        <!-- EditText의 입력글씨를 그대로 보여주는 TextView       -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.msg}"/>
            
    </LinearLayout>

</layout>

 

이제, 실행해보면 EditText에 글씨를 입력하여 변경될때 마다의 글씨가 바로 밑에 배치된 TextView에 그대로 보여질 겁니다. 

 

 

이번에는, 입력글씨가 변경될때마다 TextView의 글씨를 바꾸지 말고 버튼을 클릭하였을때 TextView에 글씨를 보여주도록 하겠습니다. 실제로는 더 많이 쓰여지는 상황입니다. EditText 뷰를 참조하지 않기 때문에 버튼을 클릭했을때 입력글씨를 얻어오는 식의 코드는 작성할 수 없습니다. 그럼 어떻게 해야 할까요? 특별한 문법이 있는 것은 아니고 조금 다른 방법으로 그렇게 동작하도록 구현해 낼겁니다.

 

EditText의 글씨가 변경될때 마다 TextView와 연결된 ObservableField<String> 객체인 msg 의 값을 set()하다보니 매번 화면이 갱신되면서 적용이 되니까, 굳이 msg의 매번 값을 설정하지 말고 버튼 눌렀을때 한번만 set()하면 되겠죠. 근데 그 때의 EditText 값을 읽어올 수 없으니 매번 글씨가 변경될 때마다 관찰되지 않는 일반 String 변수에 그 값을 저정할 겁니다. 글씨가 변경될 때 마다 발동하는 콜백메소드에서 EditText에 입력된 글씨를 일반 String 변수에 저장하고 버튼을 눌렀을때 마지막으로 저장된 String 변수값을 TextView가 보여주는 Observable 필드에 설정하면 그 순간 한번만 화면을 갱신하게 됩니다.

 

코드로 바로 소개하겠습니다. 위 실습의 User클래스에 이어서 코드를 작성하겠습니다.

# User.java
mport android.view.View;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();


    //constructor
    public User(String name, int age, boolean fav) {
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

    //change name click callback method : onclick속성에 반응하는 액티비티의 콜백메소드 작업을 데이터가 있는 이 클래스에서 코
    public void changeName(View view){
        name.set("ROBIN"); //Observable객체들의 값 변경은 반드시 set()을 해야 자동 값 갱신이 이루어짐 [ ObservableXXX 객체들은 어차피 = 대입연산자 사용 못함]
    }

    //increase age by click .. callback method
    public void increaseAge(View view){
        age.set( age.get() + 1 ); // ++연산이 불가하가이 get()으로 가져온 값에 1을 더한 값을 set()
    }

    //toggle fav by button click... callback method
    public void toggleFav(View v){
        fav.set(!fav.get());
    }

    //onCheckedChanged 일때 User객체의 fav값도 같이 변경하기 - 체크상태 변경 리스너의 콜백메소드를 그대로 만들
    public void changeFav(CompoundButton view, boolean isChecked){
        //fav의 값에 의해 체크박스의 체크상태는 자동반영되지만.. 반대로
        //체크박스의 체크상태를 변경했을때 fav멤버값이 자동으로 변경되지 않기에...직접 set
        fav.set(isChecked);
        //값이 제대로 변경되었는지 확인
        Toast.makeText(view.getContext(), "fav : " + fav.get(), Toast.LENGTH_SHORT).show();
    }


    //callback메소드가 아닌 일반 메소능 - 람다식을 이용하여 onClick에 반
    public void changeName(){
        this.name.set("HONG");
    }



    //EditText의 글씨 변경값을 저장하는 관찰되고 있는 멤버값
    public ObservableField<String> msg= new ObservableField<>();

    //EditText의 TextChanged event callback method... [ TextWatcher 클래스의 콜백메소드 - 파라미터 참고 ]
    public void onTextChanged(CharSequence s, int start, int before, int count){
        //변경될때 마다의 글씨
        msg.set(s.toString());
    }
    
    

    //EditText의 값 변경시마다의 글씨를 일반 멤버변수에 저장
    String titleStr="";

    public void changedTitle(CharSequence s){
        titleStr= s.toString();
    }

    //버튼 클릭시에 저장되어 있는 title변수의 값을 ObservableField<>의 변수값으로 설정
    public ObservableField<String> title= new ObservableField<>(""); //""값으로 초기화

    //버튼 클릭 콜백메소드
    public void clickBtn(View v){
        title.set(titleStr);
    }

    // 이렇게 데이터를 가지고 있고 데이터의 값을 변경하는 코드가 있는 클래스가 MVVM에서 MiewModel 의 역할을 하게됨.

}

 

마지막으로, 레이아웃 xml 파일에 EditText, Button, TextView 를 1개씩 추가하겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"
            android:onCheckedChanged="@{user::changeFav}"/>
            <!-- onCheckedChanged="" : onClick 속성과 비슷하게 onChcekedChagedListener의 콜백메소드를 자동호출하는 속성 :자동완성 안됨 -->

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change name"
            android:onClick="@{user::changeName}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="increase age"
            android:textAllCaps="false"
            android:backgroundTint="@color/black"
            android:onClick="@{user::increaseAge}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="change favorite"
            android:backgroundTint="@color/teal_700"
            android:onClick="@{user::toggleFav}"/>

        <!-- onclick속성에 발동하는 메소드 사용하지 않고 xml에서 바로 람다식으로 콜백메소드를 만들어 일반 메소드 호출가 -->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:backgroundTint="#FFFF3300"
            android:text="change name by lamda functioin"
            android:onClick="@{ (v)-> user.changeName() }"/>


        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="enter text"
            android:inputType="text"
            android:onTextChanged="@{user::onTextChanged}"/>
        <!-- onTextChanged=""속성- 자동완성은 안되지만 직접 작성하면 TetWatcher의 콜백메소드 호출 가능함  -->

        <!-- EditText의 입력글씨를 그대로 보여주는 TextView       -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.msg}"/>


        <!-- EditText에 글씨 입력하고 버튼 클릭하면 그 글씨 텍스트뷰에 보이기  -->
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="input title"
            android:inputType="text"
            android:onTextChanged="@{(s,start,before,count) -> user.changedTitle(s)}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="OK"
            android:onClick="@{user::clickBtn}"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.title}"/>

    </LinearLayout>

</layout>

EditText의 onTextChanged속성값으로 콜백함수를 바로 지정하지 않고 익명메소드를 람다식으로 지정하여 이벤트가 발생하면 람다식으로 표현된 함수가 호출되고 그 실행내역으로 user객체의 changeTitle()이라는 일반 메소드를 호출하면서 변경된 글씨를 가진 s를 파라미터로 전달하도록 해봤습니다. 바로 위 실습과 같이 콜백에 반응하지만 조금 다른 방식으로 소개해 봤습니다. 

 

보시다 시피 MainActivity.java는 전혀 건드리지 않았습니다. 실무에서는 이렇게 액티비티에 코드가 별로 없는 경우가 많습니다. 역할에 충실하게 코드들의 위치를 구분하여 작성하는 것이지요. 좋긴 하지만 초보분들에게는 오히려 한눈에 코드가 보이지 않아 더 복잡하게 느껴질 수도 있습니다.

 

처음에는 어색하지만 조금 익숙해지면 여러분들의 예상보다 훨씬 매력적인 기법이라는 것을 느끼실 수 있으실 겁니다.

미리 포기하지 말고 도전해 보시길 권해드립니다.

 

다음 데이터바인딩 관련 글로는 Fragment에서 데이터 바인딩을 적용하는 것을 소개하겠습니다.

반응형
반응형

 

이번 글에서는 데이터바인딩에 대한 2번째 실습으로 버튼을 클릭하였을때 TextView의 글씨가 바뀌도록 해보겠습니다.

실제 앱개발 과정에서 가장 빈번하게 작업하는 유형입니다. 특정 이벤트가 발생하였을때 뷰의 콘텐츠를 제어하는 작업이지요.

 

이전 포스트에서 잠시 소개하였듯이 데이터 바인딩은 기존에 앱개발에서 이벤트를 처리하는 방법과는 메카니즘 자체가 다릅니다. 

기존방식은 자바에서 뷰를 참조하고 이 뷰객체를 제어하여 다른 UI를 보여주는 방식이었습니다.

그에반해,  데이터바인딩은 뷰객체를 제어하는 것이 아니라 뷰객체가 보여주는 데이터를 가진 변수를 제어하여 화면이 갱신되도록 하는 방법을 사용합니다. 뷰에게 특정변수를 설정(연결)해 놓고 그 변수의 값을 바꾼다는 것이지요. 

단, 이때 우리가 일반적으로 사용하는 String이나 int같은 자료형의 변수를 이용하면 연결되어 뷰에 보여지기는 하지만 변수값을 바꾸어도 뷰가 이를 자동으로 인지하여 화면을 갱신하지 않습니다. 그래서 일반적인 자료형의 변수가 아니라 관찰(Observe) 이 가능한 자료형의 변수를 만들어 이를 뷰들에 설정(연결)합니다. 이런 자료형의 변수들을 Observable 변수라고 합니다.

 

코드를 보면서 더 소개하겠습니다.

이전 글(DataBinding #1)에서 소개했던 실습예제를 수정하면서 이번 실습을 진행하겠습니다.

 

먼저, 이전 실습에서 TextView과 연결되어 있는 데이터 클래스인 User클래스를 수정하겠습니다.

일반적인 자바 자료형인 String, int, boolean을 각각 관찰가능한(Observable) 자료형으로 변경하겠습니다.

Observable 자료형도 자바의 자료형에 대응되어 만들어져 있습니다. 설명없이 그냥 보기만 해도 어렵지 않게 구분하여 변경하실 수 있습니다.

 

값 변경이 관찰되는 자료형은 크게 3종류로 구분됩니다.

1) 자바의 primitive 자료형(기본 자료형) 인 boolean ~ double에 대응되는 ObservableBoolean ~ ObservableDouble

2) List 또는 Map 컬렉션에 대응되는 ObservableList or ObservableMap

3) String이나 Item같은 자바의 참조형 타입들에 대응되는 ObservableField<XXX> : <>제네릭안에 참조형 타입명 지정

 

# User
import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();
    

    //constructor
    public User(String name, int age, boolean fav) {
    	//파라미터로 받은 일반적인 자료형을 ObservableXXX 변수에 대입해주기 위해 .set()메소드 이용 - 자료형이 다르기에 대입연산자(=)로 값 저장불가
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

}

 

레이아웃 xml파일이나 액티비티 자바 파일에서는 ObservableXXX 변수로 바꿨다고 달라지는 코드는 없습니다. 그냥 기존에 String, int, boolean 처럼 생각하고 코딩하셔도 됩니다. 이전 포스트의 레이아웃 xml 파일 코드와 다른것은 없지만  다시 코드를 확인해 보겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"/>

    </LinearLayout>

</layout>

 

액티비티 자바 코드도 다른 것은 없지만 다시 살펴보겠습니다.

# MainActivity.java
mport androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.os.Bundle;

import com.kitesoft.databindingjava.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);

        //DataBindingUtil클래스를 통해 이 MainActivity에게 activit_main.xml 레이아웃을 ContentView()로 설정하고 연결하여 제어하는 클래스객체를 리턴
        //클래스의 이름의 xml레이아웃 파일명을 기준으로 자동으로 설계되어 만들어짐. [ex. activity_main.xml 이면 ActivityMainBinding,  activity_intro.xml 이면 ActivityIntroActivityBinding ]
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //xml문서의 <data>요소 안에 있는 <variable>의 type에 지정한 클래스 객체를 만들어 set()해주면 레이아웃파일의 뷰들에 설정한 객체의 값들이 반영됨
        binding.setUser(new User("sam", 20, true)); //초기값으로 지정할 User객체 생성 및 바인딩객체에게 설정

        //참고로 뷰들에 id가 지정되어 있다면 binding객체의 멤버변수로 id명과 같은 뷰의 참조변수들이 자동으로 만들어져 있음 [ ViewBinding 기능 ]
    }
}

 

데이터클래스인 User클래스이 name, age, fav 필드(멤버변수)들의 자료형을 ObservableXXX 로 바꾼것 말고는 똑같습니다. 수정을 하셨다면 앱을 실행시켜보시기 바랍니다. 화면에 User객체의 초기 값인 "sam", 20 이 보이고 체크박스가 체크된 상태로 보이면 데이터들이 잘 연결된 상태입니다. 

 

이제 버튼을 하나 추가해서 버튼 클릭시에 User클래스이 name변수값을 변경해보겠습니다. 단지 name변수의 값만 바꿀 겁니다. TextView를 참조하여 setText()로 설정하는 등의 작업을 전혀 하지 않을 겁니다. 뷰가 보여주는 변수의 값만 바꿈으로서 자동으로 UI가 갱신되도록 하는 기법입니다.

 

대략 메카니즘이 이해 되었나요? 이제 중요한 부분입니다.

 

버튼 클릭이벤트에 반응하기 위해 클릭리스너를 버튼에 설정해 주거나 onClick속성값으로 MainActivity.java에서 자동으로 실행될 콜백메소드명을 설정해 주고 액티비티 자바에서 원하는 작업코드를 하였을 겁니다. 아마 User객체의 참조변수를 만들고 그 멤버인 name 변수값을 변경하는 코드를 액티비티에서 작성하셨을 겁니다. 여기서 좀 생각해볼까요? 객체지향언어의 특징은 객체의 값설정이나 변경은 객체가 스스로 하도록 하는 것을 권장합니다. 니껀니가!! 라는 것이 객체지향의 가장 중요한 특징 중에 하나이지요. 근데 그동안 해오던 방법은 액티비티에서 User객체를 참조하여 User객체의 멤버값을 변경하는 방식이었습니다.  User객체의 값 변경을 왜 액티비티가 해야하는거죠? User객체 스스로 값을 변경하는 기능이 있어야 하는 거 아닐까요? 더 나아가 결국 버튼이 클릭되었을때 User클래스의 멤버변수인 name변수를 제어해야 하는데 그 코드를 왜 액티비티에서 하고 있는 었을까요? 그 변수가 선언된 User.java에서 안에서는 값 변경 처리를 하는 것이 더 자연스럽고 나중에 관리하기도 더 좋지 않을까요? name변수가 선언된 클래스안에만 name변수의 변경 코드를 작성해 놓으면 코드의 유지보수가 더 수월하지 않을까요? 이런 버튼이 한두개 라면 액티비티에서 클릭이벤트에 반응하는 메소드를 만들고 그 안에서 데이터객체의 값을 변경해도 크게 복잡해 보이지 않겠지만 만약, 버튼이 여러개이고 체크박스처럼 이벤트를 발생하여 데이터를 변경해야 하는 것이 여러개라면 액티비티 안에서 너무 많은 작업을 해야하고 정작 데이터를 저장하는 변수가 선언된 데이터클래스안에는 아무 기능이 없는 비효율적인 코딩이 될겁니다. 데이터 바인딩은 바로 이 부분을 개선하였습니다. 버튼 클릭에 반응하는 콜백 메소드를 액티비티 말고 데이터클래스에서도 만들 수 있게 한 것입니다.

Button 뷰의 onClick속성에 @{user::메소드명} 으로 설정하면 해당 메소드가 클릭이벤트에 반응하여 자동 호출됩니다.  user.name 으로 User객체의 멤버필드를 연결하였듯이 user::메소드명  으로 User클래스 안에 있는 메소드를 콜백메소드로 연결해 주는 겁니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change name"
            android:onClick="@{user::changeName}"/>

    </LinearLayout>

</layout>

 

새로 추가된 <Button>의 onClick속성 값으로 User클래스 안에 만든 'changeName' 이라는 메소드를 지정해 준겁니다. MainActivity의 메소드가 아니라 User클래스의 멤버 메소드 입니다. 그럼 User 클래스안에 changeName 이라는 이름의 메소드를 만들겠습니다.

# User.java
package com.kitesoft.databindingjava;

import android.view.View;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();


    //constructor
    public User(String name, int age, boolean fav) {
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

    //change name click callback method : onclick속성에 반응하는 액티비티의 콜백메소드 작업을 데이터가 있는 이 클래스에서 코
    public void changeName(View view){
        name.set("ROBIN"); //Observable객체들의 값 변경은 반드시 set()을 해야 자동 값 갱신이 이루어짐 [ ObservableXXX 객체들은 어차피 = 대입연산자 사용 못함]
    }

}

 

원래 액티비티 자바파일에서 하던 작업을 데이터클래스 안에서 작성한 것만 다른겁니다. MainActivity.java는 아무런 작업도 할 필요가 없는 겁니다. 이제 실행해 보시고 버튼을 눌렀을때 "sam" 이라는 글씨가 "robin"으로 변경되면 성공입니다.

결국 이벤트 처리도 데이터를 가지고 있는 데이터 클래스에서 직접 하는 겁니다. 액티비티와 분리되어 서로의 역할에 충실한 클래스들로 구조를 만들 수 있게되는 겁니다. 

이런식으로 파일의 역할별로 구분하여 프로젝트 구조를 만들어 놓으면 나중에 프로젝트를 유지보수할 때도 해당 역할의 파일만 살펴보면 되기에 보다 쉽게 관리가 가능하고 개발인력의 변경으로 인한 인수인계도 아주 수월하게 됩니다. 대략적인 점만 말했지만 이런 역할 구조로 나누어 패턴화 시킨 것을 아키텍처 패턴이라고 하고 구분기준을 조금씩 다르게 하여 이름을 명명하였으며 MVC, MVP, MVVM 등의 대표적인 패턴이 있습니다. 데이터 바인딩은 이 패턴들 중 MVVM(Model -View - ViewModel)에서 가장 기본적으로 사용됩니다. 나중에 시간을 들여 이 패턴에 대한 소개도 할 예정입니다. 지금은 그런게 있구나. 정도로 받아들여 주시기 바랍니다.

 

이제 데이터 바인딩의 클릭이벤트 처리와 Observable변수의 변경을 통한 자동 UI갱신 방식이 대략적으로 느껴지시나요?

연습을 위해 추가로 age 값도 변경하는 버튼을 추가해 보고, 마찬가지로 boolean 형 값인 fav 체크박스도 변경하는 버튼을 만들어 보겠습니다.

 

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change name"
            android:onClick="@{user::changeName}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="increase age"
            android:textAllCaps="false"
            android:backgroundTint="@color/black"
            android:onClick="@{user::increaseAge}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="change favorite"
            android:backgroundTint="@color/teal_700"
            android:onClick="@{user::toggleFav}"/>

    </LinearLayout>

</layout>

 

이제 User클래스 안에 클릭콜백메소드를 추가해 보겠습니다.

# User.java
package com.kitesoft.databindingjava;

import android.view.View;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();


    //constructor
    public User(String name, int age, boolean fav) {
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

    //change name click callback method : onclick속성에 반응하는 액티비티의 콜백메소드 작업을 데이터가 있는 이 클래스에서 코
    public void changeName(View view){
        name.set("ROBIN"); //Observable객체들의 값 변경은 반드시 set()을 해야 자동 값 갱신이 이루어짐 [ ObservableXXX 객체들은 어차피 = 대입연산자 사용 못함]
    }

    //increase age by click .. callback method
    public void increaseAge(View view){
        age.set( age.get() + 1 ); // ++연산이 불가하가이 get()으로 가져온 값에 1을 더한 값을 set()
    }

    //toggle fav by button click... callback method
    public void toggleFav(View v){
        fav.set(!fav.get());
    }

}

 

3개정도 버튼 클릭 처리를 직접 코딩해보시면 눈으로 보는 것보다 더 쉽게 이해될 겁니다.

 

이번에는 클릭이벤트 말고, 체크박스의 체크상태 변경 이벤트에 반응하는 것도 해보겠습니다. 

레이아웃 xml 파일의 <CheckBox>에 onClick속성처럼 onCheckedChanged 속성을 사용하여 콜백메소드를 지정할 수있습니다. 다만, 주의할 점은 onClick속성을 제외한 OnCheckedChanged나 OnLongClick 같은 이벤트들은 안드로이드 스튜디오에서 자동완성을 지원하지 않습니다. 그래서 처음 해보실때 자동완성이 안되서 잘못 하고 있다고 오해하여 시도를 안하는 경우도 있습니다. 자동완성이 안될 뿐 동작은 문제없이 되니까 걱정말고 따라서 써보시기 바랍니다. 

 

기존 레이아웃 xml에서 <CheckBox>뷰에 android:onCheckedChanged속성만 추가했습니다. 다른 곳은 건드리지 않았습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"
            android:onCheckedChanged="@{user::changeFav}"/>
            <!-- onCheckedChanged="" : onClick 속성과 비슷하게 onChcekedChagedListener의 콜백메소드를 자동호출하는 속성 :자동완성 안됨 -->

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change name"
            android:onClick="@{user::changeName}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="increase age"
            android:textAllCaps="false"
            android:backgroundTint="@color/black"
            android:onClick="@{user::increaseAge}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="change favorite"
            android:backgroundTint="@color/teal_700"
            android:onClick="@{user::toggleFav}"/>


    </LinearLayout>

</layout>

 

이제 User클래스 자바 파일에서 android:onCheckedChanged 에 지정한 콜백 메소드를 추가하여 테스트 해보겠습니다.

# User.java
package com.kitesoft.databindingjava;

import android.view.View;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();


    //constructor
    public User(String name, int age, boolean fav) {
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

    //change name click callback method : onclick속성에 반응하는 액티비티의 콜백메소드 작업을 데이터가 있는 이 클래스에서 코
    public void changeName(View view){
        name.set("ROBIN"); //Observable객체들의 값 변경은 반드시 set()을 해야 자동 값 갱신이 이루어짐 [ ObservableXXX 객체들은 어차피 = 대입연산자 사용 못함]
    }

    //increase age by click .. callback method
    public void increaseAge(View view){
        age.set( age.get() + 1 ); // ++연산이 불가하가이 get()으로 가져온 값에 1을 더한 값을 set()
    }

    //toggle fav by button click... callback method
    public void toggleFav(View v){
        fav.set(!fav.get());
    }

    //onCheckedChanged 일때 User객체의 fav값도 같이 변경하기 - 체크상태 변경 리스너의 콜백메소드를 그대로 만들기
    public void changeFav(CompoundButton view, boolean isChecked){
        //fav의 값에 의해 체크박스의 체크상태는 자동반영되지만.. 반대로
        //체크박스의 체크상태를 변경했을때 fav멤버값이 자동으로 변경되지 않기에...직접 set
        fav.set(isChecked);
        //값이 제대로 변경되었는지 확인
        Toast.makeText(view.getContext(), "fav : " + fav.get(), Toast.LENGTH_SHORT).show();
    }

}

실습으로는 onCheckedChange 이벤트만 처리했지만 다른 이벤트의 리스너 콜백 메소드들도 모두 이런식으로 처리 할 수 있습니다.

 

마지막으로, 이 콜백메소드는 당연히 기존 이벤트 리스너의 콜백메소드를 만들때 정해져 있던 파라미터와 리턴타입을 똑같이 했을때만 발동됩니다. 하지만 경우에 따라서는 일반 메소드를 이벤트 콜백으로 반응하게 할 필요가 있을 수도 있습니다. 그때는 xml 에서 익명메소드를 람다식으로 만들어 지정하고 이벤트가 생겼을때 그 익명메소드가 호출되면 User클래스의 일반메소드를 호출함으로서 같은 효과를 볼 수 있습니다. 코드로 소개하겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"
            android:onCheckedChanged="@{user::changeFav}"/>
            <!-- onCheckedChanged="" : onClick 속성과 비슷하게 onChcekedChagedListener의 콜백메소드를 자동호출하는 속성 :자동완성 안됨 -->

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="change name"
            android:onClick="@{user::changeName}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="increase age"
            android:textAllCaps="false"
            android:backgroundTint="@color/black"
            android:onClick="@{user::increaseAge}"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="change favorite"
            android:backgroundTint="@color/teal_700"
            android:onClick="@{user::toggleFav}"/>

        <!-- onclick속성에 발동하는 메소드 사용하지 않고 xml에서 바로 람다식으로 콜백메소드를 만들어 일반 메소드 호출가능 -->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:backgroundTint="#FFFF3300"
            android:text="change name by lamda functioin"
            android:onClick="@{ (v)-> user.changeName() }"/>

    </LinearLayout>

</layout>

 

이제 User클래스에 일반메소드를 추가합니다.

# User.java
package com.kitesoft.databindingjava;

import android.view.View;
import android.widget.CompoundButton;
import android.widget.Toast;

import androidx.databinding.ObservableBoolean;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class User {

    //값 변경이 관찰되는 멤버변수 ObservableXXX : primitive type XXX && List Or Map  && Reference type is ObservableField<>
    public ObservableField<String> name= new ObservableField<>();
    public ObservableInt age= new ObservableInt();
    public ObservableBoolean fav= new ObservableBoolean();


    //constructor
    public User(String name, int age, boolean fav) {
        this.name.set(name);
        this.age.set(age);
        this.fav.set(fav);
    }

    //change name click callback method : onclick속성에 반응하는 액티비티의 콜백메소드 작업을 데이터가 있는 이 클래스에서 코
    public void changeName(View view){
        name.set("ROBIN"); //Observable객체들의 값 변경은 반드시 set()을 해야 자동 값 갱신이 이루어짐 [ ObservableXXX 객체들은 어차피 = 대입연산자 사용 못함]
    }

    //increase age by click .. callback method
    public void increaseAge(View view){
        age.set( age.get() + 1 ); // ++연산이 불가하가이 get()으로 가져온 값에 1을 더한 값을 set()
    }

    //toggle fav by button click... callback method
    public void toggleFav(View v){
        fav.set(!fav.get());
    }

    //onCheckedChanged 일때 User객체의 fav값도 같이 변경하기 - 체크상태 변경 리스너의 콜백메소드를 그대로 만들
    public void changeFav(CompoundButton view, boolean isChecked){
        //fav의 값에 의해 체크박스의 체크상태는 자동반영되지만.. 반대로
        //체크박스의 체크상태를 변경했을때 fav멤버값이 자동으로 변경되지 않기에...직접 set
        fav.set(isChecked);
        //값이 제대로 변경되었는지 확인
        Toast.makeText(view.getContext(), "fav : " + fav.get(), Toast.LENGTH_SHORT).show();
    }


    //callback메소드가 아닌 일반 메소능 - 람다식을 이용하여 onClick에 반응
    public void changeName(){
        this.name.set("HONG");
    }

}

버튼클릭했을대 "HONG"으로 글씨가 변경되면 성공입니다.

 

 

이정도 실습을 같이 따라 코딩해보시면 데이터 바인딩이 꽤 와닿을 겁니다.

다음 포스트에서는 가장 기본이면서 중요한 실습으로 데이터 바인딩을 연습해 보겠습니다.

사용자가 EditText에 글씨를 입력하고 버튼을 클릭하여 TextView에 그 글씨가 보이도록 하는 예제입니다. 쉽고 간단하지만 가장 중요한 예제라고 생각합니다. 꼭 따라서 해보시길 권해드립니다.

반응형
반응형

 

※ findViewById()메소드를 이용한 View참조 방법이 가진 문제를 해결하기 위한 기법들[코드의 번거로움과 메소드의 동작속도 문제]

1. ButterKnife 외부 라이브러리 사용하기 (버터 나이프 라이브러리)
2. ViewBinding 안드로이드 아키텍처 구성요소
3. DataBinding (view binding + data binding) : 뷰결합 + 데이터 결합 기능까지 가진 아키텍처 구성요소

 

이번글에서는 findViewById()를 대체하는 기술의 마지막 3번째 기법인 데이터바인딩에 대해 소개하겠습니다.

이 데이터 바인딩기법은 요즘 안드로이드 앱 개발시에 많이 사용되는 프로젝트 아키텍처 패턴 중 MVVM( Medel- View - View Model)을 이해하는데 선행되어야 할 기법 중 하나여서 이 글에서 학습이 끝나지 않는 기법이니 주의깊게 봐 주시기 바랍니다.

 

코드를 보기전에 간략하게 데이터바인딩의 주요특징을 알아보겠습니다.

 

데이터바인딩은 기존에 하던 뷰와의 상호작용 방법과는 완전 그 메카니즘이 다릅니다.  즉, 기존 방법대로 이해하려고 하면 오히려 코드를 보실때 더 알아보기 어려우실 것이라는 겁니다. 아주 큰 개념의 차이점만 먼저 인지하실 필요가 있습니다.

기존에 하던 findViewById()나 ButterKnife 라이브러리, 또는 뷰바인딩은 기본적으로 데이터를 보여주는 뷰(텍스트뷰 또는 이미지뷰 등)들을 xml에서 만들고 이를 .java 언어나 .kt 코틀린언어의 프로그래밍 코드에서 참조하여 참조변수를 이용하여 뷰에게 원하는 데이터를 설정하는 방법으로 뷰의 데이터를 보여줍니다. TextView의 .setText("Hello") 나 ImageView의 .setImageResource() 같은 뷰들의 기능메소드를 이용하는 방법 말이죠. 다시말해, 데이터를 보여주는 View객체들을 참조하여 그 객체들을 제어하는 방식입니다.

 

이에 반해, 데이터바인딩은 뷰객체를 참조하는 것이 아니라 뷰객체가 보여주는 데이터를 가진 변수를 제어합니다. 즉, 뷰들에게 데이터를 가지고 있는 변수를 처음에 설정해주면 그 변수의 값이 보여질 것이고, 그런다음 버튼을 클릭하는 등의 이벤트가 발생하였을때 뷰들을 참조하여 제어하지 말고 뷰에 설정되어 있는 변수의 값을 변경하면 그 뷰가 보여주는 데이터가 바뀌니까 화면도 바뀌도록 하는 방식입니다. 뷰객체를 제어할 필요가 없기에 findViewById()를 할 필요가 없는 것이죠. 사실 기존에 하던 방법과는 완전히 다른 뷰와의 상호작용 기법이라 조금 당황스러울 수 있겠지만 이해가 어려운 것은 아닙니다.  오히려 쉽다면 쉬운겁니다. 그냥 변수값만 바꾸면 화면이 갱신되는 겁니다. 특히, 리스트뷰나 리사이클러뷰 등을 다루어 보신 분들은 더 강하게 와 닿을 겁니다. 리스트뷰나 리사이클러뷰가 보여주는 대량의 데이터에 변경이 있을때 화면을 갱신하기 위해 아답터객체의 notifyDataSetChange()나 notifityItemInsert()등의 화면갱신 코드를 매번 호출해야 하지만 데이터 바인딩은 그 코드를 일일이 신경쓰지 않아도 데이터가 바뀌면 화면이 자동 갱신됩니다. 익숙해지면 코딩이 아주 수월해 지실겁니다. 물론 아무 변수나 값이 바뀌었다고 뷰들이 무조건 갱신되지는 않습니다. 만약, 그렇게 되면 뷰의 갱신이 너무 자주 발생하여 오히려 앱의 성능에 악영향을 미칠테니까요. 그래서 관찰이 가능한(Observable) 자료형의 변수들이 바뀌었을때만 연결된 뷰의 화면이 자동갱신되어 있도록 되어 있어서 원하는 시점에만 화면의 갱신이 되도록 조절할 수 있습니다.

 

데이터 바인딩은 뷰바인딩 기능이 기본적으로 내포되어 있기에 데이터바인딩 사용 설정만으로도 원한다면 뷰바인딩으로 뷰와의 상호작용이 가능합니다. 때문에 프로젝트의 상황에 따라 개발자가 뷰바인딩과 데이터바인딩을 적절히 선택하여 개발하신다면 보다 효율적이고 성능이 개선된 앱을 만드실 수 있으실 겁니다. 

뷰바인딩에 대한 내용을 알지못한다면 이전 뷰바인딩에 대한 글을 먼저 읽어보시고 오시길 권합니다. 조금더 데이터 바인딩의 초기설정을 이해하는 데 도움이 될겁니다.

 

2021.09.15 - [소소한 소스코드] - [안드로이드 Android] 뷰 바인딩 View Binding #1

 

 

너무 서론이 길면 오히려 학습에 방해가 되니 우선 이정도만 소개하고 추후 필요한 시점에 추가적인 특성들을 더 소개하도록 하겠습니다.

 

 

이제 여러분들의 앱에 데이터 바인딩을 적용해보는 실습을 진행해보겠습니다.

 

참고로 데이터 바인딩 역시 뷰바인딩 처럼 안드로이드 아케텍처 구성요소이기에 별도의 라이브러리를 추가하는 것이 아니라 데이터바인딩 기능을 사용설정 해줌으로서 준비가 끝나기에 매우 편하게 적용가능합니다.

 

먼저, 앱 모듈수준의 build.gradle 파일에 데이터바인딩 사용 설정을 하도록 하겠습니다. 안드로이드 스튜디오의 버전에 따라 방법이 다르니 참고하시어 따라하시기 바랍니다. 

※주의. 안드로이드 개발자 공식문서에는 아직 예전 버전의 방법으로 소개되어 있습니다. 이 글을 보고있는 대부분의 분들은 본인의 안드로이드 스튜디오 버전이 최신버전이기에 4.0버전 이상일 겁니다.

# build.gradle (Module)
android {
    
    .....


    //Android Studio 4.0 버전 이상일때,
    buildFeatures {
        dataBinding = true
    }
    
    //Android Studio 4.0 버전 미만일때,
    dataBinding {
    	enabled= true
    }
}

 

데이터 바인딩의 사용설정을 해주면 뷰바인딩과 마찬가지로 레이아웃 xml 파일과 연결되어 자동으로 설계되는 바인딩 클래스가 만들어 집니다. 다만, 뷰바인딩과 다르게 무조건 생기는 것이 아닙니다. 여기서 주의 해야 합니다. 

데이터 바인딩에 의해 자동 연결되고 싶은 레이아웃 xml 파일의 root(최상위) View는 반드시 <layout> 이어야 합니다. 기존의 xml 레이아웃파일을 만들듯이 그냥 RelativeLayout아니 LinearLayout으로 시작하면 바인딩클래스는 만들어 지지 않습니다.

 

즉, 데이터바인딩으로 제어하고 싶은 레이아웃 xml은 기존 코드를 반드시 수정해야 합니다.

참고로 미리 소개하자면, 자동으로 설계되어 만들어지는 바인딩클래스의 이름은 뷰바인딩 명명규칙과 동일합니다.

activity_main.xml 과 연결되는 바인딩 클래스는 ActivityMainBinding 인 것은 뷰바인딩과 똑같습니다. 단, 뷰 바인딩과 다르게 이 바인딩클래스 객체의 뷰참조 멤버변수들을 직접 제어하지 않는 다는 것이 조금 다릅니다.

 

우선 데이터바인딩에서의 레이아웃 xml 은 최상위 root로 <layout>으로 시작하고 그 안에 크게 2개의 요소를 배치합니다.

1. <data>요소 - 뷰들과 연결되어 보여줄 데이터를 가진 데이터 클래스를 지정해주고 레이아웃파일안에서 사용할 식별명을 정해주는 요소입니다. 

2. 레이아웃의 root 뷰 요소 - 기존에 레이아웃 xml 파일에 하던 RelativieLayout 이나 LinearLayout 등이 뷰를 배치합니다.

 

주석으로 해당 요소들에 대한 소개를 하였으니 참고 바랍니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        
    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

    </LinearLayout>

</layout>

 

코드에서 보듯이 <layout>태그가 최상위 이고 그 안에 1. 과 2. 의 자식요소가 배치됩니다. 2. 레이아웃뷰는 기존에 레이아웃 xml 파일의 최상위 였던 뷰그룹입니다.

1. <data> 요소가 중요합니다. 데이터바인딩의 핵심이지요. 여기에 뷰들에 보여질 데이터를 가지고 있는 클래스를 지정해주고 이 레이아웃 파일안에서 인식할 명칭을 지정해주는 작업을 합니다.

 

먼저 간단하게 TextView를 하나 추가한 후 그 뷰가 보여줄 데이터를 멤버로 가지는 클래스를 만들어 지정해 주겠습니다.

 

앱에 사용될 데이터를 저장할 변수를 가진 클래스를 설계하는 겁니다. 데이터 바인딩의 연결 특성을 확인해보는 실습이라서 우선 간단히 만들고 다음 실습에서 다시 수정할 겁니다. TextView에 보여줄 String 멤버변수를 하나 가지는 클래스 입니다.

# User.java
public class User{
	
    //TextView에서 보여줄 데이터를 저장하고 있을 변수
    String name;
    
    //생성자메소드
    public User(String name){
    	this.name = name;
    }
    
}

 

이제 위 데이터 클래스를 activity_main.xml 에서 데이터바인딩으로 연결 설정해주기 위해 <data> 태그안에 변수값을 의미하는 태그인 <variable>로 지정해 주겠습니다. 주석으로 부연설명을 하였으니 확인바랍니다.

 

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

    </LinearLayout>

</layout>

 

<variable>태그 속성

 - name속성 :  "user" 라는 이름은 개발자가 임의로 지정하는 값입니다. 이 activity_main.xml에서 이 "user"라는 이름으로 type에 지정한 자료형(User클래스)의 객체를 인식하겠다는 의미입니다. 일종의 xml에서 사용하는 객체 참조변수명 같은 것이라고 쉽게 이해하시면 됩니다. 

- type속성 : 이 activity_main.xml과 연결될 데이터를 가지고 있을 클래스 입니다. 저 위에서 만들었던 User 클래스를 지정해 줍니다.

 

추후 MainActivity.java 에서 이 User타입의 객체를 만들어서 바인딩 해주는 코드를 작성해주게 됩니다. 

 

<data> 태그안에 데이터를 가진 변수 <variable>을 지정해 주었다면 이제 TextView를 하나 추가 하겠습니다. 특별한 점은 텍스트뷰가 보여줄 글씨값을 설정한는 android:text="" 속성안에 {}를 쓰고 위 <data>안에 만든 <variable>을 마치 객체인듯 이 곳에서 직접 연결한다는 겁니다. 주석으로 부연설명했습니다.

 

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

    </LinearLayout>

</layout>

@{user.name} 이 데이터 바인딩의 가장 핵심코드입니다. user라는 이름은 <variable>에서 name 속성안에 개발자가 임의로 지정한 이름입니다.  그리고 그 user는 type에 지정한 User클래스의 참조변수명이기에 그 안에 있는 멤버변수 String name 변수의 값을 텍스트뷰에서 보여주겠다고 설정한 것입니다. 평소에 해오던 안드로이드 코드와 달라서 조금 어색해 보일 수 있으나 생각보다 간단히 이해되는 코드입니다. 

 

근데. 여기까지만 보면 User객체를 만든적도 없고, 그 User 객체의 name 변수에 어떤 값을 저장한 적도 없었죠. 그렇기에 도데체 어떤 글씨가 보여질 것인지 전혀 예측하기 어려울 겁니다. 그 작업은 MainActivity.java 에서 하시는 겁니다.

 

곧바로 자바코드를 확인해 보겠습니다. 주석으로 주요설명을 표시하였으니 잘 살펴보시기 바랍니다.

# MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.os.Bundle;

import com.kitesoft.databindingjava.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);

        //DataBindingUtil클래스를 통해 이 MainActivity에게 activit_main.xml 레이아웃을 ContentView()로 설정하고 연결하여 제어하는 클래스객체를 리턴
        //클래스의 이름의 xml레이아웃 파일명을 기준으로 자동으로 설계되어 만들어짐. [ex. activity_main.xml 이면 ActivityMainBinding,  activity_intro.xml 이면 ActivityIntroActivityBinding ]
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //xml문서의 <data>요소 안에 있는 <variable>의 type에 지정한 클래스 객체를 만들어 set()해주면 레이아웃파일의 뷰들에 설정한 객체의 값들이 반영됨
        binding.setUser(new User("sam")); //초기값으로 지정할 User객체 생성 및 바인딩객체에게 설정

    }
}

 

이제 실행해 보시면 텍스트뷰에는 bind.setUser( new User("sam") ); 에 의해 설정된 데이터객체인  User의 멤버값 "sam"이 보여지게 될 겁니다. 

당연히 객체가 name 말고 여러개의 데이터를 추가로 더 가지고 있다면 각각의 값을 보여주는 뷰와 연결하는 작업도 같은 방식으로 할 수 있습니다. 연습을 위해 일부러 String 이 아닌 int나 boolean 같은 다른 자료형을 가지고 있도록 하고 이 값들을 적절한 뷰에 연결하도록 하겠습니다.

 

먼저 User클래스를 조금 수정하겠습니다.

#  User.java
public class User{
	
    //TextView에서 보여줄 데이터를 저장하고 있을 변수
    String name;  //이름 
    int age;      //나이
    boolean fav;  //좋아요 체크박스 여부 [true/false]
    
    //생성자메소드
    public User(String name, int age, boolean fav){
    	this.name = name;
        this.age = age;
        this.fav = fav;
    }
    
}

 

이제 age와 fav 데이터값을 보여줄 TextView와 CheckBox 뷰를 추가하여 데이터와 연결해 놓겠습니다.

# activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Data Binding의 root는 <layout> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정   -->
    <data>
        <!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정  -->
        <variable
            name="user"
            type="com.kitesoft.databindingjava.User" />

    </data>


    <!-- 2. 레이아웃 뷰 : 기존에 root로 만들었던 뷰   -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <!-- 텍스트뷰의 글씨를 <data>태에서 지정한 <variable>그 요소의 User객체의 멤버값 사용 : Observarble 멤버이기에 값변경이 관찰되어 자동 반영됨 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{user.name}"/>

        <!-- int형 값인 user.age는 TextView에 text로 직접 설정이 안되기에 String.valueOf()메소드를 이용하여 문자열로 변환하여 설정 [문자열 결합 +""는 안됨]   -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{String.valueOf(user.age)}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="좋아요"
            android:checked="@{user.fav}"/>

    </LinearLayout>

</layout>

 

age와 fav 값을 추가로 설정해주도록 자바코드를 수정하겠습니다.

# MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.os.Bundle;

import com.kitesoft.databindingjava.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);

        //DataBindingUtil클래스를 통해 이 MainActivity에게 activit_main.xml 레이아웃을 ContentView()로 설정하고 연결하여 제어하는 클래스객체를 리턴
        //클래스의 이름의 xml레이아웃 파일명을 기준으로 자동으로 설계되어 만들어짐. [ex. activity_main.xml 이면 ActivityMainBinding,  activity_intro.xml 이면 ActivityIntroActivityBinding ]
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //xml문서의 <data>요소 안에 있는 <variable>의 type에 지정한 클래스 객체를 만들어 set()해주면 레이아웃파일의 뷰들에 설정한 객체의 값들이 반영됨
        binding.setUser(new User("sam", 20, true)); //초기값으로 지정할 User객체 생성 및 바인딩객체에게 설정

        //참고로 뷰들에 id가 지정되어 있다면 binding객체의 멤버변수로 id명과 같은 뷰의 참조변수들이 자동으로 만들어져 있음 [ ViewBinding 기능 ]
    }
}

실행해 보았을때 2개의 텍스트뷰에 "sam", 20 이 보이고 체크박스는 true 값에 의해 체크된 상태로 보이면 데이터 바인딩이 잘 동작하고 있는 것입니다.

 

잠시 정리하면, 자바에서 뷰들을 findViewById()와 같은 방법으로 참조하여 값을 설정하는 것이 아니라 데이터를 가진 클래스(User) 객체를 만들고 xml 레이아웃에서 미리 연결만 해 놓으면 알아서 UI에 적용되도록 하는 기법입니다. 글로 쓴 것보다 코드를 보면 더 쉽게 이해가 되실 거라고 생각합니다.

 

대충 데이터 바인딩에 대해 알아보았습니다. 하지만 지금까지 소개한 데이터 바인딩은 큰 개념만 소개하기 위해 실습입니다. 애석하게 지금 만든것처럼 User 클래스를 만들면 클릭같은 이벤트가 발생했을때 User객체의 값을 변경해도 이 객체의 데이터를 보여주도록 설정되어 있는 뷰들의 화면을 자동 갱신되지 않습니다. 

 

다음 포스트에서 데이터를 변경했을때 자동으로 UI 가 갱신되어 뷰들에 변경된 값이 보이도록 하는 코드를 소개하겠습니다. 진짜 데이터바인딩의 코드인 것이지요.

잠시 예고를 해드린다면, User 클래스 멤버들의 자료형을 그냥 String, int, boolean으로 선언하면 값의 변경을 관찰(Observe) 하고 있지 않아 화면이 자동 갱신되지 않기에 관찰가능한 자료형으로 선언해야 합니다. 다음 포스트에서 코드로 소개해 드리겠습니다.

반응형

+ Recent posts