Kotlin Study _2

김민진·2022년 3월 15일
0

kotlin

목록 보기
4/5

코틀린 스터디 _2 의 기록

함수 정의와 호출

코틀린에서 컬렉션 만들기

val set = hashSetOf(1,7,53)

숫자로 이뤄진 집합을 만들때는 위와 같은 식으로 만들 수 있다.

비슷한 방법으로 리스트와 맵도 만들 수 있다.

val list = arrayListOf(1,7,53)

val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three")

특이한 점은 Map 을 만들때의 to 는 특별한 키워드가 아니라 일반 함수라는 점 이다.

책에서는 나중에 다룬다지만.. 뭔지는 알고 넘어가야지

여기서 to 는 중위호출 이라고 한다.

to를 이용해서 key,value를 나누는 느낌이였다.

코틀린에는 Pair라는 특정 클래스가 존재하고 위의 방법을 사용해서 Pair클래스를 사용하는 방식인거같다.. 아직까지는 잘 모르겠다

해당 변수가 어떤 클래스인지 확인하려면 javaClass 라는 키워드를 사용해야 한다.
Dart에는 비슷한 걸로 runtimeType 이라는 게 있다.

해당 사용 방법으로는

set.javaClass;
list.javaClass;
map.javaClass;

로 사용이 가능하고 각각 출력시

class java.util.HashSet
class java.util.ArrayList
class jva.util.HashMap 

으로 출력된다.

코틀린은 자신만의 컬렉션 기능을 제공하지 않는단다.

자신만의 컬렉션을 제공하지 않는 이유는

자바에서 코틀린 함수를 호출하거나 코틀린에서 자바의 함수를 호출할때 자바와 코틀린 컬렉션을 서로 변환할 필요가 없단다.

그렇게 되면 서로 서로 호출하기가 매우 용이하고 빠르기때문에.. 매우 좋은 장점으로 보인다.

다른 클래스에서 부르거나 하려면 ... 엄두도 안난다

코틀린과 자바 컬렉션은 같은 클래스이지만 코틀린에서는 더 많은 기능을 사용할 수 있다.

val strings = listOf("first","second","fourteenth")
strings.last() -> fourteenth

val numbers = setOf(1,14,2)
numbers.max() -> 14

함수가 많음으로써 더 많은걸 암기해야하고 더 효율적으로 사용할 수 있겟다.

함수를 호출하기 쉽게 만들기

자바의 컬렉션에는 default로 toString이 구현되어져 있단다.

하지만 그 default로 선언된 toString은 출력이 고정되어져 있고 우리에게 필요한 형식이 아닐 수 도 있단다... 책에 이렇게 쓰여져 있는데 이게 뭔소리지??

val list = listOf(1,2,3)
println(list)
-> [1,2,3].  -> toString() 호출

default 구현과 달리 (1;2;3;) 처럼 원소 사이를 세미콜론으로 구분하거나 괄호로 둘러싸고 싶은 커스텀을 구현해보자

다음 리스트의 joinToString 함수는 컬렉션의 원소를 StringBuilder 의 뒤에 덧붙인다.
이때 원소 사이에 구분자(separator)를 추가하고 StringBuilder 의 맨 앞과 맨 뒤에 접두사(prefix) 와 접미사(postfix)를 추가한다.

fun <T> joinToString(
	collection:Collection<T>,
    separator:String,
    prefix:String,
    postfix:String) : String {
    	val result = StringBuilder(prefix)
        for((index,element) in collection.withIndex()){
        	if(index>0) result.append(separator) -> 첫 원소 앞에는 구분자를 붙이면 안된다.
            result.append(element)
            }
            result.append(postfix)
            return result.toString();
}

이 함수는 제네릭하다. 즉 이 함수는 어떤 타입의 값을 원소로 하는 컬렉션이든 처리할 수 있다.
이 함수를 사용한다면 아래와 같이 출력된다.

val list = listOf(1,2,3)
println(joinToString(list,";","(",")"))
-> (1;2;3)

이름 붙인 인자

위의 함수를 호출할 때 마다 매번 네 인자를 모두 전달하지 않을 수는 없을까?

joinToString(collection," "," ",".")

인자로 전달한 각 문자열이 어떤 역할을 하는지 구분할 수 있을까?

각 원소는 공백으로 구분될까? 마침표로 구분될까? 함수의 시그니처를 살펴보지 않고는 이런 질문에 답하기 어렵다.

이런 문제는 특히 boolean flag 값을 전달해야 하는 경우 흔히 발생한다.

이를 해결하기 위해 일부 자바 코딩 스타일에선느 boolean 대신 enum 타입을 사용하라고 권장한다.

일부 코딩 스타일에서는 다음과 같이 파라미터 이름을 주석에 넣으라고 요구하기도 한다.

/*자바*/
joinToString(collection,/*separator*/ " ",/*prefix*/ " ",/*postfix*/ ".");

코틀린 에서는 다음과 같이 더 잘 할 수 있다.

joinToString(collection,separator=" ",prefix=" ",postfix=".")

코틀린으로 작성한 함수를 호출할 때는 함수에 전달하는 인자 중 일부의 이름을 명시할 수 있다.

위의 부분은 namedParameter을 의미하는 것 같다. 자바에서는 이 기능이...지원이 안되었던건가?????

미친거아닌가 싶다.
lt Parameter

위의 부분과 named 부분은 .. 이미 알고 있는 내용이므로 넘어간다. 심지어 c#에서도 이 기능이 지원되는걸로 알고있는데.. 자바는..안되었나??? 자바 개발자가 아니다보니 애매모호하다.

