[Kotlin] 2장. 변수와 자료형, 연산자

2장. 변수와 자료형, 연산자

변수

  • val (value) - 불변형 (immutable) : 초기화하면 값을 바꿀 수 없는 변수
  • var (variable) - 가변형 (mutable) : 초기값을 넣었다 할지라도 값을 바꿀 수 있는 변수
1
val username: String = "Kildong"

<선언 키워드>, <변수 이름>, <자료형>, <값>

1
2
3
4
fun main() {
val username: String = "Kildong"
println("username: $username")
}
1
2
3
4
5
 fun main() {
val username: String = "Kildong"
username = "Dooly"
println("username: $username")
}
1
2
3
4
5
fun main() {
var username: String = "Kildong"
username = "Dooly"
println("username: $username")
}

코틀린 컴파일러는 변수의 자료형을 추론 가능하다.

1
2
3
4
5
6
7
fun main() {
var username: String = "Kildong"
username = "Dooly"
var count = 3
//어떤 값인지 컴파일러도 알 수 있으면 굳이 자료형을 정의하지 않아도 선언 된다.
println("username: $username, count: $count")
}

코틀린은 참조형(Reference Type)으로 자료형을 사용한다.

클래스 형태 (Int, Long, Float, Double 등) 로 자료형을 사용하며 이를 통해 선언된 자료형은 하나의 객체로써 사용가능하다.

동적 메모리 공간(Heap)에 데이터를 둔 다음 이것을 참조하는 자료형을 말한다.

물론 기본형(Primitive Type)보단 성능이 떨어진다.

하지만 기본형 사용을 남발하는 것도 성능을 저하시킬 수 있기 때문에 코틀린은 기본형 자체를 쓰지않게끔 하고 있고 코딩할 땐 참조형을 통해서만 데이터를 다루게끔 한다.

이러한 참조형은 성능 최적화에 따라 JVM에서 실행하기 위해 코틀린 컴파일러에서 기본형으로 변환된다.

자료형 사용의 예

자료형 생략

1
2
3
4
val num05 = 127 //Int형으로 추론
val num06 = -32875 //Int형으로 추론
val num07 = 2147483647 //Int형으로 추론
val num08 = 9254863251568521263 //Long형으로 추론

접미사 접두사 사용

1
2
3
4
val exp01 = 123 //Int형으로 추론
val exp02 = 123L //접미사 L을 사용해 Long형으로 추론
val exp03 = 0x0F //접두사 0x를 사용해 16진수 표기가 사용된 Int형으로 추론
val exp04 = 0b00001011 //접두사 0b를 통해 2진 표기사 사용된 Int형으로 추론

작은 값의 사용

1
2
3
val exp08: Byte = 127 //명시적으로 자료형을 지정(Byte)
val exp09 = 32654 //명시적으로 자료형을 지정하지 않으면 Short형 범위의 값도 Int형으로 추론
val exp10: Short //명시적으로 자료형을 지정 (Short)

부호 없는 정수 자료형

1
2
3
4
val uint : UInt = 153u
val ushort: UShort = 65535u
val ulong: ULong = 46332342uL
val ubyte: UByte = 255u

큰 수를 읽기 쉽게 하는 방법

1
2
3
4
val number = 1_000_000
val cardNum = 1234_1234_1234_1234L
val hexVal = 0xAB_CD_EF_12
val bytes = 0b1101_0010

실수 자료형

1
2
val exp01 = 3.14 //Double형으로 추론 (8바이트)
val exp02 = 3.14F //식별자 F에 의해 Float형으로 추론 (4바이트)
1
2
3
4
5
6
7
8
fun main() {
var num: Double = 0.1

for (x in 0..999) {
num += 0.1
}
println("num = $num") //num = 100.09999999999859
}

100이 아니라 소수점 뒤에 이상한 수들이 들어가 있는 것을 볼 수 있다.

Double형의 지수부와 가수부가 제한이 있기 때문에 이런 현상이 일어난다. 이런 것에 주의하자.

크기 범위와 기타 자료형

