기존의 정의된 예외 클래스 외에 필요에 따라 프로그래머가 새로운 예외 클래스를 정의하여 사용할 수 있다. 보통 Exception클래스 또는 RuntimeException클래스로부터 상속받아 클래스를 만들지만, 필요에 따라서 알맞은 예외 클래스를 선택할 수 있다.

가능하면 새로운 예외 클래스를 만들기보다 기존의 예외클래스를 활용하자.

class MyException extends Exception {
	MyException(String msg) {	//문자열을 매개변수로 받는 생성자.
		super(msg);				// 조상인 Exception클래스의 생성자를 호출한다.
	}
}

Exception클래스로부터 상속받아서 My클래스를 만들었다. 필요하다면, 멤버변수나 메서드를 추가할 수 있다.

Exception클래스는 생성 시에 String값을 받아서 메세지로 저장할 수 있다. 

class MyException extends Exception {
	//에러 코드 값을 저장하기 위한 필드를 추가한다
    private final int ERR_CODE;	// 생성자를 통해 초기화 한다.
    
    MyException(String msg, int errCode) {	//생성자
    	super(msg);
    	Err_CODE = errCode;
    }
    
    MyException(String msg) {	// 생성자
    	this.(msg, 100);
    }
    
    public int getErrCode() {	// 에러 코드를 얻을 수 있는 메서드도 함께 추가했다.
    	return Err_CODE;		// 이 메서드는 주로 getMessage()와 함께 사용될 것이다.
    }
}

이전의 코드를 좀더 개선하여 메세지 뿐만 아니라 에러코드 값도 저장할 수 있도록 ERR_CODE와 getErrCode()를 MyException클래스의 멤버로 추가했다.

이렇게 함으로써 MyException이 발생했을 때, catch블럭에서 getMessage()와 getErrCode()를 사용해서 에러코드와 메세지를 모두 얻을 수 있을 것이다.

기존의 예외 클래스는 주로 Exception을 상속받아서 'checked예외'로 작성하는 경우가 많았지만, 요즘은 예외처리를 선택적으로 할 수 있도록 RuntimeException을 상속받아서 작성하는 쪽으로 바뀌어가고 있다. 'checked예외'는 반드시 예외처리를 해주어야 하기 때문에 예외처리가 불필요한 경우에도 try-catch문을 넣어서 코드가 복잡해지기 때문이다.

 

예외처리를 강제하도록 한 이유는 프로그래밍 경험이 적은 사람들도 보다 견고한 프로그램을 작성할 수 있게 유도하기 위한 것이었는데, 필수적이었던 예외들이 선택적으로 처리해도 되게 되었다. 그래서 필요에 따라 'unchecked예외'의 필요성이 증가했다.

class NewExceptionTest {
	public static void main(String[] args) {
		try {
			startInstall();
			copyFiles();
		} catch (SpaceException e) {
			System.out.println("에러 메세지 : " + e.getMessage());
			e.printStackTrace();
			System.out.println("공간을 확보한 후에 다시 설치하시기 바랍니다.");
		} catch (MemoryException me) {
			System.out.println("에러 메세지 : " + me.getMessage());
			me.printStackTrace();
			System.gc();
			System.out.println("다시 설치를 시도하세요.");
		} finally {
			deleteTempFiles();
		}
	}

	static void startInstall() throws SpaceException, MemoryException {
		if (!enoughSpace())
			throw new SpaceException("설치할 공간이 부족합니다.");
		if (!enoughMemory())
			throw new MemoryException("메모리가 부족합니다.");
	}

	static void copyFiles() {
		/* 파일을 복사하는 코드를 적는다 */}

	static void deleteTempFiles() {
		/* 임시파일을 삭제하는 코드를 적는다 */}

	static boolean enoughSpace() {
		// 설치하는데 필요한 공간이 있는지 확인하는 코드를 적는다.
		return false;
	}

	static boolean enoughMemory() {
		// 설치하는데 필요한 메모리공간이 있는지 확인하는 코드를 적는다.
		return true;
	}
}

class SpaceException extends Exception {
	SpaceException(String msg) {
		super(msg);
	}
}

class MemoryException extends Exception {
	MemoryException(String msg) {
		super(msg);
	}
}
에러 메세지 : 설치할 공간이 부족합니다.
a210721.SpaceException: 설치할 공간이 부족합니다.
공간을 확보한 후에 다시 설치하시기 바랍니다.
	at a210721.NewExceptionTest.startInstall(NewExceptionTest.java:24)
	at a210721.NewExceptionTest.main(NewExceptionTest.java:6)

에러문은 돌릴때마다 순서가 달라진다.

 이 두 예외는 startInstall()을 수행하는 동안에 발생할 수 있으며, enoughSpace()와 enoughMemory()의 실행결과에 따라서 발생하는 예외의 종류가 달라지도록 했다.

 

 

 

예외 되던지기(exception re-throwing)

한 메서드에서 발생할 수 있는 예외가 여럿인 경우, 몇 개는 try-catch문을 통해서 메서드 내에서 자체적으로 처리하고, 그 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 함으로써, 양쪽에서 나눠서 처리되도록 할 수 있다.

 그리고 심지어는 단 하나의 예외에 대해서도 예외가 발생한 메서드와 호출한 메서드, 양쪽에서 처리하도록 할 수 있다.이것은 예외를 처리한 후에 인위적으로 다시 발생시키는 방법을 통해서 가능한데, 이것을 '예외 되던지기(exception re-throwing)'라고 한다.

 먼저 예외가 발생할 가능성이 있는 메서드에 try-catch문을 사용해서 예외를 다시 처리해주고 catch문에서 필요한 작업을 행한 후에 throw문을 사용해서 예외를 다시 발생시킨다. 다시 발생한 예외는 이 메서드를 호출한 메서드에게 전달되고 호출한 메서드의 try-catch문에서 예외를 또다시 처리한다.

 이 방법은 하나의 예외에 대해서 예외가 발생한 메서드와 이를 호춣나 메서드 양쪽 모두에서 처리해줘야 할 작업이 있을 때 사용된다. 이 때 주의할 점은 예외가 발생할 메서드에서는 try-catch문을 사용해서 예외처리를 해줌과 동시에 메서드의 선언부에 발생할 예외를 throws에 지정해줘야 한다는 것이다.

public class ExceptionEx17 {
	public static void main(String[] args) {
		try {
			method1();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("main메서드에서 예외가 처리되었습니다.");
		}
	}

	static void method1() throws Exception {
		try {
			throw new Exception();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("method1메서드에서 예외가 처리되었습니다.");
			throw e;
		}
	}
}

결과에서 알 수 있듯이 metod1()과 main메서드 양쪽의 catch블럭이 모두 수행되었음을 알 수 있다. method1()의 catch블럭에서 예외를 처리하고도 throw문을 통해 다시 예외를 발생 시켰다. 그리고 이 예외를 main메서드 한 번 더 처리하였다.

반환값이 있는 return문의 경우, catch블럭에도 return문이 있어야 한다. 예외가 발생했을 경우에도 값을 반환해야하기 때문이다.

static int method1() {
	try{
		System.out.println("method1()이 호출되었습니다.");
		return 0;
	} catch (Exception e)	{
		e.printStackTrace();
		return 1;
	} finally {
		System.out.println("method1()의 finally블럭이 실행되었습니다.");
	}
}

catch블럭에서 예외 되던지기를 해서 호출한 메서드로 예외를 전달하면, return문이 없어도 된다. 그래서 검증에서도 assert문 대신 AssertError를 생성해서 던진다.

assert문은 검증(assertion)을 수행하기 위한 문장이다.

static int metod1() throws Exception {
	try {
		System.out.println("method1()이 호출되었습니다.");
		return 0;
	} catch (Exception e) {
		e.printStackTrace();
		return 1;
		throw new Exception();
	} finally {
		System.out.println("method1()의 finally블럭이 실행되었습니다.");
	}
}

finally 블럭내에도 return문을 사용할 수 있으며, try블럭이나 catch블럭의 return문 다음에 수행된다. 최종적으로 finally블럭 내의 return문 값이 반환된다.

 

 

 

 

연결된 예외(chained exception)

한 예외가 다른 예외를 발생시킬 수도 있다. 예를 들어 예외 A가 예외 B를 발생시켰다면, A를 B의 '원인 예외(cause exception)라고 한다. 

