20231206 (수) Kotlin 4주차 강의

2023. 12. 6. 21:00TIL

코틀린 4주차강의 내용이다.

 

1.<접근제한자>

 

간단 용어 정리

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

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

패키지: 클래스를 포함하는 폴더(우리가 가장 많이 쓰는 곳)

 

객체를 이용 > 변수나 메소드를 호출할 수 있는지의 여부를 <접근>이라고 함

 

명령어는 4개

public: 기본적인 형태, 생략도 가능
private: 가장 강한 접근제한, 같은 파일 내에서만 접근가능
internal: 같은 모듈내라면 접근가능
protected: 위 두개의 중간정도?의 접근제한 기본적으로 private과 같음 하지만 상속을 받을경우 접근 가능

 

설명을 위한 예시)

 

클래스 파일을 하나 만든다.

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() {                  <<public: 어디서든 접근가능
        println("public 입니다")
    }
    fun publicTest2() {                          <<public이 생략된 형태
        println("public 입니다")
    }
    private fun privateTest() {               <<이는 이 파일내에서만 접근가능
        println("private 입니다")
    }
    internal fun internalTest() {             <<같은 모듈이라면 가능
        println("internal 입니다")
    }
    protected fun protectedTest() {      <<상속받은 경우에만 가능
        println("protected 입니다")
    }
}

 

제일 특이케이스 protected 

이를 상속받는 클래스코드를 짜준다.(위 클래스 코드에서 open 써주기 잊지말기)

 

class AccessTestChildClass: AccessTestClass() {   <<상속받는 명령어
    fun protectedTest1() {
        println("e의 값은 ${e}")
    }
}

이후 파일을 하나 더 만들어서

fun main() {
    var accessTestClass = AccessTestClass()
    var accessTestChildClass = AccessTestChildClass()


    accessTestChildClass.protectedTest1()    <<상속받은 클래스를 대신 적어준다.
}

 

2.<예외 처리>

 

코틀린은 실행하기전에 알수있는 에러를 컴파일에러라고한다.

실행도중 에러를 런타임에러라고 한다.

실행도중에 런타임에러(예외)가 발생하면 갑자기 종료된다.

이러한 경우를 예방하고자 예외처리 명령어가 있다.

 

예시1) try-catch 구조

while(true) {                                          <<중요. 원래라면 catch에 있는 명령을 수행하고 끝나지만
        try {                                                   while(true)명령어를 줘서 무한 반복된다.
            var num1 = readLine()!!.toInt()
            println("내가 입력한 숫자는 ${num1}입니다")    <<예외 발생 가능성이 있는 코드
            break                                                                  <<여기서 명령어를 끊어준다.
        } catch(e:java.lang.NumberFormatException) {
            println("숫자를 입력하세요")                               <예외 발생시 대신 나갈 코드
        }
    }

즉 위 코드에선 숫자를 제외한 다른언어를 쓴다면 숫자를 입력하세요 문구가 뜨면서 반복된다.

 

예시2)throw 구조

이 명령어는 예외발생시 종료되는것만 방지하는 명령어이다.

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

 

예시3)try-catch-finally

while(true) {
        try {
            var num1 = readLine()!!.toInt()
            println("내가 입력한 숫자는 ${num1}입니다")
            break
        } catch(e:java.lang.NumberFormatException) {
            println("숫자를 입력하세요")
        } finally {                    <<여기가 위 코드와 다른점인데, 예외가 발생하는것과 관계없이 지속적으로 출력된다.
            println("키보드와의 연결은 정상적입니다")                    <<어떤걸 입력하든 이 문구는 지속출력된다.
        }
    }

 

3.<지연초기화>

 

초기화라고 하니까 조금 안와닿았는데 쉽게풀어서 값을 지정하는데, 그걸 지연시켜준다는 의미같다.

명령어로는 변수는 lateinit, 상수는 lazy로 지연초기화한다.

 

lateinit, isInitialized포함 예시)

 

fun main(){
    var s1 = Student()

 

    s1.age = 10                          <<여기서 age에 값을주고       
    s1.displayInfo()                    <<아래문구를 불러오지만 name값이 없기에 false문구가 나온다.


    s1.name = "참새"                 <<여기서 네임에 값을 주고,
    s1.displayInfo()                    <<여기서 아래 문구를 다시 불러오면 정상적으로 출력이된다.


}
class Student {
    lateinit var name:String       <<원래는 var name:String = "" 이런식으로 썼지만 lateinit명령어를 넣음으로써 생략
    var age:Int = 0

