반응형

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

 

반응형

+ Recent posts