Kotlin/StoreInfo

<강의> Kotlin 문법 종합반 4주차

re트 2023. 11. 29. 15:11
728x90

1. 접근제한자

  - 접근 : 객체를 이용해서 변수나 메소드를 호출할 수 있는지 여부

  - 프로젝트 : 최상단 개념이고 모듈, 패키지, 클래스를 포함

  - 모듈 : 프로젝트 아래 개념이고 패키지, 클래스를 포함

  - 패키지 : 모듈 아래 개념이고 클래스를 포함

  - 종류

    1) public : 어디서나 접근 가능 (명시하지 않을 시 기본적으로 public)

    2) private : 동일한 클래스 내부에서만 접근 가능

    3) internal : 같은 모듈 내부에서만 접근 가능

    4) protected : 기본적으로는 private이지만 상속을 받은 경우에는 타 모듈에서 접근 가능

  - 사용하는 이유

    1) 데이터에 무분별하게 접근하는 것을 막기 위해

    2) 클래스들 간에 접근 가능/불가능 상황을 구분하기 때문에 유지보수에 용이

  - 예시

// Main.kt
fun main() {
    var accessTestClass = AccessTestClass()
    var accessTestChildClass = AccessTestChildClass()

    println("AccessTestClass의 객체에서 호출하는 변수와 메소드")
    println(accessTestClass.a) // public
    println(accessTestClass.b) // 생략된 public
    // println(accessTestClass.c) <- private
    println(accessTestClass.d) // internal
    // println(accessTestClass.e) <- protected
    println("===============")
    println(accessTestClass.publicTest()) // public
    println(accessTestClass.publicTest2()) // 생략된 public
    // println(accessTestClass.privateTest()) <- private
    println(accessTestClass.internalTest()) // internal
    // println(accessTestClass.protectedTest()) <- protected
    println("===============")
    println("AccessTestClass를 상속받은 객체에서 호출하는 변수와 메소드")
    println(accessTestChildClass.a) // public
    println(accessTestChildClass.b) // 생략된 public
    // println(accessTestChildClass.c) <- private
    println(accessTestChildClass.d) // internal
    // println(accessTestChildClass.e) <- protected
    println("===============")
    println(accessTestChildClass.publicTest()) // public
    println(accessTestChildClass.publicTest2()) // 생략된 public
    // println(accessTestChildClass.privateTest()) <- private
    println(accessTestChildClass.internalTest()) // internal
    // println(accessTestChildClass.protectedTest()) <- protected
    println("===============")
    println(accessTestChildClass.protectedTest1()) // protected
}

// AccessTestClass.kt
open class AccessTestClass {
    public var a:Int = 1
    var b = 2
    private var c = 3
    internal var d = 4
    protected var e = 5

    public fun publicTest() {
        println("public 입니다")
    }

    fun publicTest2() {
        println("public 입니다")
    }

    private fun privateTest() {
        println("private 입니다")
    }

    internal fun internalTest() {
        println("internal 입니다")
    }

    protected fun protectedTest() {
        println("protected 입니다")
    }
}

// AccessTestChildClass.kt
class AccessTestChildClass: AccessTestClass() {

    fun protectedTest1() {
        println("e의 값은 ${e}")
    }
}

  <출력 결과>

AccessTestClass의 객체에서 호출하는 변수와 메소드
1
2
4
===============
public 입니다
kotlin.Unit
public 입니다
kotlin.Unit
internal 입니다
kotlin.Unit
===============
AccessTestClass를 상속받은 객체에서 호출하는 변수와 메소드
1
2
4
===============
public 입니다
kotlin.Unit
public 입니다
kotlin.Unit
internal 입니다
kotlin.Unit
===============
e의 값은 5
kotlin.Unit

 

2. 예외 처리의 활용

  - 오류 : 프로그램을 실행하기 전에 알 수 있는 컴파일 에러

  - 예외 : 프로그램을 실행하는 도중에 발생하는 런타임에러

  - 실행도중에 예외가 발생하면 프로그램이 비정상적으로 종료되는데 이를 생각해서 소스코드를 작성해야함

  - try-catch(-finally)의 구조

fun method() {
    try {
        예외가 발생할 가능성이 존재하는 코드
    } catch(예외종류) {
        예외가 발생했을때 처리할 코드
    } finally {
        예외 처리와 관계없이 항상 실행하는 코드
    }
}

 

  - throw의 구조

fun method1(num1:Int) {
    if(num1 > 10) {
        throw 예외종류
    }
}

 

  - 예시

fun main() {
    while(true) {
        try {
            var num1 = readLine()!!.toInt()
            println("내가 입력한 숫자는 ${num1}입니다")
            break
        } catch(e:java.lang.NumberFormatException) {
            println("숫자를 입력하세요")
        } finally {
            println("키보드와의 연결은 정상적입니다")
        }
    }
}

  <출력결과>

