안녕하세요! Kotlin 기초 1편에 이어서 2편 진행해보겠습니다.
1. 반복문
for문과 while문이 쓰이는데 while문은 기존 자바와 동일한 구조이기 때문에 부연설명은 하지 않고 for문에 대해서만
집중해서 설명하도록 하겠습니다.
for(i in 1..10){
println("숫자: $i")
}
1부터 10까지 값을 출력해주는 반복문입니다. 기본적으로 1씩 증가하며 1..10 으로 하게되면 1 ≤ .. ≤ 10 이란 구조를 갖게 됩니다.
for(i in IntRange(1, 10)){
println("숫자: $i")
}
위와 동일한 결과입니다. 단, 표현하는 방법을 IntRange라는 함수를 사용하여 1부터 10까지의 범위를 만들어 출력하게 됩니다.
for(i in 1 until 11){
println("숫자: $i")
}
이것 또한 위와 동일한 결과입니다. until이라는 키워드를 쓰면서 "~까지" 라는 구문을 갖게 됩니다.
즉, 11까지만 범위를 잡는다는 것으로 1 ≤ .. < 11 의 범위를 갖게 됩니다.
for(i in 1 until 11 step(2)){
println("숫자: $i")
}
위에 내용에서 step(2)라는 키워드가 붙여졌다. 이는 2칸을 더해서 상승한다는 의미입니다. step이라는 키워드로
기본 1씩 증가하는것을 2, 3, 4, ... 이상 확대하여 증가할 수 있습니다.
for(i in 10 downTo 1 step(1)){
println("숫자: $i")
}
이렇게 하면 10부터 시작인데 1까지 가는데 step은 1씩 하며, downTo 키워드를 넣어서 하강하는 의미입니다.
즉, 10부터 1까지 1씩 줄어들면서 출력될 것입니다.
2. 엘비스 연산자
non-null 체크 할 때 사용합니다. 거두절미하고 코드로 설명드리겠습니다.
fun isTypeOfString(str: Any): String {
if(str is String) {
return str
}
else {
return "문자열 타입이 아닙니다."
}
}
인자로 받은 str 값이 문자열인지 확인하기 위해 is 라는 키워드를 써서 체크합니다.
이 코드를 엘비스 연산자를 통해 표현하면 다음과 같습니다.
fun isTypeOfString(str: Any): String {
return str as? String ?: "문자열 타입이 아닙니다."
}
이렇게 한 줄로 줄어듭니다. as라는 키워드를 써서 str이 문자열로서 맞는지, 그리고 null인지 확인하는 ? 키워드를 둠으로써
str이 문자열이 맞는지 확인하고 만약 문자열이 아닌 경우 null로 만듭니다.
그래서 그게 또 null인 경우라면? "문자열 타입이 아닙니다." 가 반환됩니다.
1) str as? String → str이 문자열이 맞나요? 아니면 null로 가시죠
2) ?: "문자열 타입이 아닙니다." → 앞의 내용이 null인가요? null이면 "문자열 타입이 아닙니다." 를 반환할게요
3. NPE 조치
Null Pointer Exception이 나지 않도록 하는 것이 Kotlin의 Java와 다른 큰 장점인 것 같다.
앞서 예제에서도 선언한 값 중 null이 될 가능성이 있다면 "?" 키워드를 이용하여 표현합니다.
반대로 null이 정말 될 수 없는 곳에는 "!!" 키워드를 이용합니다.
여기서 변수 초기화를 할 때에 null로 초기화 할 수 없어서 나중에 초기화하거나 미리 초기화 한 후
나중에 사용한다고 선언하는 것이 있는데 이를 lateInit var와 lazy val입니다.
예제는 다음과 같습니다.
lateinit var age: Int
val age2: Int by lazy {
5
}
fun main(){
age = 3
println(age)
println(age2)
}
이처럼 age라는 변수를 나중에 정의하겠다 하는 방식으로 lateinit var 변수를 쓰는 것입니다. 단 여기선 타입을 반드시 적어줘야 합니다.
이후에 age = 3 이라고 초기화를 해줍니다.
age2는 val 키워드로 설명하였기 때문에 이후 변경이 불가능하므로 바로 값을 선언해줘야 합니다.
동일하게 타입을 셋팅하고 by lazy를 통해서 람다함수 형식으로 값을 정의합니다.
람다 함수는 마지막 행이 리턴이기 때문에 5가 age2에 정의될 것입니다.
age2는 미리 정의를 해두지만, 사용하지 않으면 메모리에 할당되지 않기 때문에 유지보수에도 아주 좋습니다.
더불어 다음 내용은 null로 만들지 않기 위해서 하는 것이기 때문에 lateinit var 부분은 다음처럼 변경도 가능합니다.
var age by Delegates.notNull<Int>()
위임자를 사용해서 바꿀 수도 있는데, 이건 null이 될 수 없음을 명시하며
lateinit var과는 다르게 Primitive Type도 사용가능합니다. (예: lateinit var은 Int를 못쓰고 Integer를 써야함)
단, Delegate로 선언한 변수는 read/write non-null 프로퍼티를 반환하기 때문에 lateinit으로 선언하는 것 보다는
메모리 할당이 좀 더 드는 단점이 있습니다.
또한 더 최근에 나온 것도 lateinit이기에 개인적인 생각으로는 lateinit을 쓰는게 더 나아보이지만, 타입을
Primitive 하게 주고 싶다면 Delegates.notNull로 셋팅하는 것도 괜찮다고 생각합니다.
4. 람다함수 - SAM Interface
Kotlin은 함수형 언어로 SAM 인터페이스를 구현합니다.
SAM이란, Single Abstract Method로 함수형 인터페이스를 인자로 받을 수도 있고 응답으로 리턴할 수도 있다는 것입니다.
확장함수로 클래스에 함수 추가를 만드는 방법을 보이며 설명하겠습니다.
fun main(){
Test().hi() // hi 반환
Test().bye() // bye 반환
}
class Test(){
fun hi(){
println("hi")
}
}
fun Test().bye() = run {
println("bye")
}
이렇게 클래스의 외부에 확장함수로 사용 가능합니다. run을 통해서 해당 클래스의 확장함수로 어떻게 작동할지
명시한다면, main에서 Test().bye()로 불렀을 때 정의한대로 bye가 출력될 것입니다.
확장 함수나 객체 등과 정말 많이 쓰이는 범위지정 함수에 대해서 추가로 설명하겠습니다.
5. Scope Function
범위지정함수로 Kotlin 표준 라이브러리에서 제공하는 확장함수입니다.
간결하고 명시적이며 유지보수하기 좋습니다.
객체의 컨텍스트 내에서 실행 가능한 코드 블럭을 만드는 함수입니다.
수신객체(Instance) | 확장 함수로 호출 | 함수의 인자 | |
this(생략가능) | apply | run | with |
it(생략불가능) | also | let | |
return | 수신객체(Instance) | 람다식의 마지막행 |
1. let
: 어떠한 것에도 .let { .. } 으로 붙일 수 있음
수신객체.let { it } 이렇게 둘 수 있으며, it은 다른 값으로 바꿔 쓸 수 있다.
fun main(){
val male1: User? = User("haams", 32, "male", true)
val male1Age = male1.let {
user.age // it.age 동일한 내용
}
}
class User(
val name: String,
val age: Int,
val gender: String,
var hasGlasses: Boolean = false
)
User라는 클래스의 인스턴스를 생성할 때 (nullable)로 해서 male1을 만드는데, 여기의 age를 가져오기 위해서
male1.let { it.age } 형태로 가져올 수 있습니다.
it은 user처럼 로컬변수로 치환이 가능하며, 생략이 불가능합니다.
또한 람다식의 마지막 행이 male1Age에 할당될 것이기 때문에 age값이 male1Age에 들어갈 것입니다.
2. run
: 수신 객체.run { (this) }
람다식의 마지막 행이 반환됩니다.
run은 let과 다르게 this라는 변수를 씁니다. 이는 생략이 가능합니다.
run은 ~을 수행하도록 한다의 의미로 객체를 초기화 할 때 많이 쓰입니다.
var female1 = User("NK", 27, "female", false)
var female1Age = female1.run {
this.age
}
println(female1Age)
이처럼 female1의 인스턴스(User라는 객체의)를 가지고 오는데, 이 객체의 나이를 수행해라라는 의미로 반영됩니다.
따라서 female1Age는 female1 인스턴스 객체의 나잇값을 그대로 수행하여 27을 넣어줄 것입니다.
여기서 this는 생략이 가능하며, 람다 형식의 반환이기에 리턴 값은 마지막 행이 됩니다.
3. apply
: 개인적인 생각으로는 이게 제일 많이 쓰일 것 같다는 생각입니다.
이 apply 또한 this로 받으며 로컬 변수를 설정할 수 없습니다.
리턴 값이 수신 객체 자기 자신입니다. 수신 객체 내 변수가 변경 가능할 때 (예: var로 선언된 멤버 변수)
초기화가 가능합니다.
val maleName = male1.apply {
name // this는 생략
}
println(maleName) // 이렇게하면 maleName이라는 객체의 주소값이 나올 것입니다.
val femaleName = female1.apply {
hasGlasses = true
}
println(femaleName.name)
다음과 같이 maleName은 male1의 name을 그대로 가져오도록 셋팅을 하려 했는데, maleName은 객체 자체로 변하기 때문에
원하는 값이 나오지 않을 것입니다. (나오게 하려면 run을 사용!)
femaleName을 봤을땐 User 클래스그 hasGlasses를 var로 선언했고 변경 가능한 구조로 만들어놨습니다.
따라서 hasGlasses를 초기화 할 수 있었고, femaleName은 객체로 만들어졌기 때문에 내부의 name이라는 멤버 변수에
접근하기 위해서는 "인스턴스.변수" 형태로 호출하면 됩니다.
4. also
: 수신 객체.alse { ... } 형태로 받으며 it을 받기 때문에 로컬 변수로 셋팅이 가능합니다.
객체 자체로 반환할 것이기 때문에 apply와 동일한 느낌으로 생각하면 되겠지만, also는 apply와 다르게
주로 수신 객체에 대한 값을 확인하고 로그용으로 체크할 때 많이 쓰입니다.
val maleValue = male1.also {
println("남자 1호이름: ${it.name}, 나이: ${it.age}")
}
maleValue
미리 생성해둔 male1이라는 객체에 .also를 붙이는데, it으로 반환값을 받기 때문에 생략할 수 없으므로
it.name, it.age로 멤버 변수를 가지고 옵니다.
또한 반환 값이 수신 객체 자체이므로 그냥 바로 호출만 하면 정의한 내용이 출력될 것입니다.
5. with
: 확장 함수가 아닙니다. 사용하는 방법도 앞서 4가지와 다릅니다.
with(수신 객체) { ... } 형태로 들어가며 코드블록 내에서는 람다 함수와 동일하게 작동합니다.
즉, 마지막 줄이 리턴 값입니다.
수신 객체로 this를 받고 생략이 가능합니다. 또한 var로 선언한 멤버 변수에 대해서 초기화가 가능합니다.
val result = with(male1){
hasGlasses = false
true //(^with)
}
println(result)
다음과 같이 male1이라는 수신 객체를 with(~) { .. } 형태로 넣으면서 멤버 변수로 선언한 hasGlasses(var)의 값을 변경하고
마지막 행 true를 둠으로써 출력시 true가 반환될 수 있도록 합니다.
result는 this를 받기 때문에 생략해서 다음처럼 셋팅한 것입니다.
이처럼 확장함수나 with 키워드를 잘 사용한다면 Kotlin에서 객체 내 멤버 변수나 함수를 관리할 때
또는 초기화 하는 작업이거나 상황에 맞게 속성을 변경하는데 참 유용한 것 같습니다. 추후에 안드로이드 개발을 진행할 때
다시 쓰이는 곳이 있다면 가지고 와서 부가적으로 설명하도록 하겠습니다.
긴 글 읽어주셔서 감사드리며, 다음은 Kotlin 기초 3편(마지막 편)으로 포스팅 하도록 하겠습니다.
감사합니다.
'Android' 카테고리의 다른 글
카운트를 측정하는 앱 - 화면전환 (0) | 2024.07.19 |
---|---|
Kotiln 기초 3편 (3) | 2024.07.16 |
Kotlin 기초 1편 (0) | 2024.07.11 |
BottomNavigationView (0) | 2024.07.07 |
Jetpack Compose란..? (2) | 2024.06.30 |