본문 바로가기

Language/Kotlin

[Kotlin] 9장. 표준 함수와 파일 입출력

728x90

람다식

람다식은 항상 중괄호로 묶여 있으며 중괄호 안에 매개변수는 화살표 왼쪽에 배치, 오른쪽에는 그에 따른 식을 구현한다.

 

val 변수 이름: 자료형 선언 = { 매개변수[,...] -> 람다식 본문 }

 

 

sum은 익명 함수로 만들어지며, 람다식의 x + y 값이 반환 된다. 여기서 x, y의 값은 고정된 것이 아닌 사용자가 마음대로 선언이 가능하다.

하지만, 매개변수가 1개인 경우 매개변수를 사용하여 표현할 수 있지만 매개변수를 생략하고 it으로 표기할 수 있다. 이 때, it 은 고정된 변수 이름이므로 변경해선 안된다.

클로저

람다식으로 표현된 내부 함수에서 외부 범위에 선언된 변수에 접근할 수 있는 개념을 말한다.

이 때, 람다식 안에 있는 외부 변수는 값을 유지하기 위해 람다식이 포획한 변수라고 부른다.

 

 

let() 함수

let() 함수는 함수를 호출하는 객체 T를 이어지는 block의 인자로 넘기고 block의 결괏값 R을 반환한다.

 

public inline fun <T, R> T.let(block: (T) -> R): R { ... return block(this) }

 

매개변수로는 람다식 형태인 block이 있고 T를 매개변수로 받아 R을 반환한다. 본문의 this는 객체 T를 가리키며, 람다식 결과 부분을 그대로 반환한다.

 

 

score?.let 을 통해 null 체크를 하여 null 이 아닐 경우에만 뒤의 중괄호를 수행하며, 중괄호의 결과를 반환하기 때문에 str 변수에 넣어서 해당 값을 출력할 수 있다.

여기서, 엘비스 연산자(?:)를 사용하면 null일 경우의 값 또한 출력이 가능하다.

 

 

이처럼 작성하게 되면, score 값이 null이 아닐 경우 자신의 값을 그대로 출력, null일 경우 is null 이라는 값을 출력한다.

 

also() 함수

also 함수는 let 함수와 역할이 거의 동일해 보이지만, let 함수는 마지막으로 수행된 코드 블록의 결과를 반환하고 also 함수는 블록 안의 코드 수행 결과와 상관없이 T인 객체 this를 반환한다.

 

 

여기서 출력되는 결과는 5가 아니라 원본 값 1이 출력되게 된다.

 

apply() 함수

apply 함수는 also 함수와 마찬가지로 호출하는 객체 T를 이어지는 block으로 전달하고 객체 자체인 this를 반환한다.

apply 함수와 also 함수의 다른점은 apply 함수는 T.()와 같이 람다식이 확장 함수로 처리된다는 것이다.

즉, also 함수에서는 it을 사용하며 그것을 생략할 수 없지만, apply 함수에서는 this.변수 이름 으로 사용하며 this 자체를 생략하여 변수이름 으로만 사용이 가능하다는 것이다.

 

 

람다식과 DSL

DSL(Domain-Specific Language)는 특정 애플리케이션의 도메인을 위해 특화된 언어이다. 예를들어 DB에 접근하기 위한 SQL이 대표적인 DSL이다.

파일의 입출력

readLine() 함수

 

 

다음과 같이 사용하며, 사용자의 입력을 받을 수 있도록 해주는 함수이다.

!! 혹은 ? 를 사용하여 NPE 발생 여부를 처리할 수 있으며, 입력받은 값들은 문자열 값이 기본이기 때문에 toInt() 등을 사용하여 사용에 알맞은 형태로 형변환이 가능하다.

 

Stream 처리 API

Kotlin에서는 처리할 데이터의 양에 따라, 간단한 데이터는 readBytes, readLines, readText 계열의 함수를 사용한다.

