Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

DOing

상수와 리터럴의 차이와 Kotlin의 함수 리터럴 본문

Java & Kotlin

상수와 리터럴의 차이와 Kotlin의 함수 리터럴

mangdo 2022. 3. 26. 01:01

코틀린을 다루며 자주 등장하는 함수 리터럴에 대한 개념을 제대로 잡기 위해서,
상수와 리터럴을 비교하며 리터럴이란 무엇인지, 코틀린에서의 함수 리터럴이란 무엇인지에 대해서 포스팅하고자 한다.

 


🌱 상수(Constant)

  상수와 리터럴 둘다 변하지않는 값(데이터)을 의미한다. 더 구체적으로 말하면, 상수는 변하지 않는 변수를 의미한다. 상수에 데이터는 숫자가 올 수 있지만, 클래스나 구조체와 같이 기본형에서 파생된 객체를 넣을 수 있다. 

 상수의 데이터가 변하면 안된다고 해서, 참조변수를 상수로 지정할 때, 참조변수에 넣은 인스턴스 안의 데이터까지도 않는다고 착각할 수 있다. 하지만 참조변수가 상수, 즉 참조변수 메모리의 주소값이 변하지않는다는 의미이지 그 주소가 가리키는 데이터들까지 상수라는 의미가 아니다. 다음 Java 코드에서와 같이 클래스 안의 데이터를 변경해도 상관이 없다는 의미이다.

final Test t1 = new Test(); // 상수 선언
// t1 = new Test(); : 불가
t1.num = 10; // 가능!

* 프로그래밍에서 상수를 쓸때 c계열에서는 const를, java에서는 final 제어자를 쓴다.

2021.09.14 - [java] - [java] static과 final의 차이 (하드 코딩 대신 상수 사용하기)

 

 

🌱 리터럴(Literal)

상수에 달리 리터럴은 데이터 그 자체, 변수에 넣는 변하지 않는 데이터를 의미한다.

final int a = 1; // 리터럴 1

  위의 코드의 1과 같이 변하지 않는 데이터(boolean, char, double, long, int, etc...)를 리터럴(literal)이라고 부른다. 하지만, 인스턴스(클래스 데이터)는 리터럴이 될 수 없다. 보통 인스턴스는 동적으로 사용하기 위해 작성됨으로 리터럴이될 수 없다. 왜냐하면 값이 언제 바뀔지 모르기 때문이다. 하지만, 데이터가 변하지 않도록 설계한 클래스를 한 불변 클래스(immutable)라면 이야기가 다르다.

 불변 클래스는 한번 생성하면 객체 안의 데이터가 변하지 않는다. 변할 상황이라면 새로운 객체를 만들어준다. 자바의 String, Color와 이와 같은 예시이다. 따라서 "abc"와 같은 문자열을 자바에서는 '객체 리터럴' 짧게는 리터럴이라고 표현한다.

 


🌱 함수 리터럴(Function Literal)

함수 리터럴이란, 함수를 나타내는 리터럴을 말한다. 코틀린에서는 함수 리터럴을 람다식 또는 익명함수 이 두가지 방법으로 만들 수 있다.

 

1) 람다식으로 함수 리터럴 만들기

- 함수 리터럴의 매개변수

 : number:Int부분이 함수 리터럴의 매개변수를 뜻한다. ->를 경계로, 매개변수와 함수의 내용이 분리된다.

 : {매개변수 -> 반환값}의 형태를 가지고 있다.

 

- 함수 리터럴의 반환값

 : 람다식에서는 따로 return을 적지 않는다. 함수 리터럴의 반환값은 함수 내용의 맨 마지막 표현식이 된다.

 : instantFunc이라는 함수 리터럴은 최종적으로 (Int) -> Unit 타입을 갖는다.
 : 함수 타입은 참조타입이기 때문에, 객체와 마찬가지로 스택 영역에 함수가 바로 저장되는 것이 아니라, 함수의 위치를 가리키는 형태로 저장이된다. 즉, instantFunc 참조변수에 (Int) -> Unit 타입의 함수가 저장된다.

 

