1. 코딩 준비하기, 2. 변수와 상수, 3. 조건문
- 미니 퀴즈 3-1 -
1. 실제 에러를 출력하기 위한 로그함수의 이름은 무엇인가요?
답 : android.util.Log.x(tag, msg) 입니다. x는 v, i, d, w, e 종류가 있으며 파라미터는 테그와 메시지입니다.
함수 | 의미 | 내용 |
Log.v() | verbose | 상세한 로그 내용을 출력하기 위해 사용합니다. |
Log.d() | debug | 개발에 필요한 내용을 출력하기 위해 사용합니다. |
Log.i() | information | 정보성의 일반적인 메시지를 전달하기 위해 사용합니다. |
Log.w() | warning | 에러는 아니지만 경고성 메시지를 전달하기 위해 사용합니다. |
Log.e() | Error | 실제 에러 메시지를 출력하기 위해 사용합니다. |
2. 개발자들이 보기 위한 목적으로 사용되는 로그함수의 이름은 무엇인가요?
답 : Log.d()
3. 특정 로그를 필터링하기 위해 사용되는 로그함수의 첫 번째 파라미터는 무엇인가요?
답 : tag
- 미니 퀴즈 3-2 -
1. 코드 작성 규칙에서 가장 중요한 것은 무엇인가요?
답 : 코딩 컨벤션입니다. 많은 프로그래머가 프로젝트를 진행 할 때 일관성있게 작성해야 유지보수 및 개발에 용이하기 때문입니다.
2. 16비트 정숫값을 저장할 수 있는 타입은 무엇인가요?
답 : Int
3. 2개의 문자열을 합칠 수 있는 방법은 무엇인가요?
답 : + 연산자를 사용하여 문자열을 합칠 수 있습니다. ex) var alpa = "abc" + "def"라고 한다면 alpa 출력 시 abcdef라고 나옵니다.
4. 한 번 입력한 값을 바꿀 수 없는 변수를 무엇이라고 하나요?
답 : 상수라고 합니다. (val 타입)
- 미니 퀴즈 3-3 -
1. If문의 수식이 false일 때 특정한 코드를 처리하기 위해서는 어떤 문법을 사용할 수 있을까요?
답 : else 일때 { } 코드블록이 실행됩니다.
2. when문에서 범위 값을 비교하기 위해서 무엇을 사용할 수 있나요?
답 : in을 사용해서 범위 값을 나타낼 수 있습니다. ex) in 5..10 -> { 코드내용 }
3. when문에서 비교 대상 파라미터가 없어도 사용할 수 있습니다.
답 : O
4. 다음 코드에서 변수 result에 입력되는 값은 무엇인가요?
var result = when (10) {
9 -> { true }
in 5..20 -> { false }
else -> { true }
}
답 : false
4. 배열과 컬렉션
4.1 배열
변수에 여러 개의 값을 저장해야 할 필요성이 있습니다. 코틀린은 배열(Array)과 컬렉셕(Collection)이라는 데이터 타입을 제공합니다.
( TIP. Array는 개수를 특정해야 하지만 Collection은 개수 특정을 하지 않습니다. )
배열의 형태는 다음과 같이 선언합니다.
var 변수 = Array(개수) |
var students = IntArray(10) |
var longArray = LongArray(10) |
var CharArray = CharArray(10) |
var FloatArray = FloayArray(10) |
var DoubleArray = DoubleArray(10) |
문자 배열에 빈 공간 할당하기
String은 기본 타입이 아니기 때문에 StringArray는 없지만 아래와 같이 사용할 수 있습니다.
var stringArray = Array(10, {item->""}) |
var stringArray = Array(10, {item->"123"})
for(i in stringArray.indices)
android.util.Log.i("log", "${i} -> ${stringArray[i]}")
(문자열 배열 stringArray이 10개의 공간이 할당되고 "123"으로 초기화된 모습입니다. 비슷한 방식으로 IntArray나 DoubleArray역시 배열의 크기를 입력하고 각 공간을 특정 값으로 초기화 할 수 있습니다.)
값으로 배열 공간 할당하기
arrayOf() 함수를 사용해서 String 값을 직접 할당할 수도 있습니다.
var dayArray = arrayOf("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN") |
배열에 값 입력하기 ( var students = IntArray(10, {0})
배열명[인덱스] = 값 | ex) sutdents[0] = 90 |
배열명.set(인덱스, 값) | ex) students.set(1, 91) |
인덱스를 벗어난 경우 (기존에 배열의 크기보다 큰 인덱스에 값을 넣으려고 하는 경우) |
students[10] = 100 -> Exception 발생 java.lang.ArrayindOutOfBoundsException: |
배열에 값 꺼내기
배열명[인덱스] | ex) var seventhValue = intArray[6] |
배열명.get(인덱스) | ex) var tenthValue = intArray.get(6) |
4.2 컬렉션
컬렉션은 동적 배열이라고도 하는데 고정된 크기로 배열을 선언하지 않는다는 특징이 있습니다.
컬렉션은 크게 세 가지로 리스트(List), 맵(Map), 셋(Set)이 있으며 각각은 다음과 같은 용도로 사용할 수 있습니다.
리스트(List)
리스트(List)는 저장되는 데이터에 인덱스를 부여한 컬렉션이며 중복된 값을 입력할 수 있습니다. 코틀린에서 동적으로 리스트를 사용하기 위해서는 리스트 자료형 앞에 뮤터블(Mutable)이라는 접두어(prefix)가 붙습니다. 접두어가 없는 리스트도 있지만 잘 사용하지 않기 때문에 항상 mutableList, mutalbeMap, mutableSet을 사용한다고 알고 있으면 됩니다.
사용법은 Array와 같이 '데이터 타입Of' 형태로 사용할 수 있습니다.
var list = mutableListOf("MON", "TUE", "WED") |
뮤터블이란? 영화 X맨 시리즈에서 뮤턴트(Mutant)라는 용어가 나오는데 들어본 적 있으신가요? 영화에서 뮤턴트는 돌연변이라는 뜻인데 프로그래밍 언어에서는 뮤터블을 변할 수 있는 데이터 타입을 가리키는 용어입니다. 앞서 변수 생성시 지정했던 var 타입이 바로 뮤터블입니다. 그리고 반대 개념인 이뮤터블(Immutable)이 있는데 이것은 val과 같이 변할 수 없는 데이터 타입을 가리키는 용어입니다. 코틀린은 컬렉션 데이터 타입을 설계할 때 모두 이뮤터블로 설계하였습니다. 기본 컬렉션인 리스트(List), 맵(Map), 셋(Set)은 모두 한 번 입력된 값을 바꿀 수 없습니다. 그래서 컬렉션의 원래 용도인 동적 배열로 사용하기 위해서는 뮤터블로 만들어지 데이터 타입을 사용해야 합니다. |
리스트에 값 추가하기 : add
mutableList.add("THU") |
MON | THU | WED |
[0] | [1] | [2] |
MON | THU | WED | THU |
[0] | [1] | [2] | [3] |
(add함수를 사용하면 입력될 위치인 인덱스를 따로 지정해주지 않아도 해당 값에 순서대로 인덱스가 지정됩니다.)
리스트에 입력된 값 사용하기 : get
var variable = mutableList.get(Index) |
리스트값 수정하기 : set
mutableList.set(Index, "수정할 값") |
리스트에 입력된 값 제거하기 : removeAt
mutableList.removeAt(Index) | ex) mutableList.removeAt(1) |
MUN | THU | WED | THU |
[0] | [1] | [2] | [3] |
MUN | WED | THU |
[0] | [1] | [2] |
(1에 해당하는 인덱스 값을 제거했을 때 빈자리의 인덱스를 메꿉니다. )
빈 리스트 사용하기
아무것도 없는 빈 리스트를 생성하면 앞으로 입력되는 값의 데이터 타입을 알 수 없기 때문에 값의 타입을 추론할 수 없습니다. 그래서 빈 컬렉션의 경우 앞에서처러 '데이터 타입Of'만으로는 생성되지 않고 데이터 타입을 직접적으로 알려주는 방법을 사용해야 합니다.
var 변수면 = mutableListOf<컬렉션에 입력될 값의 타입>() |
var stringList = mutableListOf<String>() |
제네릭 앞에서 리스트 컬렉션을 생성하면서 < >괄호를 사용했는데, 이 괄호의 정식 명칭은 제네릭(Generic)입니다. 제네릭은 컬렉션이 사용하는 값의 타입을 지정하기 위한 도구입니다. 코틀린에서 컬렉션은 제네릭을 사용하지 않으면 생성할 수 없습니다. 단, 값으로 초기화할 때는 입력되는 값으로 타입을 추론할 수 있기 때문에 제네릭을 쓰지 않아도 생성할 수 있습니다. |
컬렉션 개수 가져오기 : Size
size 프로퍼티를 사용하면 컬렉션의 개수를 가져올 수 있습니다.
[리스트 다루기]
package com.hellow.example3
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. 값으로 컬렉션 생성
var mutableList = mutableListOf("MON", "TUE", "WED")
// 값을 추가 : add
// 값을 출력
Log.d("Collection", "stringList에 입력된 첫 번째 값은 ${mutableList.get(0)} 입니다.")
Log.d("Collection", "stringList에 입력된 두 번째 값은 ${mutableList.get(1)} 입니다.")
// 2. 빈 컬렉션 생성
var stringList = mutableListOf<String>()
stringList.add("월")
stringList.add("화")
stringList.add("수")
// 값 변경하기 : set
stringList.set(1, "요일 변경")
// 사용
Log.d("Collection", "stringList에 입력된 두 번째 값은 ${stringList.get(1)} 입니다.")
// 삭제 : removeAt
stringList.removeAt(1)
Log.d("Collection", "stringList에 입력된 두 번째 값은 ${stringList.get(1)} 입니다.")
// 개수 출력 : size 프로퍼티
Log.d("Collection", "stringList에는 ${stringList.size} 입니다.")
}
}
셋(Set)
셋(Set)은 중복을 허용하지 않는 리스트(List)라고 할 수 있습니다. 리스트와 유사한 구조이지만 인덱스로 조회할 수 없고, get 함수도 지원하지 않습니다.
var set = mutableSetOf<String>() |
set.add("JAN") |
set.add("FEB") |
set.add("MAR") |
set.add("JAN") // 동일한 값 입력 x |
"JAN" | "FEB" | "MAR" |
(실제 set을 출력해보면 해당 3개의 값만 출력됩니다.)
셋 사용하기
셋은 인덱스로 조회하는 함수가 없기 때문에 특정 위치의 값을 직접 사용할 수 없습니다.
Log.d("Collection", "Set 전체 출력 = ${set}") |
셋 삭제하기
셋은 값이 중복되지 않기 때문에 값으로 직접 조회해서 삭제할 수 있습니다.
set.remove(값) |
package com.hellow.example3
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. Set 생성 및 값 추가 : mutableSetOf<>(), add
var set = mutableSetOf<String>()
set.add("JAN")
set.add("FEB")
set.add("MAR")
set.add("JAN") // 동일한 값은 입력 x
// 2. 전체 데이터 출력
Log.d("Collection", "set 값-> ${set}")
// 3. 특정 값 삭제 : remove
set.remove("FEB")
Log.d("Collection", "set 값-> ${set}")
}
}
맵(Map)
맵 생성하기
맵(Map)은 키(Key)와 값(Value)의 쌍으로 입력되는 컬렉션입니다. 맵의 키는 마치 리스트에서의 인덱스와 비슷한데 리스트와는 다르게 키를 직접 입력해야 합니다.
제네릭으로 키와 값의 데이터 타입을 지정해서 맵을 생성합니다.
빈 맵으로 생성하고 값 추가하기
var map = mutableMapOf<String, String>() |
map.put("키1", "값1") |
map.put("키2", "값2") |
map.put("키3", "값3") |
맵 사용하기
get( ) 함수에 키를 직접 입력해서 값을 출력합니다.
Log.d("Collection", "map에 입력된 키1의 값은 ${map.get("키1")}입니다.") |
맵 수정하기
put( ) 함수를 사용할 때 동일한 키를 가진 값이 있으면 키는 유지된 채로 그 값만 수정됩니다.
map.put("키2", "수정됨") |
맵 삭제하기
remove( ) 함수에 키를 입력해서 값을 삭제할 수 있습니다. 리스트와는 다르게 인덱스에 해당하는 키의 값이 변경되지 않고 그래도 유지됩니다.
map.remove("키2") |
값1 | 값2 | 값3 |
키1 | 키2 | 키3 |
값1 | null | 값3 |
키1 | 키2 | 키3 |
컬렉션 값의 단위 = 엘리먼트 컬렉션에 입력되는 값은 각각을 엘리먼트(Element)라고 합니다. 값이라고 해도 되지만 맵을 지칭할 때 맵의 값(엘리먼트 자체)을 가리키는 건지 엘리먼트 값(실제 값)을 가르키는 건지, 2개의 용어가 충돌할 수 있기 때문에 엘리먼트라고 이해하고 있는 것이 좋습니다. 엘리먼트는 맵의 입력 단위인 키와 값을 합친 것을 말하는데 이것은 리스트와 셋에서도 동일한 용어로 사용됩니다. 즉, 리스트의 값 또한 엘리먼트라고 부릅니다. ex) 리스트 엘리먼트 = 리스트의 (값) 맵 엘리먼트 = 맵의 (키와 값) |
4.3 이뮤터블 컬렉션
이뮤터블 컬렉션은 기존 컬렉션에서 mutable이라는 접두어가 제거된 형태로 사용됩니다. 불변형 컬렉션은 한 번 입력된 값을 변경할 수 없기 때문에 add나 set 함수는 지원하지 않고 최초 입력된 값을 사용만 할 수 있습니다.
var list = mutableListOf("1", "2") -> | var list = listOf("1", "2") |
val IMMUTABLE_LIST = listOf("JAN", "FEB", "MAR") // 생성 Log.d("Collection", "리스트의 두 번째 값은 ${IMMUTABLE_LIST.get(1)}입니다. // 사용 |
(이뮤터블 컬렉션은 보통 기준이 되는 어떤 값의 모음을 하나의 변수에 저장하할 필요가 있거나 또는 여러 개의 값을 중간에 수정하지 않고 사용할 필요가 있을 때 이뮤터블 컬렉션을 사용합니다. 대표적인 예로 요일 데이터가 있습니다. )
var DAY_LIST = listOf("월", "화", "수", "목", "금", "토", "일") |
- 요약 -
배열(Array) : 하나의 변수에 여러 개의 정해진 값을 담을 수 있게 해주는 데이터 타입입니다. 처음 정해진 값의 개수는 바꿀 수 없습니다.
컬렉션(Collection) : 여러 개의 값을 담을 수 있는 배열은 값의 개수가 증가하면 사용할 수 없는 단점이 있기 때문에 동적으로 크기를 변경할 수 있도록 만들어진 동적 배열입니다. 동적 배열에는 크게 리스트(List), 셋(Set), 맵(Map) 세 가지의 데이터 타입이 있는데, 이것들을 모두 통칭해서 부르는 용어가 컬렉션입니다.
뮤터블(Mutable) : 입력된 값을 변경할 수 있는 것을 말합니다. 대표적으로 var로 선언된 변수는 모두 뮤터블입니다.
리스트(List) : 컬렉션의 한 종류로 인덱스를 사용하는 데이터 타입입니다. 인덱스가 있기 때문에 중복된 값을 넣을 수도 있습니다.
셋(Set) : 리스트에서 인덱스가 빠진 데이터 타입입니다. 값을 중복해서 넣을 수 없습니다.
맵(Map) : 키(Key)와 값(Value)을 쌍으로 갖는 데이터 타입입니다. 맵의 키는 리스트의 인덱스처럼 사용할 수 있습니다.
- 미니 퀴즈 3-4 -
1. 여러 개의 값을 하나의 변수에 담을 수 있는 데이터 타입에는 어떤 것들이 있나요?
답 : 배열(Array), 컬렉션(Collection : List, Set, Map)
2. 리스트와 셋의 가장 큰 차이점은 무엇인가요?
답 : 셋에는 값을 가르키는 인덱스가 없습니다. 따라서 각각의 값을 출력할 수 없습니다.
3. 배열은 뮤터블인가요?
답 : 네
4. 다음 코드의 결과로 출력되는 값은 무엇인가요?
var map = mutableMapOf<String, String>()
map.put("키1", "값2")
map.put("키2", "값2")
map.put("키3", "값3")
map.remove("키3")
Log.d("Collection", "${map.get("키3")}")
답 : null
- 미니 퀴즈 3-5 -
1. 1부터 100까지 반복하면서 숫자를 출력하는 코드에는 for와 while 중 어떤 반복문을 사용하는 것이 좋은가요?
답 : for문
2. while문과 do ~ while문의 차이점은 무엇인가요?
답 : do ~ while문은 무조건 코드를 한번 동작시킨 후에 조건식에 따라 해당 코드를 실행 할지 말지 결정하게 됩니다.
반면 while문은 코드를 동작시키기 위해서는 해당 조건식이 맞아야만 동작합니다.
3. 다음 코드의 실행 결과를 예측해보세요.
for (index in 0..1000) {
if (index > 999) {
Log.d("for", "$index")
}
}
답 : 1000
4. 다음 코드의 실행결과를 예측해보세요.
var result = 1
while (result < 100) { result += result }
Log.d("while", "$result")
답 : 128
6.1 함수의 정의
fun 함수명(파라미터: 타입) : 반환 타입 { return 값 } |
6.3 함수 파라미터의 정의
fun 함수명( val<-생략되어있음 name1 : String, name2 : Int, name3 : Double) { 실행 코드 } |
코틀린에서의 함수 파라미터는 모두 상수 키워드 val이 생략된 형태라고 생각하면 됩니다. 즉, 파라미터 값은 변경 할 수 없습니다.
파라미터의 기본값 정의와 호출
fun 함수명(name1: String, name2: Int = 182, name3: Double) { 실행 코드 } |
파라미터 이름으로 값을 입력하기 (정의된 함수 호출시 값을 입력해서 호출 가능)
newFunction("Hello", weight = 67.5) |
- 미니 퀴즈 3-6 -
1. 2개의 Int 타입 입력 파라미터를 가지고 입력된 2개의 값을 더한 후에 반환하는 함수를 작성해보세요.
var result = plus(3, 4)
fun plus(num1 : Int, num2 : Int) : Int{
return num1 + num2
}
2. 1개의 Int 타입 입력 파라미터를 가지고 0부터 입력된 값까지 순서대로 모두 더한 후에 반환하는 함수를 작성해보세요.
var result = zeroNumPlus(10)
fun zeroNumPlus(num : Int) : Int {
var result : Int = 0
for (i in 0..num)
result += i
return result
}
3. 1개의 문자열 입력 파라미터를 가지고 입력된 값을 그대로 출력하는 함수를 작성해보세요.
printString("test")
fun printString(input : String) {
Log.i("output", "$input")
}
4. 문자열을 출력하는 함수 println("문자열")을 Log.d() 대신에 사용해보세요.
7. 클래스와 설계
7.1 클래스의 기본 구조
class 클래스명 { var 변수 fun 함수() { // 코드 } } |
기본생성자
생성자를 사용하지 않을 경우에는 생성 시 자동으로 기본 생성자가 호출되는데 기본 생성자는 파라미터가 아무것도 없는 빈 코드 블록입니다.
class Kotlin {
init {
// 생성자가 없으면 아무것도 없는 init 블록이 실행되는 것과 같습니다.
}
}
프라이머리 생성자
프라이머리(Primary)생성자는 마치 클래스의 헤더처럼 사용할 수 있으며 constructor 키워드를 사용해서 정의하는데 조건에 따라 생략할 수 있습니다. 프라이머리 생성자도 결국은 함수이기 때문에 파라미터를 사용할 수 있습니다.
class KotlinOne constructor(value: String) {
// 코드
}
생성자에 접근 제한자나 특정 옵션이 없다면 constructor 키워드를 생략할 수 있습니다.
class KotlinOne (생략)(value : String) {
// 코드
}
프라이머리 생성자는 마치 헤더처럼 class 키워드와 같은 위치에 작성되기 때문에 생성자 옆에 코드 블록을 작성할 수는 없지만, 이는 init 블록으로 대체할 수 있습니다. 클래스의 생성자가 호출되면 init 블록의 코드가 실행되고, init 블록에서는 생성자를 통해 넘어온 파라미터에 접근할 수 있습니다.
class KotlinTwo(value : String) {
init {
Log.d("class", "생성자로부터 전달받은 값은 ${values}입니다.")
}
}
세컨더리 생성자
세컨더리(Secondary) 생성자는 constructor 키워드를 마치 함수처럼 사용해서 작성할 수 있습니다.
class KotlinTwo {
constructor (value : String) {
Log.d("class", "생성자로부터 전달받은 값은 ${values}입니다.}
}
}
7.3 클래스의 사용
클래스명()
아무런 파라미터 없이 클래스명에 괄호를 붙여서 호출하면 init 블록이 있는 생성자가 호출되면서 블록 안의 코드가 자동으로 실행됩니다. 세컨더리 생성자의 경우 constructor 블록 안의 코드가 실행됩니다. (생성자에 파라미터가 있으면 값을 입력해야 합니다.)
프로퍼티와 메서드 클래스 내부에 정의되는 변수와 함수를 멤버 변수, 멤버 함수 부릅니다. 그리고 또 다른 용어로 프로퍼티, 메서드라고도 부릅니다. 클래스의 변수 > 멤버 변수 > 프로퍼티 (Property) 클래스의 함수 > 멤버 함수 > 메서드 (Method) 클래스 안에 정의된 변수는 프로퍼티라고 하지만 함수 안에 정의된 변수는 프로퍼티라고 하지 않고 그냥 변수 (또는 지역 변수) 라고 합니다. |
클래스를 인스턴스화 하지 않고 사용하기 : companion object
companion object 코드 블록을 사용하면 클래스를 생성자로 인스턴스화하지 않아도 블록 안의 프로퍼티와 메서드를 호출해서 사용할 수 있습니다.
class KotlinFour {
companion object {
var one : String = "None"
fun printOne() {
Log.d("class", "one에 입력된 값은 ${one}입니다.")
}
}
}
7.4 데이터 클래스
코틀린은 간단한 값의 저장 용도로 데이터 클래스(data class)를 제공합니다. data 예약어를 class 앞에 붙여서 만들고 클래스명 다음에 생성자처럼 파라미터를 정의할 수 있는데, 생성자와 다르게 파라미터 앞에 var 또는 val을 명시해서 변수인지 상수인지를 구분해야 합니다.
data class 클래스명 (val 파라미터1: 타입, var 파라미터2: 타입)
데이터 클래스의 정의와 생성
data class DataUser(val name: String, var age: Int)
var dataUser = DataUser("Hellow", 99)
Log.d("class", "DataUser는 ${dataUser.toString()}")
Output : DataUser는 DataUser(name="Hellow", age=99)
copy() 메서드 지원
var newData = dataUser.copy()
7.5 클래스의 상속과 확장
클래스의 상속
상속 대상이 되는 부모 클래스는 open 키워드로 만들어야만 자식 클래스에서 사용할 수 있습니다. 만약 open 키워드로 열려 있지 않으면 상속할 수 없습니다. 상속을 받을 자식 클래스에서는 콜론을 이용해서 상속할 부모 클래스를 지정합니다. 마지막으로 클래스 상속은 부모의 생성자를 호출해서 생성된 인스턴스를 자식이 갖는 과정이기 때문에 부모 클래스명 다음에 괄호를 입력해서 생성자를 호출합니다.
open class 상속될 부모 클래스 {
// 코드
}
class 자식 클래스 (value: String) : 부모 클래스(value) {
// 코드
}
생성자 파라미터가 있는 클래스의 상속
상속될 부모 클래스의 생성자에 파라미터가 있다면 자식 클래스의 생성자를 통해 값을 전달할 수 있습니다.
open class 부모 클래스 (value : String) {
// 코드
}
class 자식 클래스 (value : String) : 부모 클래스(value) {
// 코드
}
부모 클래스에 세컨더리 생성자가 있다면, 역시 자식 클래스의 세컨더리 생성자에서 super 키워드로 부모 클래스에 전달할 수 있습니다.
다음은 안드로이드의 View 클래스를 상속받는 예제입니다. 부모 클래스의 세컨더리 생성자를 이용하는 경우는 부모 클래스명 다음에 오는 괄호를 생략합니다.
class CustomView : View(생략) {
constructor(ctx: Context): super(ctx)
constructor(ctx: Context, attrs: AttributeSet): super(ctx, attrs)
}
부모 클래스의 프로퍼티와 메서드 사용하기
open class Parent {
var hello : String = "안녕하세요"
fun sayHello() {
Log.d("class", "${hello}")
}
}
class Chile : Parent() {
fun myHello() {
hello = "Hello"
sayHello()
}
}
(부모 클래스를 상속(즉, 부모 클래스의 생성자를 호출)하였기에 hello 프로퍼티와 sayHello() 메서드가 없어도 myHello() 메서드를 호출하여 "Hello"를 출력할 수 있습니다.)
프로퍼티와 메서드의 재정의 : 오버라이드
상속받은 부모 클래스의 프로퍼티와 메서드 중에 자식 클래스에서는 다른 용도로 사용해야 하는 경우가 있습니다. 동일한 이름의 메서드나 프로퍼티를 사용할 필요가 있을 경우에 override 키워드를 사용해서 재정의할 수 있습니다. 오버라이드할 때는 프로퍼티나 메서드도 클래스처럼 앞에 open을 붙여서 상속할 준비가 되어 있어야 합니다.
메서드 오버라이드
상속할 메서드 앞에 open 키워드를 붙이면 오버라이드할 수 있지만 open 키워드가 없는 메서드는 오버라이드할 수 없습니다.
open class BaseClass {
open fun opend() { }
fun notOpend() { }
}
class Child : BaseClass() {
override fun opend() { }
override fun notOpend() { // Error }
}
프로퍼티 오버라이드
메서드 오버라이드처럼 프로퍼티 역시 open으로 열려 있어야만 오버라이드를 할 수 있습니다.
open class BaseClass2 {
open var opend: String = "I am"
}
class ChildClass2 : BaseClass2() {
override var opend: String = "You are"
}
익스텐션
코틀린은 클래스, 메서드, 프로퍼티에 대해 익스테션(extensions)을 지원합니다. 이미 만들어져 있는 클래스에 다음과 같은 형태로 메서드를 추가할 수 있습니다.
fun 클래스.확장할 함수() {
// 코드
}
상속이 미리 만들어져 있는 클래스를 가져다 쓰는 개념이라면 익스텐션은 미리 만들어져 있는 클래스에 메서드를 붙여넣는 개념입니다.
class MyClass {
fun say()
fun walk()
fun eat()
}
// fun sleep()을 추가하고 싶다면?
MyClass.sleep() {
// 실행 코드
}
7.6 설계 도구
패키지
패키지(Package)는 클래스와 소스 파일을 관리하기 위한 디렉터리 구조의 저장 공간입니다.
package 메인 디렉터리.서브 디렉터리
class 클래스이름 {
}
추상화
abstract키워드를 사용해서 클래스의 이름과 클래스 안에 있음 직한 기능들을 유추해서 메서드 이름으로 나열한 것을 의미합니다. 추상클래스 자체는 인스턴스화가 되지 않기 때문에 구현 단계에서 상속하여 잘 설계 해야 합니다.
인터페이스
추상화와 비교하면 가장 명확하게 이해할 수 있는데 실행 코드 없이 메서드 이름만 가진 추상 클래스라고 생각해도 무방합니다. 즉, 누군가 설계해놓은 개념 클래스 중에 실행 코드가 한 줄이라도 있으면 추상화, 코드 없이 메서드 이름만 나열되어 있으면 인터페이스입니다.
인터페이스는 상속 관계의 설계보다는 외부 모듈에서 내가 만든 모듈을 사용할 수 있도록 메서드의 이름을 나열해둔 일종의 명세서로 제공됩니다.
interface 목록 {
fun say()
fun walk()
fun eat()
}
class 내부 모듈 {
fun 처리(목록) {
}
}
class 외부 모듈 {
fun 처리() {
}
}
인터페이스는 interface 예약어를 사용해서 정의할 수 있고 인터페이스에 정의된 메서드를 오버라이드해서 구현할 수 있습니다.
interface 인터페이스명 {
var 변수: String
fun 함수1()
fun 함수2()
}
인터페이스 만들기
interface 예약어로 인터페이스를 정의합니다. 코틀린은 인터페이스 내부에 프로퍼티도 정의 할 수 있습니다. 메서드는 코드 블록 없이 이름만 작성해놓습니다. 인터페이스의 프로퍼티와 메서드 앞에는 abstract 키워드가 생략된 형태입니다.
interface InterfaceKotlin {
(생략)var variable: String
(생략)fun get()
fun set()
}
(abstract 생략)
클래스에서 구현하기
인터페이스를 클래스에서 구현할 때는 상속과는 다르게 생성자를 호출하지 않고 인터페이스 이름만 지정해주면 됩니다.
class KotlinImpl : InterfaceKotlin {
override var variable: String = "init value"
override fun get() {
// 코드 구현
}
override fun set() {
// 코드 구현
}
}
인터페이스를 클래스의 상속 형태가 아닌 소스 코드에서 직접 구현할 때도 있는데, object 키워드를 사용해서 구현해야 합니다. 실제로 안드로이드 프로젝트를 시작하면 자주 사용하는 형태입니다.
var kotlinImpl = object : InterfaceKotlin {
override var variable: String = "init"
override fun get() {
// 코드
}
override fun set() {
// 코드
}
}
인터페이스의 효율적인 사용 인터페이스는 외부의 다른 모듈을 위한 의사소통 방식을 정의하는 것입니다. 혼자 개발하거나 소수의 인원이 하나의 모듈 단위를 개발할 때는 인터페이스를 사용하지 않는 것이 좋습니다. 인터페이스를 남용하면 코드의 가독성과 구현 효율성이 떨어지기 때문입니다. 안드로이드가 제공하는 인터페이스를 자주 사용하는 이유는 안드로이드가 보았을 때 개발자 만드는 모듈이 외부 모듈이기 때문입니다. 코틀린에서 모듈이란? 코틀린에서 모듈이란 한 번에 같이 컴파일되는 모든 파일을 말합니다. 안드로이드를 예로 든다면 하나의 앱이 하나의 모듈이 될 수 있습니다. 또한 라이브러리도 하나의 모듈입니다. |
접근 제한자
코틀린에서 정의되는 클래스, 인터페이스, 함수, 변수는 모두 접근 제한자(Visibility Modifiers)를 가질 수 있습니다.
함수형 언어라는 특성 때문에 코틀린은 기존 객체지향에서 접근 제한자의 기준으로 삼았던 패키지 대신에 모듈 개념이 도입되었습니다. internal 접근 제한자로 모듈 간에 접근을 제한할 수 있습니다.
접근 제한자의 종류
각 클래스, 변수 이름 앞에 아무런 예약어가 붙이지 않았을 때는 기본적으로 public 접근 제한자가 적용됩니다.
접근 제한자 | 제한 범위 |
private | 다른 파일에서 접근할 수 없습니다. |
internal | 같은 모듈에 있는 파일만 접근할 수 있습니다. |
protected | private와 같으나 상속 관계에서 자식 클래스가 접근할 수 있습니다. |
public | 제한 없이 모든 파일에서 접근할 수 있습니다. |
접근 제한자의 적용
접근 제한자를 붙이면 해당 클래스, 멤버 프로퍼티 또는 메서드에 대한 사용이 제한됩니다.
open class Parent {
private val privateVal = 1
protected open val protectedVal = 2
internal val internalVal = 3
val defaultVal = 4
}
자식 클래스에서 부모 클래스를 상속받고 테스트합니다.
[1] Parent class 상속
class Child : Parent() {
fun callVariables() {
// privateVal은 호출이 x
Log.d("Modifier", "protected 변수의 값은 ${protectedVal}")
Log.d("Modifier", "internal 변수의 값은 ${internalVal}")
Log.d("Modifier", "기본 제한자 변수 defaultVal의 값은 ${defaultVal}")
}
}
- private 멤버이기 때문에 privateVal은 접근할 수 없습니다.
- protected 멤버 protectedVal은 상속 관계이므로 접근할 수 있습니다.
- internal 멤버 internalVal은 동일한 모듈이므로 접근할 수 있습니다.
- 접근 제한자가 없는 멤머 defaultVal에는 public이 적용되어 접근할 수 있습니다.
[2] 외부 클래스에서 Parent 클래스를 생성
class Stranger {
fun callVariables {
val parent = Parent()
Log.d("Modifier", "internal 변수의 값은 ${parent.internalVal}입니다.")
Log.d("Modifier", "public 변수의 값은 ${parent.defaultVal}입니다.")
}
}
- internal멤버는 동일 모듈로 접근이 가능하며 protected멤버는 상속 관계에서만 쓸수 있기에 접근 불가능합니다.
제네릭
제네릭(Generics)은 입력되는 값의 타입을 자유롭게 사용하기 위한 설계 도구입니다.
public class MutableList<E> {
var list: Array<E>
}
클래스명 앞에 <E>라고 되어 있는 부분에 String과 같은 특정 타입이 지정되면 클래스 내부에 선언된 모든 E에 String이 타입으로 지정됩니다. 결과적으로 var list: Array<E>가 var list:Array<String>으로 변경되는 것입니다.
위와 같이 설계된 클래스를 우리는 주로 구현하는 용도로 사용하며 컬렉션이나 배열에서 입력되는 값의 타입을 특정하기 위해 다음과 같이 사용합니다.
var list: MutableList<제네릭> = mutableListOf("월", "화", "수")
fun testGenerics() {
// String을 제네릭으로 사용했기 때문에 list 변수에는 문자열만 담을 수 있습니다.
var list: MutableList<String> = mutableListOf()
list.add("월")
list.add("화")
list.add("수")
// list.add(35) // <- 입력 오류가 발생
// String 타입의 item 변수로 꺼내서 사용할 수 있습니다.
for (iten in list)
Log.d("Generic", "list에 입려된 값은 ${item}입니다.")
}
- 요약 -
클래스(class): 변수와 함수의 모음으로, 연관성 있는 코드를 그룹화하고 이름을 매긴 것입니다.
construtor: 클래스를 사용하기 위해서 호출하는 일종의 함수입니다.
init: 기본 생성자를 호출하면 실행되는 코드 블록입니다.
프로퍼티(Property): 클래스에 정의된 변수를 프로퍼티 또는 멤버 변수라고 합니다.
메서드(Method): 클래스에 정의된 함수를 메서드 또는 멤버 함수라고 합니다.
컴페니언 오브젝트(companion object): 컴패니언 오브젝트 블록 안에 변수와 함수를 정의하면 생성자를 통하지 않고 클래스의 멤버들을 사용할 수 있습니다.
상속: 코드를 재사용하기 위한 설계 도구입니다. 상속 관계에서 자식 클래스는 부모 클래스의 멤버들을 자신의 것처럼 사용할 수 있습니다.
추상화(abstract): 클래스를 개념 설계하기 위한 도구입니다.
인터페이스(Interface): 외부 모듈에 제공하기 위해서 메서드 이름을 나열한 명세서입니다.
패키지(package): 연관성 있는 클래스들을 분류하기 위한 디렉터리 구조입니다.
접근 제한자: 클래스의 멤버에 지정된 접근 제한자에 따라 외부에서 사용 여부가 결정됩니다.
제네릭(Generic): 타입을 특정해서 안정성을 유지하기 위한 설계 도구입니다.
- 미니 퀴즈 3-7 -
1. 클래스의 멤버 변수와 멤버 함수를 지칭하는 용어는 무엇인가요?
답 : 프로퍼티(Property), 메서드(Method)
2. 클래스를 사용하기 위해 호출되는 함수는 무엇인가요?
답 : constructor
3. 생성자를 통하지 않고 클래스의 멤버를 사용할 수 있게 해주는 키워드는 무엇인가요?
답 : companion object { // 멤버 변수, 함수 }
4. 상속 관계에서 자식이 부모의 멤버에 접근하는 것을 제한하는 것을 무엇이라고 하나요?
답 : protected 접근 제한자
5. 접근 제한자에는 어떤 것이 있나요?
답 : private, public, internal, protected
6. 기본적으로 아무런 접근 제한자도 지정하지 않으면 어떻게 동작하나요?
답 : public 접근제한자로 제한 없이 파일에서 접근할 수 있습니다.
7. 클래스를 설계하는 데 있어서 클래스 내부에 코드를 작성할 수 있는 설계 도구는 무엇인가요?
답 : 추상화
8. null 값에 대한 안정적인 처리 Null Safety
8.1 null 값 허용하기 : Nullable
코틀린에서 지정하는 기본 변수는 모두 null이 입력되지 않습니다. null 값을 입력하기 위해서는 변수를 선언할 때 타입 뒤에 ? (Nullable, 물음표)를 입력합니다.
var variable: String?
변수에 null 허용 설정하기
변수의 타입 뒤에 물음표를 붙이지 않으면 null값을 입력할수 없습니다. null 예외를 발생시키고 싶지 않다면 기본형으로 선언합니다.
var nullable: String? // 타입 다음에 물음표를 붙여서 null 값을 입력할 수 있습니다.
nullable = null
var notNullable: String
notNullable = null // 일반 변수에는 null을 입력할 수 없습니다.
함수 파라미터, 리턴 타입에 null 허용 설정하기
안드로이드 onCreate() 메서드의 Bundle 파라미터처럼 함수의 파라미터에도 null 허용 여부를 설정할 수 있습니다. 함수의 파라미터가 null을 허용할 경우는 코드 내부에서 해당 파라미터에 대해서 null 체크를 먼저 해야만 사용할 수 있습니다.
fun nullParameter(str: String?) {
if (str != null) {
var length2 = str.length
}
}
fun nullReturn() : String? {
return null
}
8.2 안전한 호출 : ?.
Nullable인 변수 다음에 ?.(Safe Call, 물음표와 온점)을 사용하면 해당 변수가 null일 경우 ?. 다음의 메서드나 프로퍼티를 호출하지 않습니다. 아래 코드에서처럼 문자열의 길이를 반환하는 length 프로퍼티를 호출했는데 str 변수 자체가 null일 경우는 length 프로퍼티를 호출하지 않고 바로 null을 반환합니다.
fun testSafeCall(str: String?): Int? {
// str이 null이면 length를 체크하지 않고 null을 반환합니다.
var resultNull: Int? = str?.length
return resultNull
}
(Safe Call을 하지 않고 str변수가 null이면 프로그램이 다운됩니다.)
8.3 Null 값 대체하기 : ?:
앞서 안전한 호출을 위해서 ?.(Safe Call)을 사용했는데 이제는 ?: (Elvis Operator, 물음표와 콜론)을 사용해서 원본 변수가 null일 때 넘겨줄 기본값을 설정해보겠습니다.
다음 코드에서 Safe call 다음에 호출되는 프로퍼티 뒤에 다시 ?: 을 붙였습니다. 그리고 0이라는 값을 표시했습니다. 이렇게 호출되면 str변수가 null일 경우 가장 뒤에 표시한 0을 반환합니다.
fun testElvis(str: String?): Int {
// length 오른쪽에 ?:을 사용하면 null일 경우 ?: 오른쪽의 값이 반환됩니다.
var resultNonNull: Int = str?.length?:0
return reslutNonNull
}
(즉, str변수가 null이면 Safe Call을 하여 해당 length 프로퍼티를 호출하지않고 바로 null을 반환하는데 Elvis Operator을 사용해서 뒤에있는 0변수가 반환됩니다.)
Nullable(?), Safecall(?.), Elvis operator(?:)를 구분하는 법 Nullable ▶ 선언하는 변수의 타입 다음에 물음표(?) ▶ null을 입력받기 위해 사용 ▶ var nullable : 타입? Safecall ▶ 선언된 변수의 이름 다음에 물음표(?)와 도트 연산자(.) ▶ null일 때 ?. 다음에 나오는 속성이나 명령어를 처리하지 않기 위해 사용 ▶ var result = 변수?.length 또는 변수?.프로퍼티?.something Elvis operator ▶ 선언된 변수의 이름 다음에 물음표(?)와 콜론(:) ▶ null일 때 ?: 다음에 나오는 값을 기본값으로 사용 ▶ var result = 변수?:0 또는 변수?.프로퍼티?:0 (★ 엘비스 오퍼레이터는 유명한 가수 엘비스 프레슬리의 이모티콘에서 유래된 것으로 알려져 있습니다. 코틀린뿐만 아니라 C++, PHP, C#, SQL 등 다른 언어에서도 이미 지원하고 있는데 언어마다 용도는 조금씩 다릅니다.) |
- 미니 퀴즈 3-8 -
1. 아래 코드의 결과값은 무엇인가요?
var nullable: String? = null
var size = nullable.length
Log.d("Nullable", "문자열의 길이 = $size")
답 : 컴파일 되지 않습니다.(오류 발생) 정상적으로 처리하고 싶다면 두가지 방법이 있습니다. 먼저 size변수를 nullable 변수로 설정하여 Safecall을 사용하거나 Elvis Operator를 사용하여 해당 null변수의 길이를 구하려고 할 시 특정 값으로 저장되게 합니다.
// [1] SafeCall
var size: Int? = nullable?.length
// [2]수정 -- 이렇게만 해도 안드로이드 스튜디오에서 자동적으로 size의 타입을 Int?로 인식
// [1]과 [2]는 같은 코드입니다 :)
var size = nullable?.length
// [3] Elvis Operator
var size: Int? = nullable?.length?:0
2. 아래 코드의 예상되는 결과값은 무엇인가요?
var nullable: String? = null
var size = nullable.length
Log.d("Nullable", "문자열의 길이 = $size")
해당 문제가 잘못 표기됬습니다.
var size = nullable.length 가 아니라
var size = nullable?.length 입니다.
답 : 문자열의 길이 = null
3. 아래 코드의 예상되는 결과값은 무엇인가요?
var nullable: String? = null
var size = nullable.length?:33
Log.d("Nullable", "문자열의 길이 = $size")
문제 표기가 잘못 된것 같습니다.
var size = nullable.length?:33 이 아니라
var size = nullable?.length?:33인것 같습니다.
답 : 33
이상 마치겠습니다 :)
'책 요약하기 > 이것이 안드로이드다' 카테고리의 다른 글
#5-2. 컨테이너 2021-03-12 (0) | 2021.03.12 |
---|---|
#5-1. 화면 구성하기 2021-03-12 (0) | 2021.03.12 |
#4. 위젯과 리소스 다루기 2021-03-06 (0) | 2021.03.06 |
#2. 개발도구 설치와 앱 실행하기 (0) | 2021.02.28 |
#0. Preview (0) | 2021.02.28 |