2장. 코틀린 언어 기초

Bimmer·2023년 8월 27일
0
post-thumbnail

2장. 코틀린 언어 기초

코틀린 프로그램의 기본적인 문법 요소를 살펴보고, 변수를 정의하고 사용하는 법을 배운다.

 

주석

자바와 마찬가지로 코틀린은 세 가지 주석을 지원하며, 코드를 문서화할 때 사용한다.

  • 한 줄짜리 주석: //로 시작하며 줄이 끝나면 주석도 끝난다.
  • 여러 줄 주석: /로 시작하고 /로 끝난다.
  • KDoc 여러 줄 주석: /*로 시작하고 /로 끝난다.
// 한 줄 주석

/*
여러 줄 주석
/* 주석 안에 내포된 주석 */
크크크크
 */


/**
 * KDoc 주석
 * KDoc 주석
 */

자바와 달리 여러 줄 주석을 여러 번 내포시킬 수 있다.


 

변수

코틀린에서 변수를 정의하는 가장 간단한 형태는 다음과 같다.

val value: Int = 15				// 불변(Immutable) 변수
var variable: String = "Hello"	// 가변(Mutable) 변수
  • 키워드(keyword): val 또는 var
    • val: value 불변
    • var: variable 가변
  • 변수 식별자(identifier): 새 변수에 이름을 부여하고, 나중에 이를 가리킬 때 사용한다.
  • 변수의 초깃값(inital value)을 정의하는 식: = 기호 뒤에 온다.

자바와 달리 세미콜론(;)을 붙이지 않는다.

 

타입 추론

타입 추론(type inference)은 컴파일러가 코드의 문맥에서 타입을 도출해주는 언어 기능이다.
코틀린은 강한 타입 지정(strongly typed) 언어인 동시에 사용자가 불필요한 타입 정보를 코드에 추가해서 코드가 지저분해지는 일을 막을 수 있다.

// 타입 명시
val value: Int = 15				
var variable: String = "Hello"

// 타입 생략
val value = 15
var variable = "Hello"

// 선언 시 초깃값 생략 후 초기화 -> 이 경우 변수 값을 읽기 전에 변수를 초기화 하지 않으면 컴파일 오류가 발생한다.
val value: Int
value = 15

자바 10부터 코틀린과 비슷한 지역 변수 타입 추론을 도입했다.

var text = "Hello"

 

기본 타입

자바에서는 int와 같은 원시 타입과 String과 같은 클래스를 기반으로 하는 참조 타입 사이에 명확한 구분이 있다.
코틀린에서는 똑같은 타입이 문맥에 따라 원시 타입과 참조 타입을 가리키기 때문에 이런 구분이 약간 모호하다.
자바에는 원시 타입을 감싸는 특별한 박싱 타입(boxing type)이 있지만, 코틀린은 필요할 때 암시적으로 박싱을 수행한다.

자바와 달리 모든 코틀린 타입은 근본적으로 어떤 클래스 정의를 기반으로 만들어진다.
이 말은 Int와 같이 원시 타입과 비슷한 타입들도 메서드와 프로퍼티를 제공한다는 뜻이다.

var doubleNum = 1.5
var intNum = doubleNum.toInt()

또한, 모든 코틀린 타입은 Any라는 내장 타입의 직간접적인 하위 타입이다.

val n: Any = 1	// Int는 Any의 하위 타입

 

수 변환

각 수 타입마다 값을 다른 수 타입으로 변환하는 연산이 정의돼 있다.

  • toByte()
  • toShort()
  • toInt()
  • toLong()
  • toFloat()
  • toDouble()
  • toChar()
  • toString()

자바와 달리 코틀린에서는 범위가 큰 타입이 사용돼야 하는 문맥에 범위가 작은 타입을 쓸 수 없다.
예를 들어 Int 값을 Long 변수에 대입할 수 없다.

val intNum = 100	// Int
val longNum: Long = intNum	// Error: can't assign Int to Long

이렇게 하게 된 이유는 암시적인 박싱 때문이다.
일반적인 Int 값이 꼭 원시 타입의 값으로 표현된다는 보장이 없다.
(코틀린은 똑같은 타입이 문맥에 따라 원시 타입과 참조 타입을 가리키기 때문에)

따라서 큰 범위의 타입으로 변환하는 경우 다른 박싱한 타입의 값을 만들어낼 수 있는 가능성이 생기고, 이로 인해 동등성(equality) 요구 조건을 만족시키지 못하게 되면서 오류를 발생시킬 수 있다.

자바에서도 박싱한 타입과 관련해서는 유사한 오류가 발생한다.

Integer i = 100;
Long l = n;	// Error: can't assign Integer to Long

 

문자열

