GDSC HUFS 4기/Kotlin Team #2

[2팀] 코틀린 기초 더 배우기(2)

Soongle 2022. 10. 5. 11:53

이 글은 이것이 안드로이드다 with 코틀린(개정판)를 참고하여 작성하였습니다.

작성자 : 김용순

개발환경은 Windows, Android Studio입니다.


1. 람다식

람다 표현식(lambda expression) : 이름이 없는 함수, 함수 리터럴(function literal)이다.

선언되지 않고 표현식 자체로 전달된다.

함수를 재사용하지 않을 때 유용하게 사용할 수 있다.

 

예시

fun addnNumber(a: Int, b: Int): Int {
    return a + b
}

위 함수를 아래와 같이 람다 표현식으로 작성할 수 있다.

val sum: (Int, Int) -> Int = { a: Int, b: Int -> a + b }

앞의 부분을 생략해서 더욱 짧게 표현할 수 있다.

val sum = { a: Int, b: Int -> a + b }

 


2. 접근 제한자

접근 제한자 : 클래스, 인터페이스, 프로퍼티를 제한하는데 사용하는 키워드이다.

 

public : 프로젝트 어디서든 접근 가능

코틀린에서의 기본 제한자이다(다른 제한자가 명시되지 않으면 자동으로 public이 된다.)

파일 맨 위에 위치한다.

public class Example {
}

public fun hello()

public val x = 5

 

private : 프로퍼티, 필드 등이 선언된 블록에서만 요소에 접근 가능

스코프 밖으로의 접근을 막는다.

private 패키지는 그 특정 파일 내에서만 접근할 수 있다.

fun main() {
    var ex = Example()
    ex.x = 2 // private property -> 실행 되지 않는다.
    ex.hello() // private method -> 실행 되지 않는다.
}

private class Example {

    private var x = 1

    private fun hello() {
        println("hello")
    }
}

 

open

코틀린에서 모든 클래스는 기본값이 final로 되어있기때문에 상속받을 수 없다. ➡️자바와는 반대이다.

코틀린에서 상속을 사용하려면 클래스를 open으로 만들어야 한다.

 

protected

안에있는 클래스, 서브 클래스에서만 접근 가능

(private + 서브 클래스 에서 까지 사용 가능하다.)

서브 클래스 안의 오버라이딩한 protected 선언은 변경을 명시하지 않는 protected이다.

Top-level에 선언될 수 없다(패키지는 protected가 될 수 없다.)

open class Base {
    open protected val i = 0 // Derived에서 override하기 위해 open으로 지정해야한다.
}

class Derived: Base() {
    fun getValue(): Int {
        return i
    }
    override val i = 10 // Base 클래스의 i를 override한다.
}

 

internal

자바에 없는 코틀린만의 기능

시행된 모듈 안에서만 필드가 보이게 한다.

 

코틀린에서 제한하는 모듈의 범위

  • IntelliJ IDEA 모듈
  • Maven / Gradle 프로젝트
  • 하나의 Ant 태스크 내에서 함께 컴파일되는 파일들

*프로젝트 > 모듈 > 패키지

internal class Example {
    internal val x = 5
    
    internal fun hell() {
    }
}
internal val y = 10

 

접근 제한자 사용 예시

open class Base() {
    var a = 1 // public by default
    private var b = 2 // private to Base class
    protected open val c = 3 // visible to the Base and the Derived class
    internal val d = 4 // visible inside the same module
    protected fun e() {} // visible to the Base and the Derived class
}

class Derived: Base() {
    // a, c, d, and e() of the Base class are visible
    // b is not visible
    override val c = 9 // c is protected
}

fun main(args: Array<String>) {
    val base = Base()
    // base.a and base.d are visible
    // base.b, base.c and base.e() are not visible
    
    val derived = Derived()
    // derived.c is not visible
}

 

정리

클래스, 인터페이스, 프로퍼티 패키지
private 해당 클래스, 인터페이스 내부 해당 .kt 파일 내부에서만
protected private + subclass 까지 X
internal 같은 모듈 내부 같은 모듈 내부
public 어디서든 사용 가능 어디서든 사용 가능

 


3. 중첩, 내부 클래스

중첩 클래스(Nested class) : 다른 클래스 안에 생성된 클래스

default 값 static이다

    ➡️ 데이터 멤버와 멤버 함수는 클래스 객체를 생성하지 않고도 접근할 수 있다.

외부 클래스의 데이터 멤버에 접근할 수 없다.

    외부 클래스 ➡️ 중첩 클래스 ✅

    중첩 클래스 ➡️ 외부 클래스

class OuterClass {
    private var name: String = "Mr X"
    
    class NestedClass {
        var description: String = "code inside nested class"
        private var id: Int = 101
        fun foo() {
            // print("name is ${name}") // cannot access the outer class member
            println("Id id ${id}")
        }
    }
}

fun main(args: Array<String>) {
    //nested class must be initialized
    println(OuterClass.NestedClass.description) // accessing property
    
    var obj = OuterClass.NestedClass() // object creation
    obj.foo() // access member function
}

// Output
// code inside nested class
// Id is 101

 

내부 클래스(Inner class) : inner키워드를 사용해 만든 다른 클래스 안의 클래스

inner라고 표시된 중첩 클래스가 내부 클래스이다.

내부 중첩 클래스가 아닌 곳 or 인터페이스 안에 선언될 수 없다.

외부 클래스 멤버가 private라도 접근할 수 있다.

외부 클래스의 객체가 먼저 생성되어야 내부 클래스 객체가 생성될 수 있다.

