post
update

4주차의 내용은 자바의 함수형 프로그래밍, Lambda expression, Stream, Exception, Logging, I/O Stream, Serialization, Decorator Pattern 등을 배웠습니다.

내부 클래스(inner class)

클래스 내부에 선언한 클래스

인스턴스 내부 클래스

클래스 내부에 또 다른 클래스를 선언

  • private이 아닌 내부 클래스의 경우 다른 외부 클래스에서 생성 가능
  • 외부 클래스가 생성된 후 생성되기 때문에 내부에서 static 키워드 사용 불가
OutClass outClass = new OutClass();
OutClass.InClass inClass = outClass.new InClass();

정적 내부 클래스

클래스 내부에 static class InStaticClass{} 로 클래스 선언

  • static이기 때문에 외부 클래스 생성과 무관하게 사용 가능
  • 외부 클래스의 인스턴스 변수 사용 불가능

모든 외부 클래스는 기본적으로 static 클래스이다. 정적 내부 클래스도 static의 관점에서 생각하면 이해가 쉽다.

지역 내부 클래스

메서드 내부에서 정의되는 클래스

메서드 호출 종료와 함께 메서드의 지역변수의 유효성이 사라지기 때문에 local inner class 에서 클래스를 선언한 메서드의 지역변수들은 자동으로 상수로 변해 처리된다. 따라서 이런 지역변수들은 수정이 불가능하다.

Runnable getRunnable(int i){

    int num = 100;

    class MyRunnable implements Runnable{

        int localNum = 10;

        @Override
        public void run() {
            //num = 200;   //에러 남. 지역변수는 상수로 바뀜
            //i = 100;     //에러 남. 매개 변수 역시 지역변수처럼 상수로 바뀜
            localNum = 30;
            System.out.println("i =" + i);
            System.out.println("num = " +num);
            System.out.println("localNum = " +localNum);
        }
    }
    return new MyRunnable();
}

익명 내부 클래스

클래스의 이름을 생략하고 주로 하나의 인터페이스나 하나의 추상 클래스를 구현하여 반환

  • 별도의 클래스 명 없이 클래스 생성하여 바로 return

      Runnable getRunnable(int i){
        
              int num = 100;
        
              return new Runnable(){
        
                  int localNum = 10;
        
                  @Override
                  public void run() {
                      //num = 200;   //에러 남. 지역변수는 상수로 바뀜
                      //i = 100;     //에러 남. 매개 변수 역시 지역변수처럼 상수로 바뀜
                      localNum = 30;
                      System.out.println("i =" + i);
                      System.out.println("num = " +num);
                      System.out.println("localNum = " +localNum);
                  }
              };
          }
    
  • 필요한 override를 바로 수행하여 인스턴스 생성

      Runnable runnable = new Runnable() {
              @Override
              public void run() {
                    
              }
          };
    

Functional Programming(FP)

자바 8부터 함수형 프로그래밍 방식인 람다식을 지원

  • 함수의 구현과 호출 만으로 프로그래밍이 수행되는 방식
  • 이때 함수는 순수함수
    • 매개변수와 내부 변수 만을 사용
    • 외부 변수를 사용하지 않아 외부에 side effect가 발생하지 않음
    • 함수의 기능이 자료에 독립적임을 보장
    • 동일한 자료에 대해 동일한 결과를 보장
  • 입력받는 매개변수 이외에 다른 외부 자료를 사용하지 않아 여러 자료를 병렬 처리가 가능하다.

람다식(Lambda expression)

int add(int x, int y){

    return x+y;
}

// 람다식 표현
(int x, int y) -> {return x+y;}
// 매개변수가 하나일 시 자료형과 괄호 생략 가능
str -> {return str.length;}
// 한 문장일 시 return과 중괄호 생략가능
str -> str.length; // return 키워드는 생략되어 있는 것이기 때문에 붙여버리면 오류 발

함수형 인터페이스

