kotlin_06

김재현·2023년 3월 28일
0

제네릭 함수 혹은 메서드

fun <형식 매개변수[,...]> 함수 이름(매개변수: <매개변수 자료형>[, ...]): <반환 자료형>

예)

fun <T> genericFunc(arg: T): T? {...} // 매개변수와 반환 자료형에 형식 매개변수 T가 사용됨

fun <K, V> put(key: K, value: V): Unit {...} // 형식 매개변수가 2개인 경우

배열의 인덱스 찾아내기

제네릭과 람다식

  • 형식 매개변수로 선언된 함수의 매개변수를 연산할 경우에는 자료형을 결정할 수 없기 때문에 오류가 난다.
  • 하지만 람다식을 매개변수로 받으면 자료형을 결정하지 않아도 실행 시 람다식 본문을 넘겨 줄 때 결정되므로 해결할 수 있다.

람다식에서 제네릭 사용

자료형 제한하기
자바에서 extends나 super를 사용해 제한한것을 코틀린에서는 콜론(:)을 사용해 제한한다.

클래스에서 형식 매개변수의 자료형 제한하기

자료형을 숫자형으로 제한하기

함수에서 형식 매개변수의 자료형 제한하기

  • 클래스와 동일하게 형식 매개변수 옆에 콜론(:)을 사용해 제한할 특정 자료형을 지정한다.

다수 조건의 형식 매개변수 제한하기
2개의 특정 인터페이스 구현이 포함된 클래스로 형식 매개변수를 제한하려고 할때 -> where키워드를 사용해 지정된 제한을 모두 포함하는 경우에만 허용하도록 할 수 있다.

상.하위 형식의 가변성

