2022-01-19(수) 10주차 3일

Jeongyun Heo·2022년 1월 19일
0

com.eomcs.io.ex08

상속의 한계를 극복 ⟹ 포함 관계를 이용하여 기능 확장

BufferedInputStream에 InputStream 객체를 포함한다.

그 서브클래스 객체 주소를 받겠다는 거
구체적으로 왜 안 적습니까?
모든 InputStream 가리킬 수 있다. 훨씬 유연하다.
InputStream 자리에 FileInputStream, ByteArrayInputStream, ... 가능

BufferedInputStream ◇----> InputStream
① read()
② read()
BufferedInputStream → InputStream
③ 데이터 읽기
InputStream → 데이터 저장소
데이터 저장소에서 데이터 읽음
BufferedInputStream은 버퍼를 갖고 있다.
④ 배열에 데이터 저장
버퍼(바이트 배열)에 저장
⑤ 배열에서 값을 꺼내 리턴한다.

상속관계가 아니다.

InputStream 자리에 뭐가 들어가느냐에 따라

InputStream 자리에 FileInputStream이 온다면 데이터 저장소는 파일이 된다.

InputStream 자리에 ByteArrayInputStream이 온다면 데이터 저장소는 바이트 배열이 된다.

InputStream에 상관없이
포함 관계 + 다형적 변수를 이용한 기능 확장 방법은 BufferedInputStream을 재사용하기 쉽게 만든다.

BufferedInputStream은 FileInputStream에도 붙일 수 있고

상속의 한계를 극복 ⟹ 포함 관계를 이용하여 기능 확장 2

DataInputStream ◇----> InputStream

Int 값을 읽으려면 4 바이트를 읽어야 됨

① readInt()
② read() 4번 호출
③ 데이터 읽기
④ int 메모리에 읽어온 데이터를 차례대로 담는다. (비트이동연산자 사용)
⑤ 리턴

DataInputStream을 다른 스트림 객체에 연결하여 재사용하기 쉽다.

com.eomcs.io.ex08.Exam0110.java

버퍼가 꽉 차면 FileOutputStream 통해서 출력

DataInputStream을 FileInputStream에 연결
생성자에 받아 놓은 객체에 일을 시킨다. 위임.

com.eomcs.io.ex08.Exam0230.java

ByteArrayInputStream을 넘긴다.

코드가 중복되지 않는다.
코드를 재활용할 수 있다.

230번까지 소화시키기. 그래야 310, 320 이해할 수 있음
흐름을 알아야 됨
왜 등장하게 됐는지

포함 관계의 한계

com.eomcs.io.ex08.Exam0310.java
FileOutputStream + DataOutputStream + 대량 데이터 출력

대량의 데이터를 쓰면 위임

com.eomcs.io.ex08.Exam0320.java

write 했던 순서대로 read 해야 됨
파일포맷

버퍼를 끼우고 싶음

com.eomcs.io.ex08.Exam0410.java
포함 관계로 기능 확장하기 - 현재 구조의 문제점

BufferedOutputStream은 OutputStream이 아니어서 DataOutputStream에 포함 못 함

BufferedOutputStream은 OutputStream의 서브클래스가 아니기 때문에
DataOutputStream 클래스에 연결할 수 없다.

BufferedOutputStream을 DataOutputStream에 연결할 수 없을까

자기네끼리 겹쳐서 붙이는 방법이 없을까

데코레이터 설계 기법 등장

해결책!
⟹ Decorator 설계 패턴

Decorator 설계 패턴

<<abstract>> OutputStream
↑ - FileOutputStream
↑ - ByteArrayOutputStream

기본 생성자가 없음.
FilterOutputStream이 데코레이터들의 수퍼클래스이다.

FilterOutputStream을 상속받는 클래스는 모두 데코레이터이다.

<<decorator>> FilterOutputStream
↑ - DataOutputStream
↑ - BufferedOutputStream

데코레이터도 FileOutputStream, ByteArrayOutputStream와 같은 조상의 자손이어야 한다.

FilterOutputStream 상속 받지 않았지만 생성자가 OutputStream을 요구하면 데코레이터