자바와 마찬가지로 코틀린 문자열도 불변이다. 따라서 String 객체를 만들고 나면 그 안의 문자를 변경할 수 없고 문자열을 읽기만 할 수 있으며, 문자를 바꾸고 싶으면 기존 문자열을 바탕으로 새로운 문자열을 만들어야 한다.

val name = readLint()
println("Hello, $name! \n Today is ${Date()"}

코틀린은 ${}의 문자열 템플릿(String template)으로 어떤 식이든 문자열에 넣을 수 있다.
식이 간단하면 $name과 같이 {} 중괄호 생략이 가능하다.

 

문자열 연산

문자열은 ==와 !=를 사용해 동등성을 비교할 수 있다.
이들 연산은 문자열의 내용을 비교하므로, 서로 다른 두 객체 인스턴스를 비교해도 문자들의 순서와 길이가 같으면 같은 문자열로 간주한다.

val s1 = "Hello!"
val s2 = "Hel" + "lo!"
println(s1 == s2)	// true

자바의 ==와 != 연산자는 참조 동등성(referential equality)을 비교하기 때문에 실제 문자열 내용을 비교하려면 equals() 메서드를 사용해야 한다.

코틀린에서는 ==가 기본적으로 equals()를 가리키는 편의 문법(syntatic sugar)이기 때문에 ==를 사용하면 직접 equals()를 호출하므로, 따로 equals()를 호출할 필요가 없다.

코틀린에서 참조 동등성을 쓰고 싶으면 ===, !== 연산자를 사용하면 된다.


 

배열

가장 일반적인 코틀린 배열 타입은 Array< T > 다.

val a = emptyArray<String>()
val b = arrayOf("hello", "kotlin")
val c = arrayOf(1, 4, 5)

각 함수는 제네릭(Generic)하다. 즉, 호출할 때 원소 타입을 지정해야 한다.
하지만 타입 추론 덕분에 타입을 생략해도 된다.

Array< Int >를 사용하는 배열은 제대로 작동하지만 모든 수를 박싱하기에 실용적이지 못하다.
코틀린은 더 효율적인 ByteArray, ShortArray, IntArray, LongArray, CharArray, BooleanArray라는 특화된 배욜 타입을 제공한다.

val operators = CharArrayOf('+', '-', '*')
val squares = IntArray(10) { (it + 1)*(it + 1) }

자바와 달리 코틀린에는 new 연산자가 없기에 배열 인스턴스 생성이 일반 함수 호출처럼 보인다.
코틀린에서는 배열 원소를 명시적으로 초기화해야 한다.

 

배열 사용하기

배열 타입은 문자열 타입과 꽤 비슷하다.
하지만 문자열과 달리 배열에서는 원소를 변경할 수 있다.

val sampleArray = arrayOf(1, 4, 9, 16)
println("arraySize: ${sampleArray.size}")				// 4
println("arrayLastIndex: ${sampleArray.lastIndex}")		// 3

sampleArray[2] = 100	// sampleArray: 1, 4, 100, 16
sampleArray[3] += 9		// sampleArray: 1, 4, 100, 25
sampleArray[0]--		// sampleArray: 0, 4, 100, 25

 

자바와 마찬가지로 배열 타입의 변수 자체에는 실제 데이터에 대한 참조를 저장한다.
이로 인해 배열 변수에 다른 배열을 대입하면 같은 데이터 집합을 함께 공유하게 된다.

val sampleArray = arrayOf(1, 4, 9, 16)
val newArray = sampleArray
newArray[0] = 1000	// 데이터에 대한 참조를 저장하기에 newArray[0]의 값을 변경했을 때, sampleArray[0] 값도 변경된다.
println("sampleArray[0]: ${sampleArray[0]}")	// 1000

 
원본과 별도로 배열을 만들고 싶다면 copyOf() 함수를 사용해야 한다.

val sampleArray = arrayOf(1, 4, 9, 16)
val copyArray = sampleArray.copyOf()

copyArray[0] = 1000	// sampleArray에 영향이 없다.
sampleArray.copyOf(2)	// 뒤가 잘림: 1, 4
sampleArray.copyOf(5)	// 부족한 부분에 0이 채워짐: 1, 4, 9, 16, 0

코틀린 배열 타입은 자신과 같은 타입을 제외하고는 어떤 타입과도 하위 타입 관계가 성립되지 않는다.
즉 String은 Any의 하위 타입이지만 Array< String >은 Array< Any >의 하위 타입이 아니다.

 
문자열과 달리 배열에 대한 ==와 != 연산자는 우너소 자체가 아닌 참조를 비교한다.

intArrayOf(1, 2, 3) == intArrayOf(1, 2, 3)	// false
intArrayOf(1, 2, 3).contentEquals(intArrayOf(1, 2, 3))	// true
profile
hello

0개의 댓글