가변성의 3가지 유형

  1. 공변성 : T`가 T의 하위 자료형이면, C<T`>는 C<T>의 하위 자료형이다. 생상자 입장의 out 성질
  2. 반공변성: T`가 T의 하위 자료형이면, C<T>는 C<T`>의 하위 자료형이다. 소비자 입장의 in 성질
  3. 무변성: C<T>와 C<T`>는 아무 관계가 없다. 생산자 + 소비자

8.2 배열 다루기

  • 코틀린에서 배열은 Array클래스로 표현된다.
  • 배열을 생성하기 위해서는 arrayOf()나 Array() 생성자를 사용해 배열을 만든다. 만일 빈 상태의 배열을 지정하는 경우 arrayOfNulls()를 사용할 수 있다.
  • 특정 자료형으로 제한하지 않는다면 배열의 요소로 여러 가지 자료형을 혼합할수 있다.
  • Array클래스는 get()과 set()을 가지고 있는데 대괄호를 사용해 접근할수 있다.(연산자 오버로딩으로 정의되어 있기 때문이다)

배열 선언 및 접근 연습하기

다차원 배열을 전부 출력하고 싶을때는 Arrays.deepToString(array)를 사용하면 전체 출력해준다.

표현식을 통해 배열 생성하기

val|var 변수 이름 = Array(요소 개수, 초깃값)

ex)
val arr3 = Array(5, {i -> i * 2 }) // 0,2,4,6,8
var a = arrayofNulls <Int>(1000) // 1000개 null로 채우기
var a = Array(1000, { 0 }) // 0으로 채워진 배열
var a = Array(1000, { i -> msClass(i) })

배열 제한하고 처리하기

  1. 배열 요소 추가하고 잘라내기
val arr2 = arr1.plus(6)
val arr3 = arr1.sliceArray(0..2) // 필요한 범위를 잘라내 새 배열 생성 / 0,1,2번째만 잘라서 만들어진다.
  1. 기타 배열 api
arr.first() // 첫번재 요소 확인
arr.last() // 마지막 요소 확인
arr.indexOf(3) // 요소 3의 인덱스 출력
arr.average() // 배열의 평균 값 출력
arr.count() // 요소 개수 세기
arr.contains(4) // arr 배열에 요소 4가 포함되었는지 확인
  1. 멤버 메서드를 통한 배열 순환하기
    반복문을 사용한 순환 말고도 forEach(), forEachIndexed()로 요소를 순환할 수 잇다.
    forEach() : 요소 개수만큼 지정한 구문을 반복 실행
    forEachIndexed() : 순환하며 인덱스까지 출력
arr.forEach { element -> print("$element")}

arr.forEachIndexed({ i, e -> println("arr[$i] = $e") {)

iterator()를 사용해 요소 순환을 할 수 있다.

val iter: Iterator<Int> = arr.iterator()
while (iter.hasNext()) {
	val e = iter.next()
    print("$e ")
}
  1. 배열 정렬하기
    sortedArray() : 원본은 그대로 두고 오름차순 정렬
    sortedArrayDescending() : 원본은 그대로 두고 내림차순 정렬
    sort() : 원본을 오름차순 정렬
    sorDescending() : 원본을 내림차순 정렬
    sortBy[Descending] : 특정 표현식에 따라 오름차순이나 내림차순으로 정렬 가능

기본적인 정렬 방법

  1. sortWith() 비교자로 정렬
    주어진 비교자(Comparator)에 의해 정렬

    Comparator를 사용해 제품 비교하기

  2. 배열 필터링하기
    filter() 메서드를 활용하면 원하는 데이터를 골라낼 수 있다.

    필요한 정보만 골라내어 변경하기filter로 요소를 골라내 map으로 받아 대문자로 변경하고 출력

  3. 가장 작은 값과 큰 값 골라내기
    .minBy .maxBy

  4. 배열 평탄화하기
    flatten() 매서드


8-3. 문자열 다루기

  1. 문자열 추출하기
    substring(인덱스 범위 지정)
  2. 문자열 비교하기
    s1.compareTo(s2) : 같으면 0, s1이 s2보다 작으면 양수, 그렇지 않으면 음수 반환, s1.comparTo(s2, true))는 대소문자 무시
  3. 문자열이 자주 변경되는 경우
    StringBuilder("Hello")와 같이 StringBuilder를 사용. 나중에 값을 변경할때 새로운 공간에 만들어지지 않고 원래 저장공간에 할당된 것을 바꾼다.
    • append(문자): 포함
    • insert(인덱스, 문자): 추가
    • delete(to, from) : 삭제

이외에 여러 문자열 관련 함수존재'

서점 예제 작성하기


9-1. 컬렉션의 구조와 기본

컬렉션불변형(읽기 전용)가변형
ListlistOfmutableListOf, arrayListOf
SetsetOfmutableSetOf, hashSetOf, linkedSetOf, sortedSetOf
MapmapOfmutableMapOf, hashMapOf, linkedMapOf, sortedMapOf

9-2. List 활용하기

순서에 따라 정렬된 요소를 가지는 컬렉션
불변형과 가변형이 있다는 것을 생각하고 헬퍼함수를 만든다.

List와 배열의 차이

  • List는 Array<T>와 사용 방법이 비슷하다.
    하지만 Array 클래스에 의해 생성되는 배열 객체는 내부 구조상 고정된 크기의 메모리를 가지고 있다.
  • List<T>와 MutableList<T>는 인터페이스로 설계되어 있고 이것을 하위에서 특정한 자료구조로 구현한다. 따라서 해당 자료구조에 따라 성능이 달라진다.
  • List<T>는 Array<T>처럼 메모리 크기가 고정된 것이 아니기 때문에 자료구조에 따라 늘리거나 줄이는 것이 가능하다.
  • Array<T>는 제네릭 관점에서 상,하위 자료형 관계가 성립하지 않는 무변성이기 때문에 Array<Int>는 Array<Number>와 무관하다.
    하지만 List<T>는 공변성이기 때문에 하위인 List<Int>가 List<Number>에 지정될 수 있다.

9-3. Set과 Map 활용하기

Set은 정해진 순서가 없는 요소들의 집합을 나타내는 컬렉션.
Map은 요소가 키와 값의 쌍 형태로 저장된다. 키는 중복될수 없고 유일하다.

hashSetOf과 hashMapOf은 해시테이블에 요소를 저장할 수 있다. 해시 테이블이란 내부적으로 키와 인덱스를 이용해 검색과 변경 등을 매우 빠르게 처리할수 있는 자료구조이다.

sortedSetOf와 sortedMapOf는 자바의 TreeSet 컬렉션을 정렬된 상태로 반환한다. 트리셋은 저장된 데이터의 값에 따라 정렬되는데, 일종의 개선된 이진 탐색 트리인 레드 블랙 트리 알고리즘을 사용해 자료구조를 구성한다. 최악으로 요소 배치가 되어도 검색등의 처리에서 일정한 시간을 보장하는 자료구조이다. HashSet보다 성능이 좀 떨어지고 데이터를 추가하거나 삭제하는데 시간이 걸리지만 검색과 정렬이 뛰어나다는 장점이 있다.

linkedSetOf와 linkedMapOf는 자바의 LinkedHashSet 자료형을 반환하는 헬퍼 함수이다. 링크드 리스트를 사용해 구현된 해시 테이블에 요소를 저장한다. 저장된 순서에 따라 값이 정렬되며 hash, tree보다 느리다. 다만 자료구조상 다음 데이터를 가리키는 포인터 연결을 통해 메모리 저장 공간을 좀더 효율적으로 사용할 수 있다.

9-4. 컬렉션의 확장 함수

  • 연산자(Operator) 기능의 메서드 : 더하고 빼는 등의 기능
  • 집계(Agrregator) 기능의 메서드 : 최대, 최소, 집합, 총합 등의 계산 기능
  • 검사(Check) 기능의 메서드 : 요소를 검사하고 순환하는 기능
  • 필터(Filtering) 기능의 메서드 : 원하는 요소를 골라내는 기능
  • 변환(Transformer) 기능의 메서드 : 뒤집기, 정렬, 자르기 등의 변환 기능

9-5. 시퀸스 활용하기

코틀린의 시퀸스(Sequence)는 순차적인 컬렉션으로 요소의 크기를 특정하지 않고, 나중에 결정할 수 있는 특수한 컬렉션이다. 시퀸스는 처리 중에는 계산하고 있지 않다가 toList()나 count()같은 최종 연산에 의해 결정된다.

요소 값 생성하기
generateSequence()로 생성하기

시퀸스 사용하기 시드인수에 의해 시작 요소의 값이 결정된다.
주어진 식에 따라 새로운 컬렉션을 반환하는 amp이나 filter같은 연산을 사용할 수도 있다.

요소 값 가져오기

  • 중간 연산 결과 없이 한 번에 끝가지 연산한 후 결과를 반환하려면 asSequence()를 사용할 수 있다. 특히 filter나 map을 메서드 체이닝해서 사용할 경우 순차적 연산이기 때문에 시간이 많이 걸릴 수 있지만 asSequence()를 사용하면 병렬처리되기 때문에 처리 성능이 좋아진다. 대신 작은 컬렉션에 사용할 경우 오히려 좋지 않다.

    asSequence()사용하기

시퀸스를 이용한 피보나치 수열 출력하기


10-1. 코틀린 표준 함수

람다식

  • 람다식은 항상 중괄호로 묶여 있으며 중괄호 안에 매개변수는 화살표(->)왼쪽에 배치되고 오른쪽에는 그에 따른 식을 구성한다.
val 변수 이름: 자료형 선언 = { 매개변수[,...] -> 람다식 본문 }
val sum: (Int, Int) -> Int = { x,y -> x + y }
val mul = {x: Int, y: Int -> x*y }
val add: (Int) -> Int = {it + 1}
  • sum은 익명함수로 만들어지며 매개변수는 선언부의 자료형에 의해 (Int, Int)를 가지게 된다. 반환값은 Int이므로 람다식의 x + y가 반환된다.
  • mul은 변수의 자료형 표기가 생략되었지만 람다식에 있는 매개변수의 Int 선언 표현에 의해 반환 자료형을 추론할 수 잇다.
  • add는 매개변수가 1개인 경우, 매개변수를 생략하고 it으로 표기할 수 있다.
val isPositive: (Int) -> Boolean = {
	val isPositive = it > 0
    isPositive // 마지막 표현식이 반환됨
}

val isPositiveLabel: (Int) -> Boolean = number@ {
	val isPositive = it > 0
    return@number isPositive // 라벨을 사용해 반환됨
  • isPositive같이 추론된 반환 자료형이 Unit이 아닌 경우에는 본문의 마지막 표현식이 반환값으로 처리된다.
  • isPositiveLabel과 같이 특정 라벨을 지정해 반환할 수도 있다.

고차함수

고차함수는 함수의 매개변수로 함수를 받거나 함수 자체를 반환할 수 잇는 함수이다.

fun inc(x: Int): Int {
	return x + 1
}

fun high(name: String, body: (Int)->Int): Int {
	println("name: $name")
    val x = 0
    return body(x)

위처럼 high 의 두번째 매개변수 body는 람다식 함수를 받을 수 있다. 이것을 다시 함수 본문에서 함수로 반환하고 있다.

고차함수 표현법

val result = high("Sean", { x -> inc(x + 3) }) // 함수를 이용한 람다식
val result2 = high("Sean") { inc(it + 3) } // 소괄호 바깥으로 빼내고 생략
val result3 = high("Kim", ::inc) // 매개변수 없이 함수의 이름만 사용할 때
val result4 = high("Sean") { x -> x + 3 } // 람다식 자체를 넘겨준 형태
val result5 = high("Sean") { it + 3 } // 매개변수가 1개인 경우 생략

클로저

  • 람다식으로 표현된 내부 함수에서 외부 범위에 선언된 변수에 접근할 수 있는 개념.
  • 이때 람다식 안에 있는 외부 변수는 값을 유지하기 위해 람다식이 포획(Capture)한 변수라고 부른다.

기본적으로 함수 안에 정의된 변수는 지역 변수로 스택에 저장되어 있다가 함수가 끝나면 같이 사라진다. 하지만 클로저 개념에서는 포획한 변수는 참조가 유지되어 함수가 종료되어도 사라지지 않고 함수의 변수에 접근하거나 수정할 수 잇게 해준다.

클로저의 조건
1. final 변수를 포획한 경우 변수 값을 람다식과 함께 저장한다.
2. final이 아닌 변수를 포획한 경우 변수를 특정 래퍼로 감싸서 나중에 변경하거나 읽을 수 있게 한다. 이 때 래퍼에 대한 참조를 람다식과 함께 저장한다.

클로저 테스트하기

이렇게 클로저를 사용하면 내부의 람다식에서 외부 함수의 변수에 접근해 처리할 수 잇어 효율성이 높다. 또 완전히 다른 함수에서 변수에 접근하는 것을 제한할 수 있다. 코틀린 표준 라이브러리는 이러한 개념이 사용되어 설계되었다.


코틀린 표준 라이브러리

let() apply() with() also() run() 등 여러 가지 표준 함수가 있다.

확장 함수의 람다식 접근 방법

함수 이름람다식의 접근 방법반환 방법
T.letitblock 결과
T.alsoitT caller (it)
T.applythisT caller (this)
T.run 또는 runthisblock 결과
withthisUnit

파일 입출력

Java와 비슷하지만 use()를 사용하면 자동으로 close()를 해준다.


11-1 동시성 프로그래밍

다중 작업을 진행할때 코틀린에서는 코루틴(Coroutine)을 서드파티가 아닌 기본적으로 제공해주고 있다.


profile
배운거 정리하기

0개의 댓글