[Java] 자바로 프로그래밍 입문하기: 1.5. 입출력 (1)
입출력
이번 차시에서는 표준 입력(standard input), 표준 그림(standard drawing), 표준 소리(standard audio) 등을 통해, 우리가 이미 자바 프로그램과 바깥 세상 사이에 사용했던 간단한 추상화(커맨드 라인 입력과 표준 출력)를 좀 더 확장해볼 것입니다.
I/O는 input/output의 축약입니다. 이는 프로그램이 바깥 세상과 소통하는 방법에 대한 것입니다. 여러분의 컴퓨터 운영 체제가 컴퓨터 본체에 있는 물리적 기기와 소통하는 것이죠. 표준 입출력 추상화를 구현하기 위해서, 우리는 OS의 인터페이스인 라이브러리를 사용합니다. 이러한 표준 입출력 추상화를 사용하면, 좀 더 쉽게 여러분과 프로그램이 소통할 수 있습니다. 여러분이 하드 디스크의 어느 부분에 접근할 건지, 키보드의 무슨 버튼을 눌렀는지를 일일이 신경쓴다면 머리 아프겠죠?
여러분들은 이미 명령행 인자들을 통해 명령행의 값을 수용하고 문자열을 출력해보았습니다. 이번 차시의 목적은, 자료를 처리하고 제공하는데 사용되는 도구들을 좀 더 다양하게 보여주는 데 있습니다. 이제껏 사용해왔던 System.out.print()와 System.out.println() 메소드는 수학적 함수와는 거리가 멀죠. 반환값이 없기 때문입니다. 이들의 목적은 입출력 장치에 영향(side effect)을 일으키는 것입니다. 주로 장치를 통해 정보를 들여오거나, 내보내거나 하는 것이죠.
표준 입출력 메커니즘의 필수적인 기능으로, 프로그램의 관점에서 입출력 양의 제한이 없어야 합니다. 여러분의 프로그램은 입력을 무한하게 소비하고, 출력을 무한하게 생성할 수 있습니다.
흔한 표준 입출력 메커니즘으로, 여러분의 컴퓨터 디스크에 있는 파일과 프로그램을 연결하는 것이 있습니다. 이런 연결은 자바 프로그램으로 하여금 결과를 저장하거나 불러오고, 다른 프로그램들을 참조할 수 있게 합니다.
조감도(Bird's-eye view)
멀리서 바라봅시다. 자바 프로그램은 커맨드 라인으로부터 입력 값을 받고, 문자열을 출력합니다. 기본적으로, 커맨드 라인 입력과 표준 출력은 커맨드를 입력하는 응용 프로그램으로부터 연결 됩니다. 예를 들어, 여러분이 입력한 'java HelloWorld'와, 출력된 'Hello, World!'는, <HelloWorld>라는 프로그램이 입력과 출력을 연결해준 것입니다. 그리고 이런 식의 방법은 생각보다 편리하죠.
명령행 입력(Command-line input)
우리가 프로그램에 입력값을 제공하기 위해 사용했던 이 메커니즘은 자바 프로그래밍의 표준입니다. 모든 클래스들은 main() 메소드가 있고, 인자로서 String 배열로 args[]를 받죠. args[] 배열은 우리가 입력한 명령행 인자들로 OS에게서 자바에게 제공됩니다. 편의 상 자바와 OS 둘 다 인자를 String으로 처리하므로, 숫자나 인자를 받고 싶다면 Integer.parseInt()나 Double.parseDouble() 메소드를 이용해 String에서 원하는 자료형으로 변환해야 합니다.
표준 출력(Standard output)
프로그램에 출력을 찍기 위해, System.out.println()과 System.out.print()를 사용합니다. 자바는 이 메소드들에 의한 호출 시퀀스의 결과를, 추상화된 문자 스트림의 형태로 제공하게 됩니다. 이것이 표준 출력입니다. 기본적으로 OS는 표준 출력으로 터미널 윈도우와 연결하기 때문에, 자바 프로그램의 모든 출력은 터미널 윈도우에 나타나게 됩니다.
우리의 위대한 시작점은 다음의 프로그램부터입니다. <프로그램 1.5.1, RandomSeq>는 명령행 인자 N을 받아 0부터 1사이의 무작위 숫자를 N개 출력합니다. 이는 OS가 허용하는 한 우리가 무한한 출력을 생성할 수 있음을 보여줍니다.
프로그램 1.5.1: Generating a random sequence
public class RandomSeq { public static void main(String[] args) { // Print a random sequence of N real values in [0, 1) int N = Integer.parseInt(args[0]); for (int i = 0; i < N; i++) System.out.println(Math.random()); } } |
0.2498362534343327
0.5578468691774513
0.5702167639727175
0.32191774192688727
0.6865902823177537
...
우리는 이제 우리에게 훨씬 더 유용한 프로그래밍의 모델을 제공하는 세 개의 추가적 메커니즘으로 명령행 입력의 한계를 해결할 것입니다.
표준 입력(Standard input)
클래스 StdIn은 표준 출력의 추상화를 보완하기 위해 표준 입력의 추상화를 구현하는 라이브러리입니다. 프로그램이 실행 중일 때에는 언제든지 표준 출력을 통해 값을 출력할 수 있는 것처럼, 언제든지 표준 입력 스트림을 통해 값을 읽을 수 있을 것입니다.
표준 그림(Standard drawing)
클래스 StdDraw는 프로그램에 그림을 생성할 수 있게 합니다. 이는 점과 선으로 된 그림을 그릴 수 있게 해주는 간단한 그래픽 모델입니다. StdCraw는 문자, 그림, 애니메이션과 관련된 것들도 포함하고 있습니다.
표준 소리(Standard audio)
클래스 StdAudio는 프로그램에 소리를 생성할 수 있게 합니다. 숫자 배열을 소리로 변환합니다.
커맨드 라인 입력과 표준 출력을 사용하기 위해서는 자바 기본 기능을 사용합니다. 표준 입력, 표준 그림, 표준 소리와 같은 추상화들을 제공하는 자바 기본 기능들도 물론 있지만, 그것들은 여러분들이 사용하기 조금 복잡합니다. 따라서 우리는 StdIn, StdDraw, StdAudio 안에 있는 간단한 인터페이스를 통해서 접근할 것입니다.
이는 프린스턴 대학교에서 제공하는 라이브러리들이며, 이를 CMD 혹은 Eclipse 터미널에서 사용하기 위해서는 다음 파일을 다운 받아 프로그램과 같은 경로에 설치해, 압축을 해제해야 합니다. 만약 단순히 Eclipse를 이용해서 강의를 진행하고 있다면, 다음 포스트를 참고하세요.
Eclipse에서 프로젝트에 외부 라이브러리(library) 추가하기
참고로, 위 라이브러리는 default-package에서 작성된 라이브러리이므로 직접 클래스에서 사용할 때에도 해당 클래스가 어떤 패키지에도 속하지 않아야 합니다. 만약 해당 라이브러리의 클래스를 인식하지 못하는 문제가 발생한다면, 이를 확인해보세요.
표준 출력(Standard output)
자바의 System.out.print()와 System.out.println() 메소드는 우리가 필요한 수준의 표준 출력 추상화를 구현합니다. 하지만 우리는 입문자이므로, 앞으로 사용될 다른 표준 출력들도 일관적인 방법으로 사용하고 싶습니다. 따라서 앞으로는 표준 입출력을 자바와 비슷하게 구현한 StdOut 라이브러리를 사용할 것입니다. System.out.print()와 System.out.println()은 대신 StdOut.print()와 StdOut.println()을 사용하면 됩니다.
StdOut.printf() 메소드는 이 절의 주된 주제입니다. 이를 이용하면, 출력의 표현을 좀 더 제어할 수 있습니다. 이는 C언어의 기능을 따서 구현한 것인데, C언어가 1970년부터 여지껏 살아남을 수 있었던 이유는, 여러모로 유용했기 때문입니다.
여러분은 double 값을 처음 출력해보았을 때, 지나치게 정확한 출력에 당황했을 것입니다. 예를 들어, System.out.print(Math.PI) 를 작성하면, 3.14를 훨씬 넘어 3.141592653589793까지 출력되죠.
printf() 메소드는 조금 더 유연합니다. 문자열로 데이터를 출력할 때, 몇자리까지 출력할 건지 명시할 수 있습니다. printf()로 3.14159를 출력하기 위해, 우리는 StdOut.printf("%7.5f", Math.PI) 처럼 작성할 수 있습니다. 또한 <프로그램 1.3.6>의 System.out.print(t)를 다음과 같이 바꿀 수 있습니다.
StdOut.printf("The square root of %.1f is %.6f", c, t); |
다음은, 이들의 의미와 각 내장 자료형에 따른 이 구문들의 확장된 연산들에 대해 알아보겠습니다.
포맷팅 기초(Formatted printing basics)
가장 간단한 형식으로, printf()는 두 인자를 받습니다. 첫번째 인자는 포맷 스트링(Format string, 형식/서식 문자열)입니다. 포맷 스트링은, 두번째 인자를 문자열로 바꿀 때 어떻게 바꿀 것인지에 대해 정의합니다.
간단한 포맷 스트링의 예로, %와 한 글자의 변환 지정자(Conversion specifiers)로 이루어진 것을 들 수 있습니다. 변환 지정자의 경우 자주 쓰이는 것으로는 d, f, s이며, 이는 각각 decimal(십진수의 정수), floating-point(부동소수점 수), string(문자열)을 의미합니다. 예를 들어 %d이면 해당하는 자리에는 정수가 문자열로 바뀔 것입니다.
%와 변환 지정자 사이에 정수를 넣어 자릿수(field width, 필드 폭)를 정할 수 있습니다. 만약 자릿수가 남는다면, 그만큼 왼쪽에 공백이 함께 출력됩니다. 만약 오른쪽에 공백을 추가하고 싶다면, 음수로 표기하면 됩니다. 만약 변환된 문자열이 자릿수보다 더 크다면, 자릿수는 무시됩니다.
따라서 이를 통해 double값의 소수점 이후 자릿수를 어디까지 표기할 건지, 문자열의 경우 몇 번째 문자부터 몇 번째 문자까지 표기하는지 등을 정할 수 있습니다. 잊지 말아야할 사실은, printf에서 변환 지정자의 포맷과 해당하는 위치의 자료형이 반드시 일치해야 한다는 점입니다.
포맷 스트링(Format string, 포맷/형식/서식 문자열)
printf()의 첫번째 인자는 포맷 스트링을 포함한 문자열입니다. 포맷 스트링이 아닌 부분은 그대로 출력되며, 포맷 스트링인 부분은 인자들에 따라 문자열로 치환됩니다. 다음을 참고하세요. \n은 printf()에서 개행을 하게 하는 문자입니다.
StdOut.printf("PI is approximately %.2f\n", Math.PI); |
세 개 이상의 인자(Multiple arguments)
printf() 함수는 둘을 초과하는 인자들도 받을 수 있습니다. 각 형식 지정자가 인자들에 일대일 대응합니다. 예를 들어, 대출 계좌를 관리하는 코드를 작성한다면, 반복문 안에 다음 구문이 있을 것입니다.
String formats = "%3s $%6.2f $%7.2f $%5.2f\n"; StdOut.printf(formats, month[i], pay, balance, interst); |
Jan $299.00 $9742.67 $41.67
Feb $299.00 $9484.26 $40.59
Mar $299.00 $9224.78 $39.52
여러 문자열을 다룰 때, 포맷 스트링은 꽤 편리합니다.
표준 입력(Standard input)
우리가 제공하는 StdIn 라이브러리는 표준 입력 스트림으로부터 공백으로 구분된 일련의 자료들을 받아들입니다. 각 값은 String이거나, 자바의 기본 자료형 중 하나입니다. 표준 입력 스트림의 주요한 기능 중 하나는, 여러분의 프로그램이 입력을 받을 때 해당하는 입력을 소모해버린다는 것입니다. 한 번 읽었다면, 다시 읽을 수 없습니다.
라이브러리는 다음 주요한 메소드들을 갖고 있습니다: isEmpty(), readInt(), readDouble(), readLong(), readBoolean(), readChar(), readString(), readLine(), readAll().
입력하기(Typing input)
터미널에서 java 커맨드를 사용할 때,(아마 여러분들은 IDE를 사용하느라 java 커맨드를 입력해볼 기회는 별로 없었을 것입니다) 여러분들은 실제로는 세 가지의 일을 하고 있는 것입니다: 여러분들의 프로그램을 실행시키도록 하고, 명령행 인자를 명시하며, 표준 입력 스트림을 정의합니다. 여러분이 커맨드를 입력하고, 이후에 터미널 윈도우에 입력되는 문자열들은 표준 입력 스트림입니다. 무언가를 입력하면, 프로그램과 상호작용 할 수 있습니다.
프로그램은 여러분이 표준 입력 스트림을 생성할 때까지 기다립니다. 예를 들어, 다음 프로그램을 실행하면, 명령행 인자로 N을 받고나서 N개의 숫자를 읽어들여 그들을 더하게 됩니다.
public class AddInts { public static void main(String[] args) { int N = Integer.parseInt(args[0]); int sum = 0; for (int i = 0; i < N; i++) { int value = StdIn.readInt(); sum += value; } StdOut.println("Sum is " + sum); } } |
여러분이 터미널에 'java AddInts 4'라고 타이핑했을 때, 프로그램은 StdIn.readInt() 메소드를 호출하여 여러분의 정수 입력을 기다리게 됩니다. 만약 여러분이 144를 입력하고 싶다고 해보죠. 1, 4, 4를 차례로 입력하지만, 그리고는 아무런 일도 일어나지 않습니다. 왜냐하면 프로그램은 여러분이 입력을 끝마쳤는지 아닌지를 확신할 수 없기 때문이죠. 따라서 <return>(엔터)을 입력하면, 비로소 프로그램은 하나의 정수를 읽어들이게 됩니다.
입력 형식(Input format)
StdIn.readInt() 가 정수를 기대하고 있는데 여러분이 abc, 12.2, true와 같이 입력한다면, NumberFormatException이 발생할 것입니다. 언제나 해당하는 포맷의 자바 리터럴로 입력해주어야 합니다. 편의를 위해 StdIn은 공백으로 각 입력을 구분할 수 있습니다. 숫자들 사이에 공백이 몇 칸이나 있는지는 상관 없습니다.
계속.
댓글
댓글 쓰기