이제보니 IntelliJ Edu 에 Atomi Kotlin 이란 것이 있었다.

내가 코틀린을 모르는 상태로 어찌저찌 docs를 보면서 문제를 풀고, 개념을 잡고 있었는데...

 

지금 보니 조금 멍청한 선택이었다.

 

애초에 기본 문법을 위한 코스가 따로 있었다.

 

Atomi Kotlin 으로 다시 시작하도록 하겠다.

 

-------------

 

Atomi Kotlin 으로 학습을 시작하기 전에, 일단 Kotlin docs를 보면서 의문을 가졌던

 

wildCard , PECS 등등에 대해 대충 정리하고 넘어가겠다.

 

일단 PECS

는 Produce Extends, Consumer Super 를 의미한다.

 

이 때 이 PECS 는 파라미터로 넘어가는 놈! 의 역할을 의미한다. (함수 안에서 이 파라미터가 어떤 역할인지를 생각 해보자.)

 

만약 파라미터로 넘어가는 "이 놈" 이 값을 뱉어내는 놈이다. -> 값을 생산하는 놈이다.

만약 파라미터로 넘어가는 "이 놈" 은 값을 뱉지는 않고, 되려 가져가는 놈이다. -> 값을 소비만 하는 놈이다.

 

사실 PECS는... 리스트(List)를 파라미터로 넘겼을 때 "리스트 관점에서" 생산자인지 소비자인지를 의미한다. 라고 설명된다.

이 리스트가 "나" 라고 생각 해보자. 내가 마트에 가서 물건을 골라 내 리스트에 막 넣는다. 이 때 나는 당연히 소비자이다.

내가 반대로 장바구니를 들고 가서 막 애들한테 나눠준다. 나는 "나눠주는, 생산자" 이다.

 

"나"는 이 함수 안에서, 생산자이다.

"나"는 이 함수 안에서, 소비자이다. 

 

일단, "잠깐 이 놈은 값을 생산도 하고 소비도 하는데?" 라고 할 때는 이 PECS 에서 일단 빼 놓고 얘기하자.

(상관은 없다. 하지만 이 것이 와일드카드랑 섞이기 시작하면 혼란이 찾아온다. 나처럼)

 

간단히 생각하자.

 

만약 값을 생산하는 놈은, 함수에 전달되서, "값을 뱉어내는 놈" 이다.

 

즉 T extends Animal 이라고 할 때,

T 는 반드시 Animal 을 상속하고 있는 놈이다. 따라서 Animal class에 있는 메서드와 필드를 기대할 수 있다.

즉 함수에서 이 T 타입의 파라미터를 사용할 때 아무런 문제가 없다.

 

반대로 이 T 에다가 값을 넣어보려 해보자. Animal 중 뭔지 알 수는 있나? 함수는 정확한 타입을 맞출 수 없다.

따라서 이 T 는 함수에 값을 제공하면 제공했지, 함수에서 값을 받아들일 놈은 아니란 거다. 

 

따라서 요놈은 "생성자" Producer이며, 파라미터로 넘겨질 때는 제네릭 사용시 부모 클래스를 extends 하면서 들어가야 한다.

 

 

반대로 값을 받아들이는 제네릭 T super Cat 이 함수의 파라미터로 넘어간다고 해보자.

요놈은 Cat 의 super 타입인 것이 "보장 돼 있다" 예를 들어 T 에 Animal 이 들어가면,

 

Animal 클래스에 Cat 인스턴스를 넣을 수 있나 없나? 당연히 넣을 수 있다.

 

반대로 이 T 를 함수에서 사용하려 할 때는 골치가 아파진다. 이 T 는 Cat의 super 타입이지만, 어떤 필드를 갖고 있고, 어떤 메서드를 갖고 있는지 알 수가 없다. 이 T 타입의 인스턴스를 사용 할 수 있을까?

 

따라서 이 T 타입의 인스턴스는, 함수에서 값을 가져가는(소비하는) 역할을 하지, 함수에 값을 제공하는 Producer 역할을 하는 것이 아니다.

 

따라서 Consumer 는 Super 다.

 

이런 식으로 PECS를 정리하고 가자...

 


여기서 WildCard ? 를 얹어 보자.

 

WildCard 는 말 그대로, 뭐든지 들어갈 수 있다. 라는 것을 표기하는 것이다. 이 것이 함수의 파라미터에 super, extends 와 같이 쓰일 때는 다른 작용을 한다.

 

첫 째로 WildCard로 넘어온 타입이 뭔지는 "당연히 알 수 없다." 

 

? extends Animal 이라고 해보자.

 

함수의 파라미터로 ? 타입의 인스턴스가 넘어갔다.

 

