[네이버클라우드캠프] - JAVA 개인 공부*

liho·2023년 6월 7일
0
  • App.java 파일코드와 관련된 여러 java 파일 중 중요 부분이라 생각되는 부분을 기록한다.
  • 매일 수업시간 코드 수정을 통해 코드를 변경한다.

메뉴얼 CRUD 구현

Function

updateMember 메소드

  public static void updateMember() {
            String memberNo = Prompt.inputString("번호?");
            for (int i=0; i<length; i++) {
                if (no[i] == Integer.parseInt(memberNo)) {
                    System.out.printf("이름(%s)", name[i]);
                    name[i] = Prompt.inputString("");
                    System.out.printf("이메일(%s)", email[i]);
                    email[i] = Prompt.inputString("");
                    System.out.printf("새암호(%s)");
                    password[i] = Prompt.inputString("");
                    gender[i] = inputGender(gender[i]);
                    return;
                }
            }
            System.out.println("해당 번호의 회원이 없습니다.");
        }
  • memberNo 변수에 사용자로부터 번호를 입력

  • for 루프를 사용하여 회원 정보 배열을 반복

  • no[i]와 입력받은 memberNo를 비교하여 일치하는 회원을 찾습니다.

  • 일치하는 회원이 존재하는 경우, 해당 회원의 정보를 업데이트

  • 사용자로부터 이름, 이메일, 새 암호를(을) 입력 name[i], email[i], password[i]에 할당

  • inputGender 함수를 사용하여 사용자로부터 성별을 입력받고, gender[i]에 할당

 public static void inputMember() {
        if (!available()) {
            System.out.println("더 이상 입력할 수 없다.");
            return;
        }
        name[length] = Prompt.inputString("이름?");
        email[length] = Prompt.inputString("이메일?");
        password[length] = Prompt.inputString("암호?");
        gender[length] = inputGender((char)0);

        no [length] = userId++;
        length++;
    }

정리 : 회원 정보 배열에서 번호를 기준으로 회원을 찾아 해당 회원의 정보를 업데이트하는 기능을 수행

삼항연산자

bool return = A < B ? True : False;

A < B 의 연산 결과가 참인 경우 True의 값 반환

A < B 의 연산 결과가 거짓인 경우 False의 값 반환

public static String toGenderString(char gender) {
            return gender == 'M' ? "남성" : "여성";
            }

정리 : gender 변수가 'M'이라면 남성을, 그렇지 않을 경우엔 여성을 출력

deleteMember 메소드

 public static void deleteMember() {
         int memberNo = Prompt.inputInt("번호?");

         int deletedIndex = indexOf(memberNo);
         if (deletedIndex == -1) {
             System.out.println("해당 번호의 회원이 없습니다.");
             return;
         }

         for (int i=deletedIndex; i<length -1; i++) {
             no[i] = no[i + 1];
             name[i] = name[i + 1];
             email[i] = email[i + 1];
             password[i] = password[i + 1];
             gender[i] = gender[i + 1];
         }
         no[length -1 ] = 0;
         name[length - 1] =null;
         email[length -1] = null;
         password[length -1] = null;
         gender[length -1] = (char) 0;

         length--;
        }
  • memberNo 변수에 사용자로부터 번호를 입력
  • indexOf 함수를 사용하여 memberNo와 일치하는 회원의 인덱스를 검색 후 deletedIndex 변수에 찾은 회원의 인덱스를 할당
   private static int indexOf(int memberNo) {
        for (int i=0; i<length; i++) {
            if (no[i] == memberNo) {
                return i;
            }
        }
        return -1;
}
  • deletedIndex가 -1인 경우, 즉 일치하는 회원이 없는 경우 "해당 번호의 회원이 없습니다."를 출력하고 메소드 실행을 종료.

  • for 루프를 사용하여 deletedIndex부터 length - 1까지 회원 정보를 앞으로 한 칸씩 당김. 이렇게 함으로써 삭제할 회원을 배열에서 제거

  • 마지막 배열 요소에는 빈 값으로 초기화합니다. (no, name, email, password, gender 배열의 마지막 요소를 각각 0, null, null, null, 0으로 설정)

  • length 변수를 1 감소시킵니다. 이로써 회원 배열의 길이가 1 감소

    정리 : 주어진 번호의 회원을 회원 배열에서 삭제하는 기능을 수행

Date

currentTimeMillis

  • Java 프로그래밍 언어에서 제공하는 메소드로, 현재 시스템의 시간을 밀리초 단위로 반환

  • 일반적으로 시간 측정, 타이밍 제어, 난수 생성 등 다양한 상황에서 유용하게 사용

  • 응용하여 게시판 목록에 응용하기

    for (int i = 0; i < this.length; i++) {
      Board board = this.boards[i];

      System.out.printf("%d, %s, %s, %d, %tY-%5$tm-%5$td %5$tH:%5$tM:%5$tS\n", 
      board.getNo(),
      board.getTitle(), 
      board.getWriter(),
      board.getViewCount(), 
      board.getCreatedDate());
    }
  • 출력 모습

getter/setter

  • 인스턴스 필드에 직접 접근하는 것을 막는 방법: private
  • 인스턴스 필드에 값을 저장하고 꺼내는 방법: setter/getter
  private int no;
  private String title;
  private String content;

  private String writer;
  private String password;
  private int viewCount;
  private long createdDate;