    fun displayInfo() {
if(this::name.isInitialized) {      <<isinitialized를 사용해서 값이 초기화 되었는지 확인하는 절차를 가짐
        println("이름은: ${name} 입니다.")
        println("나이는: ${age} 입니다.")    <<값이 초기화 되었다면 이 문구들이 나오고
} else {
println("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 {    <<by lazy를 붙여 지연초기화를 하겠다 라는 의미
        println("address 초기화")     <<처음엔 이 문구가 나온다.
        "seoul"                                 <<이후 지연초기화 돼서 address="seoul"이란 의미가 된다.
    }
    fun displayInfo() {
        println("이름은: ${name} 입니다.")
        println("나이는: ${age} 입니다.")
        println("주소는: ${address} 입니다.")
    }
}

이해를 돕기위한 이 코드의 결과물

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

 

 

4.<널 세이프티>

 

Null이란 뭔가의 누락을 의미하는 듯 싶다.

이는 프로그램의 가용성을 많이 저하시킨다.

따라서 ? , !! , ?. , ?: 이 4개명령어로 Null예외로부터 대처한다.

이중 !!는 Null이 아니다라는 강조의미기 때문에 Null인 데이터에 !!를 붙여버리면 큰오류가 나는듯 싶다.

때문에 사용에 주의할 것.

이제 예시를 보자

 

? 예시)

fun main(){
    var s = Student()
    s.name = "참새"
    s.address = "서울"
    s.displayInfo()
}
class Student {
    lateinit var name:String
    var address:String? = null            <<여기서 String? 이건 address 부분에 널이 들어갈수도 있다 라는 의미
    
    fun displayInfo() {
        println("이름은: ${name} 입니다")
        println("주소는: ${address} 입니다")
    }
}

 

!!예시)

fun main(){
//  var data = readLine()!!.toInt()

    var inputData = readLine()!!    <<readLine은 코드 실행 후 직접입력한다는 의미 즉 Null이 들어가지 않는다.
    var data = inputData.toInt()
    println("Null아닌 값: ${data}")
}

 

?. (안전호출연산자)예시)

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() {
        println("주소의 길이는: ${address?.length} 입니다")   <<여기서 ?를 뺀 address.length를 쓰면 오류가 나서 실행이 안된다.
    }
}

 

?: (엘비스 연산자)예시)

 

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() {
        println("주소의 길이는: ${address?.length ?: "초기화하세요"} 입니다")   <<만약 Null이 뜨면 초기화하세요 문구가 대신 나온다.
    }
}

 

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

 

5.<배열>

 

배열은 예시를 먼저 적음

// arrayOf메소드를 호출하면 배열을 리턴해줍니다
// 1,2,3,4,5 각각을 저장한 변수 5개를 배열형태로 arr에 저장합니다
var arr = arrayOf(1,2,3,4,5)   <<arr은 변수이름, arrayOf를 써서 배열을 짜줌

// 배열요소를 모두 출력합니다
println(Arrays.toString(arr))

// 배열의 첫번째 요소에 저장된 값을 출력합니다
// var num1 = 1의 num1과 arr[0]은 동일합니다
// arr[0]은 하나의 변수로 취급할 수 있습니다
// arr은 0~4번방(인덱스)까지 접근할 수 있습니다  <<배열은 첫번째가 0번째이다.
println(arr[0])

 

만약 배열을 쓰지못한다면 위에 1,2,3,4,5는 따로따로 변수이름, 값을 지정해줘야한다.

이로써 좀더 효율적으로 코드를 작성할수있다.

 

예시하나더)

배열을 불러오고 이름과 값을 이어주는 명령어

fun main() {
    var kors = arrayOf(90, 94, 96)
    for((idx, kor) in kors.withIndex()) {  <<for은 반복명령어 ,idx는 순서, kor은 배열되어있는 값(90, 94, 96)들로 지정해준다는 의미
        println("${idx}번째 국어 점수는 ${kor}입니다")  <<바로 윗줄이 없으면 제대로 나오지 않는다.
    }
}

 

6.<컬렉션>

 

코틀린의 컬렉션 사용법 추가지식

array와 대부분 비슷하지만 수정이 용이하다는 특징이 있는듯하다.

 

첫번째 List

// 읽기전용 리스트입니다
// 0번, 1번, 2번 인덱스에 접근해서 값을 변경할 수 없습니다
var scores1 = listOf(값1, 값2, 값3)   <<읽기전용이라 수정이 불가능한 listOf

// 수정가능 리스트입니다
// 0번, 1번, 2번 인덱스에 접근해서 값을 변경할 수 있습니다
var scores2 = mutableListOf(값1, 값2, 값3)   <<특정명령어를 써서 수정이 가능함
scores2.set(인덱스, 값)                   <<리스트이름.set(수정할 순서, 수정할 값) 이런식

