-
[Kotlin 문법 강의] 4주차: 객체지향 프로그래밍의 심화안드로이드 2024. 3. 6. 20:13
✏️ TIL(Today I Learned)
원래 이름, 나이 등등을 입력받을 때 readLine()!!을 사용하여 한번만 입력받았었는데, inputMyInfo() 함수를 만들어서 조건에 맞을 때까지 입력을 반복하게 바꾸었다. 매개변수로 받은 type의 값에 따라 다른 동작을 수행하기 위해 when 표현식을 사용했다. Any?는 반환 타입으로, 어떤 타입의 값을 반환할지 확정되지 않은 경우에 사용한다. 또한 반복문안에서 입력 받는 부분을 try-catch 구문으로 감싸서 예외 처리를 하였다.
fun inputMyInfo(type:String): Any? { return when(type) { "name" -> { println("이름을 입력해주세요") while(true) { try { var originName = readLine() // originName이 null이 아니고, 첫 번째 문자가 '_'나 '!'가 아닌 경우를 확인 if(originName?.first() != '_' && originName?.first() != '!') { return originName } else { println("이름을 다시 입력해주세요") } } catch(e:Exception) { println("이름을 다시 입력해주세요") } } } // 중간 코드 생략 else -> { return "no" } } }
또한 직업 클래스에 weapons 속성을 추가하였다. weapons는 소유한 무기를 나타내는 변수로, MutableList<String> 형식으로 선언했다. weapons 변수는 마법사 객체가 생성될 때 빈 리스트로 초기화되며, 필요에 따라 마법사가 보유한 무기를 추가할 수 있다. 리스트에는 추가된 순서대로 무기가 저장될 것이다. 게임 내에서 아이템을 판매하는 상점의 기능을 구현한 CashShop 클래스에서 무기를 얻을 수 있다.
class Archer : Character { var name:String var age:Int var gender:String var money:Int var hp:Int var weapons:MutableList<String> constructor(_name:String, _age:Int, _gender:String, _money:Int, _hp:Int) { weapons = mutableListOf<String>() name = _name age = _age gender = _gender money = _money hp = _hp println("${name}궁수 생성") } // 코드 생략 }
📝 공부한 Kotlin 요약 정리
접근제한자)
- 코틀린에서는 public, private, internal, protected로 클래스, 메서드, 속성 등의 멤버에 대한 접근을 제어한다.
- 객체를 이용해서 변수나 메소드를 호출할 수 있는지의 여부를 접근이라고한다.
- 종류
- public: 가장 기본적인 접근 제한자이며, 어떤 패키지에서든 접근할 수 있다. 명시적으로 접근 제한자를 지정하지 않으면 기본적으로 public으로 취급한다.
- private: 선언된 클래스 내부에서만 접근할 수 있다.
- internal: 같은 모듈 내에서만 접근할 수 있다. 모듈은 컴파일 시점에 결정되는 개념으로, 보통 하나의 프로젝트를 의미한다다.
- protected: 클래스 내부와 하위 클래스에서만 접근할 수 있다. 코틀린에서는 최상위 수준의 선언에 대해 protected 접근 제한자를 사용할 수 없다.
package com.example public class Example { public val publicProperty: String = "public" internal val internalProperty: String = "internal" protected val protectedProperty: String = "protected" private val privateProperty: String = "private" fun publicMethod() { println("This method is public") } private fun privateMethod() { println("This method is private") } } fun main() { val example = Example() println(example.publicProperty) // 가능 println(example.internalProperty) // 가능 println(example.protectedProperty) // 불가능 println(example.privateProperty) // 불가능 example.publicMethod() // 가능 example.privateMethod() // 불가능 }
-
더보기참고
- 프로젝트: 최상단 개념이고 <모듈, 패키지, 클래스>를 포함- 모듈: 프로젝트 아래의 개념이고 <패키지, 클래스>를 포함
- 패키지: 모듈 아래의 개념이고 <클래스>를 포함
예외 처리의 활용)
- try-catch의 구조
// try 블록: 예외가 발생할 수 있는 코드를 포함 // catch 블록: 예외가 발생했을 때 처리할 코드를 포함 // finally 블록: 예외 발생 여부와 상관없이 항상 실행되는 코드를 포함 fun main() { try { val result = divide(10, 0) println("Result: $result") } catch (e: ArithmeticException) { println("Division by zero error occurred") } finally { println("Finally block executed") } } fun divide(a: Int, b: Int): Int { return a / b }
- throw의 구조
// throw 키워드를 사용하여 예외를 발생, throw 키워드 뒤에는 예외 객체 지정 fun method1(num1: Int) { if (num1 > 10) { throw IllegalArgumentException("num1은 10보다 작거나 같아야 합니다.") } }
지연초기화)
- 코틀린은 클래스를 설계할 때 안정성을 위해 반드시 변수의 값을 초기화할것을 권장한다.
- 클래스를 설계할 때 초기의 값을 정의하기 난처해서 나중에 대입하기 위한 문법이다.
- 변수는 lateinit으로, 상수는 lazy로 지연초기화한다.
// lateinit 예시 class Student { lateinit var name: String var age: Int = 0 fun displayInfo() { // isInitialized를 활용해서 값이 초기화되었는지 확인 // 값이아니라 참조형태로 사용해야하기 때문에 this:: 또는 ::를 붙임 if (this::name.isInitialized) { println("이름은: $name 입니다.") println("나이는: $age 입니다.") } else { println("name 변수를 초기화해주세요.") } } } fun main() { var s1 = Student() s1.name = "참새" s1.displayInfo() // 출력: 이름은: 참새 입니다. \n 나이는: 0 입니다. s1.age = 10 s1.displayInfo() // 출력: 이름은: 참새 입니다. \n 나이는: 10 입니다. }
// lazy 예시 class Example { val lazyProperty: String by lazy { println("초기화 중...") "lazy로 초기화된 값" } } fun main() { val ex = Example() println("시작") println(ex.lazyProperty) // 처음 호출 시 lazy 프로퍼티가 초기화됨 println(ex.lazyProperty) // 두 번째 호출부터는 이미 초기화된 값이 반환됨 } // 출력: 시작 초기화 중... lazy로 초기화된 값 lazy로 초기화된 값
널 세이프티)
- 코틀린은 Null예외로부터 안전한 설계를 위해 자료형에 Null 여부를 명시할 수 있다.
- Null 예외로부터 안전한 설계를 위한 연산자
- ? (Safe Call Operator): 변수 또는 객체의 값이 null인지 여부를 확인하고, null이 아닌 경우에만 멤버나 메소드를 호출한다. 만약 값이 null이면 메서드 호출이 무시되고 null을 반환한다.
- !! (Not-null Assertion Operator): 변수 또는 객체의 값이 null이 아님이 확실한 경우에 사용한다. 만약 값이 null이면 NullPointerException을 발생시킨다. 따라서 사용할 때 주의해야한다.
- ?. (Safe Call with Let): null이 아닌 경우에만 람다를 실행한다. 그렇지 않으면 null을 반환한다.
- ?: (Elvis Operator): null인 경우 대체값을 지정할 때 사용한다. 만약 변수가 null이 아니면 변수의 값이 사용되고, null이면 우측에 있는 대체값이 사용된다.
fun main() { val str: String? = null // Nullable String 변수 선언 val length: Int? = str?.length // str이 null이 아니면 length 값을 가져옴 val lengthNotNull = str!!.length // str이 null이 아닌 것이 확실하면 그 값을 가져옴, // 만약 str이 null이면 NullPointerException 발생 val result = str?.let { // str이 null이 아니면 람다를 실행 processString(it) // it은 str의 값, null이면 람다 실행하지 않고 null 반환 } val lengthOrElse = str?.length ?: -1 // str이 null이면 -1을 반환, null이 아니면 length 값을 반환 } fun processString(str: String): String { return "Processed: $str" }
배열)
- 배열을 통해 변수에 순서를 매겨 연속적으로 활용할 수 있다.
- 코틀린은 배열을 사용하기 위해 arrayOf 메소드(키워드)를 제공한다.
// arrayOf메소드를 호출하면 배열을 리턴 // 1,2,3,4,5 각각을 저장한 변수 5개를 배열형태로 arr에 저장 var arr = arrayOf(1,2,3,4,5) // 배열요소를 모두 출력 println(Arrays.toString(arr)) // 배열의 첫번째 요소에 저장된 값을 출력합니다 // var num1 = 1의 num1과 arr[0]은 동일합니다 // arr[0]은 하나의 변수로 취급할 수 있습니다 // arr은 0~4번방(인덱스)까지 접근할 수 있습니다 // 사용 예시 fun main() { var kors = arrayOf(90, 94, 96) for((idx, kor) in kors.withIndex()) { println("${idx}번째 국어 점수는 ${kor}입니다") } }
컬렉션)
- List의 활용
- 리스트는 읽기전용과 수정가능한 종류로 구분할 수 있다.
- 배열(array)와 달리 크기가 정해져있지 않아 동적으로 값을 추가할 수 있다.
// 읽기전용 리스트 // 0번, 1번, 2번 인덱스에 접근해서 값을 변경할 수 없다 var scores1 = listOf(값1, 값2, 값3) // 수정가능 리스트 // 0번, 1번, 2번 인덱스에 접근해서 값을 변경할 수 있다 var scores2 = mutableListOf(값1, 값2, 값3) scores2.set(인덱스, 값) // 수정가능 리스트 // 0번, 1번, 2번 인덱스에 접근해서 값을 변경할 수 있다 // array로 데이터들을 저장하는 ArrayList도 mutableListOf와 동일하게 사용 가능 // 저장할 데이터의 자료형을 < > 안에 지정해야함 var scores3 = ArrayList<자료형>(값1, 값2, 값3) scores3.set(인덱스, 값)
- Map의 활용
- 맵은 키와 값의 쌍으로 이루어진 자료형이다.
- 읽기전용과 수정가능한 종류로 구분할 수 있다.
// 읽기전용 맵 // 변수명[키]로 데이터에 접근할 수 있다 var scoreInfo1 = mapOf("kor" to 94, "math" to 90, "eng" to 92) println(scoreInfo1["kor"]) // 수정가능 맵 // 변수명[키]로 데이터에 접근할 수 있다 var scoreInfo2 = mutableMapOf("kor" to 94, "math" to 90) scoreInfo2["eng"] = 92 println(scoreInfo2["eng"]) // 맵의 키와 값을 동시에 추출해서 사용할 수 있다 for((k,v) in scoreInfo2) { println("${k}의 값은 ${v}입니다") }
- Set의 활용
- 셋(Set)은 순서가 존재하지 않고 중복없이 데이터를 관리하는 집합 자료형이다.
- 읽기전용과 수정가능한 종류로 구분할 수 있다.
- 다른 컬렉션들은 요소를 찾는데에 집중하지만, Set은 요소가 존재하는지에 집중한다.
// 읽기전용 Set var birdSet = setOf("닭", "참새", "비둘기") // 수정가능 Set // var mutableBirdSet = mutableSetOf("닭", "참새", "비둘기") // mutableBirdSet.add("꿩") // mutableBirdSet.remove("꿩") println("집합의 크기는 ${birdSet.size} 입니다") // 출력: 집합의 크기는 3 입니다 var findBird = readLine()!! if(birdSet.contains(findBird)) { println("${findBird} 종류는 존재합니다.") } else { println("${findBird}는 존재하지 않습니다.") } // 입력 예시: "닭" 출력: "닭 종류는 존재합니다." 입력 예시: "독수리" 출력: "독수리는 존재하지 않습니다."
- 교집합, 차집합, 합집합으로 간편하게 요소들을 추출할 수 있다.
// 귀여운 새의 집합 var birdSet = setOf("닭", "참새", "비둘기", "물오리") // 날수있는 새의 집합 var flyBirdSet = setOf("참새", "비둘기", "까치") // 모든 새의 집합 (합집합) var unionBirdSet = birdSet.union(flyBirdSet) // 귀엽고 날수있는 새의 집합 (교집합) var intersectBirdSet = birdSet.intersect(flyBirdSet) // 귀여운 새들중에서 날수없는 새의 조합 (차집합) var subtractBirdSet = birdSet.subtract(flyBirdSet)
Single-expression function)
- 코틀린도 람다식을 지원
- 하나의 메소드를 간결하게 표현할 수 있는 방법
- Kotlin의 람다식 구조
{매개변수1, 매개변수2... -> 코드 } // 예시 var add = {num1: Int, num2: Int, num3: Int -> (num1+num2+num3) / 3} println("평균값은 ${add(10,20,30)}입니다") fun add(num1:Int, num2:Int, num3:Int) = (num1+num2+num3)/3
🔎 전체 코드
WorldMain.kt
package com.example.textrpggame fun main() { val worldName = "스코월드" var myName = inputMyInfo("name").toString() var myAge = inputMyInfo("age").toString().toInt() var myJob = inputMyInfo("job").toString() var myGender = inputMyInfo("gender").toString() var myMoney = inputMyInfo("money").toString().toInt() var myHp = inputMyInfo("hp").toString().toInt() var isNamePass = true var isAgePass = true var isJobPass = true // 중간 생략 // 모든 조건을 통과한 경우에만 환영 if(isNamePass && isAgePass && isJobPass) { // 새로 이름 추가 names.add(myName) displayInfo(worldName, myName, myAge, myJob) if(myJob == "마법사") { println("마법사는 초기 mp도 입력해주세요") var myMp = inputMyInfo("mp").toString().toInt() var myCharacter = Wizard(myName, myAge, myGender, myMoney, myHp, myMp) while(true) { println("[1] 슬라임동굴, [2] 좀비마을, [3] 캐쉬샵, [4] 종료") var selectNumber= inputMyInfo("selectNumber").toString().toInt() when(selectNumber) { 1 -> { selectWorldByWizard(1, myCharacter) } 2 -> { selectWorldByWizard(2, myCharacter) } 3 -> { openCashShopByWizard(myCharacter) } 4 -> { println("게임 종료") break } else -> { break } } } } else if(myJob == "궁수") { println("궁수를 선택했군요") // 중간 생략 } } } fun displayInfo(worldName:String, myName:String, myAge:Int, myJob:String) { println("==================${worldName}에 오신것을 환영합니다==================") println("당신의 정보는 다음과 같습니다.") println("이름: ${myName}입니다.") println("나이: ${myAge}입니다.") println("직업: ${myJob}입니다.") println("모험을 떠나 볼까요?") } fun selectWorldByArcher(selectWorld:Int, myCharacter: Archer) { if(selectWorld == 1) { // 슬라임 던전 var slime1 = Slime("초록슬라임", "초록", 30.2, 200, 10) slime1.attack() myCharacter.windArrow() slime1.poison() } else if(selectWorld == 2) { // 좀비 던전 var zombie1 = Zombie("파랑좀비", "파랑", 142.2, 500, 25) zombie1.virus() myCharacter.windJump("건물1") } } fun selectWorldByWizard(selectWorld:Int, myCharacter: Wizard) { if(selectWorld == 1) { // 슬라임 던전 var slime1 = Slime("파랑슬라임", "파랑", 30.2, 200, 10) slime1.attack() myCharacter.attack() slime1.poison() } else if(selectWorld == 2) { // 좀비 던전 var zombie1 = Zombie("파랑좀비", "파랑", 142.2, 500, 25) zombie1.virus() myCharacter.fireBall() } } fun inputMyInfo(type:String): Any? { return when(type) { "name" -> { println("이름을 입력해주세요") while(true) { try { var originName = readLine() if(originName?.first() != '_' && originName?.first() != '!') { return originName } else { println("이름을 다시 입력해주세요") } } catch(e:Exception) { println("이름을 다시 입력해주세요") } } } // 중간 생략 else -> { return "no" } } } fun openCashShopByArcher(character:Archer) { var cashShop = CashShop.getInstance() println("구매전 무기: ${character.weapons}") cashShop.purchaseBowByArcher(character) println("구매전 무기: ${character.weapons}") } fun openCashShopByWizard(character:Wizard) { var cashShop = CashShop.getInstance() println("구매전 무기: ${character.weapons}") cashShop.purchaseStaffByWizard(character) println("구매전 무기: ${character.weapons}") }
'안드로이드' 카테고리의 다른 글
[Android] 플레이스토어 배포 (0) 2024.03.08 [Android] 간단한 계산기 (추상화) (0) 2024.03.07 [Kotlin 문법 강의] 3주차: Kotlin 객체 지향 프로그래밍의 기초 (0) 2024.03.06 [Kotlin 문법 강의] 2주차: Kotlin 프로그래밍의 기초 (0) 2024.03.05 [Kotlin 문법 강의] 1주차: Kotlin을 시작하기 전에 알아야할 내용 (0) 2024.03.05