람다식을 선언하기 위한 인터페이스

  • @FunctionalInterface annotation
  • 익명 함수와 매개 변수만으로 구현되므로 인터페이스는 단 하나의 메서드만을 선언해야함
    • 여러개 메서드 선언 시 에러
  • 인터페이스 선언

      @FunctionalInterface
      public interface StringConcat {
      	public void makeString(String s1, String s2);
      }
    
  • 사용

      StringConcat concat2 = (s, v)->System.out.println(s + "," + v );
      concat2.makeString(s1, s2);
    
  • 익명 내부 클래스와의 비교

      StringConcat concat3 = new StringConcat() {
        			
      	@Override
      	public void makeString(String s1, String s2) {
      		System.out.println( s1 + "," + s2 );
      	}
      };
        		
      concat3.makeString(s1, s2);
    
  • 함수형 인터페이스는 오직 하나의 메서드만을 가지고 있어서 익명 내부 클래스로 길게 적지 않고 람다식으로 짧게 생성 가능하다.

스트림(Stream)

  • 배열, 컬렉션을 대상으로 자료의 대상과 관계없이 동일한 연산을 수행
  • 한번 생성하고 사용한 스트림은 재사용 불가
    • 다른 연산을 수행하기 위해서는 스트림을 다시 생성해야 함
      • 따라서 filter, map등 중간 연산과 forEach 같은 값에 대한 처리인 최종 연산이 필요
  • 스트림 연산은 기존 자료를 변경하지 않음
    • 자료에 대한 스트림을 생성하면 스트림이 사용하는 메모리 공간은 별도로 생성
int[] arr = {1,2,3,4,5};
		
int sumVal = Arrays.stream(arr).sum();
long count = Arrays.stream(arr).count();

System.out.println(sumVal);
System.out.println(count);

중간 연산과 최종 연산

각 구현은 람다식을 활용

  • 예) 문자열 리스트에서 길이가 5이상인 요소만 출력

      sList.stream().filter(s->s.length() >= 5).forEach(s->System.out.println(s));
    
  • 활용

      public static void main(String[] args) {
      	List<String> sList = new ArrayList<String>();
      	sList.add("Tomas");
      	sList.add("Edward");
      	sList.add("Jack");
        	
      	Stream<String> stream = sList.stream();
      	stream.forEach(s->System.out.print(s + " "));
      	System.out.println();
        	
      	sList.stream().sorted().forEach(s->System.out.print(s+ " "));
      	sList.stream().map(s->s.length()).forEach(n->System.out.println(n));
      	sList.stream().filter(s->s.length() >= 5).forEach(s->System.out.println(s));
        	
      }
    
  • 중간 연산 - filter(), map(), sorted() 등
  • 최종 연산 - forEach(), count(), sum(), reduce() 등

reduce()

스트림에 대한 최종연산으로 직접 구현한 연산 적용 가능

T reduce(T identify, BinaryOperator<T> accumulator)
  • identify는 초기값
  • accumulator에 원하는 연산 넣는다.
  • 람다식 사용

      public class ReduceTest {
      	public static void main(String[] args) {
        
      		String[] greetings = {"안녕하세요~~~", "hello", "Good morning", "반갑습니다^^"};
        		
      		System.out.println(Arrays.stream(greetings).reduce("", (s1, s2)-> 
      		                          {if (s1.getBytes().length >= s2.getBytes().length) 
      				                                  return s1;
      		                          else return s2;})); 
        		
        		
      	}
      }
    
  • BianryOperator 클래스 구현(identify 필요 x)

      class CompareString implements BinaryOperator<String>{
        
      	@Override
      	public String apply(String s1, String s2) {
      		if (s1.getBytes().length >= s2.getBytes().length) return s1;
      		else return s2;
      	}
      }
        
      public class ReduceTest {
      	public static void main(String[] args) {
      		String[] greetings = {"안녕하세요~~~", "hello", "Good morning", "반갑습니다^^"};
        		
      		String str = Arrays.stream(greetings).reduce(new CompareString()).get();
      		System.out.println(str);
        		                          
      	}
      }
    

