강의 자료
https://gitlab.com/easyspubjava/javacoursework/-/tree/master/Chapter6
예외 처리
프로그램에서의 오류
- 컴파일 오류(compile error)
- 프로그램 코드 작성 중 발생하는 문법적 오류
- 최근에는 개발 환경(eclipse)에서 대부분의 컴파일 오류는 detection 됨
- 실행 오류(runtime error)
- 실행 오류는 비정상 종료가 되는 경우 시스템의 심각한 장애를 발생할 수 있음
- 실행 중인 프로그램이 의도하지 않은 동작(bug)을 하거나 프로그램이 중지되는 오류
예외 처리가 중요한 이유: 프로그램의 비정상 종료를 피하여 시스템이 원할하게 실행되도록 한다.
JAVA는 안정적이다.
오류가 발생한 경우 log를 남겨서 추후에 log를 분석하여 원인을 파악하고 버그를 수정한다.
오류와 예외 클래스
- 시스템 오류(error) : 가상 머신에서 발생, 프로그래머가 처리 할 수 없는 오류
- 동적 메모리가 없는 경우, 스택 메모리 오버플로우등
- 예외(Exception) :프로그램에서 제어 할 수 있는 오류
- 읽어 들이려는 파일이 존재하지 않거나, 네트워크이나 DB연결이 안 되는 경우 등
- 자바는 안전성이 중요한 언어로 대부분 프로그램에서 발생하는 오류에 대해 문법적으로 예외 처리를 해야 함
예외 클래스
모든 예외 클래스의 최상위 클래스는 Exception 클래스
09. 예외 처리하기와 미루기
try-catch 문
배열의 오류처리
package Chapter6.ch07;
public class ArrayExceptionHandling {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
try {
for (int i = 0; i <= 5; i++) { //5 인덱스 참조시 오류
System.out.println(arr[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("e = " + e);
}
System.out.println("비정상 종료되지 않습니다.");
}
}
출력 결과
try-catch-finally 문
try 블록이 수행되는 경우 finally 블록은 항상 수행됨
finally 블록에서 파일을 닫거나 네트워크를 닫는 등의 리소스 해제 구현함
package Chapter6.ch07;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileExceptionHandling {
public static void main(String[] args) {
FileInputStream fis = null;
try{
fis = new FileInputStream("a.txt"); //없는 파일
} catch (FileNotFoundException e) {
System.out.println(e);
}
finally{ //항상 수행
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("수행");
}
System.out.println("여기도 수행");
}
}
try-with-resources 문
- 리소스를 사용하는 경우 close() 하지 않아도 자동으로 해제되도록 함
- 리소스를 try() 내부에서 선언해야만 함
- close()를 명시적으로 호출하지 않아도 try {} 블록에서 열린 리소스는 정상적인 경우나 예외가 발생한 경우 모두 자동으로 해제됨
- 해당 리소스 클래스가 AutoCloseable 인터페이스를 구현해야 함
- 자바 9부터 리소스는 try() 외부에서 선언하고 변수만을 try(obj)와 같이 사용할 수 있음
package Chapter6.ch07;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileExceptionHandling {
public static void main(String[] args) {
try(FileInputStream fis = new FileInputStream("a.txt")){
System.out.println("read");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("오류 처리됨");
}
}
출력결과
FileInputStream의 경우 AutoCloseable을 구현하고 있고, 이 인터페이스는 종료될 때 close()를 호출한다.
AutoCloseable 인터페이스 구현 실습
AutoCloseObj.java - close() 구현
package Chapter6.ch07;
public class AutoCloseObj implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("리소스 close()");
}
}
AutoCloseTest.java
package Chapter6.ch07;
public class AutoCloseTest {
public static void main(String[] args) {
AutoCloseable obj = new AutoCloseObj();
try (obj) {
throw new Exception();
} catch (Exception e) {
System.out.println("예외 부분입니다.");
}
}
}
출력 결과
예외 처리 미루기 : throws
throws를 이용하여 호출하는 쪽에서 try-catch로 처리하도록 함
package Chapter6.ch07;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ThrowsException {
public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(fileName); //fileNotFound Exception
Class c = Class.forName(className); //ClassNotFound Exception 발생
return c;
}
public static void main(String[] args) {
ThrowsException test = new ThrowsException();
try {
test.loadClass("a.txt", "java.lang.String");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e) { //그 외 모든 예외 처리
e.printStackTrace();
}
}
}
Exception은 모든 예외의 최상위 클래스이므로 catch문 쓸 때 맨 아래에 적기
사용자 정의 예외 클래스와 활용
프로그래머가 직접 만들어야 하는 예외
✅ 기존 예외 클래스 중 가장 유사한 예외 클래스에서 상속받아 사용자 정의 예외 클래스를 만듦
패스워드에 대한 예외 처리 하기
- 패스워드를 입력할 때 다음과 같은 경우 오류처리를 합니다.
- 비밀번호의 길이는 5 이상입니다.
- 비밀번호는 문자로만 이루어져서는 안 됩니다. (하나이상의 숫자나 특수문자를 포함)
- 비밀번호는 null일 수 없습니다.
PassWordException.java
package Chapter6.ch10;
public class PassWordException extends Exception {
public PassWordException(String message) {
super(message);
}
}
PasswordTest.java
package Chapter6.ch10;
public class PasswordTest {
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) throws PassWordException {
if (password == null) {
throw new PassWordException("비밀번호는 null이 아니어야 합니다.");
} else if (password.length() < 5) {
throw new PassWordException("비밀번호는 5자 이상이어야합니다");
} else if (password.matches("[a-zA-Z]+")) {
throw new PassWordException("비밀번호는 숫자나 특수문자여야합니다");
}
this.password = password;
}
public static void main(String[] args) {
PasswordTest test = new PasswordTest();
String password = null; //비밀번호가 null일 경우
try{
test.setPassword(password);
System.out.println("오류 없음");
} catch (PassWordException e) {
System.out.println("e.getMessage() = " + e.getMessage());
}
password = "abc";
try{
test.setPassword(password);
System.out.println("오류 없음");
} catch (PassWordException e) {
System.out.println("e.getMessage() = " + e.getMessage());
}
password = "abcde";
try{
test.setPassword(password);
System.out.println("오류 없음");
} catch (PassWordException e) {
System.out.println("e.getMessage() = " + e.getMessage());
}
password = "abcde1#";
try{
test.setPassword(password);
System.out.println("오류 없음");
} catch (PassWordException e) {
System.out.println("e.getMessage() = " + e.getMessage());
}
}
}
출력 결과
로그 남기기 - java.util.logging.Logger 활용
logging
시스템 운영에 대한 기록 => 오류가 발생했을 때 디버깅을 용이하게 함
어디까지 로그를 남기는 게 좋을까?
- 너무 적은 로그 : 정확한 시스템의 상황을 파악하기 어려움
- 너무 많은 로그 : 빈번한 file I/O의 오버헤드와 로그 파일의 백업 문제등...
logging 패키지에서 제공하는 로그 레벨은 severe, warning, info, config, fine, finer, finest이다.
오픈 소스로는 👍 Log4J2를 주로 사용한다.
Log4J2 사용법:
filehandler: 어느 파일에 로그를 찍을 것인지 정한다.
consolehandler: 화면에 출력한다. - 우리가 설정은 X
java.util.logging
- 자바에서 기본적으로 제공되는 log package
- 파일이나 콘솔에 로그 내용을 출력할 수 있음
- jre/lib/logging.properties 파일을 편집하여 로그의 출력방식 로그 레벨을 변경할 수 있음
- logging 패키지에서 제공하는 로그 레벨은 severe, warning, info, config, fine, finer, finest 임
- 오픈소스로는 log4j를 많이 사용하고 있음
MyLogger.java
package Chapter6.ch11;
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
public class MyLogger {
Logger logger = Logger.getLogger("mylogger"); //로거 객체 가져오기
private static MyLogger instance = new MyLogger(); //싱글톤 패턴
public static final String errorLog = "log.txt";
public static final String warningLog = "warning.txt";
public static final String fineLog = "fine.txt";
//file handler
private FileHandler logFile = null;
private FileHandler warningFile = null;
private FileHandler fineFile = null;
private MyLogger(){
try {
logFile = new FileHandler(errorLog, true); //파일 이름, true: append하여 로그찍기
warningFile = new FileHandler(warningLog, true);
fineFile = new FileHandler(fineLog, true);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
logFile.setFormatter(new SimpleFormatter());
warningFile.setFormatter(new SimpleFormatter());
fineFile.setFormatter(new SimpleFormatter());
logger.setLevel(Level.ALL); //finest부터 severe 로그 레벨까지 모두 로그찍기
//파일마다 로그레벨 지정 가능
fineFile.setLevel(Level.FINE); //fine레벨부터 severe 로그레벨까지 찍기
warningFile.setLevel(Level.WARNING); //warning-severe 로그 레벨까지 찍기
//파일 핸들러 추가하기
logger.addHandler(logFile);
logger.addHandler(warningFile);
logger.addHandler(fineFile);
}
public static MyLogger getLogger(){
return instance;
}
public void log(String msg){
//모든 로그 찍기
logger.finest(msg);
logger.finer(msg);
logger.fine(msg);
logger.config(msg);
logger.info(msg);
logger.warning(msg);
logger.severe(msg);
}
public void fine(String msg){
logger.fine(msg);
}
public void warning(String msg){
logger.warning(msg);
}
}
MyLogger.java
package Chapter6.ch11;
public class LoggerTest {
public static void main(String[] args) {
MyLogger logger = MyLogger.getLogger();
logger.log("log test");
}
}
실행해 보면 3개의 파일이 생긴 것을 볼 수 있다.
log.txt
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
FINEST: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
FINER: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
FINE: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
CONFIG: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
INFO: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
WARNING: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
SEVERE: log test
모든 레벨의 로그가 찍혔다.
fine.txt
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
FINE: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
CONFIG: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
INFO: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
WARNING: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
SEVERE: log test
파일 핸들러에 지정해 두었던 fine 레벨까지 찍힌다.
warning.txt
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
WARNING: log test
5월 05, 2023 4:59:23 오후 Chapter6.ch11.MyLogger log
SEVERE: log test
파일 핸들러에 지정해두었던 warning 레벨까지 찍힌다.
만약 logger 기본 레벨을 INFO로 설정한다면?
모든 파일에 영향을 준다. 즉 기본 로그 레벨이 변하면 파일마다 지정한 로그 레벨과 기본 로그 레벨 중 더 높은(더 심각한) 레벨로 찍힌다.
info가 fine보다 심각한 레벨이므로 fineFile도 info부터 찍힌다.
✅ 더 높은(위험한) 로그 레벨부터 찍힌다.
실행결과
설정된 로그 preference(logging.properties)가 info 레벨부터여서 INFO부터 찍힌다.
Logger 만들기
- 시나리오
- 학생의 이름에 오류가 있는 경우 예외 처리를 하고 예외 상황을 로그로 남긴다.
- 학생의 이름은 null 이거나 중간에 space가 3개 이상인 경우 오류가 발생한다.
- 학생 정보 시스템에 로그를 기록하도록 한다.
- 구현하기
- Logger 인스턴스를 생성한다.
- 로그를 남기기 위한 FileHandler를 생성한다.
- FileHandler의 level을 지정하고 Logger에 생성된 addHandler() 메서드로 FileHandler를 추가한다.
Student.java
package Chapter6.ch11;
public class Student {
private String studentName;
MyLogger myLogger = MyLogger.getLogger();
public Student(String studentName){
if(studentName == null){
throw new StudentNameFormatException("name must not be null");
}
if( studentName.split(" ").length > 3)
throw new StudentNameFormatException("이름이 너무 길어요");
this.studentName = studentName;
}
public String getStudentName() {
myLogger.fine("begin getStudentName()");
return studentName;
}
}
null, 이름이 너무 길 때 에러를 발생시킨다.
StudentNameFormatException.java
package Chapter6.ch11;
public class StudentNameFormatException extends IllegalArgumentException{
public StudentNameFormatException(String message){
super(message);
}
}
StudentTest.java
package Chapter6.ch11;
public class StudentTest {
public static void main(String[] args) {
MyLogger myLogger = MyLogger.getLogger();
String name = null;
try{
Student student = new Student(name); //name이 null일때
}catch( StudentNameFormatException e ){
myLogger.warning(e.getMessage());
}
try{ //name이 너무 길때
Student student = new Student("Edward Jon Kim Test");
}catch ( StudentNameFormatException e){
myLogger.warning(e.getMessage());
}
//name이 올바른 이름일때
Student student = new Student("James");
}
}
에러가 발생하면 로그 찍도록 한다.
12. 자바의 입출력을 위한 I/O 스트림
입출력 스트림
- 네트워크에서 자료의 흐름이 물의 흐름과 같다는 비유에서 유래됨
- 자바는 다양한 입출력 장치에 독립적으로 일관성 있는 입출력을 입출력 스트림을 통해 제공
- 입출력이 구현되는 곳: 파일 디스크, 키보드, 마우스, 네트워크, 메모리 등 모든 자료가 입력되고 출력되는 곳
입출력 스트림의 구분
- 대상 기준 : 입력 스트림 / 출력 스트림
- 자료의 종류 : 바이트 스트림 / 문자 스트림
- 기능 : 기반 스트림 / 보조 스트림
입력 스트림과 출력 스트림
- 입력 스트림 : 대상으로부터 자료를 읽어 들이는 스트림
- 출력 스트림 : 대상으로 자료를 출력하는 스트림
- 스트림의 종류
입력 스트림 | FileInputStream, FileReader, BufferedInputStream, BufferedReader 등 |
출력 스트림 | FileOutputStream, FileWriter, BufferedOutputStream, BufferedWriter 등 |
바이트 단위 스트림과 문자 단위 스트림
- 바이트 단위 스트림 : 동영상, 음악 파일, 실행 파일 등의 자료를 읽고 쓸 때 사용
- 문자 단위 스트림 : 바이트 단위로 자료를 처리하면 문자는 깨짐, 인코딩에 맞게 2바이트 이상으로 처리하도록 구현된 스트림
- 스트림의 종류
바이트 스트림 | FileInputStream, FileOutputStream, BufferedInputStream, BufferedOutputStream 등 |
문자 스트림 | FileReader, FileWriter, BufferedReader, BufferedWriter 등 |
기반 스트림과 보조 스트림
- 기반 스트림 : 대상에 직접 자료를 읽고 쓰는 기능의 스트림
- 보조 스트림 : 직접 읽고 쓰는 기능은 없이 추가적인 기능을 더해주는 스트림
- 보조 스트림은 직접 읽고 쓰는 기능은 없으므로 항상 기반 스트림이나 또 다른 보조 스트림을 생성자의 매개 변수로 포함함
- 스트림의 종류
기반 스트림 | FileInputStream, FileOutputStream, FileReader, FileWriter 등 |
보조 스트림 | InputStreamReader, OutputStreamWriter, BufferedInputStream, BufferedOutputStream 등 |
13. 표준 입출력 스트림
System 클래스의 표준 입출력 멤버
- System.out
표준 출력(모니터) 스트림
System.out.println("출력 메시지");
- System.in
표준 입력(키보드) 스트림
int d = System.in.read() // 한 바이트 읽기
- System.err
표준 에러 출력(모니터) 스트림
System.err.println("에러 메시지");
알파벳 출력 예제
package Chapter6.ch13;
import java.io.IOException;
public class SystemInTest11 {
public static void main(String[] args) {
System.out.println("알파벳 여러개를 쓰고 [Enter}를 누르세요");
int i;
try {
while ((i = System.in.read()) != '\n'){
System.out.print((char) i);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
한 바이트씩 출력된다.
만약 한글을 입력한다면? => 글자가 깨진다. 한글은 2바이트이기 때문이다.
=> InputStreamReader를 사용하면 된다.
InputStreamReader : byte를 문자로 바꿔주는 class, InputStream을 입력으로 받는다.
✅ System.in은 inputStream이다.
한글 입력받아서 출력하는 예제
package Chapter6.ch13;
import java.io.IOException;
import java.io.InputStreamReader;
public class SystemInTest11 {
public static void main(String[] args) {
System.out.println("알파벳 여러개를 쓰고 [Enter}를 누르세요");
int i;
try {
InputStreamReader irs = new InputStreamReader(System.in);
while ((i = irs.read()) != '\n'){
System.out.print((char) i);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
출력 결과
InputStream
- 바이트 단위 입력 스트림 최상위 추상 클래스
- 많은 추상 메서드가 선언되어 있고 이를 하위 스트림이 상속받아 구현함
- 주요 하위 클래스
스트림 클래스 | 설명 |
FileInputStream | 파일에서 바이트 단위로 자료를 읽습니다. |
ByteArrayInputStream | byte 배열 메모리에서 바이트 단위로 자료를 읽습니다. |
FilterInputStream | 기반 스트림에서 자료를 읽을 때 추가 기능을 제공하는 보조 스트림의 상위 클래스 |
- 주요 메서드
메서드 | 설명 |
int read() | 입력 스트림으로부터 한 바이트의 자료를 읽습니다. 읽은 자료의 바이트 수를 반환합니다. |
int read(byte b[]) | 입력 스트림으로 부터 b[] 크기의 자료를 b[]에 읽습니다. 읽은 자료의 바이트 수를 반환합니다. |
int read(byte b[], int off, int len) | 입력 스트림으로 부터 b[] 크기의 자료를 b[]의 off변수 위치부터 저장하며 len 만큼 읽습니다. 읽은 자료의 바이트 수를 반환합니다. |
void close() | 입력 스트림과 연결된 대상 리소스를 닫습니다. |
AutoClosable 인터페이스를 implements 하고 try에 리소스 메서드를 둬서 읽으면 종료 시 close()가 자동으로 호출된다.
- 명시적으로 호출하지 않아도 된다.
read 함수
데이터의 바이트 읽는다.
반환값 : 데이터 다음 바이, EOF일 경우 -1 반환
파일 한 바이트씩 읽기
package Chapter6.ch14;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamTest1 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("input.txt");
System.out.println((char) fis.read()); //1 byte씩 읽는다.
System.out.println((char) fis.read());
System.out.println((char) fis.read());
} catch (IOException e) { //파일을 찾을 수 없을 경우 등등
e.printStackTrace();
try {
fis.close();
} catch (IOException ex) {
ex.printStackTrace();
} catch (Exception e1) { //nullPointerException 잡기
System.out.println("e1 = " + e1);
}
} finally { //리소스 종료하기
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) { //nullPointerException 잡기
System.out.println("e2 = " + e);
}
}
System.out.println("end");
}
}
파일 끝까지 한 바이트씩 읽기
package Chapter6.ch14;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest2 {
public static void main(String[] args) {
int i;
try(FileInputStream fis = new FileInputStream("input.txt")) {
while((i = fis.read()) != -1){ //파일 끝까지 읽기
System.out.println((char)i);
}
} catch (IOException e) {
System.out.println("e = " + e);
}
}
}
파일에서 바이트 배열로 자료 읽기
package Chapter6.ch14;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamTest3 {
public static void main(String[] args) {
int i;
try(FileInputStream fis = new FileInputStream("input2.txt")) {
byte[] bs = new byte[10];
while((i = fis.read(bs)) != -1){ //10바이트씩 파일 끝까지 읽기
for (int ch : bs) {
System.out.print((char) ch);
}
System.out.println(" : " + i + "바이트 읽음");
}
} catch (IOException e) {
System.out.println("e = " + e);
}
}
}
출력 결과
출력 결과의 마지막 줄을 보면 6바이트만 읽었는데 10바이트가 출력된 것을 볼 수 있다.
이는 오류가 아니며, 이전에 저장되었던 바이트가 그대로 출력된 것이다.
이를 방지하기 위해서는 read()의 리턴값이 읽은 바이트 수임을 이용하여 그 차례까지 출력한다.
파일에서 바이트 배열로 자료 읽기 - 읽은 바이트만 출력하기
package Chapter6.ch14;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamTest3 {
public static void main(String[] args) {
int i;
try(FileInputStream fis = new FileInputStream("input2.txt")) {
byte[] bs = new byte[10];
while((i = fis.read(bs)) != -1){ //10바이트씩 파일 끝까지 읽기
for (int j=0;j<i;j++) {
System.out.print((char)bs[j]);
}
System.out.println(" : " + i + "바이트 읽음");
}
} catch (IOException e) {
System.out.println("e = " + e);
}
}
}
출력 결과
read() 함수의 offset과 len 이용하기
package Chapter6.ch14;
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamTest3 {
public static void main(String[] args) {
int i;
try(FileInputStream fis = new FileInputStream("input2.txt")) {
byte[] bs = new byte[10];
while((i = fis.read(bs, 1, 9)) != -1){ //9바이트씩 offset=1로 파일 끝까지 읽기
for (int j=0;j<i;j++) {
System.out.print((char)bs[j]);
}
System.out.println(" : " + i + "바이트 읽음");
}
} catch (IOException e) {
System.out.println("e = " + e);
}
}
}
출력 결과
OutputStream
- 바이트 단위 출력 스트림 최상위 추상 클래스
- 많은 추상 메서드가 선언되어 있고 이를 하위 스트림이 상속받아 구현함
- 주요 하위 클래스
스트림 클래스 | 설명 |
FileOutputStream | 파일에서 바이트 단위로 자료를 씁니다. |
ByteArrayOutputStream | byte 배열 메모리에서 바이트 단위로 자료를 씁니다. |
FilterOutputStream | 기반 스트림에서 자료를 쓸 때 추가 기능을 제공하는 보조 스트림의 상위 클래스 |
- 주요 메서드
메서드 | 설명 |
int write() | 한 바이트를 출력합니다. |
int write(byte b[]) | b[] 크기의 자료를 출력합니다. |
int write(byte b[], int off, int len) | b[] 배열에 있는 자료의 off 위치부터 len 개수만큼 자료를 출력합니다. |
void flush() | 출력을 위해 잠시 자료가 머무르는 출력 버퍼를 강제로 비워 자료를 출력합니다. |
void close() | 출력 스트림과 연결된 대상 리소스를 닫습니다. 출력 버퍼가 비워집니다. |
파일 한바이트씩 write하기
package Chapter6.ch14;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamTest1 {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
fos.write(65);
fos.write(66);
fos.write(67);
} catch (IOException e) {
System.out.println("e = " + e);
}
System.out.println("end");
}
}
output.txt
배열을 한꺼번에 파일에 쓰기
package Chapter6.ch14;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamTest2 {
public static void main(String[] args) throws FileNotFoundException {
FileOutputStream fos = new FileOutputStream("output2.txt", true);
try (fos) {
byte[] bs = new byte[26];
byte data = 65;
for (int i = 0; i < bs.length; i++) {
bs[i]=data++;
}
fos.write(bs); //배열로 write
} catch (IOException e) {
System.out.println(e);
}
System.out.println("end");
}
}
✅ FileOutputStream의 두번째 인자는 덮어쓰기할지 append 할지를 결정한다.
true: append(붙여쓰기), false: 덮어쓰기
output2.txt
배열을 offset과 len을 이용하여 파일에 쓰기
package Chapter6.ch14;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamTest2 {
public static void main(String[] args) throws FileNotFoundException {
FileOutputStream fos = new FileOutputStream("output2.txt", true);
try (fos) {
byte[] bs = new byte[26];
byte data = 65;
for (int i = 0; i < bs.length; i++) {
bs[i]=data++;
}
fos.write(bs,2,10); //배열의 offset 2부터 20글자 write
} catch (IOException e) {
System.out.println(e);
}
System.out.println("end");
}
}
출력 결과
flush()와 close()
flush()를 이용하여 출력 버퍼를 비운다.
close()시 flush()도 같이 호출된다.
Reader
- 문자 단위 입력 스트림 최상위 추상 클래스
- 많은 추상 메서드가 선언되어 있고 이를 하위 스트림이 상속받아 구현함
- 주요 하위 클래스
클래스 | 설명 |
FileReader | 파일에서 문자 단위로 읽는 스트림 클래스입니다. |
InputStreamReader | 바이트 단위로 읽은 자료를 문자로 변환해주는 보조 스트림 클래스 입니다. |
BufferedReader | 문자로 읽을 때 배열을 제공하여 한꺼번에 읽을 수 있는 기능을 제공하는 보조 스트림입니다. - 빠르다. |
- 주요 메서드
메서드 | 설명 |
int read() | 파일로부터 한 문자를 읽습니다. 읽은 문자를 반환합니다. |
int read(char[] buf) | 파일로부터 buf 배열에 문자를 읽습니다. |
int read(char[] buf, int off, int len) | 파일로부터 buf 배열의 off 위치로부터 len 개수만큼의 문자를 읽습니다. |
void close() | 입력 스트림과 연결된 대상 리소스를 닫습니다. |
FileReader
파일에서 문자 읽기
reader.txt
FileReaderTest.java
package Chapter6.ch15;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileReaderTest {
public static void main(String[] args) {
try(FileInputStream fis = new FileInputStream("reader.txt")){
int i;
while( (i = fis.read()) != -1){ //한 바이트씩 읽기
System.out.print((char) i);
}
} catch (IOException e) {
System.out.println("e = " + e);
}
}
}
result
한글이 깨진다 - 1 byte씩 읽기 때문
파일에서 "한글" 읽기 - FileReader 사용
package Chapter6.ch15;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderTest {
public static void main(String[] args) {
try(FileReader fr = new FileReader("reader.txt")){
int i;
while( (i = fr.read()) != -1){
System.out.print((char) i);
}
} catch (IOException e) {
System.out.println("e = " + e);
}
}
}
출력 결과
Writer
- 문자 단위 출력 스트림 최상위 추상 클래스
- 많은 추상 메서드가 선언되어 있고 이를 하위 스트림이 상속받아 구현함
- 주요 하위 클래스
클래스 | 설명 |
FileWriter | 파일에서 문자 단위로 출력하는 스트림 클래스입니다. |
OutputStreamWriter | 바이트 단위의 자료를 문자로 변환해 출력해주는 보조 스트림 클래스 입니다. |
BufferedWriter | 문자로 쓸 때 배열을 제공하여 한꺼번에 쓸 수 있는 기능을 제공하는 보조 스트림입니다. |
- 주요 메서드
메서드 | 설명 |
int write(int c) | 한 문자를 파일에 합니다. |
int write(char[] buf) | 문자 배열 buf의 내용을 출력합니다. |
int write(char[] buf, int off, int len) | 문자 배열 buf의 off위치에서부터 len 개수의 문자를 출력합니다. |
int write(String str) | 문자열 str을 출력합니다. |
int write(String str, int off, int len) | 문자열 str의 off번째 문자로부터 len 개수만큼 출력합니다. |
int flush() | 출력하기 전에 자료가 있는 공간(출력 버퍼)을 비워 출력하도록 합니다 |
void close() | 스트림과 연결된 리소스를 닫습니다. 출력 버퍼도 비워집니다. |
FileWriter
- 파일에 문자 쓰기(한글도 가능)
FileWriterTest.java
package Chapter6.ch15;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterTest {
public static void main(String[] args) {
try (FileWriter fw = new FileWriter("writer.txt")) {
fw.write('A');
char buf[] = {'B', 'C', 'D', 'E', 'F', 'G'};
fw.write(buf);
fw.write("안녕하세요. 잘 써지네요");
fw.write(buf, 1, 2); //offset 1부터 2글자 읽기
fw.write("65");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("출력 완료");
}
}
result
보조 스트림
- 실제 읽고 쓰는 스트림이 아닌 보조 기능을 제공하는 스트림
- FilterInputStream과 FilterOutputStream이 보조 스트림의 상위 클래스들
- 생성자의 매개변수로 또 다른 스트림(기반 스트림이나 다른 보조 스트림)을 가짐
- Decorator Pattern으로 구현 됨
- 상위 클래스 생성자
생성자 | 설명 |
protected FilterInputStream과(InputStream in) | 생성자의 매개변수로 InputStream을 받습니다. |
public FilterOutputStream(OutputStream out) | 생성자의 매개변수로 OutputStream을 받습니다. |
InputStreamReader와 OutputStreamWriter
- 바이트 단위로 읽거나 쓰는 자료를 문자로 변환해주는 보조 스트림
- 예시
package Chapter6.ch16;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamReaderTest {
public static void main(String[] args) {
try (InputStreamReader irs = new InputStreamReader(new FileInputStream("reader.txt"))) {
int i;
while((i=irs.read()) != -1)
System.out.print((char)i);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
바이트 단위 스트림을 문자 단위 스트림으로 변환
file reader일 경우 BufferedReader로 감싸면 된다.
BufferedInputStream과 BufferedOutputStream
- 약 8k의 배열이 제공되어 입출력을 빠르게 하는 기능이 제공되는 보조 스트림
- BufferedReader와 BufferedWriter는 문자용 입출력 보조 스트림
- 예시
package Chapter6.ch16;
import java.io.*;
public class FileCopyTest {
public static void main(String[] args) {
long milisecond = 0;
try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream("a.zip"));
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("copy.zip"))
) {
milisecond = System.currentTimeMillis();
int i;
while( (i=fis.read()) !=-1){
fos.write(i);
}
milisecond = System.currentTimeMillis() - milisecond;
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(milisecond+"소모되었습니다");
}
}
socket 문자 데이터를 빠르게 읽는 방법
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
DataInputStream과 DataOutputStream
- 자료가 메모리에 저장된 상태 그대로 읽거나 쓰는 스트림
- 2 byte이면 2 byte만큼, 8byte면 8 byte만큼 저장된 상태 그대로 read / write
- 쓴 순서대로 읽어야한다.
- DataInputStream 메서드
- DataOutputStream 메서드
- 예시 코드
package Chapter6.ch16;
import java.io.*;
public class DataStreamTest {
public static void main(String[] args) {
try(FileOutputStream fos = new FileOutputStream("data.txt");
DataOutputStream dos = new DataOutputStream(fos))
{
dos.writeByte(100);
dos.writeChar('A');
dos.writeInt(10);
dos.writeFloat(3.14f);
dos.writeUTF("Test");
}catch(IOException e) {
e.printStackTrace();
}
//파일 읽기
try(FileInputStream fis = new FileInputStream("data.txt");
DataInputStream dis = new DataInputStream(fis))
{
//write한 순서대로 읽기
System.out.println(dis.readByte());
System.out.println(dis.readChar());
System.out.println(dis.readInt());
System.out.println(dis.readFloat());
System.out.println(dis.readUTF());
}catch (IOException e) {
e.printStackTrace();
}
}
}
직렬화(serialization)
- 인스턴스의 상태를 그대로 파일 저장하거나 네트워크으로 전송하고 (serialization) 이를 다시 복원(deserializatio) 하는 방식
- 자바에서는 보조 스트림을 활용하여 직렬화를 제공함
- ObjectInputStream과 ObjectOutputStream
생성자 | 설명 |
ObjectInputStream(InputStream in) | InputStream을 생성자의 매개변수로 받아 ObjectInputStream을 생성합니다. |
ObjectOutputStream(OutputStream out) | OutputStream을 생성자의 매개변수로 받아 ObjectOutputStream을 생성합니다. |
Serializable 인터페이스
- 직렬화는 인스턴스의 내용이 외부로 유출되는 것이므로 프로그래머가 해당 객체에 대한 직렬화 의도를 표시해야 함
- 이때 쓰는 것이 Serializable 인터페이스이다.
- 구현 코드가 없는 marker interface
- transient : 직렬화 하지 않으려는 멤버 변수에 사용함 (Socket등 직렬화 할 수 없는 객체)
- 직렬화시 transient 변수는 기본값으로 입력된다.
예제 코드
package Chapter6.ch17;
import java.io.*;
class Person implements Serializable{
String name;
transient String job;
public Person(){}
public Person(String name, String job) {
this.name = name;
this.job = job;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
'}';
}
}
public class SerializationTest {
public static void main(String[] args) {
Person personLee = new Person("이순신", "대표이사");
Person personKim = new Person("김유신", "상무이사");
//파일에 작성하기
try (FileOutputStream fos = new FileOutputStream("serial.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos) ){
oos.writeObject(personLee);
oos.writeObject(personKim);
} catch (IOException e) {
System.out.println("e = " + e);
}
//파일 읽기
try (FileInputStream fis = new FileInputStream("serial.txt");
ObjectInputStream ois = new ObjectInputStream(fis) ){
Person pLee= (Person) ois.readObject();
Person pKim = (Person) ois.readObject();
System.out.println("pLee = " + pLee);
System.out.println("pKim = " + pKim);
} catch (IOException e) {
System.out.println("e = " + e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
출력 결과
Externalizable 인터페이스
- writerExternal()과 readExternal()메서드를 구현해야 함
- 프로그래머가 직접 객체를 읽고 쓰는 코드를 구현 할 수 있음
예제
class Person implements Externalizable{
String name;
transient String job;
public Person(){}
public Person(String name, String job) {
this.name = name;
this.job = job;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeObject(job);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
job = in.readUTF();
}
}
그 외 여러가지 입출력 클래스들
File 클래스
- 파일 개념을 추상화한 클래스
- 입출력 기능은 없고, 파일의 이름, 경로, 읽기 전용등의 속성을 알수 있음
- 이를 지원하는 여러 메서드들이 제공됨
public class FileTest {
public static void main(String[] args) throws IOException {
File file = new File("D:\\JAVA_LAB\\Chapter6\\newFile.txt");
file.createNewFile();
System.out.println(file.isFile());
System.out.println(file.isDirectory());
System.out.println(file.getName());
System.out.println(file.getAbsolutePath());
System.out.println(file.getPath());
System.out.println(file.canRead());
System.out.println(file.canWrite());
file.delete();
}
}
파일이 만들어지고 파일에 관한 정보를 알 수 있다.
RandomAccessFile 클래스
- 입출력 클래스 중 유일하게 파일에 대한 입력과 출력을 동시에 할 수 있는 클래스
- 파일 포인터가 있어서 읽고 쓰는 위치의 이동이 가능함
- 다양한 메서드가 제공됨
예제 코드
package Chapter6.ch18;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileTest {
public static void main(String[] args) throws IOException {
//파일 write 하기
RandomAccessFile rf = new RandomAccessFile("random.txt", "rw");
rf.writeInt(100);
System.out.println("rf.getFilePointer() = " + rf.getFilePointer());
rf.writeDouble(3.14);
System.out.println("rf.getFilePointer() = " + rf.getFilePointer());
rf.writeUTF("안녕하세요"); //문자당 3byte + null 2 byte
System.out.println("rf.getFilePointer() = " + rf.getFilePointer());
//파일 포인터 0으로 위치시키기
rf.seek(0);
//파일 읽기
int i = rf.readInt();
double d = rf.readDouble();
String str = rf.readUTF();
System.out.println("i = " + i);
System.out.println("d = " + d);
System.out.println(str);
}
}
출력 결과
데코레이터 패턴을 활용한 커피 머신 프로그램
Decorator Pattern
- 자바의 입출력 스트림은 decorator pattern 임
- 여러 decorator들을 활용하여 다양한 기능을 제공
- 상속 보다 유연한 구현 방식
- 데코레이터는 다른 데코레이터나 또는 컴포넌트를 포함해야 함
- 지속적인 기능의 추가와 제거가 용이함
- decorator와 component는 동일한 것이 아님
- 구성요소
- Component
- ConcreateComponent와 Decorator의 공통 기능을 정의
- client는 component를 통해 실제 object 사용
- ConcreteComponent: 기본 기능을 구현하는 클래스 - 커피원액, 실제 입출력 가능한 스트림
- Decorator
- Decorator의 공통 기능을 정의하는 추상 클래스
- Component로의 합성관계(Component를 멤버변수로 가진다.) - 기본기능에 개별기능을 추가하기때문에, 기본기능을 가져야한다.
- ConcreteDecorator : 개별적인 기능들 구현 - 모카시럽, 휘핑크림, 우유 - 보조 스트림
- (기반 스트림 클래스가 직접 읽고 쓸수 있음, 보조 스트림은 추가적인 기능 제공)
- Component
Decorator는 실제 object인 ConcreteComponent를 감쌀 수도 있고, Decorator를 감쌀수도 있다.
참고 : https://gmlwjd9405.github.io/2018/07/09/decorator-pattern.html
커피 만들기
Decorator Pattern을 활용하여 커피를 만들어 봅시다.
아메리카노
카페 라떼 = 아메리카노 + 우유
모카 커피 = 아메리카노 + 우유 + 모카시럽
크림 올라간 모카커피 = 아메리카노 + 우유 + 모카시럽 + whipping cream
커피는 컴포넌트고, 우유, 모카시럽, whipping cream은 모두 데코레이터임
Coffee - Component
package Chapter6.ch19;
public abstract class Coffee { //Component
public abstract void brewing();
}
KenyaAmericano - ConcreteComponent
package Chapter6.ch19;
public class KenyaAmericano extends Coffee{
@Override
public void brewing() {
System.out.print("Kenya Americano");
}
}
EtiopiaAmericano - ConcreteComponent
package Chapter6.ch19;
public class EtiopiaAmericano extends Coffee {
//ConcreteComponent
@Override
public void brewing() {
System.out.print("Etiopia Americano");
}
}
Decorator - 추상 클래스
package Chapter6.ch19;
public abstract class Decorator extends Coffee{
Coffee coffee;
public Decorator(Coffee coffee) { //파라미터 있는 생성자
this.coffee = coffee;
}
@Override
public void brewing() {
coffee.brewing();
}
}
Milk - ConcreteDecorator
package Chapter6.ch19;
public class Milk extends Decorator {
public Milk(Coffee coffee) {
super(coffee);
}
@Override
public void brewing() {
super.brewing();
System.out.print(" Adding Milk");
}
}
MochaSyrup - ConcreteDecorator
package Chapter6.ch19;
public class MochaSyrup extends Decorator {
public MochaSyrup(Coffee coffee) {
super(coffee);
}
@Override
public void brewing() {
super.brewing();
System.out.println(" Adding Mocha syrup");
}
}
WhippingCream - ConcreteDecorator
package Chapter6.ch19;
public class WhippingCream extends Decorator{
public WhippingCream(Coffee coffee) {
super(coffee);
}
@Override
public void brewing() {
super.brewing();
System.out.println(" Adding Whipping Cream");
}
}
CoffeeTest
package Chapter6.ch19;
public class CoffeeTest {
public static void main(String[] args) {
Coffee etiopiaCoffee = new EtiopiaAmericano();
etiopiaCoffee.brewing();
System.out.println();
//우유 추가하기
Coffee etiopiaLatte = new Milk(etiopiaCoffee);
etiopiaLatte.brewing();
System.out.println();
//모카 시럽 추가하기
Coffee etiopiaMocha = new MochaSyrup(new Milk(etiopiaCoffee));
etiopiaMocha.brewing();
System.out.println();
Coffee kenyaCoffeePlus = new WhippingCream(new Milk(new KenyaAmericano()));
kenyaCoffeePlus.brewing();
}
}
출력결과
'Spring > 카테캠 - TIL' 카테고리의 다른 글
TIL [0519-0520] Spring MVC 2 (2) | 2023.05.21 |
---|---|
TIL [0511- 0514] : Spring Basic + Spring MVC 1 (2) | 2023.05.14 |
TIL [0503-0504] : 객체지향 프로그래밍(내부클래스, 람다식, 스트림) (0) | 2023.05.05 |
TIL (0424 - 0429) : 객체지향 프로그래밍 (0) | 2023.04.29 |
TIL 0417 - 0423 (4) | 2023.04.23 |