예외 처리의 활용

 

Kotlin은 try - catch  throw 프로그램 실행 도중에 발생하는 예외를 적절하게 처리한다.

 

try-catch의 기본 구조

try {
	예외가 발생할 가능성이 존재하는 코드
} catch(예외종류) {
	예외가 발생했을 때 처리할 코드
}

 

throw의 기본 구조

if() {
	throw 예외종류
}

 

throw는 try-catch와 다르게 사후처리할 코드는 없고 에러로 인한 비정상적인 종료를 막고 에러 정보를 알려준다.

 

예외 처리 예제

숫자를 입력받으면 그 숫자 출력 그 외 잘못된 입력값은 숫자를 입력하라는 예외처리문구를 띄워준다.

while(true) {
	try {
    	var num = readLine()!!.toInt()
        println("${num}")
        break
    } catch(e:java.lang.NumberFormatException) {
    	println("숫자를 입력해주세요.")
    }

 

try-catch-finally 

예외 처리와 관계없이 항상 실행하는 코드를 finally에 작성한다.

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

 

접근제한자

 

Kotlin에서는 public, private, internal, protected로 변수나 메소드의 접근을 제한할 수 있다.

 

여기서 접근이란, 객체를 이용해서 변수나 메소드를 호출할 수 있는지의 여부이다.

 

public 명시하지 않으면 기본적으로 public 이다. (어디서나 접근 가능)

private 동일한 클래스 내부에서만 접근할 수 있다.

internal 같은 모듈 내부에서만 접근할 수 있다.

protected 기본적으로 private이지만 상속을 받은 경우에 타 모듈에서 접근할 수 있다.

 

우선 프로젝트의 구조를 알아보자.

프로젝트(Project) 는 최상단의 개념으로 <모듈,패키지,클래스>를 포함한다.

모듈(Module) 프로젝트 아래의 개념으로 <패키지, 클래스>를 포함한다.

패키지(Package) 모듈 아래의 개념으로 <클래스>를 포함한다. 우리가 일반적으로 알고 있는 디렉토리이다.

 

접근제한자의 필요이유?

접근권한을 통해 데이터에 무분별한 접근을 막을 수 있다.

클래스들간에 접근하면 안되는 상황을 구분하기 때문에 향후에 유지보수하기 용이하다.

상속

부모 클래스에서 중복되는 메소드 가져오기

Chicken의 fly 메소드 사용이 가능해졌다. 상속받으려면 부모클래스에서 open해주어야 한다.

open class Bird {
	fun fly()
}

class Chicken : Bird() {

}

 

override 오버라이딩

부모클래스에서 특정메소드 커스텀해서 쓰고 싶을때 사용.

open class Bird(name) {
	open fun fly() {
    }
}


class Chicken(name: String, age: Int) : Bird(name) {
	override fun fly() {
    }
}

 

overload 오버로드

매개변수의 갯수 또는 자료형만 다른녀석인데 동일한 이름으로 쓰면 안되나 해서 나온 녀석(가능하다.)

class calculator {
	fun add(num1:Int, num2:Int){
    }
    	fun add(num1:DouBle, num2:DouBle){
    }
}

 

Interface 인터페이스 

코틀린은 반드시 한 개의 부모클래스만 상속받을 수 있다.

따라서 근본적인 공통점을 상속 받고, 추가적인 기능들은 인터페이스로 추가한다.

추상 메소드 -> 로직이 존재하지 않고 이름만 존재할 때 추상 메소드라 부른다.

기본적으로 인터페이스는 추상메소드만 작성하는게 원칙이나 최근에는 아니여도 괜찮다는 흐름.

왜 추상메소드로 작성하냐 -> 어차피 자식 클래스에서 오버라이딩해서 작성할거니까

interface 인터페이스이름 {
	fun 메소드이름()
}
class Duck(name: String) : Bird(name), WaterBirdBehavior {
	override fun swim() {
    }
}

 

메소드 설계

코틀린의 메소드 기본 구조

fun 메소드이름(변수명:자료형, 변수명:자료형 ....) : 반환자료형 {
	소스코드 로직
}

 

 

클래스 설계

클래스

정보(Property) 와 행위(Method)를 포함한다.

 

데이터 클래스 (data class)

data class 클래스이름 {
	정보1
    	정보2
}

정보(Property)만 가지는 클래스 + 유용한 메소드 자동생성

 

실드 클래스 (sealed class)

sealed class 부모클래스 {
	class 자식클래스1 : 부모클래스생성자
    	class 자식클래스1 : 부모클래스생성자
}

클래스 상속과 관련된 개념 + 상속받을 수 있는 자식클래스를 미리 정의 + 무분별한 상속방지 + 컴파일 시점에 생성가능 자식 알 수 있기에 효율적으로 다형성을 구현한다.

 

오브젝트 클래스 (object class) 

 

Java의 static 대신 사용하는 키워드

프로그램을 실행하는 동시에 인스턴스화한다.

 

 

생성자 (Constructor)

 

init 주 생성자 사용예시 -> 반드시 하나의 형태 _name, _hairColor, _height 만을 사용하는 형태라면 사용

class Character(_name:String, _hairColor:String, _height:Double){
	//매개변수를 직접 넘기지 않는다.
    init {
    	this.name = _name
        this.hairColor = _hairColor
        this.height = _height
    }
}

 

constructor 부 생성자 사용예시 -> 여러 형태의 생성자 사용 가능  _name || _hairColor || _height

class Character {
	// 명시적 생성자 (Constructor)
    constructor(_name:String, _hairColor:String, _height:Double) {
    	name = _name
        hairColor = _hairColor
        height = _height
    }
    constructor(_name:String, _hairColor:String) {
    
    }
    constructor(_name:String, _hairColor:String, _weight:Double) {
    
    }
}

 

https://school.programmers.co.kr/learn/courses/30/lessons/134240

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

 

class Solution {
    fun solution(food: IntArray): String {
        var answer: String = ""
        var numArr = food.slice(1..food.size-1).map{it/2}
        var foodList = mutableListOf<Int>()
        
        for(i in numArr.indices) {
            for(j in 0 until numArr[i]) {
                if(numArr[i] != 0) {
                    foodList.add(i+1)
                }
            }
        }
        
        answer = foodList.joinToString("") + '0' + foodList.reversed().joinToString("")
        return answer
    }
}

 

코드를 먼저 보자.

 

우선 , 물을 제외하고 음식 부분의 배열만 먼저 slice로 가져온 뒤 2로 나눠 정수 부분으로 가져왔다.

 

그 후 정수 숫자 만큼 인덱스 숫자를 찍어주고 나온 리스트를 '0'과 그 역순의 리스트와 합쳐줘서 반환해준다.

 

별로 어렵지 않은 문제였다.

 

다만 코틀린스럽게 풀지는 못했다...

내가 제출한 코드

class Solution {
    fun solution(numbers: IntArray): IntArray {
        var answer: IntArray = intArrayOf()
        var intset = mutableSetOf<Int>()
        
        for(i in 0 until numbers.size - 1) {
            for(j in i+1 until numbers.size) {
                intset.add(numbers[i] + numbers[j])
            }
        }
        answer = intset.sorted().toIntArray()
        return answer
    }
}

 

이중for문을 이용해서 더해주었고 원소가 중복되면 안되기에 Set를 사용했다.

 

내 생각 코틀린스럽게 푼 사람 (사실 코틀린스럽다는 말을 정확히 몰라 틀릴 수 있음,,)

뭔가 코틀린 함수 쓴게 코틀린스러운건가,,ㅎㅎ

다른 분의 코드

class Solution {
    fun solution(numbers: IntArray): IntArray {
        val list = numbers.toList()
        return list.withIndex().flatMap { i -> list.withIndex().map { j -> i to j } }
            .filter { it.first.index != it.second.index }
            .map { it.first.value + it.second.value }
            .toSortedSet()
            .toIntArray()
    }
}

 

분석해보면 공부에 도움이 될거 같아 분석한번 해보겠습니다.

 

분석에 앞서 필요한 확장함수를 조금 알아보고 가자.

.withIndex() -> 객체의 원소를 (index, value) 형태로 접근할 수 있게 해준다.

예) list = listOf("a", "b", "c")

      indexedList = list.withIndex() 

      print(indexedList)