아니 잠깐만. 이게 T extends Animal 상황이랑 뭐가 다르지? 하는 의문이 들 것이다.

 

다르다.

 

함수에 파라미터로

 

Collection<? extends Animal>

 

요런 식으로 인스턴스를 받아들이면, 이 Collection 인스턴스에는 값을 꺼낼 순 있어도 값을 넣을 순 없다.

 

반대로 

 

Collection<? super Cat>

 

요런 식으로 인스턴스가 함수로 넘어가면, 이 인스턴스에는 값을 넣을 순 있어도, 값을 꺼낼 수는 없다.

 

List<? super Cat> 에다가 List<Animal> 인스턴스를 넣으면,

 

이 List<Animal> 타입의 인스턴스는 "소비자(왕) 이시다." 왕의 값을 감히 빼 볼 수 없다.

 

animalList.get(0) 요런식으로 값을 꺼내려고 하는 순간 바로 에러가 발생한다.

하지만 animalList.add(cat) 은 가능하다. 

 


 

이제 Kotlin 의 in 과 out 으로 넘어 가보자.

 

만약 자바에서 

 

void demo(Source<String> strs) {
  Source<Object> objects = strs;


  // ...
}

는 불가능 하다.

String s = "자바의 경우"

Object object = s 

 

왜냐면 Object는 String의 상위 클래스이기 때문이다.

 

하지만 

Source<Object>는 Sourc<String> 의 상위 타입이 아니다.

이것을 우린 "무공변성" 이라고 한다.

 

Object 는 String 의 상위 타입이니까?

Source<Object> 도 Sourcde<String> 의 상위 타입이겠지?

 

이게 허락되면 우리는 "공변성" 이 있다. (함께 변하는 관계) 라고 한다.

 

반대로 Object는 String의 상위 타입이야.

잉? 그런데 Source<String> 이 Source<Object> 의 상위 타입이야? 

이게 말이 안돼 보이는데 요런 상황이 찾아오면 이것을 "반공변성" (정 반대로 변하는 관계) 라고 한다.

 

 

위의 자바 코드를 가능하게 하려면 이렇게 해야한다.

 

 void demo(Source<? extends Object> strs) {
  Source<?> objects = strs;
  // ...
}

 

하지만 코틀린에서는 in, 과 out 을 사용해서 다음과 같이 사용할 수 있다.

 

interface Source<out T> {
    fun nextT(): T
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // This is OK, since T is an out-parameter
    // ...
}

여기서 out 의 의미는 T 제네릭을 같는 이 클래스 타입 Source 는 T 타입을 "반환" 할 수는 있지만(out)// T 타입을 파라미터로 받아들일 수는 없다! 를 의미한다.

 

그럼 val objects: Source = strs // 이건 왜 가능한 것인감??

 

Source<Any> 클래스 안에 Source<String> 타입의 "인스턴스" 가 들어가 있다고 생각해보자.

그럼 반환하는 T 는 자동으로 Any 로 나와야 하는데, String 은 Any 하위이기 때문에 Any 타입의 반환에 String 형태의 인스턴스를 반환해도 아무런 문제도 없다.

 

따라서 위와 같은 "공변" 현상이 일어날 수 있는 것이다.

 

 

일단 여기까지만 정리하도록 하겠다. 저녁 약속이 있어 나가봐야... 더 자세한 정리는 다음 기회에... 

블로그 이미지

맛간망고소바

,

2. Conventions

Convention 에는 "관례" 라는 뜻이 있다...

일단 귀찮으니 넘어가자.

 

1) Comparison

첫 번째 문제는 Comparison 이라는 문제다.

여기서 핵심은 

operator 를 overriding 할 수 있다는 개념이다.

 

클래스 내에서 함수를 override 할 때는 operator 키워드를 붙여줄 필요는 없다.

 

Comparable 을 상속해서 , compareTo 함수를 오버라이딩 해주면 끝.

 

이 compareTo 는 operator 에서 <  ==  > 등을 비교하는데 쓰인다.

 

2) In Range

 

이 문제는 보자마자 뭔 개소리지?? 하는 느낌을 주었다.

애초에 자바에 in 같은 키워드가 없기 때문이다. (적어도 난 안쓴다.)

 

쿼리로 따지면 Between 같은 느낌의 연산자이다.

 

"연산자" 이다. < > == 같은 연사자.

 

따라서 연산자 오버라이딩을 하면 된다.

 

Range

A range defines a closed interval in the mathematical sense: it is defined by its two endpoint values which are both included in the range. Ranges are defined for comparable types: having an order, you can define whether an arbitrary instance is in the range between two given instances. The main operation on ranges is contains, which is usually used in the form of in and !in operators.

To create a range for your class, call the rangeTo() function on the range start value and provide the end value as an argument. rangeTo() is often called in its operator form ...

 

