2020.03.30-개발일지

System stream 객체를 close() 했을 때 발생하는 문제점

리팩토링하면서 가장 먼저 눈에 띄는것은 ScannerSystem.out.println 이었습니다. 주로 Web MVC로 개발하다 보니 콘솔 환경에서 입.출력을 구현할 일도 없거니와, 만약 구현한다 해도 Logger를 이용했기 때문입니다. 더욱이 알고리즘등을 공부하면서 일반적인 입출력보다 속도나 성능면에서 문제가 있는 것을 알고 있는데, 개선하지 않는 것은 안 좋은 방향이라 생각했습니다.

문제를 해결하기 위해 유틸 클래스를 작성 후 입력과 출력을 담당하는 메서드를 작성했습니다. 이때 BufferdReaderBufferdWriter 를 사용하였습니다. I/O Stream은 항상 close를 해야 한다고 알고 있기에 자연스럽게 메서드 마지막 부분에 close 를 했는데 여기서 문제가 발생했습니다. 첫번째 호출에서는 문제가 없으나 두번째부터 다시 객체를 생성하려 하면 java.io.IOException: Stream closed 에러가 발생 했습니다.

알게된 내용

1 .System.out

System 클래스의 out 인스턴스 설명은 아래와 같이 주석으로 잘 되어 있습니다.

/**
* The "standard" output stream. This stream is already
* open and ready to accept output data. Typically this stream
* corresponds to display output or another output destination
* specified by the host environment or user.
* <p>
* For simple stand-alone Java applications, a typical way to write
* a line of output data is:
* <blockquote><pre>
*     System.out.println(data)
* </pre></blockquote>
* <p>
* See the <code>println</code> methods in class <code>PrintStream</code>.
*/
public final static PrintStream out = null;

주석 내용은 ‘스트림은 프로그램 실행 시에 이미 열려있으며 데이터를 출력할 준비가 되어 있음’ 이라 되어 있습니다. 당연하게도? close 를 하면 Console 관련 스트림은 연결이 해제되고 재사용할 수 없게 됩니다. (out 역시 마찬가지 입니다.)

2. Java 에서 사용자 입력을 받는 3가지 방법

  1. BufferdReader
    • Java 1.1 부터 사용
    • 명시적인 에러처리 필요
    • 속도가 매우빠름
    • 입력에 대한 validation을 직접 구현해야 함
  2. Scanner
    • Java 1.5 부터 생김
    • 명시적인 예외처릴 할 필요 없음
    • line, token 별로 read 하는 등의 다양한 기능을 제공
    • 속도가 느림
  3. Console
    • Java 1.6 부터 사용
    • 명시적인 예외처리가 필요 없음
    • Scanner와 BufferdReader의 장점을 모두 가져옴 (암호 읽기 *** 기능도 제공함)
    • 에디터에서 안되는 경우가 있음. NullPointerException 발생. Console 객체와 에디터의 콘솔이 매칭이 안되서 발생한 문제

상황에 맞게 쓰자

다양한 상황속에서 용도에 따라 분리해서 사용하는 것이 좋습니다.

  • 빠른 속도가 필요한 경우 -> BufferdReader
  • 암호, 개인정보등 추가적인 처리가 필요할 경우 -> Console
  • 형타입, 정규식 표현등을 그대로 사용하고 싶을 때 -> Scanner
  • Thread-safe 하게 사용해야 하는 경우 -> Console 또는 BufferdReader

이번에는 아래와 같이 작성 후, 프로그램 종료직전 close() 호출 하기로 결정했습니다.

public class ConsoleUtil {

	private static final BufferedReader BUFFERED_READER = new BufferedReader(new InputStreamReader(System.in));
	private static final BufferedWriter BUFFERED_WRITER = new BufferedWriter(new OutputStreamWriter(System.out));
	public static final String HORIZONTAL_RULE = "=================================";

	public static String readString(String msg) throws IOException {
		printLn(msg);
		String input = BUFFERED_READER.readLine();
		return input;
	}

	public static int readInt(String msg) throws IOException {
		printLn(msg);
		int parseInt = 0;
		boolean success = false;
		while (success != true) {
			String input = BUFFERED_READER.readLine();
			try {
				parseInt = Integer.parseInt(input);
				success = true;
			} catch (NumberFormatException e) {
				printLn("숫자만 입력해주세요!");
			}
		}
		return parseInt;
	}

	public static void printLn(String msg) throws IOException {
		BUFFERED_WRITER.write(msg);
		BUFFERED_WRITER.newLine();
		BUFFERED_WRITER.flush();
	}

	public static void close() throws IOException {
		BUFFERED_READER.close();
		BUFFERED_WRITER.close();
	}
}

Stream에 대한 이해가 많이 부족한 것 같다고 느꼈습니다. 해당 내용을 정리하는 시간을 갖는다면 좋을 것 같습니다.

댓글남기기