예외(Exception)와 에러(Error)

  • 프로그램의 오류
    • compile error : 코드 작성 중 발생하는 문법적 오류
    • runtime error: 프로그램이 실행 중 의도 하지 않은 동작을 하거나 프로그램이 중지되는 오류
  • 예외 처리의 중요성
    • 비정상 종료를 피하여 시스템이 원할이 실행되도록 함
    • 실행 오류가 발생한 경우 오류의 과정을 재현하는 것은 현실적으로 힘들다
      • 오류가 발생한 경우 log를 남겨서 추후 log 분석
  • 오류와 예외 클래스

    error.png

    • error: 가상 머신에서 발생, 처리할 수 없는 오류
      • 스택 메모리 오버플로우 등
    • Exception: 프로그램에서 제어 할 수 있는 오류
      • 파일이 존재하지 않거나, 네트워크, db 연결이 안되는 경우
  • 모든 예외 클래스의 최상위 클래스는 Exception 클래스(java.lang.Exception)

    exception2.png

예외 처리하기(try-catch)

try {
	fis = new FileInputStream("a.txt");
} catch (FileNotFoundException e) {
	System.out.println(e);
	return;
}finally{
	if(fis != null){
		try {
			fis.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
  • try문을 수행 시도
  • catch의 exception 발생 시 더 수행 하지 않고 catch문 수행
  • finally문은 성공/실패 상관없이 항상 수행
    • 파일을 닫거나 네트웍을 닫는 등의 리소스 해제 구현
    • catch나 try에 return문이 있어도 무조건 수행 됨

try-with-resources 문(자바 7이상)

리소스를 사용하는 경우 close()하지 않아도 자동으로 해제 되도록 함

  • 해당 리소스 클래스가 AutoCloseable 인터페이스를 구현해야 함
    • FileInputStream의 경우 AutoCLoseable 인터페이스 구현 하고 있다.
public class AutoCloseObj implements AutoCloseable{

	@Override
	public void close() throws Exception {
		System.out.println("리소스가 close() 되었습니다");
	}
}
AutoCloseObj obj = new AutoCloseObj();
try (obj){
	throw new Exception();
}catch(Exception e) {
	System.out.println("예외 부분 입니다");
}
  • 자바 9 부터 리소스는 try() 외부에서 선언하고 변수만을 try(obj)로 사용 가능

예외 처리 미루기 throws

try-catch 문 외에 exception을 처리하는 방법

public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException{
	FileInputStream fis = new FileInputStream(fileName); //FileNotFoundException 발생 가능
	Class c = Class.forName(className);  //ClassNotFoundException 발생 가능
	return c;
}
  • 해당 메서드를 사용하는 문장에서 예외를 처리할 수 있다.
  • throws 다른 곳으로 던진다(폭탄 돌리기)

사용자 정의 예외 클래스

  • 기존 예외 클래스중 가장 유사한 예외 클래스에서 상속 받아 사용자 정의 예외 클래스를 만든다.
  • 기본적으로 Exception 클래스를 상속하여 생성

로그 남기기 - java.util.logging.Logger

  • logging
    • 시스템 운영에 대한 기록
    • 오류가 발생 했을 때 그 오류에 대한 기록을 남겨 디버깅을 용이하게 한다
  • java.util.logging
    • 자바에서 기본적으로 제공되는 log package
    • jre/lib/logging.properties 파일을 편집하여 로그의 출력방식 로그 레벨을 변경 할 수 있음
    • 제공하는 로그 레벨: severe, warning, info, config, fine, finer, finest
    • Singleton으로 만든 Logger 예시
      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";
        	
      	private FileHandler logFile = null;
      	private FileHandler warningFile = null;
      	private FileHandler fineFile = null;
        
      	private MyLogger(){
        	
      			try {
      				logFile = new FileHandler(errorLog, true);
      				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);
      			fineFile.setLevel(Level.FINE);
      			warningFile.setLevel(Level.WARNING);
        			
      			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);
      	}
      }
    
    • 로그를 남기고 싶은 부분을 fine, warning 등의 함수를 사용하여 메시지를 남긴다.
    • exception handling 부분에서 warning을 사용해 exception 메시지를 남긴다.
    • 날짜, 시간, 발생한 클래스와 메서드, 타입과 메시지가 파일로 저장된다.

I/O Stream

자바는 다양한 입출력 장치에 독립적으로 일관성있는 입출력을 입출력 스트림을 통해 제공

입출력 스트림의 구분

  • 일반적인 경우

    iostream.png

    • 입력 스트림
      • FileInputStream, FileReader, BufferedInputStream, BufferedReader
    • 출력 스트림
      • FileOutputStream, FileWriter, BufferedOutputStream, BufferedWriter
  • 자료의 종류에 따라

    byte.png

    • 바이트 스트림
      • FileInputStream, FileOutputStream, BufferedInputStream, BufferedOutputStream
    • 문자 스트림
      • 바이트 단위로 처리 시 문자 깨질 가능성, 인코딩에 맞게 (2바이트 이상으로) 처리
      • FileReader, FileWriter, BufferedReader, BufferedWriter
      • Reader, Writer가 붙는다.
  • 기능에 따라

    second.png

    • 기반 스트림
      • 대상에 직접 자료를 읽고 쓰는 스트림
      • FileInputStream, FileOutputStream, FileReader, FileWriter
    • 보조 스트림
      • 직접 읽고 쓰는 기능 없이 추가적인 기능을 더해줌
      • InputStreamReader, OutputStreamWriter, BufferedInputStream, BufferedOutputStream

표준 I/O stream

표준 입 출력(키보드, 모니터-콘솔 창)를 다루는 스트림

public class System{ 
	public static PrintStream out; 
	public static InputStream in; 
	public static PrintStream err; 
}
  • System.out(표준 출력 스트림)
    • System.out.println("출력 메세지");
  • System.in(표준 입 스트림)
    • int d = System.in.read(); // 한 바이트 읽기
  • System.err(표준 출력 스트림)
    • System.err.println("에러 메세지");

바이트 단위 입출력 스트림

InputStream

  • 바이트 단위 입력 스트림 최상위 추상 클래스
  • 주요 하위 클래스 | Class| 설명 | | — | — | | FileInputStream | 파일에서 바이트 단위로 읽기 | | ByteArrayInputStream | byte 배열 메모리에서 바이트 단위로 읽기 | | FilterInputStream | 보조 스트림의 상위 클래스 |
  • 주요 메서드

    | Method | 설명 | | — | — | | int read() | 한 바이트의 자료를 읽기 | | int read(byte b[]) | b[] 크기의 자료를 b[]에 저장 | | int read(byte b[], int off, int len) | b[] 크기의 자료를 len 만큼 b[]의 off변수 위치부터 저장 | | void close() | 입력 스트림과 연결된 대상 리소스를 닫음 |

    • read()에서 더 이상 읽을 게 없을 때 -1을 return
    • read()의 리턴 값의 타입은 int형
    • 타입 변환이 필요

OutputStream

  • 바이트 단위 출력 스트림 최상위 추상 클래스
  • 주요 하위 클래스

    Class 설명
    FileOutputStream 파일에서 바이트 단위로 출력
    ByteArrayOutputStream byte 배열 메모리에서 바이트 단위로 출력
    FilterOutputStream 보조 스트림의 상위 클래스
  • 주요 메서드

    | Method | 설명 | | — | — | | int write() | 한 바이트의 자료를 출력 | | int write(byte b[]) | b[] 크기의 자료를 출력 | | int write(byte b[], int off, int len) | b[] 크기의 자료를 len 만큼 b[]의 off변수 위치부터 출력 | | void flush() | 출력 버퍼를 강제로 비워 자료를 출력 | | void close() | 출력 스트림과 연결된 대상 리소스를 닫음(출력 버퍼 리셋) |

    • close() 메서드 내부에서 flush()가 호출되므로 자동으로 출력 버퍼가 비워진다.

문자 단위 입출력 스트림

Reader

  • 문자 단위 입력 스트림 최상위 추상 클래스
  • 주요 하위 클래스

    Class 설명
    FileReader 파일에서 바이트 단위로 입력
    InputStreamReader byte 단위로 읽은 자료를 문자로 변환해주는 보조 스트림
    BufferedReader 배열을 제공하여 한꺼번에 읽을 수 있는 기능을 제공하는 보조 스트림

Writer

  • 문자 단위 출력 스트림 최상위 추상 클래스
  • 주요 하위 클래스

    Method 설명
    FileWriter 파일에서 바이트 단위로 출력
    OutputStreamWriter byte 단위의 자료를 문자로 변환해주는 보조 스트림
    BufferedWriter 배열을 제공하여 한꺼번에 쓸 수 있는 기능을 제공하는 보조 스트림

보조 스트림

실제 읽고 쓰는 스트림이 아닌 보조 기능을 제공하는 스트림

  • Decorator Pattern으로 구현되어 있다
    • 생성자의 매개변수로 또 다른 스트림을 가진다.
  • InputStreamReader / OutputStreamWriter
    • 바이트 단위를 문자로 변환해주는 보조 스트림
      try(InputStreamReader isr = new InputStreamReader(new FileInputStream("reader.txt"))){
      	int i;
      	while( (i = isr.read()) != -1){  //보조 스트림으로 읽습니다.
      		System.out.print((char)i);
      	}
      }catch(IOException e) {
      	e.printStackTrace();
      }
    
  • BufferedInputStream / BufferedOutputStream
    • 약 8k의 배열이 제공되어 입출력이 빠르게 하는 기능이 제공되는 보조 스트림
      try(FileInputStream fis = new FileInputStream("a.zip");
      		FileOutputStream fos = new FileOutputStream("copy.zip");
      		BufferedInputStream bis = new BufferedInputStream(fis);
      		BufferedOutputStream bos = new BufferedOutputStream(fos)){
        
      	millisecond = System.currentTimeMillis();
        	
      	int i;
      	while( ( i = bis.read()) != -1){
      		bos.write(i);
      	}
        	
      	millisecond = System.currentTimeMillis() - millisecond;
      }catch(IOException e) {
      	e.printStackTrace();
      }
      System.out.println("파일 복사 하는 데 " + millisecond + " milliseconds 소요되었습니다.");
    

그 외 추가적인 입출력 관련 클래스

  • RandomAccessFile 클래스
    • 스트림이 아님
    • 유일하게 입력과 출력을 동시에 할 수 있는 클래스
    • 파일 포인터가 존재하여 읽고 쓰는 위치의 이동이 가능하다.
  • File 클래스
    • 입출력 기능 x
    • 파일의 이름, 경로, 읽기 전용 등의 속성 저장하는 파일의 객체

Serialization - 직렬화

인스턴스의 상태를 그대로 내보내고(serialization) 이를 다시 복원(deserialization) 하는 방식

  • 클래스를 Serializableimplements하여 직렬화 가능하게 만들 수 있다.
  • ObjectInputStream / ObjectOutputStream(보조 스트림)을 통해 입출력이 가능하다.

직렬화는 인스턴스의 내용이 외부로 유출되는 것이므로 직렬화의 의도를 표시해야 한다

  • Serializable 인터페이스
    • 별개의 구현 코드 없이 implements 만 하면 된다.
      class AnyObjects implements Serializable{
      	...
      }
    
    • Socket 같이 직렬화가 불가능한 객체, 혹은 직렬화하지 않으려는 멤버 변수에는 transient 키워드를 추가한다. (복원할 때 default 값으로 복원된다)
  • Externalizable 인터페이스
    • 직접 객체를 읽고 쓰는 코드를 구현
      class AnyObjects implements Externalizable{
        	
      	String name;
      	...
        
      	@Override
      	public void writeExternal(ObjectOutput obj) throws IOException {
      		obj.writeUTF(name);
      	}
        
      	@Override
      	public void readExternal(ObjectInput obj) throws IOException, ClassNotFoundException {
      		name = obj.readUTF();
      	}
      }
    

Decorator Pattern

decorator가 감싸고 있는 component 기능을 전부 그대로 제공하는데, decorator 자신의 기능을 더 추가하여 제공한다.

자바의 입출력 스트림은 전부 decorator pattern으로 구현되어있다!

decorator.png

  • 상속보다 유연한 구현 방식
  • 지속적인 기능의 추가와 제거가 용이
  • 데코레이터는 다른 데코레이터나 컴포넌트를 포함해야 함
    • 기능이 실행될 때 먼저 감싸고 있는 component나 다른 decorator를 먼저 실행
    • 그 후, 자신의 추가적인 기능을 더 실행한다.

✏️여담

이번 주는 꽤 심화적인 내용으로 공부를 많이 했어요.

다음주부터는 스프링 나간다고 하니까 어서 배우고 싶어요!🤸🏻‍♂️

댓글남기기