char 단위의 입/출력 클래스는 이름이 Reader 혹은 Writer로 끝난다.
이런 char 단위 입출력 클래스를 이용해서 키보드로부터 문자열 한 줄을 입력 받는 프로그램을 구현해보자.
키보드로부터 문자열을 입력받아 콘솔에 출력하기 위해서는 System.in
을 사용하면 된다.
그리고 System.in 은 위 오라클 도큐먼트 이미지에서 알 수 있듯
java.lang
패키지, System
클래스에서 InputStream type의 in
필드를 가진다.
그리고 한 줄을 입력받기 위해서는 BufferedReader
메소드를 사용해야 한다.
그런데 문서를 보니 BufferedReader() 의 매개변수는 Reader 타입이다.
아니, <콘솔>에서 (한 줄) 을 입력받고 싶은데
콘솔에서 입력을 받으려면 필요한 <System.in>은 InputStream 타입이고,
한 줄을 입력받기 위해 필요한 (BufferedRader)은 매개변수가 Reader 타입이라니!
그렇다면 System.in을 Reader 타입으로 바꿔주어야 할텐데,
이 때 사용하는 것이 InputStreamReader 클래스이다.
InputStreamReader 클래스는 Reader를 상속받는 Reader 타입이다.
그리고 생성자의 매개변수는 InputStream 타입을 받고 있다.
그래! InputStreamReader 클래스를 사용하면 System.in과
BufferedReader를 사용해 키보드로부터 문자열 한 줄을 입력 받을 수 있을 것이다.
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class CharIOExam01 {
public static void main(String[] args) {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
}
}
먼저, 한 줄을 입력받기 위해 BufferedReader() 를 생성한다.
키보드로 입력받을 것이므로 () 안에는 System.in 을 넣어야 하는데
타입이 맞지 않아 바로 넣을 수는 없으니 System.in을
Reader 타입으로 바꿔주는 InputStreamReader 사용한다.
데코레이터 패턴은 하나의 클래스를 마치 장식하는 것처럼 생성자에 감싸서
새로운 기능을 계속 추가할 수 있도록 클래스를 만드는 방식이다.
이와 같이 객체에 추가적인 요건을 맞추어 특정 기능을 동적으로 사용할 수 있도록 하는 방식을 데코레이터 패턴이라고 한다.
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
public class CharIOExam01 {
public static void main(String[] args) {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//키보드로 입력받은 문자열을 저장하기 위해 line변수를 선언
String line = null;
try {
line = br.readLine()
} catch (IOException e) {
e.printStackTrace();
}
//콘솔에 출력
System.out.println(line);
}
}
BufferedReader 객체가 가진 readLine() 메소드를 사용해서
키보드로부터 입력받은 값을 String line 변수에 넣어준다.
그리고 이 line 변수를 밖에서도 선언하기 위해서 try 블록 밖에 위치하게 한다.
그런데 line = br.readLin() 줄에서 Exception이 발생할 수 있으므로
try catch문으로 예외처리를 해줘야 한다.
코드를 실행하면 br.readLine()이 실행될 때 키보드로부터 한 줄을 입력받아
line 변수에 넣어진 다음 console에 line 값이 출력된다.
이런 char 단위 입출력 클래스를 반복문으로 구현한다면 여러 줄도 입력 받을 수 있고
데코레이터 패턴으로 구현한다면 키보드 말고 파일로도 입력 받을 수 있다.
외에도 다양한 방법을 생각해볼 수 있다.
이번에는 파일로 입력받고 파일로 출력하는 프로그램을 구현해보자.
파일로 받기 위해서는 파일로 읽어주는 FileReader 객체가 필요하고
문자열을 한줄씩 읽기 위해서는 BufferedReader 객체가 필요하다.
그래서 두 개의 객체가 서로 "같이" 생성 되어야 한다.
import java.io.BufferedReader;
import java.io.FileReader;
public class CharIOExam02 {
public static void main(String[] args) {
BufferedReader br = new BufferedReader(new FileReader("src/level2/CharIOExam02.java"));
}
}
우선 BufferedReader 가 생성될 때,
파일에서 입력받기 위한 FileReader 객체를 생성한다.
이때 () 안에는 읽고싶은 파일의 경로를 쓰면 된다.
이클립스 기준으로는 프로젝트가 현재 기준이 되므로 src 경로부터 작성해주면 된다.
public static void main(String[] args) {
BufferedReader br = null;
try{
br = new BufferedReader(new FileReader("src/level2/CharIOExam02.java"));
} catch(Exception e){
...
}
...
}
new FileReader가 생성될 때 FileNotFoundException이 발생하면
try catch문으로 예외처리를 해주어야하고,
이때 BufferedReader를 try블록 밖에서도 사용하고 싶다면
BufferedReader의 선언을 아래처럼 try 블록 밖에서 해야한다.
여기까지 파일의 한 줄을 읽어들일 준비가 완료되었다.
이제 파일을 쓰는 코드를 작성할 차례이다.
파일에 쓸 수 있는 객체는 FileWriter를 쓰면 되는데,
FileWriter는 write() 메소드가 굉장히 단순하게 구현되어있다.
대표적으로 System.out.println의 System.out 타입이 PrintWriter 객체이다.
PrintWriter는 다양한 방법으로 출력하는 메소드들을 가지고 있다.
그래서 출력을 할 때에는 PrintWriter 객체를 이용하는 것이 편리하다.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class CharIOExam02 {
public static void main(String[] args) {
BufferedReader br = null;
PrintWriter pw = null;
try{
br = new BufferedReader(new FileReader("src/level2/CharIOExam02.java"));
pw = new PrintWriter(new FileWriter("test.txt"));
} catch(Exception e){
e.printStackTrace();
}
}
}
파일에서 받을 것이므로, 파일에서 받게 해주는 객체인 FileWriter를 사용한다.
new FileWriter()의 () 안에는 저장할 파일의 경로와 함께 이름을 적으면 된다.
경로를 적지 않으면 해당 프로젝트 밑에 위치하게 된다.
위 예시는 데코레이터 패턴을 사용하고 있지만
PrintWriter 생성자 자체가 파일을 받아들이는 기능도 제공하고 있어서
FileWriter를 쓰지 않고, PrintWriter만 사용하는 것도 가능하다.
try{
br = new BufferedReader(new FileReader("src/level2/CharIOExam02.java"));
pw = new PrintWriter(new FileWriter("test.txt"));
String line = null;
while((line = br.readLine())!= null){
pw.println(line);
}
}catch(Exception e){
e.printStackTrace();
}
파일이 한 줄이 아니고, 여러 줄이 있을 수도 있으니 반복문을 사용한다.
파일에 더 읽을 것이 없다면 null이 리턴되므로, 리턴 값이 null일 때까지 수행한다.
한 줄을 읽었을 때 저장할 수 있는 공간은 String line 변수를 선언해 사용한다.
String line 변수 안에 읽어들인 값을 다시 파일에 쓸 때는 PrintWriter pw 객체를 쓴다.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class CharIOExam02 {
public static void main(String[] args) {
BufferedReader br = null;
PrintWriter pw = null;
try{
br = new BufferedReader(new FileReader("src/level2/CharIOExam02.java"));
pw = new PrintWriter(new FileWriter("test.txt"));
String line = null;
while((line = br.readLine())!= null){
pw.println(line);
}
}catch(Exception e){
e.printStackTrace();
}finally {
pw.close();
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
IO는 반드시 닫아주어야 하므로, Exception 발생 여부와 상관 없이
무조건 실행하는 finally 블럭에 close() 메소드를 호출하여 마무리한다.
코드 실행 후 프로젝트 위치에서 새로고침을 하면
test.txt 파일이 생성되어있는 것을 확인할 수 있다.
자바 io 패키지는 "어디에서 읽을 것인지", "어디에다가 쓸 것인지", "어떤 방식으로 읽을 것인지", "어떤 방식으로 쓸 것인지"의 경우에 따라서 여러가지 객체의 종류들을 연결하고 조합해 사용할 수 있다.