1
2
3
4
5
6
7
8
fun main() {
println("Int: ${Int.MIN_VALUE}~${Int.MAX_VALUE}")
println("Byte: ${Byte.MIN_VALUE}~${Byte.MAX_VALUE}")
println("Short: ${Short.MIN_VALUE}~${Short.MAX_VALUE}")
println("Long: ${Long.MIN_VALUE}~${Long.MAX_VALUE}")
println("Float: ${Float.MIN_VALUE}~${Float.MAX_VALUE}")
println("Double: ${Double.MIN_VALUE}~${Double.MAX_VALUE}")
}
1
2
3
4
5
6
Int: -2147483648~2147483647
Byte: -128~127
Short: -32768~32767
Long: -9223372036854775808~9223372036854775807
Float: 1.4E-45~3.4028235E38
Double: 4.9E-324~1.7976931348623157E308

문자열

1
2
3
4
5
6
7
8
9
10
fun main() {
var str1 : String = "Hello"
var str2 = "World"
var str3 = "Hello"

println("str1 === str2 : ${str1 === str2}")
println("str1 === str3 : ${str1 === str3}")
}
//str1 === str2 : false
//str1 === str3 : true

String으로 선언되며 Heap의 String Pool이라는 공간에 구성.

=== 연산자는 코틀린의 특수한 연산자로 참조까지 비교할 수 있는 연산자이다.

“Hello”와 “World” String은 다르기 때문에 각각 다른 주소 공간에 할당되어 참조는 같지 않다.

str3는 str1과 똑같은 문자이기 때문에 새로운 주소공간에 할당되는 것이 아니라 같은 참조인 것을 볼 수있다.

1
2
3
4
5
6
fun main() {
var a = 1
val str1 = "a = $a"
val str2 = "a = ${a+2}"
println("str1 : \"$str1\", str2: \"$str2\"") //문자열 출력에 ""을 포함하여 출력
}

null을 허용한 변수 검사

코틀린의 기본 변수 선언은 null을 허용하지 않는다.

1
2
val a : Int = 30
var b : String = "Hello"

null 가능한 선언

1
2
val a : Int? = null
var b : String? = null

?를 넣어주어 null이 가능한 형태의 변수를 선언할 수 있다.

이러한 변수들은 Null 가능성을 검사해 주는 것이 좋다.

그렇지 않으면 NPE NullPointerException가 발생할 수 있다.

  • 단순 출력은 상관없으나 null인 상태에서 연산되는 멤버에 접근할 때

코틀린은 기본적으로 null을 허용하지 않기 때문에 비교적 안전한 프로그래밍이 가능하다.

1
2
3
4
5
fun main() {
var str1: String?
str1 = null
println("str1 : $str1, length: ${str1?.length}")
}

str1이 null type이기 때문에 널 가능성이 존재. 그래서 바로 멤버 접근이 되지 않는다.

str1이 null일 가능성을 염두에 두고 검사코드를 넣던지 non-null asserted call(!!.) 또는 safe(?.)을 넣어야 한다.

Safe call (세이프 콜)

*str1?.length : str1이 null이라면 뒤의 .length는 실행하지 않는다*

  • null일 때 → str1 : null, length: null
  • null이 아닐 때 (“Hello”) → str1 : Hello, length: 5

Non-null asserted call (non-null 단정 기호)

*str1!!.length : str1은 null 일리 없다! (null이더라도 check하지 않는다)*

  • null일 때 : NullPointerException 발생

최대한 non-null asserted call 연산자는 사용하지 않는 것이 좋다.

검사와 자료형을 변환해보기

1
2
val a : Int = 1
val b : Double = a.toDouble()

변환 메서드의 종류

  • toByte
  • toLong
  • toShort
  • toFloat
  • toInt
  • toDouble
  • toChar

이중 등호와 삼중 등호의 사용

  • == : 값만 비교하는 경우
  • === : 값과 참조 주소를 비교할 때

자바에서는 == 으로 값과 참조 주소를 비교하므로 주의한다.

참조 주소가 달라지는 경우

1
2
3
4
5
6
7
val a : Int = 128 //널이 가능하지 않은 자료형(기본형, Stack에 할당)
val b : Int? = 128 //널이 가능한 자료형 (기본형이 아니라 객체, Heap에 할당)

println(a==b) //true
println(a===b) //false

//값은 같지만 참조는 다른 경우
1
2
코틀린에서는 참조형으로 선언한 변수의 값이 -128 ~ 127 범위에 있으면 캐시에 그 값을 저장함.
그 범위를 넘어서면 객체를 생성하여 만들어진다.