[IndexedValue(index=0, value=a), **IndexedValue(index=1, value=b)**, IndexedValue(index=2, value=c)]

 

이런식으로 출력된다.

 

.flatMap -> 컬렉션 안에 컬렉션이 있을 경우 내부 컬렉션을 펼쳐주는 연산이며 .map과 다르게 1대1매핑이 아닌 1대 多 매핑이 가능하다. 즉 모든 요소들의 생성값을 다룰 수 있다. 

 

.filter -> 간단히 필터 조건문안에 조건을 만족하는 원소들만 받아서 가져오겠다는거다.

 

이제 코드를 하나하나 뜯어보자. (너무 깊숙이 뜯진 못한다,,)

 

val list = numbers.toList()

numbers를 리스트 형태로 바꾸어 list에 담아준다.

 

list.withIndex().flatMap { i -> list.withIndex().map { j -> i to j } }

 

이 부분이 이해하기 굉장히 어려웠던 부분이었다. 사실 완벽하게 이해한 것인지는 모르나 틀렸을 경우 댓글부탁드립니다.

우선 문제의 첫 번째 입출력 예인 [2,1,3,4,1] 을 입력값으로 해서 예시로 사용해보겠다.

 

withIndex() 를 통해 [(0,2) (1,1) (2,3) (3,4) (4,1)] 이런식의 원소의 인덱스와 실제 value가 포함된리스트가 반환된다.

 