ff
숫자를 입력하세요
키보드와의 연결은 정상적입니다
as
숫자를 입력하세요
키보드와의 연결은 정상적입니다
10
내가 입력한 숫자는 10입니다
키보드와의 연결은 정상적입니다

 

3. 지연초기화

  - Kotlin은 클래스를 설계할 때 안정성을 위해 반드시 변수값을 초기활 것을 권장하지만 초기값을 정의하기 어려울 때 나중에 대입하기 위해 쓰는 문법

  - 종류

    1) lateinit : 변수용

    2) lazy : 상수용

  - 사용하는 이유 : 변수가 사용될 때만 메모리를 차지하기 때문에 메모리를 더욱 효율적으로 사용가능

  - lateinit 예시

fun main(){
    var s1 = Student()
    s1.name = "참새"
    s1.displayInfo()

    s1.age = 10
    s1.displayInfo()
}

class Student {
    // 초기값을 정의하기 어려워서 lateinit 사용
    lateinit var name:String
    var age:Int = 0

    fun displayInfo() {
        // 값이 초기화 되었는지 확인
        // 참조형태로 사용해야하기 때문에 this:: 또는 ::을 붙임
        if(this::name.isInitialized) {
            println("이름은: ${name} 입니다.")
            println("나이는: ${age} 입니다.")
        } else {
            println("name변수를 초기화해주세요.")
        }
    }
}

  <출력결과>

이름은: 참새 입니다.
나이는: 0 입니다.
이름은: 참새 입니다.
나이는: 10 입니다.
=======================
// s1.name = "참새" 를 뺐을 때
name변수를 초기화해주세요.
name변수를 초기화해주세요.

 

  - lazy 예시

fun main(){
    var s1 = Student()
    s1.name = "참새"
    s1.displayInfo()

    s1.age = 10
    s1.displayInfo()
}

class Student {
    lateinit var name:String
    var age:Int = 0
    // 상수를 사용하는 시점에 값을 대입하고 초기화를 수행
    val address: String by lazy {
        println("address 초기화")
        "seoul"
    }

    fun displayInfo() {
        println("이름은: ${name} 입니다.")
        println("나이는: ${age} 입니다.")
        println("주소는: ${address} 입니다.")
    }
}

  <출력결과>

이름은: 참새 입니다.
나이는: 0 입니다.
address 초기화
주소는: seoul 입니다.
이름은: 참새 입니다.
나이는: 10 입니다.
주소는: seoul 입니다.

 

4. 널 세이프티

  - Null 예외 : 프로그램의 가용성을 저하시키는 치명적인 오류

  - Kotlin은 Null 예외로부터 안전하게 설계하기 위해 자료형에 Null 여부를 명시할 수 있음

  - 방법 : ?, !!, ?.(안전 호출연산자), ?:(엘비스 연산자) 키워드들을 자료형, 메소드, 변수 등에 붙임(!!은 사용 지양)

  - ? 예시

fun main(){
    var s = Student()
    s.name = "참새"
    s.address = "서울"
    s.displayInfo()
}

class Student {
    lateinit var name:String
    var address:String? = null // null 저장가능을 명시하기위해 String?으로 선언

    fun displayInfo() {
        println("이름은: ${name} 입니다")
        println("주소는: ${address} 입니다")
    }
}

  <출력결과>

이름은: 참새 입니다
주소는: 서울 입니다
====================
// s.address = "서울" 이 없을 때
이름은: 참새 입니다
주소는: null 입니다

 

  - !! 예시

fun main(){
    //  var data = readLine()!!.toInt() 를 두줄로 쓴 것
    var inputData = readLine()!! // 리턴값이 null이 아님을 !! 키워드로 보장
    var data = inputData.toInt()
    println("Null아닌 값: ${data}")
}

  <출력결과>

10
Null아닌 값: 10

 

  - ?. 키워드

fun main(){
    var s = Student()
    s.name = "참새"
    s.displayAddressLength()
    
    s.address = "서울"
    s.displayInfo()
}

class Student {
    lateinit var name:String
    var address:String? = null

    fun displayInfo() {
        println("이름은: ${name} 입니다")
        println("주소는: ${address} 입니다")
    }
    
    fun displayAddressLength() {
        // 변수가 null인지 확인하고 null이 아닐때만 참조
        println("주소의 길이는: ${address?.length} 입니다") 
    }
}

  <출력결과>

주소의 길이는: null 입니다
이름은: 참새 입니다
주소는: 서울 입니다

 

  - ?: 예시

fun main(){
    var s = Student()
    s.name = "참새"
    s.displayAddressLength()

    s.address = "서울"
    s.displayInfo()
}