class DateRange(val start: MyDate, val endInclusive: MyDate){
    operator fun contains(item: MyDate): Boolean =
            item >= start && item <= endInclusive
}

fun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean {
    return date in DateRange(first, last)
}

이 문제는 이런 식으로 풀면 된다.

 

DateRange 라는 클래스를 만들고, 이 클래스의 main constructor 는 start 와 endinclusive 를 받는다.

그리고 자기가 갖고 있는 start와 endEnclusive 로 넘어오는 MyDate instance 가 범위 안에 속하는지 아닌지를 반환하는 contains 함수를 정의 해준다. 

 

여기서 알 수 있는 것은, "operator" 키워드를 사용해서, 타 클래스의 연산에 관여할 수 있는 클래스를 만들 수 있다는 것이다.

또한 MyDate는 이 전 문제 (1번) 에서 재 정의한 compareTo 함수를 갖고 있다는 것을 명심하자.

 

1) compareTo  로 대소를 배교할 수 있는 클래스(MyDate) 를 만들게 됨.

2) 이제 대소를 비교할 수 있으니 contains 함수도 만들 수 있음. in 연산자를 오버라이딩 한 클래스가 대신 이것을 처리 해 줌.

 

 

3) RangeTo

 

확장 함수로 연산자 오버로딩을 할 수 있다.

이 전 2번 문제에서는 

date in DateRange(first,last) ... 방식으로 범위 안에 속하는지를 검사 했지만.

 

date in first..last 형식으로 이를 바꾸는 것이다.

 

date 도 first 도 last 도 모두 MyDate 클래스의 인스턴스이다.

 

.. 을 사용하기 위해서는 rangeTo operator 를 만들어 줘야 한다.

 

operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other)

class DateRange(override val start: MyDate, override val endInclusive: MyDate): ClosedRange<MyDate>

 

위와 같이, operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other)

로 만들면 된다.

 

2단원 부터 갑자기 난이도가 올라가는 면이 있는데, 대부분이 오버라이딩에 관련된 문제인데 함수의 원형을 알기 힘들기 때문이다.

 

일단 문서(docs)를 타고 올라가서 rangeTo 의 틀을 찾아보자.

 

 

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/index.html

 

kotlin.ranges - Kotlin Programming Language

 

kotlinlang.org

여기에 가서 rangeTo 를 찾아보자.

 

ClosedRange 타입의 인스턴스를 반환해야 함을 알 수 있다.

 

DateRange 클래스는 현재 ClosedRange<MyDate> 를 상속하고 있다.

따라서 DateRange 인스턴스를 만들어서 반환하면 되는구나? 라는 것을 생각할 수 있다.

 

여기서 다시 정리해보자.

 

1. compareTo  로 MyDate 클래스의 인스턴스는 대소비교가 가능해짐

2. DateRange 라는 클래스로 contains 를 오버로딩 하면 in 연산자를 사용할 수 있음.

3. rangeTo 를 만들면 first..last 형식으로 쓸 수 있음. rangeTo 의 반환형은 뭐다? -> ClosedRange 다.

 

그럼 쉽게 예상할 수 있을 것이다. ClosedRange 를 상속 받으면 무엇을 오버라이딩 해줘야 할까? -> contains다.

결국 우리가 2번에서 만든 contains 함수를 갖는, DateRange instance를 반환하게 하는 것이 rangeTo 다.

 

따라서..

 

date in DateRange(first,last)  = date in first..last

 

first..last = first.ragneTo(last) = DateRange(first, last)

 

4) For loop

in 은 단순히 대소 비교를 통해, 범위 안에 값이 들어가는지 확인만 하는 것은 아니다. (아닌가?_

 

for 문 안에서도 사용될 수 있다.

 

예를 들면

 

for(date in firstDate..secondDate) {

 

}

와 같이 사용된다.

 

하지만 여기서는 같은 in 키워드여도 다른 것이 필요하다. 단순한 ClosedRange 가 아닌, Iterable 을 상속해야 하며.

Iterabe 인터페이스를 구현하면, Iterator를 반환해야 하고.

 

이 Iterator를 반환하기 위해 새로운 클래스를 만들어야 한다.

 

그냥 Iterator를 anonymous object로 전달하자 무한 루프에 빠져 버렸다. (다른 요인일 수도 있다.)

anonymous object로 만들지 말고, 클래스를 하나 만들고, 인스턴스로 Iterator를 하나 만들어서 뱉어주니 잘 작동했다.

 

 

class DateRange(val start: MyDate, val end: MyDate): Iterable<MyDate>{
    override fun iterator(): Iterator<MyDate> = DateIterator(this)
}

class DateIterator(val dateRange:DateRange) : Iterator<MyDate> {
    var current: MyDate = dateRange.start
    override fun next(): MyDate {
        val result = current
        current = current.nextDay()
        return result
    }
    override fun hasNext(): Boolean = current <= dateRange.end
}

fun iterateOverDateRange(firstDate: MyDate, secondDate: MyDate, handler: (MyDate) -> Unit) {
    for (date in firstDate..secondDate) {
        handler(date)
    }
}

 

이런 식으로 작서 ㅇ되는데...

 

firstDate..secondDate 를 보면 알 수 있듯이, rangeTo 연산자를 오버라이딩 했다.

rangeTo 함수는 이전 3번처럼 DateRange 인스턴스를 반환하다. DateRange 는 Iterable 을 구현한 클래스이다.

 

Iterator 는 next 와 hasNext 함수를 상속해줘야 한다. 살짝 IntelliJ의 자동 완성 기능의 도움을 받으면 쉽게 오버라이딩 할 함수의 틀을 만들 수 있다.

 

5) Operator overloading

 