flatMap() 을 통해 이 안에 원소들을 1대 다 매핑이 가능하게 만들어줌과 동시에 마지막엔 싱글리스트로 반환해준다.

.map{} 안에 "i to j"는 i와 j를 pair시켜주는 역할이다. 

 

[(0,2) (1,1) (2,3) (3,4) (4,1)] 이 리스트의 i로 돌아가는 반복문과 list.withIndex().map의 j 인덱스로 돌아가는 반복문으로 

j가 i를 기준으로 각 원소를 순회하며 i to j를 실행한다. 가독성을 위해 많은 띄어쓰기를 넣었습니다...

 

[(0,2) (0,2)    ,     (0,2) (1,1)    ,    (0,2) (2,3)   ,   (0,2) (3,4)    ,  (0,2) (4,1)]

 

마찬가지로 다음 i 원소인 (1,1)을 기준으로 실행

[ (1,1) (0,2)   ,     (1,1)  (1,1)    ,   (1,1) (2,3)   ,   (1,1) (3,4)    ,    (1,1)  (4,1)]

 

이런식으로 하면 반복하면 

[(0,2) (0,2)    ,     (0,2) (1,1)    ,    (0,2) (2,3)   ,   (0,2) (3,4)    ,  (0,2) (4,1)

(1,1) (0,2)   ,      (1,1)  (1,1)   ,    (1,1) (2,3)   ,   (1,1) (3,4)    ,  (1,1)  (4,1)

(2,3) (0,2)   ,      (2,3)   (1,1)   ,   (2,3)  (2,3)  ,   (2,3) (3,4)    ,  (2,3)   (4,1)

(3,4) (0,2)   ,      (3,4)   (1,1)   ,   (3,4)  (2,3)   ,  (3,4) (3,4)    ,  (3,4)   (4,1)

(4,1) (0,2)   ,      (4,1)   (1,1)   ,   (4,1)  (2,3)   ,   (4,1) (3,4)    ,  (4,1)   (4,1)]

 

이런식의 리스트가 반환됩니다.

 

.filter { it.first.index != it.second.index }

 

서로 다른 인덱스의 있는 두 수를 더해주기 위해서는 이 리스트에서 filter를 통해 (0,2)(0,2) , (1,1)(1,1) , (2,3)(2,3) ,  (3,4) (3,4)  , (4,1)(4,1) 과 같은 인덱스 값이 같은 원소들을 제외하고 인덱스 값이 다른 원소들만 걸러줍니다.

 

[                  ,     (0,2) (1,1)    ,    (0,2) (2,3)   ,   (0,2) (3,4)    ,  (0,2) (4,1)

(1,1) (0,2)   ,                           ,    (1,1) (2,3)   ,   (1,1) (3,4)    ,  (1,1)  (4,1)

(2,3) (0,2)   ,      (2,3)   (1,1)   ,                        ,   (2,3) (3,4)    ,  (2,3)   (4,1)

(3,4) (0,2)   ,      (3,4)   (1,1)   ,   (3,4)  (2,3)   ,                        ,  (3,4)   (4,1)

(4,1) (0,2)   ,      (4,1)   (1,1)   ,   (4,1)  (2,3)   ,   (4,1) (3,4)    ,                      ]

 

.map { it.first.value + it.second.value }

 

원소들의 value 를 합해준 리스트를 반환합니다.

[3, 5, 6, 3, 

3, 4, 5, 2,

5, 4, 7, 4,

6, 5, 7, 5,

3, 2, 4, 5] 

 

.toSortedSet()

 

Set 컬렉션의 가장 큰 특징인게 중복된 원소가 없는 없다는것입니다.

toSortedSet() 으로 set 컬렉션형태로 바꾸고 오름차순으로 정렬해줍니다.

 

그리고 마지막은 반환형태에 맞게 toIntArray()로 맞춰줍니다.

 

끄읕

+ Recent posts