// 수정가능 리스트입니다
// 0번, 1번, 2번 인덱스에 접근해서 값을 변경할 수 있습니다
// array로 데이터들을 저장하는 ArrayList도 mutableListOf와 동일하게 사용할 수 있어요
// 저장할 데이터의 자료형을 < > 안에 지정해야 사용할 수 있어요
var scores3 = ArrayList<자료형>(값1, 값2, 값3)   <<위와 비슷하지만 자료형을 지정해줘야하나봄(아직 100프로 이해는 안된 상태)
scores3.set(인덱스, 값)

 

두번째 Map

// 읽기전용 맵입니다
    // 변수명[키]로 데이터에 접근할 수 있습니다
    var scoreInfo1 = mapOf("kor" to 94, "math" to 90, "eng" to 92)  <<각각의 값에 짝을 지어주는 형식
    println(scoreInfo1["kor"])                                                             <<kor에 대한 값을 불러오는 형식이라 결과는 90

    // 수정가능 맵입니다
    // 변수명[키]로 데이터에 접근할 수 있습니다
    var scoreInfo2 = mutableMapOf("kor" to 94, "math" to 90)   <<위와 같지만 수정이 가능함
    scoreInfo2["eng"] = 92                                                           <<윗줄에 "eng" to 92라는 값을 추가한것과 같음
    println(scoreInfo2["eng"])                                                       <<eng에 대한 값을 불러오는 형식이라 결과는 92

    // 맵의 키와 값을 동시에 추출해서 사용할 수 있습니다
    for((k,v) in scoreInfo2) {                                                         <<반복문이고 앞의 (k,v)는 idx와 달리 특정 이름이 있는건 아님
        println("${k}의 값은 ${v}입니다")                                       <<k는 값의 짝, v는 값을 불러옴
    }

위 부분의 결과를 보다면 이런식

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

 

세번째 Set

Set은 특이하게 순서가 정해져 있지 않음

//  읽기전용 Set입니다.
    var birdSet = setOf("닭", "참새", "비둘기")

//  수정가능 Set입니다.
//  var mutableBirdSet = mutableSetOf("닭", "참새", "비둘기")      <<다른 명령어와 비슷하게 앞에 mutable을 붙임
//  mutableBirdSet.add("꿩")                                                          <<순서가 없으니 add를 사용해서 쉽게 추가
//  mutableBirdSet.remove("꿩")                                                    <<remove를 사용해 없앰
    println("집합의 크기는 ${birdSet.size} 입니다")                         <<Set의 갯수를 세어줌

    var findBird = readLine()!!

    if(birdSet.contains(findBird)) {
        println("${findBird} 종류는 존재합니다.")
    } else {
        println("${findBird}는 존재하지 않습니다.")
    }

 

 

7. <Single-expression function>

 

하나의 메소드를 간결하게 표현하는 방법이다.

나중에 배울 내용인지 4주차강의에서는 간결하게 설명하고 넘어갔다

 

예시)

세개의 숫자 평균을 리턴해주는 함수

var add = {num1: Int, num2: Int, num3: Int -> (num1+num2+num3) / 3}
println("평균값은 ${add(10,20,30)}입니다")

 

뭐 return, answer 등등 말그대로 좀 늘어질수있는 코드를 간단명료하게 표현한 것.

최적화할때 좋을것 같다.(하지만 아직 최적화를 시도하기에 이른듯)

 

 

8.<싱글턴>

 

이도 4주차에서는 아예 동영상조차 없다. 일단은 넘어가되

5주차 영상을 보고난뒤 만약 이해된다면 다시 정리하러 와야겠다.

 

예시)

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!!
            
//            엘비스연산자와 뒷장에서배울 scope function을 이용하면
//            아래와같이 더욱 직관적인 코드 작성이 가능합니다
//            return instance ?: synchronized(this) {
//                // also는 호출한 객체를 it으로 넘김
//                // instance가 null이라면 새로 생성하고 아니면 무시함
//                instance ?: MySigletonClass().also {
//                    instance = it
//                }
//            }
        }
    }
    fun setNum(num: Int) {
        this.num = num
    }

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

 

오후 7시) 박찬규 튜터님에게 알고리즘 줌 특강을 들었다.

주된 내용은 알고리즘을 풀어나가는 방법

튜터님의 말로는 한줄한줄 단계별로 풀어나가는게 좋다 하셨다.

그리고선 추가로 알고리즘 학습하기 좋은 사이트를 하나 알려주셨다.

https://www.acmicpc.net/step

 

오늘의 한마디 : 음 딱히 적을 말은 없다. 일단 해보자!