Instance field 와 method 사용하는 이유

  1. 객체의 상태와 동작을 캡슐화하여 모듈화하고 추상화하여 일관성을 유지합니다.
    객체의 상태는 인스턴스 필드에 저장되고, 객체의 동작은 메서드로 구현됩니다.
    이를 통해 객체의 데이터와 행위가 하나의 단위로 묶여 관리됩니다.
    캡슐화와 추상화를 통해 객체를 보다 이해하기 쉽고 관리하기
    편리하게 만듭니다.

  2. 코드의 구조와 유지보수가 용이해집니다.
    관련된 데이터와 동작을 하나의 객체로 묶어 코드의 구조를 명확하게 합니다.
    객체 단위로 코드를 작성하고 관리하므로 코드의 이해와 수정이 용이합니다.
    객체의 상태나 동작이 변경되어도 다른 객체에 미치는 영향을 최소화할 수 있습니다.

  3. 객체 간의 상호작용과 협업을 강화합니다.
    객체는 자체적으로 동작하며 다른 객체와 상호작용합니다.
    인스턴스 메서드를 통해 객체 간의 협업을 구현할 수 있습니다.
    객체들은 정의된 인터페이스를 통해 상호작용하므로 코드의 유연성과 재사용성이 향상됩니다.

  4. 추상화와 다형성을 지원하여 유연성과 확장성을 높입니다.객체의 상태와 동작을 캡슐화하고 인터페이스를 통해 추상화하면 유연한 설계가 가능합니다.
    다형성을 활용하여 여러 객체가 동일한 인터페이스를 구현하면 코드의 재사용성과 확장성이 높아집니다.

  • 인스턴스에 상관없이 공통으로 사용하는 필드라면 스태틱 필드로 선언한다!
  • 인스턴스 마다 별개로 관리해야 할 Data라면, 인스턴스 필드로 선언한다!

Prompt

public class Prompt {

  private static Scanner scanner;

  // default constructor 정의
  public Prompt() {
    this.scanner = new Scanner(System.in);
  }

  // 다른 입력 도구와 연결한다면?
  public Prompt(InputStream in) {
    this.scanner = new Scanner(in);
  }
  • default constructor 정의
  1. 매개변수를 가지지 않는 생성자
  2. 클래스 내에 생성자가 명시적으로 선언되지 않은 경우에는 컴파일러가 자동으로 기본 생성자를 추가
  3. 기본 생성자는 클래스의 인스턴스를 생성할 때 호출되며, 객체의 초기화를 담당
  4. 기본 생성자는 아무런 작업을 수행하지 않거나 기본적인 초기화를 수행 가능
  • 매개변수 없음: 기본 생성자는 매개변수를 가지지 않습니다. 즉, 인자를 전달할 필요가 없습니다.
  • 클래스 이름과 동일: 기본 생성자의 이름은 해당 클래스의 이름과 동일합니다.
  • 자동 생성: 생성자가 명시적으로 정의되지 않은 경우, 컴파일러가 자동으로 기본 생성자를 추가합니다.
  • 상속 가능: 기본 생성자는 상속을 통해 하위 클래스에서 사용될 수 있습니다. 하위 클래스에서 명시적으로 다른 생성자를 정의하지 않으면 자동으로 기본 생성자가 상속됩니다
  // 0614 수정
  public String inputString(String title, Object... args) {
    System.out.printf(title, args);
    return this.scanner.nextLine();
  }
  public int inputInt(String title, Object... args) {
    return Integer.parseInt(this.inputString(title, args));
  }
  public void close() {
    this.scanner.close();
  }
}
  • args는 가변 인자(Variable Arguments)를 상징

    가변 인자 = 메서드의 매개변수로 여러 개의 인자를 전달할 수 있도록 허용하는 기능

  • Object... args의 구문은 메서드에 임의의 개수의 Object 타입 인자를 전달할 수 있음을 의미

  • args는 배열로 취급되며, 메서드 내에서 배열 형태로 사용 가능

  • 주어진 코드에서 args는 title 문자열 내의 포맷 문자열에 대한 인자를 전달하는데 사용

-사용 화면

GRASP 패턴

  // GRASP 패턴 : Information Expert
  public static final char MALE = 'M';
  public static final char FEMALE = 'W';
= 주어진 코드에서 MALE과 FEMALE은 성별을 나타내는 상수
MALE은 'M', FEMALE은 'W'를 나타내는 문자 상수로 정의

GRASP 패턴은 어떤 작업을 수행하기 위한 정보와 관련 동작을 가지고 있는 객체에 책임을 할당하는 것을 목표

GRASP 패턴의 "Information Expert"는 정보를 가지고 있는 객체에 관련 동작을 할당함으로써 책임을 부여하는 것을 의미

성별 정보를 관리하고 필요한 동작을 수행할 수 있는 객체가 정보 전문가(Information Expert)로 볼 수 있습니다. 성별 정보를 처리하기 위해 별도의 객체를 생성할 필요 없이, MALE과 FEMALE 상수를 사용하여 성별을 나타낼 수 있습니다.

게시글 CRUD 추가

독서록 CRUD 추가

  • 의존객체 방식으로 운영
public class App {