정적인 유틸리티 클래스 없애기: 최상위 함수와 프로퍼티

코틀린 컴파일러가 생성하는 클래스의 이름은 최상위 함수가 들어있던 코틀린 소스 파일의 이름과 대응한다.
코틀린 파일의 모든 최상위 함수는 이 클래스의 정적인 메서드가 된다.

파일에 대응하는 클래스의 이름을 변경하려면
@JvmName 어노테이션을 사용하면 된다.

해당 키워드의 어노테이션은 파일의 맨 앞, 패키지 이름 선언 이전에 위치해야 한다.

@file:JvmName("StringFunction") -> 클래스 이름을 지정하는 어노테이션
package strings
fun joinToString(...):String{...} -> @file:JvmName 어노테이션 뒤에 패키지 문이 와야 한다.

이제 다음과 같이 joinToString 함수를 호출할 수 있다.

import strings.StringFunctions;
StringFunctions.jonToString(lsit,",","","");

최상위 프로퍼티

함수와 마찬가지로 프로퍼티도 파일의 최상위 수준에 놓을 수 있다. 어떤 데이터를 클래스 밖에 위치시켜야 하는 경우는 흔하지는 않지만, 그래도 가끔 유용할 때가 있다.
var opCount = 0 -> 최상위 프로퍼티를 선언한다.
fun performOperation() {
	opCount++ -> 최상위 프로퍼티의 값으 변경한다.
    //...
}

fun reportOperationCount(){
	println("Operation performed $opCount times") -> 최상위 프로퍼티의 값을 읽는다.

이런 프로퍼티의 값은 정적 필드에 저장된다.
최상위 프로퍼티를 활용해 코드에 상수를 추가할 수 있다.

val UNIX_LINE_SEPARATOR ="\n"

기본적으로 최상위 프로퍼티도 다른 모든 프로퍼티처럼 접근자 메서드를 통해 자바 코드로 노출된다
(val 의 경우 getter var 의 경우 getter,setter가 생긴다)

겉으론 상수처럼 보이지만 실제로 getter를 사용해야 한다면 자연스럽지 못한다.

더 자연스럽게 사용하려면 public static final 필드로 컴파일 해야 한다.
(단 원시 타입과 String 타입의 프로퍼티만 const로 지정할 수 있다)

const val UNIX_LINE_SEPARATOR ="\n"

앞의 코드는 다음 자바 코드와 동등한 바이트 코드를 만들어 낸다.

public static final String UNIX_LIUE_SEPARATOR ="\n";

메서드를 다른 클래스에 추가 : 확장 함수와 확장 프로퍼티

기존 코드와 코틀린 코드를 자연스럽게 통합하는 것은 코틀린의 핵심 목표 중 하나다.

기존 자바 API를 재 작성하지 않고도 코틀린이 제공하는 여러 편리한 기능을 사용할 수 있다면 정말 좋은 일 아닐까?
바로 확장 함수(extendsion function)가 그런 역할을 해줄 수 있다.

확장 함수는 어떤 클래스의 멤버 메서드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수다.

문자열의 마지막 문자를 돌려주는 매서드를 추가해보자

package strings
fun String.lastChar() : Char = this.get(this.length-1)

확장 함수를 만드려면 추가하려는 함수 이름 앞에 그 함수가 확장할 클래스의 이름을 덧붙이기만 하면 된다.

클래스 이름을 수신 객체 타입(receiver type) 이라 부르며
확장 함수가 호출 되는 대상이 되는 값을 수신 객체(receiver object) 라고 부른다.

fun String(수신 객체 타입).lastChar(): Char = this(수신 객체).get(this(수신 객체).length -1)

이 함수를 호출하는 구문은 다른 일반클래스 멤버를 호출하는 구문과 똑같다.

println("Kotlin".lastChar())
n

이 예제에서는 String 이 수신 객체 타입이고. "kolint"이 수신 객체이다.

어떤 면에서는 String 클래스에 새로운 메서드를 추가하는 것과 같다.

자바 클래스로 컴파일한 클래스 파일이 있는 한 그 클래스에 원하는 대로 확장을 추가할 수 있다.

일반 메서드의 본문에서 this를 사용할 때와 마찬가지로 확장 함수 본문에도 this를쓸 수 있다. 그리고 일반 메서드와 마찬가지로 확장 함수 본문에서도 this를 생략할 수 있다.

package strings
fun String.lastChar() : Char = get(length -1) -> 수신 객체 멤버에 this 없이 접근할 수 있다.

확장 함수 내부에서는 일반적인 인스턴스 메서드의 내부에서와 마찬가지로 수신 객체의 메서드나 프로터피를 바로 사용할 수 있다.
하지만 확장 함수가 캡슐화를 깨지는 않는다.

클래스 안에서 정의한 메서드와 달리 확장 함수 안에서는 클래스 내부에서만 사용할 수 있는 비공개(private) 멤버나 호보된(protected) 멤버를 사용할 수 없다.

kotlin 도 flutter 처럼 import시 as 로 키워드를 설정할 수 있다.

import strings.lastChar as last

확장 함수를 오버라이드 할 수는 없다.
코틀린은 호출될 확장 함수를 정적으로 결정하기 때문이다.

함수형 언어는 비슷한 규격을 가지고 있는 것 같다.

배워보고 직접 써봐야 알거 같은 이론도 많은 부분이 있는거 같다..

profile
dart,c#,java 개발자! 잡다하게 해서 문제될게 없다!

0개의 댓글