class OuterClass {
    private var name: String = "Mr X"
    inner class InnerClass {
        var description: String = "code inside inner class"
        private var id: Int = 101
        fun foo() {
            println("name is ${name}") // access the private outer class member
            println("Id is ${id}")
        }
    }
}

fun main(args: Array<String>) {
    println(OuterClass().InnerClass().description) // accessing property
    
    var obj = OuterClass().InnerClass() // object creation
    obj.foo() // access member function
}

// Output
// code inside inner class
// name is Mr X
// Id is 101

의문점

// 중첩 클래스와 내부 클래스 사용 방법의 차이
OuterClass.NestedClass().description
OuterClass().InnerClass().description

// 내부 클래스는 외부 클래스의 객체를 참조하기 때문일것 같긴하다...
// OuterClass.~~~ : 외부 클래스
// OuterClass().~ : 외부 클래스 객체
// 내부 클래스가 인터페이스 안에 선언될 수 없는것도 이런 이유 때문일 것이다.

// 스터디때 토의해보기

 


4. Safe Cast, Unsafe Cast 연산자

Cast : 특정 타입의 것을 다른 타입으로 변환하는것

 

Unsafe cast

변수를 캐스트하지 못하고 예외처리되는 경우

as 연산자 사용한다.

fun main(args: Array<String>) {
    val obj: Any? = null
    val str: String = obj as String
    println(str)
}

// Output
//Exception in thread "main" kotlin.TypeCastException: null cannot be cast to non-null type kotlin.String.

null 값을 String으로 캐스트할 수 없기 때문에 예외처리된다.

따라서 코드를 아래와 같이 고쳐야 한다.

fun main() {
    val obj: Any? = null
    val str: String? = obj as String?
    println(str)
}

 

Safe cast

as? 연산자를 사용한다.

캐스팅 할 수 없을 때 예외처리 대신에 null 값을 반환한다.

따라서 Unsafe cast 보다 Safe cast를 사용하는것이 더 바람직하다.

fun main() {
    val location: Any = "Kotlin"
    val safeString: String? = location as? String
    val safeInt: Int? = location as? Int
    println(safeString)
    println(safeInt)
}

// Output
// Kotlin
// null

 


5. Try-Catch 문으로 예외 처리하기

Exception : 프로그램의 runtime-problem이고, 프로그램 종료를 일으킨다.

  • 저장 공간 부족
  • 배열 범위를 벗어남
  • 0으로 나눔

이런 Exception 문제를 처리하기 위해  예외 처리(Exception handling)를 사용한다.

 

Throable Class : 예외를 던지게 해 준다.

throw MyException("this throws an exception")

 

예외 처리에서의 4가지 키워드

  • try

예외를 발생시킬 수 있는 구문을 포함한다.

뒤에 catch나 finally가 반드시 있어야 한다.

  • catch

try 블록에서 던진 예외를 잡는다.

try 블록에서 예외가 발생하면 catch 블록이 실행된다.

  • finally

예외 처리에 상관 없이 실행된다.

주로 중요한 코드 구문을 실행하는데 사용된다.

  • throw

예외를 일으키기 위해 사용한다.

오류가 어디서 발생하는지 테스트 할 때 유용하다.

 

 

Unchecked Exception

코드 실수에 의해 발생하는 예외

RuntimeException에서 확장된 것이다.

런타임동안 발생한다.

ArithmeticException 숫자를 0으로 나눔
ArrayIndexOutOfBoundException 틀린 인덱스 값으로 배열을 확인
SecurityException 보안 위반사항 발생
NullPointerException null 객체에 매서드나 프로퍼티를 호출

 

 

Checked Exception

컴파일 시간에 발생하는 예외

Throwable 클래스로 확장된다.

  • IOException
  • SQLException

 

try-catch

package eu.tutorials.kotlinbasics

fun main() {
    val str = getNumber("10.5")
    println(str)
}

fun getNumber(str: String): Int {
    return try {
        Integer.parseInt(str)
    } catch (e: ArithmeticException) {
        0
    }
}

// Output : 0

 

try 블록에서 여러 예외가 발생할 때를 위해 여러 catch 블록을 사용할 수 있다.

*이때 하위 타입의 예외를 먼저 catch 문으로 선언해야 여러 예외 처리를 수행할 수 있다.

fun main() {
    try {
        val a = IntArray(5)
        a[5] = 10 / 0 // arithmatic, arrayindexoutofbounds exception
    } catch (e: ArithmeticException) {
        println("arithmetic exception catch")
    } catch (e: ArrayIndexOutOfBoundsException) {
        println("array index out of bounds exception")
    } catch (e: Exception) {
        println("parent exception class")
    }
    println("code after try catch...")
}

// Output
// arithmetic exception catch
// code after try catch...

 

Nested try block

fun main() {
    try {
        // code block
        try {
            // code block
        } catch (e: SomeException) {
            // exception
        }
    } catch (e: SomeException) {
        // exception
    }
}

 

finally

fun main() {
    try {
        val data = 10 / 5
        println(data)
    } catch (e: NullPointerException) {
        println(e)
    } finally {
        println("finally block always executes")
    }
    println("below code...")
}

// Output
// 2
// finally block always executes
// below code...

예외 발생 여부에 상관없이 finally 블록이 실행되었다.

 

 

throw

예외를 던지는데 사용된다.

커스텀 예외를 던질 때도 사용된다.

fun main() {
    validate(15)
    println("code after validation check..")
}

fun validate(age: Int) {
    if (age < 18)
        throw ArithmeticException("under age")
    else
        println("eligible for dive")
}

// Output : Exception in thread "main" java.lang.ArithmeticException : under age