[Kotlin] 고급 문법 정리
참고 영상
https://www.inflearn.com/course/코틀린-요약-강의/dashboard
1. 람다식 (Lambda Expression)
람다식은 마치 value처럼 다룰 수 있는 익명함수이다.
1) 메소드의 파라미터로 넘겨줄 수 있다.
2) 리턴값으로 사용할 수 있다.
람다의 기본 정의
val lambdaName : Type = { argumentList -> codeBody }
val lambdaName = { argumentName : Type -> codeBody }
이미지 출처: https://youtu.be/z8prdZgk4kA?t=317
//val square : (Int) -> (Int) = {number -> number*number}
val square = {number: Int -> number*number}
val nameAge = {name: String, age: Int ->
// 표현식이 여러 줄인 경우, 마지막 줄을 리턴함.
"My name is $name I'm $age"
}
fun main(){
println(square(12))
println(nameAge("Haeun", 22))
}
144
My name is Haeun I'm 22
1-2. 확장 함수 (extension function)
확장 함수는 클래스에 함수를 추가하고 싶을 때 사용하는 익명함수이다. 예를 들어 String 클래스를 확장하는 익명함수를 만들어보자.
fun main(){
val a = "haeun said"
val b = "mike said"
println(a.pizzaIsGreat())
println(b.pizzaIsGreat())
}
val pizzaIsGreat : String.() -> String = {
// this는 이 확장함수가 불러올 객체 (여기서는 String)
"$this : pizza is the best!"
}
haeun said : pizza is the best!
mike said : pizza is the best!
확장 함수의 기본 형식
val 람다명 : 클래스명.(입력 타입) -> 출력 타입 = { 함수 내용 및 결과 반환 }
cf) 코틀린에서는 여러 개의 값을 리턴할 수 있을까 궁금해서 찾아보니, 무작위적인 튜플을 만들 수는 없고 Pair나 Triple 클래스, 배열, 리스트를 사용할 수 있다고 한다. 보다 일반적인 건 데이터 클래스를 사용하는 것!
1-3. 람다의 리턴
fun main() {
val a = "haeun said"
val b = "mike said"
// 확장함수의 매개변수가 없는 경우
println(a.pizzaIsGreat())
println(b.pizzaIsGreat())
// 확장함수의 매개변수가 있는 경우
println(extendString("haeun", 22))
println(calculateGrade(100))
}
// String 클래스를 확장하는 익명함수
val pizzaIsGreat : String.() -> String = { // 매개변수가 없는 경우
// this는 확장함수가 불러올 String 객체 자체
"$this : pizza is the best!"
}
fun extendString(name: String, age: Int) : String {
// 매개변수가 1개인 경우 it를 사용한다.
val introduceMyself: String.(Int) -> String = {
// this는 확장함수가 불러올 String 객체 자체
"I am $this and $it years olds"
}
return name.introduceMyself(age)
}
// 람다의 리턴
val calculateGrade : (Int) -> String = {
when(it){ // when이 expression으로 쓰일 때는 else문 필수
in 0..40 -> "fail"
in 41..70 -> "pass"
in 71..100 -> "perfect"
else -> "Error"
}
}
haeun said : pizza is the best!
mike said : pizza is the best!
I am haeun and 22 years olds
perfect
1-4. 람다를 표현하는 2가지 방법
fun main() {
// 1. 람다식 정의 후 사용하기
val lambda = { number: Double ->
number == 4.3213
}
println(invokeLambda(lambda)) // 5.2343 == 4.3213 ? false
// 2. 람다 리터럴 (중괄호를 바로 사용하는 경우)
println(invokeLambda({ it > 3.22 })) // 5.2343 > 3.22 ? true
}
// 람다를 표현하는 2가지 방법
// 람다식은 마치 'value처럼' 사용할 수 있는 익명함수여서
// 함수의 매개변수 또는 리턴값이 될 수 있다.
fun invokeLambda(lambda: (Double) -> Boolean) : Boolean {
return lambda(5.2343)
}
1-5. 이벤트를 처리하는 리스너에서 람다식 사용하기
기존의 자바는 xml에서 사용한 모든 뷰를 메모리에 객체화시키기 위해서 findViewById 메소드로 인플레이트를 시키는 과정을 거쳐야 했다. 이것은 매우 매우 번거로웠는데 아래 코드처럼 앱 단위의 build.gradle 파일에 '코틀린 안드로이드 익스텐센 플러그인'을 적용하면, 이 과정을 생략할 수 있다!!
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions' // 익스텐션 플러그인 적용
}
아래 코드처럼 setOnClickListener에서도 람다식을 사용하면 획기적으로 코드 길이를 줄일 수 있다.
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener(object: View.OnClickListener{
override fun onClick(p0: View?) {
// to do...
}
})
// View.OnClickListener처럼
// 코틀린 인터페이스가 아닌 자바 인터페이스이고,
// 인터페이스의 메소드가 하나만 있는 경우, 람다식으로 쓸 수 있다.
button.setOnClickListener {
// to do...
}
}
}
// View.class 파일을 열어보면, OnClickListener 인터페이스에는 딱 하나의 메소드만 있다.
public interface OnClickListener {
void onClick(View var1);
}
2. 데이터 클래스
코틀린에서는 데이터를 담는 그릇이 되는 클래스를 따로 만들 수 있다. 자바는 POJO 클래스로 '비어있는 틀 역할'을 하는 클래스를 만들었지만, 이는 비슷한 코드가 여러 번 중복되어서 보일러 플레이트 코드가 생긴다는 문제가 있었다. 하지만 코틀린의 데이터 클래스를 사용하면 이런 문제를 해결할 수 있다.
https://charlezz.medium.com/보일러플레이트-코드란-boilerplate-code-83009a8d3297
예를 들어, 비행기 티켓에 대한 정보를 담는 Ticket 데이터 클래스를 만들어보자.
data class Ticket(
val companyName: String,
val name: String,
var date: String,
var seatNumber: Int
)
데이터 클래스는 위와 같이 주 생성자만 선언해도 toString(), hashCode(), equals(), copy() 메소드를 자동으로 생성해준다.
data class Ticket(
val companyName: String,
val name: String,
var date: String,
var seatNumber: Int
)
class TicketNormal(
val companyName: String,
val name: String,
var date: String,
var seatNumber: Int
)
fun main() {
val ticketA = Ticket("koreaAir", "haeunLee", "2022-01-03", 14)
val ticketB = TicketNormal("koreaAir", "haeunLee", "2022-01-03", 14)
println(ticketA)
println(ticketB) // ticketB 객체가 할당된 메모리 주소 출력
}
Ticket(companyName=koreaAir, name=haeunLee, date=2022-01-03, seatNumber=14)
com.tutorial.kotlinbasic.TicketNormal@2d6e8792
일반적인 클래스의 객체는 메모리 주소가 출력되지만, 코틀린의 데이터 클래스는 객체 하나가 담고 있는 정보를 보여준다.
3. Companion Object
https://wikidocs.net/228
https://www.bsidesoft.com/8187
자바에서 static은 언제 사용할까?
모든 객체가 동일한 값을 가진다면 매번 새롭게 메모리를 할당할 필요 없이 static으로 만들어주면 된다. static은 말그대로 한번 할당된 메모리가 '정적'이어서 모든 객체가 공유할 수 있다. static 멤버는 클래스 멤버라고도 불리는데, 그 이유는 객체를 생성하지 않고도 '클래스명으로' 메소드를 직접 호출할 수 있기 때문이다.
Companion object
companion object는 말그대로 이해하면 클래스와 함께 동반되는 객체이다. 클래스명으로 직접 호출할 수 있다는 점에서 자바의 static과 비슷해 보이지만, companion object는 어쨌든 객체라는 점에서 static과 다르다.
class MyClass{
companion object{
val prop = "나는 Companion object의 속성이다."
fun method() = "나는 Companion object의 메소드다."
}
}
fun main() {
// companion object의 멤버에 접근할 때,
// Companion 키워드를 생략하고 클래스명으로 바로 접근할 수 있다!
println(MyClass.Companion.prop)
println(MyClass.Companion.method())
println(MyClass.prop)
println(MyClass.method())
val comp1 = MyClass.Companion // --(1)
println(comp1.prop)
println(comp1.method())
// 명시적으로 객체를 생성할 때도 Companion 키워드 생략 가능
val comp2 = MyClass // --(2)
println(comp2.prop)
println(comp2.method())
}
위 코드에서 주석 (1)처럼 companion 객체는 변수에 할당하는 것이 가능하지만, 자바의 static 멤버는 이런 것이 불가능하다.
그리고 주석 (2)에서 또 기억해야 할 것은 클래스 내에 정의된 companion object는 클래스 이름만으로도 참조 접근이 가능하다는 것이다.
여기서 알 수 있듯이 static 키워드만으로는 클래스 멤버를 companion object처럼 하나의 독립된 객체로 여길 수 없다.
Companion object가 왜 필요할까?
class Book private constructor(val id: Int, val name: String){
companion object {
fun create() = Book(0, "animal farm")
}
}
fun main() {
//val book = Book() // Cannot access '<init>': it is private in 'Book'
val book = Book.Companion.create()
println("${book.id} ${book.name}")
}
위의 코드처럼 private 생성자로 인해 외부에서 객체를 생성하지 못하지만 클래스의 멤버에는 접근하고 싶을 때, companion 객체를 사용할 수 있다.
class Book private constructor(val id: Int, val name: String){
companion object BookFactory : IdProvider{
override fun getId(): Int {
return 100
}
val myBook = "new book"
fun create() = Book(getId(), myBook)
}
}
interface IdProvider {
fun getId() : Int
}
fun main() {
val book = Book.create()
println("${book.id} ${book.name}")
// companion object가 오버라이딩 한 메소드 역시 클래스명으로 접근 가능하다.
val bookId = Book.getId()
println(bookId)
}
4. Object
코틀린에는 자바에 없는 독특한 싱글턴(singleton; 인스턴스가 하나만 있는 클래스) 선언 방법이 있다. 아래처럼 class 키워드 대신 object 키워드를 사용하면 된다.
// Singleton Pattern: 인스턴스가 오직 하나만 생성되어야 하는 경우 사용하는 패턴
// CarFactory 객체는 모든 앱을 실행할 때 딱 한번만 만들어진다. (불필요한 메모리 사용 방지)
object CarFactory {
val cars = mutableListOf<Car>()
fun makeCar(horsePower: Int) : Car {
val car = Car(horsePower)
cars.add(car)
return car
}
}
data class Car(val horsePower: Int)
fun main() {
// CarFactory 인스턴스는 컴파일할 때 한번만 만들어서 계속 재사용
val car = CarFactory.makeCar(10)
val car2 = CarFactory.makeCar(200)
println(car)
println(car2)
println(CarFactory.cars.size.toString())
}
Car(horsePower=10)
Car(horsePower=200)
2
object는 var obj = object: MyClass(){}
또는 var obj = object: MyInterface{}
처럼 특정 클래스나 인터페이스를 확장해서 만들 수 있으며, 선언문이 아닌 표현식(var obj = object{}
)으로 생성할 수 있다.
싱글톤이기 때문에 object의 메소드를 정의하여 시스템 전체에서 사용되는 기능을 수행하는 데는 큰 도움이 될 수 있지만, 전역 상태를 유지하다가 스레드 경합 등의 문제가 생길 수 있으니 주의해야 한다.
cf) 경합: 어떤 스레드가 다른 스레드가 획득하고 있는 락(lock)이 해제되기를 기다리는 상태
언어 수준에서 안전한 싱글턴을 만들어 준다는 점에서 object는 매우 유용하다.
Author And Source
이 문제에 관하여([Kotlin] 고급 문법 정리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jxlhe46/Kotlin-고급-문법-정리저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)