[Kotlin 문법] 10. 입출력 (1) - 콘솔 입출력, 문자열 포맷팅(출력 형식 지정)
지금까지는 kotlin의 전체적인 구조를 이해할 수 있도록 중요한 구조들을 공부하였다.
이제부터는 코딩테스트나 개발에 직접적으로 사용할 기본적인 기능과 함수들에 대해 공부할 예정이다.
그에 대한 첫 공부로, kotlin 입출력 방법에 대해 알아보겠다.
kotlin에서 입출력 받는 방법은 크게 '콘솔 입출력 / 파일 입출력 / 네트워크 입출력'이 있다.
다만, 네트워크 입출력은 사실 네트워크 관련 자세한 지식이 필요하기 때문에, 여기서 다루지는 않을 것이다.
이번에는 '콘솔 입출력'에 대해 공부하겠다.
입력 받기
입력을 받는 방법은 크게 세 가지가 존재한다.
1) readLine() / readln()
- 기본적인 입력을 받기 위해 사용
fun main() {
val s1 = readLine() // 사용자가 입력한 값을 문자열(String)로 저장
}
readLine()은 String? 타입을 반환한다.
따라서, !!나 ?:를 사용해 null에 대한 처리를 할 수 있다.
반면에, readln()은 non-null 타입이라는 차이가 존재한다.
2) Scanner
- 여러 개를 입력받을 때 사용
import java.util.Scanner
fun main() {
val scanner = Scanner(System.`in`) // in이 키워드이므로 감싸주어야 함
val num = scanner.nextInt() // 정수 입력 받기
val doub = scanner.nextDouble() // 실수 입력 받기
val text = scanner.next() // 문자열 입력 받기
val text2 = scanner.nextLine() // 공백 포함 문자열 입력 받기
}
Scanner를 사용하면 nextInt()나 nextDouble() 등 다양한 타입을 입력받을 수 있다.
Scanner.next()는 공백을 기준으로 입력받으며,
Scanner.nextLine()은 띄어쓰기가 포함된 문자열을 입력받을 수 있다. 즉, 개행문자를 기준으로 입력받는다.
3) BufferedReader
- 대량의 데이터를 빠르게 읽을 때 사용
- 속도차이가 다른 입력 방식과 크게 나기 때문에, 속도가 중요하다면 이 클래스를 사용하는 것을 추천
import java.io.BufferedReader
import java.io.InputStreamReader
fun main() {
val reader = BufferedReader(InputStreamReader(System.`in`))
val input = reader.readLine()
}
이렇게 InputStreamReader를 사용하는 방식이 있고,
아래와 같이 한 번에 선언하는 방법이 있다.
import java.io.BufferedReader
fun main(){
val reader = System.`in`.bufferedReader()
val input = reader.readLine()
}
출력하기
출력하기 위해 사용할 수 있는 방법은 세 가지가 존재한다.
1) print()
- 줄바꿈 없이 내용을 출력하는 함수
fun main(){
print("Hello Kotlin")
}
2) println()
- 줄바꿈을 포함해 출력하는 함수
fun main(){
println("Hello Kotlin")
}
3) BufferedWriter
- 대량의 데이터를 출력할 때 효율적
- 즉시 출력하는 println()과 달리, 버퍼에 저장 후 한 번에 출력하기 때문에 속도가 빠름
import java.io.BufferedWriter
import java.io.OutputStreamWriter
fun main() {
val writer = BufferedWriter(OutputStreamWriter(System.out))
writer.write("Hello, Kotlin!\n") // 줄바꿈 포함
writer.write("BufferedWriter 출력 예제\n")
writer.flush()
writer.close()
}
여기서 주의할 점은, 문자열만 버퍼에 저장할 수 있기 때문에 정수를 입력하더라도 그 값에 해당하는 유니코드 문자가 버퍼에 저장된다는 점이다.
예를 들어, writer.write(97)을 하면, 97이 출력되는 것이 아니라 a가 출력된다.
flush()를 호출해야 실제로 출력되며, write()를 여러 번 호출해도 한 번에 출력되어 성능이 향상된다.
close()는 버퍼를 비워서 출력하는 flush()의 역할과 동시에 버퍼를 닫아주는 역할도 포함하고 있다.
따라서, 둘 중 하나만 사용하더라도 정상적으로 출력 후 종료된다.
flush()는 스트림을 닫지 않으므로 추가 출력이 가능하다.
즉, 아래와 같은 사용이 가능하다.
import java.io.BufferedWriter
import java.io.OutputStreamWriter
fun main() {
val writer = BufferedWriter(OutputStreamWriter(System.out))
writer.write("Hello, Kotlin!\n")
writer.flush() // 버퍼에 있는 데이터를 즉시 출력
writer.write("추가 출력\n")
writer.flush() // 다시 출력 가능 (스트림이 닫히지 않음)
writer.close() // 마지막에 닫아주기
}
반면에,
close()는 자동으로 flush()가 실행되면서 스트림이 닫히게 된다.
따라서 아래처럼 close 후 다시 출력하는 일은 불가능하다.
import java.io.BufferedWriter
import java.io.OutputStreamWriter
fun main() {
val writer = BufferedWriter(OutputStreamWriter(System.out))
writer.write("Hello, Kotlin!\n")
writer.close() // 스트림 닫기 (자동으로 flush() 실행됨)
writer.write("출력 불가능!") // ❌ 오류 발생: 스트림이 닫혀서 write() 사용 불가
}
문자열 포맷팅
문자를 출력하는 과정에서 원하는 형식으로 지정해 출력할 수 있다.
예를 들어, a라는 변수의 값을 출력하고 싶을 때,
"a"
와 같이 단순히 a라는 변수의 값만 출력되도록 할 수 있지만,
"a = 5 and b = 2."
처럼 여러 개의 값을 형식을 지정해 출력할 수도 있다. 이것을 쉽게 만들어 주는 방식이 '문자열 포맷팅'이다.
print()만 사용한다면, 다음과 같은 방식으로 출력할 수 있을 것이다.
print("a = ")
print(a)
print(" and b = ")
print(b)
print(".")
그러나 이 방식은 너무 귀찮다.
이걸 좀더 빠르게 할 수 있게 두 가지 방법을 사용할 수 있다.
1) string interpolation (문자열 보간)
- 변수 이름에 '$' 문자를 붙이면 이 변수의 값으로 인식한다.
println("a = $a and b = $b.")
2) format()
- String.format() 함수를 사용해 형식을 지정한다.
- string interpolation과 다르게 변수의 값을 지정할 때는 %를 붙여 나타내며, '%[출력 형식]'으로 사용한다.
- 앞에서 사용된 변수의 개수만큼 뒤에 순서대로 변수의 이름을 나열해준다.
println(String.format("a = %d and b = %d.", a, b))