개인참고자료/자바(네트워크)

자바 IO - 프로그래밍을 잘하려면(API)

경진 2008. 7. 13. 02:56
자바 IO 프로그래밍을 잘하려면

자바 IO 프로그래밍을 잘하려면 자바 API를 한번쯤은 꼭 읽어볼 필요가 있다.
그래야만 어떤 클래스에 어떤 메소드가 있는지 알 수 있기 때문이다. 보통 입력에 관련된 클래스는 입력관련된 메소드고, 출력에 관련된 클래스는 출력에 관련된 메소드가 있을테지만 말이다.

문제. "키보드로부터 한 줄씩 입력 받아 화면에 한 줄씩 출력하시오."

객체지향 프로그래머라면 우선 위 문제를 해결해 줄 객체부터 생각해야 한다.
일단, 키보드로 입력받는 것을 표준 입력 장치기 때문에 System.in을 사용해야 하고 출력하려면 표준 출력 장치기 때문에 System.out 을 사용해야 한다.

이번엔 메소드로 있을 법한 것을 살펴 보면 "한 줄씩 입력 받는다"라는 문장이다. 위의 문장을 java.io 패키지 클래스에 있는 메소드를 하나씩 살펴본다.

BufferedReader에 있는 readLine() 메소드

자바 IO 클래스를 선택해서 메소드를 보면 readLine() 이라는 메소드를 발견할 수 있다.
설명을 읽어보면 "읽다 하나 줄 의 텍스트"? 한 줄의 문장을 읽어 들인다.

그렇다면 사용해야 할 객체는 다음과 같이 세 가지가 되었다.

1. BufferedReader에 있는 readLine() 메소드라는 한 줄을 입력 받는 메소드가 있으므로 사용해야 한다.
2. System.in 키보드로부터 입력 받으면 사용해야 한다. InputStream 형식이다.
3. System.out 화면에 출력하려고 사용한다. PrintStream 형식이며, 문자열을 출력하는 println() 메소드가 있다.

위의 재료가 준비되었다면, 객체를 메모리에 올린 후 사용할 수 있게 해야 한다.
먼저 readLine() 메소드를 사용하려면 new 연산자를 이용해서 BufferedReader 클래스를 힙 메모리에 올려야 한다.

하지만 문제가 있다 BufferedReader 클래스의 생성자는 기본 생성자가 존재하지 않는다.

BufferedReader에 생성자가 없다

따라서 BufferedReader 클래스의 생성자를 사용하려면 Reader 객체를 인자로 지정해야 한다.(정확하게는 Reader의 자식이나 자손 객체를 지정해야 한다.)

즉, 다음과 같은 코드를 사용해야 한다.

BufferedReader bt = new BufferedReader(Reader 객체나 Reader의 자손);

즉, BufferedReader를 사용하려면 Reader 객체를 생성해야 한다. API에서 Reader를 클릭해본다.

사용자 삽입 이미지

그림에서 처럼 Reader 클래스는 추상 클래스라는 것을 알수 있다.
추상 클래스는 new 연산자를 이용해서 생성할 수 없다. 그렇다는 것은 BufferedReader 클래스 생성자의 인자로는 Reader 클래스를 상속받는 클래스만 올 수 있다.

그림을 보면 Direct Known Subclasses라는 목록이 있는 것을 알 수 있다. 직역하면 Reader의 직계자손이다. 직계자손이 의미하는 것은 BuffereReader 생성자의 인자로 들어갈 수 있는 객체를 의미한다.

생성자의 역할은 읽어 들이거나 쓰기 위한 대상을 지정해준다. 그리고 BufferedReader는 한 줄을 입력 받는 메소드를 가지고 있어서 사용한다고 했지만 실제로는 키보드로부터 읽어 들어야 한다.

즉, BufferedReader의 생성자에는 키보드에 대한 객체가 지정되어야만 키보드로 부터 한 줄 씩 입력받을 수 있다. 그렇다면 Reader의 직계자손 중 어떤 클래스를 이용해야 할까?