생성자에 OutputStream을 요구
데코레이터는 스스로 객체를 만들 수 없음
데코레이터는 자신의 수퍼클래스 객체를 포함한다.

com.eomcs.io.ex09

com.eomcs.io.ex09.step1.Exam0110.java

이제 BufferedInputStream도 InputStream의 자식이다. 그래서 DataInputStream에 연결할 수 있다.

데코레이터 패턴 적용하기

① 1단계

InputStream
  FileInputStream
  DataInputStream ← InputStream를 포함한다.
  BufferedInputStream ← InputStream를 포함한다.

공통점 : InputStream를 포함한다.

② 2단계
둘 사이의 공통점을 DecoratorInputStream로 수퍼클래스로 정의
generalization 수행

InputStream
  FileInputStream
  DecoratorInputStream ← InputStream를 포함한다.
    DataInputStream
    BufferedInputStream

구조를 바꾼 거. 속도는 똑같음.

이런 원리로 만든 게 java.io.* 패키지

java.io.* ⟹ 데코레이터 설계 기법이 적용되어 있다.

com.eomcs.io.ex10

이제 java.io.* 패키지 있는 거 씀

07.5 파일 API 활용: 데이터를 바이너리 형식으로 읽고 쓰기

FileWriter부터

1) 바이트 스트림 객체 준비
FileOutputStream out = new FileOutputStream("boards.data");

String java.sql.Date.toString()
yyyy-mm-dd

데코레이터 붙이는 순서가 있음
중간에 넣어준다.

  @RequestMapping("/board/save")
  public Object save() throws Exception {
    // 1) 바이트 스트림 객체 준비
    DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("boards.data")));

    Object[] arr = boardList.toArray();
    for (Object obj : arr) {
      Board board = (Board) obj;
      out.writeUTF(board.getTitle());
      out.writeUTF(board.getContent());
      out.writeInt(board.getViewCount());
      out.writeUTF(board.getCreatedDate().toString());
    }

    out.close(); // 데코레이터에서 close()하면 그 데코레이터와 연결된 모든 객체도 자동으로 close() 한다.
    return arr.length;
  }

http://localhost:8080/board/save

자바에서 제공해주는 writeUTF
처음 2 바이트가 나머지 바이트 개수

읽을 때도 바이너리 파일 읽자

더 이상 읽을 데이터가 없으면 오류가 뜬다

  public BoardController() throws Exception {
    System.out.println("BoardController() 호출됨!");

    DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("boards.data"))); // 데코레이터

    while (true) {
      try {        
        Board board = new Board();
        board.setTitle(in.readUTF());
        board.setContent(in.readUTF());
        board.setViewCount(in.readInt());
        board.setCreatedDate(Date.valueOf(in.readUTF()));

        boardList.add(board); // 파일에서 읽은 한 줄의 CSV 데이터로 객체를 만든 후 목록에 등록한다.
      } catch (Exception e) {
        break;
      }
    }

    in.close();
    // in.close(); // 데코레이터를 close() 하면 그 데코레이터와 연결된 객체들도 모두 close() 된다.
  }
  @RequestMapping("/book/save")
  public Object save() throws Exception {
    DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("books.data"))); // 따로 경로를 지정하지 않으면 파일은 프로젝트 폴더에 파일이 생성된다.

    Object[] arr = bookList.toArray();
    for (Object obj : arr) {
      Book book = (Book) obj;
      out.writeUTF(book.getTitle());
      out.writeUTF(book.getAuthor());
      out.writeUTF(book.getPress());
      out.writeInt(book.getPage());
      out.writeInt(book.getPrice());
      if (book.getReadDate() == null) {
        out.writeUTF("");
      } else {
        out.writeUTF(book.getReadDate().toString());
      }
      out.writeUTF(book.getFeed());
    }

    out.close();
    return arr.length;
  }
  public BookController() throws Exception {
    System.out.println("BookController() 호출됨!");

    DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("books.data"))); // 주 객체에 데코레이터 객체를 연결

    while (true) {
      try {
        Book book = new Book();
        book.setTitle(in.readUTF());
        book.setAuthor(in.readUTF());
        book.setPress(in.readUTF());
        book.setPage(in.readInt());
        book.setPrice(in.readInt());
        String date = in.readUTF();
        if (date.length() > 0) {
          book.setReadDate(Date.valueOf(date));
        }
        book.setFeed(in.readUTF());

        bookList.add(book);
      } catch (Exception e) {
        break;
      }
    }

    in.close();
  }

