2023. 12. 12. 20:30ㆍ스파르타/Kotlin
4주차 '객체지향 프로그래밍의 심화' 강의 내용 정리
8-1. 접근제한자
변수나 메소드의 접근을 제한할 수 있다.
코틀린에서는 public, private, internal, protected 로 접근을 제한한다.
객체를 이용해서 변수나 메소드를 호출할 수 있는 지의 여부를 접근이라고 한다.
용어 정리
프로젝트 : 최상단 개념이고 <모듈, 패키지, 클래스> 를 포함한다.
모듈 : 프로젝트 아래의 개념이고 <패키지, 클래스> 를 포함한다.
패키지 : 모듈 아래의 개념이고 <클래스> 를 포함한다.
public : 명시하지 않으면 기본적으로 public 이다. (어디서나 접근 가능)
private : 동일한 클래스 내부에서만 접근할 수 있다.
internal : 같은 모듈 내부에서만 접근할 수 있다.
protected : 기본적으로 private 이지만 상속을 받은 경우에 타 모듈에서 접근할 수 있다.
접근제한자가 필요한 이유
접근권한을 통해 데이터에 무분별한 접근을 막을 수 있다.
클래스들간에 접근하면 안되는 상황을 구분하기 때문에 향후 유지보수를 하기 용이하다.
8-2. 예외 처리
프로그램을 실행하기 전에 알 수 있는 컴파일 에러를 오류라고 한다.
프로그램을 실행하는 도중에 발생하는 런타임 에러인 예외가 발생할 수 있다.
실행 도중에 예외가 발생하면 프로그램이 비정상적으로 종료된다.
코틀린은 try-catch 와 throw 로 예외를 처리한다.
try-catch 기본 구조
fun method1() {
try {
예외가 발생할 가능성이 존재하는 코드
} catch(예외종류) {
예외가 발생했을때 처리할 코드
}
}
throw 기본 구조
fun method1(num1:Int) {
if(num1 > 10) {
throw 예외종류
}
}
예외 처리가 필요한 이유
고품질의 프로그램이란 사용성을 해치지 않아야 한다.
여러 측면의 사용성이 있지만 프로그램이 도중에 종료되는 것은 심각한 문제이다.
미리 예외를 생각하고 소스코드를 작성해야 안정성을 높인 프로그램이라 할 수 있다.
예를 들어 숫자를 입력받아서 더하는 프로그램인데 실수로 문자를 입력했다면 예외를 처리해야 한다.
다른 예시로 사진을 다운로드 받는 도중 인터넷이 끊긴다면 예외를 처리해야 한다.
또 다른 예시로는 수술을 받고 있는데 메인 전력이 끊겼다면 보조 전력을 사용하도록 예외를 처리해야 한다.
예시 코드 1
예외를 처리한 상황 (try-catch)
while(true) {
try {
var num1 = readLine()!!.toInt()
println("내가 입력한 숫자는 ${num1}입니다")
break
} catch(e:java.lang.NumberFormatException) {
println("숫자를 입력하세요")
}
}
입력한 내용을 toInt 메소드로 정수 변환할 때 예외가 발생한다.
숫자를 입력할 때까지 반복문을 무한으로 실행하는 코드이다.
break 는 가장 가까운 반복문을 탈출시켜 주는 키워드이다.
예시 코드 2
예외를 처리한 상황 (try-catch-finally)
while(true) {
try {
var num1 = readLine()!!.toInt()
println("내가 입력한 숫자는 ${num1}입니다")
break
} catch(e:java.lang.NumberFormatException) {
println("숫자를 입력하세요")
} finally {
println("키보드와의 연결은 정상적입니다")
}
}
예외 처리와 관계없이 항상 실행하는 코드를 finally 에 작성한다.
* 실제 예시 1) USB와 연결하는 코드는 반드시 사용후에 연결을 끊어야 한다. (자원낭비)
* 실제 예시 2) GPS를 사용하는 코드는 반드시 사용후에 연결을 끊어야 한다. (자원낭비)
8-3. 지연초기화
코틀린은 클래스를 설계할 때 안정성을 위해 반드시 변수의 값을 초기화 할 것을 권장한다.
클래스를 설계할 때 초기의 값을 정의하기 난처해서 나중에 대입하기 위한 문법이다.
코틀린은 지연초기화 또는 늦은초기화를 위해 lateinit, lazy 키워드를 활용한다.
저사양으로 제한되어 있는 환경에서는 메모리를 더욱 효율적으로 사용할 수 있다.
예시 코드 1
변수의 지연초기화
fun main(){
var s1 = Student()
s1.name = "참새"
s1.displayInfo()
s1.age = 10
s1.displayInfo()
}
class Student {
lateinit var name:String
var age:Int = 0
fun displayInfo() {
println("이름은: ${name} 입니다.")
println("나이는: ${age} 입니다.")
}
}
name 변수 값을 초기에 정의하기 어렵기 때문에 lateinit 을 사용한다.
물론, ""과 같이 공백으로 처리할 수 있지만 가독성 측면에서 좋은 행위는 아니다.
class Student {
lateinit var name:String
var age:Int = 0
fun displayInfo() {
if(this::name.isInitialized) {
println("이름은: ${name} 입니다.")
println("나이는: ${age} 입니다.")
} else {
println("name변수를 초기화해주세요.")
}
}
}
변수를 사용하기 전에 초기화 되었는지 확인해야 안정성을 높일 수 있다.
isInitalized 를 활용해서 값이 초기화 되었는지 확인할 수 있다. (true/false)
사용할 때는 값이 아니라 참조 형태로 사용해야 하기 때문에 this:: 또는 :: 을 붙인다.
예시 코드 2
상수의 지연초기화
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} 입니다.")
}
}
lazy 키워드를 활용해서 지연초기화를 한다.
상수를 사용하는 시점에 값을 대입하고 초기화가 수행된다.