직계자손 중 BufferedReader는 후보에서 탈락시킨다. 그 이유는 우리가 사용하고자 하는 클래스가 BufferedReader인데, BufferedReader 객체를 중복해서 사용할 필요가 없다. 그렇다면 CharArrayReader는 어떨까? CharArray라는 단어가 붙은 클래스는 문자 배열에게 읽거나 쓴다고 했기 때문에 우리가 찾고자 하는 것과는 상관이 없다.

InputStreamReader 클래스 생성자

InputStreamReader는 Reader의 자식 클래스로서 BufferedReader 생성자의 인자로 전달 할 수 있다.
그런데 InputStreamReader의 객체를 생성하려면 그림에서 처럼 인자로 InputStream을 지정해야 한다.
InputStreamReader는 인자로 지정된 InputStream으로부터 입력을 받아 들이기 때문이다.

System.in 생성자

System.in이 InputStream이기 때문에 다음과 같은 방법으로 BufferedReader 객체를 생성할 수 있다.

InputStreamReader isr  = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);

InputStreamReader는 System.in으로 부터 읽어들이고, BufferedReader는 InputStreamReader로 부터 읽어 들인다.

키보드(System.in) → InputStreamReader → BufferedReader → 메소드

키보드에서 입력한 문자열은 키보드 버퍼에 그 정보가 쌓이게 된다 사용자가 엔터키를 입력하게 되면 키보드 버퍼에 입력되어 있던 문자열이 운영체제를 거쳐서 JVM에게 전달된다. JVM에게 전달된 문자열은 표준 입력을 담당하는 System.in 으로 전달되고, System.in을 통해서 읽어 들인 문자열은 InputStreamReader에게 전달된다. InputStreamReader에게 전달된 문자열은 다시 BufferredReaderr에게 전달 되며, BufferedReader에는 버퍼가 있기 때문에 문자열을 버퍼에 저장해 놓았다가 readLine() 메소드를 이용해서 한 줄을 한번에 읽어 들이게 된다.

InputStreamReader가 생성자에서 InputStream을 받아들인다는 것은 InputStreamReader가 내부적으로 InputStream에 있는 메소드를 이용해서 읽어들인다. 당연히 BufferedReader가 생성자에서 Reader를 받아들인다는 것은 BufferedReader가 내부적으로 Reader에 있는 메소드를 이용해서 읽어 들인다는 것을 의마한다.

우리가 휴대폰을 구입하면 내부적으로 어던 동작으로 전화가 걸리는지 몰라도 휴대폰을 사용할 수 있다. 이와 마찬가지로 BufferedReader를 지정하면 BufferedReader는 "해당 Reader로부터 읽어 들인다"라고 이해하면 된다. 내부적인 구현은 몰라도 된다. 객체지향에서 중요하는 것이 클래스 내부를 은닉하는 것이기 때문이다. 즉, 내부를 몰라도 클래스를 사용할 수 있다는 것이 중요하다.

만약 키보드에 해당하는 부분을 네트워크로부터 읽어 들이게 하면 어떻게 될까?  이 경우에는 BufferedReader가 네트워크로부터 한 줄의 문장을 읽어 들이게 된다.

또한 InputStreamReader대신에 FileReader를 지정한다면 BuffredeReader는 파일로부터 한 줄씩 입력 받게 된다.

키보드로 부터 한 줄을 입력 받아 화면에 출력하고 종료하는 프로그램이다.
(결국 이거 하나 해보려고 몇시간 동안 정리한거냐...)

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class BufferedReaderTest {
    public static void main(String[] args) throws Exception {
        InputStreamReader isr  = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(isr);
        String line = br.readLine();
        System.out.println("키보드로 부터 입력 받은 문자열 : " + line);
    }
}

※ 참고 (책을 열심히 따라치다 발견한거)

InputStream 설명