대량의 데이터는 copyTo, forEachBlock, forEachLine 과 같은 API를 사용한다.

InputStream, Reader, Writer를 쓸 때는 호출 후 사용이 완료되면 반드시 닫아야 한다.

 

블로킹과 넌블로킹

프로그램에서 쓰려고 하는데 쓸 공간이 없으면 공간이 비워질 때까지 기다리게 되며, 반대로 읽으려고 하는데 읽을 내용이 없다면 기다리게 된다.

따라서, 비워지거나 채워지기 전까지는 쓰고 읽을 수 없기 때문에 호출한 코드에서 계속 멈춰있는 것을 블로킹이라고 한다.

하지만, 메인 코드의 흐름을 방해하지 않도록 입출력 작업 시 스레드나 비동기 루틴에 맡겨 별개의 흐름으로 작업하게 되는 것을 넌블로킹이라고 한다.

 

파일에 쓰기

Files 클래스의 write()를 사용해 경로에 지정된 파일을 생성하고 내용을 쓴다.

경로는 Paths 클래스를 사용하고 있으며 지정된 문자열 text를 toByteArray()로 변환하여 지정하고 있다.

파일을 생성할 떄의 옵션으로 StandardOpenOption을 사용하고 있는데, 주요 옵션은 다음과 같이 4개가 존재한다.

  • READ : 파일을 읽기용으로 연다
  • WRITE : 파일을 쓰기용으로 연다
  • APPEND : 파일이 존재하면 마지막에 추가한다
  • CREATE : 파일이 없으면 새 파일을 생성한다

 

PrintWriter, BufferedWriter 클래스

PrintWriter의 경우 기본적인 printWriter() 외에도 print(), println(), printf(), write()처럼 파일에 출력하는 메서드를 제공하고 있어 기존에 콘솔에 출력하듯이 바이트 단위로 파일에 쓸 수 있다.

BufferedWriter버퍼를 사용해 데이터를 메모리 영역에 두었다가 파일에 쓰는 좀 더 효율적인 파일 쓰기를 지원한다.

 

 

printWriter는 이처럼 사용한다. 위에서는 close를 사용했지만, 아래서는 close를 사용하지 않고 있는데, 이는 use()를 사용하면 닫을 수 있는 객체를 자동으로 닫아주기 때문에 close를 직접 선언하지 않아도 되기 때문이다. 즉, use() 내부적으로 close()를 호출한다.

 

bufferedWriter 는 버퍼를 사용한다는 차이점만 빼면 사용법은 printWriter와 같다. 즉, 위의 예제에서 printWriter 대신 bufferedWriter를 사용하면 된다는 것이다.

 

파일에서 읽기

FileReader 클래스의 readText()를 사용하여 텍스트를 읽어들이는데, readText는 내부적으로 StringWriter()를 호출해 텍스트를 메모리로 가져온 후 그 내용을 반환한다.

 

 

copyTo()

copyTo()는 파일에 대한 복사 작업을 처리하는데 사용한다.

 

public fun File.copyTo(target: File, overwrite: Boolean = false, 
	bufferSize: Int = DEFAULT_BUFFER_SIZE): File

 

copyTo()는 목적지인 target에 파일을 버퍼 크기만큼 한번에 복사한다.

overwrite 매개변수를 통해 기존에 파일이 존재하면 덮어쓸지 결정하며, bufferSize를 통해 버퍼 크기를 설정한다.

선언부에서 나와있듯이 overwrite와 bufferSize의 경우 기본 값이 설정되어 있기 때문에 생략이 가능하다.

 

 

이 때, 첫 번째 매개변수인 path 값이 없으면 FileNotFoundException 오류가 발생하며, 복사할 대상은 오로지 파일이어야만 한다.

파일 용량이 크다면 복사되는 도중에 블로킹하면서 멈출 가능성이 있다.

728x90