오버로딩과 오버라이딩의 차이는 잘 알 것이다.

 

보통 부모의 함수를 상속받아, 자기 껄로 대체 하는것을 오버라이딩이라고 하고.

함수 이름은 같은데 매개변수가 다른 여러 함수를 만드는 것을 오버로딩이라고 한다.

 

오버라이딩은 런타임 시에, 오버로딩은 컴파일 시에 정의 된다... 

 

간략히 여기 까지만 설명 하고.

 

연산자 오버라이딩에서 왜 갑자기 연산자 오버로딩이 제목이 된 걸까? 이를 활용하는 것이 이번 문제의 핵심이자. 정말 재밌는 문제다.

 

난 못 풀었다.

 

6) Destructing declarations.

 

자바스크립트에 "구조 분해 할당" 이란 것이 있다.

 

그거랑 비슷 한건데,

fun isLeapDay(date: MyDate): Boolean {

val (year, month, dayOfMonth) = date

// 29 February of a leap year
return year % 4 == 0 && month == 2 && dayOfMonth == 29
}

 

이렇게 있을 때 MyDate 의 필드 값들이 year, month, dayOfMonth 에 분해돼 들어가는 느낌을 주는... 

 

이것 또한 연산자로 지정해 줘야 한단다...

 

다음과 같다.

 

class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) {
    operator fun component1(): Int {
        return year
    }
    operator fun component2(): Int {
        return month
    }
    operator fun component3(): Int {
        return dayOfMonth
    }
}

componentN() 을 정의해주면 된다.

N 에 1~N 까지으 숫자가 들어간다.

 

6) Invokable

 

별 거 없는 문제인데 한 번 기억해두면 좋을 요소가 하나는 있다.

 

class Invokable {
    var numberOfInvocations: Int = 0
        private set
    operator fun invoke(): Invokable {
        numberOfInvocations++
        return this
    }
}

fun invokeTwice(invokable: Invokable) = invokable()()

짜잔.

 

invoke 라는 연산자를 오버라이딩하면..

인스턴스에다가 () 를 붙여서 함수처럼 쓸 수 있다...  위에 invokeTwice 는 말 그대로 invoke 두번 한거다.

 

여기서 private set 부분을 한번 보고 넘어가자.

 

 

Kotlin 에서는 클래스 안에 var a = 3 이런 식으로 "필드" 를 선언해 놓으면 자동으로

get 과 set 함수가 생긴다.

 

자바에서는 아마 @Getter @Setter 등을 붙이거나. (lombok 사용 시에) 직접 만들어야 했을 것이다.

 

만약 상수 선언을 의미하는 val 을 쓴다면? setter 가 생기지 않는다.

 

위에서 필드 선언 부분 아래에서 private set 부분을 확인할 수 있다.

set 함수를 private으로 만든 것이다. var 로 선언했기 때문에 값이 변할 수는 있지만, 이 값을 외부에서 직접 특정 값으로 수정하지는 못하게 한 것이다.

 

이 부분만 집고 넘어가자.

 

2단원은 이렇게 마친다.

 

블로그 이미지

맛간망고소바

,

1. Introduction

1.1 Hello, World!

 

코틀린에서는 function을 다음과 같이 선언할 수 있다.

fun start(): String = "OK"

String 을 반환형으로 사용하는, 파라미터를 받지 않는 start 함수다.

 

https://kotlinlang.org/docs/reference/basic-syntax.html#defining-functions

 

Basic Syntax - Kotlin Programming Language

 

kotlinlang.org

링크가 걸려있다. function 을 여러 방법으로 선언할 수 있다.

 

1.2 Java to Kotlin conversion

자바 코드를 자동으로 코틀린 코드로 바꿔주는 것을 보여주는 예제다.

