1. 시작하기 전에
사람들은 보통 매일 살아가면서 할 일 목록이나 행사 참석자 목록, 위시리스트, 식료품 목록 등 다양한 종류의 목록을 만듭니다. 프로그래밍에서도 목록은 매우 유용합니다. 예를 들어 앱 내에 뉴스 기사나 노래, 캘린더 일정, 소셜 미디어 게시물 목록이 있을 수 있습니다.
목록을 만들고 사용하는 방법을 아는 것은 도구 상자에 추가할 중요한 프로그래밍 개념이며 이를 통해 더 정교한 앱을 만들 수 있습니다.
이 Codelab에서는 Kotlin 플레이그라운드를 사용하여 Kotlin의 목록을 숙지하고 다양한 국수를 주문하는 프로그램을 만듭니다. 준비되셨나요?
기본 요건
- Kotlin 플레이그라운드를 사용하여 Kotlin 프로그램을 만들고 수정하는 방법을 잘 알고 있어야 합니다.
main()
함수, 함수 인수 및 반환 값, 변수, 데이터 유형, 작업, 제어 흐름 문 등 Kotlin의 Android 기본사항 과정 1단원의 기본 Kotlin 프로그래밍 개념을 잘 알고 있어야 합니다.- Kotlin 클래스를 정의하고 그 클래스에서 객체 인스턴스를 만들어 속성과 메서드에 액세스할 수 있어야 합니다.
- 서브클래스를 만들고 서브클래스가 서로 상속하는 방법을 파악할 수 있어야 합니다.
학습할 내용
- Kotlin에서 목록을 만들고 사용하는 방법
List
와MutableList
의 차이점과 각각을 사용하는 시점- 목록의 모든 항목을 반복하고 각 항목에 관해 작업을 실행하는 방법
빌드할 항목
- Kotlin 플레이그라운드에서 목록과 목록 작업을 시도합니다.
- Kotlin 플레이그라운드에서 목록을 사용하는 음식 주문 프로그램을 만듭니다.
- 프로그램을 통해 주문을 만들고 국수와 채소를 추가한 다음 총 주문 비용을 계산할 수 있습니다.
필요한 항목
- Kotlin 플레이그라운드에 액세스할 인터넷이 연결된 컴퓨터
2. 목록 소개
이전 Codelab에서는 Int
, Double
, Boolean
, String
과 같은 Kotlin의 기본 데이터 유형을 알아봤습니다. 이러한 유형을 통해 변수 내에 특정 유형의 값을 저장할 수 있습니다. 그러나 값을 둘 이상 저장하려면 어떻게 해야 하나요? 이때 List
데이터 유형이 있으면 유용합니다.
목록은 특정 순서가 있는 항목의 모음입니다. Kotlin의 목록 유형에는 두 가지가 있습니다.
- 읽기 전용 목록:
List
는 만든 후 수정할 수 없습니다. - 변경 가능한 목록:
MutableList
는 만든 후 수정할 수 있습니다. 즉, 요소를 추가하거나 삭제, 업데이트할 수 있습니다.
List
나 MutableList
를 사용할 때는 포함될 수 있는 요소 유형을 지정해야 합니다. 예를 들어 List<Int>
에는 정수 목록이 있고 List<String>
에는 문자열 목록이 있습니다. 프로그램에서 Car
클래스를 정의하면 Car
객체 인스턴스 목록이 있는 List<Car>
를 보유할 수 있습니다.
목록을 이해하는 가장 좋은 방법은 직접 사용해보는 것입니다.
목록 만들기
- Kotlin 플레이그라운드를 열고 제공된 기존 코드를 삭제합니다.
- 빈
main()
함수를 추가합니다. 다음 코드 단계는 모두 이main()
함수 안에 들어갑니다.
fun main() {
}
main()
내부에서List<Int>
유형의numbers
라는 변수를 만듭니다. 읽기 전용 정수 목록이 포함되기 때문입니다. Kotlin 표준 라이브러리 함수listOf()
를 사용하여 새List
를 만들고 목록의 요소를 쉼표로 구분된 인수로 전달합니다.listOf(1, 2, 3, 4, 5, 6)
은 1에서 6까지 읽기 전용 정수 목록을 반환합니다.
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
- 변수 유형을 할당 연산자(=) 오른쪽에 있는 값에 기반하여 추측하거나 추론할 수 있으면 변수의 데이터 유형을 생략할 수 있습니다. 따라서 이 코드 줄을 다음과 같이 줄일 수 있습니다.
val numbers = listOf(1, 2, 3, 4, 5, 6)
println()
을 사용하여numbers
목록을 출력합니다.
println("List: $numbers")
문자열에 $를 넣으면 평가되어 이 문자열에 추가될 표현식이 뒤따릅니다(문자열 템플릿 참고). 이 코드 줄은 다음과 같이 작성할 수도 있습니다. println("List: " + numbers).
numbers.size
속성을 사용하여 목록의 크기를 검색하고 이 또한 출력합니다.
println("Size: ${numbers.size}")
- 프로그램을 실행합니다. 목록의 모든 요소 목록과 목록의 크기가 출력됩니다. 대괄호
[]
는List
임을 나타냅니다. 대괄호 안에는 쉼표로 구분된numbers
요소가 있습니다. 또한 요소의 순서는 요소를 만든 순서와 같습니다.
List: [1, 2, 3, 4, 5, 6] Size: 6
액세스 목록 요소
목록 관련 기능은 위치를 나타내는 정수인 색인을 통해 목록의 각 요소에 액세스할 수 있다는 것입니다. 다음은 각 요소와 이에 상응하는 색인을 보여주는 numbers
목록의 다이어그램입니다.
색인은 실제로 첫 번째 요소의 오프셋입니다. 예를 들어 list[2]
의 경우 목록의 두 번째 요소를 요청하는 것이 아니라 첫 번째 요소에서 오프셋 위치가 2인 요소를 요청하는 것입니다. 따라서 list[0]
은 첫 번째 요소(오프셋 0)이고 list[1]
은 두 번째 요소(오프셋 1), list[2]
는 세 번째 요소(오프셋 2), 이런 식으로 진행됩니다.
main()
함수에서 기존 코드 뒤에 다음 코드를 추가합니다. 각 단계가 끝난 후에 코드를 실행하여 예상대로 출력되는지 확인할 수 있습니다.
- 색인 0에 목록의 첫 번째 요소를 출력합니다. 원하는 색인과 함께
get()
함수를numbers.get(0)
으로 호출하거나 색인을 대괄호로 묶은 짧은 구문을numbers[0]
으로 사용할 수 있습니다.
println("First element: ${numbers[0]}")
- 다음으로 색인 1에 목록의 두 번째 요소를 출력합니다.
println("Second element: ${numbers[1]}")
목록의 유효한 색인값('색인')은 0에서 마지막 색인(목록 크기에서 1을 뺀 값)까지 이어집니다. 즉, numbers
목록의 경우 색인은 0에서 5까지입니다.
- 목록의 마지막 요소를 출력하고
numbers.size - 1
을 사용하여 색인을 계산하면5
가 됩니다. 다섯 번째 색인의 요소에 액세스하면 출력으로6
이 반환됩니다.
println("Last index: ${numbers.size - 1}")
println("Last element: ${numbers[numbers.size - 1]}")
- Kotlin은 목록에서
first()
및last()
작업도 지원합니다.numbers.first()
와numbers.last()
를 호출하여 출력을 확인해보세요.
println("First: ${numbers.first()}")
println("Last: ${numbers.last()}")
numbers.first()
가 목록의 첫 번째 요소를 반환하고 numbers.last()
는 목록의 마지막 요소를 반환합니다.
- 또 다른 유용한 목록 작업은 주어진 요소가 목록에 있는지 확인하는
contains()
메서드입니다. 예를 들어 회사 직원 이름 목록이 있다면contains()
메서드를 사용하여 특정 이름이 목록에 있는지 확인할 수 있습니다.
numbers
목록에서 목록에 있는 정수 중 하나와 함께 contains()
메서드를 호출합니다. numbers.contains(4)
는 true
값을 반환합니다. 그런 다음 목록에 없는 정수와 함께 contains()
메서드를 호출합니다. numbers.contains(7)
은 false
를 반환합니다.
println("Contains 4? ${numbers.contains(4)}")
println("Contains 7? ${numbers.contains(7)}")
- 완성된 코드는 다음과 같이 표시됩니다. 주석은 선택사항입니다.
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6)
println("List: $numbers")
println("Size: ${numbers.size}")
// Access elements of the list
println("First element: ${numbers[0]}")
println("Second element: ${numbers[1]}")
println("Last index: ${numbers.size - 1}")
println("Last element: ${numbers[numbers.size - 1]}")
println("First: ${numbers.first()}")
println("Last: ${numbers.last()}")
// Use the contains() method
println("Contains 4? ${numbers.contains(4)}")
println("Contains 7? ${numbers.contains(7)}")
}
- 코드를 실행합니다. 다음과 같이 출력됩니다.
List: [1, 2, 3, 4, 5, 6] Size: 6 First element: 1 Second element: 2 Last index: 5 Last element: 6 First: 1 Last: 6 Contains 4? true Contains 7? false
읽기 전용인 목록
- Kotlin 플레이그라운드에서 코드를 삭제하고 다음 코드로 바꿉니다.
colors
목록은Strings
로 표시되는 3가지 색상 목록으로 초기화됩니다.
fun main() {
val colors = listOf("green", "orange", "blue")
}
- 읽기 전용
List
에서는 요소를 추가하거나 변경할 수 없습니다. 목록에 항목을 추가하려고 하거나 새 값과 같도록 설정하여 목록의 요소를 수정하려고 하면 어떻게 되는지 알아보세요.
colors.add("purple")
colors[0] = "yellow"
- 코드를 실행하면 오류 메시지가 여러 개 표시됩니다. 본질적으로 오류는
add()
메서드가List
에 없고 요소의 값을 변경할 수 없음을 나타냅니다.
- 잘못된 코드를 삭제합니다.
읽기 전용 목록을 변경할 수 없는 것을 직접 확인했습니다. 그러나 목록을 변경하지는 않지만 새 목록을 반환하는 여러 작업이 목록에 있습니다. 이 중 두 개가 reversed()
와 sorted()
입니다. reversed()
함수는 요소가 역순으로 있는 새 목록을 반환하고 sorted()
는 요소가 오름차순으로 정렬된 새 목록을 반환합니다.
colors
목록을 역전시키는 코드를 추가합니다. 내용을 출력합니다.colors
요소가 역순으로 포함되는 새 목록입니다.- 두 번째 코드 줄을 추가하여 원래
list
를 출력하면 원래 목록이 변경되지 않은 것을 확인할 수 있습니다.
println("Reversed list: ${colors.reversed()}")
println("List: $colors")
- 출력된 두 목록은 다음과 같이 표시됩니다.
Reversed list: [blue, orange, green] List: [green, orange, blue]
sorted()
함수를 사용하여 정렬된 버전의List
를 반환하는 코드를 추가합니다.
println("Sorted list: ${colors.sorted()}")
알파벳순으로 정렬된 새로운 색상 목록이 출력됩니다. 훌륭합니다.
Sorted list: [blue, green, orange]
- 정렬되지 않은 숫자 목록에서
sorted()
함수를 사용해 볼 수도 있습니다.
val oddNumbers = listOf(5, 3, 7, 1)
println("List: $oddNumbers")
println("Sorted list: ${oddNumbers.sorted()}")
List: [5, 3, 7, 1] Sorted list: [1, 3, 5, 7]
이제 목록 만들기의 유용성을 알 수 있습니다. 그러나 목록을 만들고 나서 수정할 수 있으면 더 좋으므로 이제는 변경 가능한 목록을 살펴보겠습니다.
3. 변경 가능한 목록 소개
변경 가능한 목록은 만든 후에 수정할 수 있는 목록입니다. 항목을 추가하거나 삭제, 변경할 수 있습니다. 읽기 전용 목록으로 할 수 있는 작업도 모두 할 수 있습니다. 변경 가능한 목록은 MutableList
유형이고 mutableListOf()
를 호출하여 만들면 됩니다.
MutableList 만들기
main()
에서 기존 코드를 삭제합니다.main()
함수 내에서 변경 가능한 빈 목록을 만들어entrees
라는val
변수에 할당합니다.
val entrees = mutableListOf()
코드를 실행하려고 하면 다음 오류가 발생합니다.
Not enough information to infer type variable T
앞서 언급했듯이 MutableList
나 List
를 만들 때 Kotlin은 전달된 인수에서 목록에 포함된 요소의 유형을 추론합니다. 예를 들어 listOf("noodles")
를 작성하면 Kotlin은 개발자가 String
목록을 만들려는 것으로 추론합니다. 요소 없이 빈 목록을 초기화하면 Kotlin은 요소의 유형을 추론할 수 없으므로 유형을 명시적으로 표시해야 합니다. mutableListOf
나 listOf
바로 뒤에 유형을 꺾쇠괄호로 묶어 추가하면 됩니다. 문서에서는 <T>
로 이를 확인할 수 있습니다. 여기서 T
는 type 매개변수를 나타냅니다.
- 변수 선언을 수정하여 변경 가능한
String
유형 목록을 만들려고 한다고 지정합니다.
val entrees = mutableListOf<String>()
오류를 수정할 수 있는 또 다른 방법은 변수의 데이터 유형을 미리 지정하는 것입니다.
val entrees: MutableList<String> = mutableListOf()
- 목록을 출력합니다.
println("Entrees: $entrees")
- 빈 목록의 경우
[]
가 표시되어 출력됩니다.
Entrees: []
목록에 요소 추가
변경 가능한 목록은 요소를 추가하고 삭제, 업데이트할 때 흥미로워집니다.
entrees.add("noodles").
를 사용하여 목록에"noodles"
를 추가합니다.add()
함수는 목록에 요소가 성공적으로 추가되면true
를 반환하고 추가되지 않으면false
를 반환합니다.- 목록을 출력하여
"noodles"
가 실제로 추가되었는지 확인합니다.
println("Add noodles: ${entrees.add("noodles")}")
println("Entrees: $entrees")
출력은 다음과 같습니다.
Add noodles: true Entrees: [noodles]
- 목록에 다른 항목
"spaghetti"
를 추가합니다.
println("Add spaghetti: ${entrees.add("spaghetti")}")
println("Entrees: $entrees")
결과 entrees
목록에는 이제 항목이 두 개 있습니다.
Add spaghetti: true Entrees: [noodles, spaghetti]
add()
를 사용하여 요소를 하나씩 추가하는 대신 addAll()
을 사용하여 한 번에 여러 요소를 추가하고 목록을 전달할 수 있습니다.
moreItems
목록을 만듭니다. 변경하지 않아도 되므로val
과 변경 불가능으로 만듭니다.
val moreItems = listOf("ravioli", "lasagna", "fettuccine")
addAll()
를 사용하여 새 목록의 항목을 모두entrees
에 추가합니다. 결과 목록을 출력합니다.
println("Add list: ${entrees.addAll(moreItems)}")
println("Entrees: $entrees")
목록이 성공적으로 추가되었다고 표시됩니다. 이제 entrees
목록에는 항목이 총 5개 있습니다.
Add list: true Entrees: [noodles, spaghetti, ravioli, lasagna, fettuccine]
- 이제 이 목록에 숫자를 추가해보세요.
entrees.add(10)
다음 오류가 표시되면서 실패합니다.
The integer literal does not conform to the expected type String
entrees
목록에서는 String
유형 요소를 예상하는데 개발자는 Int
를 추가하려고 하기 때문입니다. 올바른 데이터 유형의 요소만 목록에 추가해야 합니다. 그러지 않으면 컴파일 오류가 발생합니다. 이는 Kotlin이 유형 안전성으로 코드를 더 안전하게 보호하는 한 가지 방법입니다.
- 잘못된 코드 줄을 삭제하면 코드가 컴파일됩니다.
목록에서 요소 삭제
remove()
를 호출하여 목록에서"spaghetti"
를 삭제합니다. 목록을 다시 출력합니다.
println("Remove spaghetti: ${entrees.remove("spaghetti")}")
println("Entrees: $entrees")
"spaghetti"
를 삭제하면 true가 반환됩니다. 요소가 목록에 있어서 성공적으로 삭제할 수 있기 때문입니다. 이제 목록에 남은 항목은 4개뿐입니다.
Remove spaghetti: true Entrees: [noodles, ravioli, lasagna, fettuccine]
- 목록에 없는 항목을 삭제하려고 하면 어떻게 되나요?
entrees.remove("rice")
를 사용하여 목록에서"rice"
를 삭제해보세요.
println("Remove item that doesn't exist: ${entrees.remove("rice")}")
println("Entrees: $entrees")
remove()
메서드가 false
를 반환합니다. 요소가 없어서 삭제할 수 없기 때문입니다. 목록은 항목이 여전히 4개뿐인 채로 변경되지 않고 유지됩니다. 출력:
Remove item that doesn't exist: false Entrees: [noodles, ravioli, lasagna, fettuccine]
- 삭제할 요소의 색인을 지정할 수도 있습니다.
removeAt()
을 사용하여 색인0
에서 항목을 삭제합니다.
println("Remove first element: ${entrees.removeAt(0)}")
println("Entrees: $entrees")
removeAt(0)
의 반환 값은 목록에서 삭제된 첫 번째 요소("noodles"
)입니다. 이제 entrees
목록에 남은 항목은 3개입니다.
Remove first element: noodles Entrees: [ravioli, lasagna, fettuccine]
- 전체 목록을 삭제하려면
clear()
를 호출하면 됩니다.
entrees.clear()
println("Entrees: $entrees")
이제 빈 목록이 출력됩니다.
Entrees: []
- Kotlin에서는
isEmpty()
함수를 사용하여 목록이 비어 있는지 확인할 수 있습니다.entrees.isEmpty().
를 출력해보세요.
println("Empty? ${entrees.isEmpty()}")
현재 포함된 요소가 없어 목록이 비어 있으므로 true가 출력됩니다.
Empty? true
isEmpty()
메서드는 목록에서 작업을 실행하거나 특정 요소에 액세스하려고 하지만 먼저 목록이 비어 있지 않은지 확인하고 싶을 때 유용합니다.
변경 가능한 목록을 위해 작성한 모든 코드는 다음과 같습니다. 주석은 선택사항입니다.
fun main() {
val entrees = mutableListOf<String>()
println("Entrees: $entrees")
// Add individual items using add()
println("Add noodles: ${entrees.add("noodles")}")
println("Entrees: $entrees")
println("Add spaghetti: ${entrees.add("spaghetti")}")
println("Entrees: $entrees")
// Add a list of items using addAll()
val moreItems = listOf("ravioli", "lasagna", "fettuccine")
println("Add list: ${entrees.addAll(moreItems)}")
println("Entrees: $entrees")
// Remove an item using remove()
println("Remove spaghetti: ${entrees.remove("spaghetti")}")
println("Entrees: $entrees")
println("Remove item that doesn't exist: ${entrees.remove("rice")}")
println("Entrees: $entrees")
// Remove an item using removeAt() with an index
println("Remove first element: ${entrees.removeAt(0)}")
println("Entrees: $entrees")
// Clear out the list
entrees.clear()
println("Entrees: $entrees")
// Check if the list is empty
println("Empty? ${entrees.isEmpty()}")
}
4. 목록 반복
목록의 각 항목에 관해 작업을 실행하려면 목록을 순환하면 됩니다(목록을 반복한다고도 함). 루프는 Lists
및 MutableLists
와 함께 사용할 수 있습니다.
while 루프
루프의 한 유형이 while
루프입니다. while
루프는 Kotlin에서 while
키워드로 시작됩니다. 괄호 안의 표현식이 true인 한 계속해서 반복 실행되는 코드 블록이 중괄호 안에 포함되어 있습니다. 코드가 영구적으로 실행(무한 루프라고 함)되지 않도록 하려면 코드 블록에 표현식의 값을 변경하는 로직을 포함하여 최종적으로 표현식이 false가 되고 루프 실행이 중지되도록 해야 합니다. 이 시점에서 while
루프를 종료하고 루프 뒤에 오는 코드를 계속 실행합니다.
while (expression) {
// While the expression is true, execute this code block
}
while
루프를 사용하여 목록을 반복합니다. 목록에서 현재 보고 있는 index
를 추적하는 변수를 만듭니다. 이 index
변수는 목록의 마지막 색인에 도달할 때까지 매번 1씩 증분한 후 루프를 종료합니다.
- Kotlin 플레이그라운드에서 기존 코드를 삭제하고 빈
main()
함수를 보유합니다. - 파티를 연다고 가정해보겠습니다. 각 요소가 각 가족에서 응답한 참석자 수를 나타내는 목록을 만듭니다. 첫 번째 가족은 가족 중 2명이 참석한다고 했습니다. 두 번째 가족은 4명이 참석한다고 했습니다. 이런 식으로 계속 이어집니다.
val guestsPerFamily = listOf(2, 4, 1, 3)
- 총 참석자 수가 얼마가 될지 파악합니다. 루프를 작성하여 답을 알아보세요. 총 참석자 수의
var
을 만들어0
으로 초기화합니다.
var totalGuests = 0
- 앞에서 설명한 대로
index
변수의var
을 초기화합니다.
var index = 0
while
루프를 작성하여 목록을 반복합니다. 조건은index
값이 목록의 크기보다 작으면 코드 블록을 계속 실행하는 것입니다.
while (index < guestsPerFamily.size) {
}
- 루프 내에서 현재
index
의 목록 요소를 가져와 총 참석자 수 변수에 추가합니다.totalGuests += guestsPerFamily[index]
는totalGuests = totalGuests + guestsPerFamily[index].
와 같습니다.
루프의 마지막 줄은 index++
를 사용하여 index
변수를 1씩 증분하므로 다음 루프 반복이 목록의 다음 가족을 살펴보게 됩니다.
while (index < guestsPerFamily.size) {
totalGuests += guestsPerFamily[index]
index++
}
while
루프 후 결과를 출력할 수 있습니다.
while ... {
...
}
println("Total Guest Count: $totalGuests")
- 프로그램을 실행하면 다음과 같이 출력됩니다. 목록에서 숫자를 직접 더해 이 답이 올바른지 확인할 수 있습니다.
Total Guest Count: 10
다음은 전체 코드 스니펫입니다.
val guestsPerFamily = listOf(2, 4, 1, 3)
var totalGuests = 0
var index = 0
while (index < guestsPerFamily.size) {
totalGuests += guestsPerFamily[index]
index++
}
println("Total Guest Count: $totalGuests")
while
루프를 사용하면 색인을 추적하고 목록의 색인에 있는 요소를 가져오고 이 색인 변수를 업데이트하는 변수를 만드는 코드를 작성해야 했습니다. 목록을 반복하는 더 빠르고 간단한 방법이 있습니다. for
루프를 사용하는 것입니다.
for 루프
for
루프는 루프의 또 다른 유형입니다. 훨씬 쉽게 목록을 순환할 수 있습니다. Kotlin에서 for
키워드로 시작되고 중괄호로 코드 블록이 묶여 있습니다. 코드 블록 실행 조건은 괄호 안에 표시되어 있습니다.
for (number in numberList) {
// For each element in the list, execute this code block
}
이 예에서 number
변수는 numberList
의 첫 번째 요소와 같게 설정되고 코드 블록이 실행됩니다. 그러면 number
변수가 자동으로 numberList
의 다음 요소가 되도록 업데이트되고 코드 블록이 다시 실행됩니다. 이 작업은 numberList
가 끝날 때까지 목록의 각 요소에 반복됩니다.
- Kotlin 플레이그라운드에서 기존 코드를 삭제하고 다음 코드로 바꿉니다.
fun main() {
val names = listOf("Jessica", "Henry", "Alicia", "Jose")
}
for
루프를 추가하여names
목록의 항목을 모두 출력합니다.
for (name in names) {
println(name)
}
while
루프로 작성해야 하는 것보다 훨씬 쉽습니다.
- 출력은 다음과 같습니다.
Jessica Henry Alicia Jose
목록에 관한 일반적인 작업은 목록의 각 요소로 작업을 실행하는 것입니다.
- 루프를 수정하여 해당하는 사람의 이름에 있는 문자 수도 출력합니다. 힌트:
String
의length
속성을 사용하여String
의 문자 수를 확인할 수 있습니다.
val names = listOf("Jessica", "Henry", "Alicia", "Jose")
for (name in names) {
println("$name - Number of characters: ${name.length}")
}
출력:
Jessica - Number of characters: 7 Henry - Number of characters: 5 Alicia - Number of characters: 6 Jose - Number of characters: 4
루프의 코드는 원래 List
를 변경하지 않았습니다. 출력된 내용에만 영향을 미쳤습니다.
목록 항목 1개에 어떤 일이 발생해야 하는지 안내를 훌륭하게 작성할 수 있고 코드는 목록 항목마다 실행됩니다. 루프를 사용하면 반복되는 코드를 수없이 입력하는 수고를 덜 수 있습니다.
목록과 변경 가능한 목록을 모두 만들고 사용해보고 루프도 알아봤으므로 이제 샘플 사용 사례에 배운 내용을 적용해보겠습니다.
5. 완성된 모양
지역 음식점에서 음식을 주문할 때 보통 한 고객의 음식 주문에는 여러 항목이 포함되어 있습니다. 목록을 사용하면 주문 정보를 저장하는 데 유용합니다. 클래스와 상속에 관한 지식도 활용하여 main()
함수 내에 모든 코드를 배치하는 대신 좀 더 견고하고 확장 가능한 Kotlin 프로그램을 만들어야 합니다.
일련의 다음 작업에서는 다양한 조합의 음식 주문을 허용하는 Kotlin 프로그램을 만듭니다.
먼저 최종 코드의 출력 예를 살펴보세요. 이 모든 데이터를 구성하기 위해 어떤 종류의 클래스를 만들어야 하는지 파악할 수 있나요?
Order #1 Noodles: $10 Total: $10 Order #2 Noodles: $10 Vegetables Chef's Choice: $5 Total: $15
출력에 표시되는 내용은 다음과 같습니다.
- 주문 목록이 표시됩니다.
- 각 주문에는 번호가 표시됩니다.
- 각 주문에는 국수, 채소 등 항목 목록이 포함될 수 있습니다.
- 각 항목에는 가격이 표시됩니다.
- 각 주문에는 개별 항목의 가격 합계인 총 가격이 표시됩니다.
Order
를 나타내는 클래스와 Noodles
나 Vegetables
와 같은 각 음식 항목을 나타내는 클래스를 만들 수 있습니다. Noodles
와 Vegetables
에 유사성이 있음을 추가로 관찰할 수 있습니다. 둘 다 음식 항목이고 각각 가격이 표시되기 때문입니다. Noodle
클래스와 Vegetable
클래스가 모두 상속할 수 있는 공유 속성이 포함된 Item
클래스를 만들어 볼 수 있습니다. 이렇게 하면 Noodle
클래스와 Vegetable
클래스에서 모두 로직을 복제하지 않아도 됩니다.
- 다음 시작 코드가 제공됩니다. 전문 개발자는 새 프로젝트에 참여하거나 다른 개발자가 만든 기능에 추가할 때 등 다른 개발자의 코드를 읽어야 하는 경우가 많습니다. 코드를 읽고 이해할 수 있는 것이 중요합니다.
천천히 다음 코드를 살펴보며 내용을 파악하세요. 코드를 복사하여 Kotlin 플레이그라운드에 붙여넣고 실행합니다. Kotlin 플레이그라운드의 기존 코드를 삭제한 후에 새 코드를 붙여넣어야 합니다. 출력된 내용을 관찰하고 코드를 더 잘 이해하는 데 도움이 되는지 확인합니다.
open class Item(val name: String, val price: Int)
class Noodles : Item("Noodles", 10)
class Vegetables : Item("Vegetables", 5)
fun main() {
val noodles = Noodles()
val vegetables = Vegetables()
println(noodles)
println(vegetables)
}
- 다음과 유사하게 출력됩니다.
Noodles@5451c3a8 Vegetables@76ed5528
다음은 코드에 관한 자세한 설명입니다. 먼저 Item
이라는 클래스가 있습니다. 여기서 매개변수 2개를 생성자가 사용합니다. 하나는 항목의 name
(문자열)이고 다른 하나는 price
(정수)입니다. 두 속성은 모두 전달된 후 변경되지 않으므로 val
로 표시됩니다. Item
은 상위 클래스이고 서브클래스가 상위 클래스에서 확장되므로 클래스는 open
키워드와 함께 표시됩니다.
Noodles
클래스 생성자는 매개변수를 가져오지 않지만 Item
에서 확장되고 "Noodles"
를 이름과 10 가격으로 전달하여 슈퍼클래스 생성자를 호출합니다. Vegetables
클래스도 비슷하지만 "Vegetables"
와 5 가격으로 슈퍼클래스 생성자를 호출합니다.
main()
함수는 Noodles
및 Vegetables
클래스의 새 객체 인스턴스를 초기화하여 출력합니다.
toString() 메서드 재정의
객체 인스턴스를 출력하면 객체의 toString()
메서드가 호출됩니다. Kotlin에서는 모든 클래스가 자동으로 toString()
메서드를 상속합니다. 이 메서드의 기본 구현에서는 인스턴스의 메모리 주소가 있는 객체 유형을 반환합니다. Noodles@5451c3a8
과 Vegetables@76ed5528
보다 좀 더 의미 있고 사용자 친화적인 내용을 반환하도록 toString()
을 재정의해야 합니다.
Noodles
클래스 내에서toString()
메서드를 재정의하여 메서드에서name
을 반환하도록 합니다.Noodles
는 상위 클래스Item
에서name
속성을 상속합니다.
class Noodles : Item("Noodles", 10) {
override fun toString(): String {
return name
}
}
Vegetables
클래스에서도 동일하게 작업합니다.
class Vegetables() : Item("Vegetables", 5) {
override fun toString(): String {
return name
}
}
- 코드를 실행합니다. 이제 결과가 더 명확해졌습니다.
Noodles
Vegetables
다음 단계에서는 일부 매개변수를 사용하도록 Vegetables
클래스 생성자를 변경하고 추가 정보를 반영하도록 toString()
메서드를 업데이트합니다.
주문에서 채소 맞춤설정
국수를 좀 더 다채롭게 만들려면 주문에 다양한 채소를 포함하면 됩니다.
main()
함수에서 입력 인수가 없는Vegetables
인스턴스를 초기화하는 대신 고객이 원하는 특정 채소 유형을 전달합니다.
fun main() {
...
val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
...
}
지금 코드를 컴파일하려고 하면 다음과 같은 오류가 발생합니다.
Too many arguments for public constructor Vegetables() defined in Vegetables
이제 문자열 인수 3개를 Vegetables
클래스 생성자에 전달하므로 Vegetables
클래스를 수정해야 합니다.
- 다음 코드와 같이 문자열 매개변수 3개를 사용하도록
Vegetables
클래스 헤더를 업데이트합니다.
class Vegetables(val topping1: String,
val topping2: String,
val topping3: String) : Item ("Vegetables", 5) {
- 이제 코드가 다시 컴파일됩니다. 그러나 이 솔루션은 고객이 정확히 채소 3개를 항상 주문하려는 경우에만 효과가 있습니다. 채소를 하나 또는 다섯 개 주문하고 싶은 고객은 주문할 수 없습니다.
- 각 채소의 속성을 사용하는 대신
Vegetables
클래스의 생성자에서 채소 목록(길이는 상관없음)을 허용하여 문제를 해결할 수 있습니다.List
에는Strings
만 포함되어야 하므로 입력 매개변수 유형은List<String>
입니다.
class Vegetables(val toppings: List<String>) : Item("Vegetables", 5) {
main()
에서 코드를 변경하여 토핑 목록을 먼저 만든 후에 Vegetables
생성자에 전달해야 하므로 가장 명쾌한 솔루션은 아닙니다.
Vegetables(listOf("Cabbage", "Sprouts", "Onion"))
이보다 더 좋은 문제 해결 방법이 있습니다.
- Kotlin에서
vararg
수정자를 사용하면 동일한 유형의 가변적인 인수 수를 함수나 생성자에 전달할 수 있습니다. 이렇게 하면 목록 대신 개별 문자열로 다양한 채소를 제공할 수 있습니다.
Vegetables
의 클래스 정의를 변경하여 String
유형의 vararg
toppings
를 가져옵니다.
class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
main()
함수의 다음 코드가 이제 작동합니다. 여러 토핑 문자열을 전달하여Vegetables
인스턴스를 만들 수 있습니다.
fun main() {
...
val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
...
}
- 이제
Vegetables
클래스의toString()
메서드를 수정하여Vegetables Cabbage, Sprouts, Onion
형식의 토핑도 언급하는String
을 반환하도록 합니다.
항목 이름(Vegetables
)으로 시작합니다. 그런 다음 joinToString()
메서드를 사용하여 모든 토핑을 단일 문자열로 결합합니다. 사이에 공백이 있는 +
연산자를 사용하여 두 부분을 함께 추가합니다.
class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
override fun toString(): String {
return name + " " + toppings.joinToString()
}
}
- 프로그램을 실행하면 다음과 같이 출력됩니다.
Noodles Vegetables Cabbage, Sprouts, Onion
- 프로그램을 작성할 때는 가능한 모든 입력을 고려해야 합니다.
Vegetables
생성자의 입력 인수가 없으면 좀 더 사용자 친화적인 방법으로toString()
메서드를 처리합니다.
고객이 채소를 원하지만 어떤 채소인지 표현하지 않았으므로 한 가지 솔루션은 기본값인 셰프가 선택한 채소를 제공하는 것입니다.
전달된 토핑이 없다면 Vegetables Chef's Choice
를 반환하도록 toString()
메서드를 업데이트합니다. 앞서 알아본 isEmpty()
메서드를 활용합니다.
override fun toString(): String {
if (toppings.isEmpty()) {
return "$name Chef's Choice"
} else {
return name + " " + toppings.joinToString()
}
}
main()
함수를 업데이트하여 생성자 인수 없이 그리고 여러 인수로Vegetables
인스턴스를 만드는 가능성을 모두 테스트합니다.
fun main() {
val noodles = Noodles()
val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
val vegetables2 = Vegetables()
println(noodles)
println(vegetables)
println(vegetables2)
}
- 예상대로 출력되는지 확인합니다.
Noodles Vegetables Cabbage, Sprouts, Onion Vegetables Chef's Choice
주문 만들기
이제 음식 항목이 생겼으므로 주문을 만들 수 있습니다. 프로그램의 Order
클래스 내에서 주문 로직을 캡슐화합니다.
Order
클래스에 적합한 속성과 메서드를 생각해보세요. 도움이 되도록 여기 최종 코드의 샘플 출력을 다시 표시합니다.
Order #1 Noodles: $10 Total: $10 Order #2 Noodles: $10 Vegetables Chef's Choice: $5 Total: $15 Order #3 Noodles: $10 Vegetables Carrots, Beans, Celery: $5 Total: $15 Order #4 Noodles: $10 Vegetables Cabbage, Onion: $5 Total: $15 Order #5 Noodles: $10 Noodles: $10 Vegetables Spinach: $5 Total: $25
- 다음 속성과 메서드를 생각했을 수 있습니다.
주문 클래스
속성: 주문 번호, 항목 목록
메서드: 항목 추가, 여러 항목 추가, 주문 요약 출력(가격 포함)
- 먼저 속성에 중점을 두고 각 속성의 데이터 유형은 무엇이어야 하나요? 클래스에 공개되어야 하나요 비공개여야 하나요? 인수로 전달해야 하나요? 아니면 클래스 내에서 정의해야 하나요?
- 이를 구현하는 방법에는 여러 가지가 있지만 한 가지 솔루션은 다음과 같습니다. 정수
orderNumber
생성자 매개변수가 있는class
Order
를 만듭니다.
class Order(val orderNumber: Int)
- 주문의 모든 항목을 미리 알지 못할 수 있으므로 인수로 전달할 항목 목록은 필요하지 않습니다. 대신 최상위 클래스 변수로 선언하고
Item
유형의 요소를 보유할 수 있는 빈MutableList
로 초기화할 수 있습니다. 이 클래스만 항목 목록을 직접 수정할 수 있도록 변수를private
로 표시합니다. 이렇게 하면 이 클래스 외부의 코드로 인해 예상치 못한 방식으로 목록이 수정되는 것을 방지합니다.
class Order(val orderNumber: Int) {
private val itemList = mutableListOf<Item>()
}
- 클래스 정의에 메서드도 추가해 봅니다. 각 메서드에 적합한 이름을 자유롭게 선택하고 각 메서드 내의 구현 로직을 지금은 비워 두어도 됩니다. 어떤 함수 인수와 반환 값이 필요한지도 결정합니다.
class Order(val orderNumber: Int) {
private val itemList = mutableListOf<Item>()
fun addItem(newItem: Item) {
}
fun addAll(newItems: List<Item>) {
}
fun print() {
}
}
addItem()
메서드가 가장 간단해 보이므로 이 함수를 먼저 구현합니다. 그러면 새Item
을 가져오고 메서드는itemList
에 새 항목을 추가해야 합니다.
fun addItem(newItem: Item) {
itemList.add(newItem)
}
- 다음으로
addAll()
메서드를 구현합니다. 그러면 읽기 전용 항목 목록을 가져옵니다. 이러한 항목을 모두 내부 항목 목록에 추가합니다.
fun addAll(newItems: List<Item>) {
itemList.addAll(newItems)
}
- 그런 다음 총 주문 가격과 함께 모든 항목과 항목의 가격에 관한 요약을 출력하는
print()
메서드를 구현합니다.
주문 번호를 먼저 출력합니다. 그런 다음 루프를 사용하여 주문 목록의 모든 항목을 반복합니다. 각 항목과 항목의 가격을 출력합니다. 지금까지의 총 가격을 유지하고 목록을 반복하면서 계속 합산합니다. 마지막에 총 가격을 출력합니다. 이 로직을 직접 구현해보세요. 도움이 필요하다면 아래 솔루션을 확인하세요.
출력을 더 쉽게 읽을 수 있도록 통화 기호를 포함하는 것이 좋습니다. 다음은 솔루션을 구현하는 한 가지 방법입니다. 이 코드는 $ 통화 기호를 사용하지만 현지 통화 기호로 자유롭게 수정할 수 있습니다.
fun print() {
println("Order #${orderNumber}")
var total = 0
for (item in itemList) {
println("${item}: $${item.price}")
total += item.price
}
println("Total: $${total}")
}
itemList
에 있는 각 item
의 경우 item
을 먼저 출력하고(toString()
이 item
에서 호출되도록 트리거함) 항목의 price
를 출력합니다. 또한 루프 전에 total
정수 변수를 0이 되도록 초기화합니다. 그런 다음 현재 항목의 가격을 total
에 추가하여 총계에 계속 추가합니다.
주문 만들기
main()
함수 내에Order
인스턴스를 만들어 코드를 테스트합니다. 먼저 현재main()
함수에 있는 내용을 삭제합니다.- 이 샘플 주문을 사용하거나 직접 주문을 만들 수 있습니다. 주문 내에서 다양한 항목 조합을 시도하여 코드 내 모든 코드 경로를 테스트해야 합니다. 예를 들어
Order
클래스 내에서addItem()
및addAll()
메서드를 테스트하고Vegetables
인스턴스를 인수를 포함해 만들거나 포함하지 않고 만드는 등입니다.
fun main() {
val order1 = Order(1)
order1.addItem(Noodles())
order1.print()
println()
val order2 = Order(2)
order2.addItem(Noodles())
order2.addItem(Vegetables())
order2.print()
println()
val order3 = Order(3)
val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
order3.addAll(items)
order3.print()
}
- 위 코드는 다음과 같이 출력됩니다. 총 가격이 올바르게 합산되고 있는지 확인합니다.
Order #1 Noodles: $10 Total: $10 Order #2 Noodles: $10 Vegetables Chef's Choice: $5 Total: $15 Order #3 Noodles: $10 Vegetables Carrots, Beans, Celery: $5 Total: $15
아주 좋습니다. 이제 음식 주문 같습니다.
6. 코드 개선
주문 목록 유지
국수 전문점에서 실제로 사용할 프로그램을 빌드하고 있다면 모든 고객의 주문 목록을 추적하는 것이 좋습니다.
- 모든 주문을 저장할 목록을 만듭니다. 읽기 전용 목록인가요 아니면 변경 가능한 목록인가요?
- 다음 코드를
main()
함수에 추가합니다. 처음에는 목록이 비어 있도록 초기화합니다. 그리고 각 주문이 만들어지면 목록에 주문을 추가합니다.
fun main() {
val ordersList = mutableListOf<Order>()
val order1 = Order(1)
order1.addItem(Noodles())
ordersList.add(order1)
val order2 = Order(2)
order2.addItem(Noodles())
order2.addItem(Vegetables())
ordersList.add(order2)
val order3 = Order(3)
val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
order3.addAll(items)
ordersList.add(order3)
}
시간이 지남에 따라 주문이 추가되므로 목록은 Order
유형의 MutableList
여야 합니다. 그런 다음 MutableList
에서 add()
메서드를 사용하여 각 주문을 추가하세요.
- 주문 목록이 있으면 루프를 사용하여 각 주문을 출력할 수 있습니다. 주문 사이에 빈 줄을 인쇄하면 출력된 내용을 더 쉽게 읽을 수 있습니다.
fun main() {
val ordersList = mutableListOf<Order>()
...
for (order in ordersList) {
order.print()
println()
}
}
이렇게 하면 main()
함수에서 중복 코드가 삭제되고 코드를 더 쉽게 읽을 수 있습니다. 출력된 내용은 이전과 같습니다.
주문의 빌더 패턴 구현
Kotlin 코드를 더 간결하게 하려면 주문을 만드는 데 빌더 패턴을 사용하면 됩니다. 빌더 패턴은 단계별 접근 방식으로 복잡한 객체를 빌드할 수 있는 프로그래밍의 디자인 패턴입니다.
Order
클래스의addItem()
및addAll()
메서드에서Unit
(또는 아무것도 없음)을 반환하는 대신 변경된Order
를 반환합니다. Kotlin은 키워드this
를 제공하여 현재 객체 인스턴스를 참조합니다.addItem()
및addAll()
메서드 내에서this
를 반환하여 현재Order
를 반환합니다.
fun addItem(newItem: Item): Order {
itemList.add(newItem)
return this
}
fun addAll(newItems: List<Item>): Order {
itemList.addAll(newItems)
return this
}
main()
함수에서 이제 다음 코드와 같이 호출을 함께 연결할 수 있습니다. 이 코드는 새Order
를 만들고 빌더 패턴을 활용합니다.
val order4 = Order(4).addItem(Noodles()).addItem(Vegetables("Cabbage", "Onion"))
ordersList.add(order4)
Order(4)
가 Order
인스턴스를 반환한 후 이 인스턴스에서 addItem(Noodles())
를 호출할 수 있습니다. addItem()
메서드가 동일한 Order
인스턴스(새 상태)를 반환하면 다시 이 인스턴스에서 채소로 addItem()
을 호출할 수 있습니다. 반환된 Order
결과는 order4
변수에 저장될 수 있습니다.
Orders
를 만드는 기존 코드는 계속 작동하므로 변경하지 않아도 됩니다. 이러한 호출을 연결하는 것이 필수는 아니지만 함수의 반환 값을 활용하는 일반적이고 권장되는 방법입니다.
- 이 시점에서는 실제로 주문을 변수에 저장하지 않아도 됩니다.
main()
함수에서(주문을 출력하는 최종 루프 전에)Order
를 직접 만들어orderList
에 추가합니다. 각 메서드 호출이 한 줄에 하나씩 들어가면 코드를 더 쉽게 읽을 수 있습니다.
ordersList.add(
Order(5)
.addItem(Noodles())
.addItem(Noodles())
.addItem(Vegetables("Spinach")))
- 코드를 실행하면 다음과 같이 출력됩니다.
Order #1 Noodles: $10 Total: $10 Order #2 Noodles: $10 Vegetables Chef's Choice: $5 Total: $15 Order #3 Noodles: $10 Vegetables Carrots, Beans, Celery: $5 Total: $15 Order #4 Noodles: $10 Vegetables Cabbage, Onion: $5 Total: $15 Order #5 Noodles: $10 Noodles: $10 Vegetables Spinach: $5 Total: $25
이제 이 Codelab을 완료했습니다.
지금까지 데이터를 목록에 저장하고 목록을 변경하며 목록을 순환하는 것이 얼마나 유용한지 살펴봤습니다. 다음 Codelab에서는 여기서 배운 내용을 사용하여 Android 앱 컨텍스트에서 화면에 데이터 목록을 표시합니다.
7. 솔루션 코드
다음은 Item
, Noodles
, Vegetables
, Order
클래스의 솔루션 코드입니다. main()
함수는 이러한 클래스를 사용하는 방법도 보여줍니다. 이 프로그램을 구현하는 데는 여러 방법이 있으므로 코드가 약간 다를 수 있습니다.
open class Item(val name: String, val price: Int)
class Noodles : Item("Noodles", 10) {
override fun toString(): String {
return name
}
}
class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
override fun toString(): String {
if (toppings.isEmpty()) {
return "$name Chef's Choice"
} else {
return name + " " + toppings.joinToString()
}
}
}
class Order(val orderNumber: Int) {
private val itemList = mutableListOf<Item>()
fun addItem(newItem: Item): Order {
itemList.add(newItem)
return this
}
fun addAll(newItems: List<Item>): Order {
itemList.addAll(newItems)
return this
}
fun print() {
println("Order #${orderNumber}")
var total = 0
for (item in itemList) {
println("${item}: $${item.price}")
total += item.price
}
println("Total: $${total}")
}
}
fun main() {
val ordersList = mutableListOf<Order>()
// Add an item to an order
val order1 = Order(1)
order1.addItem(Noodles())
ordersList.add(order1)
// Add multiple items individually
val order2 = Order(2)
order2.addItem(Noodles())
order2.addItem(Vegetables())
ordersList.add(order2)
// Add a list of items at one time
val order3 = Order(3)
val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
order3.addAll(items)
ordersList.add(order3)
// Use builder pattern
val order4 = Order(4)
.addItem(Noodles())
.addItem(Vegetables("Cabbage", "Onion"))
ordersList.add(order4)
// Create and add order directly
ordersList.add(
Order(5)
.addItem(Noodles())
.addItem(Noodles())
.addItem(Vegetables("Spinach"))
)
// Print out each order
for (order in ordersList) {
order.print()
println()
}
}
8. 요약
Kotlin에서는 Kotlin 표준 라이브러리를 통해 데이터 컬렉션을 더 쉽게 관리하고 조작할 수 있는 기능을 제공합니다. 컬렉션은 동일한 데이터 유형의 여러 객체로 정의할 수 있습니다. Kotlin에는 목록, 집합, 지도와 같은 다양한 기본 컬렉션 유형이 있습니다. 이 Codelab에서는 목록에 특히 중점을 두었고 향후 Codelab에서 집합과 지도를 자세히 알아봅니다.
- 목록은 특정 유형 요소의 정렬된 컬렉션입니다(예:
Strings.
목록). - 색인은 요소의 위치를 나타내는 정수 위치입니다(예:
myList[2]
). - 목록에서 첫 번째 요소는 색인 0(예:
myList[0]
)에 있고 마지막 요소는myList.size-1
(예:myList[myList.size-1]
또는myList.last()
)에 있습니다. - 두 가지 목록 유형은 다음과 같습니다.
List
,MutableList.
List
는 읽기 전용으로, 초기화가 완료되면 수정할 수 없습니다. 그러나 원본을 변경하지 않고 새 목록을 반환하는sorted()
및reversed()
와 같은 작업을 적용할 수 있습니다.MutableList
는 요소를 추가하거나 삭제, 수정하는 등 만든 후에 수정할 수 있습니다.addAll()
을 사용하여 항목 목록을 변경 가능한 목록에 추가할 수 있습니다.while
루프를 사용하여 표현식이 false로 평가될 때까지 코드 블록을 실행하고 루프를 종료합니다.
while (expression) {
// While the expression is true, execute this code block
}
for
루프를 사용하여 목록의 모든 항목을 반복합니다.
for (item in myList) {
// Execute this code block for each element of the list
}
vararg
수정자를 사용하면 가변적인 인수 수를 함수나 생성자에 전달할 수 있습니다.