try {
	startInstall();
	copyFiles();
} catch (SpaceException e) {
	InstallException ie = new InstallException("설치중 예외발생");
	ie.initCause(e);
	throw ie;
} catch(MemoryException me)	{
...

먼저 InstallException을 생성한 후에, initCause()로 SpaceException을 InstallException의 원인 예외로 등록한다. 그러고'throw'로 이 예외를 던진다.

 

initCause()는 Exception클래스의 조상인 Throwable클래스에 정의되어 있기 때문에 모든 예외에서 사용가능하다.

Throwable initCause (Throwable cause) 지정한 예외를 원인 예외로 등록
Throwable getCause()				  원인 예외를 반환

발생한 예외를 원인 예외로 등록해서 다시 예외를 발생시키는 이유는 여러 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서이다.

 

예외 발생시키기

키워드 throw를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있다.

1. 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
	Exception e = new Exception("고의로 발생시켰음");
    2. 키워드 throw를 이용해서 예외를 발생시킨다.
    throw e;

 

public class ExceptionEx09 {
	// Exception 클래스들 : checked exception
	// RuntimeException 클래스들 : unchecked exception
	
	public static void main(String[] args) {
		try {
		Exception e = new Exception("고의 발생 예외");
		throw e;
	} catch (Exception e1) {
		System.out.println("에러 메세지 : " + e1.getMessage());
		e1.printStackTrace();
	}
	System.err.println("프로그램이 정상 종료됨");
//		throw new Exception("이거도 가능");
	}

Exception인스턴스를 생성할 때, 생성자에 String을 넣어 주면, 이 String이 Exception인스턴스에 메세지로 저장된다. 이 메세지는 getMessage()를 이용해서 얻을 수 있다.

 

 

public class ExceptionEx11 {
	public static void main(String[] args) {
		throw new RuntimeException(); //RuntimeException을 고의로 발생시킨다.
	}
}

이 예제는 예외처리를 하지 않았음에도 불구하고 이전의 예제와는 달리 성공적으로 컴파일되나, RuntimeException이 발생하여 비정상적으로 종료된다.

RuntimeException클래스와 그 자손(RuntimeException클래스들)에 해당하는 예외는 프로그래머에 의해 실수로 발생하는 것들이기 때문에 예외처리를 강제하지 않는 것이다. 만일 RuntimeException클래스들에 속하는 예외가 발생할 가능성이 있는 코드에서도 예외처리를 필수로 해야 한다면, 아래와 같이 참조변수와 배열이 사용되는 모든 곳에 예외처리를 해주어야 할 것이다.

try{
	int[] arr= new int[10];

	System.out.println(arr[10]);
} catch(ArrayIndexOutOfBoundsException ae) {
		...
} catch(NullPointerException ne) {
		...
}

컴파일러가 예외처리를 확인하지 않는 RuntimeException클래스들은 'unchecked예외)라고 부르고, 예외처리를 확인하는 Exception클래스들은 'checked예외'라고 부른다.

 

 

 

메서드에 예외 선언하기

예외를 처리하는 방법에는 지금까지 배워 온 try-catch문을 사용하는 것 외에, 예외를 메서드에 선언하는 방법이 있다.

 메서드에 예외를 선언하려면, 메서드의 선언부에 키워드 throws를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어주기만 하면 된다. 그리고 예외가 여러 개일 경우에는 쉼표(,)로 구분한다.

void method() throws Exception {
	//메서드의 내용
}

이렇게 예외를 선언하면, 이 예외뿐만 아니라 그 자손타이브이 예외까지도 발생할 수 있다는 점에 주의하자. 앞서 오버라이딩에서 살펴본 것과 같이, 오버라이딩할 때는 단순히 선언된 예외의 개수가 아니라 상속관계까지 고려해야 한다.

 

매서드의 선언부에 예외를 선언함으로써 메서드를 사용하려는 사람이 메서드의 선언부를 보앗을 때, 이 메서드를 사용하기 위해서는 어떠한 예외들이 처리되어져야 하는지 쉽게 알 수 있다.

 기존의 많은 언어들에서는 메서드에 예외선언을 하지 않았기 때문에, 경험 많은 프로그래머가 아니고서는 어떤 상황에 어떤 종류의 예외가 발생할 가능성이 있는지 충분히 예측하기 힘들기 때문에 그에 대한 대비를 하는 것이 어려웠다.

 그러나 자바에서는 메서드를 작성할 때 메서드 내에서 발생할 가능성이 있는 예외를 메서드의 선언부에 명시하여 이 메서드를 사용하는 쪽에서는 이에 대한 처리를 하도록 강요하기 때문에, 프로그래머들의 짐을 덜어 주는 것은 물론이고 보다 견고한 프로그램 코드를 작성할 수 있도록 도와준다.

 

 메서드에 예외를 선언할 때 일반적으로 RuntimeException클래스들은 적지 않는다. 이 들을 메서드 선언부에 throws에 선언한다고 해서 문제가 되지는 않지만, 보통 반드시 처리해주어야 하는 예외들만 선언한다.

 치처럼 JAVA API문서를 통해 사용하고자 하는 메서드의 선언부와 Throws: 를 보고, 이 메서드에서는 어떤 예외가 발생할 수 있으며 반드시 처리해주어야 하는 예외에는 어떤 것들이 있는지 확인하는 것이 좋다.

 

public class ExceptionEx12 {
	public static void main(String[] args) throws Exception {
		method1();
	}
	static void method1() throws Exception {
		method2();
	}
	static void method2() throws Exception {
		throw new Exception();
	}
}

위의 실행결과를 보면, 프로그램의 실행도중 java.lang.Exception이 발생하여 비정상적으로 종료했다는 것과 예외가 발생했을 때 호출스택(call stack)의 내용을 알 수 있다.

 위의 결과로부터 다음과 같은 사실을 알 수 있다.

1 예외가 발생했을 때, 모두 3개의 메서드(main, method1, method2)가 호출스택에 있었으며,
2 예외가 발생한 곳은 제일 윗줄에 있는 method2()라는 것과
3 main메서드가 method1()을, 그리고 method1())은 method2()를 호출했다는 것을 알 수 있다.

위의 예제를 보면, method2()에서 'throw new Exception():'문장에 의해 예외가 강제적으로 발생했으나 try-catch문으로 예외처리를 해주지 않았으므로, method2()는 종료되면서 예외를 자신을 호출한 method1()에게 넘겨준다. 마찬가지로 main메서드에게까지 예외를 넘겨준다.

 그러나 main메서드에서도 예외처리를 해주지 않았으므로 main메서드가 종료되어 프로그램이 예외로 인해 비정상적으로 종료되는 것이다.

예외가 발생한 메서드에서 예외처리를 하지 않고 자신을 호출한 메서드에게 예외를 넘겨줄 수는 있지만, 이것으로 예외가 처리된 것은 아니고 예외를 단순히 전달만 하는 것이다. 결국 어느 한 곳에서는 반드시 try-catch문으로 예외처리를 해주어야 한다.

 

public class ExceptionEx13 {
	public static void main(String[] args) {
		method1();
	}
	static void method1() {
		try {
			throw new Exception();
		} catch (Exception e) {
			System.out.println("method1메서드에서 예외가 처리되었습니다.");
			e.printStackTrace();
		}
	}
}

예외는 처리되었지만, printStackTrace()를 통해 예외에 대한 정보를 화면에 출력하였다. 예외가 method1()에서 발생했으며, main메서드가 method1()을 호출했음을 알 수 있다.

 

 

 

 

finally블럭

finally블럭은 예외의 발생여부에 상관없이 실행되어야할 코드를 포함시킬 목적으로 사용된다. try-catch문의 끝에 자원반납을 목적(네트워크 관리)으로 선택적으로 덧붙여 사용할 수 있으며, try-catch-finally의 순서로 구성된다.

try {
	// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (Exception e1) {
	// 예외처리를 위한 문장을 적는다.
} finally {
	// 예외의 발생여부에 관계없이 항상 수행되어야 하는 문장들을 넣는다.
	// finally블럭은 try-catch문의 맨 마지막에 위치해야한다.
}

예외가 발생한 경우에는 try > catch > finally의 순으로 실행되고, 예외가 발생하지 않은 경우에는 try > finally의 순으로 실행된다.

 

public class FinallyTest {
	public static void main(String[] args) {
		try {
			startInstall();
			copyFiles();
			deleteTempFiles();
		} catch (Exception e) {
			e.printStackTrace();
			deleteTempFiles();
		}
	}
	
	static void startInstall() {
		/*프로그램 설치에 필요한 준비를 하는 코드를 적는다. */
	}
	static void copyFiles() {/* 파일들을 복사하는 코드를 적는다 */}
	static void deleteTempFiles() {/* 임시파일들을 삭제하는 코드를 적는다.*/}
}

startInstall(), copyFiles(), deleteTempFiles()에 주석문 이외에는 아무런 문장이 없지만, 각 메서드의 의미에 해당하는 작업을 수행하는 코드들이 작성되어 있다고 생각하자.

 

이예제의 목적은 프로그램 설치를 위한 준비를 하고 파일들을 복사하고 설치가 완료되면, 프로그램을 설치하는데 사용된 임시파일들을 삭제하는 순서로 진행된다.

프로그램의 설치과정중에 예외가 발생하더라도, 설치에 사용된 임시파일들이 삭제되도록catch블럭에 deleteTempFiles()메서드를 넣었다.

 결국 try블럭의 문장들을 수행하는 동안에(프로그램을 설치하는 과정에), 예외의 발생여부에 관계없이 deleteTempfiles()메서드는 실행되어야 하는 것이다.

이럴 때 finally블럭을 사용하면 좋다. 아래의 코드는 위의 예제를 finally블럭을 사용해서 변경한 것이며, 두 예제의 기능은 동일하다.

public class FinallyTest3 {
	public static void main(String[] args) {
		FinallyTest3.method1();
		System.out.println("method1()의 수행을 마치고 main메서드로 돌아왔습니다.");
		new RuntimeException("내가만든 unchecked exception");
	}

	static void method1() {
		try {
			System.out.println("method1()이 호출되었습니다.");
			return;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			System.out.println("method1()의 finally블럭이 실행되었습니다.");
		}
	}
}
method1()이 호출되었습니다.
method1()의 finally블럭이 실행되었습니다.
method1()의 수행을 마치고 main메서드로 돌아왔습니다.

위의 결과에서 알 수 있듯이, try블럭에서 return문이 실행되는 경우에도 finally블럭의 문장들이 먼저 실행된 후에, 현재 실행 중인 메서드를 종료한다.

 이와 마찬가지로 catch블럭의 문장 수행 중에 return문을 만나도 finally블럭의 문장들은 수행된다.

 

 

 

자동 자원 반환 try-with-resource문

JDK1.7부터 try-with-resources문이라는 try-catch문의 변형이 새로 추가되었다. 이 구문은 입출력(I/O)와 관련된 클래스를 사용할 때 유용한데, 아직 입출력에 대해 배우지 않았으므로 유용함을 느끼기는 아직 이르다.

 주로 입출력에 사용되는 클래스 중에는 사용한 후에 꼭 닫아 줘야 하는 것들이 있다. 그래야 사용했던 자원(resources)이 반환되기 때문이다.

try {
	fis = new FileInputStream("score.dat");
	dis = new DataInputStream(fis);
		...
} catch(IOException ie) {
	ie.printStackTrace();
} finally {
	try {
		if(dis != null)
			dis.close();
	}catch(IOException ie) {
		ie.printStackTrace();
	}
}

DataInputStream으로부터 데이터를 읽는 코드이다. finally블럭 안에 try-catch문을 추가해서 close()에서 발생할 수 있는 예외를 처리하도록 변경했는데, 코드가 복잡해져서 좋지않고, try블럭과 finally블럭에서 모두 예외가 발생하면, try블럭의 예외는 무시된다.

이러한 점을 개선하기 위해서 try-with-resources문이 추가된 것이다. 코드를 try-with-resources문으로 바꾸면 다음과 같다.

 

try (FileInputStream fis = new FileInputStream("score.dat");
	DataInputStream dis = new DataInputStream(fis)) {
	while(true) {
		score = dis.readInt();
		sum += score;
	}
} catch(EOFException e) {
	System.out.println("점수의 총합은 " + sum "입니다.");
} catch(IOException ie) {
	ie.printStackTrace();
}

try - with - resources문의 괄호()안에 객체를 생성하는 문장을 넣으면, 이 객체는 따로 close()를 호출하지 않아도 try블럭을 벗어나는 순간 자동적으로 close()가 호출된다. 그 다음에 catch블럭 또는 finally블럭이 수행된다.

try블럭의 괄호()안에 변수를 선언하는 것도 가능하며, 선언된 변수는 try블럭 내에서만 사용할 수 있다.

이처럼 try-with-resources문에 의해 자동으로 객체의 close()가 호출될 수 있으려면, 클래스가 AUtoCloseable이라는 인터페이스를 구현한 것이어야만 한다.

public interface AutoCloseable {
	void close() throws Exception;
}

위의 인터페이스는 각 클래스에서 적절히 자원반환작업을 하도록 구현되어 있다. 그런데, 위의 코드를 잘 보면 close()도 Exception을 발생시킬 수 있다. 만일 자동 호출된 close()에서 예외가 발생하면 어떻게 처리를 할 것인가?

public class TryWithResourceEx {
	public static void main(String[] args) {
		try (CloseableResource cr = new CloseableResource()) {
			cr.exceptionWork(false);
		} catch (WorkException e) {
			e.printStackTrace();
		} catch (CloseException e) {
			e.printStackTrace();
		}
		System.out.println();

		try (CloseableResource cr = new CloseableResource()) {
			cr.exceptionWork(true);
		} catch (WorkException e) {
			e.printStackTrace();
		} catch (CloseException e) {
			e.printStackTrace();
		}
	}
}

class CloseableResource implements AutoCloseable {
	public void exceptionWork(boolean exception) throws WorkException {
		System.out.println("exceptionWork(" + exception + "가 호출됨");

		if (exception)
			throw new WorkException("WorkException발생!!!");
	}

	public void close() throws CloseException {
		System.out.println("close()가 호출됨");
		throw new CloseException("CloseException발생!!!");
	}
}

class WorkException extends Exception {
	WorkException(String msg) {
		super(msg);
	}

}

class CloseException extends Exception {
	CloseException(String msg) {
		super(msg);
	}
}

main메서드에 두 개의 try-catch문이 있는데, 첫 번째 것은 close()에서만 예외를 발생시키고, 두 번째 것은 exception Work()와 close()에서 모두 예외를 발생시킨다.

 첫 번째는 일반적인 예외가 발생하였을 때와 같은 형태로 출력되지만, 두 번째는 출력형태가 다르다. 먼저 첫번째 exceptionWork()에서 발생한 예외에 대한 내용이 출력되고, close()에서 발생한 예외는'억제된(suppressed)'이라는 의미의 머리말과 함께 출력되었다.

 두 예외가 동시에 발생할 수는 없기 때문에, 실제 발생항 예외를 WorkException으로 하고, CloseException은 억제된 예외로 다룬다. 억제도니 예외에 대한 정보는 실제로 발생한 예외인 WorkException에 저장된다.

 Throwable에는 억제된 예외와 관련된 다음과 같은 메서드가 정의되어 있다.

void addSuppressed(Throwable exception) 억제된 예외를 추가
Throwable[] getSuppressed()				억제된 예외(배열)을 반환

만일 기존의 try-catch문을 사용했다면, 먼저 발생한 WorkException은 무시되고, 마지막으로 발생한 CloseException에 대한 내용만 출력되었을 것이다.

 

 

7-11 문제 7-10에서 작성한 MyTv2 클래스에 이전 채널(previous channel)로 이동하는 기능의 메서드를 추가해서 실행결과와 같은 결과를 얻도록 하시오.

이전 채널의 값을 저장할 멤버변수를 정의하라.

 

메서드명 : gotoPrevChannel

기     능 : 현재 채널을 이전 채널로 변경한다.

반환 타입 : 없음

매개변수 : 없음

class MyTv2 {
	boolean isPowerOn;
	int channel;
	int volume;
	int prechannel;
	final int MAX_VOLUME = 100;
	final int MIN_VOLUME = 0;
	final int MAX_CHANNEL = 100;
	final int MIN_CHANNEL = 1;

	public void setVolume(int volume) {
		if (volume > MAX_VOLUME || volume < MIN_VOLUME)
			return;
		this.volume = volume;
		
	}

	public int getVolume() {
		return volume;
	}

	public void setChannel(int channel) {
		if (channel > MAX_CHANNEL || channel < MIN_CHANNEL)
			return;
		prechannel = this.channel;
		this.channel = channel;
	}

	public int getChannel() {
		return channel;
	}
	
	public void gotoPrevChannel() {
		setChannel(prechannel);
	}
	
}

class ExerciseEx7_11 {
	public static void main(String args[]) {
		MyTv2 t = new MyTv2();
		t.setChannel(10);
		System.out.println("CH:"+t.getChannel());
		t.setChannel(20);
		System.out.println("CH:"+t.getChannel());
		t.gotoPrevChannel();
		System.out.println("CH:"+t.getChannel());
		t.gotoPrevChannel();
		System.out.println("CH:"+t.getChannel());

	}
}

 

 

 

7-12 다음 중 접근 제어자에 대한 설명으로 옳지 않은 것은?(모두 고르시오) c

a. public은 접근 제한이 전혀 없는 접근 제어 자이다.

b. (default)가 붙으면, 같은 패키지 내에서만 접근이 가능하다.

c. 지역변수에도 접근제어자를 사용할 수 있다.

d. protect가 붙으면, 같은 패키지 내에서도 접근이 가능하다.

e. protected가 붙으면, 다른 패키지의 자손 클래스에서 접근이 가능하다.

 

c. 지역변수에는 접근제어자를 사용할 수 없다.

 

 

 

7-13 Math클래스의 생성자는 접근 제어자가 private이다. 그 이유는 무엇인가?

Math클래스의 모든 메서드가 static메서드이고 인스턴스 변수가 존재하지 않기 때문에 객체를 생성할 필요가 없기 때문이다.

 

 

 

7-14 문제 7-1에 나오는 섰다 카드의 숫자와 종류(isKwang)는 사실 한번 값이 지정되면 변경되어서는 안 되는 값이다. 카드 숫자가 한번 잘못 바뀌면 똑같은 카드가 두장이 될 수도 있기 때문이다. 이러한 문제점이 발생하지 않도록 아래의 SutdaCard를 수정하시오.

class SutdaCard {
	final int num;
	final boolean isKwang;

	SutdaCard() {
		this(1, true);
	}

	SutdaCard(int num, boolean isKwang) {
		this.num = num;
		this.isKwang = isKwang;
	}

	public String toString() {
		return num + (isKwang ? "K" : "");
	}
}

class Exercise7_14 {
	public static void main(String args[]) {
		SutdaCard card = new SutdaCard(1, true);
	}
}

 

 

 

 

7-15 클래스가 다음과 같이 정의되어 있을 때, 형 변환을 올바르게 하지 않은 것은? e

	class Unit {}
	class AirUnit extends Unit {}
	class GroundUnit extends Unit {}
	class Tank extends GroundUnit {}
	class AirCraft extends AirUnit {}
	Unit u = new GroundUnit();
	Tank t = new Tank();
	AirCraft ac = new AirCraft();

a. u = (Unit) ac;

b. u = ac;

c. GroundUnit gu = (GroundUnit) u;

d. AirUnit au = ac;

e. t = (Tank) u;

f. GroundUnit gu = t;

 

e. 조상 타입의 인스턴스를 자손 타입으로 형 변환할 수 없다.

 

 

7-16 다음 중 연산 결과가 true가 아닌 것은? (모두 고르시오) e

	class Car {}
	class FireEngine extends Car implements Movable {}
	class Ambulance extends Car {}
	FireEngine fe = new FireEngine()

a. fe instanceof FireEngine

b. fe instanceof Movable

c. fe instanceof Object

d. fe instanceof Car

e. fe instanceof Ambulance

 

e Abulance는 FireEngine과 아무 관계도 없다.

 

 

7-17 아래 세 개의 클래스로부터 공통부분을 뽑아서 Unit이라는 클래스를 만들고, 이 클래스를 상속받도록 코드를 변경하시오.

class Marine { // 보병
	int x, y; // 현재 위치
	void move(int x, int y) { /* */ } //지정된 위치로 이동
	void stop() { /* */ } //현재 위치에 정지
	void stimPack() { /* .*/} //스팀팩을 사용한다
}
class Tank { // 탱크
	int x, y; // 현재 위치
	void move(int x, int y) { /* */ } //지정된 위치로 이동
	void stop() { /* */ } //현재 위치에 정지
	void changeMode() { /* . */} //공격모드를 변환한다
}
class Dropship { // 수송선
	int x, y; // 현재 위치
	void move(int x, int y) { /* */ }// 지정된 위치로 이동
	void stop() { /* */ } //현재 위치에 정지
	void load() { /* .*/ } //선택된 대상을 태운다
	void unload() { /* .*/ } //선택된 대상을 내린다
}
public class JavaExercise7_17 {
		abstract class Unit {
		int x, y;
		abstract void move(int x, int y);
		void stop() { /* */ }	
	
	class Marine extends Unit{ // 보병
		void move(int x, int y) { }
		void stimPack() { /* .*/}
		}
		class Tank extends Unit{ // 탱크
		void move(int x, int y) { }
		void changeMode() { /* . */} 
		}
		class Dropship extends Unit{ // 수송선
		void move(int x, int y) { }
		void load() { /* .*/ } 
		void unload() { /* .*/ } 
		}
	}
}

 

 

 

7-18 다음과 같은 실행결과를 얻도록 코드를 완성하시오.

메서드명 : action

기     능 : 주어진 객체의 메서드를 호출한다.

              DanceRobot인 경우, dance()를 호출하고,

              SingleRobot인 경우, sing()를 호출하고,

              DrawRobot인 경우, draw()를 호출한다.

반환 타입 : 없음

매개변수 : Robot r - Robot인스턴스 또는 Robot의 자손 인스턴스

public class JavaExercise7_18 {
	public static void main(String[] args) {
		Robot[] arr = { new DanceRobot(), new SingRobot(), new DrawRobot() };
		for (int i = 0; i < arr.length; i++)
			action(arr[i]);
	} // main
}

class Robot {
}

class DanceRobot extends Robot {
	void dance() {
		System.out.println("춤을 춥니다");
	}
}

class SingRobot extends Robot {
	void sing() {
		System.out.println("노래를 합니다");
	}
}

class DrawRobot extends Robot {
	void draw() {
		System.out.println("그림을 그립니다");
	}
}
춤을 춥니다
노래를 합니다
그림을 그립니다

 

 

 

public class JavaExercise7_18 {
	public static void action(Robot r) {
		if (r instanceof DanceRobot) {
			DanceRobot dr = (DanceRobot) r;
			dr.dance();
		} else if (r instanceof SingRobot) {
			SingRobot sr = (SingRobot) r;
			sr.sing();
		} else if (r instanceof DrawRobot) {
			DrawRobot dr = (DrawRobot) r;
			dr.draw();

		}

	}

	public static void main(String[] args) {
		Robot[] arr = { new DanceRobot(), new SingRobot(), new DrawRobot() };
		for (int i = 0; i < arr.length; i++)
			action(arr[i]);
	} // main
}

class Robot {
}

class DanceRobot extends Robot {
	void dance() {
		System.out.println("춤을 춥니다");
	}
}

class SingRobot extends Robot {
	void sing() {
		System.out.println("노래를 합니다");
	}
}

class DrawRobot extends Robot {
	void draw() {
		System.out.println("그림을 그립니다");
	}
}
춤을 춥니다
노래를 합니다
그림을 그립니다

 

 

 

7-19 다음은 물건을 구입하는 사람을 정의한 BUyer클래스이다. 이 클래스는 멤버 변수로 돈(money)과 장 바위나(cart)를 가지고 있다. 제품을 구입하는 기능의 buy메서드와 장바구니에 구입한 물건을 추가하는 add메서드, 구입한 물건의 목록과 사용금액, 그리고 남은 금액을 출력하는 summary메서드를 완성하시오

 

1. 메서드명 : buy

   기      능 : 지정된 물건을 구입한다. 가진 돈(money)에서 물건의 가격을 빼고, 장바구니(cart)에 넣는다.

                 만일 가진 돈이 물건의 가격보다 적다면 바로 종료한다.

   반환 타입 : 없음

   매개변수 : Product p - 구입할 물건

 

2. 메서드명 : add

   기      능 : 지정된 물건을 장바구니에 담는다.

                 만일 지정된 장바구니에 담을 공간이 없으면, 장바구니의 크기를 2배로 늘린 다음에 담는다.

   반환타입 : 없음

   매개변수 : Product p - 구입한 물건

 

3. 메서드명 : summary

   기      능 : 구입한 물건의 목록과 사용금액, 남은 금액을 출력한다.

   반환타입 : 없음

   매개변수 : 없음

class Exercise7_19 {
	public static void main(String args[]) {
		Buyer b = new Buyer();
		b.buy(new Tv());
		b.buy(new Computer());
		b.buy(new Tv());
		b.buy(new Audio());
		b.buy(new Computer());
		b.buy(new Computer());
		b.buy(new Computer());
		b.summary();
	}
}

class Buyer {
	int money = 1000;
	Product[] cart = new Product[3]; // 구입한 제품을 저장하기 위한 배열
int i = 0; // Product cart index 배열 에 사용될

void buy(Product p) {
// 1.1 . 가진 돈과 물건의 가격을 비교해서 가진 돈이 적으면 메서드를 종료한다
if(money < p.price) {
System.out.println(" "+ p +" / ."); 잔액이 부족하여 을 를 살수 없습니다
return;
}
잔액이 부족하여 을 를 살수 없습니다 Computer / .
구입한 물건:Tv,Computer,Tv,Audio,Computer,Computer,
사용한 금액:850
남은 금액:150

 

 

 

class Exercise7_19 {
	public static void main(String args[]) {
		Buyer b = new Buyer();
		b.buy(new Tv());
		b.buy(new Computer());
		b.buy(new Tv());
		b.buy(new Audio());
		b.buy(new Computer());
		b.buy(new Computer());
		b.buy(new Computer());
		b.summary();
	}
}

class Buyer {
	int money = 1000;
	Product[] cart = new Product[3]; // 구입한 제품을 저장하기 위한 배열
	int i = 0; // Product cart index 배열 에 사용될

	void buy(Product p) {
		if (money < p.price)
			return;
		else if (true) {
			money -= p.price;
			add(p);
		}
		cart[i++] = p;
	}

	void add(Product p) {
		if (i >= cart.length) {
			Product[] tmp = new Product[cart.length * 2];
			System.arraycopy(cart, 0, tmp, 0, cart.length);
			cart = tmp;
		}
	}

	void summary() {
		String itemList = "";
		int sum = 0;
		for (int i = 0; i < cart.length; i++) {
			if (cart[i] == null)
				break;
			itemList += cart[i] + ",";
			sum += cart[i].price;
		}
		System.out.println(" :" + itemList);
		System.out.println(" :" + sum);
		System.out.println(" :" + money);
	} 
}

class Product {
	int price; // 제품의 가격

	Product(int price) {
		this.price = price;
	}
}

class Tv extends Product {
	Tv() {
		super(100);
	}

	public String toString() {
		return "Tv";
	}
}

class Computer extends Product {
	Computer() {
		super(200);
	}

	public String toString() {
		return "Computer";
	}
}

class Audio extends Product {
	Audio() {
		super(50);
	}

	public String toString() {
		return "Audio";
	}
}

 

 

 

 

 

7-20 다음의 코드를 실행한 결과를 적으시오.

class Exercise7_20 {
	public static void main(String[] args) {
		Parent p = new Child();
		Child c = new Child();
		System.out.println("p.x = " + p.x);
		p.method();
		System.out.println("c.x = " + c.x);
		c.method();
	}
}

class Parent {
	int x = 100;

	void method() {
		System.out.println("Parent Method");
	}
}

class Child extends Parent {
	int x = 200;

	void method() {
		System.out.println("Child Method");
	}
}
p.x = 100
Child Method
c.x = 200
Child Method

객체를 컴퓨터에 저장했다가 꺼내쓰는것과 컴퓨터간에 서로 객체를 주고받을 수 있게 해 주는것을 직렬화를 통해 가능하게한다.

 

직렬화(serialization)란 객체를 데이터 스트림으로 만드는 것을 뜻한다. 객체에 저장된 데이터를 스트림에 쓰기(write)위해 연속적인(serial) 데이터로 변환하는 것을 말한다. 반대로 스트림으로부터 데이터를 읽어서 객체를 만드는 것을 역직렬화(deserialization)이라고 한다.

객체는 클래스에 정의된 인스턴스변수의 집합이다. 객체는 클래스변수나 메서드가 포함되지 않는다. 객체는 오직 인스턴스변수들로만 구성되어 있다. 인스턴스변수는 인스턴스마다 다른 값을 가질 수 있어야하기 때문에 별도의 메모리공간이 필요하지만 메서드는 변하는 것이 아니라 인스턴스마다 같은 내용의 코드를 포함시킬 이유는 없다.

객체를 저장한다는 것은 바로 객체의 모든 인스턴스변수의 값을 저장한다는 것이다. 어떤 객체를 저장하고자 한다면, 현재 객체의 모든 인스턴스 변수의값을 저장하기만 하면 된다. 그리고 저장했던 객체를 다시 생성하려면, 객체를 생성한 후에 저장했던 값을 읽어서 생성한 객체의 인스턴스변수에 저장하면 되는 것이다.

클래스에 정의된 인스턴스변수가 단순히 기본형일 때는 인스턴스변수의 값을 저장하는 일이 간단하지만, 인스턴스변수의 타입이 참조형 일 때는 그리 간단하지 않다. 그러나 우리에게는 직렬화/역직렬화를 할 수 있는 ObjectInputStream/ObjectOutputStream을 사용하는 방법만 알면된다.

두 객체가 동일한지 판단하는 기준이 두 객체의 인스턴스변수의 값들이 같고 다름이라는 것을 기억하자.

 

 

 

ObjectInputStream, ObjectOutputStream

직렬화(스트림에 객체를 출력)에는 ObjectOutputStream을 사용하고 역직렬화(스트림으로부터 객체를 입력)에는 ObjectInputStream을 사용한다.

ObjectInputStream과 ObjectOutputStream은 각각 InputStream과 OutputStream을 직접 상속받지만 기반스트림을 필요로하는 보조스트림이다. 그래서 객체를 생성할 때 입출력(직렬화/역직렬화)할 스트림을 지정해 주어야 한다.

ObjectInputStream(InputStream in)
ObjectOutputStream(OutputStream out)

만일 파일에 객체를 저장(직렬화)하고 싶다면 다음과 같이 하면 된다.

FileOutputStream fos = new FileOutputStream("objectfile.ser");
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(new UserInfo());

위의 코드는 objectfile.ser이라는 파일에 UserInfo객체를 직렬화하여 저장한다. 출력할 스트림(FileOutputStream)을 생성해서 이를 기반스트림으로하는 ObjectOutputStream을 생성한다.

ObjectOutputStream의 writeObject(Object obj)를 사용해서 객체를 출력하면, 객체가 파일에 직렬화되어 저장된다.

역직렬화 방법은 입력스트림을 사용하고 wrieteObject(Object obj)대신 readObject()이기 때문에 객체 원래의 타입으로 형변환 해주어야 한다.

ObjectInputStream과 ObjectOutputStream에는 readObject()와 writeObject()이외에도 여러 가지 타입과 값을 입출력할 수 있는 메서드를 제공한다.

ObjectInputStream ObjectOutputStream
void
int
int
boolean
byte
char
double
float
int
long
short
Object
int
int
Object
string
defaultReadObject()
read()
read(byte[]buf, int off, int len)
readboolean()
readByte()
readChar()
readDouble()
readFloat()
readInt()
readLong()
readShort()
readObject()
readUnsignedByte()
readUnsignedShort()
readUnshared()
readUTF()
void defaultWriteObject
void write(byte[] buf)
void write(byte[] buf, int off, int len)
void write(int val)
void writeBoolean(boolean val)
void writeByte(int val)
void writeBytes(String str)
void writeChar(int val)
void writeChars(String str)
void writeDouble(double val)
void writeFloat(float val)
void writeInt(int val)
void writeLong(long val)
void writeObject(Object obj)
void writeShort(int val)
void writeUnshared(Object obj)
void write UTF(String str)

이 메서드들은 직렬화와 역직렬화를 직접 구현할 때 주로 사용되며, defaultReadObject(0와 defaultWriteObject()는 자동 직렬화를 수행한다. 객체를 직렬화/역직렬화하는 작업은 객체의 모든 인스턴스변수가 참조하고 있는 모든 객체에 대한 것이기 때문에 상당히 복잡하며 시간도 오래 걸린다. readObject()와 writeObject()를 사용한 자동 직렬화가 편리하기는 하지만 직렬화 작업시간을 단축시키려면 직렬화하고자 하는 객체의 클래스에 추가적으로 다음과 같은 2개의 메서드를 직접 구현해주어야 한다. 

private void writeObject(ObjectOutputStream out)
	throws IOException {
	}
private void readObject(ObjectInputStream in)
	throws IOException, ClassNotFoundExceaption{
    }

 

 

 

직렬화가 가능한 클래스 만들기 - Serializable, transient

직렬화가 가능한 클래스를 만드는 방법은 간단하다. 직렬화하고자 하는 클래스가 java.io.Serializable인터페이스를 구현하도록 하면 된다.

 예를 들어 왼쪽과 같이 UserInfo라는 클래스가 있을 때, 이 클래스를 직렬화가 가능하도록 변경하려면 오른쪽과 같이 Serializable인터페이스를 구현하도록 변경하면 된다.

public class UserInfo {
	String name;
	String password;
	int age;
}
public class UserInfo
	implements java.io.Serializable {
		String name;
		String password;
		int age;
}

Serializable 인터페이스는 아무런 내요도 없는 빈 인터페이스이지만, 직렬화를 고려하여 작성한 클래스인지를 판단하는 기준이 된다.

public interface Serialzable{ }

아래와 같이 Serializable을 구현한 클래스를 상속받는다면, Serializable을 구현하지 않아도 된다. UserInfo는 Serializable을 구현하지 않았지만 조상인 SuperUserInfo가 Serializable를 구현하였으므로 UserInfo역시 직렬화가 가능하다.

public class SuperUserInfo implement Serializable {
	String name;
	String password;
}
public class UserInfo extends SuperUserInfo{
	int age;
}

위의 경우 UserInfo객체를 직렬화하면 조상인 SuperUserInfo에 정의된 인스턴스변수name, password도 함께 직렬화된다.

 

그러나 조상클래스가 Serializable을 구현하지 않았다면 자손클래스를 직렬화할 때 조상클래스에 정의된 인스턴스변수 name과 password는 직렬화 대상에서 제외된다.

public class SuperUserInfo {
	String name;
	String password;
}
public class UserInfo extends SuperUserInfo implements Serializable {
	int age;
}

조상클래스에 정의된 인스턴스변수 name과 password를 직렬화 대상에 포함시키기 위해서는 조상클래스가 Serializable을 구현하도록 하던가, UserIInfo에서 조상의 인스턴스변수들이 직렬화되도록 처리하는 코드를 직접 추가해 주어야 한다. 

public class UserInfo implements Serializable {
	String name;
	String password;
	int age;
	Object obj = new Object(); // Object객체는 직렬화할 수 없다.
}

이 경우에는 직렬화 될 수 없다.

public class UserInfo implements Serializable {
	String name;
	String password;
	int age;
	Object obj = new String("abc");	// String은 직렬화될 수 없다.
}

인스턴스변수 obj의 타입이 직렬화가 안 되는 Objct이긴 하지만 실제로 저장된 객체는 직렬화가 가능한 String인스턴스이기 때문에 직렬화가 가능하다. 

 

직렬화하고자 하는 객체의 클래스에 직렬화가 안 되는 객체에 대한 참조를 포함하고 있다면, 제어자 transient를 붙여서 직렬화 대상에서 제외되도록 할 수 있다.

 또는 password와 같이 보안상 직렬화되면 안 되는 값에 대해서 transient를 사용할 수 있다. 다르게 표현하면 transient가 붙은 인스턴스변수의 값은 그 타입의 기본값으로 직렬화된다고 볼 수 있다.

즉, UserInfo객체를 역직렬화하면 참조변수인 obj와 password의 값은 null이 된다.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

class SuperUserInfo{
	String name;
	String password;
	
	public SuperUserInfo(){
		this("Unknown", "1111");
	}
		
	public SuperUserInfo(String name, String password){
		this.name = name;
		this.password = password;
	}
}

public class UserInfo2 extends SuperUserInfo implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -7739307153548748307L;
	int age;
	String addr;
	
	public UserInfo2() {
		this("Unknown", "1111",0);
	}
	public UserInfo2(String name, String password, int age) {
		super(name, password);
		this.age = age;
	}
	@Override
	public String toString() {
		return "UserInfo2 [age=" + age + ", name=" + name + ", password=" + password + "]";
	}
	
	private void writeObject(ObjectOutputStream oos) throws IOException{
		oos.writeUTF(name);
		oos.writeUTF(password);
		oos.defaultWriteObject();
	}
	private void readObject(ObjectInputStream ois) throws Exception {
		name = ois.readUTF();
		password = ois.readUTF();
		ois.defaultReadObject();
	}
	public static void main(String[] args) throws Exception {
		
		List<UserInfo2> list = new ArrayList<UserInfo2>();
		list.add(new UserInfo2("1길동", "1111", 10));
		list.add(new UserInfo2("2길동", "1212", 20));
		
		new ObjectOutputStream(new FileOutputStream("abcd.ser")).writeObject(list);
		
		List<UserInfo2> list2 = (List<UserInfo2>) new ObjectInputStream(new FileInputStream("abcd.ser")).readObject();
		
		list2.forEach(System.out::println);
	}
}

아래는writeObject()와 readObject()를 추가해서 조상으로부터 상속받은 인스턴스변수인 name과 password가 직접 직렬화되도록 해야 한다. 이 메서드들은 직렬화/역직렬화 작업시에 자동적으로 호출된다.

 

 

 

직렬화가능한 클래스의 버전관리

 

직렬화된 객체를 역직렬화할 때는 직렬화 했을 때와 같은 클래스를 사용해야한다. 그러나 클래스의 이름이 같더라도 클래스의 내용이 변경된 경우 역직렬화는 실패한다.

역직렬화를 이용해 클래스의 버전을 비교함으로써 직렬화할 때의 클래스 버전과 일치하는지 확인할 수 있다. 그러나 static변수나 상수 transient가 붙은 인스턴스변수가 추가되는 경우에는 직렬화에 영향을 미치지 않기 때문에 클래스의 버전을 다르게 인식하도록 할 필요는 없다.

 

표준입출력 - System.in, System.out, System.err

표준입출력은 콘솔(console, 도스창)을 통한 데이터 입력과 콘솔로의 데이터 출력을 의미한다. 자바에서는 표준 입출력(standard I/O)을 위해 3가지 입출력 스트림, System.in, System.out, System.err을 제공하는데, 이 들은 자바 어플리케이션의 실행과 동시에 사용할 수 있게 자동적으로 생성되기 때문에 개발자가 별도의 스트림을 생성하는 코드를 작성하지 않고도 사용이 가능하다.

System.in 콘솔로부터 데이터를 입력받는데 사용
System.out 콘솔로 데이터를 출력하는데 사용
System.err 콘솔로 데이터를 출력하는데 사용

 

System클래스의 소스에서 알 수 있듯이, in, out, err은 System클래스에 선언된 클래스변수(static변수)이다. 선언부분만을 봐서는 out, err, in의 타입은 InputStream과 PrintStream이지만 실제로는 버퍼를 이용하는 BufferedInputStream과 BufferedOutputStream을 사용한다(속도가 빠르다)

public final class System {
	public final static InputStream in = nullInputStream();
	public final static PrintStream out = nullPrintStream();
	public final static PrintStream err = nullPrintStream();
	...
}

Editplus나 이클립스와 같은 에디터에서는 콘솔로의 출력을 중간에 가로채서 에디터에 뿌려주는 것이다. Editplus의 설정화면에서 'Capture Output'옵션을 선택하면 콘솔에 출력하는 내용이 Editplus의 하단에 출력된다.

 

 

public class StandardIOEx1 {
	public static void main(String[] args) {
		try {
			int input = 0;

			while ((input = System.in.read()) != -1) {
				System.out.println("input :" + input + ", (char)input : " + (char) input);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

콘솔입력은 버퍼를 가지고 있기 때문에 Backspace키를 이용해서 편집이 가능하며 한 번에 버퍼의 크기만큼 입력이 가능하다.

콘솔에 데이터를 입력하고 Enter키를 누르면 입력대기상태에서 벗어나 입력된 데이터를 읽기 시작하고 입력된 데이터를 모두 읽으면 다시 입력대기 상태가 된다.

이러한 과정이 반복되다가 사용자가 ctrl+z를 입력하면, read()는 입력이 종료되었음을 인식하고 -1을 반환하여 while문을 벗어나 프로그램이 종료된다.

enter키를 두개 누르는 것은 두 개의 특수문자'\r'과'\n'이 입력된 것으로 간주된다. '\r'은 캐리지리턴(carriage return), 즉 커서를 현재 라인의 첫 번째 컬럼으로 이동시키고'\n'은 커서를 다음 줄로 이동시키는 줄바꿈(new line)을 한다.

그래서 Enter키를 누르면, 캐리지리턴과 줄바꿈이 수행되어 다음 줄의 첫 번째 칼럼으로 커서가 이동하는 것이다.

 

 

 

표준입출력 대상변경 - setOut(), setErr(), setln()

초기에는 System.in, System.out, System.err의 입출력대상이 콘솔화면이지만, setln(), setOut(), setErr()를 사용하면 입출력을 콘솔 이외에 다른 입출력 대상으로 변경하는 것이 가능하다.

 

메서드 설명
static void setOut(PrintStream out) System.out의 출력을 지정된 PrintStream으로 변경
static void setErr(PrintStream err) System.err의 출력을 지정한 PrintStream으로 변경
static void setln(InputStream in) System.in의 입력을 지정한 InputStream으로 변경

 

public class StandardIOEx2 {
	public static void main(String[] args) {
		System.out.println("out : Hello World!");
		System.err.println("err : Hello World!");
	}
}

 

 

 

RandomAccessFile

자바에서는 입력과 출력이 각각 분리되어 별도로 작업을 하도록 설계되어 있는데 RandomAccessFile만은 하나의 클래스로 파일에 대한 입력과 출력을 모두 할 수 있도록 되어 있다.

InputStream이나 OutputStream으로 상속받지 않고, DataInput인터페이스와 DataOutput인터페이스를 모두 구현했기 때문에 읽기와 쓰기가 모두 가능하다.

 

DataInputStream은 DataInput인터페이스를, DataOutputStream은 DataOutput인터페이스를 구현했다. 이 두 클래스의 기본 자료형(primitive data type)을 읽고 쓰기위한 메서드들은 모두 이 2개의 인터페이스에 정의되어있는 것들이다.

따라서, RandomAccessFile클래스도 DataInputStream과 DataOutputStream처럼, 기본자료형 단위로 데이터를 읽고 쓸 수 있다. 

RandomAccessFile클래스의 가장 큰 장점은 파일의 어느 위치에나 읽기/쓰기가 가능하다는 것이다.

이것은 RandomAccessFile클래스 내부의 파일포인터를 사용하는데, 입출력 시에 작업이 수행되는 곳이 바로 파일 포이터가 위치한 곳이 된다.

파일 포인터의 위치는 파일의 제일 첫 부분(0부터 시작)이며, 일기 또는 쓰기를 수행할 때 마다 작업이 수행된 다음 위치로 이동하게 된다. 순차적으로 읽기나 쓰기를 한다면, 파일 포인터를 이동시키기 위해 별도의 작업이 필요하지 않지만, 파일의 임의의 위치에 있는 내용에 대해서 작업하고자 한다면, 먼저 파일 포인터를 원하는 위치로 옮긴 다음 작업을 해야 한다.

현재 작업 중인 파일에서 파일 포인터의 위치를 알고 싶을 때는 getFilePointer()를 사용하면 되고, 파일 포인터의 위치를 옮기기 위해서는 seek(long pos)나 skipBytes(int n)를 사용하면 된다.

모든 입출력에 사용되는 클래스들은 입출력 시 다음 작업이 이루어질 위치를 저장하고 있는 포인터를 내부적으로 갖고 있다. 다만 내부적으로만 사용될 수 있기 때문에 작업자가 포인터의 위치를 마음대로 변경할 수 없다는 것이 RandomAccessFile과 다른 점이다.

 

생성자/메서드 설명
RandomAccessFile(File file, String mode)
RandomAccessFile(String fileName, String mode)
주어진 file에 읽기 또는 읽기+쓰기를 하기 위한
RandomAccessFile인스턴스를 생성한다. mode의 값은 "r", "rw", "rws", "rwd"가 지정가능하다.
"r" - 파일로부터 읽기(r)만 수행할 때
"rw" - 파일로부터 읽기(r)와 쓰기(w)를 수행할 때
"rws - rw+출력내용(파일의 내용 + 메타정보)을 파일에 지연없이 바로 쓰이게 한다. 
"rwd"- rw+출력내용(파일의 내용만)을 파일에 지연없이 바로 쓰이게 한다. 
FileChannel getChannel() 파일의 파일 채널을 반환한다.
FileDescriptor getFD() 파일의 파일 디스크립터를 반환한다.
long getFilePointer() 파일의 포인터의 위치를 알려 준다.
long length() 파일의 크기를 얻을 수 있다.(byte단위)
void seek(long pos) 첫 부분부터 pos크기만큼 떨어진 곳까지 파일 포인터의 위치를 변경한다. 
void setLength(long newLength) 파일의 크기를 지정된 길이로 변경한다.(byte단위)
int skipBytes(int n) 지정된 수만큼 byte를 건너뛴다.

RandomAccessFile의 인스턴스를 "rw" mode로 생성할 때, 지정된 파일이 없으면 새로운 파일을 생성한다.

 

public class RandomAccessFileEx1 {
	public static void main(String[] args) {
		try {
			RandomAccessFile raf = new RandomAccessFile("test.dat", "rw");
			System.out.println("파일 포인터의 위치: " + raf.getFilePointer());
			raf.writeInt(100);
			System.out.println("파일 포인터의 위치: " + raf.getFilePointer());
			raf.writeLong(100L);
			System.out.println("파일 포인터의 위치: " + raf.getFilePointer());

		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 

파일 포인터의 위치: 0
파일 포인터의 위치: 4
파일 포인터의 위치: 12

이 예제는 파일에 출력작업이 수행되었을 때 파일 포인터의 위치가 어떻게 달라지는지에 대해서 보여 준다.

 

 

 

File

파일은 기본적이면서도 가장 많이 사용되는 입출력 대상이기 때문에 중요하다.

그래서 관련된 기본적인 내용뿐 만 아니라 다양한 활용예제들을 실었다. 이 들을 응용해서 다양한 예제들을 만들어 보면 실력향상에 많은 도움이 될 것이다.

자바에서는 File클래스를 통해서 파일과 디렉토리를 다룰 수 있도록 하고 있다. 그래서 File인스턴스는 파일 일 수도 있고 디렉토리일 수도 있다.

생성자/메서드 설명
File(String fileName) 주어진 문자열(fileName)을 이름으로 갖는 파일을 위한 File인스턴스를 생성한다.파일 뿐만 아니라 디렉토리도 같은 방법으로 다룬다.
여기서 fileName은 주로 경로(path)를 포함해서 지정해주지만, 파일 
File(String pathName, String fileName)
File(File pathName, String fileName)
파일의 경로와 이름을 따로 분리해서 지정할 수 있도록 한 생성자. 이 중 두 번째 것은 경로를 문자열이 아닌 File인스턴스인 경우를 위해서 제공된 것이다.
File(URI uri) 지정된 uri로 파일을 생성
String getName() 파일이름을 String으로 반환
String getPath() 파일의 경로(path)를 String으로 반환
String getAbsolutePath()
File getAbsoluteFile()
파일의 절대경로를 String으로 반환
파일의 절대경로를 File로 반환
String getParent()
File getParentFile()
파일의 조상 디렉토리를 String으로 반환
파일의 조상 디렉토리를 File로 반환
String getCanonicalPath()
File getCanonicalFile()
파일의 정규경로를 String으로 반환
파일의 정규경로를 File로 반환

 

멤버변수 설명
static String pathSeparator OS에서 사용하는 경로(path) 구분자. 윈도우";", 유닉스"."
static char pathSeparatorChar OS에서 사용하는 경로(path) 구분자.
윈도우에서는 ';', 유닉스':'
static String separator OS에서 사용하는 이름 구분자. 윈도우 "\", 유닉스"/"
static char separatorChar OS에서 사용하는 이름 구분자. 윈도우"\", 유닉스"/"

파일의 경로(path)와 디렉토리나 파일의 이름을 구분하는 데 사용 되는 구분자가 OS마다 다를 수 있기 때문에, OS독립적으로 프로그램을 작성하기 위해서는 반드시 위의 멤버변수들을 이용해야한다. 만일 윈도우에서 사용하는 구분자를 코드에 직접 적어 놓았다면, 이 코드는 다른 OS에서는 오류를 일으킬 수 있다.

 

1. 이미 존재하는 파일을 참조할 때 :
File f = new File("c:\\jdk1.8\\work\\ch15", "FileEx1.java");
2. 기존에 없는 파일을 새로 생성할 때 :
File f = new File("c:\\jdk1.8\\work\\ch15", "NewFile.java");
f.creadteNewFile();

 

 

 

메서드 설명
boolean canRead() 읽을 수 있는 파일인지 검사한다.
boolean canWrite() 쓸 수 있는 파일인지 검사한다.
boolean canExecute() 실행할 수 있는 파일인지 검사한다.
int compareTo(File pathname) 주어진 파일 또는 디렉토리를 비교한다. 같으면 0을 반환하며, 다르면 1 또는 -1을 반환한다.(Unix시스템에서는 대소문자를 구별하며, Windows에서는 구별하지 않는다)
boolean exists() 파일이 존재하는지 검사한다.
boolean isAbsolute() 파일 또는 디렉토리가 절대경로명으로 지정되었는지 확인한다.
boolean isDirectory() 디렉토리인지 확인한다.
boolean isFile() 파일인지 확인한다.
boolean isHidden() 파일의 속성이 '숨김(Hidden)'인지 확인한다.
또한 파일이 존재하지 않으면 false를 반환한다.
boolean creadteNewFile() 아무런 내용이 없는 새로운 파일을 생성한다.(단, 생성하려는 파일이 이미 존재하면 생성되지 않는다.)
File f = new File("c:\\jdk1.8\\work\\test3.java");
f.createNewFile();
static File createTempFile
(String prefix, String suffix)
임시파일을 시스템의 임시 디렉토리에 생성한다.
System.out.println(File.createTempFile("work",".tmp"));
결과 : c:\windows\TEMP\work14247.tmp
static File createTempFile
(String prefix, String suffix, File directory)
임시파일을 시스템의 지정된 디렉토리에 생성한다.
boolean delete() 파일을 삭제한다.
void deleteOnExite() 응용 프로그램 종료시 파일을 삭제한다. 주로 실행 시 작업에 사용된 임시파일을 삭제하는데 사용된다.
boolean equals(Object obj) 주어진 객체(주로 File인스턴스)가 같은 파일인지 비교한다. (Unix시스템에서는 대소문자를 구별하며, Windows에서는 구별하지 않는다.)
long lastModified() 파일의 마지막으로 수정된 시간을 지정된 시간으로 반환
long length() 파일의 크기를 반환한다.
String[] list() 디렉토리의 파일목록(디렉토리 포함)을 String배열로 반환한다.
String[] list(FilenameFilter filter)
File[] list(FilenameFilter filter)
FilenameFilter인스턴스에 구현된 조건에 맞는 파일을 String배열(File배열)로 반환한다.
File[] listFiles()
File[] listFiles(FileFilter filter)
File[] listFiles(FilenameFilter f)
디렉토리의 파일목록(디렉토리 포함)을 File배열로 반환(filter가 지정된 경우에는 filter의 조건과 일치하는 파일만 반환)
static File[] listRoots()
long getFreeSpace()
long getTotalSpace()
long getUsableSpace()
컴퓨터 파일시스템의 root의 목록(floppy, CD-ROM, HDD drive)을 반환(예: A:\, D:\)
get으로 시작하는 메서드들은 File이 root일 때, 비어있는 공간, 전체 공간, 사용가능한 공간을 바이트 단위로 반환
boolean mkdir()
boolean mkdrs()
파일에 지정된 경로로 디렉토리(폴더)를 생성, 성공하면 true mkdirs는 필요하면 부모 디렉토리까지 생성
boolean renameTo(File dest) 지정된 파일(dest)로 이름을 변경
boolean setExcutable(boolean excutable)
boolean setExcutable(boolean excutable, boolean ownerOnly)
boolean setReadable(boolean readable)
boolean setReadable(boolean readable, boolean wonerOnly)
boolean setReadOnly()
boolean setWritable(boolean writable)
boolean setWritable(boolean writable, boolean ownerOnly)
파일의 속성을 변경한다.
OwnerOnly가 true면, 파일의 소유자만 해당 속성을 변경할 수 있다.
boolean setLastModified(long t) 파일의 마지막으로 수정된 시간을 지정된 시간(t)로 변경
Path toPath() 파일을 Path로 변환해서 반환
URI toRUI() 파일 URI로 변환해서 반환

 

BufferedReader/BufferedWriter는 버퍼를 이용해서 입출력의 효율을 높일 수 있도록 해주는 역할을 한다. 버퍼를 이용하면 입출력의 효율이 비교할 수 없을 정도로 좋아지기 때문에 사용하는 것이 좋다.

BufferedReader의 readLine()을 사용하면 데이터를 라인단위로 읽을 수 있고 BufferedWriter는 newLine()이라는 줄바꿈 해주는 메서드를 가지고 있다.

public class BufferedReaderEx1 {
	public static void main(String[] args) {
		try {
			FileReader fr = new FileReader("src/a210727/BufferedReaderEx1.java");
			BufferedReader br = new BufferedReader(fr);
			
			String line = "";
			for(int i=1;(line = br.readLine())!=null;i++) {
				if(line.indexOf(";")!=-1)
					System.out.println(i+":"+line);
			}
			br.close();
		} catch(Exception e) {}
	}

}
1:package a210727;
3:import java.io.BufferedReader;
4:import java.io.FileReader;
9:			FileReader fr = new FileReader("src/a210727/BufferedReaderEx1.java");
10:			BufferedReader br = new BufferedReader(fr);
12:			String line = "";
13:			for(int i=1;(line = br.readLine())!=null;i++) {
14:				if(line.indexOf(";")!=-1)
15:					System.out.println(i+":"+line);
17:			br.close();

BufferedReader의 readLine()을 이용해서 파일의 라인단위로 읽은 다음 indexOf()를 이ㅛㅇㅇ해서 ':'를 포함하고 있는지 확인하여 출력하는 예제이다. 파일에서 특정 문자 또는 문자열을 포함한 라인을 쉽게 찾아낼 수 있음을 보여 준다.

 

 

 

InputStreamReader와 OutputStreamWriter

InputStreamReader/OutputStreamWriter는 이름에서 알 수 있는 것과 같이 바이트기반 스트림을 문자기반 스트림으로 연결시켜주는 역할을 한다. 그리고 바이트기반 스트림의 데이터를 지정된 인코딩의 문자데이터로 변환하는 작업을 수행한다.

InputStreamReader/OutputStreamWriter는 Reader/Writer의 자손이다.

 

생성자/메서드 설명
InputStreamReader(InputStream in) QS에서 사용하는 기본 인코딩의 문자로 변환하는 InputStreamReader를 생성한다
InputStreamReader(InputSteram in, String encoding) 지정된 인코딩을 사용하는 InputStreamReader를 생성한다.
String getEncoding() InputStreamReader의 인코딩을 알려 준다.

 

생성자/메서드 설명
OutputStream(OutputStream out) OS에서 사용하는 기본 인코딩의 문자로 변환하는 OutputStreamWriter를 생성한다.
OutputStreamWriter(OutputStream out, String encoding) 지정된 인코딩을 사용하는 OutputStreamWriter를 생성한다.
String getEncoding() OutputStreamWriter의 인코딩을 알려 준다.

한글윈도우에서 중국어로 작성된 파일을 읽을 때 InputStreamReader(InputStream in, String encoding)를 이용해서 인코딩이 중국어로 되어 있다는 것을 지정해주어야 파일의 내용이 깨지지 않고 올바르게 보일 것이다. 인코딩을 지정해 주지 않는다면 OS에서 사용하는 인코딩을 사용해서 파일을 해석해서 보여 주기 때문에 원래 작성된 대로 볼 수 없을것이다.

이와 마찬가지로 OutputStreamWriter를 이용해서 파일에 텍스트데이터를 저장할 때 생성자 OutputStreamWriter(OutputStream out, String encoding)를 이용해서 인코딩을 지정하지 않으면 OS에서 사용하는 인코딩으로 데이터를 저장할 것이다.

시스템 속성에서 sun.jnu.encoding의 값을 보면 OS에서 사용하는 인코딩의 종류를 알 수 있다.

문자데이터를 다루는데 사용된다는 것을 제외하고 바이트기반 스트림과 문자기반 스트림의 사용법은 거의 같다.

 

Read와 Writer

바이트기반 스트림의 조상이 InputStream/OutputStream인 것과 같이 문자기반의 스트림에서는 Reader/Writer가 그와 같은 역할을 한다. 다음은 Reader/Writer의 메서드인데 byte배열 대신 char배열을 사용한다는 것 외에는 InputStream/OutputStream의 메서드와 다르지 않다.

 

메서드 설명
abstract void close() 입력스트림을 닫음으로써 사용하고 있던 자원을 반환한다.
void mark(int readlimit) 현재위치로 표시해놓는다. 후에 reset()에 의해서 표시해 놓은 위치로 다시 돌아갈 수 있다.
boolean markSupported() mark()와 reset()을 지원하는지를 알려 준다.
int read() 입력소스로부터 하나의 문자를 읽어 온다. cahr의 범위인 0~65535범위의 정수를 반환하며, 입력스트림의 마지막 데이터에 도달하면, -1을 반환한다.
int read(char[] c); 입력소스로부터 매개변수로 주어진 배열 c의 크기만큼 읽어서 배열c에 저장한다. 읽어 온 데이터의 개수 또는 -1을 반환한다.
abstract in read(char[] c, int off, int len) 입력소스로부터 최대 len개의 문자를 읽어서, 배열 c의 저장된 위치(off)부터 읽은 만큼 저장한다. 읽어 온 데이터의 개수 또는 -1을 반환한다.
int read(CharBuffer target) 입력소스로부터 읽어서 문자버퍼(target)에 저장한다.
boolean ready() 입력소스로부터 데이터를 읽을 준비가 되어있는지 알려 준다.
void reset() 입력소스에서 위치를 마지막으로 mark()가 호출되었던 위치로 되돌린다.
long skip(long n) 현재 위치에서 주어진 문자 수(n)만큼을 건너뛴다.

 

 

 

메서드 설명
Writer append(char c) 지정된 문자를 출력소스에 출력한다.
Writer append(CharSequence c) 지정된 문자열(CharSequence)을 출력소스에 출력한다.
Writer append(CharSequence c, int start, int end) 지정된 문자열(CharSequence)의 일부를 출력소스에 출력(CharBuffer, String, String Buffer가 CharSequence를 구현)
abstract void close() 출력스트림을 닫음으로써 사용하고 있던 자원을 반환한다.
abstract void flush() 스트림 버퍼에 있는 모든 내용을 출력소스에 쓴다.(버퍼가 있는 스트림에만 해당됨)
void write(int b) 주어진 값을 출력소스에 쓴다.
void write(char[] c) 주어진 배열 c에 저장된 모든 내용을 출력소스에 쓴다.
abstract void write(char[] c, int off, int len) 주어진 배열 c에 저장된 내용 중에서 off번째부터 len길이만큼만 출력소스에 쓴다.
void write(String str) 주어진 문자열(str)을 출력소스에 쓴다.
void write(String str, int off, int len) 주어진 문자열(str)의 일부를 출력소스에 쓴다.(off번째 문자부터 len개 만큼의 문자열)

문자기반 스트림 Reader/Writer 그리고 그 자손들은 여러 종류의 인코딩과 자바에서 사용하는 유니코드(UTF-16)간의 변환을 자동적으로 처리해준다. Reader는 특정 인코딩을 일겅서 유니코드로 변환하고 Writer는 유니코드를 특저 인코딩으로 변환하여 저장한다.

 

 

 

 

FileReader와 FileWriter

FileReader/FileWriter는 파일로부터 텍스트데이터를 읽고, 파일에 쓰는데 사용된다. 사용방법은 FileInputStream/FileOutputStream과 같다.

 

 

 

PipedReader와 PipedWriter

PipedReader/PipedWriter는 쓰레드 간에 데이터를 주고받을 때 사용된다. 다른 스트림과는 달리 입력과 출력스트림을 하나의 스트림으로 연결(connect)해서 데이터를 주고받는다는 특징이 있다.

 스트림을 생성한 다음에 어느 한쪽 쓰레드에서 connect()를 호출해서 입력스트림과 출력스트림을 연결한다. 입출력을 마친 후에넌 어느 한쪽 스트림만 닫아도 나머지 스트림은 자동으로 닫힌다. 이 점을 제외하고는 일반 입출력방법과 다르지 않다.

 

쓰레드를 시작하기 전에 PipedReader와 PipedWriter를 연결해야한다는 것에 유의 하자. 

 

 

StringReader와 StringWriter

StringReader/StringWriter는 CharArrayReader/CharArrayWriter와 같이 입출력 대상이 메모리인 스트림이다. StringWriter에 출력되는 데이터는 내부의 StringBuffer에 저장되며 StringWriter 다음과 같은 메서드를 이용해서 저장된 데이터를 얻을 수 있다.

StringBuffer getBuffer() StringWriter에 출력한 데이터가 저장된	StringBuffer를 반환한다.
String toString()	StringWriter에 출력된(StringBuffer에 저장된) 문자열을 반환한다.

 

FilterInputStream/FilterOutputStream은 InputStream/OutputStream의 자손이면서 모든 보조스트림의 조상이다. 보조스트림은 자체적으로 입출력을 수행할 수 없기 때문에 기반스트림을 필요로 한다.

protected FilterInputStream(InputStream in)
public	filterOutputStream(OutputStream out)

FilterInputStream/FilterOutputStream의 모든 메서드는 단순히 기반스트림의 메서드를 그대로 호출할 뿐이다. FilterInputStream/FilterOutputStream자체로는 아무런 일도 하지 않음을 의미한다. FilterInputStream/FilterOutputStream은 상속을 통해 원하는 작업을 수행하도록 읽고 쓰는 메서드를 오버라이딩해야한다. 생성자 FilterInputStream(InputStream in)는 접근 제어자가 protected이기 때문에 Filter InputStream의 인스턴스를 생성해서 사용할 수 없고 상속을 통해서 오버라이딩되어야 한다. FilterInputStream/FilterOutputStream을 상속받아서 기반스트림에 보조기능을 추가한 보조스트림 클래스는 다음과 같다.

FilterInputStream의 자손	BufferedInputStream, DataInputStream, PushbackInputStream 등
FilterOutputStream의 자손	BufferedOutputStream, DataOutputStream, PrintStream 등

 

 

 

BufferedInputStream과 BufferedOutputStream

BufferedInputStream/BufferedOutputStream은 스트림의 입출력 효율을 높이기 위해 버퍼를 사용하는 보조스트림이다.

한 바이트씩 입출력하는 것 보다는 버퍼(바이트배열)를 이용해서 한 번에 여러 바이트를 입출력하는 것이 빠르기 때문에 대부분의 입출력 작업에 사용된다.

 

생성자 설명
BufferedInputStream(InputStream in, int size) 주어진 InputStream인스턴스를 입력소스(input source)로하며 지정된 크기(byte단위)의 버퍼를 갖는 BufferedInputStream인스턴스를 생성한다.
BufferedInputStream(InputStream in) 주어진 InputStream인스턴스를 입력소스(input source)로 하며 버퍼의 크기를 지정해주지 않으므로 기본적으로 8192byte 크기의 버퍼를 가젝 된다.

bufferedInputStream의 버퍼크기는 입력소스로부터 한 번에 가져올 수 있는 데이터의 크기로 지정하면 좋다.

프로그램에서 입력소스로부터 데이터를 읽기 위해 처음으로 read메서드를 호출하면, BufferedInputStream은 입력소스로 부터 버퍼 크기만큼의 데이터를 읽어다 자신의 내부 버퍼에 저장한다. 이제 프로그램에서는 BufferedInputStream의 버퍼에 저장된 데이터를 읽으면 되는 것이다. 외부의 입력소스로 부터 읽는 것보다 내부의 버퍼로 부터 읽는 것이 훨씬 빠르기 때문에 그만큼 작업 효율이 높아진다.

 

 

메서드/생성자 설명
BufferedOutputStream(OuputStream out,int size) 주어진 OutputStream인스턴스를 출력소스(outputsource)로하며 지정된 크기(단위byte)의 버퍼를 갖는 BufferedOutputStream인스턴스를 생성한다.
BufferedOutputStream(OutputStream out) 주어진 OutputStream인스턴스를 출력소스(output ource)로하며 버퍼의 크기를 지정해주지 않으므로 기본적으로 8192 byte 크기의 버퍼를 갖게 된다.
flush() 버퍼의 모든 내용을 출력소스에 출력한 다음, 버퍼를 비운다.
close() flush()를 호출해서 버퍼의 모든 내용을 출력소스에 출력하고, bufferedOutputStream인스턴스가 사용하던 모든 자원을 반환한다.

BufferedOutputStream 역시 버퍼를 이용해서 출력소스와 작업을 하게 되는데, 입력소스로부터 데이터를 읽을 때와 반대로, 프로그램에서 write메서드를 이용한 출력이 BufferedOutputStream의 버퍼에 저장된다, 버퍼가 가득 차면, 그 때 버퍼의 모든내용을 출력소스에 출력한다. 그리고는 버퍼를 비우고 다시 프로그램으로부터의 출력을 저장할 준비를 한다.

버퍼가 가득 찼을 때문 출력소스에 출력을 하기 때문에, 마지막 출력부분이 출력소스에 쓰이지 못하고 BufferedOutputStream의 버퍼에 남아있는 채로 프로그램이 종료될 수 있다는 점을 주의해야한다.

따라서 프로그램에서 모든 출력작업을 마친 후 BufferedOutputStream에 close()나 flush()를 호출해서 마지막에 버퍼에 있는 모든 내용이 출력소스에 출력되도록 해야 ㅎ나다.

BufferedOutputStream의 close()는 flush()를 호출하여 버퍼의 내용을 출력스트림에 쓰도록 한 후, BufferedOutputSream인스턴스의 참조변수에 null을 지정함으로써 사용하던 자원들이 반환되게 한다.

 

 

public class BufferedOutputStreamEx1 {
	public static void main(String[] args) {
		try {
			FileOutputStream fos = new FileOutputStream("123.txt");
			BufferedOutputStream bos = new BufferedOutputStream(fos, 5);
			for (int i = '1'; i <= '9'; i++) {
				bos.write(i);
			}

			fos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

이 예제에서 fos.close()를 호출해서 스트림을 닫아주기는 했지만, 이렇게 해서는 BufferedOutputStream의 버퍼에 있는 내용이 출력되지 않는다. bos.close();와 같이 해서 BufferedOutputStream의 close()를 호출해야 주어진 버퍼에 남아있떤 모든 내용이 출력된다.

 

 

 

3.5 PrintStream

PrintStream은 데이터를 기반스트림에 다양한 형태로 출력할 수 있는 print, println, printf와 같은 메서드를 오버로딩하여 제공한다.

 PrintStream은 데이터를 적절한 문자로 출력하는 것이기 때문에 문자기반 스트림의 역할을 수행한다. 그래서 JDK1.1에서 부터 PrintStream보다 향상된 기능의 문자기반 스트림인 PrintWriter가 추가되었으나 그 동안 매우 빈번히 사용되던 System.out이  PrintStream이다 보니 둘 다 사용할 수밖에 없게 되었다.

PrintStream과 PrintWriter는 거의 같은 기능을 가지고 있지만 PrintWriter가 PrintStream에 비해 다양한 언어의 문자를 처리하는데 적합하기 때문에 가능하면 PrintWriter를 사용하는것이 좋다.

PrintStream은 System클래스의 static멤버인 out과 err, 즉 System.out, System.err이 PrintStream이다.

 

생성자/메서드 설명
PrinStream(File file)
PrinStream(File file, String scn)
PrinStream(OutputStream out)
PrinStream(OutputStream out, boolean autoFlush)
PrinStream(OutputStream out, boolean autoFlush, String encoding)
PrinStream(String fileName)
PrinStream(String fileName, String scn)
지정된 출력스트림을 기반으로 하는 PrintStream인 스턴스를 생성한다. autoFlush의 값을 true로 하면 println메서드가 호출되거나 개행문자가 출력될 때 자동으로 flush된다. 기본값은 false이다.
boolean checkError() 스트림을 flush하고 에러가 발생했는지를 알려 준다.
void print
void print
void print
void print
void print
void print
void print
void print
void print
void println
void println
void println
void println
void println
void println
void println
void println
void println
인자로 주어진 값을 출력소스에 문자로 출력한다.
println메서드는 출력 후 줄바꿈을 하고, print메서드는 줄을 바꾸지 않는다.
void println 줄바꿈 문자(line separator)를 출력함으로써 줄을 바꾼다.
PrintStream printf 정형화된(formatted)출력을 가능하게 한다.
protected void setError() 작업 중에 오류가 발생했음을 알린다.
(setError()를 호출한 후에, checkError()를 호출하면 true를 반환한다.)

print()나 println()을 이용해서 출력하는 중에 PrintStream의 기반스트림에서 IOException이 발생하면 checkError()를 통해서 인지할 수 있다. println()이나 print()는 예외를 던지지않고 내부에서 처리하도록 정의하였는데, 그 이유는 println()과 같은 메서드가 매우 자주 사용되는 것이기 때문이다./

만일 println()이 예외를 던지도록 정의되었다면 println()을 사용하는 모든 곳에 try-catch문을 사용해야 할 것이다.

 

public class PrintStream extends FileOutputStream implements Appendable, Closeable {
	...
	private boolean trouble = false;
	public void print(int i) {
		write(String.valueOf(i));
	}
	private void write(String s) {
		try {
			...
		} catch (IOExcepton x) {
			trouble = true;
		}
}
	...
	public boolean checkError() {
		if(out != null) flush();
		return trouble;
	}
}

i+와 String.valueOf(i)는 같은 결과를 얻집만, String.valueOf(i)가 더 성능이 좋다.

 

printf()는 JDK.15부터 추가된 것으로, C언어와 같이 편리한 형식화된 출력을 지원하게 되었다. printf()에 사용될 수 있는 옵션은 꽤나 다양한데 그에 대한 자세한 내용은 JAVAAPI문서에서 Formatter클래스를 참고하면 된다.

 

format 설명 결과(int i=65)
%d 10진수(decimal integer) 65
%o 8진수(ocrtal integer) 101
%x 16진수(hexadecimal integer) 41
%c 문자 A
%s 문자열 65
%5d 5자리 숫자. 빈자리는 공백으로 채운다    65
%-5d 5자리 숫자. 빈자리는 공백으로 채운다(왼쪽 정렬) 65
%05d 5자리 숫자. 빈자리는 0으로 채운다. 00065

 

 

format 설명 결과(String str = "ABC")
%s 문자열(string) ABC
%5s 5자리 문자열. 빈자리는 공백으로 채운다.   ABC
%-5s 5자리 문자열. 빈자리는 공백으로 채운다.(왼쪽 정렬) ABC

 

 

format 설명 결과(float f = 1234.56789f)
%e 지수형태표현(exponent) 1.234568e+03
%f 10진수(decimal float) 1234.56789
%3.1f 출력될 자리수를 최소 3자리(소수점포함). 소수점 이하 1자리(2번째 자리에서 반올림) 1234.6
%8.1f 소수점이상 최소 6자리. 소수점 이하 1자리.
출력될 자리수를 최소 8자리(소수점포함)를 확보한다. 빈자리는 공백으로 채워진다.(오른쪽 정렬)
  1234.6
%08.1f 소수점이상 최소 6자리, 소수점 이하 1자리.
출력될 자리수를 최소 8자리(소수점포함)를 확보한다. 빈자리는 0으로 채워진다.
001234.6
%-8.1f 소수점이상 최소 6자리, 소수점 이하 1자리
출력될 자리수를 최소 8자리(소수점포함)를 확보한다. 빈자리는 공백으로 채워진다.(왼쪽 정렬)
1234.6
1234.6

 

format 설명
\t 탭(tab)
%n 줄바꿈 문자(new line)
%% %

 

 

format 설명 결과
%tR
%tH:%tM
시분(24시간) 21:05
21:05
%tT
%tH:%tM:%tS
시분초(24시간) 21:05:33
21:05:33
%tD
%tm/%td/%ty
월일년 11/16/15
11/16/15
%tF
%tY-%tm-%td
년월일 2015-11-16
2015-11-16

날짜와 시간에 사용될 수 있는 옵션

+ Recent posts