그냥 JavaCode를 복사해서 붙여넣으면 끝이다. Java to Kotlin Converter의 힘을 감상하기만 하면 끝.

 

1.3 Named arguments

함수를 직접 써보는 예제다. 코틀린은 함수에 파라미터를 넘길 때, 파라미터 이름을 지정해서 넘길 수 있다.

이름을 지정 안하면 순서대로 넘어간다.

 

fun joinOptions(options: Collection<String>) = options.joinToString(", ", "[", "]")

 

코틀린의 Collection은 기본적으로 자바와 같다고 보면 된다. 여기서 더 편리한 기능 확장이 추가돼 있다.

콜렉션이 넘어오면, 인스턴스로 함수를 호출한다.  위와 같이 호출 할 수도 있고,

 

fun joinOptions(options: Collection<String>) = options.joinToString(separator = ", ",
prefix = "[",
postfix = "]")

 

이런 식으로도 호출 할 수 있다.

 

또한 함수를 선언 시에 Default Argument 를 사용할 수 있다.

 

fun 함수이름(a: Int = 1, b: Int = 2) = a + b

이런 식으로 해 놓으면 파라미터에 아무것도 넘어가지 않을 때, 기본 값을 사용한다.

 

"코틀린은 함수 선언시에 기본 값을 지정할 수 있다. 또한 함수 호출 시에 파라미터 이름과 함께 넘길 수 있다."

 

1.4 Default Arguemnts

위에 설명한, 기본 값 지정에 대한 문제이다. 나처럼 풀면 이렇게 된다.

 

fun foo(name: String, number: Int, toUpperCase: Boolean) =
(if (toUpperCase) name.toUpperCase() else name) + number


fun foo(name: String, number: Int) = foo(name, number, false)

fun foo(name: String) = foo(name, 42)

fun foo(name: String, toUpperCase: Boolean) = foo(name, 42, toUpperCase)

 

fun useFoo() = listOf(
foo("a"),
foo("b", number = 1),
foo("c", toUpperCase = true),
foo(name = "d", number = 2, toUpperCase = true)
)

하지만 코틀린은 "기본 값을 지정" 할 수 있다.

다음과 같이 간단히 작성 할 수 있다.

 

fun foo(name: String, number: Int = 42, toUpperCase: Boolean = false) =
(if (toUpperCase) name.toUpperCase() else name) + number


fun useFoo() = listOf(
foo("a"),
foo("b", number = 1),
foo("c", toUpperCase = true),
foo(name = "d", number = 2, toUpperCase = true)
)

 

1.5 Lambdas

람다는 자바에서도 8 버전에서부터 지원하는 것으로 알고 있다.

코틀린 또한 당연히 람다를 지원한다.

 

함수를 파라미터로 받는 함수를, Higher order Function이라고 한다.

이런 함수에, 파라미터를 넘겨줄 때 람다를 쓰면 아주 깨끗한 코드를 만들 수 있다. (사람마다 코드의 미적 기준은 다를 수 있다.)

 

아래가 그 예시이다.

 

// Lambdas are code blocks enclosed in curly braces.
items.fold(0, { 
    // When a lambda has parameters, they go first, followed by '->'
    acc: Int, i: Int -> 
        print("acc = $acc, i = $i, ") 
    val result = acc + i
    println("result = $result")
    // The last expression in a lambda is considered the return value:
    result
})

 

 

함수 타입은 다음과 같이 나타낸다.

(R, T) -> R

 

코틀린은 변수를 쓸 때  var a: Int = 1  이런 식으로 쓸 수 있다. 

함수 타입이 (R, T) -> R 과 같은 방식이란 말은

 

var sampleFunction: (Int, Int) -> String = {
a: Int, b: Int -> "the result is $a + $b"
}

print(sampleFunction(1, 2))

결과: the result is 3

 

요런 식으로 쓸 수 있다는 말이다.

 

코틀린은 특이한 문법이 있다.

가장 마지막에 들어가는 "함수 인자" 는 parenthesis 밖으로 뺄 수 있다. (paraenthesis = ( or ) )

 

이게 무슨 개소린가 싶기도 한데...

 

fun higherOrderFunctionDefault(a: Int, b: Int, f: (Int, Int) -> Int): Int {
return f(a, b)
}

var result = higherOrderFunctionDefault(1,2, {a: Int, b: Int -> a + b})

print(result) -> 결과는 3이 나온다.

 

위와 같은 경우에.

 

var result = higherOrderFunctionDefault(1,2, {a: Int, b: Int -> a + b})

이 부분을

 

var result = higherOrderFunctionDefault(1,2){a: Int, b: Int -> a + b}

 

