├── Anko ├── Anko Commons │ └── Anko Commons.md ├── Anko Coroutines │ └── Anko Coroutines.md ├── Anko Layouts │ └── Anko Layouts.md ├── Anko SQLite │ └── Anko SQLite.md └── Introducing Anko.md ├── Arrow ├── Arrow Type.md └── Start Arrow.md ├── Characteristics of Kotlin ├── 함수 │ └── 함수.md ├── 람다 표현식 │ └── 람다 표현식.md ├── 코틀린의 여타 특징 │ └── 코틀린의 여타 특징.md ├── 클래스 │ └── 클래스.md └── 함수 │ └── 함수.md ├── Functional Kotlin ├── Immutability.md └── Functional Programing.md ├── Java Vs Kotlin ├── 제네릭 │ └── 제네릭.md ├── 기본 자료형 │ └── 기본 자료형.md ├── 널 안정성 │ └── 널 안정성.md ├── 예외 │ └── 예외.md ├── 자료, 자료형의 확인 및 변환 │ └── 자료,자료형의 확인 및 변환.md ├── 제네릭 │ └── 제네릭.md ├── 컬렉션 │ └── 컬렉션.md ├── 클래스 및 인터페이스 │ └── 클래스 및 인터페이스.md └── 흐름 제어 │ └── 흐름 제어.md ├── Koin └── Koin.md ├── Kotlin Android Extension ├── Introducing Kotlin Android Extension.md ├── 리사이클러뷰에서 사용해보기 │ └── Kotlin Android Extension 리사이클러뷰.md ├── 사용법 │ └── Kotlin Android Extension 사용하기.md └── 설정하는 법 │ └── Kotlin Android Extension 설정하기.md ├── Kotlin Coroutines └── Kotlin Coroutines.md ├── Kotlin Restart └── Kotlin Restart.md ├── LICENSE ├── README.md ├── Standard Library of Kotlin ├── 범위 지정 함수 │ └── 범위 지정 함수.md ├── 스트림 함수 │ └── 스트림 함수.md ├── 조건 확인 함수 │ └── 조건 확인 함수.md └── 컬렉션 생성 함수 │ └── 컬렉션 생성 함수.md ├── Start Kotlin ├── Let's Introduce Kotlin.md ├── 변수 │ └── variable.kt ├── 조건, 반복문 │ └── If For-each.kt ├── 클래스 인터페이스 │ └── ClassInterface.kt └── 함수 │ └── funtion.kt └── Using Kotlin with Java ├── 자바에서 코틀린코드 사용하기 └── 자바에서 코틀린코드 사용하기.md └── 코틀린에서 자바코드 사용하기 └── 코틀린에서 자바코드 사용하기.md /Anko/Anko Commons/Anko Commons.md: -------------------------------------------------------------------------------- 1 | # Anko Commons 2 | ## 의존성 3 | * Anko Commons은 안드로이드 애플리케이션을 작성할 때 일반적으로 자주 구현하는 기능을 간편하게 추가할 수 있는 유틸리티 함수를 제공합니다. 4 | * Anko Commons을 사용하여면 이를 사용할 모듈의 빌드스크립트에 의존성을 추가하면 됩니다. 5 | ~~~gradle 6 | // build.gradle 7 | 8 | android { 9 | ... 10 | } 11 | 12 | dependencies { 13 | 14 | // Anko Commons 라이브러리를 추가합니다. 15 | implementation "org.jetbrains.anko:anko-commons:0.10.2" 16 | 17 | ... 18 | } 19 | ~~~ 20 | * 서포트 라이브러리에 포함된 클래스를 사용하는 경우, 필요에 따라 anko-appcompat-v7-commons 혹은 anko=support-v4-commons을 빌드스크립트 내 의존성을 추가하면 됩니다. 21 | ~~~gradle 22 | // build.gradle 23 | 24 | android { 25 | ... 26 | } 27 | 28 | dependencies { 29 | 30 | // Anko Commons 라이브러리를 추가합니다. 31 | implementation "org.jetbrains.anko:anko-commons:0.10.2" 32 | 33 | // appcompat-v7용 Anko Commons 라이브러리를 추가합니다. 34 | implementation "org.jetbrains.anko:anko-appcompat-v7-commons:0.10.2" 35 | 36 | // support-v4용 Anko Commons 라이브러리를 추가합니다. 37 | implementation "org.jetbrains.anko:anko-support-v4-commons:0.10.2" 38 | } 39 | ~~~ 40 | ## 토스트 표시하기 41 | * toast() 및 toastLong() 함수를 사용하면 토스트 메시지를 간편하게 표시할 수 있습니다. 42 | * 토스트를 표시하려면 Context 클래스의 인스턴스가 필요하므로, 이 클래스 혹은 이를 상속하는 클래스(액티비티, 프래그먼트)내부에서만 사용할 수 있습니다. 43 | ~~~kotlin 44 | // 다음 코드와 동일한 역할을 합니다. 45 | // Toast.makeText(Context, "Hello, Kotlin!", Toast.LENGTH_SHORT).show() 46 | toast("Hello, Kotlin!") 47 | 48 | // 다음 코드와 동일한 역할을 합니다. 49 | // Toast.makeText(Context, R.string.hello, Toast.LENGTH_SHORT).show() 50 | toast(R.string.hello) 51 | 52 | // 다음 코드와 동일한 역할을 합니다. 53 | // Toast.makeText(Context, "Hello, Kotlin!", Toast.LENGTH_LONG).show() 54 | longToast("Hello, Kotlin!) 55 | ~~~ 56 | ## 다이얼로그 생성 및 표시하기 57 | * alert() 함수를 사용하면 AlertDialog를 생성할 수 있습니다. 58 | * 토스트와 마찬가지로 Context 클래스 혹은 이를 상속하는 클래스(액티비티, 프래그먼트) 내부에서만 사용할 수 있습니다. 59 | ~~~kotlin 60 | // 다이얼로그의 제목과 본문을 지정합니다. 61 | alert(title = "Message", message = "Let's learn Kotlin!") { 62 | 63 | // AlertDialog.Builder.setPositiveButton()에 대응합니다. 64 | positiveButton("Yes") { 65 | // 버튼을 클릭했을 때 수행할 동작을 구현합니다. 66 | toast("Yay!") 67 | } 68 | 69 | // AlertDialog.Builder.setNegativeButton()에 대응합니다. 70 | negativeButton("No") { 71 | // 버튼을 클릭했을 때 수행할 동작을 구현합니다. 72 | longToast("No way...") 73 | } 74 | }.show() 75 | ~~~ 76 | * 프레임워크에서 제공하는 다이얼로그가 아닌 서포트 라이브러리에서 제공하는 다이얼로그(android.support.v7.app.AlertDialog)를 생성하려면, 77 | * anko-appcompat-v7-commons을 의존성에 추가한 후 다음과 같이 Appcompat을 함께 인자에 추가하면 됩니다. 78 | ~~~kotlin 79 | // import 문이 추가됩니다. 80 | import org.jetbrains.anko.appcompat.v7.Appcompat 81 | 82 | // Appcompat을 인자에 추가합니다. 83 | alert(Appcompat, title = "Message", message = "Let's learn Kotlin!") { 84 | ... 85 | }.show() 86 | ~~~ 87 | * 여러 항목 중 하나를 선택하도록 할 떄 사용하는 리스트 다이얼로그는 selector() 함수를 사용하여 생성할 수 있습니다. 88 | ~~~kotlin 89 | // 다이얼로그에 표시할 목록을 생성합니다. 90 | val cities = listOf("Seoul", "Tokyo", "Mountain View", "Singapore") 91 | 92 | // 리스트 다이얼로그를 생성하고 표시합니다. 93 | selector(title = "Select City", items = cities) { dig, selection -> 94 | 95 | // 항목을 선택했을 때 수행할 동작을 구현합니다. 96 | toast("You selected ${cities[selection]}!") 97 | } 98 | ~~~ 99 | * 작업의 진행 상태를 표시할 때 사용하는 프로그레스 다이얼로그는progressDialog()와 indeterminateProgressDialog() 함수를 사용하여 생성할 수 있습니다. 100 | * progressDialog() 함수는 파일 다운로드 상태와 같이 진행률을 표시해야 하는 다이얼로그를 생성할 때 사용합니다. 101 | * indeterminateProgressDialog() 함수는 진행률을 표시하지 않는 다이얼로그를 생성할 때 사용합니다. 102 | ~~~kotlin 103 | // 진행률을 표시하는 다이얼로그를 생성합니다. 104 | val pd = progressDialog(title = "File Download", message = "Downloading...") 105 | 106 | // 다이얼로그를 표시합니다. 107 | pd.show() 108 | 109 | // 진행률을 50으로 조정합니다. 110 | pd.progress = 50 111 | 112 | // 진행률을 표시하지 않는 다이얼로그를 생성하고 표시합니다. 113 | indenterminateProgressDialog(message = "Please wait...").show() 114 | ~~~ 115 | ## 인텐트 생성 및 사용하기 116 | * 인텐트는 컴포넌트 간에 데이터를 전달할 때에도 사용하지만 주로 액티비티나 서비스를 실행하는 용도로 사용합니다. 117 | * 다른 컴포넌트를 실행하기 위해 인텐트를 사용하는 경우, 이 인텐트는 대상 컴포넌트에 대한 정보와 기타 부가 정보를 포함합니다. 118 | ~~~kotlin 119 | // DetailActivity 액티비티를 대상 컴포넌트로 지정하는 인텐트 120 | val intent = Intent(this, DetailActivity::class.java) 121 | 122 | // DetailActivity를 실행합니다. 123 | startActivity(intent) 124 | ~~~ 125 | * 이 인텐트에 부가 정보를 추가하거나 플래그를 설정하는 경우 인텐트를 생성하는 코드는 다음과 같습니다. 126 | ~~~kotlin 127 | val intent = Intent(this, DetailActivity::class.java) 128 | 129 | // 인텐트에 부가정보를 추가합니다. 130 | intent.putExtra("id", 150L) 131 | intent.putExtra("title", "Awesome item") 132 | 133 | // 인텐트에 플래그를 생성합니다. 134 | intent.setFlag(Intent.FLAG_ACTIVITY_NO_HISTORY) 135 | ~~~ 136 | * intentFor() 함수를 사용하면 훨씬 간소한 현태로 동일한 역할을 하는 인텐트를 생성할 수 있습니다. 137 | ~~~kotlin 138 | val intent = intentFor( 139 | // 부가 정보를 Pair 형태로 추가합니다. 140 | "id" to 150L, "title" to "Awesome item") 141 | 142 | // 인텐트 플래그를 설정합니다. 143 | .noHistory() 144 | ~~~ 145 | * 인텐트에 플래그를 지정하지 않는다면, startActivity() 함수나 startService() 함수를 사용하여 인텐트 생성과 컴포넌트 호출을 동시에 수행할 수 있습니다. 146 | * 이들 함수는 모두 Context 클래스를 필요로 하므로, 이 클래스 혹은 이를 상속하는 클래스(액티비티, 프래그먼트) 내부에서만 사용할 수 있습니다. 147 | ~~~kotlin 148 | // 부가정보 없이 DetailActivity를 실행합니다. 149 | startActivity() 150 | 151 | // 부가정보를 포함하여 DetailActivity를 실행합니다. 152 | startActivity("id" to 150L, "title" to "Awesome item") 153 | 154 | // 부가정보 없이 DataSyncService를 실행합니다. 155 | startService() 156 | 157 | // 부가정보를 포함하여 DataSyncService를 실행합니다. 158 | startService("id" to 1000L) 159 | ~~~ 160 | * 이 외에도, 자주 사용하는 특정 작업을 바로 수행할 수 있는 함수들을 제공합니다. 161 | ~~~kotlin 162 | // 전화를 거는 인텐트를 실행합니다. 163 | makeCall(number = "01012345678") 164 | 165 | // 문자메시지를 발송하는 인텐트를 실행합니다. 166 | sendSMS(number = "01012345678", text = "Hello, Kotlin!") 167 | 168 | // 웹 페이지를 여는 인텐트를 실행합니다. 169 | browse(url = "https://google.com") 170 | 171 | // 이메일을 발송하는 인텐트를 실행합니다. 172 | email(email = "jyte82@gmail.com", subject = "Hello, Taeho Kim", text = "How are you?") 173 | ~~~ 174 | ## 로그 메시지 기록하기 175 | * 안드로이드 애플리케이션에서 로그메시지를 기록하려면 android.util.Log 클래스에서 제공하는 메서드를 사용해야 합니다. 176 | * 하지만 로그를 기록하는 함수를 호출할 때마다 매번 태그를 함께 입력해야 하므로 다소 불편합니다. 177 | * Anko 라이브러리에서 제공하는 AnkoLogger를 사용하면 훨씬 편리하게 로그 메시지를 기록할 수 있습니다. 178 | * AnkoLogger에서는 다음과 같이 android.util.Log 클래스의 로그 기록 메서드에 대응하는 함수를 제공합니다. 179 | 180 | |android.util.Log|AnkoLogger| 181 | |----------------|----------| 182 | |v()|verbose()| 183 | |d()|debug()| 184 | |i()|info()| 185 | |w()|warn()| 186 | |e()|error()| 187 | |wtf()|wtf()| 188 | * AnkoLogger를 사용하려면 이를 사용할 클래스에서 AnkoLogger 인터페이스를 구현하면 됩니다. 189 | * AnkoLogger 인터페이스를 구현한 액티비티에서의 사용 예는 다음 코드에서 확인할 수 있습니다. 190 | * 출력할 메시지의 타입으로 String만 허용하는 android.util.Log 클래스와 달리 모든 타입을 허용하는 모습을 확인할 수 있습니다. 191 | ~~~kotlin 192 | // AnkoLogger 인터패이스를 구현합니다. 193 | class MainActivity: AppCompatActivity(), AnkoLogger { 194 | 195 | fun doSomething() { 196 | // Log.INFO 레벨로 로그 메시지를 기록합니다. 197 | info("doSomething() called") 198 | } 199 | 200 | fun doSomethingWithParameter(number: Int) { 201 | // Log.DEBUG 레벨로 로그 메시지를 기록합니다. 202 | // String 타입이 아닌 인자는 해당 인자의 toString() 함수 변환값을 기록합니다. 203 | debug(number) 204 | } 205 | ... 206 | } 207 | ~~~ 208 | * AnkoLogger에서 제공하는 함수를 사용하여 로그 메시지를 기록하는 경우, 로그 태그로 해당 함수가 호출되는 클래스의 이름을 사용합니다. 209 | * 따라서 앞의 예제에서는 "MainActivity"를 로그 태그로 사용합니다. 210 | * 로그 태그를 바꾸고 싶다면 loggerTag 프로퍼티를 오버라이드하면 됩니다. 211 | ~~~kotlin 212 | class MainActivity: AppCompatActivity(), AnkoLogger { 213 | // 이 클래스 내에서 출력되는 로그 태그를 "Main"으로 지정합니다. 214 | override val loggerTag: String 215 | get() = "Main" 216 | 217 | ... 218 | } 219 | ~~~ 220 | ## 단위 변환하기 221 | * 안드로이드는 다양한 기기를 지원하기 위해 픽셀(px) 단위 대신 dip(혹은 dp; device independent pixels)나 sp(scale independent pixels)를 사용합니다. 222 | * dp나 sp 단위는 각 단말기의 화면 크기나 밀도에 따라 화면에 표시되는 크기를 일정 비율로 조정하므로, 다양한 화면 크기나 밀도를 가진 단말기에 대응하는 UI를 작성할 때 유용합니다. 223 | * 커스텀 뷰 내뷰와 같이 뷰에 표시되는 요소의 크기를 픽셀 단위로 다루는 경우 dp나 sp단위를 픽셀 단위로 변환하기 위해 복잡한 과정을 거쳐야 합니다. 224 | ~~~kotlin 225 | class MainActivity : AppCompatActivity() { 226 | fun doSomething() { 227 | // 100dp를 픽셀 단위로 변환합니다. 228 | val dpInPixel = TypedValue.applyDimension( 229 | TypedValue.COMPLEX_UNIT_DIP, 100f, resources.displayMetrics) 230 | 231 | // 16sp를 픽셀 단위로 변환합니다. 232 | val spInPixel = TypedValue.applyDimension( 233 | TypedValue.COMPLEX_UNIT_SP, 16f, resources.displayMetrics) 234 | } 235 | ... 236 | } 237 | ~~~ 238 | * Anko에서 제공하는 dip() 및 sp() 함수를 사용하면 이러한 단위를 매우 간단히 변환할 수 있습니다. 239 | * 단위를 변환하기 위해 단말기의 화면 정보를 담고 있는 DisplayMetrics 객체가 필요하므로, 이 함수들은 단말기 화면 정보에 접근할 수 있는 클래스인 Context를 상속한 클래스 혹은 커스텀 뷰 클래스 내에서 사용할 수 있습니다. 240 | * dip() 함수 및 sp() 함수를 사용하여 앞의 코드를 간단하게 표현한 모습입니다. 241 | * TypedValue.applyDimension() 메서드는 Float 형 인자만 지원했지만, dip() 및 sp() 함수는 Int 형 인자도 지원합니다. 242 | ~~~kotlin 243 | // 100dp를 픽셀 단위로 변환합니다. 244 | val dpInPixel = dip(100) 245 | 246 | // 16sp를 픽셀 단위로 변환합니다. 247 | val spInPixel = sp(16) 248 | ~~~ 249 | * 반대로, 픽셀 단위를 dp나 sp 단위로 변환하는 함수도 제공합니다. 각각 px2dip(), px2sp() 함수를 사용합니다. 250 | ~~~kotlin 251 | // 300px를 dp 단위로 변환합니다. 252 | val pxInDip = px2dip(300) 253 | 254 | // 80px를 sp 단위로 변환합니다. 255 | val pxInSp = px2sp(80) 256 | ~~~ 257 | ## 기타 258 | * 여러 단말기 환경을 지원하는 애플리케이션은, 단말기 환경에 따라 다른 형태의 UI를 보여주도록 구현하는 경우가 많습니다. 259 | * 이러한 경우, configuration() 함수를 사용하면 특정 단말기 환경일 때만 실행할 코드를 간단하게 구현할 수 있습니다. 260 | 261 | |매개변수 이름|단말기 환경 종류| 262 | |---------|----------| 263 | |density|화면 밀도| 264 | |language|시스템 언어| 265 | |long|화면 길이| 266 | |nightMode|야간모드 여부| 267 | |orientation|화면 방향| 268 | |rightToLeft|RTL(Right-to-Left)레이아웃 여부 269 | |screenSize|화면 크기| 270 | |smallestWidth|화면의 가장 작은 변의 길이| 271 | |uiMode|UI 모드(일반, TV, 차량, 시계, VR 등)| 272 | * configuration() 또한 단말기 환경에 접근해야 하므로 이 정보에 접근할 수 있는 Context 클래스 혹은 이를 상속한 클래스(액티비티, 프래그먼트)에서만 사용할 수 있습니다. 273 | ~~~kotlin 274 | class MainActivity : AppCompatActivity() { 275 | fun doSomething() { 276 | configuration(orientation = Orientation.PORTRAIT) { 277 | // 단말기가 세로 방량일 때 수행할 코드를 작성합니다. 278 | ... 279 | } 280 | configuration(orientation = Orientation.LANDSCAPE, language = "ko") { 281 | // 단말기가 가로 방향이면서 시스템 언어가 한국어로 설정되어 있을 때 282 | // 수행할 코드를 작성합니다. 283 | ... 284 | } 285 | } 286 | ... 287 | } 288 | ~~~ 289 | * 단순히 단말기의 OS 버전에 따라 분기를 수행하는 경우 doFromSdk()와 doIfSdk()를 사용할 수 있습니다. 290 | ~~~kotlin 291 | doFromSdk(Build.VERSION_CODES.0) { 292 | // 안드로이드 8.0 이상 기기에서 수행할 코드를 작성합니다. 293 | ... 294 | } 295 | 296 | doIfSdk(Build.VERSION_CODES.N) { 297 | // 안드로이드 7.0 기기에서만 수행할 코드를 작성합니다. 298 | ... 299 | } 300 | ~~~ -------------------------------------------------------------------------------- /Anko/Anko Coroutines/Anko Coroutines.md: -------------------------------------------------------------------------------- 1 | # Anko Coroutines 2 | ## Using Anko Coroutines in your project 3 | * build.gradle의 dependency에 anko-coroutines를 추가합니다. 4 | ~~~kotlin 5 | dependencies { 6 | implementation "org.jetbrains.anko:anko-coroutines:$anko_version" 7 | } 8 | ~~~ 9 | ## Listener helpers 10 | ## asReference() 11 | * 비동기 API가 취소를 지원하지 않으면 코루틴이 무기한 정지 될 수 있습니다. 12 | * 코루틴은 캡처 된 객체에 대한 강력한 참조를 보유하므로 Activity 또는 Fragment 인스턴스의 인스턴스를 캡처하면 메모리 누수가 발생할 수 있습니다. 13 | * 이러한 경우에는 직접 캡처 대신 asReference ()를 사용하면 됩니다. 14 | ~~~kotlin 15 | suspend fun getData(): Data { ... } 16 | 17 | class MyActivity : Activity() { 18 | fun loadAndShowData() { 19 | // Ref uses the WeakReference under the hood 20 | val ref: Ref = this.asReference() 21 | 22 | async(UI) { 23 | val data = getData() 24 | 25 | // Use ref() instead of this@MyActivity 26 | ref().showData(data) 27 | } 28 | } 29 | 30 | fun showData(data: Data) { ... } 31 | } 32 | ~~~ 33 | ## bg() 34 | * bg ()를 사용하여 백그라운드 스레드에서 코드를 쉽게 실행할 수 있습니다. 35 | ~~~kotlin 36 | fun getData(): Data { ... } 37 | fun showData(data: Data) { ... } 38 | 39 | async(UI) { 40 | val data: Deferred = bg { 41 | // Runs in background 42 | getData() 43 | } 44 | 45 | // This code is executed on the UI thread 46 | showData(data.await()) 47 | } 48 | ~~~ -------------------------------------------------------------------------------- /Anko/Anko Layouts/Anko Layouts.md: -------------------------------------------------------------------------------- 1 | # Anko Layouts 2 | * 안드로이드 애플리케이션을 작성할 때, 대부분 XML 레이아웃을 사용하여 화면을 구성합니다. 3 | * 소스 코드(Java 혹은 Kotlin)를 사용하여 화면을 구성하는 것도 가능하지만 XML 레이아웃에 비해 복잡하고 까다라로워 대다수의 사람들이 선호하지 않습니다. 4 | * 하지만 XML로 작성된 레이아웃을 사용하려면 이 파일에 정의된 뷰를 파싱하는 작업을 먼저 수행해야 합니다. 5 | * 때문에 소스 코드를 사용하여 화면을 구성한 경우에 비해 애플리케이션의 성능이 저하되고, 파싱 과정에서 자원이 더 필요한 만큼 배터리도 더 많이 소모합니다. 6 | * Anko Layouts는 소스 코드로 화면을 구성할 때 유용하게 사용할 수 있는 여러 함수들을 제공하며, 아룰 사용하면 XML 레이아웃을 작성하는 것처럼 편리하게 소스코드로도 화면을 구성할 수 있습니다. 7 | * Anko Layouts을 사용하려면 이를 사용할 모듈의 빌드스크립트에 의존성을 추가하면 되며, 애플리케이션의 minSdkVersion에 따라 사용하는 라이브러리가 달라집니다. 8 | * 애플리케이션의 minSdkVersion에 대응하는 Anko Layouts 라이브러리는 다음과 같습니다. 9 | 10 | |minSdkVersion|Anko Layouts 라이브러리| 11 | |---------|----------| 12 | |15이상 19미만|anko-sdk15| 13 | |19이상 21미만|anko-sdk19| 14 | |21이상 23미만|anko-sdk21| 15 | |23이상 25미만|anko-sdk23| 16 | |25이상|anko-sdk25| 17 | ~~~gradle 18 | android { 19 | defaultConfig { 20 | // minSdkVersion이 15로 설정되어 있습니다. 21 | minSdkVersion 15 22 | targetSdkVersion 27 23 | ... 24 | } 25 | ... 26 | } 27 | 28 | dependencies { 29 | // minSdkVersion에 맞추어 Anko Layouts 라이브러리를 추가합니다. 30 | compile "org.jetbrains.anko:anko-sdk15:0.10.2" 31 | } 32 | ~~~ 33 | * 서프트 라이브러리에 포함된 뷰를 사용하는 경우, 사용하는 뷰가 포함된 라이브러리에 대응하는 Anko Layouts 라이브러리를 의존성에 추가하면 됩니다. 34 | * 각 서프트 라이브러리에 대응하는 Anko Layouts 라이브러리는 다음과 같습니다. 35 | |서포트 라이브러리|Anko Layouts 라이브러리| 36 | |---------|----------| 37 | |appcompat-v7|anko-appcompat-v7| 38 | |cardview|anko-cardview-v7| 39 | |design|anko-design| 40 | |gridlayout|anko-gridlayout-v7| 41 | |recyclerview-v7|anko-recyclerview-v7| 42 | |support-v4|anko-support-v4| 43 | * 다음은 서포트 라이브러리와 이에 대응하는 Anko Layouts 라이브러리를 의존성으로 추가한 예입니다. 44 | ~~~gradle 45 | android { 46 | ... 47 | } 48 | 49 | dependencies { 50 | // appcompat-v7 서포트 라이브러리 추가 51 | implementation "com.android.support:appcompat-v7:27.0.1" 52 | 53 | // appcompat-v7용 Anko Layouts 라이브러리를 추가합니다. 54 | implementation "org.jetbrains.anko:anko-appcompat-v7:0.10.2" 55 | } 56 | ~~~ 57 | ## DSL로 화면 구성하기 58 | * Anko Layouts을 사용하면 소스 코드에서 화면을 DSL(Domain Specific Language) 형태로 정의할 수 있습니다. 59 | * 다음은 DSL을 사용하여 화면을 구성하는 간단한 예를 보여줍니다. XML 레이아웃으로 정의할 때보다 더 간단하게 화면을 구성할 수 있는 것을 확인할 수 있습니다. 60 | ~~~kotlin 61 | verticalLayout { 62 | padding = dip(12) 63 | 64 | textView("Enter Login Credentials") 65 | 66 | editText { 67 | hint = "E-mail" 68 | } 69 | 70 | editText { 71 | hint = "Password" 72 | } 73 | 74 | button("Submit") 75 | } 76 | ~~~ 77 | * 앞의 코드에서 사용한 verticalLayout(), textView(), editText(), button()은 Anko Layout에서 제공하는 함수로, 뷰 혹은 다른 뷰를 포함할 수 있는 레이아웃을 생성하는 역할을 합니다. 78 | * 다음은 여기에서 제공하는 함수 중 자주 사용하는 함수 몇 개의 목록입니다. 79 | 80 | |함수|생성하는 뷰|비고| 81 | |---|--------|----| 82 | |button()|android.widget.Button|| 83 | |checkBox()|android.widget.CheckBox|| 84 | |editText()|android-widget.EditText|| 85 | |frameLayout()|android-widget.FrameLayout|| 86 | |imageView()|android-widget-ImageView|| 87 | |linearLayout()|android.widget.LinearLayout|| 88 | |radioButton()|android.widget.RadioButton|| 89 | |relativeLayout()|android-widget.RelativeLayout|| 90 | |switch()|android-widget.Switch|서포트 라이브러리에서 제공하는 뷰는 switchCompat() 사용| 91 | |verticalLayout()|android-widget-LinearLayout|orientation 값으로 LinearLayout.VERTICAL을 갖는 LinearLayout| 92 | |webView()|android-webkit.WebView|| 93 | * XML 레이아웃 파일에 XML로 구성한 레이아웃을 저장하듯이, DSL로 구성한 뷰는 AnkoComponent 클래스를 컨테이너로 사용합니다. 94 | * AnkoComponent에는 정의되어 있는 화면을 표시할 대상 컴포넌트의 정보를 포함합니다. 95 | * 다음은 MainActivity 액티비티에 표시할 뷰의 정보를 가지는 AnkoComponent의 코드 예시를 보여줍니다. 96 | ~~~kotlin 97 | class MainActivityUI : AnkoComponent { 98 | 99 | override fun createView(ui: AnkoContext) = ui.apply { 100 | vertivalLayout { 101 | // LinearLayout의 padding을 12dp로 설정합니다. 102 | padding = dip(12) 103 | 104 | // TextView를 추가합니다. 105 | textView("Enter Login Credentials") 106 | 107 | // EditText를 추가하고, 힌트 문자열을 설정합니다. 108 | editText { 109 | hint = "E-mail" 110 | } 111 | 112 | editText { 113 | hint = "password" 114 | } 115 | 116 | // 버튼을 추가합니다. 117 | button("Submit") 118 | } 119 | }.view 120 | } 121 | ~~~ 122 | * 추가로, 액티비티에서는 AnkoComponent 없이 직접 액티비티 내에서 DSL을 사용하여 화면을 구성할 수 있습니다. 123 | * 다음은 앞의 코드와 동일한 레이아웃을 AnkoComponent 없이 구성하는 예입니다. 이 방식으로 화면을 구성하는 경우 setContentView()를 호출하지 않아도 됩니다. 124 | ~~~kotlin 125 | class MainActivity: AppcompatActivity() { 126 | override fun onCreate(savedInstanceState: Bundle?) { 127 | super.onCreate(savedInstanceState) 128 | 129 | // setContentView()가 없어도 됩니다. 130 | verticalLayout { 131 | padding = dip(12) 132 | 133 | textView("Enter Login Credentials") 134 | 135 | editText { 136 | hint = "E-mail" 137 | } 138 | 139 | editText { 140 | hint = "Password" 141 | } 142 | 143 | button("Submit") 144 | } 145 | } 146 | } 147 | ~~~ 148 | ## 프래그먼트에서 사용하기 149 | * 프래그먼트에서 Anko Layouts을 사용하려면 프래그먼트를 위한 AnkoComponent를 만들고, onCreateView()에서 createView()를 직접 호출하여 프래그먼트의 화면으로 사용할 뷰를 반환하면 됩니다. 150 | * createView()를 직접 호출하려면 AnkoContext 객체를 직접 만들어 인자로 전달하면 됩니다. 151 | ~~~kotlin 152 | class MainFragment : Fragment() { 153 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?{ 154 | // AnkoComponent.createView() 함수를 호출하여 뷰를 반환합니다. 155 | return MainFragmentUI().createView(AnkoContext.create(context, this)) 156 | } 157 | } 158 | 159 | // 프래그먼트를 위한 AnkoComponent를 만듭니다. 160 | class MainFragmentUI : AnkoComponent { 161 | override fun createView(ui: AnkoContext) = ui.apply { 162 | verticalLayout { 163 | 164 | textView("Enter Login Credentials") 165 | 166 | editText { 167 | hint = "E-mail" 168 | } 169 | 170 | editText { 171 | hint = "Password" 172 | } 173 | 174 | button("Submit") 175 | } 176 | }.view 177 | } 178 | ~~~ 179 | ## Anko Support Plugin 180 | * Anko Support Plugin은 Anko와 같이 사용할 수 있는 부가 기능을 제공하는 IDE 플러그인입니다. 181 | * 플러그인을 설치하려면 코틀린 IDE 플러그인을 설치하는 과정과 동일하게 진행하면 되며, 플러그인 검색 다이얼로그에서 다음과 같이 'Anko Support'를 선택하여 설치하면 됩니다. 182 | 183 | * Anko Support Plugin에서는 AnkoComponent로 작성한 화면이 어떻게 표시되는지 미리 확인할 수 있는 레이아웃 프리뷰 기능을 제공합니다. 184 | * 레이아웃 프리뷰를 사용하려면, 먼저 프리뷰 기능으로 확인하고 싶은 AnkoComponent가 구혀되어 있는 파일을 연 후 AnkoComponent의 구현부 내부 아무곳에 커서를 둡니다. 185 | * 그 다음, [View > Tools Windows > Anko Layout Preview]를 선택하여 레이이웃 프리뷰 창을 띄웁니다. 186 | * 레이아웃 프리뷰 창은 XML 레이아웃 프리뷰 창과 거의 유사한 형태로 구성되어 있습니다. 187 | * 앞에서 선택한 AnkoComponent의 레이아웃 프리뷰를 보여주며, 화면이 표시되지 않거나 바뀐 내용이 반영되지 않았다면 프로젝트를 다시 빌드하면 됩니다. -------------------------------------------------------------------------------- /Anko/Anko SQLite/Anko SQLite.md: -------------------------------------------------------------------------------- 1 | # Anko SQLite 2 | * Android 커서를 사용하여 SQLite 쿼리 결과를 파싱하는 것은 힘듭니다. 3 | * 쿼리의 결과 행을 구문 분석하기 위해 많은 상용구 코드를 작성하고 이를 열거 된 모든 리소스를 적절하게 닫으려면 셀 수없는 try..finally 블록으로 묶어야합니다. 4 | * Anko는 SQLite databases와 함께 간단하게 작동할 수 있도록 많은 기능들을 제공합니다. 5 | 6 | ## Using Anko SQLite in your project 7 | * build.gradle의 dependency에 anko-sqlite를 추가합니다. 8 | ~~~gradle 9 | dependencies { 10 | implementation "org.jetbrains.anko:anko-sqlite:$anko_version" 11 | } 12 | ~~~ 13 | ## Accessing the database 14 | * SQLiteOpenHelper를 사용하는 경우 일반적으로 getReadableDatabase () 또는 getWritableDatabase ()를 호출합니다. 15 | * 결과는 실제로 프로덕션 코드에서 동일하지만 수신 된 SQLiteDatabase에서 close () 메서드를 호출해야합니다. 16 | * 또한 어딘가에 도우미 클래스를 캐시해야하며 여러 스레드에서이 클래스를 사용하는 경우 동시 액세스를 인식하고 있어야합니다. 17 | * 이 모든 것은 꽤 힘듭니다. 그래서 안드로이드 개발자는 디폴트 SQLite API에 열중하지 않고 대신 ORM과 같은 값 비싼 래퍼를 선호합니다. 18 | * Anko는 기본 클래스를 완벽하게 대체하는 ManagedSQLiteOpenHelper 클래스를 제공합니다. 사용 방법은 다음과 같습니다. 19 | ~~~kotlin 20 | class MyDatabaseOpenHelper(ctx: Context) : ManagedSQLiteOpenHelper(ctx, "MyDatabase", null, 1) { 21 | companion object { 22 | private var instance: MyDatabaseOpenHelper? = null 23 | 24 | @Synchronized 25 | fun getInstance(ctx: Context): MyDatabaseOpenHelper { 26 | if (instance == null) { 27 | instance = MyDatabaseOpenHelper(ctx.getApplicationContext()) 28 | } 29 | return instance!! 30 | } 31 | } 32 | 33 | override fun onCreate(db: SQLiteDatabase) { 34 | // Here you create tables 35 | db.createTable("Customer", true, 36 | "id" to INTEGER + PRIMARY_KEY + UNIQUE, 37 | "name" to TEXT, 38 | "photo" to BLOB) 39 | } 40 | 41 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { 42 | // Here you can upgrade tables, as usual 43 | db.dropTable("User", true) 44 | } 45 | } 46 | 47 | // Access property for Context 48 | val Context.database: MyDatabaseOpenHelper 49 | get() = MyDatabaseOpenHelper.getInstance(getApplicationContext()) 50 | ~~~ 51 | * try 블록에 코드를 포함하는 대신 이제 다음과 같이 작성할 수 있습니다. 52 | ~~~kotlin 53 | database.use { 54 | // 'this' is a SQLiteDatabase instance 55 | } 56 | ~~~ 57 | * {} 안에 모든 코드를 실행 한 후에 데이터베이스가 완전히 닫힙니다. 58 | 59 | * 비동기 호출 예제 : 60 | ~~~kotlin 61 | class SomeActivity : Activity() { 62 | private fun loadAsync() { 63 | async(UI) { 64 | val result = bg { 65 | database.use { ... } 66 | } 67 | loadComplete(result) 68 | } 69 | } 70 | } 71 | ~~~ 72 | * 아래 언급된 메서드들과 모든 메서드를은 SQLiteException에 throw 할 수 있습니다. 73 | * Anko가 오류가 발생하지 않는 것처럼 가장하는 것은 무리일 수 있으므로 직접 처리해야합니다. 74 | ## Creating and dropping tables 75 | * Anko와 함께라면 쉽게 새로운 테이블을 만들고 삭제할 수 있습니다. 구문은 간단합니다. 76 | ~~~kotlin 77 | database.use { 78 | createTable("Customer", true, 79 | "id" to INTEGER + PRIMARY_KEY + UNIQUE, 80 | "name" to TEXT, 81 | "photo" to BLOB) 82 | } 83 | ~~~ 84 | * SQLite에서는 NULL, INTEGER, REAL, TEXT, BLOB 총 5개의 메인 타입들이 있습니다. 85 | * 하지만 각 열에는 PRIMATY KEY 또는 UNIQUE와 같은 수정자가 있을 수 있습니다. 이러한 수정자는 기본 유형 이름에 "추가"와 함께 추가 할 수 있습니다. 86 | * 테이블을 지울 때에는 dropTable 함수를 사용합니다. 87 | ~~~kotlin 88 | dropTable("User", true) 89 | ~~~ 90 | ## Inserting data 91 | * 일반적으로 테이블에 행을 삽입하려면 ContentValues ​​인스턴스가 필요합니다. 다음은 그 예입니다. 92 | ~~~kotlin 93 | val values = ContentValues() 94 | values.put("id", 5) 95 | values.put("name", "John Smith") 96 | values.put("email", "user@domain.org") 97 | db.insert("User", null, values) 98 | ~~~ 99 | * Anko는 insert() 함수의 인자로 값을 직접 전달하여 이러한 코드의 거치래을 제거 할 수 있습니다. 100 | ~~~kotlin 101 | // Where db is an SQLiteDatabase 102 | // eg: val db = database.writeableDatabase 103 | db.insert("User", 104 | "id" to 42, 105 | "name" to "John", 106 | "email" to "user@domain.org" 107 | ) 108 | ~~~ 109 | * database.use를 사용하면 110 | ~~~kotlin 111 | database.use { 112 | insert("User", 113 | "id" to 42, 114 | "name" to "John", 115 | "email" to "user@domain.org" 116 | ) 117 | } 118 | ~~~ 119 | ## Querying data 120 | * Anko는 편리한 쿼리 빌더를 제공합니다. db.select (tableName, vararg columns)를 사용하여 만들 수 있습니다. db는 SQLiteDatabase의 인스턴스입니다. 121 | 122 | 함수 | 기능 123 | --------------------------------------|---------- 124 | `column(String)` | 검색어 선택하는 열 추가하기 125 | `distinct(Boolean)` | 고유한 쿼리 126 | `whereArgs(String)` | 원시 문자열 `where` 쿼리를 지정하기 127 | `whereArgs(String, args)` (중요) | 인자를 사용하여`where` 쿼리를 지정하기 128 | `whereSimple(String, args)` | `? '마크 인자를 가진`where` 쿼리를 지정하기 129 | `orderBy(String, [ASC/DESC])` | 이 열의 순서 130 | `groupBy(String)` | 이 열의 그룹화하기 131 | `limit(count: Int)` | 쿼리 결과 행 수 제한하기 132 | `limit(offset: Int, count: Int)` | 오프셋이있는 쿼리 결과 행 수 제한하기 133 | `having(String)` | 원시 'having'표현식 지정하기 134 | `having(String, args)` (중요) | 인자로 `having' 표현식을 지정하기 135 | * (중요) 로 표시된 함수는 특별한 방법으로 인자를 구문 분석합니다. 어떤 순서로든 값을 제공하고 원활한 escaping을 지원합니다. 136 | ~~~kotlin 137 | db.select("User", "name") 138 | .whereArgs("(_id > {userId}) and (name = {userName})", 139 | "userName" to "John", 140 | "userId" to 42) 141 | ~~~ 142 | * 여기에서 {userId} 부분은 42, {userName}은 'John'으로 바뀝니다. 형식이 숫자가 아닌 경우 (Int, Float 등) 또는 Boolean 값이면 escaped됩니다. 다른 형식의 경우 toString() 표현이 사용됩니다. 143 | * whereSimple 함수는 String 자료형을 허용합니다. 이것은 SQLiteDatavase의 query() 와 같은 일을 합니다. (질문 표시 ?는 자료형의 실제 값과 대체됩니다.) 144 | * 이 쿼리를 어떻게 실행할까요? exec() 함수를 씁니다. Cursor. () -> T 형식의 확장 기능을 허용합니다. 수신한 확장 기능을 시작한 다음 Cursor를 닫아 스스로 수행할 필요가 없도록합니다. 145 | ~~~kotlin 146 | db.select("User", "email").exec { 147 | // Doing some stuff with emails 148 | } 149 | ~~~ 150 | ## Parsing query results 151 | * 그래서 우리는 Cursor를 가지고 있고, 그것을 정규 클래스로 어떻게 파싱할 수 있을까요? 152 | * Anko는 parseSingle, parseOpt 및 parseList 함수를 제공하여 훨씬 쉽게 처리 할 수 ​​있습니다. 153 | 154 | | Method | Description | 155 | |--------|-------------| 156 | | parseSingle(rowParser): T | Parse exactly one row| 157 | | parseOpt(rowParser): T? | Parse zero or one row | 158 | | parseList(rowParser): List | Parse zero or more rows | 159 | 160 | * 수신 된 Cursor가 둘 이상의 행을 포함하면 parseSingle () 및 parseOpt ()가 예외를 throw합니다. 161 | * rowParser 란 무엇일까요? 각 함수는 RowParser와 MapRowParser인 두 가지 유형의 파서를 지원합니다. 162 | ~~~kotlin 163 | interface RowParser { 164 | fun parseRow(columns: Array): T 165 | } 166 | 167 | interface MapRowParser { 168 | fun parseRow(columns: Map): T 169 | } 170 | ~~~ 171 | * 매우 효율적인 방법으로 쿼리를 작성하려면 RowParser를 사용합니다 (하지만 각 열의 인덱스를 알아야합니다). 172 | * parseRow는 Any의 타입을 받아들입니다 (Any 형은 Long, Double, String 또는 ByteArray 이외의 것일 수 있음). 173 | * 반면에 MapRowParser를 사용하면 열 이름을 사용하여 행 값을 가져올 수 있습니다. 174 | * Anko는 이미 간단한 단일 열 행에 대한 파서를 보유하고 있습니다. 175 | 176 | * ShortParser 177 | * IntParser 178 | * LongParser 179 | * FloatParser 180 | * DoubleParser 181 | * StringParser 182 | * BlobParser 183 | * 또한 클래스 생성자에서 행 파서를 만들 수 있습니다. 184 | 클래스가 있다고 가정합니다. 185 | ~~~kotlin 186 | class Person(val firstName: String, val lastName: String, val age: Int) 187 | ~~~ 188 | * 파서는 간단해집니다. 189 | ~~~kotlin 190 | val rowParser = classParser() 191 | ~~~ 192 | * 현재로서는 기본 생성자에 선택적 매개 변수가있는 경우 Anko는 이러한 파서 작성을 지원하지 않습니다. 193 | * 또한 생성자는 Java Reflection을 사용하여 호출되므로 커다란 데이터 세트의 경우 사용자 정의 RowParser를 작성하는 것이 더 합리적입니다. 194 | * Anko db.select () 빌더를 사용하는 경우에는 parseSingle, parseOpt 또는 parseList를 직접 호출하고 적절한 파서를 전달할 수 있습니다. 195 | ## Custom row parsers 196 | * 예를 들어, 열 (Int, String, String)에 대해 새 파서를 만들어 봅시다. 가장 일반적인 방법은 다음과 같습니다. 197 | ~~~kotlin 198 | class MyRowParser : RowParser> { 199 | override fun parseRow(columns: Array): Triple { 200 | return Triple(columns[0] as Int, columns[1] as String, columns[2] as String) 201 | } 202 | } 203 | ~~~ 204 | * 자, 이제 코드에 3가지 명시적 캐스트가 있습니다. rowParser 함수를 사용하여 제거해보겠습니다. 205 | ~~~kotlin 206 | val parser = rowParser { id: Int, name: String, email: String -> 207 | Triple(id, name, email) 208 | } 209 | ~~~ 210 | * 이게 다 입니다. rowParser는 모든 캐스트를 생성하고 원하는대로 람다 매개 변수의 이름을 지정할 수 있습니다. 211 | ## Cursor streams 212 | * Anko는 SQLite Cursor에 기능적으로 접근하는 방법을 제공합니다. 213 | * cursor.asSequence () 또는 cursor.asMapSequence () 확장 함수를 호출하여 일련의 행을 가져옵니다. 커서를 닫는 것을 잊지 마세요 :) 214 | ## Updating values 215 | * 사용자 중 한 명에게 새로운 이름을 줍니다. 216 | ~~~kotlin 217 | update("User", "name" to "Alice") 218 | .where("_id = {userId}", "userId" to 42) 219 | .exec() 220 | ~~~ 221 | * 또한 전통적인 방식으로 쿼리를 제공하려는 경우 update에는 whereSimple () 메서드가 있습니다. 222 | ~~~kotlin 223 | update("User", "name" to "Alice") 224 | .`whereSimple`("_id = ?", 42) 225 | .exec() 226 | ~~~ 227 | ## Delete Data 228 | * 행을 삭제 해 봅시다 (delete 메소드에는 whereSimple () 메소드가 없으며, 대신 인수에 직접 쿼리를 제공합니다). 229 | ~~~kotlin 230 | val numRowsDeleted = delete("User", "_id = {userID}", "userID" to 37) 231 | ~~~ 232 | ## Transaction 233 | * transaction ()이라는 특별한 함수가 있는데, 여러 개의 데이터베이스 연산을 하나의 SQLite 트랜잭션으로 묶을 수 있습니다. 234 | ~~~kotlin 235 | transaction { 236 | // Your transaction code 237 | } 238 | ~~~ 239 | * {} 블록 내에 예외가 발생하지 않으면 트랜잭션은 성공으로 표시됩니다. 240 | * 어떤 이유로 트랜잭션을 중단하려면 TransactionAbortException을 throw 하세요. 이 경우에는이 예외를 직접 처리 할 필요가 없습니다. 241 | 242 | ## 원문 243 | * https://github.com/Kotlin/anko/wiki/Anko-SQLite -------------------------------------------------------------------------------- /Anko/Introducing Anko.md: -------------------------------------------------------------------------------- 1 | # Introducing Anko 2 | ## Anko란 3 | * Anko는 Kotlin 언어의 제작사인 젯브레인에서 직접 제작하여 배포하는 Kotlin 라이브러리입니다. 4 | * 안드로이드 애플리케이션 개발에 유용한 유틸리티 함수를 제공합니다. 5 | 6 | ## 제공 라이브러리 종류 7 | * Anko에서는 유틸리티 함수의 성격에 따라 다음과 같이 네 종류의 라이브러리를 제공합니다. 8 | * Anko Commons 9 | * Anko Layouts 10 | * Anko SQLite 11 | * Anko Coroutines 12 | * Anko Commons과 Anko Layouts에은 대다수의 애플리케이션에서 유용하게 사용할 수 있습니다. 13 | * Anko SQLite는 SQLite 데이터베이스를 다룰 때 유용한 함수를 제공합니다. 14 | * Anko Coroutines은 Kotlin 코루틴 라이브러리(kotlinx.coroutines)와 함게 사용할 수 있는 함수를 제공합니다. 15 | 16 | ## 링크 17 | * [Anko SQLite] 18 | * [Anko Coroutines] 19 | 20 | [Anko SQLite]: https://github.com/Kotlin/anko/wiki/Anko-SQLite 21 | [Anko Coroutines]: https://github.com/Kotlin/anko/wiki/Anko-Coroutines 22 | -------------------------------------------------------------------------------- /Arrow/Arrow Type.md: -------------------------------------------------------------------------------- 1 | # Arrow Type 2 | * 애로우에서는 Option, Either, Try 같은 시존 함수형 타입의 많은 구현과 펑터, 모나드같은 다른 타입 클래스를 포함합니다. 3 | 4 | ## Option 5 | * Option 데이터 타입은 T값의 유무를 표현합니다. 6 | * 애로우에서 Option는 T 값의 존재를 나타내는 데이터 클래스 Some와 값이 없음을 나타내는 오브젝트 None의 두 하위 타입으로 구성된 sealed 클래스입니다. 7 | * sealed 클래스로 정의된 Option는 다른 하위 타입을 가질 수 없습니다. 8 | * 따라서 컴파일러는 철저하게 구문을 확인할 수 있습니다. 두 경우 모두 라면 Some와 None이 다뤄집니다. 9 | 10 | * Option은 null이 가능한 타입보다 훨씬 많은 값을 제공합니다. 11 | 12 | ~~~kotlin 13 | fun divide(num: Int, den: Int): Int? { 14 | return if (num % den != 0) { 15 | null 16 | } else { 17 | num / den 18 | } 19 | } 20 | 21 | fun division(a: Int, b: Int, den: Int): Pair? { 22 | val aDiv = divide(a, den) 23 | return when (aDib) { 24 | is Int -> { 25 | val bDiv = divide(b, den) 26 | when(bDiv) { 27 | 28 | is Int -> aDiv to bDiv 29 | eles -> null 30 | } 31 | } 32 | else -> null 33 | } 34 | } 35 | ~~~ 36 | 37 | * division 함수는 정수 두 개와 분모라는 세 파라미터를 받고 두 숫자 모두 den으로 나눌 수 있다면 Pair를 반환하고 그 외에는 null을 반환합니다. 38 | 39 | ~~~kotlin 40 | fun optionDivide(num: Int, den: Int): Option = divide(num, den).toOption() 41 | 42 | fun optionDivision(a: Int, b: Int, den: Int): Option> { 43 | val aDiv = optionDivide(a, den) 44 | return when (aDiv) { 45 | is Some -> { 46 | val bDiv = optionDivide(b, den) 47 | when (bDiv) { 48 | is Some -> Some(aDiv.t to bDiv.t) 49 | else -> None 50 | } 51 | } 52 | else -> None 53 | } 54 | } 55 | ~~~ 56 | 57 | * optionDivide 함수는 나눗셈에서 null이 가능한 결과를 얻고 그것을 toOption() 확장 함수를 사용해 Option으로 잔환합니다. 58 | * optionDivision과 division을 비교하면 큰 차이가 없으며, 다른 타입으로 표현된 같은 알고리즘입니다. 59 | * 여기서 멈추면 Option는 null이 가능한 값의 위에 추가적인 값을 제공하지 않습니다. 60 | 61 | ~~~Kotlin 62 | fun flatMapDivision(a: Int, b: Int, den: Int): Option> { 63 | return optionDivide(a, den).flatMap { aDiv: Int -> 64 | optionDivide(b, den).flatMap { bDiv: Int -> 65 | Some(aDiv to bDiv) 66 | } 67 | } 68 | } 69 | ~~~ 70 | 71 | * Option은 내부 값을 처히는 몇 가지 함수를 제공합니다. 72 | * 이 경우 모나드로서 flatMap이며, 이제 코드가 더 짧아 보입니다. 73 | 74 | * Option 함수의 일부입니다. 75 | * exists(p: Predicate): Boolean: 값 T가 존재하면 predicate p 결과를, 그 외에는 null을 반환합니다. 76 | * filter(p: Predicate): Option: T 값이 존재하고 predicate P를 충족하면 Some를, 그외에는 None을 반환합니다. 77 | * flatMap(f: (T) -> Option): Option: flatMap 변형 함수 78 | * fold(ifEmpty: () -> R, some: (T) -> R): R: R로 변환된 값을 반환하고 None에 대해 ifEmpty를, Some에 대해 some을 호출합니다. 79 | * getOrElse(default:() -> T): T: T가 존재한다면 T를 반환하고 그 외에는 기본 결과를 반환합니다. 80 | * map(f: (T) -> R): Option: 펑터같은 트랜스폼 함수 81 | * orNull(): T?: 값 T를 null이 가능한 T?로 반환합니다. 82 | 83 | * division의 마지막 구현은 컴프리헨션을 사용합니다. 84 | 85 | ~~~kotlin 86 | fun comprhensionDivision(a: Int, b: Int, den: Int): Option> { 87 | return Option.monad().binding { 88 | val aDiv: Int = optionDivide(a, den).bind() 89 | val bDiv: Int = optionDivide(b, den).bind() 90 | aDiv to bDiv 91 | }.ev() 92 | } 93 | ~~~ 94 | 95 | * 컴프리헨션은 flatMap 함수가 포함된 모든 타입에 대해 순차적으로 계산하는 기술이며, 모나드의 인스턴스를 제공할 수 있습니다. 96 | * 애로우에서 컴프리헨션은 코루틴을 사용합니다. 코루틴은 비동기 실행 도메인 밖에서 유용합니다. 97 | * 다음은 내용을 요약한 코드입니다. 98 | 99 | ~~~kotlin 100 | fun comprehensionDivision(a: Int, b: Int, den: Int): Option> { 101 | return Option.monad().binding { 102 | val aDiv: Int = optionDivide(a, den).bind() 103 | // 컨티뉴에이션 1 시작 104 | val bDiv: Int = optionDivide(b, den).bind() 105 | // 컨티뉴에이션 2 시작 106 | aDiv to bDiv 107 | // 컨티뉴에이션 2 끝 108 | // 컨티뉴에이션 1 끝 109 | }.ev() 110 | } 111 | ~~~ 112 | 113 | * Option.monad().binding은 코루틴 빌더이고, bind() 함수는 suspended 함수입니다. 114 | * 컨티뉴에이션은 일시 중지 지점 이후의 모든 코드를 나타내는 것입니다. 115 | * 예시 코드에서 두 개의 Suspension 포인트와 두 컨티뉴에이션이 있습니다. 116 | * 반환할 때 두 번째 컨티뉴에이션이 있으며, aDiv와 bDiv에 접근할 수 있습니다. 117 | 118 | * 이 알고리즘을 컨티뉴에이션으로 읽는 것은 flatMapDivision 함수와 매우 비슷합니다. 119 | * 씬 뒤에서 Option.monad().binding은 컴프리헨션을 만들기 위해 컨티뉴에이션과 함께 Option.flatMap을 사용합니다. 120 | * 컴파일되면 ComprehensionDivision과 flatMapDivision은 대략적으로 동일합니다. 121 | 122 | ## Either 123 | * Either은 가능한 두 값 L과 R 중 하나를 표시하지만 동시에 둘 다는 안됩니다. 124 | * Either는 Left과 Right 서브타입 두 개를 가진 sealed 클래스입니다. 125 | * 보통 Either는 실패할 수 있는 결과를 표현하기 위해 사용되며, 왼쪽은 에러 표시로 사용되고 오른쪽은 성고적인 결과 표시로 사용됩니다. 126 | * 실패할 수 았눈 적업 표시는 일반적인 시나리오이므로 애로우의 Either는 오른쪽으로 편향돼 있습니다. 127 | * 즉, 문서화되지 않았면 모든 작업은 오른쪽에서 실행됩니다. 128 | 129 | * division을 Option에서 Either로 변환합니다. 130 | ~~~kotlin 131 | fun eitherDivide(num: Int, den: Int): Either { 132 | val option = optionDivide(num, den) 133 | return when(option) { 134 | is Some -> Right(option.t) 135 | None -> Left("$num은 $den으로 나눌 수 없다.") 136 | } 137 | } 138 | ~~~ 139 | * 이제 None 값을 반환하는 대신 유저에게 귀중힌 정보를 반환합니다. 140 | ~~~kotlin 141 | fun eitherDivision(a: Int, b: Int, den: Int): Either> { 142 | val aDiv = eitherDivide(a, den) 143 | return when (aDiv) { 144 | is Right -> { 145 | val bDiv = eitherDivide(b, den) 146 | when (bDiv) { 147 | is Right -> Right(aDiv.getOrElse { 0 } toT bDiv.getOrElse { 0 }) 148 | is Left -> bDiv as Either 149 | } 150 | } 151 | is Left -> aDiv as Either 152 | } 153 | } 154 | ~~~ 155 | 156 | * eitherDivision에서 코틀린의 Pair 대신 애로우의 Tuple를 사용합니다. 157 | * 튜플은 Pair/Triple보다 많은 기능을 제공하며, 지금부터 이것을 사용할 것입니다. 158 | * Tuple2를 만들려면 중위 확장 함수 toT를 사용합니다. 159 | 160 | * flatMap, map, isLeft, isRight 등의 함수들이 있습니다. 161 | 162 | * flatMap은 다음과 같이 작동합니다. 163 | ~~~kotlin 164 | fun flatMapEitherDivision(a: Int, b: Int, den: Int): Either> { 165 | return eitherDivide(a, den).flatMap { aDiv -> 166 | eitherDivide(b, den).flatMap { bDiv -> 167 | Right(aDiv toT bDiv) 168 | } 169 | } 170 | } 171 | ~~~ 172 | * Either는 모나드 구현을 가지므로 바인딩함수를 호출할 수 있습니다. 173 | 174 | ~~~Kotlin 175 | fun comprehensionEitherDivision(a: Int, b: Int, den: Int): Either> { 176 | return Either.monad().binding { 177 | val aDiv = eitherDivide(a, den).bind() 178 | val bDiv = eitherDivide(b , den).bind() 179 | aDiv toT bDiv 180 | }.ev() 181 | } 182 | ~~~ 183 | * Either.monad()에 주의해야합니다. Either은 L 타입을 정의해야 합니다. 184 | 185 | ~~~kotlin 186 | fun main(args: Array) { 187 | eitherDivision(3, 2, 4).fold(::println, ::println) //3은 4로 나눠지지 않는다. 188 | } 189 | ~~~ 190 | 191 | ## 모나드 트랜스포머 192 | * Either와 Option은 사용하기 쉽지만 둘을 결합하면 어떻게 될까요? 193 | 194 | ~~~kotlin 195 | object UserService { 196 | fun findAge(user: String): Either> { 197 | //Magic 198 | } 199 | } 200 | ~~~ 201 | 202 | * UserService.findAge는 Either>를 반환합니다. 203 | * 데이터베이스 또는 다른 인프라스트럭처에 접근하는 에러는 Left을 반환합니다. 204 | * 데이터베이스에서 값을 찾지 못한 경우에는 Right을 반환하며, 값을 찾은 경우에는 Right>를 반환합니다. 205 | 206 | ~~~kotlin 207 | import arrow.core.* 208 | import arrow.syntax.function.pipe 209 | 210 | fun main(args: Array) { 211 | val anakinAge: Either> = UserService.findAge("Anakin") 212 | 213 | anakinAge.fold(::identity, { op -> 214 | op.fold({ "Not found" }, Int::toString) 215 | }) pipe ::println 216 | } 217 | ~~~ 218 | 219 | * age를 출력하려면 두 개의 중첩된 폴드가 필요합니다. 220 | * 여러 값에 접근하는 작업을 수행해야 하는 경우 문제가 발생합니다. 221 | 222 | ~~~kotlin 223 | import arrow.core.* 224 | import arrow.syntax.function.pipe 225 | import kotlin.math.absoluteValue 226 | 227 | fun main(args: Array) { 228 | val anakinAge: Either> = UserService.findAge("Anakin") 229 | val padmeAge: Either> = UserService.findAge("Padme") 230 | 231 | val difference: Either>>> = 232 | anakinAge.map { aOp -> 233 | padmeAge.map { pOp -> 234 | pOp.map {p -> 235 | (a - b).absoluteValue 236 | } 237 | } 238 | } 239 | 240 | difference.fold(::identity, { op1 -> 241 | op1.fold({ "Not Found"}, { either -> 242 | either.fold(::identity, { op2 -> 243 | op2.fold({ "Not Found" }, Int::toString)}) 244 | }) 245 | }) pipe ::println 246 | } 247 | ~~~ 248 | 249 | * 모나드는 부드럽지 않으므로 이런 작업을 만드는 것이 아주 빠르게 복잡해질 수 있습니다. 250 | * 그러나 언제나 컴프리헨션에 의지 할 수 있습니다. 251 | 252 | ~~~kotlin 253 | import arrow.core.* 254 | import arrow.syntax.function.pipe 255 | import arrow.typeclasses.binding 256 | import kotlin.math.absoluteValue 257 | 258 | fun main(args:Array) { 259 | val anakinAge: Either> = UserService.findAge("Anakin") 260 | val padmeAge: Either> = UserService.findAge("Padme") 261 | val difference: Either>> = 262 | Either.monad().binding { 263 | val aOp: Option = anakinAge.bind() 264 | val pOp: Option = padmeAge.bind() 265 | aOp.map { a -> 266 | pOp.map { p -> 267 | (a - p).absoluteValue 268 | } 269 | } 270 | }.ev() 271 | 272 | difference.fold(::identity, { op1 -> 273 | op1.fold({ "Not found" }, { op2 -> 274 | op2.fold({ "Not found" }, Int::toString)})}) pipe ::println 275 | } 276 | ~~~ 277 | 278 | * 반환 타입은 그렇게 길지 않고 폴드는 처리하기 더 쉽습니다. 279 | 280 | * 다음 코드는 중첩된 컴프리헨션입니다. 281 | 282 | ~~~kotlin 283 | fun main(args: Array) { 284 | val anakinAge: Either> = UserService.findAge("Anakin") 285 | val padmeAge: Either> = UserService.findAge("Padme") 286 | val difference: Either>> = 287 | Either.monad().binding { 288 | val aOp: Option = anakinAge.bind() 289 | val pOp: Option = padmeAge.bind() 290 | Option.monad().binding { 291 | val a: Int = aOp.bind() 292 | val p: Int = pOp.bind() 293 | (a - b).absoluteValue 294 | }.ev() 295 | }.ev() 296 | 297 | difference.fold(::identity, { op -> 298 | op.fold({ "Not found" }, Int::toString) 299 | }) pipe ::println 300 | } 301 | ~~~ 302 | 303 | * 같은 타입의 값과 결과를 얻었지만 모나드 트랜스포머는 다른 옵션을 가지고 있습니다. 304 | 305 | * 모나드 트랜스포머는 하나처럼 실행될 수 있는 두 모나드의 조합입니다. 306 | 307 | * 예를 들어 Option은 Either 내에 중첩된 모나드 타입이므로 OptionT를 사용할 것입니다. 308 | 309 | ~~~kotlin 310 | import arrow.core.* 311 | import arrow.data.OptionT 312 | import arrow.data.monad 313 | import arrow.data.value 314 | import arrow.systax.function.pipe 315 | import arrow.typeclasses.binding 316 | import kotlin.math.absoluteValue 317 | 318 | fun main(args: Array) { 319 | val anakinAge: Either> = 320 | OptionT.monad>().binding { 321 | val a: Int = OptionT(anakinAge).bind() 322 | val p: Int = OptionT(padmeAge).bind() 323 | (a - p).absoluteValue 324 | }.value().ev() 325 | 326 | difference.fold(::identity, { op -> 327 | op.fold({ "Not found" }, Int::toString) 328 | }) pipe ::println 329 | } 330 | ~~~ 331 | 332 | * OptionT.monad>().binding을 사용했습니다. 333 | * EitherKindPartial 모나드는 래퍼 타입이 Either> 타입의 값으로 OptionT를 사용합니다. 334 | * 이전에는 ev() 메소드만 사용했지만 이제는 value() 메소드를 사용해 OptionT 내부값을 추출해야 합니다. 335 | 336 | ## Try 337 | * Try는 계산이 실패했는지의 여부를 나타냅니다. 338 | * Try는 실패를 나타내는 Failure와 성공적인 작없을 나타내는 Success라는 두 개의 하위 클래스를 가진 sealed 클래스입니다. 339 | 340 | ~~~kotlin 341 | import arrow.data.Try 342 | 343 | fun tryDivide(num: Int, den: Int): Try = Try { divide(num, den)!! } 344 | ~~~ 345 | * Try 인스턴스를 만드는 가장 간단한 방법은 Try.invoke 연산자를 사용하는 것입니다. 346 | * 블록 내부에서 예외가 발생하면 Failure를 반환합니다. 347 | * 모든 것이 잘되면 Success를 반환합니다. 348 | * 예를 들어 !! 연산자는 나누기가 null을 반환하면 NPE를 던질 것입니다. 349 | 350 | ~~~kotlin 351 | fun tryDivision(a: Int, b: Int, den: Int): Try> { 352 | val aDiv = tryDivide(a, den) 353 | return when (aDiv) { 354 | is Success -> { 355 | val bDiv = tryDivide(b, den) 356 | when (bDiv) { 357 | is Success -> { 358 | Try { aDiv.value toT bDiv.value } 359 | } 360 | is Faulure -> Failure(bDiv.exception) 361 | } 362 | } 363 | is Failure -> Failure(aDiv.exception) 364 | } 365 | } 366 | ~~~ 367 | 368 | * Try 함수의 간단한 목록을 살펴봅니다. 369 | * 함수는 flatMap, isFailure, isSuccess, getOrDefault 등이 있습니다. 370 | 371 | * flatMap 구현은 Either나 Option과 매우 유사하며, 이름 및 행동 규약의 공통 세트를 가진 값을 보여줍니다. 372 | 373 | ~~~kotlin 374 | fun flatMapTryDivision(a: Int, b: Int, den: Int): Try> { 375 | return tryDivide(a, den).flatMap { aDiv -> 376 | tryDivide(b, den).flatMap { bDiv -> 377 | Try { aDiv toT bDiv } 378 | } 379 | } 380 | } 381 | ~~~ 382 | * 모나딕 컴프리헨션은 Try에서도 사용 가능합니다. 383 | 384 | ~~~kotlin 385 | fun comprehensionTryDivision(a: Int, b: Int, den: Int): Try> { 386 | return Try.monad().binding { 387 | val aDiv = tryDivide(a, den).bind() 388 | val bDiv = tryDivide(b, den).bind() 389 | aDib toT bDiv 390 | }.ev() 391 | } 392 | ~~~ 393 | 394 | * MonadError 인스턴스를 사용하는 다른 종류의 모나딕 컨프리헨션이 있습니다. 395 | 396 | ~~~kotlin 397 | fun monadErrorTryDivision(a: Int, b: Int, den: Int): Try> { 398 | return Try.monadError().bindingCatch { 399 | val aDiv = divide(a, den)!! 400 | val bDiv = divide(b, den)!! 401 | aDiv toT bDiv 402 | }.ev() 403 | } 404 | ~~~ 405 | * monadError.bindingCatch를 사용하면 예외를 발생하는 모든 연산은 실패로 끝나고 마지막에 반환은 Try로 래핑됩니다. 406 | * MonadError는 Option과 Either에서도 사용할 수 있습니다. 407 | 408 | ## State 409 | * State는 애플리케이션 상태를 처리하기 위한 함수적 접근 방식을 제공하는 구조입니다. 410 | 411 | * State는 S -> Tuple2에 대한 추상화입니다. 412 | * S는 상태 타입을 나타내며, Tuple2은 결과인데, S는 새롭게 업데이트된 상태이며, A는 함수 반환입니다. 413 | 414 | * 다음은 가격고 이를 계산하는 두 가지를 반환하는 간단한 예입니다. 415 | * 가격을 계산하려면 VAT 20%를 추가하고 가격이 일정 기준을 초과하는 경우 할인을 적용해야 합니다. 416 | ~~~kotlin 417 | import arrow.core.Tuple2 418 | import arrow.core.toT 419 | import arrow.data.State 420 | 421 | typealias PriceLog = MutableList> 422 | 423 | fun addVat(): State = State { log: PriceLog -> 424 | val (_, price) = log.last() 425 | val vat = price * 0.2 426 | log.add("Add VAT: $vat" toT price + vat) 427 | log toT Unit 428 | } 429 | ~~~ 430 | 431 | * MutableList>을 위한 타입 앨리어스 PriceLog를 가집니다. 432 | 433 | * PriceLog는 State 대표가 됩니다. 각 단계는 Tuple2로 표현됩니다. 434 | 435 | * 첫 번째 함수 addVat(): State은 첫 번째 단계를 표현합니다. 436 | 437 | * PriceLog와 단계를 적용하기 전의 state를 받고 Tuple2을 반환해야 하는 State 빌더를 사용하는 함수를 작성합니다. 438 | 439 | * 현재는 각격이 필요 없으므로 Unit을 사용합니다. 440 | 441 | ~~~kotlin 442 | fun applyDiscount(threshold: Double, discount: Double): State = State { log -> 443 | val (_, price) = log.last() 444 | if(price > threshold) { 445 | log.add("Applying - $discount" toT price - discount) 446 | } else { 447 | log.add("No discount applied" toT price) 448 | } 449 | log toT Unit 450 | } 451 | ~~~ 452 | * applyDiscount 함수는 두 번째 단계입니다. 여기서 소개한 유일한 새로운 요소는 threshold, discount라는 파라미터입니다. 453 | 454 | ~~~kotlin 455 | fun finalPrice(): State = State { log -> 456 | val (_, price) = log.last() 457 | log.add("Final Price" toT price) 458 | log toT price 459 | } 460 | ~~~ 461 | * 마지막 단계는 finalPrice() 함수로 표현되며, 이제 Unit 대신 Double을 반환합니다. 462 | 463 | ~~~kotlin 464 | import arrow.data.ev 465 | import arrow.instances.monad 466 | import arrow.typeclasses.binding 467 | 468 | fun calculatePrice(threshold: Double, discount: Double) = 469 | State().monad().binding { 470 | addVat().bind() 471 | applyDiscount(threshold, discount).bind() //Unit 472 | val price: Double = finalPrice().bind() 473 | price 474 | }.ev() 475 | ~~~ 476 | 477 | * 일련의 단계를 표현하기 위해 모나드 컴프리헨션을 사용하고 State 함수를 순차적으로 사용합니다. 478 | 479 | * 한 함수에서 다음 함수로 PriceLog 상태가 암시적으로 흐릅니다. 480 | * 마지막에 최종 가격을 산출합니다. 새 단계를 추가하거나 기존 것을 전환하는 것은 선을 추가하거나 이동하는 것처럼 쉽습니다. 481 | 482 | ~~~kotlin 483 | import arrow.data.run 484 | import arrow.data.runA 485 | 486 | fun main(args: Array) { 487 | val (history: PriceLog, price: Double) = calculatePrice(100.0, 2.0).run(mutableListOf("Init" toT 15.0)) 488 | println("Price: $price") 489 | println("::History::") 490 | history.map { (text, value) -> "$text\t|\t$value" } 491 | .forEach(::println) 492 | 493 | val bigPrice: Double = calculatePrice(100.0, 2.0).runA(mutableListOf("Init" toT 1000.0)) 494 | println("bigPrice = $bigPrice") 495 | } 496 | ~~~ 497 | * calculatePrice 함수를 사용하려면 threshold와 discount 값을 제공해야 하며, 그런 다음 최초 상태와 함께 확장 함수를 실행해야 합니다. 498 | * 가격에만 관심이 있다면 runA만 실행하고 history뿐이라면 runS를 실행합니다. 499 | 500 | ### State가 있는 코리커젼 501 | * State는 코리커젼에서 유용합니다. 502 | 503 | ~~~kotlin 504 | fun unfold(s: S, f: (S) -> Pair?): Sequence { 505 | val result = f(s) 506 | return if (result != null) { 507 | squenceOf(result.first) + unfold(result.second, f) 508 | } else { 509 | sequenceOf() 510 | } 511 | } 512 | ~~~ 513 | * 원래 unfold 함수는 State와 매우 비슷한 f: (S) -> Pair?를 사용합니다. 514 | 515 | ~~~kotlin 516 | fun unfold(s: S, state: State>): Sequence { 517 | val (actualState: S, value: Option) = state.run(s) 518 | return value.fold( 519 | { sequenceOf() }, 520 | { t -> 521 | sequenceOf(t) + unfold(actualState, state) 522 | }) 523 | } 524 | ~~~ 525 | * lambda (S) -> Pair?를 갖는 대신 State>를 사용하고 None을 위해 빈 시퀀스 혹은 Some를 위해 재귀 호출을 갖는 Option으로부터 폴드 함수를 사용합니다. 526 | 527 | ~~~kotlin 528 | fun factorial(size: Int): Sequence { 529 | return sequenceOf(1L) + unfold(1L to 1) { (acc, n) -> 530 | if(size > n) { 531 | val x = n * acc 532 | (x) to (x to n + 1) 533 | } else null 534 | } 535 | } 536 | ~~~ 537 | * 이 팩토리얼 함수는 Pair와 람다 (Pair) -> Pair>>를 갖는 unfold를 사용합니다. 538 | 539 | ~~~kotlin 540 | import arrow.syntax.option.some 541 | 542 | fun factorial(size: Int): Sequence { 543 | return sequenceOf(1L) + unfold(1L toT 1, State { (acc, n) -> 544 | if (size > n) { 545 | val x = n * acc 546 | (x toT n + 1) toT x.some() 547 | } else { 548 | (0L toT 0) toT None 549 | } 550 | }) 551 | } 552 | ~~~ 553 | * 리팩토링된 팩토리얼은 State, Option>을 사용하지만 내부 로직은 대부분 같습니다. 554 | * 새 팩토리얼은 null을 사용하지 않는데, 이 것은 상당한 개선입니다. 555 | 556 | ~~~kotlin 557 | fun fib(size: Int): Sequence { 558 | return sequenceOf(1L) + unfold(Triple(0L, 1L, 1)) { (cur, next, n) -> 559 | if (size > n) { 560 | val x = cur + next 561 | (x) to Triple(next, x, n + 1) 562 | } else null 563 | } 564 | } 565 | ~~~ 566 | * 마찬가지로 피보나치는 Triple와 람다 (Triple) -> Pair>? 를 갖는 unfold를 사용합니다. 567 | 568 | ~~~kotlin 569 | import arrow.syntax.tuples.plus 570 | 571 | fun fib(size: Int): Sequence { 572 | return sequenceOf(1L) + unfold((0L toT 1L) + 1, State { (cur, next, n) -> 573 | if(size > n) { 574 | val x = cur + next 575 | ((next toT x) + (n + 1)) toT x.some() 576 | } else { 577 | ((0L toT 0L) + 0) toT None 578 | } 579 | }) 580 | } 581 | ~~~ 582 | 583 | * 그리고 리팩토링된 피보나치는 State, Option>을 사용합니다. 584 | * Tuple2와 함께 사용되는 확장 연산자 plus와 C는 Tuple3를 반환한다는 점에 주의를 기울여야 합니다. 585 | 586 | ~~~kotlin 587 | fun main(args: Array) { 588 | factorial(10).forEach(::println) 589 | fib(10).forEach(::println) 590 | } 591 | ~~~ 592 | * 이제는 시퀀스를 생성하기 위해 코리커젼 함수를 사용할 수 있습니다. 593 | * 엔터프라이즈 인티그레이션 패턴의 메시지 히스토리 혹은 비행 확인 긴 등록 양식과 같은 많은 단계를 갖는 폼 안내와 같은 State의 더 많은 사용법이 있습니다. -------------------------------------------------------------------------------- /Characteristics of Kotlin/함수/함수.md: -------------------------------------------------------------------------------- 1 | # 함수 2 | ## 함수 3 | * Kotlin의 함수는 Java의 메서드와 동일한 기능을 수행하지만, 표현 형태가 더 자유롭고 Java의 메서드에서는 제공하지 않는 여러 유용한 기능을 갖추고 있습니다. 4 | * 이 문서에서는 Kotlin의 함수에서만 사용할 수 있는 유용한 특징과 기능에 대해 알아봅니다. 5 | ## 명명된 인자 6 | * Kotlin에서는 명명된 인자를 사용함으로써 함수를 호출할 때 매개변수의 순서와 상관없이 인자를 전달할 수 있습니다. 7 | * 명명된 인자를 사용하면 매개변수의 수가 많아지더라도 각 인자에 어떤 값이 전달되는지 쉽게 구분할 수 있습니다. 8 | ~~~kotlin 9 | // 원을 그리는 함수 10 | fun drawCircle(x: Int, y: Int, radius: Int){ 11 | 12 | } 13 | ~~~ 14 | * 위의 함수를 호출할 때 다음과 같이 항상 매개변수가 정의된 순서대로 인자를 대입해야 합니다. 함수의 매개변수 정보를 알고 있지 않다면, 대입된 값이 각각 무엇을 의미하는 알기 어렵습니다. 15 | ~~~kotlin 16 | // 중심축이 (10, 5)이고 반지름이 25인 원을 그립니다. 17 | drawCircle(10, 5, 25) 18 | ~~~ 19 | * Kotlin에서는 명명된 인자를 지원하므로 매개변수의 이름과 함께 인자를 대입할 수 있습니다. 20 | ~~~ kotlin 21 | // 명명된 인자를 사용하여 함수를 호출합니다. 22 | drawCircle(x = 10, y = 5, radius = 25) 23 | 24 | // 대입하는 인자 중 일부에만 사용할 수도 있습니다. 25 | drawCircle(10, 5, radius = 25) 26 | ~~~ 27 | ## 기본 매개 변수 28 | * Java에서 메서드의 매개변수가 많은 경우, 이를 조금 더 편리하게 사용하기 위해 축약된 매개변수를 갖는 메서드와 전체 매개변수를 갖는 메서드를 별도로 만들어 사용했습니다. 29 | ~~~java 30 | // 반지름을 지정하지 않을 경우 25로 설정합니다. 31 | void drawCircle(int x, int y) { 32 | // 원본 메서드를 호출합니다. 33 | drawCircle(x, y, 25); 34 | } 35 | 36 | // 모든 매개변수를 갖는 원본 메세드 37 | void drawCircle(int x, int y, int radius) { 38 | 39 | } 40 | 41 | // 중심축이 (10, 5)인 원을 그립니다. 42 | // 반지름을 지정하지 않았으므로 원의 반지름은 25가 됩니다. 43 | drawCircle(10, 5) 44 | ~~~ 45 | * Java애서는 매개변수에 아무 값이 대입되지 않을 경우 기본값을 지정할 수 없기에 앞의 예와 같이 두 종류의 메서드를 만들어야 했습니다. 46 | * Kotlin에서는 함수의 매개변수에 기본값을 지정할 수 있으며, 이때, 지정하는 값을 기본 매개변수(default parameter)라 부릅니다. 47 | ~~~kotlin 48 | // 반지름의 기본값으로 25를 갖는 함수 49 | fun drawCirCle(x: Int, y: Int, radius: Int = 25) { 50 | 51 | } 52 | 53 | // 중심축이 (10, 5)인 원을 그립니다. 54 | // 반지름을 지정하지 않았으므로 원의 반지름은 25가 됩니다. 55 | drawCircle(10, 5) 56 | ~~~ 57 | 58 | ## 단일 표현식 표기 59 | * Kotlin에서는 Unit타입을 제외한 타입을 반환하는 함수라면 함수의 내용을 단일 표현식을 사용하여 정의할 수 있습니다. 60 | ~~~kotlin 61 | fun theAnswerToLifeTheUniverseAndEverything(): Int { 62 | return 21 * 2 63 | } 64 | ~~~ 65 | * 단일 표현식 표기를 사용하면 다음과 같이 정의할 수 있습니다. 66 | ~~~kotlin 67 | fun theAnswerToLifeTheUniverseAndEverything(): Int = 21 * 2 68 | ~~~ 69 | * 단일 표현식 표기를 사용하는 경우, 다음과 같이 반환 타입을 생략하는 것도 가능합니다. 70 | ~~~kotlin 71 | fun theAnswerToLifeTheUniverseAndEverything() = 21 * 2 72 | ~~~ 73 | ## 확장 함수 74 | * Java에서는 기존에 만들어져 있는 클래스에 새로운 메서드를 추가하려면 해당 클래스를 상속하는 새로운 클래스를 작성해야 합니다. 75 | * Kotlin에서는 확장 함수(extension function)를 사용하여 상속없이 기존 클래스에 새로운 함수를 추가할 수 있습니다. 76 | * 확장 함수를 추가할 대상 클래스는 리시버 타입(receiver type)이라 부르며, 이는 리시버 타입 뒤에 점(.)을 찍고 그 뒤에 원하는 함수의 형태를 적는 방식으로 정의합니다. 77 | * 확장 함수 구현부에서는 this를 사용하여 클래스의 인스턴스에 접근할 수 있으며, 아룰 리시버 객체(receiver object)라 부릅니다. 78 | ~~~kotlin 79 | // String 클래스에 withPostfix() 함수를 추가합니다. 80 | // this를 사용하여 인스턴스에 접근할 수 있습니다. 81 | private fun String.withPostfix(postFix: String) = "$this$postFix" 82 | 83 | // this를 사용하여 인스턴스에 접근할 수 있으므로, 앞에서 정의한 확장 함수를 사용할 수 있습니다. 84 | fun String.withBar() = this.withPostfix("Bar") 85 | ~~~ 86 | * 이렇게 정의한 확장 함수는 리시버 타입에 정의한 함수를 사용하는 것과 동일한 방법으로 호출 할 수 있습니다. 87 | ~~~kotlin 88 | val foo = "Foo" 89 | 90 | // String 클래스에 포함된 함수를 호출하듯이 사용합니다. 91 | // 값 foobar에는 "FooBar"가 할당됩니다. 92 | val foobar = goo.withBar() 93 | ~~~ 94 | * 확장 함수를 호출하는 모습이 클래스 내 정의된 함수의 경우와 똑같다 할지라도, 이는 엄연히 클래스 외부에서 정의하는 함수입니다. 95 | * 따라서 리시버 객체에서는 클래스 내 public으로 정의된 프로퍼티나 함수에만 접근할 수 있습니다. 96 | ## 기존 클래스에 확장함수를 추가하는 법 97 | * 확장함수는 리시버 타입에 직접 추가되는 함수가 아닙니다. 리시버 타입과 확장 함수의 인자를 인자로 받는 새로운 함수를 만들고, 확장 함수를 호출하면 이 새로운 함수를 대신 호출합니다. 98 | * 새로운 함수가 정의되는 위치는 확장 함수를 정의하는 위치에 따라 달라집니다. 99 | ~~~kotlin 100 | class MyExtension { 101 | fun String.withFoo() = this.withPrefix("Foo") 102 | 103 | private fun String.withPrefix(prefix: String) = "$prefix$this" 104 | } 105 | ~~~ 106 | * 위 코드는 컴파일 과정에서 다음의 Java 코드와 동일한 형태로 변환됩니다. 107 | ~~~java 108 | public final class MyExtension { 109 | @NonNull 110 | public final String withFoo(@NonNull String $receiver) { 111 | return withPrefix($receuver, "Foo"); 112 | } 113 | 114 | private final String withPrefix(@NonNull String $receiver, String prefix) { 115 | return prefix + $receiver; 116 | } 117 | } 118 | ~~~ 119 | * 확장 함수는 패키지 수준으로도 선언할 수 있습니다. 120 | ~~~kotlin 121 | package com.example.foo 122 | 123 | fun String.withBar() = this.withPostfix("Bar") 124 | 125 | private fun String.withPostfix(postFix: String) = "$this$postFix" 126 | ~~~ 127 | * 확장 함수는 {정의된 파일 이름}Kt 클래스 내 정적 함수로 변환됩니다. 즉, 확장 함수를 정의한 파일 이름이 MyExtension.kt였다면 MyExtensionKt 클래스가 생성됩니다. 128 | * 다음은 패키지 단위로 정의된 확장 함수가 Java코드로 변환된 결과를 보여줍니다. 129 | ~~~java 130 | public final class MyExtensionKt { 131 | public static final String withBar(@NotNull String $receiver, String postFix) { 132 | return $receiver + postFix; 133 | } 134 | } 135 | ~~~ 136 | ## 연산자 오버로딩 137 | * Java는 연산자 오버로딩(operator overloading)을 일체 허용하지 않지만, Kotlin은 사용자 정의 타입에 한해 연산자 오버로딩을 지원합니다. 138 | * 연산자 오버로딩을 지원하는 다른 언어와 유사하게, 각 연산자별로 사전에 정의된 함수를 재정의하는 방식으로 연산자 오버로딩을 사용할 수 있습니다. 139 | * 연산자 오버로딩을 위함 함수는 함수 정의에 operator 키워드가 추가되며, 기존의 연산자를 재정의 하는 것만 허용합니다. 140 | * 먼저 단항 연산자입니다. 141 | 142 | | 연산자 | 함수 | 143 | | ----- | ---- | 144 | | + | unaryPlus| 145 | | - | unaryMinus| 146 | | ! | not| 147 | | ++ | inc| 148 | | -- | dec| 149 | 150 | ~~~kotlin 151 | class Volume(var left: Int, var right: Int) { 152 | 153 | // 단항 연산자 '-'를 재정의합니다. 154 | operrator fun unaryMinus() : Volume { 155 | this.left = -this.left 156 | this.right = -this.right 157 | return this 158 | } 159 | 160 | // 단항 연산자 '++'를 재정의합니다. 161 | operator fun inc() : Volume { 162 | this.left += 1 163 | this.right += 1 164 | return this 165 | } 166 | 167 | // 단항 연산자 '--'를 재정의합니다. 168 | operator fun dec() : Volume { 169 | this.left -= 1 170 | this.right -= 1 171 | return this 172 | } 173 | } 174 | 175 | var volume = Volume(50, 50) 176 | 177 | // Volume 클래스 내 left, right 값이 반전되어 할당됩니다. 178 | val v1 = -volume 179 | 180 | // volume 객체의 left, right 값이 각각 1씩 증가합니다. 181 | volume++ 182 | 183 | // volume 객체의 left, right 값이 각각 1씩 감소합니다. 184 | volume-- 185 | ~~~ 186 | * 확장 함수를 사용하여 연산자를 재정의하는 것도 가능합니다. 187 | ~~~kotlin 188 | class Volume(var left: Int, var right: Int) 189 | 190 | // 확장 함수를 사용하여 단항 연산자 '-'를 재정의합니다. 191 | operator fun Volume.unaryMinus() : Volume { 192 | this.left = -this.left 193 | this.right = -this.right 194 | return this 195 | } 196 | 197 | // 확장 함수를 사용하여 단항 연산자 '++'를 재정의합니다. 198 | operator fun Volume.inc() : Volume { 199 | this.left += 1 200 | this.right += 1 201 | return this 202 | } 203 | 204 | // 확장 함수를 사용하여 단항 연산자 '--'를 재정의합니다. 205 | operator fun Volume { 206 | this.left -= 1 207 | this.right -= 1 208 | return this 209 | } 210 | ~~~ 211 | 212 | * 이항 연산자(binary operator)에 해당하는 함수들입니다. 213 | 214 | | 연산자 | 함수 | 215 | | ----- | ---- | 216 | | + | plus| 217 | | - | minus| 218 | | * | times| 219 | | / | div| 220 | | % | rem| 221 | ~~~kotlin 222 | class Volume(val left: Int, val right: Int) 223 | 224 | // 이항 연산자 '+'를 재정의합니다. 225 | operator fun Vloume.plus(other: Volume) 226 | = Volume(this.left + other.left, this.right + other.right) 227 | 228 | // 이항 연산자 '-'를 재정의합니다. 229 | operator fun Vloume.minus(other: Volume) 230 | = Volume(this.left - other.left, this.right - other.right) 231 | 232 | // v1에는 Volume(30, 40)과 동일한 값이 할당됩니다. 233 | val v1 = Volume(10, 10) + Volume(20, 30) 234 | 235 | // v2에는 Volume(30, 20)과 동일한 값이 할당됩니다. 236 | val v2 = Volume(50, 30) - Volume(20, 10) 237 | ~~~ 238 | 239 | * 비교 연산자(comparison operator)는 다른 연산자와 달리 각 연산자가 모두 동일한 함수에 할당되며, 해당 함수가 반환하는 값의 크기에 따라 해당 연산자의 참 거짓 여부를 판변합니다. 240 | * compareTo 함수의 반환형은 항상 Int이어야 합니다. 241 | 242 | | 연산자 | 함수 | 참인 경우 | 243 | | ----- | ---- | ----- | 244 | | > | compareTo| 반환값이 0보다 큰 경우| 245 | | < | compareTo| 반환값이 0보다 작은 경우| 246 | | >= | compareTo| 반환값이 0보다 크거나 같은 경우| 247 | | <= | compareTo| 반환값이 0보다 작거나 같은 경우| 248 | ~~~kotlin 249 | class Rectangle(val width: Int, val height: Int) 250 | 251 | // 사각형의 넓이를 비교한 값을 반환합니다. 252 | operator fun Rectangle.compareTo(other: Rectangle) : Int { 253 | val myDimension = this.width * this.height 254 | val otherDimension = other.width * other.height 255 | return myDimension - otherDimension 256 | } 257 | 258 | // 너비 10, 높이 10인 사각형(넓이 = 100) 259 | val a = Rectangle(10, 10) 260 | 261 | // 너비 2, 높이 10인 사각형(넓이 = 20) 262 | val b = Rectangle(2, 10) 263 | 264 | // true true false false가 출려됩니다. 265 | println("${a > b} ${a >= b} ${a <= b} ${a < b}") 266 | ~~~ 267 | * 동일성 비교 연산자(==)는 두 객체가 서로 같은 값을 가지고 있는가 여부를 확인하며, 이는 equals 함수에 할당됩니다. 268 | * Kotlin에서 == 연산자를 사용하면 equals 함수가 불린다는 것을 제외하면 Java의 equals 함수와 다를 것이 없으므로, Java에서 equals 메서드를 재정의하던 방법과 동일하게 함수 본체를 작성하면 됩니다. 269 | * 다른 연산자와 다르게 동일성 비교 연산자를 재정의할 때는 operator 키워드를 추가하지 않습니다. 270 | ~~~kotlin 271 | class Volume(var left: Int, var right: Int) { 272 | // '==' 연산자를 재정의합니다. 273 | // Java에서 equals() 메서드를 재정의하는 방식과 동일합니다. 274 | override fun equals(other: Any?): Boolean { 275 | if(other == this) { 276 | return true 277 | } 278 | if(other !is Volume) { 279 | return false 280 | } 281 | return other.left = this.left && other.right == this.right 282 | } 283 | } 284 | ~~~ 285 | * 배열이나 리스트의 인자에 접근할 때 사용하는 인덱스 접근 연산자(index access operator) 입니다. 대괄호([])를 사용하는 연산자이며, 값을 처리하는 방법에 따라 할당되는 함수가 달라집니다. 286 | 287 | | 연산자 | 용도 | 함수 | 288 | | ----- | ---- | ----- | 289 | | [ ] | 값을 읽는 목적| get| 290 | | [ ] | 값을 쓰는 목적| set| 291 | ~~~kotlin 292 | class Triple(var firstL Int, var second: Int, var third: Int) 293 | 294 | // Triple[index]가 값을 반환하는 경우 호출되는 함수를 재정의합니다. 295 | operator fun Triple.get(intdex: Int) = when(intdex) { 296 | 0 -> this.first 297 | 1 -> this.second 298 | 2 -> this.third 299 | else -> IllegalArgumentException() 300 | } 301 | 302 | // Triple[index]에 값이 할당되는 경우 호출되는 함수를 재정의합니다. 303 | operator fun Triple.set(intdex: Int, value: Int) { 304 | when(index) { 305 | 0 -> this.first = value 306 | 1 -> this.second = value 307 | 2 -> this.third = value 308 | else -> IllegalArgumentException() 309 | } 310 | } 311 | 312 | val triple = Triple(10, 20, 30) 313 | 314 | // triple 객체 내 first, second, third 프로퍼티의 값을 출력합니다. 315 | // 10 20 30이 출력됩니다. 316 | println("${triple[0]} ${triple[1]} ${triple[3]}") 317 | 318 | // triple 객체 내 first, second 프로퍼티 값을 변경합니다. 319 | triple[0] = 30 320 | triple[1] = 30 321 | 322 | // 30 30 30이 출력됩니다. 323 | println("${triple[0]} ${triple[1]} ${triple[3]}) 324 | ~~~ 325 | * 값의 변경과 할당을 동시에 하는 연산자도 있습니다. 이런 연산자를 복합 할당 연산자(augment assignment operator)라 하며, 이러한 연산자들도 재정의가 가능합니다. 326 | * 복합 할당 연산 중 일부를 재정의하고 사용할 수 있습니다. 327 | ~~~kotlin 328 | class Volume(var left: Int, var right: Int) 329 | 330 | // '+=' 연산자를 재정의합니다. 331 | operator fun Volume.plusAssign(other: Int) { 332 | this.left += other 333 | this.right += other 334 | } 335 | 336 | // '-=' 연산자를 재정의합니다. 337 | operator fun Volume.minusAssign(other: Int) { 338 | this.left -= other 339 | this.right -= other 340 | } 341 | 342 | val volume = Volume(50, 50) 343 | 344 | // volume 객체의 left, right 값을 20씩 증가시킵니다. 345 | volume += 20 346 | 347 | // volume 객체의 left, right 값을 10씩 감소시킵니다. 348 | volume -= 10 349 | ~~~ 350 | * 특정 원소의 포함 여부를 가리기 위해, Kotlin에서는 in 연산자를 사용합니다. 이 연산자를 재정의하려면 contains 함수를 재정의하면 됩니다. 351 | ~~~koltin 352 | class Line(val start: Int, val end: Int) 353 | 354 | // 'in' 연산자를 재정의합니다. 355 | // 주어진 점이 선의 시작점과 끝점 내에 있는지 확인합니다. 356 | operator fun Line.contains(pointL Int) : Boolean { 357 | return point in start..end 358 | } 359 | 360 | val line = Line(0, 10) 361 | 362 | // 점 5와 -1이 선 내에 포함되는지, 그리고 -1이 선 내에 포함되지 않는지 여부를 확인합니다. 363 | // true false true가 출력됩니다. 364 | println("${5 in line} ${-1 in Line} ${-1 !in line}") 365 | ~~~ 366 | ## 중위 표기법 지원 367 | * Kotlin에서는 사용자가 정의한 함수를 중위 표기법(infix notation)을 사용하여 호출할 수 있으며, 해당 함수는 다음 조건을 만족해야 합니다. 368 | * 함수 선언에 infix 키워드를 표기해야함 369 | * 확장 함수 혹은 멤버 함수이면서, 매개변수가 하나일 것 370 | * 다음 코드는 중위 표기법을 지원하는 함수를 선언하는 예와 그 사용 예를 보여줍니다. 371 | ~~~kotlin 372 | class Volume(var left: Int, var right: Int) { 373 | // 멤버로 선언된 함수에 중위 표기를 지원하도록 합니다. 374 | infix fun increaseBy(amount: Int) { 375 | this.left += amount 376 | this.right += amount 377 | } 378 | } 379 | 380 | // 확장 함수로 선언된 함수에 중위 표기를 지원하도록 합니다. 381 | infix fun Volume.decreaseBy(amount: Int) { 382 | this.left -= amount 383 | this.right -= amount 384 | } 385 | 386 | // 중위 표기를 지원하는 함수를 사용하는 예 387 | val currentVolume = Volume(50, 50) 388 | 389 | // currentVolume.increaseBy(30)과 동일합니다. 390 | currentVolume increaseBy 30 391 | 392 | // currentVolume.decreaseBy(20)과 동일합니다. 393 | currentVolume decreaseBy 20 394 | ~~~ 395 | -------------------------------------------------------------------------------- /Characteristics of Kotlin/람다 표현식/람다 표현식.md: -------------------------------------------------------------------------------- 1 | # 람다 표현식 2 | ## 람다 표현식 3 | * Kotlin의 람다 표현식은 Java의 람다 표현식보다 훨씬 간편하고 직관적인 문법을 가지고 있습니다. 4 | * 이 문서에서는 Java와 Kotlin의 람다 표현식이 어떻게 다른지 간단히 살펴보고, Kotlin의 람다 표현식에서만 사용할 수 있는 기능들을 살펴봅니다. 5 | ## 자바와 코틀린의 람다 표현식 6 | * 람다 표현식(lambda expression)dms 하나의 함수를 표현할 수 있습니다. 특히 익명 클래스(anonymous class)를 간결하게 표현할 때 사용할 수 있으므로 매우 유용합니다. 7 | * 다음은 Java로 익명 구현 객체를 이용하여 버튼의 리스너를 설정하는 코드입니다. 8 | ~~~java 9 | Button button = ....// 버튼 인스턴스 10 | button.setOnClickListener(new View.OnClickListener() { 11 | @Overrid 12 | public void onClick(View v) { 13 | // 버튼이 눌렸을 때 수행할 동작을 지정합니다. 14 | doSomething; 15 | } 16 | }); 17 | ~~~ 18 | * Java8부터는 람다 표현식을 지원하여, 하나의 메서드를 갖는 익명 클래스 대신 람다 표현식을 사용할 수 있습니다. 19 | ~~~java 20 | // 람다 표현식을 사용하여 리스너를 선언합니다. 21 | button.setOnClickListener((View v) -> doSomething()); 22 | 23 | // 인자의 타입을 생략할 수 있습니다. 24 | button.setOnClickListener(v -> doSomething()); 25 | ~~~ 26 | * Java7를 기반으로 하는 프로젝트에서도 Kotlin을 사용하면 람다 표현식을 사용할 수 있습니다. 27 | * Kotlin의 람다 표현식은 Java의 람다 표현식과 형태가 매우 유사하지만, 중괄호를 사용하여 앞뒤를 묶어준다는 점이 다릅니다. 28 | ~~~kotlin 29 | // 매개변수 // 함수 본체 30 | {x : Int, y : Int -> x + y} 31 | ~~~ 32 | * 앞의 Java코드를 Kotlin으로 바꾸면 다음과 같습니다. 33 | ~~~kotlin 34 | val button: Button = ...// 버튼 인스턴스 35 | 36 | // 람다 표현식을 사용하여 리스너를 선언합니다. 37 | button.setOnClickListener({ v: View -> doSomething()}) 38 | 39 | // 자바와 마찬가지로, 인자 타입을 생략할 수 있습니다. 40 | button.setOnClickListener({v -> doSomething()}) 41 | ~~~ 42 | * Java에서, 하나의 메서드만 호출하는 람다 표현식은 메서드 참조(method reference)를 사용하여 간략하게 표현할 수 있습니다. 43 | * Kotlin에서는 이를 멤버 참조(member reference)라는 이름으로 지원하며, 사용 방법이 Java와 동일합니다. 44 | ~~~kotlin 45 | // View를 인자로 받는 함수 46 | fun doSomethingWithView(view: View) { 47 | ... 48 | } 49 | 50 | val button: Button = ... // 버튼 인스턴스 51 | 52 | // 람다 표현식 내에서 doSomethingWithView() 함수 하나만 호출하고 있습니다. 53 | button.setOnClickListener({v -> doSomethingWithView(v)}) 54 | 55 | // 멤버 참조를 사용하여 doSomethingWithView() 함수를 바로 대입할 수 있습니다. 56 | button.setOnClickListener(::doSomethingWithView) 57 | ~~~ 58 | * 메서드만 참조할 수 있는 Java와 달리, Kotlin에서는 프로퍼티도 멤버 참조를 지원합니다. 59 | ~~~kotlin 60 | class Person (val naem: String, val age: Int) { 61 | // 성인 여부를 표시하는 프로퍼티 62 | val adult = age > 19 63 | } 64 | 65 | // 전체 사람 목록 중, 성인의 이름만 출력합니다. 66 | fun printAdults(people: List) { 67 | // 필터링 조건을 람다 표현식을 사용하여 대입하고 있습니다. 68 | // 단순히 adult 프로퍼티 값만 반환합니다. 69 | people.filter({person -> person.adult}) 70 | .forEach { println("Name= ${it.name}")} 71 | 72 | // 멤버 참조를 사용하여 adult 프로퍼티를 바로 대입합니다. 73 | people.filter(Person::adult) 74 | .forEach { println("Name= ${it.name}")} 75 | } 76 | ~~~ 77 | ## 코틀린 람다 표현식의 유용한 기능 78 | * 함수를 호출할 때 대입하는 인자 중 마지막 인자가 함수 타입이고, 이 인자에 함수를 대입할 때 람다 표현식을 사용한다면 이 람다표현식은 함수의 인자를 대입하는 괄호 외부에 선언할 수 있습니다. 79 | * 다음은 다이얼로그를 만드는 코드에서 함수의 인자로 리스너를 전달할 때, 전달하는 함수를 괄호 외부에 선언한 것입니다. 80 | ~~~kotlin 81 | val dialog = AlertDialog.Builder(this) 82 | ... 83 | // 함수 타입의 인자를 마지막 인자로 대입하고 있습니다. 84 | .setPositiveButton("OK"), { dialog, which -> doOnOkay(which)}) 85 | 86 | // 함수 타입의 인자는 괄호 외부에 선언할 수 있습니다. 87 | .setNegaticeButton("Cancel") { dialog, which -> doOnCancel(which)}.create() 88 | ~~~ 89 | * 함수가 단 하나의 함수 타입 매개변수를 가질 경우, 인자 대입을 위한 괄호를 생략하고 바로 람다 표현식을 사용할 수 있습니다. 90 | ~~~kotlin 91 | val button: Button = ... // 버튼 인스턴스 92 | 93 | // setOnClickListener의 마지막 인자로 함수 타입을 대입하고 있습니다. 94 | button.setOnClickListener({v -> doSomething()}) 95 | 96 | // 다른 인자가 없으므로, 괄호 없이 바로 외부에 람다 표현식을 사용할 수 있습니다. 97 | button.setOnClickListener { v -> doSomething()} 98 | ~~~ 99 | * Java에서는 람다 표현식에 무조건 매개변수를 선언해 주어야 했습니다. Kotlin에서는 람다 표현식 내 매개변수의 개수가 하나인 경우 매개변수 선언을 생략할 수 있으며, 이때 매개변수에 대한 참조가 필요한 경우 it을 사용할 수 있습니다. 100 | * Kotlin에서 it을 사용하여 람다 표현식을 더욱 간단하게 사용할 수 있습니다. 101 | ~~~kotlin 102 | val button: Button = ... // 버튼 인스턴스 103 | 104 | // 리스너에서 View를 인자로 받는 함수 doSomethingWithView()를 호출하고 있습니다. 105 | buttom,setOnClickListener { v -> doSomethingWithView(v)} 106 | 107 | // 매개변수가 하나만 있으므로 선언을 생략하고 it을 대신 사용할 수 있습니다. 108 | button.setOnClickListener { doSomethingWithView(it)} 109 | ~~~ 110 | * 여러 개의 매개변수를 갖는 람다 표현식에서 사용하지 않는 매개변수가 있을 경우, ㅐㅁ개변수 이름 대신_를 사용하여 사용하지 않는 매개변수라는 것을 명시할 수 있습니다. 111 | ~~~kotlin 112 | val dialog = AlertDialog.Builder(this) 113 | ... 114 | // 리스너 내에서 dialog 매개변수는 사용하고 있지 않습니다. 115 | .setPositiveButton("OK"), { dialog, which -> doOnOkay(which)}) 116 | 117 | // 사용하지 않는 매개변수에 이름 대신 '_'를 사용할 수 있습니다. 118 | .setNegativeButton("Cancel") {_, which -> doOnCancel(which)} 119 | .create() 120 | ~~~ 121 | ## 인라인 함수 122 | * 람다 표현식을 사용하면, 함수를 인자로 넘길 수 있는 고차함수(higher-order function)에 들어갈 함수형 인자를 쉽게 표현할 수 있습니다. 123 | * 그런데 람다 표현식을 사용하여 작성한 함수는 컴파일 과정에서 익명 클래스로 변환됩니다. 따라서 익명 클래스를 사용하는 코드를 호출할 때마다 매번 새로운 객체가 생성되므로 이러한 코드가 여러 번 호출되는 경우 실행 시점의 성능에 영향을 미치게 됩니다. 124 | * 인라인 함수(inline function)를 사용하면, 함수의 매개변수로 받는 함수형 인자의 본체를 해당 인자가 사용되는 부분에 그래도 대입하므로 성능 하락을 방지할 수 있습니다. 125 | * 인라인 함수로 선언하려면 함수 선언 앞에 inlin 키워드를 추가하면 됩니다. 126 | ~~~kotlin 127 | // 인자로 받은 함수를 내부에서 실행하는 함수 128 | inline fun doSomething(body: () -> Unit) { 129 | println("onPreExecute()") 130 | body() 131 | println("onPostExecute") 132 | } 133 | ~~~ 134 | ~~~kotlin 135 | // 인라인 함수를 호출합니다. 136 | doSomething { println("do Something")} 137 | // 앞의 구문은 다음과 같이 변환됩니다. 138 | println("onPreExecute()") 139 | // 인자로 전달된 함수 본체의 내용이 그대로 복사된 것을 확인할 수 있습니다. 140 | println("do Something()") 141 | println("onPostExecute()") 142 | ~~~ 143 | * 인라인 함수의 함수형 매개변수는 별도의 표기가 없을 경우 모두 인라인 처리됩니다. 인라인 함수의 함수형 인자 중, 인라인 처리되지 않아야 하는 항목이 있다면 매개변수에 noinline 키워드를 추가하면 됩니다. 144 | ~~~kotlin 145 | inline fun doSomething( 146 | inlinedBody: () -> Unit, 147 | noinline notInlinedBody: () -> Unit) { 148 | ... 149 | } 150 | ) 151 | ~~~ -------------------------------------------------------------------------------- /Characteristics of Kotlin/코틀린의 여타 특징/코틀린의 여타 특징.md: -------------------------------------------------------------------------------- 1 | # 코틀린의 여타 특징 2 | ## 코틀린의 여타 특징 3 | * 이 문서는 Kotlin에서만 제공하는 기능 중에서, 매우 유용하게 사용할 수 있는 몇가지 기능에 대해 간단히 설명합니다. 4 | ## 타입 별칭 5 | * 제네릭 타입을 사용하다 보면, 다소 복잡한 형태의 타입을 사용하게 되는 경우가 종종 있습니다. 이렇게 되면 제네릭의 타입 정의만으로는 개발자가 표현하고자 했던 정보를 정확히 유추하기 어렵습니다. 6 | * Kotlin에서는 타입 별칭(type alias) 기능을 제공하며, 이를 사용하여 복잡한 구조로 구성된 타입을 간략하게 표현할 수 있습니다. 타입 별칭은 typealias를 사용하여 정의합니다. 7 | ~~~kotlin 8 | // 사람 정보를 저장하는 리스트 9 | typealias PeopleList = List 10 | 11 | // 특정 태그를 가진 사람의 리스트를 포함하는 맴 12 | typealias PeopleInTags = Map 13 | ~~~ 14 | * 타입 별칭으로 선언한 타입은 기존의 타입과 완전히 동일하게 사용할 수 있습니다. 15 | ~~~kotlin 16 | // 인자로 받은 사람에게 메시지를 보내는 함수 17 | fun sendMessage(people: List) { 18 | people.forEach { 19 | // 메시지 전송 20 | } 21 | } 22 | ~~~ 23 | * 앞의 코드에서 List을 PeopleList라는 이름을 갖는 타입 별칭으로 선언하면 List 대신 PeopleList를 사용할 수 있습니다. 24 | ~~~Kotlin 25 | // 타입 별칭 선언 26 | typealias PeopleList = List 27 | 28 | // List 대신 PeopleList를 사용합니다. 29 | fun sendMessage(people: PeopleList) { 30 | people.forEach { 31 | // 메시지 전송 32 | } 33 | } 34 | ~~~ 35 | * 클래스나 함수와 마찬가지로 타입을 인자로 받을 수도 있습니다. 36 | ~~~kotlin 37 | // 특정 태그를 가진 자료의 리스트를 포함하는 맵 38 | typealias ItemInTag = Map 39 | ~~~ 40 | * 함수형 타입에도 타입 별칭을 지정할 수 있습니다. 41 | ~~~kotlin 42 | // 메시지를 보낼 사람을 선택할 때 기준이 되는 조건을 함수의 인자(filterFunc)로 받습니다. 43 | fun sendMessage(people: List, filterFunc: (Person) -> Boolean{ 44 | people.filter(filterFunc) 45 | .forEach { 46 | // 메시지 전송 47 | } 48 | } 49 | ~~~ 50 | * 앞의 코드에서 (Person) -> Boolean을 PersonFilter라는 이름으로 타입 별칭 선언 51 | ~~~kotlin 52 | // 함수형 타입을 타입 별칭으로 설정합니다. 53 | typealias PersonFilter = (Person) -> Boolean 54 | // 선언한 타입 별칭을 기존의 타입과 바꿔 사용할 수 있습니다. 55 | fun sendMessage(people: List, filterFunc: PersonFilter) { 56 | people.filter(filterFunc) 57 | .forEach { 58 | // 메시지 전송 59 | } 60 | } 61 | ~~~ 62 | * 타입 벼링을 사용하여 새로운 타입을 선언한다고 해서, 이 타입에 행당하는 새로운 클래스가 생성되는 것은 아닙니다. 63 | * 타입 별칭으로 선언된 타입은 컴파일 시점에 모두 원래 타입으로 변환되므로 실행 시점의 부하가 없다는 또 다른 장점이 있습니다. 64 | ~~~java 65 | class Person { 66 | ... 67 | public int getAge() { ... } 68 | public String getName() { ... } 69 | ... 70 | } 71 | 72 | Person person = ... // 사람을 표현하는 객체 73 | 74 | // 사람 객체에 포함된 필드를 각각 사용하려면 이를 수동으로 각 변수에 할당해야 합니다. 75 | int ageOfPerson = person.getAge(); 76 | String nameOfPerson = person.getName(); 77 | ~~~ 78 | * 반면, Kotlin에서는 각 프로퍼티가 가진 자료의 값을 한번에 여러 개의 값(val) 혹은 변수에 할당할 수 있습니다. 79 | * 이러한 기능을 분해 선언(destructuring declarations)이라 부릅니다. 80 | ~~~kotlin 81 | data class Person(val age: Int, val name: String) 82 | 83 | val person : Person = ... // 사람을 표현하는 객체 84 | 85 | // 사람 객체에 포함된 필드의 값을 한번에 여러 값(val)에 할당합니다. 86 | val (ageOfPerson, nameOfPerson) = person 87 | ~~~ 88 | * 분해 선언이 프로퍼티가 가진 자료의 값을 전달하는 방법 89 | ~~~kotlin 90 | val ageOfPerson: Int = person.component1() 91 | val nameOfPerson: String = person.component2() 92 | ~~~ 93 | * 분해 선언을 사용하면 내부적으로 각 값에 component1(), component2() 함수의 반환값을 할당합니다. 프로퍼티의 수가 늘어나는 경우 component3(), component4()와 같이 함수 뒤의 숫자가 증가하는 형태, 즉 componentN() 형태의 함수를 추가로 사용하게 됩니다. 94 | * 분해 선언을 사용하려면 클래스에 프로퍼티의 수만큼 componentN() 함수가 있어야하며, 이 함수들을 포함하고 있는 클래스에만 분해 선언을 사용할 수 있습니다. 95 | * 다음은 분해 선언을 기본으로 제공하는 클래스들입니다. 96 | * 데이터 클래스(data class)로 선언된 클래스 97 | * kotlin.Pair 98 | * kotlin.Triple 99 | * kotlin.collections.Map.Entry 100 | * 분해 선언은 반복문에서도 사용할 수 있으며, 특히 맴 자료구조를 사용할 때 유용합니다. 101 | * 다음 코드는 맵을 순회하는 코드에서 분해 선언을 사용하여 각각 키와 값을 선언하는 Kotlin 코드입니다. 102 | ~~~kotlin 103 | val cities: Map = ... // 도시 정보를 저장하고 있는 맵 104 | 105 | // 맵 내 각 항목의 키와 값을 별도로 선언하여 사용합니다. 106 | for((cityCode, name) in cities) { 107 | System.out.println("$cityCode=$name) 108 | } 109 | ~~~ 110 | * 람다 표현식에서도 이 기능을 사용할 수 있습니다. 111 | ~~~kotlin 112 | val cities: Map = ... // 도시 정보를 저장하고 있는 맵 113 | 114 | // 람다 표현식 내 매개변수에서도 분해 선언을 사용할 수 있습니다. 115 | cities.forEach { cityCode, name -> 116 | System.out.println("$cityCode=$name") 117 | } 118 | ~~~ 119 | * Kotlin에서 개발자가 작성한 클래스에서 분해 선언 기능을 사용하고 싶다면, 해당 클래스 내에 별도로 componentN() 함수를 프로퍼티의 선언 순서 및 타입에 알맞게 추가해주면 됩니다. 120 | * 이 함수는 일종의 규칙처럼 선언되어야 하는 만큼 componentN() 함수를 선언할 때에는 앞에 operator를 붙여 주어야 합니다. 121 | ~~~kotlin 122 | class Person(val age: Int, val name: String) { 123 | // 첫 번째 프로퍼티의 값을 반환합니다. 124 | operator fun component1() = this.age 125 | 126 | // 두 번째 프로퍼티의 값을 반환합니다. 127 | operator fun componenet2() = this.name 128 | } 129 | 130 | val person: Person = ... // 사람을 표현하는 객체 131 | 132 | // 분해 선언을 사용할 수 있습니다. 133 | val (age, name) = person 134 | ~~~ 135 | * 람다 표현식의 매개변수와 마찬가지로, 분해 표현식에서도 사용하지 않는 값 혹은 변수가 있다면 이름 대신_를 사용하여 별도의 값이나 변수로 선언되지 않도록 할 수 있습니다. 136 | ~~~kotlin 137 | val person: Person = ... // 사람을 표현하는 객체 138 | 139 | // name 값만 사용하고 싶은 경우 다음과 같이 선언합니다. 140 | val (_, name) = person 141 | ~~~ -------------------------------------------------------------------------------- /Characteristics of Kotlin/클래스/클래스.md: -------------------------------------------------------------------------------- 1 | # 클래스 2 | ## 클래스 3 | * Kotlin의 클래스는 Java와 유사하지만, 몇몇 기능을 추가로 제공합니다. 4 | ## 데이터 클래스 5 | * Java에서는 여러 가지 유형의 자료를 구분하고 그 값을 관리하기 위해 Class를 사용합니다. 6 | ~~~java 7 | class Person { 8 | private String name; 9 | 10 | private String address; 11 | 12 | public Person(String name, String address) { 13 | this.naem = name; 14 | this.address = address; 15 | } 16 | 17 | public void setName(String name) { 18 | this.name = name; 19 | } 20 | 21 | public void setAddress(String address) { 22 | this.address = address; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public String getAddress() { 30 | return address; 31 | } 32 | } 33 | ~~~ 34 | 35 | * 단순히 자료를 담는 용도로 사용한다면 위의 코드만으로 충분합니다. 하지만 위 클래스로 표현된 자료 간의 비교나 연산을 제대로 수행하려면, 코드에 equals() 및 hashCode() 메서드를 추가로 구현해야 합니다. 36 | * equals() 및 hashCode() 메서드를 생성하는 시점에 정의된 필드를 기준으로 생성된 것이므로 차후 필드가 추가될 때 equals() 및 hashCode() 메서드를 함께 갱신해 주어야 합니다. 37 | * 또, equals() 및 hashCode() 메서드를 갱신하는 절차를 누락하면 버그가 발생할 간으성이 높고, 필드의 수에 비례하여 코드의 양도 늘어나므로 코드리뷰를 수행할 때에 힘이 듭니다. 38 | --- 39 | * 하지만 Kotlin은 이런 자료를 저장하는 클래스를 만드는 과정을 단순하게 하기 위해, 데이터 클래스(data class)라는 특별한 클래스를 제공합니다. 40 | * 데이터 클래스는 자료를 구성하는 프로퍼티만 선언하면 컴파일러가 equals(), hashCode(), toString() 함수를 자동으로 생성해 줍니다. 41 | * 주 생성자에서 데이터 클래스에 포함되는 프로퍼티를 함께 선언합니다. 42 | ~~~kotlin 43 | data class Person(val nameL String, val address: String) 44 | ~~~ 45 | * 이 코드는 줄어들었을 뿐만 아니라 더 유연하고 동일한 기능을 합니다. 46 | * 데이터 클래스 내에 컴파일러가 생성한 equals(), hashCode(), toString() 함수의 동작을 확인할 수 있습니다. 47 | ~~~kotlin 48 | val john = Person("John Doe", "Somewhere") 49 | val john2 = Person("John Doe", "Somewhere") 50 | val jane = Person("Jane Doe", "Anywhere") 51 | 52 | println("John == John2? = ${john == john2}") 53 | println("John == Jane? = ${john == jane}") 54 | println("John.hashCode() = ${john.hashCode()}") 55 | 56 | // john.toString 57 | println("John = $john") 58 | 59 | // jane.toString() 60 | println("Jane = $jane") 61 | ~~~ 62 | * 잘 작동합니다. 63 | ## 한정 클래스 64 | * 한정 클래스(sealed class)는 enum 클래스를 확장한 개념을 가진 클래스로, 각 종류별로 하나의 인스턴스만 생성되어 있는 enum 클래스와 달리 인스턴스를 여러 개 생성할 수 있습니다. 65 | * 한정 클래스 enum 클래스의 특징을 그대로 가지고 있으므로, 이를 상속하는 클래스는 한정 클래스로 정의되는 여러 종류 중 하나로 취급됩니다. 66 | * 한정 클래스 사용 예 67 | ~~~kotlin 68 | sealed class MobileApp(val os: String) { 69 | class Android(os: String, val packageName: String) : MobileApp(os) 70 | 71 | class IOS(os: String, val bundleId: String) : MobileApp(os) 72 | } 73 | ~~~ 74 | * 한정 클래스를 상속하는 클래스는 일반적으로 클래스 내에 중첩하여 선언합니다. 같은 파일이라면 클래스 외부에 선언할 수도 있습니다. 75 | ~~~kotlin 76 | sealed class MobileApp(val os: String) 77 | 78 | class Android(os: String, val packageName: String) : MobileApp(os) 79 | 80 | class IOS(os: String, val bundleId: String) : MobileApp(os) 81 | ~~~ 82 | * 한정 클래스는, 한정 클래스로 정의된 클래스의 종류에 따라 다른 작업을 처리해야 할 때 매우 유용합니다. 83 | ~~~kotlin 84 | fun whoami(app: MobileApp) = when (app) { 85 | is MobileApp.Android -> println("${aoo.os} / ${app.packageName}") 86 | is MobileApp.IOS -> println("${app.os} / ${app.bundleId}") 87 | // 모든 경우를 처리했으므로 else를 쓰지 않아도 됩니다. 88 | } 89 | ~~~ 90 | * MobileApp 클래스가 한정 클래스이므로, when 문에서 MobileApp 클래스를 상속하는 모든 클래스를 처리했는지 여부를 알 수 있습니다. 91 | * 모든 경우를 처리했다면 else를 처히라지 않아도 됩니다. 이런 특징은 한정 클래스를 상속하는 다른 유형의 클래스를 추가할 떄 매우 유용합니다. 92 | ~~~kotlin 93 | sealed class MobileApp(val os: String) { 94 | class Android(os: String, val packageName: String) : MobileApp(os) 95 | 96 | class IOS(os: String, val bundleId: String) : MobileApp(os) 97 | 98 | class WindowsMobile(os: String, package: String) : MobileApp(os) 99 | } 100 | ~~~ 101 | * 만약 MobileApp 클래스가 한정 클래스가 아닐 경우 이 클래스를 상속하는 클래스는 Android와 IOS 클래스 이외에도 얼마든지 존재할 수 있습니다. 그래서 when문에 else 절을 추가해야 합니다. 102 | ~~~kotlin 103 | fun whoami(app: MobileApp) = when (app) { 104 | is MobileApp.Android -> println("${app.os}/${app.packageName}") 105 | is MobileApp.IOS -> println("${app.os}/${app.bundleId}") 106 | // MobileApp 클래스를 상속한 클래스 중 Android, IOS 클래스가 아닌 경우를 처리해야합니다. 107 | else -> println("${app.os}") 108 | } 109 | ~~~ 110 | * 새로 추가된 클래스는 else절에서 처리됩니다. 유의할 점은 추가된 클래스를 별도의 처리를 해주지 않아도 컴파일 에러가 발생하지 않는다는 것입니다. 그래서 누락하기 쉽습니다. 111 | * 한정 클래스로 지정하고 else 절을 사용하지 않도록 변경하면 이러한 실수를 방지할 수 있습니다. 112 | * 새로 추가된 유형을 처리하지 않으면 컴파일 에러가 발생하므로 새로운 유형에 대한 처리가 누락되는 것을 방지할 수 있습니다. 113 | ~~~kotlin 114 | // when 문에서 Android, IOS의 경우만 처리하고 새로 추가된 유형은 처리하지 않고 있으므로 115 | // 'add necessary ' is WindowsMobile' branch or 'else' branch instead' 메시지와 함꼐 116 | // 컴파일 에러가 발생합니다. 117 | fun whoami(app: MobileApp) = when (app) { 118 | is MobileApp.Android -> println("${app.os} / ${app.packageName}") 119 | is MobileApp.IOS -> println("${app.os}/${app.bundleId}") 120 | // else나 WindowsMobile에 대한 처리가 누락되어 있습니다. 121 | } 122 | ~~~ 123 | 124 | ## 프로퍼티의 사용자 지정 Getter/Setter 125 | * 프로퍼티에는 내부에 저장된 필드 값을 가져오거나 설정할 수 있도록 Getter 및 Setter를 내부적으로 구현하고 있으며, 단순히 필드의 값을 반환하거나 설젇하도록 구현되어 있습니다. 126 | * 사용자 지정 Getter/Setter를 사용하면 프로퍼티에서 Getter 및 Setter의 구현을 원하는 대로 변경할 수 있으며, 특정 객체의 값에 따른 다양한 정보를 속성 형태로 제공할 때 유용합니다. 127 | * 사용자 지정 Getter/Setter는 프로퍼티 선언과 함께 get() 및 set(value)를 사용하여 선언할 수 있습니다. 128 | ~~~kotlin 129 | var [: ] [=] 130 | [] 131 | [] // 사용자 지정 Getter, Setter를 의미 132 | ~~~ 133 | * 사람에 대한 정보 134 | ~~~kotlin 135 | class Person(val age: Int, val name: String) 136 | ~~~ 137 | * 나이에 따른 성인 여부를 다음과 같이 새로운 프로퍼티 adult와 사용자 지정 Getter를 사용하면 쉽게 구현할 수 있습니다. 138 | ~~~kotlin 139 | class Person(val age: Int, val name: String) { 140 | val adult: Boolean 141 | get() = age >= 19 // 19세 이상이면 성인으로 간주합니다. 142 | } 143 | ~~~ 144 | * 사용자 지정 Setter를 사용하면 프로퍼티 내 필드에 설정되는 값을 제어할 수 있으나, 읽고 쓰기가 모두 가능한 프로퍼티(var)에서만 사용할 수 있습니다. 145 | ~~~koltin 146 | class Person(val age: Int, val name: String) { 147 | val adult: Boolean 148 | get() = age >= 19 149 | 150 | val address: String = "" 151 | set(value) { 152 | // 인자로 들어온 문자열의 앞 10 자리만 필드에 저장합니다. 153 | field = value.substring(0..9) 154 | } 155 | } 156 | ~~~ -------------------------------------------------------------------------------- /Characteristics of Kotlin/함수/함수.md: -------------------------------------------------------------------------------- 1 | # 함수 2 | ## 함수 3 | * Kotlin의 함수는 Java의 메서드와 동일한 기능을 수행하지만, 표현 형태가 더 자유롭고 Java의 메서드에서는 제공하지 않는 여러 유용한 기능을 갖추고 있습니다. 4 | * 이 문서에서는 Kotlin의 함수에서만 사용할 수 있는 유용한 특징과 기능에 대해 알아봅니다. 5 | ## 명명된 인자 6 | * Kotlin에서는 명명된 인자를 사용함으로써 함수를 호출할 때 매개변수의 순서와 상관없이 인자를 전달할 수 있습니다. 7 | * 명명된 인자를 사용하면 매개변수의 수가 많아지더라도 각 인자에 어떤 값이 전달되는지 쉽게 구분할 수 있습니다. 8 | ~~~kotlin 9 | // 원을 그리는 함수 10 | fun drawCircle(x: Int, y: Int, radius: Int){ 11 | 12 | } 13 | ~~~ 14 | * 위의 함수를 호출할 때 다음과 같이 항상 매개변수가 정의된 순서대로 인자를 대입해야 합니다. 함수의 매개변수 정보를 알고 있지 않다면, 대입된 값이 각각 무엇을 의미하는 알기 어렵습니다. 15 | ~~~kotlin 16 | // 중심축이 (10, 5)이고 반지름이 25인 원을 그립니다. 17 | drawCircle(10, 5, 25) 18 | ~~~ 19 | * Kotlin에서는 명명된 인자를 지원하므로 매개변수의 이름과 함께 인자를 대입할 수 있습니다. 20 | ~~~ kotlin 21 | // 명명된 인자를 사용하여 함수를 호출합니다. 22 | drawCircle(x = 10, y = 5, radius = 25) 23 | 24 | // 대입하는 인자 중 일부에만 사용할 수도 있습니다. 25 | drawCircle(10, 5, radius = 25) 26 | ~~~ 27 | ## 기본 매개 변수 28 | * Java에서 메서드의 매개변수가 많은 경우, 이를 조금 더 편리하게 사용하기 위해 축약된 매개변수를 갖는 메서드와 전체 매개변수를 갖는 메서드를 별도로 만들어 사용했습니다. 29 | ~~~java 30 | // 반지름을 지정하지 않을 경우 25로 설정합니다. 31 | void drawCircle(int x, int y) { 32 | // 원본 메서드를 호출합니다. 33 | drawCircle(x, y, 25); 34 | } 35 | 36 | // 모든 매개변수를 갖는 원본 메세드 37 | void drawCircle(int x, int y, int radius) { 38 | 39 | } 40 | 41 | // 중심축이 (10, 5)인 원을 그립니다. 42 | // 반지름을 지정하지 않았으므로 원의 반지름은 25가 됩니다. 43 | drawCircle(10, 5) 44 | ~~~ 45 | * Java애서는 매개변수에 아무 값이 대입되지 않을 경우 기본값을 지정할 수 없기에 앞의 예와 같이 두 종류의 메서드를 만들어야 했습니다. 46 | * Kotlin에서는 함수의 매개변수에 기본값을 지정할 수 있으며, 이때, 지정하는 값을 기본 매개변수(default parameter)라 부릅니다. 47 | ~~~kotlin 48 | // 반지름의 기본값으로 25를 갖는 함수 49 | fun drawCirCle(x: Int, y: Int, radius: Int = 25) { 50 | 51 | } 52 | 53 | // 중심축이 (10, 5)인 원을 그립니다. 54 | // 반지름을 지정하지 않았으므로 원의 반지름은 25가 됩니다. 55 | drawCircle(10, 5) 56 | ~~~ 57 | 58 | ## 단일 표현식 표기 59 | * Kotlin에서는 Unit타입을 제외한 타입을 반환하는 함수라면 함수의 내용을 단일 표현식을 사용하여 정의할 수 있습니다. 60 | ~~~kotlin 61 | fun theAnswerToLifeTheUniverseAndEverything(): Int { 62 | return 21 * 2 63 | } 64 | ~~~ 65 | * 단일 표현식 표기를 사용하면 다음과 같이 정의할 수 있습니다. 66 | ~~~kotlin 67 | fun theAnswerToLifeTheUniverseAndEverything(): Int = 21 * 2 68 | ~~~ 69 | * 단일 표현식 표기를 사용하는 경우, 다음과 같이 반환 타입을 생략하는 것도 가능합니다. 70 | ~~~kotlin 71 | fun theAnswerToLifeTheUniverseAndEverything() = 21 * 2 72 | ~~~ 73 | ## 확장 함수 74 | * Java에서는 기존에 만들어져 있는 클래스에 새로운 메서드를 추가하려면 해당 클래스를 상속하는 새로운 클래스를 작성해야 합니다. 75 | * Kotlin에서는 확장 함수(extension function)를 사용하여 상속없이 기존 클래스에 새로운 함수를 추가할 수 있습니다. 76 | * 확장 함수를 추가할 대상 클래스는 리시버 타입(receiver type)이라 부르며, 이는 리시버 타입 뒤에 점(.)을 찍고 그 뒤에 원하는 함수의 형태를 적는 방식으로 정의합니다. 77 | * 확장 함수 구현부에서는 this를 사용하여 클래스의 인스턴스에 접근할 수 있으며, 아룰 리시버 객체(receiver object)라 부릅니다. 78 | ~~~kotlin 79 | // String 클래스에 withPostfix() 함수를 추가합니다. 80 | // this를 사용하여 인스턴스에 접근할 수 있습니다. 81 | private fun String.withPostfix(postFix: String) = "$this$postFix" 82 | 83 | // this를 사용하여 인스턴스에 접근할 수 있으므로, 앞에서 정의한 확장 함수를 사용할 수 있습니다. 84 | fun String.withBar() = this.withPostfix("Bar") 85 | ~~~ 86 | * 이렇게 정의한 확장 함수는 리시버 타입에 정의한 함수를 사용하는 것과 동일한 방법으로 호출 할 수 있습니다. 87 | ~~~kotlin 88 | val foo = "Foo" 89 | 90 | // String 클래스에 포함된 함수를 호출하듯이 사용합니다. 91 | // 값 foobar에는 "FooBar"가 할당됩니다. 92 | val foobar = goo.withBar() 93 | ~~~ 94 | * 확장 함수를 호출하는 모습이 클래스 내 정의된 함수의 경우와 똑같다 할지라도, 이는 엄연히 클래스 외부에서 정의하는 함수입니다. 95 | * 따라서 리시버 객체에서는 클래스 내 public으로 정의된 프로퍼티나 함수에만 접근할 수 있습니다. 96 | ## 기존 클래스에 확장함수를 추가하는 법 97 | * 확장함수는 리시버 타입에 직접 추가되는 함수가 아닙니다. 리시버 타입과 확장 함수의 인자를 인자로 받는 새로운 함수를 만들고, 확장 함수를 호출하면 이 새로운 함수를 대신 호출합니다. 98 | * 새로운 함수가 정의되는 위치는 확장 함수를 정의하는 위치에 따라 달라집니다. 99 | ~~~kotlin 100 | class MyExtension { 101 | fun String.withFoo() = this.withPrefix("Foo") 102 | 103 | private fun String.withPrefix(prefix: String) = "$prefix$this" 104 | } 105 | ~~~ 106 | * 위 코드는 컴파일 과정에서 다음의 Java 코드와 동일한 형태로 변환됩니다. 107 | ~~~java 108 | public final class MyExtension { 109 | @NonNull 110 | public final String withFoo(@NonNull String $receiver) { 111 | return withPrefix($receuver, "Foo"); 112 | } 113 | 114 | private final String withPrefix(@NonNull String $receiver, String prefix) { 115 | return prefix + $receiver; 116 | } 117 | } 118 | ~~~ 119 | * 확장 함수는 패키지 수준으로도 선언할 수 있습니다. 120 | ~~~kotlin 121 | package com.example.foo 122 | 123 | fun String.withBar() = this.withPostfix("Bar") 124 | 125 | private fun String.withPostfix(postFix: String) = "$this$postFix" 126 | ~~~ 127 | * 확장 함수는 {정의된 파일 이름}Kt 클래스 내 정적 함수로 변환됩니다. 즉, 확장 함수를 정의한 파일 이름이 MyExtension.kt였다면 MyExtensionKt 클래스가 생성됩니다. 128 | * 다음은 패키지 단위로 정의된 확장 함수가 Java코드로 변환된 결과를 보여줍니다. 129 | ~~~java 130 | public final class MyExtensionKt { 131 | public static final String withBar(@NotNull String $receiver, String postFix) { 132 | return $receiver + postFix; 133 | } 134 | } 135 | ~~~ 136 | ## 연산자 오버로딩 137 | * Java는 연산자 오버로딩(operator overloading)을 일체 허용하지 않지만, Kotlin은 사용자 정의 타입에 한해 연산자 오버로딩을 지원합니다. 138 | * 연산자 오버로딩을 지원하는 다른 언어와 유사하게, 각 연산자별로 사전에 정의된 함수를 재정의하는 방식으로 연산자 오버로딩을 사용할 수 있습니다. 139 | * 연산자 오버로딩을 위함 함수는 함수 정의에 operator 키워드가 추가되며, 기존의 연산자를 재정의 하는 것만 허용합니다. 140 | * 먼저 단항 연산자입니다. 141 | 142 | | 연산자 | 함수 | 143 | | ----- | ---- | 144 | | + | unaryPlus| 145 | | - | unaryMinus| 146 | | ! | not| 147 | | ++ | inc| 148 | | -- | dec| 149 | 150 | ~~~kotlin 151 | class Volume(var left: Int, var right: Int) { 152 | 153 | // 단항 연산자 '-'를 재정의합니다. 154 | operrator fun unaryMinus() : Volume { 155 | this.left = -this.left 156 | this.right = -this.right 157 | return this 158 | } 159 | 160 | // 단항 연산자 '++'를 재정의합니다. 161 | operator fun inc() : Volume { 162 | this.left += 1 163 | this.right += 1 164 | return this 165 | } 166 | 167 | // 단항 연산자 '--'를 재정의합니다. 168 | operator fun dec() : Volume { 169 | this.left -= 1 170 | this.right -= 1 171 | return this 172 | } 173 | } 174 | 175 | var volume = Volume(50, 50) 176 | 177 | // Volume 클래스 내 left, right 값이 반전되어 할당됩니다. 178 | val v1 = -volume 179 | 180 | // volume 객체의 left, right 값이 각각 1씩 증가합니다. 181 | volume++ 182 | 183 | // volume 객체의 left, right 값이 각각 1씩 감소합니다. 184 | volume-- 185 | ~~~ 186 | * 확장 함수를 사용하여 연산자를 재정의하는 것도 가능합니다. 187 | ~~~kotlin 188 | class Volume(var left: Int, var right: Int) 189 | 190 | // 확장 함수를 사용하여 단항 연산자 '-'를 재정의합니다. 191 | operator fun Volume.unaryMinus() : Volume { 192 | this.left = -this.left 193 | this.right = -this.right 194 | return this 195 | } 196 | 197 | // 확장 함수를 사용하여 단항 연산자 '++'를 재정의합니다. 198 | operator fun Volume.inc() : Volume { 199 | this.left += 1 200 | this.right += 1 201 | return this 202 | } 203 | 204 | // 확장 함수를 사용하여 단항 연산자 '--'를 재정의합니다. 205 | operator fun Volume { 206 | this.left -= 1 207 | this.right -= 1 208 | return this 209 | } 210 | ~~~ 211 | 212 | * 이항 연산자(binary operator)에 해당하는 함수들입니다. 213 | 214 | | 연산자 | 함수 | 215 | | ----- | ---- | 216 | | + | plus| 217 | | - | minus| 218 | | * | times| 219 | | / | div| 220 | | % | rem| 221 | ~~~kotlin 222 | class Volume(val left: Int, val right: Int) 223 | 224 | // 이항 연산자 '+'를 재정의합니다. 225 | operator fun Vloume.plus(other: Volume) 226 | = Volume(this.left + other.left, this.right + other.right) 227 | 228 | // 이항 연산자 '-'를 재정의합니다. 229 | operator fun Vloume.minus(other: Volume) 230 | = Volume(this.left - other.left, this.right - other.right) 231 | 232 | // v1에는 Volume(30, 40)과 동일한 값이 할당됩니다. 233 | val v1 = Volume(10, 10) + Volume(20, 30) 234 | 235 | // v2에는 Volume(30, 20)과 동일한 값이 할당됩니다. 236 | val v2 = Volume(50, 30) - Volume(20, 10) 237 | ~~~ 238 | 239 | * 비교 연산자(comparison operator)는 다른 연산자와 달리 각 연산자가 모두 동일한 함수에 할당되며, 해당 함수가 반환하는 값의 크기에 따라 해당 연산자의 참 거짓 여부를 판변합니다. 240 | * compareTo 함수의 반환형은 항상 Int이어야 합니다. 241 | 242 | | 연산자 | 함수 | 참인 경우 | 243 | | ----- | ---- | ----- | 244 | | > | compareTo| 반환값이 0보다 큰 경우| 245 | | < | compareTo| 반환값이 0보다 작은 경우| 246 | | >= | compareTo| 반환값이 0보다 크거나 같은 경우| 247 | | <= | compareTo| 반환값이 0보다 작거나 같은 경우| 248 | ~~~kotlin 249 | class Rectangle(val width: Int, val height: Int) 250 | 251 | // 사각형의 넓이를 비교한 값을 반환합니다. 252 | operator fun Rectangle.compareTo(other: Rectangle) : Int { 253 | val myDimension = this.width * this.height 254 | val otherDimension = other.width * other.height 255 | return myDimension - otherDimension 256 | } 257 | 258 | // 너비 10, 높이 10인 사각형(넓이 = 100) 259 | val a = Rectangle(10, 10) 260 | 261 | // 너비 2, 높이 10인 사각형(넓이 = 20) 262 | val b = Rectangle(2, 10) 263 | 264 | // true true false false가 출려됩니다. 265 | println("${a > b} ${a >= b} ${a <= b} ${a < b}") 266 | ~~~ 267 | * 동일성 비교 연산자(==)는 두 객체가 서로 같은 값을 가지고 있는가 여부를 확인하며, 이는 equals 함수에 할당됩니다. 268 | * Kotlin에서 == 연산자를 사용하면 equals 함수가 불린다는 것을 제외하면 Java의 equals 함수와 다를 것이 없으므로, Java에서 equals 메서드를 재정의하던 방법과 동일하게 함수 본체를 작성하면 됩니다. 269 | * 다른 연산자와 다르게 동일성 비교 연산자를 재정의할 때는 operator 키워드를 추가하지 않습니다. 270 | ~~~kotlin 271 | class Volume(var left: Int, var right: Int) { 272 | // '==' 연산자를 재정의합니다. 273 | // Java에서 equals() 메서드를 재정의하는 방식과 동일합니다. 274 | override fun equals(other: Any?): Boolean { 275 | if(other == this) { 276 | return true 277 | } 278 | if(other !is Volume) { 279 | return false 280 | } 281 | return other.left = this.left && other.right == this.right 282 | } 283 | } 284 | ~~~ 285 | * 배열이나 리스트의 인자에 접근할 때 사용하는 인덱스 접근 연산자(index access operator) 입니다. 대괄호([])를 사용하는 연산자이며, 값을 처리하는 방법에 따라 할당되는 함수가 달라집니다. 286 | 287 | | 연산자 | 용도 | 함수 | 288 | | ----- | ---- | ----- | 289 | | [ ] | 값을 읽는 목적| get| 290 | | [ ] | 값을 쓰는 목적| set| 291 | ~~~kotlin 292 | class Triple(var firstL Int, var second: Int, var third: Int) 293 | 294 | // Triple[index]가 값을 반환하는 경우 호출되는 함수를 재정의합니다. 295 | operator fun Triple.get(intdex: Int) = when(intdex) { 296 | 0 -> this.first 297 | 1 -> this.second 298 | 2 -> this.third 299 | else -> IllegalArgumentException() 300 | } 301 | 302 | // Triple[index]에 값이 할당되는 경우 호출되는 함수를 재정의합니다. 303 | operator fun Triple.set(intdex: Int, value: Int) { 304 | when(index) { 305 | 0 -> this.first = value 306 | 1 -> this.second = value 307 | 2 -> this.third = value 308 | else -> IllegalArgumentException() 309 | } 310 | } 311 | 312 | val triple = Triple(10, 20, 30) 313 | 314 | // triple 객체 내 first, second, third 프로퍼티의 값을 출력합니다. 315 | // 10 20 30이 출력됩니다. 316 | println("${triple[0]} ${triple[1]} ${triple[3]}") 317 | 318 | // triple 객체 내 first, second 프로퍼티 값을 변경합니다. 319 | triple[0] = 30 320 | triple[1] = 30 321 | 322 | // 30 30 30이 출력됩니다. 323 | println("${triple[0]} ${triple[1]} ${triple[3]}) 324 | ~~~ 325 | * 값의 변경과 할당을 동시에 하는 연산자도 있습니다. 이런 연산자를 복합 할당 연산자(augment assignment operator)라 하며, 이러한 연산자들도 재정의가 가능합니다. 326 | * 복합 할당 연산 중 일부를 재정의하고 사용할 수 있습니다. 327 | ~~~kotlin 328 | class Volume(var left: Int, var right: Int) 329 | 330 | // '+=' 연산자를 재정의합니다. 331 | operator fun Volume.plusAssign(other: Int) { 332 | this.left += other 333 | this.right += other 334 | } 335 | 336 | // '-=' 연산자를 재정의합니다. 337 | operator fun Volume.minusAssign(other: Int) { 338 | this.left -= other 339 | this.right -= other 340 | } 341 | 342 | val volume = Volume(50, 50) 343 | 344 | // volume 객체의 left, right 값을 20씩 증가시킵니다. 345 | volume += 20 346 | 347 | // volume 객체의 left, right 값을 10씩 감소시킵니다. 348 | volume -= 10 349 | ~~~ 350 | * 특정 원소의 포함 여부를 가리기 위해, Kotlin에서는 in 연산자를 사용합니다. 이 연산자를 재정의하려면 contains 함수를 재정의하면 됩니다. 351 | ~~~koltin 352 | class Line(val start: Int, val end: Int) 353 | 354 | // 'in' 연산자를 재정의합니다. 355 | // 주어진 점이 선의 시작점과 끝점 내에 있는지 확인합니다. 356 | operator fun Line.contains(pointL Int) : Boolean { 357 | return point in start..end 358 | } 359 | 360 | val line = Line(0, 10) 361 | 362 | // 점 5와 -1이 선 내에 포함되는지, 그리고 -1이 선 내에 포함되지 않는지 여부를 확인합니다. 363 | // true false true가 출력됩니다. 364 | println("${5 in line} ${-1 in Line} ${-1 !in line}") 365 | ~~~ 366 | ## 중위 표기법 지원 367 | * Kotlin에서는 사용자가 정의한 함수를 중위 표기법(infix notation)을 사용하여 호출할 수 있으며, 해당 함수는 다음 조건을 만족해야 합니다. 368 | * 함수 선언에 infix 키워드를 표기해야함 369 | * 확장 함수 혹은 멤버 함수이면서, 매개변수가 하나일 것 370 | * 다음 코드는 중위 표기법을 지원하는 함수를 선언하는 예와 그 사용 예를 보여줍니다. 371 | ~~~kotlin 372 | class Volume(var left: Int, var right: Int) { 373 | // 멤버로 선언된 함수에 중위 표기를 지원하도록 합니다. 374 | infix fun increaseBy(amount: Int) { 375 | this.left += amount 376 | this.right += amount 377 | } 378 | } 379 | 380 | // 확장 함수로 선언된 함수에 중위 표기를 지원하도록 합니다. 381 | infix fun Volume.decreaseBy(amount: Int) { 382 | this.left -= amount 383 | this.right -= amount 384 | } 385 | 386 | // 중위 표기를 지원하는 함수를 사용하는 예 387 | val currentVolume = Volume(50, 50) 388 | 389 | // currentVolume.increaseBy(30)과 동일합니다. 390 | currentVolume increaseBy 30 391 | 392 | // currentVolume.decreaseBy(20)과 동일합니다. 393 | currentVolume decreaseBy 20 394 | ~~~ 395 | -------------------------------------------------------------------------------- /Functional Kotlin/ Immutability.md: -------------------------------------------------------------------------------- 1 | # Immutability 2 | 3 | ## 불변성이란? 4 | * 본질적으로 함수형 프로그래밍은 스레드 안전입니다. 5 | * 불변성은 스레드를 안전하게 만드는 데 큰 역할을 합니다. 6 | * 사전적인 정의로 가면 불변성은 무언가가 변할 수 없다는 것을 의미합니다. 7 | * 실제로 불변성은 변경 금지에 대한 것이 아니라 변경 처리에 대한 것입니다. 8 | * 속성의 값을 직접 변경하는 대신 새 속성을 만들고 적용된 변경 사항으로 값을 복사합니다. 9 | 10 | * 대부분의 기본 유형은 동일한 방식으로 작동하고 그것이 불변성이라고 부르는 것입니다. 11 | 12 | ## 불변성의 장점 13 | 14 | ### 스레드 안정성 15 | * 여러 스레드에서 클래스에 접근할 때 오브젝트 잠금 및 해제와 동기화 같은 것으로 보장해야 하지만, 여러 스레드에서 불변 데이터에 접근할 때는 이런 것이 필요하지 않습니다. 16 | 17 | ### 낮은 커플링 18 | * 스레드 간의 코드 의존성을 커플링이라고 하는데 복잡성을 피하고 코드 베이스를 읽기 쉽고 유지 보수하기 쉽도록 커플링을 최대한 낮게 유지해야 합니다. 19 | * 불변성을 도입한 곳에서는 커플링이 감소합니다. 한 스레드의 작업과 변경이 다른 스레드에 영향을 주지 않았기 때문입니다. 20 | 21 | ### 참조 투명성 22 | * 참조 투명성의 개념은 컨텍스트나 다른 분산과 관계없이 표현식이 언제나 같은 값을 평가한다는 것입니다. 23 | * 순수 함수의 도음이 있으면 불변은 참조 투명성을 확립할 수 있습니다. 참조 투명성은 가변 상태의 데이터를 강력하게 거부합니다. 24 | 25 | ### 실패 원자성 26 | * 전통적 프로그래밍에서 한 스레드의 실패는 다른 스레드에 쉽게 영향을 미칠 수 있습니다. 27 | * 불변성은 낮은 커플링을 강요하므로 애플리케이션의 내부 상태는 어떤 모듈/스레드에서 예외가 발생하더라도 일관성이 있습니다. 28 | * 불변 오브젝트는 절대로 상태를 변경하지 않습니다. 29 | * 따라서 한파트/모듈/스레드에서 에러가 발생하더라도 거기서 바로 멈추며, 애플리케이션의 다른 부분으로 확산될 가능성을 갖지 않습니다. 30 | 31 | ### 캐싱 32 | * 불변 오브젝트는 변경되지 않으므로 성능 향상을 위해 쉽게 캐싱할 수 있습니다. 33 | * 동일한 함수/변수를 여러번 호출하는 것을 쉽게 피할 수 있으며, 대신에 로컬로 캐싱하고 충분한 처리 시간을 절약할 수 있습니다. 34 | 35 | ### 컴파일러 최적화 36 | * 불변성과 참조 투명성은 컴파일러가 광범위한 최적화를 할 수 있게 도우므로 코드에서 수동 최적화의 필요성을 대체하고 프로그래머를 이 교환으로부터 자유롭게 합니다. 37 | 38 | ### 순수 함수 39 | * 순수 함수는 불변성을 사용해 얻을 수 있는 가장 큰 것입니다. 40 | * 불변성 없는 순수함수를 구현할 수 없으며, 순수함수 없이 불변성을 완성할 수 없습니다. 41 | 42 | ## 코틀린에서 불변성의 구현방법 43 | * 코틀린은 불변 변수를 갖지만 상태의 진정한 깊은 불변성을 보장하는 언어 메커니즘은 없습니다. 44 | * val 변수가 가변 오브젝트를 참조한다면 여전히 내용을 수정할 수 있습니다. 45 | * 우선 var, val, const val의 차이점을 살펴봅니다. 46 | 47 | ## val과 var 48 | * var는 다른 명령형 언어에 있는 것과 같이 그저 단순한 변수입니다. 49 | * val는 불변성에 좀 더 가깝습니다. 50 | * 커스텀 게터 없이 val 변수를 사용하면 참조 불변성을 얻을 수 있습니다. 51 | * 그래서 val로는 불변성을 보장할 수 없습니다. 52 | 53 | ## val와 const val: 정말 변하지 않을까? 54 | * val와 const val의 차이점 55 | * val 속성은 읽기 전용 변수이지만 const val은 컴파일 타임상수입니다. 56 | * 함수의 출력을 const val에 대입할 수 없습니다. 57 | * val 속성은 커스텀 게터를 가질 수 있지만, const val는 불가능합니다. 58 | * const val는 클래스/오브젝트의 최상위 멤버여야만 합니다. 59 | * const val 속성을 위한 델리게이트를 작성할 수 없습니다. 60 | * 모든 타입에 대한 val 속성을 가질 수 있지만 const val는 기본 데이터 타입과 문자열만 될 수 있습니다. 61 | * const val 속성의 null 가능한 데이터 타입을 가질 수 없습니다. 그 결과 const val 속성의 null 값을 가질 수도 없습니다. 62 | 63 | * 결과적으로 const val 속성은 값의 불변성을 보장하지만 유연성은 떨어집니다. 64 | * 그리고 const val로 오직 기본 데이터 타입만 사용해야 하므로 항상 목적을 달성할 수는 없습니다. 65 | 66 | ## 불변성의 종류 67 | ### 참조 불변 68 | * 참조 불변은 일단 참조가 할당되면 다른 것에 할당될 수 없게 합니다. 69 | ### 불변값 70 | * 불변 값은 값을 변경하지 않게 합니다. 71 | * 따라서 유저 관리가 정말로 복잡합니다. 72 | * 코틀린에서 const val 속성은 값의 불변성을 강요하지만 융통성이 부족해 기본 타입만 사용해야 합니다. 73 | 74 | ## 불변성의 단점 75 | * 불변성에 대해 들을 수 있는 것은 그 것을 수정할 때마다 새로운 오부젝트를 생성해야 한다는 점입니다. 76 | * 일부 시나리오에서는, 특히 많은 오브젝트 세트로 작업하는 곳에는 맞는 말입니다. 77 | * 그러나 작은 데이터셋이나 오브젝트로 작업할 때는 영향을 주지 않습니다. -------------------------------------------------------------------------------- /Functional Kotlin/Functional Programing.md: -------------------------------------------------------------------------------- 1 | # Functional Programing 2 | 3 | * 함수형 프로그래밍은 하나의 패러다임입니다. 4 | * 본질적으로 표현식으로 데이터를 변환하는 것입니다. 5 | 6 | * 함수형 프로그래밍 스타일은 다음과 같은 이점을 제공합니다. 7 | * 코드는 읽기 쉽고 테스트하기 쉽습니다. 외부 가변 상태에 의존하지 않는 함수는 더 쉽게 접근할 수 있고, 더 쉽게 증명할 수 있습니다. 8 | * 상태와 부수 효과가 주의 깊게 계획됩니다. 상태 관리를 코드에 개별적으로 특정 위치로 제한하는 것은 유지와 리팩토링을 쉽게 만듭니다. 9 | * 동시성이 좀 더 안전해지며 더 자연스러워집니다. 가변 상태가 없다는 것은 동시성 코드가 코드 주변에서 잠금이 적거나 필요 없다는 것을 뜻합니다. 10 | 11 | ## 기본 개념 12 | ### 일급 함수 및 고차함수 13 | * 함수형 프로그래밍의 가장 기본적인 컴셉은 일급함수입니다. 14 | * 일급함수를 지원하는 프로그래밍 언어는 함수를 다른 타입으로 취급합니다. 15 | * 함수를 변수, 파라미터, 반환, 일반화 ㅏ입 등으로 사용할 수 있게 합니다. 16 | * 파라미터와 반환에 대해 말하자면 다른 함수를 사용하거나 반환하는 함수가 고차함수입니다. 17 | 18 | * 한번에 이해해봅시다 19 | ~~~kotlin 20 | val capitalize = { str: String -> str.capitalize() } 21 | 22 | fun main(args: Array) { 23 | println(capitalize("헬로 월드!")) 24 | } 25 | ~~~ 26 | * 이해 되죠? 27 | * capitalize 람다 함수는 타입이 (String) -> String입니다. 28 | * 즉, capitalize는 String을 받아 다른 String을 반환합니다. 29 | * 이제 일반화를 적용해볼까요? 30 | 31 | ~~~kotlin 32 | fun transform(str:String, fn: (String) -> String): String { 33 | return fn(str) 34 | } 35 | ~~~ 36 | * transform(String, (String) -> String) 함수는 하나의 String을 받아 람다 함수를 적용합니다. 37 | * 결과적으로 변환을 일반화할 수 있습니다. 38 | 39 | ~~~kotlin 40 | fun transform(t: T, fn: (T) -> T): T { 41 | return fn(t) 42 | } 43 | ~~~ 44 | * 함수 호출을 해볼까요? 45 | 46 | ~~~kotlin 47 | fun main(args: Array) { 48 | println(transform("kotlin", capitalize)) 49 | } 50 | ~~~ 51 | * 좀 더 다양한 방법은? 52 | * 일단 다음과 같은 함수가 있습니다. 53 | 54 | ~~~kotlin 55 | fun reverse(str: String): String { 56 | return str.reversed() 57 | } 58 | 59 | fun main(args: Array) { 60 | println(transform("kotlin", ::reverse)) 61 | } 62 | ~~~ 63 | * reverse는 함수입니다. 다음과 같이 더블 콜론을 사용해 참조를 전달할 수 있습니다. 64 | * 또한 인스턴스나 컴패니언 오브젝트 메소드에 참조를 전달할 수도 있습니다. 65 | * 그러나 가장 일반적인 경우는 람다를 직접 전달하는 것입니다. 66 | 67 | * 그리고 함수가 마지막 파라미터로 람다를 받으면 람다는 괄호밖으로 전달될 수 있습니다. 68 | ~~~kotlin 69 | fun main(args: Array) { 70 | println(transform(("kotlin") {str -> str.substring(0..1)}) 71 | } 72 | ~~~ 73 | 74 | * 이 기능은 코틀린으로 도메인 특화 언어(DSL)를 생성할 수 있는 가능성을 열어줍니다. 75 | * 한번 체험해볼까요? if의 반대인 unless를 만들어봅시다. 76 | ~~~kotlin 77 | fun unless(condition: Boolean, block:() -> Unit) { 78 | if(!condition) block() 79 | } 80 | 81 | fun main(args: Array) { 82 | val securitryCheck = false 83 | unless(securityCheck) { 84 | println("이 웹 사이트에 접근할 수 없습니다.") 85 | } 86 | } 87 | ~~~ 88 | 89 | * 이제 타입 앨리어스는 함수와 혼합해 간단한 인터페이스를 대체하는 데 사용할 수 있습니다. 90 | ~~~kotlin 91 | interface Machine { 92 | fun process(product: T) 93 | } 94 | 95 | fun useMachine(t: T, machine: Machine) { 96 | machine.process(t) 97 | } 98 | 99 | class PrintMachine: Machine { 100 | override fun process(t: T) { 101 | println(t) 102 | } 103 | } 104 | 105 | fun main(args: Array) { 106 | useMachine(5, PrintMachine()) 107 | sueMachine(5, object: Machine { 108 | override fun process(t: Int) { 109 | println(t) 110 | } 111 | }) 112 | } 113 | ~~~ 114 | 115 | * 이것은 타입 앨리어스로 태체될 수 있으며, 모든 함수의 구문적 특징과 함께 사용될 수 있습니다. 116 | ~~~kotlin 117 | typealias Machine = (T) -> Unit 118 | 119 | fun useMachine(t: T, machine: Machine) { 120 | machine(t) 121 | } 122 | 123 | class PrintMachine: Machine { 124 | override fun invoke(p1: T) { 125 | println(p1) 126 | } 127 | } 128 | 129 | fun main(args: Array) { 130 | useMachine(5, PrintMachine()) 131 | 132 | useMachine(5, ::println) 133 | useMachine(5) { i -> 134 | println(i) 135 | } 136 | } 137 | ~~~ 138 | 139 | ### 순수 함수 140 | * 순수 함수에는 부수 효과나 메모리, I/O가 없습니다. 141 | * 순수 함수는 참조 투명도, 캐싱, 기타 다양한 속성을 가집니다. 142 | * 코틀린에서 순수 함수를 작성하는 것은 가능하지만, 순수 함수형 프로그래밍을 실행하지 않습니다. 143 | * 원한다면 순수한 함수형 스타일로 작성할 수 있는 기능을 포함한 대단한 유연성을 제공합니다. 144 | 145 | ### 재귀 함수 146 | * 재귀함수는 실행을 멈추는 조건과 함께 스스로를 호출하는 함수입니다. 147 | * 코틀린에서 재귀 함수는 스택을 유지하지만 tailrec 수정자를 통해 최적화할 수 있습니다. 148 | 149 | ~~~kotlin 150 | fun tailrecFib(n: Long): Long { 151 | tailrec fun go(n: Long, prev: Long, cur: Long): Long { 152 | return if(n == 0L) { 153 | prev 154 | } else { 155 | go(n - 1, cur, prev + cur) 156 | } 157 | } 158 | 159 | return go(n, 0, 1) 160 | } 161 | ~~~ -------------------------------------------------------------------------------- /Java Vs Kotlin/제네릭/제네릭.md: -------------------------------------------------------------------------------- 1 | # 제네릭 2 | ## 제네릭 3 | * 제네릭(generics) 혹은 제네릭 타입(generic type)은 인자로 사용되는 값에 따라 구체화되는 클래스나 인터페이스를 의미합니다. 4 | * Kotlin에서의 사용법은 Java에서와 크게 다르지 않습니다. 하지만 약간 다른부분도 존재합니다. 5 | ## 제네릭 클래스의 인스턴스 생성 및 사용 6 | * Kotlin에서 제네릭 클래스는 Java와 동일하게 <> 안에 타입을 넣어 표현합니다. 7 | ~~~kotlin 8 | val names: List 9 | val entries: Map 10 | ~~~ 11 | * Kotlin은 반드시 제네릭 클래스에 타입을 넣어 주어야합니다. 12 | ~~~Kotlin 13 | // 컴파일 오류 14 | val names: List 15 | ~~~ 16 | ## 제네릭 클래스/인터페이스 정의 17 | * 제네릭을 사용하는 클래스나 인터페이스르 정의하는 방법도 Java와 동일합니다. 18 | ~~~Kotlin 19 | class Car { 20 | 21 | } 22 | 23 | // 항목을 담거나 뺄 수 있는 제네릭 인터페이스 Container 정의 24 | interface Container { 25 | 26 | fun put(item: T) 27 | 28 | fun take() : T 29 | } 30 | 31 | // 자동차(Car)를 담거나 뺄 수 있는 32 | // 클래스 Garage 정의 33 | class Garage : Container { 34 | override fun put(item: Car) { 35 | 36 | } 37 | 38 | override fun take(): Car { 39 | 40 | } 41 | } 42 | ~~~ 43 | * 제네릭 클래스나 인터페이스가 인자로 받을 수 있는 타입을 한정하는 방법 또한 동일합니다. 44 | ~~~kotlin 45 | interface Container { 46 | fun put(item: T) 47 | 48 | fun take() : T 49 | } 50 | ~~~ 51 | ## 제네릭을 인자로 받는 함수 52 | * 타입이 정의되어 있는 제네릭을 인자로 받거나 호출 시점에 타입을 지정하는 함수는 Java와 동일한 방법으로 정의합니다. 53 | * 단, 호출 시점에 타입을 정의하는 함수는 타입 정의 위치가 Java와 약간 다릅니다. 54 | ```kotlin 55 | // 타입이 정의되어 있는 제네릭을 인자로 받는 예 56 | fun processItems ( 57 | items: List) { 58 | 59 | } 60 | // 호출 시점에 타입이 정해지는 제네릭을 인자로 받는 예 61 | fun processItems (items: List) { 62 | 63 | } 64 | ``` 65 | * 호출 시점에 타입이 정해지는 제네릭을 인자로 받는 경우, 정해지는 타입 및 그 하위 타입을 받도록 지정하거나(upper bound) 정해지는 타입 및 그 상위 타입을 받도록 (lower bound) 지정할 수 있습니다. 66 | * Kotlin도 Java와 동일한 기능을 지원하며, Java의 ? super T, ? extends T는 Kotlin에서 각각 in T, out T로 사용됩니다. 67 | ~~~kotlin 68 | // 자동차 클래스 69 | open class Car { } 70 | 71 | // 일반 승용차 클래스 72 | class Sedan : Car() { } 73 | 74 | // 트럭 클래스 75 | class Truck : Car() { } 76 | 77 | // src로 받은 콕록을 dest에 추가합니다. 78 | fun append(dest: MutableList, src: List) { 79 | dest.addAll(src) 80 | } 81 | 82 | // 사용 예 83 | // 일반 승용차 리스 생성 84 | val sedans: List = 85 | 86 | // 트럭 리스트 생성 87 | val trucks: List = 88 | 89 | // 자동차를 담을 수 있는 리스트 생성 90 | val cars: MutableList = 91 | 92 | // 자동차를 담는 리스트에 일반 승용차 리스트 추가 93 | append(cars, sedans) 94 | 95 | // 자동차를 담는 리스트에 트럭 리스트 추가 96 | append(cars, trucks) 97 | ~~~ 98 | -------------------------------------------------------------------------------- /Java Vs Kotlin/기본 자료형/기본 자료형.md: -------------------------------------------------------------------------------- 1 | # 기본 자료형 2 | ## 기본자료형 3 | * Java의 자료형은 원시 자료형(int, double 등)과 참조 타입(String 등)으로 나뉩니다. 4 | * 하지만 Kotlin에서는 모든 타입을 객체로 표현하기 때문에 원시 타입와 래퍼 클래스를 구분하지 않습니다. 5 | * 원시 타입 및 래퍼에 해당하는 자료형 외에도, 일부 자료형은 Kotlin만의 자료형으로 처리됩니다. 6 | 예) java.lang.Annotation -> kotlin.Annotation! 7 | 8 | ### 숫자 9 | * 숫자를 표현하는 모든 자료형은 Number 클래스를 상속합니다. 10 | * Kotlin도 다른 자료형으로 바꿔주는 함수를 제공합니다. 11 | 추가적으로 숫자를 문자로 바꿔주는 toChar() 함수도 제공합니다. 12 | * 숫자, 타입, 진법을 함께 표현하기 위한 리터럴(Literal) 표기법은 Java와 동일하지만 Long타입은 대문자입니다. 13 | * 리터럴 표기법 예시 14 | ```kotlin 15 | // 10진수 표기 16 | val decValue: Int = 100 17 | // 16진수 표기 18 | val hexValue: Int = 0x100 19 | // 2진수 표기 20 | val binaryValue: Int = 0b100 21 | 22 | // Long에 한해 대문자만 사용 23 | Val longValue: Long = 100L 24 | 25 | val doubleValue: Double = 100.1 26 | 27 | val floatValue = 100.0f 28 | // 100.f 형태 지원 x 29 | ``` 30 | * 사칙 연산자는 Java와 동일하지만 비트 연산자는 좀 더 직관적입니다. 31 | & == and, | == or, << == shl 등 32 | 33 | ### 문자 34 | * Kotlin에서 문자 자료형에는 문자만 입력이 가능합니다. 35 | * 만약 숫자를 넣을 경우 컴파일 에러가 발생합니다. 36 | * 숫자를 넣고 싶은 경우 toChar()를 이용하면 아스키 코드 해석에 따라 문자로 입력됩니다. 37 | ```kotlin 38 | val code : Int = 65 39 | val ch : Char = code.toChar() 40 | ``` 41 | 42 | ### 논리 43 | * Java에서의 boolean이 Kotlin에서는 Boolean이고 사용법은 동일합니다. 44 | ```kotlin 45 | val foo : Boolean = true 46 | ``` 47 | 48 | ### 문자열 49 | * Java에서의 문자열과 Kotlin에서의 문자열은 매우 비슷합니다. 50 | ```kotlin 51 | val foo : String = "Lorem ipsum" 52 | ``` 53 | * Kotlin에서는 문자열 내의 특정 위치의 문자에 접근하기 위해서 get() 메서드 나 []와 인덱스를 사용합니다. 54 | ```kotlin 55 | val foo : String = "Lorem ipsum" 56 | 57 | val ch1 : Char = foo.get(4); 58 | val ch2 : Char = foo[6]; 59 | ``` 60 | * String.format() 함수를 이용하여 규격화된 문자열을 사용할 수도 있습니다. 61 | ```kotlin 62 | val length : Int = 3000 63 | 64 | // "Length: 3000 meters" 값 할당 65 | val lengthText : String.format("Length: %d meters", length) 66 | ``` 67 | * String.format() 말고 Kotlin의 문자열 템플릿 기능을 사용하면 문자열 내에 직접 인자를 대입합니다. 68 | ```kotlin 69 | // "Length: 3000 meters" 값 할당 70 | val lengText : String = "Length: $length meters" 71 | ``` 72 | * 템플릿 문자열의 포함할 인자는 $로 구분합니다. 만약 인자로 넣고 싶은 것이 값, 변수가 아닌 표현식이라면 표현식 부분을 중괄호로 구분하면 됩니다. 73 | * 템플릿에 인자로 들어온 문자열의 길이를 표시하는 문자열을 할당하는 예입니다. 74 | ```kotlin 75 | val text : String = "Lorem ipsum 76 | // "TextLength: 4" 할당 77 | val lengthText : String = "TextLength: ${text.length}" 78 | ``` 79 | * 문자열 내의 $를 포함해야하는 경우 80 | ```kotlin 81 | val price : Int = 1000 82 | // "price: $1000" 할당 83 | val priceText : String = "Price: ${'$'}$price" 84 | ``` 85 | ### 배열 86 | * Kotlin에서의 배열은 타입 인자를 갖는 Array 클래스로 표현합니다. 87 | ```kotlin 88 | val words : Array = arrayOf ("Lorem", "ipsum", "dolor", "sit") 89 | ``` 90 | * arrayOf 함수는 입력받은 인자로 구성된 배열을 생성합니다. 91 | * 원시 타입은 Array 클래스의 인자값으로 쓸 수 없습니다. 하지만 IntArray, ByteArray 등을 제공합니다. 92 | ```kotlin 93 | val intArr : IntArray = intArrayOf(1, 2, 3, 4, 5) 94 | ``` 95 | * 원시 타입이 아닌 래퍼 타입 배열은 그대로 사용가능합니다. 96 | ```kotlin 97 | val intArr : Array = arrayOf(1, 2, 3, 4, 5) 98 | ``` 99 | * 함수의 가변인자에 배열을 전달하는 경우에만 스프레드 연산자(*)를 사용합니다. (인자를 받을 때는 사용 X) 100 | ```kotlin 101 | fun foo(arr: Array) { 102 | 103 | } 104 | 105 | fun bar(vararg args: String) { 106 | 107 | } 108 | // foo() 함수 호출 109 | val intArr: Array = arrayOf(1, 2, 3, 4, 5) 110 | foo(intArr) // 배열을 바로 인자로 대입 111 | 112 | // bar() 함수 호출 113 | val stringArr: Array = arrayOf("Lorem", "ipsum", "dolor", "sit) 114 | bar(*stringArr) // 스프레드 연산자 사용 115 | ``` -------------------------------------------------------------------------------- /Java Vs Kotlin/널 안정성/널 안정성.md: -------------------------------------------------------------------------------- 1 | # 널 안정성 2 | ## 널 안정성 3 | * Java에서 프로그래밍을 하다보면 가장 빈번하게 발생하는 것으로 널포인터 에외(null pointer exception)을 꼽을 수 있습니다. 4 | * Java로 개발할 때는 @Nullable, @NonNull 어노테이션(안드로이드 서포틀 라이브러리)을 사용하여 객체의 널 허용 여부를 표시했습니다. 5 | * 하지만 어노테이션을 통한 널 허용 여부 확인은 IDE같은 정적 분석 도구에서만 지원하므로 컴파일 단계에서는 여전히 널 포인터 예외가 발생할 소지가 있었습니다. 6 | * Kotlin은 이러한 문제를 해결하기 위해 모든 타입에 명시적으로 널 허용 여부를 함께 표기합니다. 7 | ## 널 허용 여부 표기 8 | * Java에서는 변수의 널 허용 여부를 이렇게 표기합니다. 9 | ~~~java 10 | @Nullable 11 | String nullableString; 12 | 13 | @NonNull 14 | String nonNullString; 15 | ~~~ 16 | * 위처럼 변수를 초기화하지 않아도 컴파일 단계에서 오류가 발생하지 않아 무결성을 보장하기 어렵습니다. 17 | * 하지만 Kotlin은 별도의 표기가 없을 경우 null을 허용하지 않습니다. null값을 가질 수 있도록 하려면 명시적으로 타입 뒤에 ?를 붙여주어야 합니다. 18 | ~~~kotlin 19 | val nullableString : String? = null 20 | val nonNullString : String = "Foo" 21 | ~~~ 22 | * Kotlin은 null을 허용하지 않는 값을 초기하지 않거나, null을 대입하면 컴파일 오류를 발생시킵니다. 23 | ~~~kotlin 24 | val name : String // 오류: 값이 초기화되지 않음 25 | val address : String = null // 오류: null을 허용하지 않는 값에 null 대입 불가 26 | ~~~ 27 | * 이런 규칙은 함수의 파라미터나 반환 값에도 동일하게 적용됩니다. 28 | ~~~kotlin 29 | // 인자 line2에는 null 사용 가능 30 | fun formatAddress 31 | (line1: String, line2: String?, city: String) : String { 32 | 33 | } 34 | 35 | // 입력한 주소에 해당하는 우편번호를 반환하지만, 검색 결과가 없을 경우 null을 반환 36 | fun findPostalCode(address : String) : PostalCode? { } 37 | ~~~ 38 | * 변수와 마찬가지로 함수의 파라미터나 반환 값에 올바르지 않은 타읩을 사용하면 컴파일 오류가 발생합니다. 39 | ~~~kotlin 40 | // 오류: 인자 line1은 null 값을 이용하지 않음 41 | formatAddress(null, nullm "San Francisco") 42 | 43 | // 오류: 값 postalCode는 null 값을 허용하지 않으나 findPostalCode 함수는 null 값을 반환 가능 44 | val postal : PostalCode = findPostalCode("1600 Amphitheatre Pkwy") 45 | ~~~ 46 | ## 널 값을 대신하는 방법: 엘비스(?:) 연산자 47 | * 널 값을 허용하지 않는 값 혹은 변수에 null 값을 반환할 수 있는 함수의 결과를 대입해야 하는 경우, 이 처리를 별도로 해야합니다. 48 | * 엘비스 연산자를 사용하면 편리하게 처리할 수 있습니다. 49 | ~~~kotlin 50 | // foo가 null이 아닐 경우에는 foo를, null이라면 bar를 반환 51 | foo ?: bar 52 | ~~~ 53 | * 함수가 null 값을 반환할 때 대신 사용할 값을 지정할 수 있습니다. 54 | ~~~kotlin 55 | // 함수가 null 값을 반환하는 경우 PostalCode.NONE 값을 대입합니다. 56 | val postal : PostalCode 57 | = findPostalCode("1600 Amphitheatre Pkwy") ?: PostalCode.NONE 58 | ~~~ 59 | * 만약 입력한 주소의 우편번호를 기반으로 지도 이미지를 생성하고, 우편번호 검색 결과가 없을 경우 null 값을 반환하는 함수를 작성한다고 가정합니다. 60 | * Java는 null여부를 확인하는 작업을 추가해야 하지만 Kotlin은 엘비스 연산자를 사용하여 이를 간단히 처리할 수 있습니다. 61 | ~~~kotlin 62 | fun generateMapImage(address: String) : Image? { 63 | // 우편번호 검색 결과가 없을 경우 바로 64 | // 함수 실행을 종료하고 결과로 null 반환 65 | val postal = findPostalCode(address) ?: 66 | return null 67 | 68 | // 지도 이미지 생성 69 | 70 | } 71 | ~~~ 72 | * 값을 반환하는 대신 에외가 발생하도록 할 수도 있습니다. 73 | ~~~kotlin 74 | fun generateMapWithAddress(address: String) : Image? { 75 | // 우편번호 검색 결과가 없을 경우 IllegalStateException 발생 76 | val postal = findPostalCode(address) ?: throw IllegalStateException() 77 | 78 | // 지도 이미지 생성 79 | 80 | } 81 | ~~~ 82 | ## 널 값 확인과 처리를 한번에: 안전한 호출(?.) 연산자 83 | * Java에서는 null 값 여부를 확인하기 위해 주로 if문을 사용합니다. 구조가 간단한 경우 이 것만으로 충분하지만, 복잡한 단계로 구성된 자료를 다룬다면 효츌이 크게 떨어집니다. 84 | ~~~java 85 | class Contact{ 86 | @NonNull 87 | String name; 88 | 89 | @Nullable 90 | Address address; 91 | } 92 | 93 | class Address { 94 | @NonNull 95 | String line1; 96 | 97 | @Nullable 98 | String line2; 99 | } 100 | ~~~ 101 | * Java는 주소록 내에 포함된 주소의 두 번째 주소에 안전하게 접근하려면 if문 내에서 contact.address의 null 여부를 확인하고 address.line2의 null 여부를 확인해야 합니다. 102 | ~~~java 103 | Contact contact = ; // 주소록 항복 객체 104 | String line; 105 | 106 | if(contact.address != null && contact.address.line2 != null) { 107 | line = contact.address.line2; 108 | } else { 109 | line = "No address" 110 | } 111 | ~~~ 112 | * Kotlin에서는 안전한 호출(safe call) 연산자를 사용하여 null 값 확인과 값 접근/함수 호출을 한번에 할 수 있습니다. 113 | ~~~kotlin 114 | // bar가 null이 아닐 경우에만 해당 값을 대입, 그렇지 않은 경우 null을 foo에 대입 115 | val foo = bar?.baz 116 | 117 | // foo가 null이 아닐 경우에만 bar() 호출 118 | foo?.bar() 119 | ~~~ 120 | * 안전한 호출 연산자는 이 연산자를 사용하는 객체가 null 값이 아닌 경우에 연산자 뒤의 문장을 수행합니다. null일 경우에는 뒤의 문장을 수행하지 않고 null을 반환합니다. 121 | * 따라서 널 값인 객체의 프로퍼티를 참조하거나 함수를 호출하는 일을 방지할 수 있습니다. 122 | ~~~ kotlin 123 | // Address.line1은 null 값을 허용하지 않지만, 124 | // address가 null인 경우 null을 반환하므로 값 line의 타입은 null 값을 허용해야 합니다. 125 | val line : String? = contact.address?.line1 126 | ~~~ 127 | * 엘비스 연산자를 함께 사 용하면 null 값을 반환할 때, 대신 사용할 값을 지정할 수 있습니다. 128 | ~~~ kotlin 129 | val contact : Contact = // 주소록 항목 객체 130 | 131 | // 주소가 없거나 line2가 없을 경우 기본값인 "No address" 반환 132 | val line : String = contact.address?.line2 ?: "No address" 133 | ~~~ 134 | ## 안전한 자료형 반환: as? 연산자 135 | * 지원되지 않는 자료형으로 반환을 시도하는 경우 예외가 발생합니다. 136 | ~~~ kotlin 137 | val foo : String = "foo" 138 | 139 | // java.lang.ClassCastException 발생: String은 Int 자료형으로 변환할 수 없습니다. 140 | val bar : Int = foo as Int 141 | ~~~ 142 | * Java에서는 지원되지 ㅇ낳는 자료형으로 변환을 시도할 가능성이 있는 부분을 try-catch 블록으로 감싸는 방법으로 처리해야 하지만, Kotlin은 as?연산자를 사용하여 이 문제를 간단하게 해결할 수 있습니다. 143 | * 안전한 변환 연산자는 자료형 변환이 실패할 경우 예외를 발생시키는 대신 null 값을 반환합니다. 따라서 반환되는 값을 통해 반환 결과를 바로 확인할 수 있습니다. 144 | ~~~kotlin 145 | val foo : String = "foo" 146 | 147 | // bar가 null 값을 허용하도록 Int?로 정의합니다. 148 | // 자료형 변환에 실패하므로 bar에는 null 값이 할당됩니다. 149 | val bar : Int? = foo as? Int 150 | ~~~ 151 | * 안전한 변환 연산자가 변환에 실패했을 때 null 값을 반환하므로, 엘비스 연산자를 함께 사용하면 변환에 실패했을 떄 기본값을 지정할 수 있습니다. 152 | * 변환된 값을 받는 자료형의 null 허용 여부를 수정할 필요가 없으므로 더욱 유연하게 대처할 수 있습니다. 153 | ~~~ kotlin 154 | val foo: String = "foo" 155 | 156 | // 자료형 변환에 실패할 경우 기본값을 0으로 지정합니다. 157 | val bar : Int = foo as? Int ?: 0 158 | ~~~ 159 | 160 | ## 널 값이 아님을 명시하기: 비 널 값 보증(!!) 161 | * 상황에 따라 null 값을 포함할 수 있는 타입에 null 값이 아닌 값만 포함되는 경우가 생길 수 있습니다. 162 | * 비 null 값 보증(non-null assertions)을 사용하면 null 값을 포함할 수 있는 타입을 null 값을 포함하지 않는 타입으로 변환하여 사용할 수 있습니다. 보증하려는 항목 뒤에 !!을 붙여 사용합니다. 163 | ~~~kotlin 164 | // 값 foo는 널 값을 포함할 수 있는 Foo 타입 165 | val foo : Foo? = ... 166 | 167 | // 값 foo는 널 값을 포함하지 않음을 보증 168 | val nonNullFoo : Foo = foo!! 169 | 170 | // 값 foo가 널 값이 아님을 보장하면서 bar() 함수 호출 171 | foo!!.bar() 172 | 173 | // 값 foo가 널 값이 아님을 보장하면서 baz 포로퍼티 접근 174 | val myBaz = foo!!.baz 175 | ~~~ 176 | * 사용 예시입니다. 177 | ~~~kotlin 178 | // data2 프로퍼티는 널 값을 포함할 수 있습니다. 179 | class Record(val data1: String, val data2: String?) 180 | 181 | class Person(record: Record) { 182 | val name : String 183 | 184 | val address : String 185 | 186 | init { 187 | name = record.data1 188 | // Person 클래스를 생성할 때 인자로 받은 Record 객체 내 data2 프로퍼티는 널 값을 포함하지 않음을 보증합니다. 189 | address = record.data2!! 190 | } 191 | } 192 | ~~~ 193 | * 비 null 값 보증을 사용하였으나 실제 객체에 null 값이 들어가 있을 경우, null 포인터 예외가 발생하므로 유의하여 사용해야합니다. 194 | * 비 null 값 보증은 다음과 같이 중첩하여 사용하는 것을 권장하지 않습니다. 둘 중 하나라도 null 값이라면 null 포인터 예외가 발생합니다. 195 | ~~~kotlin 196 | val contact : Contact = ...// 주소록 항목 객체 197 | 198 | // Address와 line2 모두 null 값이 아님을 보장 199 | val line : String = contact.adress!!.line!! 200 | ~~~ 201 | * 어디서 오류가 났는지 알 수가 없기 때문에 비 null 값 보증은 중첩 호출 단계보다는 하나의 호출 단계에만 사용하는 것을 권장합니다. 202 | ## 나중에 초기화되는 변수를 위해: lateinit 키워드 203 | * 클래스의 프로퍼티는 클래스를 생성할 때 생성자와 함께 값을 할당하는 경우도 많지만, 의존성 주입(dependency injection)을 사용하거나 설계상 이유로 클래스를 생성한 후 나중에 따로 초기화를 수행하는 경우도 있습니다. 204 | * Kotlin은 null 값을 허용하지 않는 경우 초기화를 해주거나 생성자를 통해 값을 초기화하도록 강제하고 있지만, lateinit 키워드를 사용하면 초기화 없이 변수만 선언할 수 있습니다. 205 | ~~~ kotlin 206 | class MyActivity: Activity() { 207 | // 나중에 초기화를 수행할 객체로 표시하였으므로 바로 초기화를 하지 않아도 됩니다. 208 | lateinit var api : Api 209 | 210 | } 211 | ~~~ 212 | * 비 null 값 보증과 마찬가지로 초기화를 하지 않은 상태로 사용하려 하면 null 포인터 예외가 발생하니 초기화 작업을 빠뜨리지 않도록 유의합니다. 213 | ## 자바로 작성된 클래스의 널 처리 214 | * Java로 작성된 클래스는 기본적으로 null 값이 허용되도록 처리되며, Kotlin에서는 이를 플랫폼 타입(platform types)이라 부릅니다. 215 | * 플랫폼 타입(platform types)이라 부릅니다. 플랫폼 타입은 Type!과 같은 형태로 표시됩니다. 216 | * 플랫폼 타입은 Kotlin에서 Java로 작성한 클래스를 사용할 때에 자동으로 지정되는 타입으로, 이 타입을 개발자가 직접 사용할 수 없습니다. 217 | ~~~kotlin 218 | val myPlatformTYpe: MyType! = ...// 오류: 플랫폼 타입을 선언할 수 없습니다. 219 | ~~~ 220 | * 플랫폼 타입은 Kotlin에서 값 및 변수의 타입을 지정할 때 널을 허용하는 타입과 그렇지 않은 타입에 자유롭게 할당할 수 있습니다. 221 | ~~~kotlin 222 | val person : Person = ... // Person 객체 생성 223 | // 값 n1은 널 값을 허용하지 않습니다. 224 | val n1 : String = person.name 225 | 226 | // 값 n2는 널 값을 허용합니다. 227 | val n2 : String? = person.name 228 | ~~~ 229 | * 이와 같은 특징 떄문에, 플랫폼 타입 객체를 사용항 때에는 항상 객체의 널 값 여부를 확인해야 합니다. 그렇지 ㅇ낳으면 실행 중 널 포인터 예외가 발생할 수 있습니다. 230 | * Kotlin은 이를 해결하기 위해 Java에서 널리 사용하는 몇몇 어노테이션을 인식하여 객체의 널 허용 여부를 판단합니다. Kotlin에서 인식 가능한 어노테이션의 종류는 다음과 같습니다. 231 | 232 | | 종류 | 패키지/클래스 | 233 | | -------- | --------------------| 234 | | JetBrains|org.jetbrains.annotations| 235 | | Android|com.android.annotations, android.support.annotations| 236 | | JSR-305|javax.annotation| 237 | | FindBugs|edu.umd.cs.findbugs.annotations| 238 | | Eclipse|org.eclipse.jdk.annotation| 239 | | Lombok|lombok.NonNull| 240 | ~~~java 241 | class Person { 242 | @Nullable 243 | String name; 244 | 245 | public String getName() { 246 | return name; 247 | } 248 | } 249 | ~~~ 250 | * 이와 같이 적용하면, Person 클래스의 name 필드는 Kotlin에서 null을 포함할 수 있는 프로퍼티(String?)로 인식됩니다. 251 | * 따라서 다음과 같이 null을 허용하는 타입에만 사용할 수 있습니다. 252 | ~~~kotlin 253 | val person : Person = ... // Person 객체 생성 254 | 255 | // 실패: 값 n1은 널 값을 허용하지 않습니다. 256 | val n1 : String = person.name 257 | 258 | // 성공: 값 n2는 널 값을 허용합니다. 259 | val n2 : String? = person.name 260 | ~~~ 261 | * 어노테이션을 사용하여 플랫폼 타입의 널 허용 여부를 명시한다고 해도 Kotlin으로 작성되지 않은 곳(JSON 파싱 결과 등)에서 플랫폼 타입의 객체를 생성하는 경우 컴파일과정에서 해당 필드에 대한 널 여부를 검증할 수 없습니다. 262 | * Kotlin으로 작성되지 않은 부분에 검증 코드를 별도로 추가해야 합니다. 263 | * 이런 불편이 존재하기 때문에 해당 부분을 가급적 Kotlin 코드로 변환하여 사용하는 것을 권장합니다. -------------------------------------------------------------------------------- /Java Vs Kotlin/예외/예외.md: -------------------------------------------------------------------------------- 1 | # 예외 2 | ## 예외 3 | * Kotlin에서 예외를 발생시키려면 throw 키워드를 사용해야하며, 객체 생성과 마찬가지로 new 키워드는 사용하지 않습니다. 4 | ~~~kotlin 5 | fun checkAge(age: Int) { 6 | if(age < 0) { 7 | throw IllegalArgumentException 8 | ("Invalid age: $age") 9 | } 10 | } 11 | ~~~ 12 | * 예외를 처리하기 위해서는 Java와 동일하게 try-catch 및 finally문을 사용하면 됩니다. Kotlin에서 try-catch문은 값을 반환할 수 있습니다. 13 | ~~~kotlin 14 | // try-catch 문에서 바로 값을 받습니다. 15 | val valid : Boolean = try { 16 | // 예외를 발생시킬 수 있는 코드들 17 | 18 | // 예외가 발생하지 않았을 경우 true를 반환 19 | true 20 | } catch (e: Exception) { 21 | // 예외가 발생했을 떄 수행할 동작 22 | 23 | // false 반환 24 | false 25 | } finally { 26 | // 예외 발생 여부와 상관없이 수행할 동작 27 | 28 | } 29 | ~~~ 30 | * Kotlin은 Checked exception을 따로 검사하지 않습니다. 대부분의 예외를 Java에서 처럼 try-catch문으로 감싸지 않고 선택적으로 사용할 수 있습니다. 31 | ~~~kotlin 32 | fun readFromJson(fileName: String): String { 33 | // IOException을 발생시킬 수 있는 코드 34 | 35 | } 36 | 37 | fun process() { 38 | // try-catch문을 사용하지 않아도 됩니다. 39 | val json: String = 40 | readFromJson("foo.json") 41 | } 42 | ~~~ -------------------------------------------------------------------------------- /Java Vs Kotlin/자료, 자료형의 확인 및 변환/자료,자료형의 확인 및 변환.md: -------------------------------------------------------------------------------- 1 | # 자료/자료형의 확인 및 변환 2 | ## 자료/자료형의 확인 및 변환 3 | * Kotlin에서 서로 다른 객체가 가진 자료를 비교하거나, 각 객체의 자료형을 비교하고 필요에 따라 다른 자료형으로 변환하는 방법 4 | ## 저료의 동일성 확인: ==, === 연산자 5 | * Java는 객체의 값만 비교할 것인가의 따라 equals() 와 ==으로 나누었지만 Kotlin은 모두 ==으로 통일합니다. 6 | ```kotlin 7 | val foo : Int = ... 8 | val bar : Int = ... 9 | 10 | val equals : Boolean = foo == bar 11 | ``` 12 | * 비교하는 과정에서 수행되는 동작은 다음 의사코드(pseudocode)로 표현할 수 있습니다. 13 | ```kotlin 14 | if(foo가 널 값이 아니라면) { 15 | foo.equals(bar) 결과 반환 16 | } else { 17 | bar == null 결과 반환 18 | } 19 | ``` 20 | * Kotlin의 == 연산자는 비교하는 값의 널 여부를 함께 확인합니다. 21 | * 객체 자체가 동일한지 여부에 대한 비교가 필요한 경우, Kotlin에서는 === 연산자를 사용하면 됩니다. 22 | ```kotlin 23 | val a : Pair = Pair('A', 65) 24 | val b = a 25 | val c : Pair = Pair('A', 65) 26 | 27 | // a와 b의 값이 동일하므로 true 28 | val aEqualsToB :Boolean = a == b 29 | 30 | // a와 c의 값이 동일하므로 true 31 | val aEqualsToC : Boolean = a == c 32 | 33 | // a와 b는 동일한 객체이므로 true 34 | val aIdenticalToB : Boolean = a === b 35 | 36 | // a와 c는 동일한 객체가 아니므로 false 37 | val aIdenticalToC : Boolean = a === c 38 | ``` 39 | 40 | ## 자료형 확인: is 연산자 41 | * Kotlin에서는 자료형을 확인하기 위해 is 연산자를 사용하며, 이는 Java의 instanceOf 연사자와 같은 역할을 합니다. 42 | ```kotlin 43 | fun printTypeName(obj: Any) { 44 | if (obj is Int) { 45 | Log.d("Type", "Type = Integer") 46 | } else if(obj is Float) { 47 | Log.d("Type", "Type = Float") 48 | } else if(obj is String) { 49 | Log.d("Type", "Type = String") 50 | } else { 51 | Log.d("Type", "Unknown type") 52 | } 53 | } 54 | ``` 55 | * 타입이 아닌 경우를 확인하려면 !is로 표현하면 됩니다. 56 | ```kotlin 57 | if (obj !is Int) { 58 | 59 | } 60 | ``` 61 | ## 자료형 변환: as 연산자 62 | * 특정 변수를 자신이 원하는 자료형으로 변환하기 위해 Kotlin은 괄호 대신 as 연산자를 사용합니다. 63 | ```kotlin 64 | fun processNumber(number: Number) { 65 | // 인자를 Int 자료형으로 캐스팅 66 | val foo : Int = number as Int 67 | } 68 | ``` 69 | ## 스마트 캐스트 70 | * Kotlin은 자료형 추론이 가능할 경우 캐스팅없이 해당하는 자료형으로 객체를 사용할 수 있도록 스마트 캐스트(Smart cast) 기능을 지원합니다. 71 | ```kotlin 72 | override fun onBindViewHolder( 73 | holder: RecyclerView.ViewHolder, 74 | postion: Int) { 75 | if(holder is PhotoHolder) { 76 | 77 | // 스마트 캐스트가 지원되어 캐스팅 없이 사용할 수 있습니다. 78 | holder.setImageUrl(mImageUrl) 79 | } else if(holder is TextHolder) { 80 | holder.setText(mTitles[position]) 81 | } 82 | } 83 | ``` 84 | * 스마트 캐스트는, 값을 검사하는 시점과 이용하는 시점 사이에 값이 변하지 않았다는 것이 보장되는 경우에만 지원됩니다. 85 | * 언제든지 값이 변할 수 있는 변수(var)는 스마트 캐스트가 지원 되지 않습니다. -------------------------------------------------------------------------------- /Java Vs Kotlin/제네릭/제네릭.md: -------------------------------------------------------------------------------- 1 | # 제네릭 2 | ## 제네릭 3 | * 제네릭(generics) 혹은 제네릭 타입(generic type)은 인자로 사용되는 값에 따라 구체화되는 클래스나 인터페이스를 의미합니다. 4 | * Kotlin에서의 사용법은 Java에서와 크게 다르지 않습니다. 하지만 약간 다른부분도 존재합니다. 5 | ## 제네릭 클래스의 인스턴스 생성 및 사용 6 | * Kotlin에서 제네릭 클래스는 Java와 동일하게 <> 안에 타입을 넣어 표현합니다. 7 | ~~~kotlin 8 | val names: List 9 | val entries: Map 10 | ~~~ 11 | * Kotlin은 반드시 제네릭 클래스에 타입을 넣어 주어야합니다. 12 | ~~~Kotlin 13 | // 컴파일 오류 14 | val names: List 15 | ~~~ 16 | ## 제네릭 클래스/인터페이스 정의 17 | * 제네릭을 사용하는 클래스나 인터페이스르 정의하는 방법도 Java와 동일합니다. 18 | ~~~Kotlin 19 | class Car { 20 | 21 | } 22 | 23 | // 항목을 담거나 뺄 수 있는 제네릭 인터페이스 Container 정의 24 | interface Container { 25 | 26 | fun put(item: T) 27 | 28 | fun take() : T 29 | } 30 | 31 | // 자동차(Car)를 담거나 뺄 수 있는 32 | // 클래스 Garage 정의 33 | class Garage : Container { 34 | override fun put(item: Car) { 35 | 36 | } 37 | 38 | override fun take(): Car { 39 | 40 | } 41 | } 42 | ~~~ 43 | * 제네릭 클래스나 인터페이스가 인자로 받을 수 있는 타입을 한정하는 방법 또한 동일합니다. 44 | ~~~kotlin 45 | interface Container { 46 | fun put(item: T) 47 | 48 | fun take() : T 49 | } 50 | ~~~ 51 | ## 제네릭을 인자로 받는 함수 52 | * 타입이 정의되어 있는 제네릭을 인자로 받거나 호출 시점에 타입을 지정하는 함수는 Java와 동일한 방법으로 정의합니다. 53 | * 단, 호출 시점에 타입을 정의하는 함수는 타입 정의 위치가 Java와 약간 다릅니다. 54 | ```kotlin 55 | // 타입이 정의되어 있는 제네릭을 인자로 받는 예 56 | fun processItems ( 57 | items: List) { 58 | 59 | } 60 | // 호출 시점에 타입이 정해지는 제네릭을 인자로 받는 예 61 | fun processItems (items: List) { 62 | 63 | } 64 | ``` 65 | * 호출 시점에 타입이 정해지는 제네릭을 인자로 받는 경우, 정해지는 타입 및 그 하위 타입을 받도록 지정하거나(upper bound) 정해지는 타입 및 그 상위 타입을 받도록 (lower bound) 지정할 수 있습니다. 66 | * Kotlin도 Java와 동일한 기능을 지원하며, Java의 ? super T, ? extends T는 Kotlin에서 각각 in T, out T로 사용됩니다. 67 | ~~~kotlin 68 | // 자동차 클래스 69 | open class Car { } 70 | 71 | // 일반 승용차 클래스 72 | class Sedan : Car() { } 73 | 74 | // 트럭 클래스 75 | class Truck : Car() { } 76 | 77 | // src로 받은 콕록을 dest에 추가합니다. 78 | fun append(dest: MutableList, src: List) { 79 | dest.addAll(src) 80 | } 81 | 82 | // 사용 예 83 | // 일반 승용차 리스 생성 84 | val sedans: List = 85 | 86 | // 트럭 리스트 생성 87 | val trucks: List = 88 | 89 | // 자동차를 담을 수 있는 리스트 생성 90 | val cars: MutableList = 91 | 92 | // 자동차를 담는 리스트에 일반 승용차 리스트 추가 93 | append(cars, sedans) 94 | 95 | // 자동차를 담는 리스트에 트럭 리스트 추가 96 | append(cars, trucks) 97 | ~~~ 98 | -------------------------------------------------------------------------------- /Java Vs Kotlin/컬렉션/컬렉션.md: -------------------------------------------------------------------------------- 1 | # 컬렉션 2 | ## 컬렉션 3 | * Kotlin에서 컬렉션은 Java에서 제공하는 클래스들을 그대로 사용합니다. 4 | * 단 타입 별칭(type alias)을 사용해 컬렉션 내 다른 클래스와의 일관성을 유지합니다. 5 | Kotlin 컬렉션 예 : kotlin.collections.HashMap 등 6 | * Kotlin에서는 컬렉션 내 자료의 수정 가능 여부에 따라 컬렉션의 종류를 구분합니다. 7 | 즉, 새로운 타입을 선언하는 것이 아닌 Interface를 통해 사용 가능한 함수를 제한 하는 방식으로 구현되어 있습니다. 8 | * Java의 List Interface는 Interable과 Collection Interface를 상속하고 있고 이 내부에는 자료를 조회하고 수정하는 메서드가 모두 담겨있습니다. 9 | * Kotlin의 컬렉션은 컬렉션 내 자료를 수정할 수 있는 가변 타입(mutable)과 수정이 불가한 불변 타입(immutable)으로 구분합니다. 10 | * Kotlin의 Collection, List Interface에는 자료 조회 함수만 포함되어 있어서 자료가 한번 할당되면 수정이 불가합니다. 11 | 대신, 각 인터페이스를 상속한 MutableCollection, MutableList 인터페이스에 자료를 수정하는 함수가 포함되어 있습니다. 12 | Set, Map도 똑같이 적용됩니다. 13 | ```kotlin 14 | // 자료 수정이 불가능한 리스트 반환 15 | fun immutable() : List { 16 | 17 | } 18 | 19 | // 자료 수정이 가능한 리스트 반환 20 | fun mutable() : MutableList { 21 | 22 | } 23 | ``` 24 | * 배열과 마찬가지로 Kotlin 표준 라이브러리에서 컬렉션을 쉽게 생성할 수 있는 함수를 제공합니다. 25 | 26 | | 함수명 | 자료 수정 가능 여부 | 반환 타입 | 27 | | -------- | --------------------| ----------------| 28 | | listOf() |X| kotlin.collections.List| 29 | | arrayListOf()|O| kotlin.collections.ArrayList(java.util.ArrayList)| 30 | | setOf() |X| kotlin.collections.Set| 31 | | hashSetOf() |O| kotlin.collections.HashSet(java.util.HashSet)| 32 | | linkedSetOf() |O| kotlin.collections.LinkedHashSet(java.util.LinkedHashSet| 33 | | sortedSet() |O| kotlin.collections.TreeSet(java.util.TreeSet)| 34 | | mapOf() |X| kotlin.collections.Map| 35 | | hashMapOf() |O| kotlin.collections.HashMap(java.util.HashMap)| 36 | | linkedMapOf() |O| kotlin.collections.LinkedHashMap(java.util.LinkedHashMap| 37 | | sortedMapOf() |O| kotlin.collections.SortedMap(java.util.SortedMap)| 38 | * 사용 예) 39 | ```kotlin 40 | // 자료를 수정할 수 없는 리스트 생성 41 | val immutableList : List = list("Lorem", "ipsum", "dolor", "sit") 42 | 43 | // 컴파일 에러: 자료 수정을 위한 함수를 지원하지 않음 44 | mutableList.add("amet") 45 | 46 | // 자료를 수정할 수 있는 리스트 생성 47 | val mutableList : MutableList = arrayListOf("Lorem", "ipsum", "dolor", "sit") 48 | 49 | // 자료 수정 가능 50 | mutableList.add("amet") 51 | 52 | // 자료를 수정하지 않는 자료형으로 재할당 53 | val immutableList2 : List = mutableList 54 | 55 | // 컴파일 에러: 자료 수정을 위한 함수를 지원하지 않음 56 | mutableList.add("amet") 57 | ``` 58 | 59 | * Kotlin에서 컬렉션의 특정 항목에 접근하는 방법은 배열에서의 방법과 동일합니다. 60 | ```kotlin 61 | val immutableList: LIst = listOf("Lorem", "ipsum", "dolor", "sit") 62 | 63 | // 첫 번째 항목 읽기 - get(0)과 동일 64 | val firstItem: String = immutableList 65 | 66 | // 컴파일 에러: 값 설정 - set(0)과 동일 67 | immutableList[0] = "Lollypop" 68 | 69 | val mutableList: MutableList = arrayListOf("Lorem", "ipsum", "dolor", "sit") 70 | 71 | // 자료 변경 가능 72 | mutableList[0] = "Lollypop" 73 | ``` 74 | * Map은 숫자 인덱스 대신 키값을 넣어 찾을 수 있습니다. 75 | ```kotlin 76 | val immutableMap: Map = mapOf(Pair("A", 65), Pair("B", 66)) 77 | 78 | // 키 "A"에 해당하는 값 - get("A")와 동일 79 | val code : Int = immutableMap["A"] 80 | 81 | // 컴파일 에러: 값 설정 - put("C", 67)과 동일 82 | immutableMap["C"] = 67 83 | 84 | val mutableMap : HashMap = hashMapOf(Pair("A", 65), Pair("B", 66)) 85 | 86 | // 자료 변경 가능 - "C" 키로 값 67 삽입 87 | mutableMap["C"] = 67 88 | ``` 89 | * Map을 생성하는 함수들은 키와 값을 인자로 받기 위해 Pair 클래스를 이용합니다. 90 | Kotlin에서 제공하는 to 함수를 이용하면 좀더 편리하게 사용할 수 있습니다. 91 | ```kotlin 92 | val map : Map = mapOf("A" to 65, "B" to 66) 93 | ``` -------------------------------------------------------------------------------- /Java Vs Kotlin/클래스 및 인터페이스/클래스 및 인터페이스.md: -------------------------------------------------------------------------------- 1 | # 클래스 및 인터페이스 2 | ## 클래스 및 인터페이스 3 | * Java와 유사하지만 새로운 개념이 추가되거나 약간 다른 방식이 있기도 합니다. 4 | 5 | ## 클래스와 인터페이스의 선언 및 인스턴스 생성 6 | * 선언 법은 Java와 거의 동일합니다. 7 | ```kotlin 8 | package foo.bar; 9 | 10 | class Baz { 11 | 12 | } 13 | ``` 14 | * Kotlin에서 접근 제한자를 지정하지 않은 경우엔 public으로 간주합니다. 15 | ```kotlin 16 | // 클래스 본체 없이 클래스를 선언할 수 있습니다. 17 | class Foo 18 | ``` 19 | * 인터페이스도 동일합니다. 20 | ```kotlin 21 | interface Foo{ 22 | 23 | } 24 | // 인터페이스 본체 없이 Interface를 선언하는 것도 가능합니다. 25 | interface Foo 26 | ``` 27 | * Kotlin은 클래스의 인스턴스를 생성하지 위해 new 키워드를 사용하지 않습니다. 28 | ```kotlin 29 | // new 키워드 생략 30 | val foo: Foo = Foo() 31 | 32 | // 인자 하나를 받는 생성자로 인스턴스 생성 33 | val bar: Bar = Bar(1) 34 | ``` 35 | * 추상 클래스(abstract class)는 Java와 동일한 방법으로 선언하지만, 추상 클래스의 인스턴스를 생성하는 형태는 매우 다릅니다. 36 | ```kotlin 37 | // 추상 클래스 선언 38 | abstract class Foo { 39 | abstract fun bar() 40 | } 41 | 42 | // 추상 클래스의 인스턴스 생성 43 | // object: [생성자] 형태로 선언 44 | val foo = object : Foo(){ 45 | 46 | override fun bar(){ 47 | // 함수 구현 48 | } 49 | } 50 | ``` 51 | * Interface를 선언하고 인스턴스를 생성하는 방법은 Java와 유사합니다. 52 | ```kotlin 53 | // 인터페이스 선언 54 | interface Bar { 55 | fun baz() 56 | } 57 | 58 | // 인터페이스의 인스턴스 생성 59 | // object: [인터페이스 이름] 형태로 선언 60 | val bar = object : Bar { 61 | override fun baz() { 62 | // 함수 구현 63 | } 64 | } 65 | ``` 66 | * 유의: 추상클래스에서는 인스턴스 생성 시 생성자를 사용하지만, 생성자가 없는 인스턴스는 인스턴스 이름만 사용합니다. 67 | 68 | ## 프로퍼티 69 | * Java에서는 자료를 저장하기 위해 Getter/Setter를 이용해야 했습니다. 하지만 이는 불필요한 코드의 양을 늘렸습니다. 70 | * Kotlin에서는 이런 불편함을 해결하기 위해 프로퍼티(property)를 사용합니다. 71 | * 프로퍼티는 자료를 저장할 수 있는 필드와 이에 상응하는 Getter/Setter 메서드를 함께 제공하며 필드와 유사한 형태로 선언합니다. 72 | ```kotlin 73 | class Person { 74 | var name : String? = null 75 | var address : String? = null 76 | } 77 | ``` 78 | * 프로퍼티의 타입으로 String?을 사용함은 널(null) 값이 들어갈 수 있음을 의미합니다. 79 | 80 | * Kotlin의 프로퍼티도 값(val) 혹은 변수(var) 중 하나로 선언합니다. 81 | * Getter/Setter 메서드가 모두 존재하는 한 값은 언제든 바뀔 수 있기 때문에 var로 선언하고 읽기만 할 수 있는 자료는 val로 선언합니다. 82 | ```kotlin 83 | class Person{ 84 | val name : String? = null // 값을 읽을 수만 있는 val 85 | var address :String? = null // 값을 읽고 쓰는 게 모두 가능한 var 86 | } 87 | ``` 88 | * null을 명시적으로 할당한 이유는 Kotlin에서는 클래스의 멤버로 사용하는 프로퍼티는 초깃값을 명시적으로 지정해야 하기 때문입니다. 89 | * 단, 생성자에서 프로퍼티의 값을 할당한다면 선언 시 할당하지 않아도 됩니다. 90 | * 프로퍼티 선언 시점이나 생성자 호출 시점에 값을 할당할 수 없는 경우에는 lateinit 키워드를 사용하여 이 프로퍼티의 값이 나중에 할당 될 것임을 명시합니다. 91 | lateinit 키워트는 var 프로퍼티에만 사용 가능하며, 선언 시점에 값 할당을 요구하는 val 프로퍼티에는 사용할 수 없습니다. 92 | ```kotlin 93 | class Person { 94 | val name : String? = null // val 프로퍼티는 항상 선언과 함꼐 값을 할당해야 합니다. 95 | 96 | lateinit var address : String? // 선언 시점에 값을 할당하지 않아도 컴파일 에러가 발생X 97 | } 98 | ``` 99 | * 만약 lateinit 키워드를 사용한 프로퍼티를 초기화 없이 사용하여 한다면 Uninitialized PropertyAccessException 예외가 발생합니다. 100 | * 이 키워드를 사용할 경우 프로퍼티의 초기화 여부를 확인하는 것이 좋습니다. 101 | * 프로퍼티에 초깃값을 할당하는 시점에서 해당 프로퍼티의 타입을 추론할 수 있다면, 타입 선언을 생략할 수 있습니다. 102 | ```kotlin 103 | class Person { 104 | var name = "No Name" // var name : String = "No Name"과 동일합니다. 105 | 106 | var address : String? = null // null만으로는 타입을 추론할 수 없기에 타입 선언이 필요합니다. 107 | } 108 | ``` 109 | 110 | ## 접근 제한자 111 | * Kotlin에서도 Java에서 처럼 접근 제한자를 클래스와 함수, 프로퍼티의 가시성을 제어하기 위해 사용합니다. 112 | ```kotlin 113 | class Foo{ 114 | // 접근 제한자가 없으면 115 | // public으로 간주합니다. 116 | val a = 1 117 | 118 | protected val b = 2 119 | private val c = 3 120 | 121 | // internal을 대신 사용합니다. 122 | internal val d = 4 123 | } 124 | ``` 125 | * public 제한자는 제한자가 없을 시 자동으로 취급되므로 생략하는 것이 좋습니다. 126 | * 이런 접근 제한자는 패키지 단위라서 견고하지 못하다는 단점이 있었으나 Kotlin에서는 internal 접근 제한자를 제공하여 해결하였습니다. 127 | * internal 접근 제한자는 동일한 모듈 내에 있는 클래스들로의 접근을 제한합니다. 128 | 따라서 외부 모듈에서는 이 접근 제한자로 선언된 요소에 접근할 수 없습니다. 129 | * '모듈'의 범위 130 | 1. InteliJ IDEA 모듈 131 | 2. Maven/Gradle 프로젝트 132 | 3. 하나의 Ant 태스트 내에서 함께 컴파일되는 파일들 133 | 134 | ## 생성자 135 | * Kotlin에서는 Java와 달리 생성자를 좀 더 명확한 방법으로 정의합니다. 136 | ```kotlin 137 | init { 138 | // 생성자에서 수행할 작업들 139 | } 140 | ``` 141 | * Kotlin은 init 블록을 사용하여 기본 생성자를 대체합니다. 142 | * 생성자에서 인자를 받아야 할 때(주 생성자 primary constructor)에는 여기서 받은 인자는 init블록에서 사용이 가능합니다. 143 | ```kotlin 144 | class Foo(a: Int) { 145 | init { 146 | Log.d("Foo", "Number: $a") 147 | } 148 | } 149 | ``` 150 | * 생성자의 인자를 통해 바로 클래스 내부의 프로퍼티에 값을 할당할 수 있습니다. 이 경우 추가적으로 프로퍼티 선언을 하지 않아도 됩니다. 151 | * Kotlin은 생성자의 인자에서 프로퍼티 선언도 이루어지고, 값 할당도 생성자 호출과 동시에 수행됩니다. 152 | ```kotlin 153 | class Foo(val a: Int, var b: Char) 154 | ``` 155 | * 또 다른 생성자가 필요할 경우에는 constructor 키워드를 이용하여 추가 생성자를 선언할 수 있습니다. 156 | ```kotlin 157 | class Foo(val a: Int, var b: Char){ 158 | 159 | // a의 값만 인자로 받는 추가 생성자 160 | // 기본 생성자를 반드시 호출해야 합니다. 161 | constructor(a: Int) : this(a, 0) 162 | 163 | // 두 인자의 값을 모두 0으로 지정하는 생성자 164 | constructor(): this(0, 0) 165 | } 166 | ``` 167 | * 추가 생성자를 정의하는 경우 주 생성자를 반드시 호출해야합니다. 168 | * 추가 생성자에서는 프로퍼티와 인자를 함께 생성할 수 없어서 주 생성자에서 프로퍼티 선언을 해주어야 합니다. 169 | * 생성자의 가시성을 변경하려면 constructor 키워드 앞에 접근 제한자를 추가하고 170 | 주 생성자에서는 constructor를 추가하고 접근 제한자도 추가하면 됩니다. 171 | ```kotlin 172 | // 주 생서자의 가시성을 internal로 지정, constructor 키워드 표기 필요 173 | class Fo internal constructor(val a: Int, var b: Car) { 174 | // 추가 생성자의 가시성 지정 175 | private constructor(a: Int) : this(a, 0) 176 | 177 | // 접근 제한자를 지정하지 않았으므로 public 178 | constructor(): this(0, 0) 179 | } 180 | ``` 181 | ## 함수 182 | * Kotlin에서는 클래스 내 메서드를 함수(funtion)로 표현합니다. 183 | ```kotlin 184 | class Foo { 185 | // 아무 값도 반환하지 않는 함수 186 | fun foo(): Unit { 187 | 188 | } 189 | 190 | // 정수 값을 반환하는 함수 191 | private fun bar() : Int { 192 | return 0 193 | } 194 | } 195 | ``` 196 | * 특별한 값을 반환하지 않는 경우 '함수 자체'를 의미하는 Unit 타입을 반환합니다. Unit 타입은 생략 가능합니다. 197 | ```kotlin 198 | // 특별한 값을 반환하지 않는 함수는 반환 타입을 생략할 수 있습니다. 199 | fun foo(){ 200 | 201 | } 202 | ``` 203 | ## 상속 및 인터페이스 구현 204 | * Kotlin은 클래스의 상속과 인터페이스의 구현은 : 뒤에 이들을 표기하는 것으로 대체합니다. 205 | ```kotlin 206 | class MainActivity: 207 | AppCompatActivity(), 208 | View.OnClickListener { 209 | 210 | } 211 | ``` 212 | * 클래스를 상속하는 경우 반드시 부모 클래스의 생성자를 호출해야합니다. 213 | * 부모 클래스의 생성자가 여러 형태일 경우, 클래스 선언부에서 부모 클래스의 생성자를 호출하는 대신 별도의 생성자 선언에서 부모 클래스의 생성자를 호출하도록 구현할 수 있습니다. 214 | * 부모 클래스의 생성자는 자바와 동일하게 super 키워드를 사용하여 호출합니다. 215 | ```kotlin 216 | class MyView : View { 217 | constructor(context: Context) : super(context) { 218 | // 뷰 초기화 219 | } 220 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 221 | // 뷰 초기화 222 | } 223 | } 224 | ``` 225 | * 생성자가 여럿인 경우 this 키워드를 사용하여 자기 자시느이 생성자를 호출할 수 있습니다. 226 | ```kotlin 227 | class MyView : View { 228 | constructor(context: Context) : this(context, null) 229 | 230 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 231 | // 뷰 초기화 232 | } 233 | } 234 | ``` 235 | * Kotlin에서는 상속받거나 구현한 함수의 앞에 무조건 override 키워드를 붙이도록 강제합니다. (@Override와 동일) 236 | ```kotlin 237 | class MyActicity: AppCompatActivity(), View.OnClickListener { 238 | // AppCompatActivity의 onCreate() 메서드 상속 239 | override fun onCreate(savedInstanceState: Bundle?) { 240 | super,onCreate(savedInstanceState) 241 | } 242 | 243 | // View.OnClickListener 인터페이스 구현 244 | override fun onClick(v : View) { 245 | 246 | } 247 | } 248 | ``` 249 | * Kotlin에서 open 키워드를 붙인 클래스나 함수가 아니라면 클래스를 상속하거나 함수를 재정의할 수 없습니다. 250 | ```kotlin 251 | // open 키워드를 사용하여 클래스를 상속받을 수 있도록합니다. 252 | open class OpenClass { 253 | // 프로퍼티의 값을 상속한 클래스에서 재정의할 수 있도록 합니다. 254 | open val openProperty = "foo" 255 | 256 | // open 키워드가 없다면, 상속한 클래스에서 프로퍼티 값을 재정의할 수 없습니다. 257 | val finalProperty = "bar" 258 | 259 | // 상속한 클래스에서 함수를 재정의할 수 있도록 합니다. 260 | open fun openFunc(){} 261 | 262 | // open 키워드가 없다면, 상속한 클래스에서 함수를 재정의할 수 없습니다. 263 | fun finalFunc() {} 264 | } 265 | 266 | // 성공: OpenClass는 상속 가능한 클래스입니다. 267 | 268 | class FunalClass : OpenClass() { 269 | // 성공: openProperty는 재정의 가능한 프로퍼티입니다. 270 | override val openProperty = "FOO" 271 | 272 | // 오류: finalProperty는 재정의 가능하도록 설정되지 않았습니다. 273 | override val finalProperty = "BAR" 274 | 275 | // 성공: openFunc 함수는 재정의 가능한 함수입니다. 276 | override fun openFunc() { 277 | Log.d("Log", "openFunc()") 278 | } 279 | 280 | // 오류: finalFunc는 재정의 가능하도록 설정되지 않았습니다. 281 | override fun finalFunc() { 282 | Log.d("Log", "finalFunc()") 283 | } 284 | } 285 | 286 | // 실패: FinalClass는 상속 가능하도록 설정되지 않았습니다. 287 | class Foo : FinalClass() 288 | ``` 289 | ## this 290 | * Java에서의 용도와 동일하게 사용됩니다. 291 | ```kotlin 292 | // 액티비티에 버튼 클릭 리스너를 구현합니다. 293 | class MyActivity : AppCompatAcitivty(), 294 | View.OnClickListener { 295 | lateinit var btnHello: Button 296 | 297 | override fun onCreate( 298 | savedInstanceState: Budle?) { 299 | super.onCreate(savedInstaceState) 300 | 301 | btnHello = findViewById(R.id.btn_hello) as Button 302 | // 버튼 클릭 리스너로 MyActivity를 설정합니다. 303 | btnHello.setOnClickListener(this) 304 | } 305 | 306 | override fun onClick(view: View) { 307 | // 버튼 클릭 리스너 구현 308 | } 309 | } 310 | ``` 311 | * this 키워드를 단독으로 사용한 것은, 해당 위치에서 가장 가까운 범위의 클래스를 의미합니다. 312 | * 클래스 내에서 다른 클래스나 인터페이스의 인스턴스를 동적으로 생성하여 사용하는 경우 키워드를 사용하는 위치에 따라 this가 의미하는 클래스가 달라질 수 있습니다. 313 | * 이런 문제를 해결하기 위해 Java에서는 {클래스 이름}.this라고 하지만 314 | Kotlin에서는 this@{클래스 이름} 형태로 표기합니다. 315 | ```kotlin 316 | class MyActivity : AppCompatActivity () { 317 | lateinit var btnHello: Button 318 | 319 | override fun onCreate(savedInstanceState: Budle?) { 320 | super.onCreate(savedInstanceState) 321 | btnHello = findViewById(R.id.btn_hello) as Button 322 | 323 | // 클릭 리스너를 동적으로 생성합니다. 324 | btnHello.setOnClicktener(object: View.OnClickLitener { 325 | override fun onClick(view: View) { 326 | override fun onClick(view: View) { 327 | 328 | // this = View.OnClickListener 329 | // 액티비티의 인스턴스를 참고하기 위해 330 | // this@MyAcitivty를 사용합니다. 331 | Toast.makeText(this@MyAcitivty, "Hello", Toast.LENGTH_SHORT).show()) 332 | } 333 | }) 334 | } 335 | } 336 | } 337 | ``` 338 | ## 정적 필드 및 메서드 339 | * Kotlin에서는 클래스 내에 상수를 정의하거나 인스턴스 생성없이 사용할 수 있는 메서드를 만들려면 조금 다른 방법을 사용해야 합니다. 340 | * 일반 적인 경우 클래스 내에 선언했던 정적 필드나 메서드는 패키지 단위(package-level)로 선언할 수 있습니다. 341 | ```kotlin 342 | // Foo.kt 343 | package foo.bar 344 | 345 | // 값 FOO를 패키지 foo.bar에 선언합니다. 346 | const val FOO = 123 347 | 348 | // 함수 foo를 패키지 foo.bar에 선언합니다. 349 | fun foo() { } 350 | 351 | class Foo { 352 | // 함수 bar는 FOO의 인스턴스를 생성해야 사용할 수 있습니다. 353 | fun bar() { } 354 | } 355 | ``` 356 | * 패키지 단위로 선언한 값이나 함수는 패키지에 종속되므로 import문에서도 {패키지 이름}.{값 혹은 함수 이름}을 사용합니다. 357 | ```kotlin 358 | import foo.bar.FOO 359 | import foo.bar.foo 360 | 361 | class Bar { 362 | fun bar() { 363 | // foo.bar 패키지 내의 값 FOO의 값을 참조합니다. 364 | val foo = FOO 365 | 366 | // foo.bar 패키지 내의 함수 foo를 호출합니다. 367 | foo() 368 | } 369 | } 370 | ``` 371 | * 패키지 단위 함수는 특정 클래스에 속하지 않아 클래스 내 private로 선언된 멤버에 접근해야 하는 팩토리 메서드(factory method)는 패키 단위 함수로 구현할 수 없습니다. 372 | * 이러한 경우 동반 객체(companion object)를 사용하면 모든 멤버에 접근할 수 있으면서 인스턴스 생성 없이 호출할 수 있는 함수를 작성할 수 있습니다. 373 | * 동반 객체란 클래스 내에 정적 필드, 함수를 둘 수 없는 대신 Kotlin에서 클래스별로 하나씩 클래스의 인스턴스 생성 없이 사용할 수 있는 오브젝트를 정의할 수 있는 것입니다. 374 | ```kotlin 375 | // 생성자의 접근 제한자가 private이므로 외부에선 접근할 수 없습니다. 376 | class User private constructor(val name: String, val registerTime: Long) { 377 | companion object { 378 | // compaion object는 클래스 내부에 존재하므로 379 | // private로 선언된 생성자에 접근할 수 있습니다. 380 | fun create(name: String) : User { 381 | return User(name, System.currentTimeMillis()) 382 | } 383 | } 384 | } 385 | ``` 386 | * 동반 객체로 선언한 함수는 Java의 정적 메서드와 사용 방법이 동일합니다. 앞의 예의 함수의 호출은 User.create("John Doe") 형태로 됩니다. 387 | 388 | ## 싱글톤 389 | * 싱글톤은 단 하나의 인스턴스만 생성되도록 제약을 둔 디자인 패턴입니다. 390 | * Kotlin에서는 오브젝트를 이용하여 간편하게 선언할 수 있습니다. 391 | Kotlin은 단 한줄이면 됩니다. 392 | ```kotlin 393 | object Singleton 394 | ``` 395 | * 오브젝트 내 선언된 값이나 함수는 Java의 정적 멤버와 동일한 방법으로 사용합니다. 396 | ```kotlin 397 | object Foo { 398 | val FOO = "foo" 399 | 400 | fun foo() { } 401 | } 402 | // 오브젝트 Foo의 값 FOO 참조 403 | val fooValue = Foo.FOO 404 | 405 | // 오브젝트 Foo의 foo() 함수 호출 406 | Foo.foo() 407 | ``` 408 | ## enum 클래스 409 | * Kotlin의 enum 클래스는 Java의 enum 타입과 동일한 역할을 하며, 선언 타입만 다릅니다. 410 | ```kotlin 411 | enum class Direction { 412 | NORTH, SOUTH, WEST, EAST 413 | } 414 | ``` 415 | * enum 클래스에 프로퍼티를 추가하는 방법 또한 자바와 유사합니다. 416 | ```kotlin 417 | enum class Direction (val lable: String) { 418 | NORTH, SOUTH, WEST, EAST 419 | } 420 | ``` 421 | ## 어노테이션 클래스 422 | * Kotlin에서도 Java와 동일하게 어노테이션을 정의하고 사용할 수 있으며 선언 형태, 추가 용법을 제외하면 대부분 동일합니다. 423 | ```kotlin 424 | annotation class Foo 425 | ``` 426 | * Kotlin에서도 어노테이션이 멤버를 가질 수 있고 멤버를 가지는 어노테이션을 선언하고 클래스에 이를 사용할 수 있고, 바로 값을 대입할 수 있습니다. 427 | ```kotlin 428 | annotaion class Foo ( 429 | val name: String 430 | ) 431 | // 멤버 이름이 'value'가 아니더라도 생략하고 값을 대입할 수 있습니다. 432 | @Foo("John Doe") 433 | class Bar { 434 | } 435 | ``` 436 | * 멤버의 기본값을 지정하는 경우 Kotlin은 default 대신 기본 매개변수를 지정하는 방법과 동일한 방식을 사용합니다. 437 | ```kotlin 438 | annotation class Foo ( 439 | // 기본값을 John Doe로 설정 440 | val name: String = "John Doe" 441 | ) 442 | 443 | // 멤버 name의 기본값을 사용합니다. 444 | @Foo 445 | class Bar{ 446 | 447 | } 448 | ``` 449 | * 다음은 어노테이션 멤버로 사용할 수 있는 타입입니다. 450 | 1. Java 원시 타입에 대응하는 타입(Int, Long 등) 451 | 2. 문자열(String) 452 | 3. 클래스 453 | 4. enum 클래스 454 | 5. 멤버가 속한 어노테이션이 아닌 다른 어노테이션 클래스 455 | 6. 위에 나열된 타입으로 구성된 배열 456 | * Kotlin에서는 배열 타입의 멤버가 포함하는 타입에 따라 값을 지정하는 방식이 달라집니다. 457 | * 해당 타입이 Java 원시 타입에 대응하는 경우 IntArray나 LongArray와 같이 각 원시 타입을 위한 전용 배열 클래스를 이용해야 합니다. 458 | * 그 외의 것들은 일반 배열을 사용하여 값을 대입해야 합니다. 459 | ```kotlin 460 | annotation class Foo ( 461 | val numbersL IntArray, 462 | val names: Array 463 | ) 464 | // 자바 원시 타입을 갖는 배열과 그렇지 않은 465 | // 배열 멤버에 값을 지정하는 방식이 다릅니다. 466 | @Foo(numbers = intArrayOf(1, 2, 3), 467 | names = arrayOf("a", "b", "c")) 468 | class Bar { 469 | 470 | } 471 | ``` 472 | * 어노테이션에는 부가 정보를 표시하기 위해 별도의 어노테이션, 즉 메타 어노테이션을 지정할 수 있습니다. 473 | * Kotlin은 Java에서 제공하는 메타 어노테이션을 모두 지원하며 표기만 다를 뿐 유사한 형태를 하고 있습니다. 474 | ```kotlin 475 | @Target(AnnotationTarget.TYPE) 476 | @Retention(AnnotationRetention.SOURCE) 477 | @Repeatable 478 | @MustBeDocumented 479 | annotation class Foo 480 | ``` 481 | * 클래스에 어노테이션을 사용하는데 이어, 생성자나 메서드(함수)의 파라미터에 어노테이션을 지정할 수 있습니다. 클래스와 크게 다르지 않습니다. 482 | ```kotlin 483 | annotation class Foo 484 | class Bar { 485 | @Foo constructor() { 486 | 487 | } 488 | 489 | @Foo fun bar(@Foo b: String) { 490 | 491 | } 492 | } 493 | ``` 494 | * Kotlin에서 주 생성자를 이용하는 경우, constructor 키워드 앞에 어노테이션을 넣어줍니다. 495 | ```kotlin 496 | annotation class Foo 497 | // 주 생성자 앞에 어노테이션을 추가합니다. 498 | class Bar @Foo constructor(val param: String) { 499 | 500 | } 501 | ``` 502 | * 클래스 내에 프로퍼티를 포함할 수 있습니다. 503 | * Kotlin에서는 프로퍼티 자체에 어노테이션을 지정하는 것뿐 아니라, 프로퍼티를 구성하는 각 요소에 별도로 지정할 수 있도록 어노테이션에 적용 대상(use-site targets)을 지정하는 기능을 제공합니다. 504 | ```kotlin 505 | class Bar { 506 | // bar의 Setter 및 Setter의 매개변수에 어노테이션을 지정합니다. 507 | @setparam:Foo 508 | @set:Foo 509 | var bar: String = "bar" 510 | 511 | // baz 필드에 어노테이션을 지정합니다. 512 | @field:Foo 513 | val baz: String = "baz" 514 | } 515 | ``` 516 | * 앞에 코드는 다음의 Java 코드와 같으 역할을 합니다. 517 | ```kotlin 518 | class Bar { 519 | private String bar = "bar"; 520 | 521 | // baz 필드에 어노테이션을 지정합니다. 522 | @Foo 523 | private final String baz = "baz"; 524 | 525 | public String getBar() { 526 | return this.bar; 527 | } 528 | 529 | // bar의 Setter 및 Setter의 매개변수에 어노테이션을 지정합니다. 530 | @Foo 531 | public void setBar(@Foo String bar) { 532 | this.bar = bar; 533 | } 534 | 535 | public String getBaz() { 536 | return this.baz; 537 | } 538 | } 539 | ``` 540 | * 프로퍼티의 구성요소 외 다른 요소에도 사용 시점 대상을 사용할 수 있습니다. 다음은 Kotlin에서 지원하는 어노테이션입니다. 541 | 1. file: 하나의 소스파일 542 | 2. property: 하나의 프로퍼티 543 | 3. get: 프로퍼티 내 Getter 메서드 544 | 4. set: 프로퍼티 내 Setter 매서드 545 | 5. receiver: 리시버 546 | 6. param: 생성자의 매개변수 547 | 7. setparam: Setter 메서드의 매개변수 548 | 8. delegate: delegate 프로퍼티의 인스턴스르 저장하는 필드 549 | * 어노테이션의 적용 대산을 사용할 때, 하나의 요소애 여러 개의 어노테이션을 지정해야하는 경우 대괄호를 사용하여 지정할 수 있습니다. 550 | ```kotlin 551 | class Bar { 552 | // 프로퍼티 bar의 Setter에 Foo와 Baz 어노테이션을 지정합니다. 553 | @set: [Foo Baz] 554 | var bar: String = "bar" 555 | } 556 | ``` 557 | ## 중첩 클래스 558 | * 특정 클래스 간 종속관계가 있는 경우 이를 중첩 클래스(nested class)로 표현할 수 있습니다. 종류에 따라 사용문법이 다릅니다. 559 | * 정적 중첩 클래스(static nested class)를 선언할 때 별도의 키워드를 붙이지 않아도 됩니다. 560 | * 하지만 비 정적 중첩 클래스를 선언할 때는 inner 키워드를 추가해야 합니다. 561 | ```kotlin 562 | class Outher { 563 | // 키워드가 없으면 정적 중첩 클래스로 간주 564 | class StaticNested { 565 | 566 | } 567 | // inner 키워드를 사용하여 비 정적 중첩 클래스 선언 568 | inner class NonStaticNested { 569 | 570 | } 571 | } 572 | // 정적 중첩 클래스: Outher 클래스의 인스턴스 생성 없이 인스턴스 생성 가능 573 | val staticInstance = Outer.StaticNested() 574 | 575 | // 비 정적 중첩 클래스: Outer 클래스의 인스턴스를 생성해야 인스턴스 생성 가능 576 | val nonStaticInstance = Outer().NonStaticNested() 577 | ``` -------------------------------------------------------------------------------- /Java Vs Kotlin/흐름 제어/흐름 제어.md: -------------------------------------------------------------------------------- 1 | # 흐름 제어 2 | ## 흐름 제어 3 | * 코드의 흐름 제어는 프로그램 개발의 핵심 요소입니다. 4 | * 흐름 제어에 사용하는 문법 또는 기능이 얼마나 잘 갖춰져 있는가에 따라 코드의 가독성 및 알고리즘 작성 효율이 크게 달라집니다. 5 | ## if-else 문 6 | * Kotlin도 if-else 문을 이용하여 조건문을 작성합니다. 7 | ```kotlin 8 | val age: Int = 25 9 | val ageRange: String 10 | 11 | if(age >= 10 && age < 20) { 12 | ageRang = "10대" 13 | } else if(age >= 20 && age <30) { 14 | ageRange("20대") 15 | } else if(...) { 16 | 17 | } else { 18 | ageRange = "기타" 19 | } 20 | ``` 21 | * Kotlin else if 문의 값을 반한활 수 있습습니다. 22 | ```kotlin 23 | val ageL Int = 25 24 | val ageRenge: String = if (age >= 0&& age < 20> { 25 | "10대" 26 | } else if (age >= 20 && age < 30) { 27 | "20대" 28 | } else if(...) { 29 | ... 30 | } else { 31 | "기타" 32 | } 33 | ``` 34 | * Kotlin도 삼항 연사자가 있습니다. 35 | ```kotlin 36 | val nubmer: Int = 20 37 | val Str: String = if(nuber % 2 == 0) { 38 | "Even" else "Odd" 39 | } 40 | ``` 41 | * when문은 swich문을 대체합니다. 중괄호를 사용하여 구분합니다. 42 | ```kotlin 43 | val bags: Int = 1 44 | when(bags) { 45 | // 각 case에 해당하는 값만 적습니다. 46 | 0 -> Log.d("Basgd", "We have no bags") 47 | 48 | // 여러 개의 case는 쉼표로 구분하여 적습니다. 49 | 1, 2 -> { 50 | Log.i("Bags", "Extra charge required") 51 | Log.d("Bags", "We have $bags bags(s)") 52 | } 53 | 54 | // default 대신 else로 표현합니다. 55 | else -> Log.e("Bags", "Cannot have more bags") 56 | } 57 | ``` 58 | * when문도 if-else문과 마찬가지로 값을 반환할 수 있습니다. 59 | ```kotlin 60 | val bags: Int = 1 61 | val bagString: String = when(bags) { 62 | 0 -> "We have $bags bag(s)" 63 | else -> "Cannot have more bags" 64 | } 65 | 66 | // "We have 1bags(s) 출력 67 | Log.d("Bags", bagString) 68 | ``` 69 | * Kotlin에서는 각 조건을 표현식(expression)으로 작성할 수 있습니다. 70 | ```kotlin 71 | val e : Exception = ...// 값 e에 여러 종류의 예외가 대입될 때 72 | 73 | // 예외의 종류에 알맞은 로그 메시지를 출려합니다. 74 | when(e) { 75 | is IOException -> Log.d("Message", "Network Error") 76 | is IllegalStateException -> Log.d("Message", "Invalid State") 77 | ... 78 | } 79 | 80 | val str : String = ...// 값 str에 임의의 문자열이 대입될 때 81 | 82 | // 문자열의 첫 번째 문자에 따라 알맞은 로그 메시지를 출력합니다. 83 | when (str) { 84 | str.startsWith('a') -> Log.d("Message", "A for Android") 85 | str.startsWith('k') -> Log.d("Message", "A for Android") 86 | } 87 | ``` 88 | ## while 문 89 | * Kotlin의 while문과 do while문의 기능 및 문법은 Kotlin 문법의 일반적인 특징을 제외하면 Java와 동일합니다. 90 | ```kotlin 91 | // while문 92 | var a: Int = 0 93 | var b: Int = 10 94 | 95 | while (a = ... // 이름 목록 115 | 116 | // 변수 name의 타입은 리스트 names를 통해 String으로 추론하므로 타입을 적지 않아도 됩니다. 117 | for(name in names) { 118 | // 이름과 함께 로그 출력 119 | Log.d("Name", "name=" + name) 120 | } 121 | ``` 122 | * for문 내에서 현재 항목의 인덱스가 필요할 경우, Collection.indicies 프로퍼티를 사용면 컬렉션의 인덱스를 순환하면 인덱스 인자로 배열 내 항목에 접근할 수 있습니다. 123 | ```kotlin 124 | val names: List = ... // 이름 목록 125 | 126 | // Collection.indicies는 컬렉션의 인덱스 범위를 반환합니다. 127 | for( i in names.indicies ) { 128 | // 인덱스 인자로 배열 내 항목 접근 129 | Log.e("Name", "name=${names[i]}") 130 | } 131 | ``` 132 | * Kotlin은 순환 범위를 표현하기 위해 별도의 자료구조를 이용하며, IntRange 클래스 등이 있습니다. 133 | ## 범위 134 | * 범위(range)는 Kotlin에만 있는 독특한 자료구조로, 특정 범위를 순환하거나 해당 범위 내에 특정 항목이 포함되어 있는지 확인할 때 사용합니다. 135 | * 범위는 ..연산자를 사용하여 정의합니다. 136 | ~~~kotlin 137 | 138 | // 0부터 10까지, 시작과 끝을 포함하는 범위를 정의합니다. 139 | val myRange : IntRange = 0..10 140 | 141 | // 앞에서 정의한 범위 내를 순환하는 for문 142 | for (i in myRange) { 143 | // Do something 144 | } 145 | 146 | // for문 내에서 바로 범위를 정의할 수 있습니다. 147 | for(i in 0..10) { 148 | // Do something 149 | } 150 | ~~~ 151 | * 인데스 순환을 위한 범위를 생성하는 경우 .. 연산자 대신 until 함수를 사용하는 가장 마지막 값을 포함하지 않는 범위를 생성할 수 있습니다. 152 | ~~~kotlin 153 | val items: List = ... // 항목이 담긴 리스트가 있다고 가정할 때 154 | 155 | // 0부터 인덱스부터 3번 인덱스까지 총 4개의 항목을 포함하는 범위 156 | val myRange : IntRange = 0..3 157 | 158 | // myRange와 동일한 항목을 포함하는 범위 159 | val myRange2 : IntRange = 0 until 4 160 | ~~~ 161 | * 범위 내에 특정 항목이 있는지 알아보려면 in 연산자를 사용합니다. 162 | ~~~ 163 | val myRange : IntRange = 0..10 // 범위 지정 164 | 165 | // 5가 myRange 내에 포함되어 있는지 확인합니다.: true 반환 166 | val foo : Boolean = 5 in myRange 167 | 168 | // 5가 myRange 내에 포함되지 않는지 확인합니다.: false 반환 169 | val bar: Boolean = 5 !in myRange 170 | ~~~ 171 | * 항목들의 순서가 반대로 정렬된 범위를 생성하려면 downTo() 함수를 사용합니다. 172 | * 첫 번쨰 인자로 시작 값을, 두 번째 인자로 마지막 값을 대입합니다. 173 | ~~~kotlin 174 | // '54321' 출력 175 | for (i in 5 downTo 1) { 176 | System.out.print(i) 177 | } 178 | ~~~ 179 | * downTo() 함수는 기본적으로 1씩 감소시키며, step() 함수를 사용하면 감소 폭을 변경할 수 있습니다. 180 | ~~~kotlin 181 | // '531' 출력 182 | for(i in downTo 1 step 2) { 183 | System.out.print(i) 184 | } 185 | ~~~ -------------------------------------------------------------------------------- /Koin/Koin.md: -------------------------------------------------------------------------------- 1 | # Koin 2 | ## Koin이란? 3 | * 안드로이드 앱을 개발할때 Dagger 와 같은 Dependency Injection(이하 DI)을 사용하는 것을 종종 볼 수 있습니다. 4 | * 각 컴포넌트간의 의존성을 외부 컨테이너에서 관리하는 방식을 통해 코드 재사용성을 높이고 Unit Test도 편하게 할 수 있게 되는 장점을 가지고 있습니다. 5 | * Koin은 코틀린 개발자를 위한 실용적인 API제공을 하는 경량화된 의존성 주입 프레임워크입니다. 6 | * 코틀린에서는 DSL을 제공하기때문에 어노테이션을 통한 코드 생성 대신 DSL을 활용하여, 의존성을 주입을 하기위한 똑똑하고 실용적인 API를 만들어낼 수 있습니다. 7 | ~~~gradle 8 | def koin_version="1.0.0" // 최신버전은 위의 github링크 참조 9 | implementation "org.koin:koin-android:$koin_version" 10 | ~~~ 11 | ## 초기화 12 | * Koin의 초기화는 정말 간단합니다. Application을 상속받은 클래스에 startKoin 함수를 호출하면서 미리 정의한 module들을 인자로 넘깁니다. 13 | ~~~kotlin 14 | class Koin: Application(){ 15 | override fun onCreate() { 16 | super.onCreate() 17 | 18 | startKoin(this, appModules) 19 | } 20 | } 21 | ~~~ 22 | 23 | ## 모듈 정의 24 | * Koin에서는 오직 필요한 모듈들을 정의하고 필요한 곳에 by inject() 키워드를 통해 의존성을 주입하면 됩니다. 25 | * 추가적인 component 나 subcomponent들은 필요없습니다. Koin은 모듈을 정의할때 kotlin dsl를 사용하여 좀 더 직관적으로 정의가 가능합니다. 26 | 27 | * ### Koin의 DSL 키워드 28 | * module - Koin 모듈을 정의할때 사용 29 | * factory - Dagger에서의 ActivityScope, FragmentScope와 유사한 기능으로 inject하는 시점에 해당 객체를 생성 30 | * single - Dagger에서의 Singleton 과 동일하며 앱이 살아있는 동안 전역적으로 사용가능한 객체를 생성 31 | * bind - 생성할 객체를 다른 타입으로 바인딩하고 싶을때 사용 32 | * get - 주입할 각 컴포넌트끼리의 의존성을 해결하기 위해 사용합니다. 33 | * 잘이해가 안되더라도 예제를 보면 쉽게 이해할 수 있습니다. 34 | * api를 호출하기 위한 retrofit service 객체를 모듈에 정의하고 위에 startKoin 인자에 넘길 appModules array를 생성해 보겠습니다. 35 | ~~~kotlin 36 | val apiModule: Module = module { 37 | 38 | single { 39 | Retrofit.Builder() 40 | .client(OkHttpClient.Builder().build()) 41 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 42 | .addConverterFactory(GsonConverterFactory.create()) 43 | .baseUrl(getProperty("BASE_URL")) 44 | .build() 45 | .create(Api::class.java) 46 | } 47 | } 48 | 49 | val photoListModule: Module = module { 50 | factory { 51 | PhotoPresenterImpl(get()) as PhotoPresenter 52 | } 53 | } 54 | 55 | val appModules = listOf(apiModule, photoListModule) 56 | ~~~ 57 | * 코틀린은 이런 경우 따로 class 를 만들필요가 없기 때문에 위와 같이 단순히 모듈들을 변수로 정의했습니다. 여기에서 single, factory 키워드를 사용해 객체를 생성했습니다. 58 | * 이렇게 생성할 경우 Retrofit api 객체는 전역적으로 한개의 객체만 생성이 가능하며 PhotoPresenter 객체는 생성한 액티비티 혹은 프래그먼트로 scope가 제한됩니다. 59 | * 아래는 동일한 기능을 하는 Dagger의 모듈 구현부입니다 60 | ~~~kotlin 61 | @Provides 62 | @Singleton 63 | fun provideApi(): Api { 64 | return Retrofit.Builder() 65 | .client(OkHttpClient.Builder().build()) 66 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 67 | .addConverterFactory(GsonConverterFactory.create()) 68 | .baseUrl(baseUrl) 69 | .build() 70 | .create(Api::class.java) 71 | } 72 | ~~~ 73 | * 위에 Koin모듈 정의를 보면 get() 키워드를 사용하고 있습니다. 여기서 get()을 호출하면 apiModule 에 정의된 retrofit Api 클래스 객체가 넘어갑니다. 74 | * Koin은 생성하고자 하는 객체의 인자 타입을 보고 의존성을 판단해 쉽게 컴포넌트간의 의존성을 해결합니다. 실제로 PhotoPresenterImpl의 클래스 정의는 아래와 같습니다. 75 | ~~~kotlin 76 | class PhotoPresenterImpl(val api: Api): PhotoPresenter() { 77 | override fun requestPhoto(id: Long) { 78 | //구현체 79 | } 80 | } 81 | ~~~ 82 | * 좀 더 쉽게 설명하자면 아래와 같은 의존성이 있는 클래스들이 있다면 83 | ~~~kotlin 84 | class ComponentA() 85 | class ComponentB(val componentA : ComponentA) 86 | ~~~ 87 | * 모듈 정의를 아래와 같이 하게 됩니다. 88 | ~~~kotlin 89 | val moduleA = module { 90 | // Singleton ComponentA 91 | single { ComponentA() } 92 | } 93 | 94 | val moduleB = module { 95 | // Singleton ComponentB with linked instance ComponentA 96 | single { ComponentB(get()) } 97 | } 98 | ~~~ 99 | ## 의존성 주입 100 | * 이제 사용한 모듈들을 정의했으니 사용하고 싶은 곳에 주입해보겠습니다. Koin에서 주입은 by inject() 키워드를 사용(Kotlin Delegated Properties 방식을 사용)합니다. 101 | * 이는 Dagger에서의 @Inject 키워드와 유사하며 항상 지연초기화(lazy init)을 사용하게 됩니다. 즉 객체를 사용하는 시점에 생성을 하므로 성능상 이점이 있습니다. 102 | ~~~kotlin 103 | class PhotoActivity : AppCompatActivity(), PhotoScene { 104 | 105 | private val presenter: PhotoPresenter by inject() 106 | 107 | override fun onCreate(savedInstanceState: Bundle?) { 108 | super.onCreate(savedInstanceState) 109 | setContentView(R.layout.activity_photo) 110 | 111 | presenter.scene = this 112 | presenter.requestPhoto(1) 113 | } 114 | ... 115 | } 116 | ~~~ 117 | * 예제에서는 PhotoPresenter 인터페이스 객체를 주입시키고 있고 실제 객체가 생성되는 시점은 presenter.scene을 호출하는 시점입니다. 118 | * 모듈을 정의하다보면 가끔 같은 객체를 여러개 생성해 각각 다른 목적으로 사용하고 싶을때가 있습니다. 119 | * Koin 에서는 모듈의 이름을 정의해 이와 같은 conflict 를 해결합니다. 120 | ~~~kotlin 121 | val dialogModule: Module = module { 122 | module("cancelableDialogBuilder"){ 123 | single { 124 | AlertDialog.Builder(androidContext()) 125 | .setCancelable(true) 126 | } 127 | } 128 | module("dialogBuilder"){ 129 | single { 130 | AlertDialog.Builder(androidContext()) 131 | .setCancelable(false) 132 | } 133 | } 134 | } 135 | ~~~ 136 | ~~~kotlin 137 | // Request dependency from namespace 138 | val cancelableDialog: AlertDialog.Builder by inject("cancelableDialogBuilder") 139 | ~~~ 140 | * 실제 프로젝트에서는 이렇게 사용하지는 않겠지만 이해를 돕기 위해 AlertDialog 사용해봤습니다. 141 | 142 | * 여기까지 모듈을 정의하고 정의된 모듈을 주입하는것까지 해보았습니다. 이 외에 ViewModel 주입을 더 편하게 해주는 143 | * koin-android-viewmodel 라이브러리도 있고 Scoping을 편하게 해주는 koin-android-scope 라이브러리도 제공을 하고 있습니다. 144 | * Dagger와 비교했을때 실제 학습비용은 매우 낮지만 장단점이 있습니다. 판단은 각자의 몫이지만 좀더 가벼운 프로젝트를 시작할때 한번쯤 사용해보는것도 좋을 것 같습니다. 145 | -------------------------------------------------------------------------------- /Kotlin Android Extension/Introducing Kotlin Android Extension.md: -------------------------------------------------------------------------------- 1 | # Introducing Kotlin Android Extension 2 | ## Introducing problem 3 | * findViewById() 메서드는 액티비티나 프래그먼트 등 레이아웃 파일에 선언된 여러 개의 뷰(view)로 구성된 화면에서 특정 뷰의 인스턴스를 얻기 위해 사용합니다. 4 | * 하지만 이 메서드에서 반환한 뷰 객체를 잘못된 타입의 뷰로 캐스팅하거나 다른 레이아웃에 선언된 ID를 잘못 사용하면 널 값을 반환합니다. 5 | * 즉, 실수로 버그를 발생시키기 매우 쉬워 안드로이드 앱 개발자에게 이 메서드는 애증의 존재입니다. 6 | * 이 뿐만 아니라, 화면을 구성하는 뷰의 개수만큼 findViewById() 메서드를 사용해야합니다. 7 | * 때문에, 복잡한 구조로 구성된 화면을 다루는 경우 뷰 인스턴스를 받는 코드만 몇십 줄을 차지하여 코드의 가독성이 떨어집니다. 8 | 9 | ## problem code 10 | ~~~java 11 | public class MainActivity extends AppCompatActivity { 12 | // 뷰 인스턴스 선언 13 | private TextView tvTitle; 14 | private TextView tvSubTitle; 15 | private ImageView ivProfile; 16 | private Button btnEdit; 17 | private TextView tvAddress; 18 | private TextView tvMemo; 19 | 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState) 22 | setContentView(R.layout.activity_main); 23 | 24 | // 뷰 인스턴스 초기화 수행 25 | tvTitle = (TextView) findViewById(R.id.tv_title); 26 | tvSubTitle = (TextView) findViewById(R.id.tv_sub_title); 27 | tvProfile = (ImageView) findViewById(R.id.iv_profile); 28 | btnEdit = (Button) findViewById(R.id.btn_edit); 29 | tvAddress = (TextView) findViewById(R.id.tv_address); 30 | tvMemo = (TextView) findViewById(R.id.tv_memo); 31 | } 32 | } 33 | ~~~ 34 | 35 | ## Introducing Kotlin Android Extension 36 | * Kotlin Android Extension은 이런 불편을 말끔히 해결하기 위해 존재합니다. -------------------------------------------------------------------------------- /Kotlin Android Extension/리사이클러뷰에서 사용해보기/Kotlin Android Extension 리사이클러뷰.md: -------------------------------------------------------------------------------- 1 | # Kotlin Android Extension 리사이클러뷰 2 | 3 | ## 리사이클러뷰 문제점 4 | * 리사이클러뷰는 각 항목을 표시하기 위해 뷰홀더를 사용하며, 뷰홀더에서 표시할 뷰를 구성하기 위해 주로 레이아웃 파일을 사용합니다. 5 | * 때문에, 액티비티나 프래그먼트와 마찬가지로 findViewById()를 사용하여 뷰의 인스턴스를 받아 사용했습니다. 6 | 7 | ## 예시 레이아웃 8 | * 레이아웃(item_city.xml)은 다음과 같이 도시 이름과 코드를 표시하기 위한 TextView 두 개로 구성되어 있습니다. 9 | ~~~xml 10 | 11 | 17 | 18 | 22 | 23 | 27 | 28 | 29 | ~~~ 30 | ## 예시 Java코드 31 | * 코드는 어댑터 Java 코드입니다. 32 | * 각 항목을 리스트에 표시하기 위해 사용하는 뷰홀더 내부에서 액티비티와 프래그먼트와 유사하게 findViewById() 메서드를 사용하는 것을 확인할 수 있습니다. 33 | ~~~java 34 | public class CityAdapter extends RecyclerView.Apdater{ 35 | 36 | private List cities; 37 | 38 | public CityAdapter() { 39 | cities = new ArrayList<>(); 40 | cities.add(Pair.create("Seoul", "SEO")); 41 | cities.add(Pair.create("Tokyo","TOK")); 42 | cities.add(Pair.create("Mountain View","MTV")); 43 | cities.add(Pair.create("Singapore", "SIN")); 44 | cities.add(Pair.create("New York", "NYC")); 45 | } 46 | 47 | @Override 48 | public Holder onCreateViewHolder(ViewGroup parent, int viewType) { 49 | return new Holder(parent); 50 | } 51 | 52 | @Override 53 | public void onBindViewHolder(Holder holder, int position) { 54 | Pair item = cities.get(position); 55 | 56 | // 각 부분에 해당하는 값을 반영합니다. 57 | holder.cityName.setText(item.first); 58 | holder.cityCode.setText(item.second); 59 | } 60 | 61 | @Override 62 | public int getItemCount() { 63 | return null != cities ? cities.size() : 0; 64 | } 65 | 66 | class Holder extends RecyclerView.ViewHolder { 67 | 68 | // 뷰 인스턴스 선언 69 | TextView cityName; 70 | 71 | TextView cityCode; 72 | 73 | Holder(ViewGroup parent) { 74 | super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_city, parent, flase)); 75 | 76 | // findViewById()를 사용하여 뷰 인스턴스를 받아옵니다. 77 | cityName = (TextView) itemView.findViewById(R.id.tv_city_name); 78 | cityCode = (TextView) itemView.findViewById(R.id.tv_city_code); 79 | } 80 | } 81 | } 82 | ~~~ 83 | 84 | ## Kotlin Android Extension 적용 85 | * Kotlin Android Extension을 사용하면 앞의 Java 코드를 다음과 같이 표현할 수 있습니다. 86 | * Kotlin Android Extension은 뷰홀더의 itemView를 통해 레이아웃 내 뷰에 접근할 수 있도록 지원합니다. 87 | 88 | ~~~kotlin 89 | // item_city.xml 레이아웃에 있는 뷰를 사용하기 위한 import 문 90 | import kotlinx.android.synthetic.main.item_city.view.* 91 | ... 92 | 93 | class CityAdapter : RecyclerView.Adapter() { 94 | private val cities = listOf( 95 | "Seoul" to "SEO", 96 | "Tokyo" to "TOK", 97 | "Mountain View" to "MTV", 98 | "Singapore" to "SIN", 99 | "New York" to "NTC") 100 | 101 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = Holder(parent) 102 | 103 | override fun onBindViewHolder(holder: Holder, position: Int) { 104 | val (city, code) = cities[position] 105 | 106 | // 코틀린 안드로이드 익스텐션을 사용하여 레이아웃 내 뷰에 접근하려면 107 | // 뷰홀더 내의 itemView를 거쳐야 합니다. 108 | with(holder.itemView) { 109 | 110 | // 뷰 ID를 사용하여 인스턴스에 바로 접근합니다. 111 | tv_city_name.text = city 112 | tv_city_code.text = code 113 | } 114 | } 115 | 116 | override fun getItemCount() = cities.size 117 | 118 | inner class Holder(parent: ViewGroup) 119 | : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflater(R.layout.item_city, parent, false)) 120 | } 121 | ~~~ -------------------------------------------------------------------------------- /Kotlin Android Extension/사용법/Kotlin Android Extension 사용하기.md: -------------------------------------------------------------------------------- 1 | # Kotlin Android Extension 사용하기 2 | 3 | ## 예시와 함께 설명 4 | * EditText를 통해 이름을 입력받고, 버튼을 누르면 입력한 이름과 메시지가 출력되는 기능을 갖추고 있는 액티비티를 예로 듭니다. 5 | * 레이아웃은 다음과 같습니다. 6 | ~~~xml 7 | 8 | 13 | 14 | 18 | 19 |