Kotlin에서 컬렉션 타입(collection type)은 여러 개의 데이터를 표현하는 방법이며 Array, List, Set, Map이 있다. List, Set, Map은 Collection 인터페이스를 구현한 클래스이며, 이를 통틀어 컬렉션 타입 클래스라고 한다. 그중 List는 순서가 있는 데이터 집합으로 데이터의 중복을 허용한다.
Collection 타입의 클래스는 가변 클래스와 불변 클래스로 나뉜다. Kotlin에서는 불변 타입인 List, 가변 타입인 MutableList를 제공한다. 불변 타입은 요소 추가, 삭제가 불가능하며, 가변 타입은 .add(), .remove() 등의 메서드를 통해 값을 자유롭게 수정할 수 있다.
listOf()함수로 List 객체를, mutableListOf()함수를 통해 MutableList를 생성할 수 있다. 또한 mutableListOf() 함수는 타입 추론이 가능하기 때문에 명시적인 타입 선언 없이도 앞서 사용한 타입을 그대로 사용할 수 있다.
fun main() {
// Immutable list
// val shoppingList = listOf("Processor", "RAM", "Graphics Card", "SSD")
// mutable list
val shoppingList = mutableListOf("Processor", "RAM", "Graphics Card RTX 3060", "SSD")
// adding items to lists
shoppingList.add("Cooling System")
shoppingList.remove("Graphics Card RTX 3060")
shoppingList.add("Graphics Card RTX 4090")
println(shoppingList) // [Processor, RAM, SSD, Cooling System, Graphics Card RTX 4090]
}
Array와 List의 차이점
Array는 고정 크기의 배열, List는 요소 개수 제한 없는 컬렉션이다.
mutableListOf()를 val로 선언하는 이유
val은 참조(reference)를 바꿀 수 없다는 뜻으로, 그 안에 있는 내용(data)는 바꿀 수 있다.
val list = mutableListOf("a", "b", "c")
list.add("d") // ✅ 가능
list.remove("b") // ✅ 가능
// list = mutableListOf("x", "y") // ❌ 안 됨! -> 참조 자체를 바꾸려 하니까
val list로 선언한 것은 이 변수가 특정 리스트를 가리키는 참조(주소)를 바꾸지 않겠다는 의미다. 이 리스트의 주소는 고정되지만, 리스트 내부의 요소는 변경할 수 있다. 반면 var로 선언하면, 이 변수는 나중에 다른 리스트를 가리키는 것으로 변경될 수 있다. 즉, 리스트 전체를 새로 덮어쓸 수 있다는 의미다. 따라서 일반적으로는 리스트 안의 요소만 바꾸고 참조는 유지하는 경우가 많기 때문에, 리스트 전체를 다른 것으로 바꾸는 실수를 막기 위해 val 선언을 권장한다.
리스트에서의 set 메서드
set()메서드는 리스트에서 특정 인덱스의 항목을 새로운 값으로 교체할 때 사용된다. 두 개의 매개변수를 받으며, 첫 번째는 바꾸고 싶은 요소의 인덱스, 두 번째는 새로 넣을 값이다.
Contains 메서드 - 항목이 리스트에 있는지 확인
contains() 메서드는 리스트 안에 특정 항목이 포함되어 있는지 여부를 확인할 때 사용한다. 찾고 싶은 값을 인자로 전달하면 되고, 결과는 Boolean 타입으로 반환된다.
Kotlin에서 리스트 조작 연습
package com.example.kotlinbasics
fun main() {
val fruitsList = mutableListOf("apple", "peach", "strawberry", "cherry", "banana")
println(fruitsList)
fruitsList.add("melon")
println(fruitsList)
fruitsList.remove("apple")
println(fruitsList)
if (fruitsList.contains("watermelon")) {
println("Has watermelon")
}
else {
println("No watermelon")
}
}
리스트와 함께 사용하는 for루프
for(item in shoppingList) {
println(item)
if (item == "RAM"){
shoppingList.removeLast()
break;
}
}
println(shoppingList)
for 루프에서 인덱스 가져오기
for (i in 1..10) { ... } : 1부터 10까지 1씩 증가
for (i in 1 until 10) { ... } : 1부터 9까지 1씩 증가(10 미포함)
fun makeCoffee(name : String, sugarCount : Int) {
if (sugarCount == 0) {
println("Coffee with no sugar for $name")
}
else if (sugarCount == 1) {
println("Coffee with $sugarCount spoon of sugar for $name")
}
else {
println("Coffee with $sugarCount spoons of sugar for $name")
}
}
함수의 매개변수에는 기본값을 선언할 수 있다. 만약 어떤 매개변수에 기본값을 선언했다면 호출할 때 인자를 전달하지 않아도 되며 이때 선언문에 명시한 기본값이 적용된다.
fun read(
b: ByteArray,
off: Int = 0,
len: Int = b.size,
) { /*...*/ }
사용자 입력과 함께 커피는 누구를 위한 것인가
package com.example.kotlinbasics
fun main() {
println("Who is this coffee for?")
val name = readln()
println("How many pieces of sugar do you want?")
val sugarCount = readln().toInt()
makeCoffee(name, sugarCount)
}
// Define Function
fun makeCoffee(name : String, sugarCount : Int) {
if (sugarCount == 0) {
println("Coffee with no sugar for $name")
}
else if (sugarCount == 1) {
println("Coffee with $sugarCount spoon of sugar for $name")
}
else {
println("Coffee with $sugarCount spoons of sugar for $name")
}
}
반환 유형에 대해 더 알아보기
fun divide(num1 : Int, num2 : Int):Double {
val result = num1 / num2
return result
}
해당 함수는 에러가 발생한다. Int와 Int를 나누면 결과가 Int일 것이라고 예상하지만, 반환 타입을 Double로 명시해두었기 때문에 오류가 발생하는 것이다. 이 문제를 해결하려면 num1과 num2 모두를 Double로 변환하거나, 둘 중 하나만 Double로 변환해도 된다.
클래스의 멤버는 생성자, 변수, 함수, 클래스로 구성된다. 이 중 생성자는 constructor 키워드로 선언하는 함수이다. 그리고 클래스 안에 다른 클래스를 선언할 수도 있다. 이때 생성자는 주 생성자와 보조 생성자로 구분되는데 주 생성자는 클래스 선언부에 constructor 키워드를 사용해 정의하며 반드시 선언해야 하는 것은 아니고 한 클래스에 하나만 가질 수 있다. 만약 어노테이션이나 public, private과 같은 접근 제한자가 없다면, constructor 키워드는 생략할 수 있다.
init 키워드로 지정한 영역은 클래스 객체를 생성하는 순간 자동으로 실행된다.
class User(name: String, count: Int) {
init {
println("I am init...")
}
}
fun main() {
val user = User("yeonee", 10)
}
// i am init...
생성자의 매개변수는 기본적으로 생성자에서만 사용할 수 있는 지역 변수이다. 즉 init 영역에서만 사용가능하다.
class User(name: String, count: Int) {
init {
println("name : $name, count : $count") // 성공
}
fun someFun() {
println("name : $name, count : $count") // 오류
}
]
코틀린은 프로퍼티를 선언하고, 그것을 주 생성자에서 초기화하는 간결한 문법을 제공한다. 주 생성자 안에서 val이나 var 키워드로 선언하면 클래스의 멤버 변수가 된다.
멤버 변수, 즉 프로퍼티는 실제 객체의 일부라는 뜻이고 매개변수는 그저 객체에 정보를 전달한 것이다. 변수나 클래스의 프로퍼티를 String으로 다루고 싶다면 중괄호를 추가해서 어디서부터 어디까지가 영역인지 구분한다. 또한 프로퍼티에도 기본값을 줄 수 있다.
package com.example.kotlinbasics
class Book (val title : String = "Unknown",
val author : String = "Anonymous",
val yearPublished : Int = 2025) {
}
코틀린의 데이터 클래스는 주로 데이터를 저장하는 데 사용된다. class대신에 data class 키워드를 사용하여 선언하다.data class를 사용하면 toString(), equals(), hashCode() 같은 유용한 함수들이 자동으로 만들어진다. 데이터 클래스에서는 별도의 함수 정의가 불가능하다.
데이터 클래스의 주요 특징은 다음과 같다.
Immutability : 불변성
Standard Methods : 표준 메서드
Destructuring Declarations : 구조 분해 선언
우선, 데이터 클래스는 주로 val을 사용해 불변 속성을 권장하므로, 단순하고 변경되지 않는 데이터를 표현하기에 적합하다. 또한, toString(), equals(), hashCode() 같은 기본 메서드들을 코틀린이 자동으로 생성해준다. 마지막으로, 구조 분해 선언을 지원해 객체의 속성들을 개별 변수로 간편하게 꺼내 쓸 수 있다.
package com.example.kotlinbasics
data class CoffeeDetails(
val sugarCount: Int,
val name: String,
val size: String,
val creamAmount: Int)
fun main() {
val coffeeForYeonee = CoffeeDetails(0, "yeonee", "L", 0)
makeCoffee(coffeeForYeonee)
}
// Define Function
fun makeCoffee(coffeeDetails: CoffeeDetails) {
if (coffeeDetails.sugarCount == 0) {
println("Coffee with no sugar for ${coffeeDetails.name}")
}
else if (coffeeDetails.sugarCount == 1) {
println("Coffee with ${coffeeDetails.sugarCount} " +
"spoon of sugar for ${coffeeDetails.name}")
}
else {
println("Coffee with ${coffeeDetails.sugarCount} spoons of sugar for ${coffeeDetails.name}")
}
}
-128 ~ 127 대신 0~255를 사용한다. 1이 아닌 0부터 시작하고 양수 방향으로만 범위가 커진다.
대입하는 값 끝에 u또는 U를 붙여줘야 한다. 대문자, 소문자는 관계없다.
val b: UByte = 1u // UByte, expected type provided
val s: UShort = 1u // UShort, expected type provided
val l: ULong = 1u // ULong, expected type provided
val a1 = 42u // UInt: no expected type provided, constant fits in UInt
val a2 = 0xFFFF_FFFF_FFFFu // ULong: no expected type provided, constant doesn't fit in UInt
val a = 1UL // ULong, even though no expected type provided and the constant fits into UInt
일련의 문자, 즉 단어나 문단들 같은 긴 문자 같은 것들을 저장할 수 있다. 큰따옴표(")로 감싸서 표현한다. 문자열(String)의 요소들은 문자(Char)들이며, s[i]와 같은 인덱싱 연산을 통해 접근할 수 있다. 또한, for (c in str) 같은 for문을 사용해 이 문자들을 순회(iterate)할 수 있다.
한 번 문자열을 초기화하면, 그 값을 바꾸거나 새 값을 할당할 수 없다. 문자열을 변형하는 모든 연산은 새로운 String 객체로 결과를 반환하며, 원래 문자열은 그대로 유지된다.
val str = "abcd"
// Creates and prints a new String object
println(str.uppercase())
// ABCD
// The original string remains the same
println(str)
// abcd
readln() 함수를 사용하여 표준 입력으로부터 데이터를 읽는다. 이때 전체 줄을 문자열로 읽는다. 만약 다른 자료형으로 변환하고 싶으면 .toInt(),.toLong(),.toDouble(),.toFloat(), 또는.toBoolean()와 같은 함수를 이용한다. 즉 .toInt()를 사용해서 Integer을 만들 수 있다. 이는 String을 파싱하여 Integer 숫자로 결과를 반환한다.
val age = readln().toInt()
.toInt(), .toDouble() 같은 형 변환 함수는, 입력된 문자열이 해당 자료형에 맞는 올바른 값일 거라고 가정하고 동작한다. 따라서서 "hello"처럼 숫자가 아닌 문자열을 변환하려고 하면 에러(예외)가 발생한다.
Else if 및 in 키워드
if (age in 18..39){...}
if (age >= 18 && age < 40){...}
in은 범위 지정 연산자로, 두 조건식은 같은 의미이다.
가위 바위 보
컴퓨터의 선택 받기
package com.example.rockpaperscissors
fun main(){
var computerChoice = ""
var playerChoice = ""
println("Rock, Paper or Scissors? Enter your choice!")
playerChoice = readln()
val randomNumber = (1..3).random()
if (randomNumber == 1) computerChoice = "Rock"
else if (randomNumber == 2) computerChoice = "Paper"
else computerChoice = "Scissors"
println(computerChoice)
}
1부터 3까지의 숫자 중 .random() 함수를 이용하여 숫자 하나를 무작위로 선정하여 변수 randomNumber에 저장한다.
승자 찾기
when문을 이용한다. when 키워드 다음의 소괄호()에 넣은 데이터가 조건이 되고 이 값에 따라 -> 오른쪽에 있는 구문을 실행한다. 또는 데이터를 명시하지 않고 조건만 명시할 수도 있다. 표현식으로도 사용할 수 있는데, 즉 when 문의 실행 결과를 반환할 수 있다. when문을 표현식으로 사용할 때는 else문을 생략할 수 없다.
if (winner == "Tie") {
println("It's a tie")
}
else {
println(winner + " won!")
}
if (winner == "Tie") {
println("It's a tie")
}
else {
println("$winner won!")
}
String 타입의 데이터에 변숫값이나 어떤 연산식의 결괏값을 포함해야 할 때는 $ 기호를 이용한다. 이를 문자열 템플릿이라고 한다.
카운터와 함께 While 루프
fun main() {
var count = 0
while (count < 3) {
println("Count is $count")
count++
}
}
사용자 입력과 함께 While 루프
fun main() {
var userInput = readln()
while (userInput == "1") {
println("While loop executed")
userInput = readln()
}
println("Loop is done!")
}
퀴즈7: 코딩 연습 - 가위바위보 게임에서 플레이어 입력 유효성 검사
package com.example.rockpaperscissors
fun main(){
var computerChoice = ""
var playerChoice = ""
println("Rock, Paper or Scissors? Enter your choice!")
playerChoice = readln().lowercase()
while(playerChoice != "rock" && playerChoice != "paper" && playerChoice != "scissors"){
println("Rock, Paper or Scissors? Enter your choice!")
playerChoice = readln().lowercase()
}
val randomNumber = (1..3).random()
when (randomNumber) {
1 -> {
computerChoice = "Rock"
}
2 -> {
computerChoice = "Paper"
}
3 -> {
computerChoice = "Scissors"
}
}
println(computerChoice)
computerChoice = computerChoice.lowercase()
val winner = when {
playerChoice == computerChoice -> "Tie"
playerChoice == "rock" && computerChoice == "scissors" -> "Player"
playerChoice == "paper" && computerChoice == "rock" -> "Player"
playerChoice == "scissors" && computerChoice == "paper" -> "Player"
else -> "Computer"
}
if (winner == "Tie") {
println("It's a tie")
}
else {
println("$winner won!")
}
}
플레이어가 가위, 바위, 보만 입력하도록 강제
대소문자 구분을 없앰 -> rock, Rock, ROCK, rOCk 모두 바위로 처리
println(computerChoice) // Rock
computerChoice.lowercase()
println(computerChoice) // Rock
computerChoice = computerChoice.lowercase()
println(computerChoice) // rock
ComponentActivity : Android에서 Jetpack의 Activity 구조 중 하나이다. Activity의 기본 구현체로, Jetpack 생명주기(Lifecycle), ViewModel, SavedState 같은 최신 아키텍처 컴포넌트를 지원하는 기반 클래스