- 함수타입의 변수 호출

  1. ()로 바로 호출

  2. invoke를 통해 호출

    : 일반적으로는 ()로 호출하지만, 변수가 Nullable일때는 instantFunc?.invoke(33)처럼 invoke를 사용해서 호출하는 편이 Null 처리하기 편해진다.

 

2) 익명 함수로 함수 리터럴 만들기

 익명함수는 함수의 이름이 없다는 점만 빼면 일반 함수와 형태가 거의 동일하다. 익명함수는 람다식보다 복잡하지만, return으로 반환값을 지정해줄 수 있기 때문에 마지막 표현식이 자동으로 반환값이 되어버리는 람다식보다 버그를 일으킬 확률이 적다.

 

 

🌱 함수 리터럴의 사용예시 : 고차함수

 고차함수란, 인수로 함수를 받거나 함수를 반환하는 함수를 말한다.

 만약 어떤 함수를 호출하기 전 후로 고정적인 작업이 있는 상황에서 고차함수를 활용할 수 있다. 예를들어 어떤 함수를 호출하기 전 후로 고 "작업시작! 작업 끝"을 출력해야한다고 하자. 이때 매번 println을 호출하자니 코드가 중복되어 보기에 좋지 않을 수 있다. 고차함수를 이용하여 코드를 작성하면 다음과 같다.

 

🌱 클로저

 f()가 호출되는 시점에는 num 매개변수는 이미 사라지고 없다. returnFunc함수가 끝나느 순간 num 매개변수는 소멸하기 때문이다. 하지만 f()는 30을 출력한다. 어떻게 이것이 가능할까? 바로 함수 리터럴이 자신이 만들어질 때의 상황을 기억하고 있기 때문이다. 함수 리터럴이 만들어지는 순간, 함수 리터럴은 자기 주변의 상황을 함께 저장한다. 즉, 함수가 만들어질 때 num 매개변수의 값을 복사해 갖고 있는다. 이렇게 함수가 만들어질때 주변상황을 기억하는 함수를 클로저(Closure)라고 한다.

 

🌱 it 식별자

람다식의 매개변수를 생략하면 it이라는 특별한 식별자가 만들어진다. 여기서 it은 생략한 매개변수를 대체한다.

예제코드에서는 it이 생략한 Int타입의 매개변수를 대체한다.

(Int) -> Unit타입에 맞는 함수 리터럴을 작성하려면, Int입의 매개변수를 적어주어야하지만 생략했다.

 

🌱 리시버가 붙은 함수 리터럴와 this 키워드

  함수 리터럴에 리시버를 사용하여 확장함수처럼 만들 수 있다.

  Int.(left:Int, right:Int)->Int는 리시버 타입이 Int이고, 매개변수 타입이 (Int, Int)이며 반환타입이 Int인 함수 타입이다. 리시버가 적용된 함수 리터럴을 만들고 있다. 람다식으로 함수 리터럴을 작성할 때는 기존과 동일하게 적으면 된다. 익명함수 형태로 함수 리터럴을 작성하고 싶다면, fun Int.(left:Int, right:Int):Int {...}로 적으면된다. 리시버가 붙은 함수 리터럴에는 리시버를 나타내는 this키워드를 사용할 수 있다.

리시버가 적용된 함수타입의 변수는 리시버.변수(인수) 형태로 호출할 수 있다. 15,18,25가 각각 범위에 맞게 가공되어 출력된다.

일반 함수 타입과 호환되는 리시버가 붙은 함수 타입

Int.(Int, Int)->Int 타입은 (Int, Int, Int) -> Int 타입에 대입할 수 있다.

일반함수 타입으로 호출할때는 리시버를 첫번째 인수로 전달하면된다.

 

 

 

🌱  함수 참조

함수 타입의 변수는 이미 선언되어있는 함수나 객체의 멤버 함수를 가리킬 수 있다.