07.6 파일 API 활용: 데이터를 객체 단위로 읽고 쓰기

직렬화 도구를 사용하여 인스턴스를 읽고 쓴다.

DataOutputStream vs ObjectOutputStream

writeXxx() → 바이트 배열
'문자열' 및 'primitive type의 데이터'를 바이트로 변환

writeObject(Object obj) → 바이트 배열
인스턴스의 모든 필드를 자동으로 바이트 배열로 변환
개발자가 각 필드의 값을 일일이 바이트로 출력할 필요가 없다.
단, serialize 대상이 되는 객체는 java.io.Serializable 인터페이스를 구현해야 한다.

🔹 직렬화(serialization) : 자동으로 바이트 배열로 변환

객체 → serialization → 바이트 배열

데코레이터 ObjectOutputStream을 사용하여 인스턴스를 직접 출력한다.

ObjectOutputStream으로 교체

  @RequestMapping("/board/save")
  public Object save() throws Exception {
    // 1) 바이트 스트림 객체 준비
    ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("boards.ser")));

    Object[] arr = boardList.toArray();
    for (Object obj : arr) {
      out.writeObject(obj);
    }

    out.close(); // 데코레이터에서 close()하면 그 데코레이터와 연결된 모든 객체도 자동으로 close() 한다.
    return arr.length;
  }

이 인터페이스는 메서드가 없음
얘는 serialize 하는 걸 허락해

구현할 거 없음

public class Board implements java.io.Serializable {

http://localhost:8080/board/save

데코레이터 ObjectInputStream을 사용하여 직렬화 데이터를 읽는다.

readObject() : Object

리턴타입이 Object여서 형변환 해줘야 됨

Board board = (Board) in.readObject();

  public BoardController() throws Exception {
    System.out.println("BoardController() 호출됨!");

    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("boards.ser"))); // 데코레이터

    while (true) {
      try {        
        Board board = (Board) in.readObject();

        boardList.add(board);
      } catch (Exception e) {
        break;
      }
    }

    in.close();
  }

생성자 호출 없이 그냥 인스턴스가 만들어진다.

public class ArrayList implements java.io.Serializable {

ArrayList 객체 통째로 출력

  @RequestMapping("/board/save")
  public Object save() throws Exception {
    // 1) 바이트 스트림 객체 준비
    ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("boards.ser")));

    // 1) 다음과 같이 목록에 들어 있는 객체를 한 개씩 순차적으로 serialize 할 수도 있고,
    //    Object[] arr = boardList.toArray();
    //    for (Object obj : arr) {
    //      out.writeObject(obj);
    //    }

    // 2) 다음과 같이 목록 자체를 serialize 할 수도 있다.
    out.writeObject(boardList);
    out.close(); // 데코레이터에서 close()하면 그 데코레이터와 연결된 모든 객체도 자동으로 close() 한다.
    return boardList.size();
  }
  public BoardController() throws Exception {
    System.out.println("BoardController() 호출됨!");

    ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("boards.ser2"))); // 데코레이터

    // 1) 객체가 각각 따로 serialize 되었을 경우, 다음과 같이 객체 단위로 읽으면 되고,
    //    while (true) {
    //      try {        
    //        Board board = (Board) in.readObject();
    //
    //        boardList.add(board);
    //      } catch (Exception e) {
    //        break;
    //      }
    //    }

    // 2) 목록이 통째로 serialize 되었을 경우, 한 번에 목록을 읽으면 된다.
    boardList = (ArrayList) in.readObject(); // 단, 기존에 생성한 ArrayList 객체는 버린다.

    in.close();
  }

에러나면 바로 종료됨
시스템을 멈추지 말고 계속 실행시키는
예외 처리 문법

com.eomcs.io.ex11 공부하기

com.eomcs.io.ex11

