안녕하세요. 이번 시간에는 코틀린의 기초 언어에 대하여 알아보겠습니다.
1. 기본 문법
- 기본 문법에서는 지역변수를 정의하는 방법과 참조, 함수 호출, 단항/이항 연산자 같은 코틀린에서 정말 기본적인 것들을 다뤄보겠습니다.
1.1 주석
- 주석의 사전적 정의는 "낱말이나 문장의 뜻을 쉽게 풀이함 또는 그런글."로 말그대로 문장의 뜻을 쉽게 풀이한 것이다.
- 주석은 프로그래밍 업무를 하면서 반드시 사용하게된다.
- 주석을 적용하면 해당 부분은 컴퓨터가 읽지 않는다.
① 주석의 종류
- 한 줄짜리 주석 : //로 시작하며 줄이 끝나면 주석도 끝난다.
- 여러 줄 주석 : /*로 시작하고 */로 끝난다.
- KDoc 주석 : /**로 시작하고 */로 끝난다.
② 예시
< 코드 >
/* 주석 */
fun main() {
println("Hello World!!") // 한 줄짜리 주석
/*
여러줄 주석
예시 입니다.
/* 주석안에 주석 입니다....1 */
/* 주석안에 주석 입니다....2 */
/* 주석안에 주석 입니다....3 */
*/
/**
* KDoc 주석 예시 입니다.
*
* */
}
< 결과 >
③ vs Java
- 여러 줄 주석 예시를 보면 자바와는 달리 코틀린에서는 여러 줄 주석을 여러 번 내포 할 수 있다.
1.2 변수 정의하기
- val 키워드 : 값을 뜻하는 value에서 유래
- 변수 식별자 : 변수에 이름을 부여하고, 나중에 이를 가르킬 때 사용.
- 변수의 초깃값 : = 기호 뒤에 온다.
① 예시
< 코드 >
/* 변수 정의하기 */
fun main() {
val nAge = 32
println("내 나이는 $nAge" + "살 입니다.")
}
< 결과 >
② vs Java
- 자바와 달리 문장 끝에 세비콜론인 ';'를 생략해도 된다.
1.3 식별자
- 자바 식별자와 비슷하다.
- 오직 문자, 숫자, 밑줄 문자( _ )만 포함한다.
- 숫자로 식별자를 시작할 수 없다.
- 하드 키워드를 식별자로 사용할 수 없다.
① 예시
< 코드 >
/* 식별자 */
fun main() {
val nAge = 32
val nAge2 = 31
val TEST_JJY = 30
// val 2nAge = 31 // Error
// val fun = 29 // Error
// val $ = 28 // Error
var `fun` = 29
println("nAge = $nAge")
println("nAge2 = $nAge2")
println("TEST_JJY = $TEST_JJY")
println("`fun` = $`fun`")
}
< 결과 >
② vs Java
- 달러 기호( $ )를 식별자로 쓸수 없다.
- 작은역따옴표( ` )로 감싼 실별자로, 두 작은역따옴표 사이에는 빈 문자열을 제외한 아무문자열 가능하다.
1.4 가변 변수
- 불변 변수인 val 키워드는 한번 초기화 하면 다시는 값을 대입할 수 없다. ( Java의 final 변수와 비슷 )
- 가변 변수인 var 키워드를 사용하면 원할 때 변수값을 얼마든지 바꿀 수 있다.
- 전위/후위 연산자 사용가능 하다.
① 예시
< 코드 >
/* 가변변수 */
fun main() {
// 불변 변수 재대입 예시
/*
val nSum = 1
nSum = nSum + 2
*/
// 가변 변수 예시
var nSum = 1
nSum = nSum + 2
println("nSum = $nSum")
// 가변 변수 잘못된 타입 대입 예시
/*
var nTest = 1
nTest = "Test"
*/
// 복합 대입 연산 예시
var nSum2 = 1
nSum2 += 2
println("nSum2 = $nSum2")
// 연쇄 대입문 사용 불가 예시
var a = 1
var b = 2
var c = 3
// a = b = c // Error
// 전위 후위 연산자
var nNum = 1
println("nNum = " + nNum++)
println("nNum = " + ++nNum)
println("nNum = " + --nNum)
println("nNum = " + nNum--)
}
< 결과 >
② vs Java
- 코틀린에서는 자바와 다르게 a = b = c 같은 연쇄 대입문을 쓸 수 없다. ( 복합 대입 연산도 마찬가지 )
1.5 식과 연산자
① 코틀린 식 분류
- 각 타입에 속하는 구체적인 값을 표현하는 리터럴
- 변수/프로퍼티 참조와 함수 호출
- 전위와 후위 단항 연산
- 이항 연산
② 연산자 우선순위
우선순위 | 분류 | 연산자 |
1 | 후위 | ++, --, . |
2 | 전위 | +, -, ++, --, ! |
3 | 곱셈 | *, /, % |
4 | 덧셈 | +, - |
5 | 중위 | 이름이 붙은 중위 연산자들 |
6 | 비교 | <, >, <=, >= |
7 | 동등 | ==, != |
8 | 논리곱 | && |
9 | 논리합 | || |
10 | 대입 | =, +=, *=, /=, %= |
2. 기본 타입
- 기본 타입에서는 수, 문자, 불 값 등을 표현하는 코틀린 타입에 대해 다뤄보겠습니다.
2.1 정수 타입
① 정수 타입 종류
이름 | 크기(바이트) | 범위 | vs Java |
Byte | 1 | -128 ~ 127 | Byte |
Short | 2 | -32768 ~ 32767 | Short |
Int | 4 | -2^31 ~ 2^31 - 1 | Int |
Long | 8 | -2^63 ~ 2^63 - 1 | Long |
- 가장 간단한 리터럴 정수는 10진수 이다.
- 리터럴에 "_" 를 넣어서 가독성을 높일 수 있다.
- 리터럴에 "L"이다 "l"을 접두사로 붙이면 Long 타입이 된다.
- 2진수( 0b )나 16진수( 0x )를 붙여서 리터럴 작성이 가능하다.
- 수 리터럴 경우 0이 아닌이상 0을 맨앞에 올 수 없다.
- 음수는 단항 음수 연산자( - )를 사용 한다.
- 각 정수 타입에는 최솟값과 최댓값을 포함하는 상수 정의가 들어있다.\
② 예시
< 코드 >
/* 정수 타입 */
fun main() {
val nYear = 2023
val nBigNum = 123_456_789
val nLongNum = 100L
// val nIntNum: Int = 100L // Error
val nBin = 0b10101
val nHex = 0xF9
val nZero = 0
// val nZero2 = 01 // Error
val nNeg = -10
val nShortMin = Short.MIN_VALUE
val nIntMax = Int.MAX_VALUE + 1
println("10진수형 리터럴 : $nYear")
println("리터럴에 _ 넣기 : $nBigNum")
println("Long타입 변환 : $nLongNum")
println("2진수형 리터럴 : $nBin")
println("16진수형 리터럴 : $nHex")
println("0 리터럴 : $nZero")
println("단항 음수 연산자 : $nNeg")
println("Short 정수 타입 최솟값 : $nShortMin")
println("Int 정수 타입 최댓값 : $nIntMax")
}
< 결과 >
2.2 부동소수점 수
- Java와 동일하게 Float와 Double 제공
- 10진 소수 형태로, 정수 부분과 소수 부분을 나눠 소수점( . )을 찍어 놓았다.
- 정수 부분이 비어있는 경우 정수 부분을 0으로 간주한다.
- 코틀린은 과학적 표기법인 e나 E 뒤에 10을 몇 번 거듭제곱하는지 알려주는 숫자를 허용한다.
- 디폴트로 부동소수점 리터럴은 Double이며 f나 F를 뒤에 사용 시 Float 타입이 된다.
① 예시
< 코드 >
/* 부동소수점 수 */
fun main() {
val nPi = 3.14
println("10진소수 형태 : $nPi")
val nQuarter = .25
println("10진소수의 정수부분 0생략 가능 : $nQuarter")
val nPi100 = 0.314E3 // 0.314 * 1000
val nPiOver100 = 3.14E-2 // 3.14 / 100
println("거듭제곱 표현1 : $nPi100")
println("거듭제곱 표현2 : $nPiOver100")
val nPiF = 3.14F
println("Float 형변환 : $nPiF")
}
< 결과 >
② vs Java
- Double이나 Float의 16진 리터럴을 지원하지 않는다.
- Java에서는 D나 d를 붙여 강제로 Double을 만들었지만 코틀린에서는 허용하지 않으며 디폴트가 Double 이다.
2.3 산술 연산
① 산술 연산 종류
연산 | 뜻 |
+(단항) | 원래 값과 같은 값 |
-(단항) | 원래 값의 부호를 반전한 값 |
+ | 덧셈 |
- | 뺼셈 |
* | 곱셈 |
/ | 나눗셈 |
% | 나머지 |
- 코틀린에는 정수 나눗셈인 floorDiv()함수와 정수 나머지인 mod() 함수가 있다.
- 단항 +/- 연산의 겨로가는 인자들의 타입과 같다. 다만 Byte와 Short의 경우에는 Int를 내놓는다.
- Double > Float > Long > Int > Short > Byte 순으로 크다.
② 예시
<코드>
/* 산술연산 */
fun main() {
println("floorDiv함수 예시 : " + 9.floorDiv(4))
println("mod함수 예시 : " + 9.mod(4))
val nByte: Byte = 1
val nShort: Short = 1
val nInt: Int = 1
val nFloat = 1.5f
val nDouble = 1.5
println("Byte형 -연산 : " + -nByte) // -1: Int
println("Short형 -연산 : " + -nShort) // -1: Int
println("Int형 -연산 : " + -nInt) // -1: Int
println("Float형 -연산 : " + -nFloat) // -1.5: Float
println("Double형 -연산 : " + -nDouble) // -1.5: Double
}
<결과>
2.4 비트 연산
- Int와 Long는 비트 수준의 연산을 지원한다.
① 비트 연산 종류
연산 | 뜻 | vs Java |
shl | 왼쪽 시프트(shift) | << |
shr | 오른쪽 시프트(shift) | << |
ushr | 부호 없는 오른쪽 시프트 | >>>> |
and | 비트 곱(AND) | & |
or | 비트 합(OR) | | |
xor | 비트 배타합(XOR) | ^ |
inv | 비트 반전(inversion) | ~ |
2.5 문자 타입
- Char 타입으로 한 글자를 표현하며 16비트이다.
- 리터럴을 작은따옴표( ' ) 사이에 문자를 넣어 표현 한다.
- \t는 탭, \n은 새줄, /r은 캐리지 리턴, \'는 작은따옴표, \"는 큰따옴표, \\는 역슬래시, \$는 달러표시, \u 다음에 네 자리 16진수를 넣어 임의의 유니코드 문자를 넣을 수 있다.
- +/- 연산자를 사용해 새 문자를 반환할 수 있다.
- ++/-- 연산자를 사용해 새문자를 반환할 수 있다.
① 예시
< 코드 >
/* 문자 타입 Char */
fun main() {
val sJ = 'J'
println("Char형 표현: $sJ")
val sTab = '\t'
val sNewLine = '\n'
val sCarriageRetun = '\r'
val sSingleQuote = '\''
val sDoubleQuote = '\"'
val sBackslash = '\\'
val sDollar = '\$'
println("탭 $sTab 탭")
println("새 $sNewLine 줄")
println("캐리지 $sCarriageRetun 리턴")
println("$sSingleQuote 작은따옴표 $sSingleQuote")
println("$sDoubleQuote 큰따옴표 $sDoubleQuote")
println("백슬래쉬 : $sBackslash")
println("달러 : $sDollar")
val sPi = '\u03C0'
println("16진수 파이 : $sPi")
var sA = 'a'
var sY = 'y'
println("+/- 연산자 사용하여 새문자 생성 : " + sA + 7)
println("++/-- 연산자 사용하여 새문자 생성 : " + --sY)
}
< 결과 >
② vs Java
- Java에서는 Char형 산술연산 결과가 정수로 반환되지만 코틀린에서는 Char형으로 반환한다.
2.6 수 변환
- 각 수 타입마다 값을 다른 수 타입으로 변환하는 연산이 정의돼 있다.
- 정수 타입 사이의 변환은 대상 타입이 더 큰 범위를 담는 타입인 경우 손실 없이 수행된다.
- 부동소수점 수 타입과 관련된 경우, 일반적으로 대상 타입과 무관하게 정밀도를 잃을 수 있다.
① 예시
< 코드 >
/* 수 변환 */
fun main() {
val nNumber = 1
val nFloat = 1.5
println("Byte 변환 : " + nNumber.toByte())
println("Short 변환 : " + nNumber.toShort())
println("Int 변환 : " + nNumber.toInt())
println("Long 변환 : " + nNumber.toLong())
println("Float 변환 : " + nNumber.toFloat())
println("Double 변환 : " + nNumber.toDouble())
val nNumber2 = 945
println("Byte 변환 : " + nNumber2.toByte()) // 손실
println("Short 변환 : " + nNumber2.toShort())
println("Char 변환 : " + nNumber2.toChar())
println("Long 변환 : " + nNumber2.toLong())
val nFloat2 = 2.5
println("Int 변환 : " + nFloat2.toInt()) // 소수점 손실
val nNumber3 = 1_000_000_000_000
println("Flat 변환 후 Long 변환 : " + nNumber3.toFloat().toLong()) // 정밀도 손실
}
< 결과 >
② vs Java
- 범위가 큰 타입이 사용돼야 하는 문맥에 범위가 작은 타입을 사용 할 수 없다.
2.7 불 타입과 논리 연산
- 참(true) or 거짓(false) 중 하나로 판명되는 불(Boolean) 타입과 놀리 연산을 제공.
- !은 논리부정, or은 즉시계산 논리합, and는 즉시계산 논리곱, xor은 즉시계산 논리배타합, ||은 지연계산 논리합, &&은 지연계산 논리곱
① vs Java
- &와 | 연산자를 제공하지 않고 and와 or가 대체한다
2.8 비교와 동등성
- ==(같다), !=(같지 않다), <(~보다 작다), <=(~보다 작거나 같다), >(~보다 크다), >=(~보다 크거나 같다) 비교 연산 제공
- 두인자가 모두 같은 타입일 때만 ==와 != 허용.
- 모든 수 타입의 값은 서로 비교 연산 가능
- char와 Boolean 값도 비교연산 가능
- NaN값은 어떤값과도 같지 않고 무한대를 포함한 다른 어떤 값보다 작지도 않고 크지도 않다.
① vs Java
- Java에서는 Boolean형은 동등성 비교만 가능하다.
3. 문자열
- String 타입은 문자들로 이뤄진 문자열을 표현한다.
- String 객체를 만들고나면 그 안에 문자를 수정할 수 없으며, 문자를 바꾸려면 새로운 문자열을 만들어야 한다.
3.1 문자열 템플릿
- 문자열 리터럴을 정의하는 가장 간단한 방법은 큰따옴표( " )로 문자열을 감싸는 것이다.
① 예시
< 코드 >
/* 문자열 템플릿 */
fun main() {
val sHelloWorld = "Hello, World"
println("문자열 기본 형태 : $sHelloWorld")
}
< 결과 >
3.2 기본 문자열 연산
- length와 문자열의 마지막 문자 인덱스를 표현하는 lastIndex 프로퍼티를 제공한다.
- 문자열의 인덱스는 0부터 시작한다.
- + 연산자를 이용해 두 문자열을 연결 할 수 있다.
- ==, !=를 사용해 동등성 비교가 가능하다. ( 순서와 길이가 같으면 같은 문자열로 간주한다. )
- <, >, <=, >= 같은 연사자를 사용해 비교 가능하다.(사전 순서)
① 유용 함수
함수 | 뜻 |
isEmpty() | 문자열이 비어있는지 검사 |
isNotEmpty | 문자열이 안비어있는지 검사 |
substring | 부분 문자열 추룰 |
startsWith | 접두사나 인지 검사 |
endsWith | 접미사 인지 검사 |
indexOf | 인자로 받은 문자나 문자열이 수신 객체인 문자열에 나타나는 첫번째 인덱스를 반환한다. |
② 예시
< 코드 >
/* 기본 문자열 연산 */
fun main() {
val sHello = "Hello"
println("문자열 길이 : ${sHello.length}")
println("문자열 마지막인덱스 : ${sHello.lastIndex}")
println("문자열 인덱스 값 : ${sHello[0]}")
val sWorld = "World"
println("문자열 +연산 : ${sHello + sWorld}")
println("문자열 비교1 : ${sHello == sWorld}")
println("문자열 비교2 : ${sHello != sWorld}")
println("문자열 비교3 : ${sHello > sWorld}")
println("문자열 유용함수1 : ${sHello.isEmpty()}")
println("문자열 유용함수2 : ${sHello.isNotEmpty()}")
println("문자열 유용함수3 : ${sHello.substring(1, 3)}")
println("문자열 유용함수4 : ${sHello.startsWith("Hel")}")
println("문자열 유용함수5 : ${sHello.endsWith("lo")}")
}
< 결과 >
4. 배열
- 배열은 내장된 코틀린 데이터 구조로, 미리 정해진 숫자만큼 같은 타입의 원소를 모아서 저장하고 각각을 인덱스로 참조할 수 있게 해준다.
4.1 배열 정의하기
- 배열 구조를 구현하는 가장 일반적인 코틀린 타입은 Array<T>이고 T는 원소의 타입을 나타낸다.
- 중괄호를 사용하여 람다식 배열이 가능하다.
① 예시
< 코드 >
import java.util.*
/* 배열 정의 */
fun main() {
val aEmpty = emptyArray<String>()
val aTest1 = arrayOf("Hellow", "World")
val aTest2 = arrayOf(1, 3, 5, 7)
println("aEmpty 배열 갯수 : " + aEmpty.size)
println("aTest1 배열 갯수 : " + aTest1.size)
println("aTest2 배열 갯수 : " + aTest2.size)
val aLamda = Array(5, {i -> i*2}) // 크기가 5인 짝수형 람다식 배열 표현
println("aLamda: ${Arrays.toString(aLamda)}")
}
< 결과 >
② vs Java
- Java와 달리 new 연산자가 없어 일반 함수 호출처럼 보인다.
- 코틀린에서는 배열 원소를 명시적으로 초기화 해야한다.
4.2 배열 사용하기
- size와 lastIndex 프로퍼티가 있다는 점에서 문자열 타입과 비슷하다.
- 잘못된 인덱스를 사용하면 런타임에 IndexOutOfBoundsException 예외가 발생한다.
- 문자열과는 달리 배열에서 원소를 변경할 수 있다.
- 자바와 마찬가지로 배열 타입의 변수 자체에는 실제 데이터에 대한 참조를 저장한다.
- 원본과 별도로 배열을 만들고 싶다면 copyOf() 함수를 사용한다.
- 배열 타입 변수에 타입이 다른 배열을 대입할 수 없다
- 배열을 생성하고나면 그 길이를 바꿀 수 없지만 + 연산을 사용해 새로운 배열을 만들 수 있다.
- 문자열과는 달리 ==와 != 연산자는 원소 자체를 비교하지 않고 참조를 비교한다.
- 배열 원소를 비교하고 싶으면 contentEquals() 함수를 사용한다.
① 유용 함수
함수 | 뜻 |
isEmpty() | 배열이 비어있는지 검사 |
isNotEmpty | 배열에 원소가 있는지 검사 |
indexOf | 인자와 일치하는 최초 배열 아이템의 인덱스를 반환(일치하는게 없으면 -1 반환) |
② 예시
< 코드 >
import java.util.*
/* 배열 사용하기 */
fun main() {
val aNumber = arrayOf(1, 3, 5, 7, 9)
println("aNumber 배열 크기 : " + aNumber.size)
println("aNumber 배열 마지막 인덱스 : " + aNumber.lastIndex)
println("aNumber 배열 값 : " + aNumber[2])
aNumber[2] = 5000
println("aNumber 배열 값 변경 : " + aNumber[2])
val aNumber2 = aNumber
aNumber2[0] = 1000
println("aNumber 배열 값도 같이 변경 : " + aNumber[0])
val aNuber3 = aNumber.copyOf()
aNuber3[1] = 3000
println("aNumber 배열 값은 변경 안됨 : " + aNumber[1])
println("aNuber3 배열 값만 변경 : " + aNuber3[1])
// val aNumber = arrayOf("One", "Three", "Five", "Seven", "Nine") // Errors
val aNumber4 = arrayOf(1, 3, 5, 7, 9) + 11
val aNumber5 = arrayOf(1, 3, 5, 7, 9) + arrayOf(11, 13)
println("aNumber4: ${Arrays.toString(aNumber4)}")
println("aNumber5: ${Arrays.toString(aNumber5)}")
println("배열은 ==/!= 연산자는 참조 비교 : " + (aNumber == aNumber))
println("배열은 contentEquals함수로 값 비교 : " + aNumber.contentEquals(aNumber))
println("배열 유용함수1 : " + aNumber.isEmpty())
println("배열 유용함수2 : " + aNumber.isNotEmpty())
println("배열 유용함수3-1 : " + aNumber.indexOf(9))
println("배열 유용함수3-2 : " + aNumber.indexOf(10))
}
< 결과 >
이번시간에는 코트린의 기본에 대해 처음으로 맛보았습니다. 다음시간에는 코틀린 함수에 대해 알아보는 시간을 갖겠습니다. 감사합니다 :)
'조재연의 Kotlin 개꿀떡 > 조재연의 Kotlin 기초 개꿀떡' 카테고리의 다른 글
Kotlin 기초 - 널 가능성 (0) | 2023.04.03 |
---|---|
Kotlin 기초 - 클래스 정의 (0) | 2023.04.03 |
Kotlin 기초 - 기초 함수 (0) | 2023.03.13 |
Kotlin 기초 - 안드로이드 스튜디오(Android Studio)로 문법 연습하기 (0) | 2023.01.28 |
Kotlin 기초 - 코틀린(Kotlin)이란? (0) | 2023.01.17 |