이렇게 쓸 수 있다는 말이다. 심지어 IntelliJ Edu 에서는 뒷 부분을 더 선호한다. ??? 

 

1.6 Strings

이건 그냥 답 봤다. 도저히 못 찾겠다.

String Template 에 관련된 문제인데... Docs 링크가 걸려있긴 하지만, 전혀 몰랐던 Template 적용 방식이 있었다.

자바 때부터 전해내려온 것인지, Kotlin에만 있는 것인지조차 잘 모르겠다. 이 부분은 좀 약하다.

 

1.7 Data Classes

class 앞에 data 를 붙이면, lomobk 의 @Data 와 비슷한 작동을 한다.

 

equals/hashCode, toString

 

함수를 자동으로 만들어 준다.

 

문제 자체는 간단하지만, 여기서 Kotlin의 클래스 개념을 잡고 넘어가야 한다.

 

오른쪽에 있는 링크를 따라가 차분히 읽어보도록 하자.

많은 것들을 다 적을 순 없고 간단히 특이한 점만 정리해 놓겠다.

 

1. 코틀린의 클래스 안의 필드들은, 반드시 초기화 돼야 한다.

2. main counstructor  라는 놈이 있다.

3. 일단 상속되는 놈이 먼저 생성되는건 자바와 똑같다. 따라서 주의할 점도 똑 같다.

4. 클래스 안에서의 문(Statement) 들은 무조건 순서대로 시작된다. 단 constructor는 예외 케이스가 있다.

   main constructor 가 있다면, 클래스 안쪽에 선언하는 놈들은 다 secondary constructor 인데,

   constructor keyword 를 이용해서 생성자를 추가할 수 있다. 이 때 반드시, main constructor을 this(name) 이런 식으로 호출해 줘야한

   다... 

ex)

class Constructors(var a: Int = 3) {
	constructor(i: Int, b: Int): this(i) {
  		println("Constructor")
	}
	init {
  		println("Init block")
	}
}

따라서 위와 같은 경우 init 이 먼저 시작된다. secondary constructor 보다는 init 이 우선시 된다.

main constructor 가 없다? 없을 수는 없다. 사실 암시적으로 갖고 있는 것이다. 마치 자바의 기본 생성자가 있듯이... 만약 main constructor 가 없다면, 암시적으로 호출하고 있다고 생각해야 한다.

 

그렇다면 순서대로라는 것은 무슨 의미인가?

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    
    init {
        println("First initializer block that prints ${name}")
    }
    
    val secondProperty = "Second property: ${name.length}".also(::println)
    
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

위와 같은 경우 반드시 순서대로 시작된다.  (위의 also 는 kotlin 의 확장 함수 중 하나이다. 자기 자신을 매개변수로 넘기고, 자기 자신을 return 한다.

val person: Person = getPerson().also {
	print(it.name)
	print(it.age)
}

마지막엔 자동으로 자기 자신을 return 한다.

 

5. main constructor 에는 연산을 넣을 수 없다. 따라서 연산을 위해서는 클래스 안에 init 을 사용하도록 해라.

6. class 는 클래스 : 부모클래스() 와 같이 상속. interface  는 클래스: 언터페이스 와 같이 상속한다.

7. 상속 하려면 open 키워드를 붙여야 한다.

 

다른게 무지 많다. 자세히 알아보지도 않고 대충 정리만 해놨다. 무엇보다 1번이 가장 애매한데... 초기화 안하면 컴파일러에서 빽빽돼서 저리 적어 놓았다.

 

1.8 Nullable Type

꽤나 중요한 문제이다.

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
){
    val email = client?.personalInfo?.email
    if (email != null && message != null) {
        mailer.sendMessage(email, message)
    }
}

위가 정답이다. 깔끔하고 멋있다. kotlin 의 ? 의 힘이다.

자료형에 ? 가 붙어있는 것을 볼 수 있는데, null 이 들어갈 수도 있는 자료형을 나타낸다.

인스턴스에 ? 를 붙인뒤, 인스턴스의 자료 혹은 함수에 접근하려고 하면, null 을 자동으로 체크해준다.

만약 접근하려는 놈이 null 일 경우 그냥 null 을 반환한다. 그래서 마지막에 email != null 을 체크하는 것이다.

 

처음에 if 문을 하나만 써서 해결하라는 문구를 보고 나는 다음과 같이 문제를 해결했다.

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
){
    if(client != null
            && message != null
            && mailer != null
            && client.personalInfo != null
            && client.personalInfo.email != null)
        mailer.sendMessage(client.personalInfo.email!!, message!!)
}

 굳이 이 흉한 코드를 올려놓은 이유는, !! 또한 키워드 중 하나이기 때문이다. ? 자료형의 변수에, !! 를 뒤에 붙여주면.