00 0b (11) : 이름 바이트 수
00 00 00 1b (27) : 나이
01 (true) : 여자

DataOutputStream으로 출력했으면 읽을 때는 DataInputStream으로

저장한 순서대로 읽어야 됨

ObjectOutputStream 데코레이터는 객체 인스턴스 주소를 주면 인스턴스 필드를 추출해서 출력

직렬화 : 인스턴스의 필드 값을 바이트 배열로 만들어

Serialize와 Deserialize

04-입출력스트림 / 41 페이지

🔹 serialize (직렬화) = marshalling
인스턴스 -- write() --> ObjectOutputStream --> 바이트 배열

🔹 deserialize (역직렬화/복원) = unmarshalling
인스턴스 <-- read() -- 직렬화를 통해 출력된 바이트 배열
ObjectInputStream을 사용해야 한다

인스턴스 <-- read() -- ObjectInputStream <-- 바이트 배열

Serializable 인터페이스는 아무런 메서드가 정의되어 있지 않다.
단지 Serialize를 활성화시키는 기능을 수행한다.

직접 출력할 때는 클래스 정보 그런 거 없고 크기도 작음
writeObject(Object obj)로 출력하면 사이즈가 커짐

초보자는 실행속도, 메모리 많이 차지하는 거, 파일 크기 커지는 거 생각하지 말아라
첫 번째, 일단 돌아가게 만들기
두 번째, 자기 실력 내에서 조금 더 객체 지향적으로 리팩토링하기

writeObject(Object obj)로 출력한 건 readObject()로 읽어야 한다.

Serialize 데이터의 구성

04-입출력스트림 / 42 페이지

인스턴스 -- serialize --> 데이터  • 클래스명, 
                              • 인스턴스 필드 이름, 
                              • 인스턴스 필드 값, 
                              • 버전(serialVersionUID 스태틱 변수)

✓ 인스턴스를 저장할 때 버전 번호도 함께 저장한다.

상위 버전으로 저장한 걸 하위 버전에서 읽으려고 하면 에러남

최신 버전으로 작성한 파일을 교수님께 드리면 교수님이 구버전을 갖고 있으면 못 읽음

상위 버전으로 작성된 파일입니다. 계속 읽겠습니까? 데이터가 깨질 수 있습니다.

항상 파일을 저장할 때는 버전 번호를 남겨둬야 된다.

내가 읽을 수 있는 형식인지 없는 형식인지 판단을 내린다.

인스턴스 <-- deserialize -- 데이터

✓ 직렬화 데이터를 현재 갖고 있는 클래스를 이용하여 객체에 저장할 수 있는지 검사하기 위하여 버전 번호를 확인한다.

✓ 만약 직렬화 데이터에 있는 버전 번호와 역직렬화 할 때 사용할 클래스의 버전 번호가 다르다면 제대로 역직렬화를 할 수 없기 때문에 오류를 발생시킨다.

com.eomcs.io.ex11.b.Exam0230.java

수퍼클래스를 지정하지 않으면 extends java.lang.Object가 자동으로 붙는다.

오버라이딩할 때 접근범위를 확대할 순 있지만 좁힐 수는 없다
리턴타입을 리턴타입의 서브타입으로 바꿔도 된다.

    @Override
    public Score clone() throws CloneNotSupportedException {
      return (Score) super.clone();
    }

접근범위 변경
리턴타입 변경

Cloneable 인터페이스를 구현한 객체만 호출할 수 있다.

복제가 지원되지 않는 객체입니다.

Serializable처럼 구현한 이유만으로 OK
직렬화할 수 있
Cloneable

static class Score implements Cloneable

인스턴스 복제
똑같은 값을 갖는 인스턴스를 여러 개 만들 때 사용
똑같은 객체를 여러 개 복제해야 될 때가 있음

이 클래스는 복제할 수 있는 클래스입니다
이 클래스는 ~할 수 있는 클래스입니다
일종의 허락한다고 표시하는
이 기능을 활성화시켜라 의미
그래서 인터페이스 이름도 -able

메서드가 없는 인터페이스
구현할 가치가 없는데?
표시하기 위해서
이 클래스의 인스턴스는 복제할 수 있어

0개의 댓글