Smart Cast

구체적으로 명시되지 않은 자료형을 자동 변환

  • 값에 따라 자료형을 결정
  • Number 형은 숫자를 저장하기 위한 특수한 자료형으로 스마트 캐스트 됨
1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
var test : Number = 12.2 //12.2로 인해 test는 Float형으로 스마트 캐스트
println("$test")

test = 12 //Int 형으로 스마트 캐스트
println("$test")

test = 120L //Long 형으로 스마트 캐스트
println("$test")

test += 12.0f //Float형으로 스마트 캐스트
println("$test")
}

is 키워드를 사용한 검사

1
2
3
4
5
6
7
8
fun main() {
val num = 256
if (num is Int) {
print(num)
} else if (num !is Int) {
print("Not a Int")
}
}

Any : 자료형이 정해지지 않은 경우

  • 모든 클래스의 뿌리 - Int나 String은 Any형의 자식 클래스이다.
  • Any는 언제든 필요한 자료형으로 자동 변환 (스마트 캐스트)
1
2
3
4
5
fun main() {
var a: Any = 1 //Any형 a는 1로 초기화 될때 Int형이 됨
a = 20L //Int형이었던 a는 변경된 값 20L에 의해 Long이 된다.
println("a: $a type: ${a.javaClass}") //a의 자바 기본형을 출력하면 long이 나옴.
}

묵시적 변환 이해하기

Any형은 자료형이 특별히 정해지지 않은 경우에 사용한다.

코틀린의 Any형은 모든 클래스의 뿌리입니다. 우리가 자주 사용한 Int나 String 그리고 사용자가 직접 만든 클래스까지 모두 Any형의 자식 클래스이다.

즉,코틀린의 모든 클래스는 바로 이 Any 형이라는 슈퍼 클래스(Superclass)를 가진다.

따라서 Any를 사용해 변수를 선언하면 해당 변수는 어떤 형으로도 변환할 수 있게 되는 것이다.

1
2
3
4
5
6
7
8
9
10
11
fun checkArg(x : Any) {
if (x is String)
println("x is String: $x")
if (x is Int)
println("x is Int: $x")
}

fun main() {
checkArg("Hello")
checkArg(3)
}

비트 연산을 위한 비트 메서드

비트 연산을 위한 메서드를 알아보면, 이런 메서드들은 메서드처럼 사용해도 되지만 연산자처럼 사용할 수도 있다. 예를 들어 비트 전체를 왼쪽으로 이동시키는 shl 메서드는 4.shl(1) 또는 4 shl 1과 같은 방법으로 사용할 수 있다.

4 shl 1과 같이 멤버에 접근하는 점(.) 연산자와 소괄호를 생략하는 표현식을 중위 표현식이라고 부른다.

[비트 연산자]

표현식 설명
4.shl(bits) 4를 표현하는 비트를 bits만큼 왼쪽으로 이동(부호 있음)
7.shr(bits) 7을 표현하는 비트를 bits만큼 오른쪽으로 이동(부호 있음)
12.ushr(bits) 12를 표현하는 비트를 bits만큼 오른쪽 이동(부호 없음)
9.and(bits) 9를 표현하는 비트와 bits를 표현하는 비트로 논리곱 연산
4.or(bits) 4를 표현하는 비트와 bits를 표현하는 비트로 논리합 연산
24.xor(bits) 23를 표현하는 비트와 bits를 표현하는 비트의 배타적 연산
78.inv 78을 표현하는 비트를 모두 뒤집음

1
2
3
4
5
6
7
8
9
fun main() {
val x = 4 //0100(2) 4(10)
val y = 0b0000_1010 //2진법 5
val z = 0x0f // 0b0000_1111(2) 15(10)

println("x shl 2 -> ${x.shl(2)}") //16(10) : 0000_0100 -> 0001_0000
println("x inv -> ${x.inv()}")
// -5(10) : 부호비트가 1로 바뀌었기 때문에 음수, 1111.. 1011
}

Reference

네이버 커넥트 재단 부스트코스 코틀린 프로그래밍 기본 1

Author

MoonDoni

Posted on

2021-07-11

Updated on

2021-07-11

Licensed under

댓글