"이 값은 null 일리가 없다!" 라는 것을 의미한다. 따라서 String? 자료형의 변수는 String 으로 변해서 파라미터로 전달되는 효과가 있다.

 

1.9 smart casts

 

제목 그대로 "똑똑한 형변환" 을 의미한다. 일단 kotlin 에서는 if 문과 when 문을 사용해서 변수에 값을 할당할 수 있다는 것을 알고 넘어가자.

var a = 3;

var b = if(a == 3) 4 else 5 

이런 식으로 쓸 수 있다는 말이다. (많이 신기함..)

 

위에서는 If문과 When 문이라고 했지만.. Kotlin 에서의 If 는 "Expression" 이다.

이게 뭔 의미냐면, 반환형이 있다는 말이다.

If 표현의 마지막 문장은 자동으로 "반환 값" 이 된다.

 

따라서 Kotlin 에는 ? a : b 표현이 없다. If 로 충분하기 때문이다.

 

when 은 Java에서의 Switch 랑 비슷한데 여러모로 다른 점이 있다. 

fun eval(expr: Expr): Int =
        when (expr) {
            is Num -> expr.value
            is Sum -> eval(expr.left) + eval(expr.right)
            else -> throw IllegalArgumentException("Unknown expression")
        }

위를 보면 알 수 있듯이, when 은 is 로 타입을 확인 할 수 있다. 자바로 따지면 instance of 같은 느낌이다.

왜 "똑똑한 형변환" 일까?

위에서 Expr 이 인터페이스라고 하자. 인터페이스를 구현한 클래스 A : Num , B : Sum 이 있다.

그럼 eval 함수에는 A 도, B 도 들어갈 수 있겠지? 

 

이 때 when 의 is 로 타입을 구분해서, 사용할 수 있다는 말이다.

 

잘 보면 expr.value, expr.left expr.right 라고 저녛 다른 것들을 사용 하는 것을 볼 수 있다.

 

interface 가 when 절의 is 를 통과했을 때, "자동으로" is 에서 비교한 타입으로 형변환 해서 넘어간다.

따라서 is Num 을 통과한 녀석은 Num 자료형으로 자동으로 변경돼서 들어간다.

똑똑하다는 말이다.

 

when의 is 는 타입을 비교할 때 쓴다, 보통은 왼쪽에 "조건식" 이 들어간다.

 

when (num) {

  1 -> "하나"

 2 -> "둘"

else -> "둘 초과"

}

이런 식으로 사용할 수 있다.

 

만약 when 에 아무런 인자도 넘어가지 않는다면, 그냥 "순수한 조건식" 만을 이용해서 사용할 수도 있다.

 

var a = 3

 

when {

  a == 3 -> "a는 3이 맞어"

  else -> "a 는 3이 아니여"

}

이런 식으로 말이다.

물론 위와 같이 쓸 필요는 없다. 위와 같으 2개의 분기로 나눠 질때는 if를 쓰면 된다.

 

3가지 이상의 분기로 나눠질 때 when을 사용하도록 하자.

 

1.9 Extension Function

 

확장 함수를 의미한다. 클래스의 확장 함수를 부여 할 수 있다.

그냥 클래스 안에다가 함수 만드는 것은 확장 함수가 아니다.

 

1.10 Object Expression

코틀린에서는 익명 클래스를 어떻게 사용할 수 있을까?

 

object: 인터페이스{

 override fun 함수이름() {}

}

    Collections.sort(arrayList, object: java.util.Comparator<Int> {
        override fun compare(o1: Int?, o2: Int?): Int {
            if(o1!! > o2!!) return -1 else if(o1 < o2) return 1 else return 0
        }
    })

 

https://kotlinlang.org/docs/reference/object-declarations.html

 

Object Expressions, Object Declarations and Companion Objects - Kotlin Programming Language

 

kotlinlang.org

위의 object 표현식을 활용 하도록 하자.

주의 해야할 점은, 이 anonymous object 의 scope 이다.

anonymous object는 private이나 local 에서만 사용 가능하다.

 

public 으로 노출되면, 이 object는 순식간에 Any? 타입으로 바뀌어 버린다.

(Kotlin 에서의 Any 는 Java에서의 Object와 비슷한 맥락이지만. 절대 같지는 않다.)

 

class C {
    // Private function, so the return type is the anonymous object type
    private fun foo() = object {
        val x: String = "x"
    }

    // Public function, so the return type is Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // Works
        val x2 = publicFoo().x  // ERROR: Unresolved reference 'x'
    }
}

 

1.11 SAM Conversion

 

SAM Conversions

Just like Java 8, Kotlin supports SAM conversions. This means that Kotlin function literals can be automatically converted into implementations of Java interfaces with a single non-default method

 

1.12 Extension On Collection

 