class Student {
    lateinit var name:String
    var address:String? = null

    fun displayInfo() {
        println("이름은: ${name} 입니다")
        println("주소는: ${address} 입니다")
    }
    
    fun displayAddressLength() {
        // 변수가 null인지 확인하고 null이라면 ?: 뒤에 있는 문자열 출력
        println("주소의 길이는: ${address?.length ?: "초기화하세요"} 입니다")
    }
}

  <출력결과>

주소의 길이는: 초기화하세요 입니다
이름은: 참새 입니다
주소는: 서울 입니다

 

5. 배열

  - 변수에 순서를 매겨 연속적으로 활용 가능

  - arrayOf 메소드(키워드) 제공

  - 예시

fun main() {
    var kors = arrayOf(90, 94, 96)
    for((idx, kor) in kors.withIndex()) {
        println("${idx}번째 국어 점수는 ${kor}입니다")
    }
}

  <출력결과>

0번째 국어 점수는 90입니다
1번째 국어 점수는 94입니다
2번째 국어 점수는 96입니다

 

6. 컬렉션

  - Kotlin에서는 리스트, , 집합 자료구조(컬렉션)를 지원

  - 리스트 사용법

// 동적으로 값 추가 가능

// 읽기전용 리스트
// 값을 변경 불가
var scores1 = listOf(값1, 값2, 값3)

// 수정가능 리스트
// 값을 변경 가능
var scores2 = mutableListOf(값1, 값2, 값3)
scores2.set(인덱스, 값)

// 수정가능 리스트
// 값을 변경 가능
// array로 데이터들을 저장하는 ArrayList도 mutableListOf와 동일하게 사용 가능
// 저장할 데이터의 자료형을 < > 안에 지정
var scores3 = ArrayList<자료형>(값1, 값2, 값3)
scores3.set(인덱스, 값)

 

  - (키와 값의 쌍으로 이루어진 자료형) 사용법

// 읽기전용 맵
// 변수명[키]로 데이터에 접근 가능
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}입니다")
}

  <출력결과>

94
92
kor의 값은 94입니다
math의 값은 90입니다
eng의 값은 92입니다

 

  - 집합(순서가 존재하지 않고 중복없이 데이터를 관리하는 자료형) 사용법

// 귀여운 새의 집합
// 읽기 전용 Set
var birdSet = setOf("닭", "참새", "비둘기", "물오리")

// 수정가능 Set
var mutableBirdSet = mutableSetOf("닭", "참새", "비둘기")
mutableBirdSet.add("꿩")
mutableBirdSet.remove("꿩")

// 날수있는 새의 집합
var flyBirdSet = setOf("참새", "비둘기", "까치")

// 모든 새의 집합 (합집합)
var unionBirdSet = birdSet.union(flyBirdSet)

// 귀엽고 날수있는 새의 집합 (교집합)
var intersectBirdSet = birdSet.intersect(flyBirdSet)

// 귀여운 새들중에서 날수없는 새의 조합 (차집합)
var subtractBirdSet = birdSet.subtract(flyBirdSet)

println("=====합집합=====")
println("모든 새의 집합 : ${unionBirdSet}")

println("=====교집합=====")
println("귀엽고 날수있는 새의 집합 : ${intersectBirdSet}")

println("=====차집합=====")
println("귀엽고 날수없는 새의 집합 : ${subtractBirdSet}")

  <출력결과>

=====합집합=====
모든 새의 집합 : [닭, 참새, 비둘기, 물오리, 까치]
=====교집합=====
귀엽고 날수있는 새의 집합 : [참새, 비둘기]
=====차집합=====
귀엽고 날수없는 새의 집합 : [닭, 물오리]

 

  - 예시

fun main() {
    var students = mutableListOf<Student>()
    var averages = mutableMapOf<String, Int>()

    for(idx in 0..2) {
        println("학생의 이름을 입력하세요")
        var name = readLine()!!

        println("국어 점수를 입력하세요")
        var kor = readLine()!!.toInt()

        println("수학 점수를 입력하세요")
        var math = readLine()!!.toInt()

        println("영어 점수를 입력하세요")
        var eng = readLine()!!.toInt()

        var average = (kor + math + eng) / 3
        var tempStudent = Student(name, kor, math, eng)

        students.add(tempStudent)
        averages[name] = average
    }

    for(student in students) {
        var average = averages[student.name]
        student.displayInfo()
        println("평균점수는 ${average} 입니다")
    }
}

class Student(name:String, kor:Int, math:Int, eng:Int) {
    var name:String
    var kor:Int
    var math:Int
    var eng:Int
    
    init {
        this.name = name
        this.kor = kor
        this.math = math
        this.eng = eng
    }