  public static void main(String[] args) {

    Prompt prompt = new Prompt();
    MemberHandler memberHandler = new MemberHandler(prompt); // 의존객체 주입
    BoardHandler boardHandler = new BoardHandler(prompt);
    BoardHandler readHandler = new BoardHandler(prompt);
  	  ...

Interface

= 클래스 사용법을 특성 방식으로 강제할 수있는 문법(규칙)

Interface 생성 후 메소드 정리

Interface 목록을 다루는 코드를 별도의 class로 분리

  • MemberList / BoarderList
  public void add(Member m) {
    increase();
    this.members[this.length++] = m;
    //members 배열의 인덱스 this.length에 m 객체를 할당하고, this.length 값을 1 증가시키는 역할
    //이를 통해 add 메서드를 호출하여 새로운 게시물을 배열에 추가
  }
  private void increase() {
  배열 증가에서 확인!
  }

  public Member[] list() {
   // 리턴할 값을 담을 배열 생성
    Member[] arr = new Member[this.length];
  // 원본 배열에서 입력된 인스턴스 주소를 꺼내 새 배열에 담음

    for (int i = 0; i < this.length; i++) {
      arr[i] = this.members[i];
    }
 // 새 배열을 리턴
    return arr;
  }
  public Member get(int no) { 
  //주어진 회원 번호(no)에 해당하는 회원을 배열 members에서 찾아 반환하는 역할
    for (int i = 0; i < this.length; i++) {
      Member m = this.members[i]
 //배열 members의 현재 인덱스 i에 해당하는 요소를 m 변수에 할당
      if (m.getNo() == no) {
        return m;      }  }
 // m 객체의 회원 번호(getNo())가 주어진 회원 번호(no)와 일치하는지 확인, 일치하는 경우, 즉 해당 회원을 찾은 경우 m을 반환하고 메서드를 종료
    return null;   }
  public boolean delete(int no) {
    int deletedIndex = indexOf(no);
    //indexOf 메서드를 호출하여 주어진 게시물 번호(no)에 해당하는 게시물이 
    //배열 members에서의 인덱스를 deletedIndex 변수에 저장
    if (deletedIndex == -1) {
      return false;
    }
  //주어진 게시물 번호(no)에 해당하는 게시물이 배열에서 찾지 못한 경우, 
  //false를 반환하고 메서드를 종료
  
  for (int i = deletedIndex; i < this.length - 1; i++) {
      this.members[i] = this.members[i + 1];
 //deletedIndex부터 시작하여 this.length - 1까지 반복하면서 members 배열의 값을 
 //한 칸씩 앞으로 당김 과정에서 삭제하고자 하는 게시물의 
 // 인덱스 이후의 값들이 한 칸씩 앞으로 이동
 
    }
    this.members[--length] = null;
    return true;
    // 반복문을 모두 순회한 후에도 일치하는 회원을 찾지 못한 경우 null을 반환
  }
 

indexOf 메소드는 HandlerList와 동일하기에 생략!
MemberList와 BoardList는 동일하기에 하나만!

배열 증가하기

private void increase() {
    // 기존 배열보다 50% 더 큰 배열 생성
    if (this.length == boards.length) {
      Board[] arr = new Board[boards.length + (boards.length >> 1)];

      // 기존 배열의 값을 새 배열로 복사
      for (int i = 0; i < boards.length; i++) {
        arr[i] = boards[i];
      }
       // (boards.length >> 1)는 boards.length의 비트를 오른쪽으로 1만큼 이동시킨 값, 이는 boards.length를 2로 나눈결과와 동일
      boards = arr;
      // boards 레퍼런스가 새 배열을 지목하도록 유도
      System.out.println("배열을 늘렸음!");
    }
  }

  • 배열을 늘려주지 뭇하거나 지목하지 않으면 실행 실패값이 뜨는걸 확인

for-each 문

  • 형식(예시) for ( Object 타입으로 선언된 변수 obj : arr은 배열)
  • 향상된 for문은 배열이나 컬렉션과 같은 Iterable 객체를 순회하는 데 사용, 이를 통해 배열 또는 컬렉션의 각 요소에 순차적으로 접근가능
  • 반복문의 한 번의 반복마다 obj 변수는 배열의 다음 요소를 참조

상속

  • ArrayList
public class ArrayList {
  public boolean add(Object obj) {
      if (this.length == list.length) {
      increase();
    }
    this.list[this.length++] = obj;
    return true;
  }
  private void increase() {  }
  public Object[] list() {
    Object[] arr = new Object[this.length];

    for (int i = 0; i < this.length; i++) {
      arr[i] = this.list[i];
    }
    return arr;
  }
  public Object get(Object obj) {
  for (int i = 0; i < this.length; i++) {
      Object item = this.list[i];
      if (item.equals(obj)) {
        return item;
      }
    }
    return null;
  }
  public boolean delete(Object obj) {  }
  private int indexOf(Object obj) {  }
}

MemberList / BoarderList과 add, get 메소드를 제외하고 모든 코드가 equal 함을 확인 = ArrayList가 있기에 각 List의 존재가 불필요하다 = 코드 재사용 및 관리가 편리해진다!

  • ArrayList를 사용하기 위한 각 Handler의 변수에 수정전 코드

  • ArrayList를 사용하기 위한 각 Handler의 변수에 수정한 모습

통일된 코드로 정리할수 있었다.

  • Overloading = 오버로딩
    = 생성자 오버로딩한 코드

    같은 기능을 수행하는 메서드는 프로그래밍의 일관성을 부여하기 위해 같은 이름으로 생성

  • Overriding = 오버라이딩
    = 수퍼 클리래시스의메서드를서브클래스에서 자신의 역할과 목적에 맞게 재정의


equals 메소드를 오버라이딩

  • equals 메소드가 Object 클래스의 equals 메소드를 오버라이딩

  • 먼저, 매개변수로 전달받은 obj가 null인지 확인합니다. 만약 null이면 두 객체가 다르므로 false를 반환합니다.

  • 다음으로, this.getClass()와 obj.getClass()를 사용하여 두 객체의 클래스를 비교합니다. 클래스가 다르면 두 객체가 서로 다르다고 판단하고 false를 반환합니다.

  • 이렇게 오버라이딩된 equals 메소드는 객체의 클래스가 동일하고 null이 아닌 경우에만 true를 반환합니다.


  • Instance 변수를 ArrayList 생성 후 변경하지 않으면 상속 실패 코드를 맛볼수 있다.

LinkedList

  • ArrayList와 LinkList의 차이점
    사진 첨부하기
  • retrieve

코드 리뷰

public class LinkedList implements List {

  Node head;
  Node tail;
  int size;
//이 변수들은 연결 리스트의 시작 노드와 끝 노드, 그리고 리스트의 크기를 추적하는 데 사용
  • add
  @Override
  public boolean add(Object value) {
    // 1. 새 node 생성
    Node node = new Node();

    // 2. 새 node 값 저장
    node.value = value;

    if (head == null) { // null이 아니면 최소 한개의 노드는 존재!
      head = node;
    } else if (this.tail != null) {
      // 3. 리스트 마지막 노드에 새 노드 연결
      this.tail.next = node;
    }

    tail = node;
    size++;
    return true;
  } // add 끗
  

add 메서드는 새로운 요소를 연결 리스트에 추가 새로운 노드를 생성하고 값을 저장

  @Override
  public Object[] toArray() {
    Object[] arr = new Object[this.size];

    Node cursor = this.head;
    for (int i = 0; i < this.size; i++) {
      arr[i] = cursor.value;
      cursor = cursor.next; // 주소 입력받고 증가
    }

    return arr;
  }

toArray 메서드는 리스트의 크기만큼의 새로운 배열을 생성한 다음, 리스트를 순회하면서 각 노드의 값을 배열의 해당 인덱스에 할당

  • get
  @Override
  public Object get(int index) {
    if (!isValid(index)) {
      return null;
    }
    Node cursor = this.head;

    for (int i = 0; i < index; i++) {
      cursor = cursor.next;
    }
    return cursor.value;
  }

get 메서드는 인덱스가 유효한지 (리스트 범위 내에 있는지) 확인한 후, 해당 인덱스까지 리스트를 순회하고 해당 노드의 값을 반환

  • remove(boolean 형식)
  @Override
  public boolean remove(Object value) {
    Node cursor = this.head;
    Node prev = null;

    while (cursor != null) {
      if (cursor.value.equals(value)) {
		 //만약 head가 value를 가리킨다면
        if (prev == null) {
          // 삭제할 node가 시작이라면
          head = cursor.next; // cusor 다음 head 넘기기

          if (head == null) { // 만약 삭제할 node가 시작 node이자 끝 node일 경우
            tail = null; // cusor와 tail에 둘다 null 값 대입
          }
        } else if (cursor.next == null) { // 삭제할 node가 끝 node일 경우
          tail = prev; // tail의 이전 노드가 prev

          tail.next = null; 
          //다음 노드의 값은 null을 대입해야 삭제 확인

        } else {
          prev.next = cursor.next; // 중간 node라면, 다음 노드의 주소를 이전 node에 저장
        }
        size--;
        // garbage 객체를 초기화시켜서 garbage가 instance를 가리키지 않도록 제제
        // 최근 garbage collector는 자동으로 값을 지정하여 읽히지 않도록 함. ★
        cursor.next = null;
        cursor.value = null;

        return true;
      }
      // 현재 cursor가 가리키는 node를 prev에 보관
      prev = cursor;

      // 현재 cursor를 다음 node로 이동
      cursor = cursor.next;
    }
    return false;
  }
  • 연결 리스트에서 지정된 인덱스의 요소를 가져옴.
  • 인덱스가 유효한지 (리스트 범위 내에 있는지) 확인한 후, 해당 인덱스까지 리스트를 순회하고 해당 노드의 값을 반환
  • remove(int 형)
  @Override
  public Object remove(int index) {

    if (!isValid(index)) {
      return null;
    }
    
    // 삭제하려는 값이 있는 node까지 이동
    Node cursor = this.head;
    Node prev = null;
	
    for (int i = 0; i < index; i++) {
      prev = cursor; 
      // 다음 node로 이동하기 전에 현제 cursor가 가리키는 node를 prev에 보관
      cursor = cursor.next; // 커서를 다음 node로 이동
    }

    // 삭제할 값을 return 할수 있도록 보관 필수
    Object old = cursor.value;

    if (prev == null) {
      head = cursor.next;
	// head에 들어올 다음 node 주소가 null이라면 없다 인식함
      if (head == null) {
        tail = null;
      }
    } else if (cursor.next == null) {
      tail = prev;
      tail.next = null;
      //현재 node 위치에서 다음 node가 null이면 끝을 맺음
    } else {
      prev.next = cursor.next; 
      // 현재 cursor의 다음 노드를 현재 cursor의 이전 node와 연결
    }
    size--; // 사이즈 축소는 작업 종료를 의미
    cursor.next = null;
    cursor.value = null;
    return old; //삭제 과정을 거쳤으면 다시 return
  }
  
  • 연결 리스트에서 지정된 인덱스의 요소를 제거
  • 인덱스가 유효한지 확인한 후, 리스트를 순회하여 해당 인덱스까지 이동 후, 링크를 업데이트하여 노드를 리스트에서 제거 (제거한 요소의 값을 반환)
  • 중간 항목 삭제 시

    아래 코드에서 cursor.next = null,cursor.value = null은 삭제된 노드의 참조를 해제하기 위해 수행되는 작업으로 이는 가비지 컬렉터(Garbage Collector)가 더 이상 사용되지 않는 객체를 감지하고 메모리에서 해제하기 위해 참조를 제거하는 과정이다.
    그러나 여기서 주의해야 할 점은, 일반적으로 가비지 컬렉터는 자동으로 객체를 정리하므로 명시적으로 값을 null로 설정하여 참조를 제거하는 것은 필요하지 않을 수 있다.
    실제로 가비지 컬렉터는 더 이상 접근할 수 없는 객체를 스스로 인식하고, 해당 객체에 대한 메모리를 해제한다.
  @Override
  public int size() {
    return this.size;
  }
  private boolean isValid(int index) {
    return index >= 0 && index <= this.size;
  }

size 메소드는 리스트의 크기를 반환하고, isValid 메서드는 인덱스가 유효한지 확인하기 위한 보조 메서드

  • 중첩클래스문
  static class Node {
    Node next; // 다음 노드를 가리키는 참조 변수
    Object value;//  노드에 저장된 값(데이터)를 나타내는 변수
  } 

LinkedList 클래스 내에서 노드의 생성, 값 저장, 연결 정보 관리 등의 작업을 수행하는 데 사용되고, 이러한 중첩 클래스 구조를 사용하면 LinkedList 클래스 내부에서 노드 관련 작업을 캡슐화하고, 노드와 관련된 상세 구현을 외부로부터 감추는 장점을 지닌다.

Stack/ Queue

  • 두 Class는 모두 List interface의 규칙을 따른다.
public interface List {
  boolean add(Object value); 
  Object get(int index);
  Object[] toArray();
  Object remove(int index);
  boolean remove(Object value);
  int size();
}//. JVM은 인터페이스를 구현하는 클래스에서 이 메서드들을 구현할 때 자동으로 public 접근 제어자를 적용
  • Queue와 Stack을 사용하여 명령어 히스토리와 메뉴 관리 기능을 구현
public class MenuPrompt extends Prompt {

  private Queue commandHistory = new Queue();
  private Stack menus = new Stack();
  private Stack breadcrumbs = new Stack();

주요 멤버 변수:
commandHistory: 명령어 히스토리를 저장하는 Queue 객체
menus: 메뉴를 저장하는 Stack 객체
breadcrumbs: 경로를 저장하는 Stack 객체

  public void appendBreadcrumb(String title, String menu) {
    breadcrumbs.push(title);
    this.menus.push(menu);
  }

경로(title)와 메뉴(menu)를 받아서 breadcrumbs와 menus 스택에 추가

  public void removeBreadcrumb() {
    this.breadcrumbs.pop();
    this.menus.pop();
  }

breadcrumbs와 menus 스택에서 가장 최근에 추가된 항목을 제거

  public void printMenu() {
    System.out.println(menus.peek());
  }

현재 메뉴를 menus 스택의 맨 위에 있는 항목을 확인하여 출력

public void printCommandHistory() {
    for (int i = 0; i < commandHistory.size; i++) {
      System.out.println(commandHistory.get(i));
    }  }

명령어 히스토리를 commandHistory 큐의 내용을 순서대로 출력


  public String inputMenu() {
    StringBuilder titleBuilder = new StringBuilder(); // 예) 메인/ 회원>
    for (int i = 0; i < this.breadcrumbs.size(); i++) {
      if (titleBuilder.length() > 0) {
        titleBuilder.append("/");
      }
      titleBuilder.append(this.breadcrumbs.get(i));
    }
    titleBuilder.append("> ");

    String command = null;
    // this.inputString(titleBuilder.toString());

    while (true) {
      command = this.inputString(titleBuilder.toString());
      if (command.equals("history")) {
        this.printCommandHistory();
      } else if (command.equals("menu")) {
        this.printMenu();
      } else if (findMenuItem(command) == null) {
        System.out.println("메뉴 번호가 옳지 않습니다.");
      } else {
        break;
      }
    }

    // 사용자가 입력한 명령어를 history에 보관
    if (commandHistory.size() == 10) {
      // 명령어 목록은 최대 10개 유지
      // 10개 초과할 경우 맨앞 기록을 제거!
      commandHistory.poll();
    }

    String menuItem = findMenuItem(command);
    if (menuItem != null) {
      commandHistory.offer(titleBuilder.toString() + ":" + menuItem);
    } else {
      commandHistory.offer(command);
    }
    return command;
  }
  • 메뉴를 입력받는 메서드, 현재 경로를 표시하고 사용자로부터 메뉴 입력 저장
  • history를 입력하면 printCommandHistory() 메서드를 호출하여 명령어 히스토리를 출력하고, menu를 입력하면 printMenu() 메서드를 호출하여 현재 메뉴를 출력
  • findMenuItem() 메서드를 사용하여 입력된 메뉴 번호를 확인하고, 유효한 메뉴 번호인 경우 해당 메뉴를 반환
  private String findMenuItem(String command) {
    String menuTitle = null;

    // command에 해당하는 메뉴가 있다면 그 메뉴 이름을 return 하고
    // 없다면 원래 command 값을 return

    // 1) 현재 메뉴를 알아낸다. 메뉴 스택에서 맨 마지막에 입력한 메뉴 조회
    String menu = (String) menus.peek();

    // 2) 꺼낸 메뉴에서 해당 번호의 메뉴 검색
    String[] menuitems = menu.split("\n");
    for (String menuitem : menuitems) {
      if (menuitem.startsWith(command)) { // startsWith = true 출력
        return menuitem;
      }
    }
    return menuTitle;
  }
}
  • 주어진 명령어(command)에 해당하는 메뉴를 찾아서 반환
  • 현재 메뉴를 확인한 후, 해당 메뉴에서 입력된 명령어와 일치하는 메뉴를 찾아서 반환
  • 일치하는 메뉴를 찾지 못한 경우 원래 명령어를 반환

Stack

  • Stack의 구동원리
public class Stack extends LinkedList {

  public void push(Object value) {

    // 목록 맨 끝에 추가
    // 따로 생성 필요 x
    // super class에 있는 method를 이용하여 기능 구현

    this.add(value); // 상속받은 method = sub class에서 사용할 수 있는 super class의 method
  }
  public Object pop() {
    // pop 연산은 Stack의 가장 위에 있는 요소를 제거하고 반환하는 연산
    if (this.empty()) {
      return null;
    }
    return this.remove(this.size() - 1); // -1하는 이유는 마지막껄 꺼낸다
  }
  public Object peek() {
  //peek 연산은 Stack의 가장 위에 있는 요소를 반환하는 연산
    if (this.empty()) {
      return null;
    }
    return this.get(this.size() - 1); // -1하는 이유는 마지막껄 꺼낸다
  }
  public boolean empty() {
    return this.size() == 0;
  }
}

Queue

  • Queue의 구동원리

public class Queue extends LinkedList {
 //Queue는 LinkedList의 모든 기능을 상속받고, 추가적인 메서드를 구현할 수 있다.
  public void offer(Object value) { // Queue에 요소를 추가하는 역할
    this.add(value);
  }
  public Object poll() { //  Queue의 첫 번째 요소를 제거하고 반환하는 역할
    if (this.size == 0) { //  Queue가 비어있는지 확인
      return null;
    }
    return this.remove(0); //  LinkedList의 첫 번째 요소를 제거하고 반환
  }
}

Composite, Command, Observer 디자인 패턴

Composite

  • Menu
public class Menu {
  private String title;
  private ArrayList listners = new ArrayList();

  public Menu(String title) {
    this.title = title;
  }

//오버라이딩
  public Menu(String title, ActionListener listener) {
    this(title);
    this.addActionListener(listener);
  }


  public void addActionListener(ActionListener listner) {
    this.listners.add(listner);
  }

  public void removeActionListener(ActionListener listner) {
    this.listners.remove(listner);
  }

  public String getTitle() {
    return title;
  }

  public void execute(BreadcrumbPrompt prompt) {
    for (int i = 0; i < listners.size(); i++) {
      ActionListener listener = (ActionListener) listners.get(i);
      listener.service(prompt);
    }
  }
}
  • MenuGroup
package bitcamp.util;

public class MenuGroup extends Menu {

  ArrayList childs;

  public MenuGroup(String title) { 
    super(title);
    // Implicit super constructor Menu() is undefined. Must explicitly invoke another constructor 오류 작동되기에 필수 ★
    this.childs = new ArrayList();
  }

  public void add(Menu menu) {
    this.childs.add(menu);
  }

  @Override
  public void execute(BreadcrumbPrompt prompt) {
    prompt.appendBreadcrumb(this.getTitle());

    this.printMenu();
    while (true) {
      String input = prompt.inputMenu();
      if (input.equals("menu")) {
        this.printMenu();
        continue;
      }

      int menuNo = Integer.parseInt(input);
      if (menuNo < 0 || menuNo > childs.size()) {
        System.out.println("메뉴 번호가 옳지 않습니다!");
      } else if (menuNo == 0) {
        prompt.removeBreadcrumb();
        return;
      } else {
        Menu menu = (Menu) this.childs.get(menuNo - 1);
        menu.execute(prompt);
      }
    }

  }

  private void printMenu() {
    for (int i = 0; i < childs.size(); i++) {
      Menu menu = (Menu) childs.get(i);
      System.out.printf("%d. %s\n", i + 1, menu.getTitle());
    }
    System.out.println("0. 이전 또는 종료");
  }

}

Command

Observer 디자인 패턴

Data I/O stream API

  • Binary Stream API 사용(Data 저장)
Data 읽기Data 쓰기

프로젝트 적용

  • 각각 다양한 데이터 형식을 바이트 단위로 파일에 쓰기 위한 메서드들로 구성

  • 패키지를 따로 분리(io 패키지)하여 작성

  • 생성 방법은
    우클릭 > source >
    Generate Constructors from Superclass로 생성하면 수월

  • 각 코드엔 공통의 import가 존재

import java.io.FileNotFoundException;
//파일을 찾을 수 없는 경우 발생하는 예외
//파일이 존재하지 않거나 액세스할 수 없는 경우에 이 예외가 발생 
//주로 파일을 열거나 생성하는 작업에서 사용됩니다. 예외 처리를 통해 이 예외를 처리 가능

import java.io.IOException;
//입출력 작업 중 발생하는 일반적인 입출력 예외 
//입출력 작업을 수행하는 동안 발생할 수 있는 다양한 문제를 나타내는 예외
//파일 읽기, 쓰기, 닫기 등의 작업에서 예외가 발생가능하기에 예외 처리를 통해이 예외를 처리
  • 일반적으로 try-catch 블록을 사용하여 예외를 처리하거나
  • IOException을 선언하여 입출력 작업 중 발생하는 예외를 처리

DataInputStream

import java.io.FileInputStream;

public class DataInputStream extends FileInputStream {
  • DataInputStream 시작
  • 이 클래스는 FileInputStream 클래스를 상속
  public DataInputStream(String name) throws FileNotFoundException {
    super(name);  }
  • DataInputStream 클래스의 생성자인 DataInputStream(String name) 코드는 FileInputStream 클래스의 생성자인 FileInputStream(String name)을 호출
  • super(name)은 부모 클래스인 FileInputStream의 생성자를 호출하여 DataInputStream 객체를 초기화 (DataInputStream 객체가 생성될 때 FileInputStream 객체도 생성되고, FileInputStream의 생성자에는 name 매개변수가 전달)
    • 이렇게 함으로써 DataInputStream 클래스는 FileInputStream 클래스를 상속받아 그 기능을 이용하고 확장 가능, FileInputStream 클래스의 기능을 상속받으면서 DataInputStream 클래스에서 필요한 추가적인 기능을 구현 가능
  public short readShort() throws IOException {
    return (short) (this.read() << 8 | this.read());  }
  • 파일로부터 2바이트를 읽어와서 short 타입으로 변환하여 반환
  • this.read()를 호출하여 파일로부터 한 바이트를 읽은 후, 이를 8비트 왼쪽으로 시프트한 후, 다음 바이트를 읽어와서 이를 반환하는 값을 이어붙임
  public int readInt() throws IOException {
    return this.read() << 24 | this.read() << 16 | this.read() << 8 | this.read();  }
    																	┗ 마지막으로 마지막 바이트를 읽어와서 반환하는 값을 이어붙입니다.
  • 파일로부터 읽어온 4바이트(readInt) 값을 int 값으로 변환하여 반환하는 수식
  • 이를 통해 파일에서 읽은 바이트들을 int 형식으로 해석하고 사용 가능
  public long readLong() throws IOException {
    return (long) this.read() << 56 | (long) this.read() << 48 | (long) this.read() << 40
        | (long) this.read() << 32 | (long) this.read() << 24 | (long) this.read() << 16
        | (long) this.read() << 8 | this.read();  }

파일로부터 8바이트(readLong)를 읽어와서 long 타입으로 변환하여 반환하는 수식

  • 이를 통해 파일에서 읽은 바이트들을 long 형식으로 해석하고 사용 가능
  public char readChar() throws IOException {
    return (char) (this.read() << 8 | this.read());    }
  • 파일로부터 2바이트(readChar)를 읽어와서 char 타입으로 변환하여 반환
  • 이를 통해 파일에서 읽은 바이트들을 char 타입으로 해석하고 사용 가능
  public String readUTF() throws IOException {
    int length = this.read() << 8 | this.read();
    byte[] buf = new byte[length];
    this.read(buf);
    return new String(buf, "UTF-8");   }}
  • 파일로부터 UTF-8로 인코딩된 문자열을 읽어와, 먼저 length 변수에 2바이트를 읽어와서 저장
    -그 다음, length 값을 이용하여 적절한 길이의 바이트 배열 buf를 생성하고, this.read(buf)를 호출하여 파일로부터 buf에 데이터를 읽어와서 저장
  • 마지막으로, new String(buf, "UTF-8")를 사용하여 buf를 UTF-8로 디코딩하여 문자열을 생성하고 반환

DataOutputStream

이 클래스는 FileOutputStream 클래스를 상속

import java.io.FileOutputStream;

public class DataOutputStream extends FileOutputStream {
  • DataOutputStream 시작
  • 이 클래스는 FileOutputStream 클래스를 상속
public DataOutputStream(String name) throws FileNotFoundException {
    super(name);   }
  public void writeShort(int v) throws IOException {
    this.write(v >> 8);
    this.write(v);   }
  • 주어진 int 값을 바이트 단위로 나눠서 파일 작성
  • 먼저 v 값을 8비트 오른쪽으로 시프트하여 상위 8비트를 버린 다음, 그 다음 8비트를 파일 작성
  public void writeInt(int v) throws IOException {
    this.write(v >> 24);
    this.write(v >> 16);
    this.write(v >> 8);
    this.write(v);  }
  • 주어진 int 값을 바이트 단위로 나눠서 파일작성
  • v(int) 값을 24비트, 16비트, 8비트, 그리고 0비트로 시프트하여 각각 8비트 단위로 파일 작성
  public void writeLong(long v) throws IOException {
    this.write((int) (v >> 56));
    this.write((int) (v >> 48));
    this.write((int) (v >> 40));
    this.write((int) (v >> 32));
    this.write((int) (v >> 24));
    this.write((int) (v >> 16));
    this.write((int) (v >> 8));
    this.write((int) v);  }
  • 주어진 long 값을 바이트 단위로 나눠서 파일작성
  • v(long) 값을 시프트순서에 맞춰 각각 8비트 단위로 파일 작성
  public void writeChar(int v) throws IOException {
    this.write(v >> 8);
    this.write(v);  }

주어진 int 값을 바이트 단위로 나눠서 파일 작성

  • v 값을 8비트 오른쪽으로 시프트하여 상위 8비트를 버린 다음, 그 다음 8비트를 파일에 씁니다.
  public void writeUTF(String str) throws IOException {
    byte[] bytes = str.getBytes("UTF-8");
    this.write(bytes.length >> 8);
    this.write(bytes.length);
    this.write(bytes);	  }}
  • 문자열을 UTF-8로 인코딩한 후, 인코딩된 바이트 배열의 길이를 바이트 단위로 나눠서 파일작성
  • 먼저 str을 UTF-8로 인코딩한 바이트 배열의 길이를 8비트 오른쪽으로 시프트하여 상위 8비트를 버린 다음, 그 다음 8비트를 파일작성, 그리고 인코딩된 바이트 배열을 파일작성

사용 코드

  • APP
    출력할 값의 형식을 정해주는 코드들이기에 최초 출력의 집합인 APP에서 주로 사용

    BoardMember

    |

  • 각 메소드 별 실행 시 저장되는 data 확인!

기존의 클래스에 버퍼 기능을 추가

  • BufferedDataInputStream = DataInputStream + 버퍼 기능
  • BufferedDataOutputStream = DataOutputStream + 버퍼 기능
  • DataInput/OutputStream에서 복사해왔기에 공통된 부분 생략

BufferedDataInputStream

package bitcamp.io;

import java.io.IOException;
import java.io.InputStream;

public class BufferedInputStream extends InputStream {

 InputStream original;

 byte[] buf = new byte[8192];
 int size; // 배열에 저장되어 있는 바이트의 수
 int cursor; // 바이트 읽은 배열의 위치

 public BufferedInputStream(InputStream original) {
   this.original = original;
 }

  @Override
  public int read() throws IOException {
    if (size == -1) {
      return -1;
    }

    if (cursor == size) { // 바이트 배열에 저장되어 있는 데이터를 모두 읽었다면,
      if ((size = original.read(buf)) == -1) { // 다시 파일에서 바이트 배열로 데이터를 왕창 읽어 온다.
        return -1;
      }
      cursor = 0;
    }
    return buf[cursor++] & 0x000000ff;
  }

  @Override
  public void close() throws IOException {
    original.close();
  }
  public short readShort() throws IOException {
    return (short) (this.read() << 8 | this.read());
  }

BufferedDataOutputStream

package bitcamp.io;

import java.io.IOException;
import java.io.OutputStream;

public class BufferedOutputStream extends OutputStream {

 OutputStream original;

 byte[] buf = new byte[8192];
 int cursor;

 public BufferedOutputStream(OutputStream original) {
   this.original = original;
 }
  
  @Override
  public void write(int b) throws IOException {
    if (cursor == buf.length) { // 버퍼가 다차면
      original.write(buf); // 버퍼에 들어있는 데이터를 한 번에 출력.
      cursor = 0; // 다시 커서를 초기화
    }
    buf[cursor++] = (byte) b; // 버퍼에 빈 공간이 있다면 버퍼에 저장
  }
  

  @Override
  public void flush() throws IOException {
    original.write(buf, 0, cursor);
    cursor = 0;
  }

  @Override
  public void close() throws IOException {
    this.flush();
    original.close();
  }

입출력 기능 확장에 상속 대신 Decorator 패턴을 적용

App 클래스에서 분해 해봄

  • BufferedDataInputStream 분해
    BufferedInputStream, DataInputStream, FileInputStream
  private void loadMember() {
    try {
      FileInputStream in0 = new FileInputStream("member.data");
      DataInputStream in = new DataInputStream(in0); // Decorator 역할을 수행~!!
  private void loadBoard(String filename, List<Board> list) {
    try {
      FileInputStream in0 = new FileInputStream(filename);
      DataInputStream in = new DataInputStream(in0); // Decorator 역할을 수행~!!
  • BufferedDataOutputStream 분해
    BufferedOutputStream, DataOutputStream, FileOutputStream

      private void saveMember() {
       try {
         FileOutputStream out0 = new FileOutputStream("member.data");
         BufferedOutputStream out1 = new BufferedOutputStream(out0); // <= Decorator(장식품) 역할 수행
         DataOutputStream out = new DataOutputStream(out1); // <= Decorator(장식품) 역할 수행

```java
  private void saveBoard(String filename, List<Board> list) {
    try {
      FileOutputStream out0 = new FileOutputStream(filename);
      BufferedOutputStream out1 = new BufferedOutputStream(out0); // <= Decorator(장식품) 역할 수행
      DataOutputStream out = new DataOutputStream(out1); // <= Decorator(장식품) 역할 수행

   

0개의 댓글