문제 자체는 아주 간단하다. 

 

제목 그대로 Collection을 어떻게 확장해서 사용하고 있는지를 알려준다.

 

차트를 보면 3단원에 제대로 Collection을 다룰 예정인듯 하니 기다리도록 하자.

 

 

 

 


 

이렇게 1단원을 끝냈다.

굳이 정리할 필요는 없겠지만, 기본 문법이니 만큼 적어두는 것이 좋다고 생각했다.

 

이 포스트의 내용 만으로는 완벽히 Kotlin을 이해하기 힘들다.

글을 쓰고 있는 사람부터가 이해하고 쓰는게 아니고, 이해하기 위해, 혹은 이해하고 나서 훗날 기억하기 위해 적고 있기  때문이다.

 

화이팅 하자.

블로그 이미지

맛간망고소바

,

Voice Diary 를 만들기 위해, 안드로이드 어플리케이션을 만들어야 한다.

안드로이드 개발 책을 샀긴 했는데, Kotlin 에 대한 설명이 조금 부족해 보였다.

 

물론 어플리케이션을 만드는데는 충분할 정도로 설명이 돼있긴 했으나, 학습이 목적이니만큼 코틀린 부터 익숙해 지고자 한다.

 

일단 IntelliJ Edu 를 설치했고, Kotlin 부분을 학습 중이다.

살짝 어려운 감이 있는 것이, 일단 Java를 알고 있다는 전제 하에 학습이 진행된다.

 

학습은 문제를 제시하고 대뜸 풀라고 하는 형식인데, 나름 옆에 힌트가 있다.

이게 또 제법 퍼즐 푸는 느낌이 있어 재미있기도 하다.

 

IntelliJ Edu에서 제공하고 있는... 교육용 프로젝트

현재 1단원을 다 풀고 2단원을 풀고 있다.

이런 식으로 공부해 본 적은 없어서 색다른 기분이 든다.

 

Kotlin Docs로 공부하려고 했지만, 내용이 방대하여 살짝 거리감이 느껴졌는데, 이렇게 문제를 풀면서 Docs를 읽으니 체계적으로 정리가 되는 기분이다.

 

문제를 풀려면 반드시 Kotlin을 알아야 한다. 문제를 보고, 이 문제를 풀기 위한 Kotlin Docs를 찾아보고, 답을 적어서 Check를 누르면 된다.

도저히 못 풀 것 같으면 일단 틀린 다음에 Peek Solution 을 보는 것도 나쁘지 않다.

 

처음 써보는 언어라 문법 자체가 생소할 수 있다. 부끄럼 없이 Peek Solution 을 누르자. :)

-계속 누르지만 않으면 된다.-

블로그 이미지

맛간망고소바

,

프로젝트 이름은 아무 생각 없이 정했습니다.

그냥 일기 쓰기 귀찮을 때, 녹음으로 일기 하면 참 편할 것 같다는 생각이 들어 제작하게 됐습니다.

 

그냥 핸드폰 기본 음성 기록 어플 쓰면 되는 거 아니야? 라고 생각할 수 있겠지만... 사람들이 기본 녹음 기능을 일기로써 사용하지 않는 데엔 다 이유가 있습니다.

 

이유는 간단합니다. 핸드폰의 기본 녹음 기능이 "일기 용도" 가 아니라는 인식 때문입니다.

 

아마 제가 만드려는 어플은 분명 이미 개발돼 있을 겁니다. 하지만 상관이 없지요.

이 프로젝트는 제가 생각하기에 간단하고, 재미 있을것 같은 주제를 정한 것입니다.

(간단하지 않을 수 있습니다. 그냥 제 생각입니다.)

 

프로젝트에는 Spring Boot 로 서버를 구축하고, 안드로이드로 프론트를 개발할 예정입니다.

 

사실 안드로드이드 개발은 지금이 처음입니다.

 

스프링으로 서버 개발은 조금 해 보았지만, 이번 기회에 착실히 기본부터 다져가고자 합니다.

혹시라도 이 포스트를 보게 되시는 분이 있다면, 멍청한 방향으로 나아가는 프로젝트를 보고 감탄하실지도 모릅니다.

 

최대한 기본에 충실해 개발 단계를 밟아나갈 예정입니다. 혹시 보시는 분이 있다면 쓴 소리는 달게 받겠습니다만, 

"왜 이딴 프로젝트를 만들고 있지?" 라는 말은 삼가해 주세요...

 

이 프로젝트는 개인 학습용 + 재미용 프로젝트입니다.

티스토리에 글을 남기는 이유는, 혹시라도 제가 중간에 포기할 것을 염려하여 증거를 남기기 위해서입니다.

 

화이팅.

 

블로그 이미지

맛간망고소바

,