    fun displayInfo() {
        println("이름: $name")
        println("국어: $kor")
        println("수학: $math")
        println("영어: $eng")
    }
}

  <출력결과>

학생의 이름을 입력하세요
철수
국어 점수를 입력하세요
100
수학 점수를 입력하세요
100
영어 점수를 입력하세요
100
학생의 이름을 입력하세요
민수
국어 점수를 입력하세요
99
수학 점수를 입력하세요
98
영어 점수를 입력하세요
100
학생의 이름을 입력하세요
영지
국어 점수를 입력하세요
38
수학 점수를 입력하세요
20
영어 점수를 입력하세요
50
이름: 철수
국어: 100
수학: 100
영어: 100
평균점수는 100 입니다
이름: 민수
국어: 99
수학: 98
영어: 100
평균점수는 99 입니다
이름: 영지
국어: 38
수학: 20
영어: 50
평균점수는 36 입니다

 

  - 제너릭

    1) 코드를 작성하다보면 다양한 타입에 동일한 로직을 적용시킬 때가 많은데 매번 Any타입으로 받는 것은 타입 안정성을 저하시킬 수 있다.(오버로딩 얘기인가?)

    2) 클래스 내부에서 사용할 자료형을 인스터스로 생성할 때 고정하는 방식으로 컴파일 시간에 자료형을 검색해 적당한 자료형을 선택할 수 있도록 함

    3) 객체 자료형의 안정성이 높아지고 형 변환의 번거로움이 줄어듦

  - 제너릭 예시

fun <T> test(arr: Array<T>, data: T): Int {
    for(i in arr.indices) {
        if(arr[i] == data) return i
    }
    return -1
}

fun main() {
    val obj1: Array<String> = arrayOf("c", "java", "kotlin")

    val index = test<String>(obj1, "kotlin")
    println(index)
}

  <출력결과>

2

 

7. Single-expression function

  - 하나의 메소드를 간결하게 표현할 수 있는 방법(= 람다식)

  - Kotlin의 람다식 구조

{매개변수1, 매개변수2... -> 
    코드
}

  - 예시

// 함수를 람다식으로 정의하는 방식
fun add(num1:Int, num2:Int, num3:Int) = (num1+num2+num3)/3

// 메소드를 선언하지 않고 람다식으로 로직을 저장하는 방식
var add = {num1: Int, num2: Int, num3: Int -> (num1+num2+num3) / 3}

 

 

8. 싱글턴

  - 메모리 전역에서 유일한 객체임을 보장하고 위치정보가 고정됨

  - 프로그램이 실행되는 시점에 메모리에 바로 로드해서 위치를 잡음

  - Kotlin의 싱글턴 구현 방법은 companion, object 키워드를 사용하는 것

  - 사용하는 이유

    1) 전역적으로 활용할 수 있어서 다른 클래스에서 쉽게 접근 가능

    2) 전역에서 공통적으로 사용하는 정보이기 때문에 메모리를 더욱 효율적으로 활용가능

    3) 객체 자원간의 충돌 방지 가능

  - 예시1

// 생성자 호출 없는 경우의 싱글턴

// 객체 생성 없이 바로 클래스의 정보에 접근함
fun main() {
    Bird.fly("참새")
}

object Bird {
    fun fly(name:String) {
        println("${name}가 날아요~")
    }
}

  <출력결과>

참새가 날아요~

 

  - 예시 2

// 생성자 호출이 있는 경우의 싱글턴

fun main() {
    // trash와 같이 생성자에 매개변수 전달 가능
    var singletonObject1 = MySingletonClass.getInstance(trash = 1)
    singletonObject1.setNum(5)
    println("num값은: ${singletonObject1.getNum()}")

    // singletonObject2에서 num을 10으로 대입
    var singletonObject2 = MySingletonClass.getInstance(trash = 1)
    singletonObject2.setNum(10)

    // singletonObject1의 num이 10으로 출력됨
    // singletonObject1과 singletonObject2는 같은 객체를 공유하기 때문
    println("num값은: ${singletonObject1.getNum()}")

}

class MySingletonClass private constructor() {
    private var num:Int = 0

    companion object {
        @Volatile private var instance: MySingletonClass? = null
        private var trash = 0

        fun getInstance(trash: Int): MySingletonClass {
            this.trash = trash
            // 외부에서 요청왔을때 instance가 null인지 검증
            if(instance == null) {
                // synchronized로 외부 쓰레드의 접근을 막음
                synchronized(this) {
                    instance = MySingletonClass()
                }
            }
            return instance!!
        }
    }
    
    fun setNum(num: Int) {
        this.num = num
    }

    fun getNum(): Int{
        return this.num
    }
}

  <출력결과>

num값은: 5
num값은: 10

 

반응형