본문 바로가기

국비 교육

2020.8.18일자 수업 : Stack, Queue, 클래스 관계

*프로그램 설계 언어 

  • internalization(I18N) - 프로그래밍 중에 라벨의 언어를 다양하게 하여 이용자에 적합한 언어를 제공
  • localization(L10N) - 이미 구현된 프로그램 안에서 라벨의 언어를 지역에 맞게 변경

 실습 - Queue 구현하기 

git/ bitcamp-20200713/ bitcamp-java-basic/ src/ main/java com.eomcs.algoritm.queue.MyQueue01~04.java

Queue 를 구현하기위해 기존에 작성한 MyLinkedList를 상속받고 값을 정의하는 offer(Object)와 제일 앞에 있는 값을 꺼내는 poll(), 그리고 제일 앞에 있는 값을 조회하는 peek()을 정의한다.

package com.eomcs.corelib.ex06;

import com.eomcs.algorithm.data_structure.linkedlist.MyLinkedList;

public class MyQueue extends MyLinkedList {
  
  public boolean offer(Object e) {
    return this.add(e);
  }
  
  public Object poll() {
    if (this.size() == 0) {
      return null;
    }
    return this.remove(0);
  }
  
  public Object peek() {
    if (this.size() == 0) {
      return null;
    }
    return this.get(0);
  }
}

 


 Queue 

/git / bitcamp-20200713/ bitcamp-java-basic/ src/ main/ java com.eomcs.corelib.ex06.Exam0110.java

First In First Out(선입선출) - 가장 먼저 들어간 것이 가장 먼저 나오는 컬렉션 클래스

  • offer(Object) : 컬렉션에 값을 넣는 메서드

  • poll() : 가장 먼저 넣은 값부터 꺼내는 메서드
    컬렉션에 아무 값도 없는 상태에서 호출하면 null이 리턴된다.

  • peek() : 가장 먼저 넣은 값을 조회하는 메서드
    컬렉션에 아무 값도 없는 상태에서 호출하면 null이 리턴된다.

package com.eomcs.corelib.ex06;

import java.util.concurrent.ArrayBlockingQueue;

public class Exam0110 {

  public static void main(String[] args) {
    String s1 = new String("aaa");
    String s2 = new String("bbb");
    String s3 = new String("ccc");
    String s4 = new String("ddd");
    String s5 = new String("eee");

    ArrayBlockingQueue queue = new ArrayBlockingQueue(10);
    queue.offer(s1); // aaa,
    queue.offer(s2); // aaa, bbb,
    queue.offer(s3); // aaa, bbb, ccc,
    print(queue);

    System.out.println("==>" + queue.poll()); // bbb, ccc,
    System.out.println("==>" + queue.poll()); // ccc,
    print(queue);

    queue.offer(s4); // ccc, ddd,
    queue.offer(s5); // ccc, ddd, eee,
    print(queue);

    System.out.println("------------------------");

    String value;
    while ((value = (String) queue.poll()) != null) {
      System.out.println(value);
    }
  }

  static void print(ArrayBlockingQueue queue) {
    Object[] arr = queue.toArray();
    for (int i = 0; i < arr.length; i++) {
      System.out.print(arr[i] + ", ");
    }
    System.out.println();
  }
}

*ArrayBlockingQueue

만약 동시에 둘 이상의 이용자들이 값을 꺼내는 메서드를 동시에 호출할 때 한 항목에 복제되어 꺼내지는 것을 막기 위해 한 스레드는 전 스레드 실행이 끝날때까지 기다리다가 호출할 수 있게 블로킹을 하는 기능이 있다. 

이와 같이 다양한 기능을 제공하는 queue가 존재한다.

 


 실습 - Stack 구현하기 

 

git/ bitcamp-20200713/ bitcamp-java-basic/ src/ main/ java com.eomcs.algorithm.data_structure.stack.MyStack01~04.java

Stack을 구현하기 위해 기존에 작성한 MyLinkedList를 상속받고, 스택에 값을 추가하는 push() 메서드를 정의한다. 스택에서 제일 마지막에 추가한 값을 꺼내는 pop() 메서드와 스택에서 제일 마지막에 입력한 값을 조회하는 peek() 메서드를 정의한다. 

package com.eomcs.corelib.ex05;

import java.util.EmptyStackException;
import com.eomcs.algorithm.data_structure.linkedlist.MyLinkedList;

public class MyStack extends MyLinkedList {
  
  public Object push(Object e) {
    this.add(e);
    return e;
  }
  
  public Object pop() {
    if (this.size() == 0) {
      throw new EmptyStackException();
    }
    return this.remove(this.size() - 1);
  }
  
  public Object peek() {
    if (this.size() == 0) {
      throw new EmptyStackException();
    }
    return this.get(this.size() - 1);
  }
}

 


 Stack 

/git / bitcamp-20200713/ bitcamp-java-basic/ src/ main/ java com.eomcs.corelib.ex05.Exam0110.java

Last In First Out (후입선출) - 가장 나중에 들어간 것이 가장 먼저 꺼내지는 컬렉션 클래스. 

  • push(Object) : 컬렉션에 값을 넣는 메서드

  • pop() : 가장 마지막에 추가한 값을 꺼내는 메서드
    컬렉션에 아무 값도 없는 상태에서 메서드를 호출하면 EmptyStackException 예외를 띄운다.

  • peek() : 가장 마지막에 추가한 값을 조회하는 메서드
    컬렉션에 아무 값도 없는 상태에서 메서드를 호출하면 EmptyStackException 예외를 띄운다

// Stack 구현과 사용
package com.eomcs.corelib.ex05;

import java.util.Stack;

public class Exam0110 {

  public static void main(String[] args) {
    String s1 = new String("aaa");
    String s2 = new String("bbb");
    String s3 = new String("ccc");
    String s4 = new String("ddd");
    String s5 = new String("eee");

    Stack stack = new Stack();
    stack.push(s1); // aaa
    stack.push(s2); // aaa, bbb
    stack.push(s3); // aaa, bbb, ccc
    print(stack);

    System.out.println("==>" + stack.pop()); // ccc
    System.out.println("==>" + stack.pop()); // bbb
    print(stack); // aaa

    stack.push(s4); // aaa, ddd
    stack.push(s5); // aaa, ddd, eee
    print(stack);

    System.out.println("-----------------");

    String value;
    try {
      while (true) {
        System.out.println(stack.pop());
      }
    } catch (Exception e) {
      System.out.println("스택에서 더이상 꺼낼 데이터가 없습니다.");
    }
  }

  static void print(Stack list) {
    for (int i = 0; i < list.size(); i++) {
      System.out.print(list.get(i) + ", ");
    }
    System.out.println();
  }
}

try{} catch{} 예외 처리 : 실행중에 예외가 발생하더라도 JVM이 실행을 멈추지 않도록 예외를 처리하는 기능

 

 


 HashSet 

/git / bitcamp-20200713/ bitcamp-java-basic/ src/ main/ java com.eomcs.corelib.ex07.Exam0110~0340.java

HashSet에서 Set은 집합을 의미하며 집합에서는 중복값을 허용하지 않는다.
중복여부를 판단하는 기준은 hashCode() 값의 동일 여부와 equals()의 동일 여부이다.

값을 순서대로 저장하지 않는다. 각 객체의 hashCode 값을 기준으로 정렬하므로 순서대로 저장되지 않는다. 따라서 index를 사용하여 꺼낼 수도 없다.

// java.util.HashSet 클래스 사용 - 중복저장 불가 테스트
package com.eomcs.corelib.ex07;

import java.util.HashSet;

public class Exam0110 {
  public static void main(String[] args) {
    String v1 = new String("aaa");
    String v2 = new String("bbb");
    String v3 = new String("ccc");
    String v4 = new String("ddd");
    String v5 = new String("ccc");

    HashSet set = new HashSet();
    set.add(v1);
    set.add(v2);
    set.add(v3);
    set.add(v4);
    set.add(v5);
   
    System.out.println(v3 == v5); // false
    System.out.println(v3.equals(v5)); // true
    System.out.println(v3.hashCode() + "," + v5.hashCode());
    System.out.println("---------------------");

    print(set);
    // aaa,ccc,bbb,ddd,
  }

  static void print(HashSet set) {
    Object[] values = set.toArray();
    for (Object value : values) {
      System.out.print(value + ", ");
    }
    System.out.println();
  }
}

따라서 v3와 v5는 다른 인스턴스 이지만 String 클래스는 같은 문자열을 가지면 같은 해시코드 값을 리턴하도록 hashCode()를 오버라이딩했고, 같은 문자열이면 true가 나오도록 equals()를 오버라이딩했기 때문에 문자열이 같은 두 인스턴스는 해시 셋에서 같은 값으로 취급된다. 따라서 v5는 저장되지 않는다.

또한 순서가 없이 저장되므로 toArray()를 통해 배열로 만들어도 저장한 순서대로 나열되어 리턴되지 않는다.

 

* HashSet의 저장 원리

HashSet은 배열을 여러개 갖고 있다. 입력한 객체의 해시코드 값을 배열 개수(ex- 4개)로 나누고 그에 따른 나머지 값에 해당하는 위치의 배열(ex - 0,1,2,3)에 순서대로 저장한다. add로 중복된 값을 저장하면 해당 해시코드에서 나눈 나머지 위치 배열에서 같은 값을 찾는다. 같은 값이 있다면 해당 배열에 저장하지 않는다. 배열을 여러개 저장하기 때문에 메모리를 더 많이 차지한다.

 

HashSet 과 ArrayList 의 차이

ArrayList는 같은 값을 가진 인스턴스들의 중복을 허용할 뿐만 아니라 완전히 같은 인스턴스의 중복도 허용한다.

 

HashSet에서 값을 꺼내는 방법

지정한 순서대로 값을 꺼낼 수 없기 때문에 get()과 같은 메서드를 호출할 수 없다.

따라서 다음과 같은 방법으로 값을 꺼내야한다.

  • toArray()를 통해 값들을 배열로 받아 꺼낸다. 
  • 값을 꺼내주는 Iterator객체의 도움을 받는다. 
// java.util.HashSet 클래스 사용 - 값을 꺼내는 방법
package com.eomcs.corelib.ex07;

import java.util.HashSet;
import java.util.Iterator;

public class Exam0210 {
  public static void main(String[] args) {
    String v1 = new String("aaa");
    String v2 = new String("bbb");
    String v3 = new String("ccc");
    String v4 = new String("ddd");
    String v5 = new String("ccc");

    HashSet set = new HashSet();
    set.add(v1);
    set.add(v2);
    set.add(v3);
    set.add(v4);
    set.add(v5);

    // 저장한 순서대로 꺼낼 수 없기 때문에 index를 이용하여 값을 꺼낼 수 없다.
    // set.get(0); //<== 이런 메서드가 없다.

    // 값을 꺼내는 방법
    // 1) HashSet에 들어있는 값을 배열로 받아 사용한다.
    Object[] values = set.toArray();
    for (Object value : values) {
      System.out.print(value + ", ");
    }
    System.out.println();

    // 2) 창고에서 값을 꺼내주는 객체의 도움을 받는다.
    // => HashSet에서 값을 꺼내는 객체를 얻는다.
    Iterator 컬렉션에서값을꺼내주는객체 = set.iterator();

    // => 값을 꺼내주는 객체를 통해 값을 꺼낸다.
    while (컬렉션에서값을꺼내주는객체.hasNext()) {
      // => 꺼낼 데이터가 있다면 값을 꺼내달라고 명령한다.
      System.out.print(컬렉션에서값을꺼내주는객체.next() + ", ");
    }
    System.out.println();
  }
}

Iterator

Iterator 는 다양한 형태의 컬렉션에 접근하여 값를 꺼낼 수 있는 객체이다. 즉 Iterator는 컬렉션에서 값을 꺼내는 기능을 객체화한 결과물이다. 다음과 같은 메서드를 호출함으로써 Iterator에게 특정역할을 수행시킬 수 있다.

  • hasNext() : 꺼낼 수 있는 값이 있는지 확인하기
  • next() : 컬렉션 종류에 적절한 방식으로 값을 꺼내기
    예) ArrayList => get(), Stack => pop(), Queue => poll()

이 객체 하나로 ArrayList, Stack, Queue 등의 다양한 형태의 컬렉션 객체에서 값을 꺼내는 방법을 통일시킬 수 있다. 각각 컬렉션 클래스에서 제공하고 있는 메서드들을 통해 조회하는 방식에 비해 훨씬 일관적이다. 이러한 형태의 Iterator 패턴은 핵심 설계 기법 중 하나이다. 이 밖에도 자바에서는 핵심 설계 기법들을 적절하게 적용시켜왔다.

// Iterator 의 사용
package com.eomcs.corelib.ex07;

import java.util.ArrayDeque;
import java.util.Iterator;

public class Exam0231 {
  public static void main(String[] args) {
    String s1 = new String("aaa");
    String s2 = new String("bbb");
    String s3 = new String("ccc");
    String s4 = new String("ddd");
    String s5 = new String("eee");

    ArrayDeque stack = new ArrayDeque();
    stack.push(s1);
    stack.push(s2);
    stack.push(s3);
    stack.push(s4);
    stack.push(s5);

    Iterator 컬렉션에서값을꺼내주는객체 = stack.iterator();
    while (컬렉션에서값을꺼내주는객체.hasNext()) {
      System.out.print(컬렉션에서값을꺼내주는객체.next() + ", ");
    }
    System.out.println();
    
    Iterator 컬렉션에서값을꺼내주는객체 = stack.iterator();
    while (컬렉션에서값을꺼내주는객체.hasNext()) {
      System.out.print(컬렉션에서값을꺼내주는객체.next() + ", ");
    }
    System.out.println();
  }
}

ArrayDeque에서 De는 반대라는 뜻으로 Deque는 queue가 아닌 stack의 한 종류이다. 위에서 보는 것처럼 Iterator가 next()를 통해 스택에서 값을 꺼내는 경우에는 pop()과 같이 가장 마지막 값을 꺼낸다.

 

핵심 설계 기법(design pattern)

저명한 건축학자와 소프트웨어 학자들 네명(GoF)이 프로그래밍의 효율성을 높이기 위해 23개의 다양한 설계 기법을 만들었다. 그 중 가장 대표적인 것들은 Method Factory, Singleton, Flyweight, Facade, Decorator(클래스를 상속받을때 필요한 기능만 받는 기능 - 탈부착식), Proxy(중간에서 대행해주는 기법), Command(기존 코드에 손대지 않고 클래스에 명령을 여러개 처리해주는 기법), Iterator 등이 있다.

 

HashSet와 사용자 정의 데이터 타입

Hashset에서는 넣는 값의 타입 클래스에서 equals()와 hashCode() 를 오버라이딩하지 않으면 인스턴스가 다르기만 해도 모두 다르다는 결과를 내놓는다. 따라서 사용자 정의 데이터 타입을 오버라이딩하지 않고 hashSet에 중복된 값을 넣으면 같은 값으로 취급하지 않아 중복 저장된다. 따라서 사용자 정의 데이터 타입의 중복 저장을 피하려면 해당 클래스에서 equals()와 hashCode()의 오버라이딩은 필수이다.

 


 HashMap 

/git / bitcamp-20200713/ bitcamp-java-basic/ src/ main/ java com.eomcs.corelib.ex08.Exam0110~0331.java

HashMap은 값과 key라는 객체를 쌍으로 저장하여 key를 통해 값을 빠르게 꺼낼 수 있는 컬렉션이다. 어떤 객체든 key가 될 수 있으며 값을 저장할 때  key의 해시코드 값을 기준으로 위치를 지정한다. 따라서 key는 중복되지 않지만 값은 중복될 수 있다. 만약 존재하지 않는 key를 저장하면 null을 리턴한다. 또한 이미 값이 저장된 키에 다른 값을 저장하면 기존 값을 덮어쓴다. key는 HashSet과 마찬가지로 중복 여부를 equals() 와 hashCode() 의 결과로 따진다.

 

key로 String 객체를 많이 사용하지만, Wrapper 클래스도 사용할 수 있다. 오토박싱을 이용하면 primitive data 타입을 집어넣어도

더 간편하게 랩퍼 클래스를 사용할 수 있다.

 

만약 key를 직접 데이터 타입으로 정의한다면 equals()와 hashCode() 메서드를 꼭 오버라이딩해야한다. 하지 않으면 같은 값을 가진 다른 key 인스턴스를 같은 key로 취급하지 않아 원하는 값을 얻을 수 없다. 

package com.eomcs.corelib.ex08;

import java.util.HashMap;

public class Exam0120 {

  static class MyKey {
    String major;
    int no;

    public MyKey(String major, int no) {
      this.major = major;
      this.no = no;
    }

    @Override
    public String toString() {
      return "MyKey [major=" + major + ", no=" + no + "]";
    }
  }

  public static void main(String[] args) {
    Member v1 = new Member("홍길동", 20);
    Member v2 = new Member("임꺽정", 30);
    Member v3 = new Member("유관순", 16);
    Member v4 = new Member("안중근", 30);
    Member v5 = new Member("윤봉길", 25);

    MyKey k1 = new MyKey("컴공", 1);
    MyKey k2 = new MyKey("컴공", 2);
    MyKey k3 = new MyKey("컴공", 3);
    MyKey k4 = new MyKey("컴공", 4);
    MyKey k5 = new MyKey("컴공", 5);

    HashMap map = new HashMap();

    map.put(k1, v1);
    map.put(k2, v2);
    map.put(k3, v3);
    map.put(k4, v4);
    map.put(k5, v5);

    System.out.println(map.get(k1));
    System.out.println(map.get(k2));
    System.out.println(map.get(k3));
    System.out.println(map.get(k4));
    System.out.println(map.get(k5));

    MyKey k6 = new MyKey("컴공", 3); // k3와 같은 값을 갖는다.

    System.out.println(k3 == k6);
    System.out.printf("equals(): %b\n", k3.equals(k6));
    System.out.printf("hashCode(): %d, %d\n", k3.hashCode(), k6.hashCode());
    System.out.println("-----------------------------------");


    System.out.println(map.get(k6)); // null
  }
}


 

HashMap에서 값을 꺼내는 방법

1. HashMap 컬렉션에서 추출한 key 목록(집합)을 통해 key 객체들 하나하나를 조회하여 그에 해당하는 값을 꺼낸다.

  • keySet() 메서드를 이용하여 컬렉션에서 key Set 추출하기
    단, keySet()으로 만들어진 객체 Set은 key 객체들을 꺼내는 그 순간에 목록을 만들어 리턴한다. 즉 keySet()을 호출한 그 순간에 목록이 만들어지는 것이 아니라는 것이다.
  • java.util.Set keys = map.keySet();
  • key Set을 통해 key 객체를 하나씩 꺼내기 (= HashSet 과 동일한 방법)
    • Iterator의 next() 메서드 이용하여 꺼내기
      단, key Set을 대상으로 Iterator을 생성하고나서 해시맵의 key 목록에 변경 사항이 생기면 기존의 Iterator는 무효한 객체가 된다. 무효해진 Iterator 객체를 사용하면 실행 오류가 발생한다. -> java.util.ConcurrentModificationException
    • toArray() 메서드를 통해 배열로 받아 확인하기 
    • for(:) (java.util.Collection 규칙을 따라 만든 클래스라면 굳이 배열과 같은 방식으로 for 문 사용 가능)  사용하기
  • (HashMap 객체).get(key 객체)를 통해 특정 key에 해당하는 값 확인하기

 

2. entrySet()을 통해서 key와 그에 상응하는 value를 한쌍으로 묶은 형태의 Set 얻을 수 있다. 이것으로 Key만 꺼내거나 value만 꺼내거나 혹은 두개가 묶인 것을 꺼낼 수 있다.

// java.util.HashMap - key/value 한쌍으로 묶어 꺼내기 
package com.eomcs.corelib.ex08;

import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;

public class Exam0220 {
  public static void main(String[] args) {
    Member v1 = new Member("홍길동", 20);
    Member v2 = new Member("임꺽정", 30);
    Member v3 = new Member("유관순", 16);
    Member v4 = new Member("안중근", 20);
    Member v5 = new Member("윤봉길", 25);

    HashMap map = new HashMap();
    map.put("s01", v1);
    map.put("s02", v2);
    map.put("s03", v3);
    map.put("s04", v4);
    map.put("s05", v5);

    Set entrySet = map.entrySet(); // key/value가 한쌍으로 묶여있는 객체들이 들어있다.

    for (Object obj : entrySet) {
      // Set 컬렉션에 들어있는 개체는 원래 Entry 객체이다.
      // Entry 객체에서 key와 값을 꺼내려면 
      // 원래의 타입으로 형변환 해야 한다. 
      Entry entry = (Entry) obj;
      System.out.printf("%s ===> %s\n", 
          entry.getKey(), entry.getValue());
    }
  }
}

entrySet() 메서드로 받은 Entry 객체(key, value를 한 쌍으로 갖는 객체)를 Object를 요소로 갖는 Set에 넣었다. 따라서 이 Set에서 요소들을 꺼내 key와 value 값을 확인하고 싶다면 다시 Entry로 형변환해야한다. 그 후에는 getKey()와 getValue()메서드를 호출하여 각 값들을 조회할 수 있다.

 

3. values() 를 사용하면 key가 아닌 값들의 Set만 꺼낼 수 있다.

Collection values = map.values();
for (Object value : values) {
  System.out.println(value);
}

 

 

 


 Hashtable 

동작원리는 HashMap과 동일하다.

 

HashMap 과 Hashtable의 차이

  • 엄격성: HashMap은 key나 value에 null을 넣을 수 있다. 그러나 Hashtable은 key나 value에 null를 넣을 수 없다.
  • 동기화 / 비동기화: Hashtable는 멀티스레드 상태에서 동시에 값을 꺼내지 못하도록 막는다.(동기화)
    HashMap은 동시에 값을 꺼낼 수 있다.(비동기화)

 실습 - 미니 프로젝트 : 클래스들 간의 의존 관계 형성하기 

git/ eomcs-java-project-2020/ mini-pms-10 / src/ main/ java com.eomcs.pms.handler.MemberHandler.java
git/ eomcs-java-project-2020/ mini-pms-10 / src/ main/ java com.eomcs.pms.handler.ProjectHandler.java
git/ eomcs-java-project-2020/ mini-pms-10 / src/ main/ java com.eomcs.pms.handler.TaskHandler.java

클래스 관계 

UML 표준은 개발자와 개발자가 의사소통하기 위해 만들어진 것. 다양한 클래스의 관계를 UML로 표현이 가능하다.

UML에서의 클래스 관계 표현법

  • 상속 관계 (Generalization) - 한 클래스가 다른 클래스를 상속받은 관계

  • 연관 관계 (Association) -  지속성을 갖는다. 

    • 직접 연관 관계 - 한 클래스가 다른 클래스를 참조하는 관계 : 의존 관계와 달리 지속적이다. UML에서 1...*은 대상 클래스가 가질 수 있는 인스턴스의 개수 범위를 말하며 ...을 기준으로 앞의 값은 최소값, 뒤의 값은 최대값을 의미한다. *는 최대값을 정하지 않았다는 의미이다.

    • 집합 연관 관계 (Aggregation) - 전체와 부분의 관계 : 직접 연관 관계과 코드 상의 차이가 잘 보이지 않을 수 있다. 다만 소속 되는 특징이 강조되면 포함관계라고 표현하는 것이다. 관계에 있는 두 클래스의 life cycle 이 다르다. 컴퓨터와 모니터, 마우스, 키보드의 관계가 그러하다. 집합 연관은 관계가 약하다는 의미에서 약집합이다.

    • 합성 연관 관계 (Composition) - 집합 관계와 유사하지만 관계에 있는 두 클래스의 life cycle 이 같다. 컴퓨터와 메인보드의 관계가 그러하다. 합성 연관은 관계가 강하다는 의미에서 강집합이다.

  • 의존 관계 (dependency) - 한 클래스가 다른 클래스를 참조하는 관계 : 직접 연관 관계와 달리 지속성이 없다.
    참조의 형태는 상대 클래스의 인스턴스 생성하거나 사용 / 상대 클래스의 메서드 호출 / 상대 클래스의 인스턴스를 메서드의 파라미터로 사용 등의 형태로 나타난다.
    의존 객체 = 의존이 되는 객체 = 사용되는 객체 = 메서드를 제공하는 공급자 클래스(supplier)
    상대 객체에 의존하는 객체 = 즉 사용의 주체가 되는 객체 = 사용자 클래스(client)

* 클래스들의 관계는 코드만 봤을 때는 구별하기 애매할 수 있다. 개념적인 부분에서의 분류이기 때문이다. 

 

실습 목표 :
1) Member 목록을 조회하여 프로젝트 담당자와 팀원, 작업 담당자의 유효성 검증하기
2) 프로젝트 등록 취소 기능 추가하기

 

1단계 : MemberHandler 클래스에 findByName() 스태틱 메서드를 추가한다. 스태틱 필드인 member배열에 있는 각 항목의 name과 파라미터로 들어가는 문자열이 같은 지 비교하고 같은 것이 있으면 해당 문자열을 리턴하고, 같은 것이 없다면 null을 리턴한다.

package com.eomcs.pms.handler;

import java.sql.Date;
import com.eomcs.util.Prompt;

public class MemberHandler {

  // 회원 데이터
  static class Member {
    int no;
    String name;
    String email;
    String password;
    String photo;
    String tel;
    Date registeredDate;
  }
  
  static final int LENGTH = 100;
  static Member[] list = new Member[LENGTH]; // list로 이름을 바꾼다.
  static int size = 0;

  // 다른 패키지에서 이 메서드를 사용할 수 있도록 public 으로 사용 범위를 공개한다.
  public static void add() {
    System.out.println("[회원 등록]");
    
    Member member = new Member();
    member.no = Prompt.inputInt("번호? ");
    member.name = Prompt.inputString("이름? ");
    member.email = Prompt.inputString("이메일? ");
    member.password = Prompt.inputString("암호? ");
    member.photo = Prompt.inputString("사진? ");
    member.tel = Prompt.inputString("전화? ");
    member.registeredDate = new java.sql.Date(System.currentTimeMillis());
    
    list[size++] = member;
  }
  
  public static void list() {
    System.out.println("[회원 목록]");
    
    for (int i = 0; i < size; i++) {
      Member member = list[i];
      System.out.printf("%d, %s, %s, %s, %s\n",
          member.no, 
          member.name, 
          member.email, 
          member.tel, 
          member.registeredDate);
    }
  }
  // findByName() 메서드 추가
  public static String findByName(String name) {
    for (int i = 0; i < size; i++) {
      if (list[i].name.equals(name)) {
        return name;
      } 
    }
    return null;
  }
}

 

2단계 : projectHandler 클래스에서 만든이의 이름을 입력받는 명령어를 수정한다. 기존의 MemberHandler에 있는 멤버 배열에 없는 이름을 입력하면 등록된 이름이 없음을 알리고 다시 묻게 한다. 옳은 이름을 입력하기 전까지는 등록 과정에서 나갈 수가 없으므로 빈 문자열을 입력하면 등록 메서드를 나갈 수 있게 한다.

package com.eomcs.pms.handler;

import java.sql.Date;
import com.eomcs.util.Prompt;

public class ProjectHandler {

  // 프로젝트 데이터
  static class Project {
    int no;
    String title;
    String content;
    Date startDate;
    Date endDate;
    String owner;
    String members;
  }
  static final int LENGTH = 100;  // PLENGTH 를 LENGTH 로 변경한다.
  static Project[] list = new Project[LENGTH]; // projects 를 list 로 변경한다.
  static int size = 0; // psize 를 size 로 변경한다.

  //다른 패키지에서 이 메서드를 사용할 수 있도록 public 으로 사용 범위를 공개한다.
  public static void add() {
    System.out.println("[프로젝트 등록]");
    
    Project project = new Project();
    project.no = Prompt.inputInt("번호? ");
    project.title = Prompt.inputString("프로젝트명? ");
    project.content = Prompt.inputString("내용? ");
    project.startDate = Prompt.inputDate("시작일? ");
    project.endDate = Prompt.inputDate("종료일? ");
    // 만든이 입력받는 명령 수정
    while (true) {
      String name = Prompt.inputString("만든이?(종료하려면 빈 문자열을 입력) ");
      // 빈문자열 입력하면 메서드 나가기
      if (name.length() == 0) {
        System.out.println("등록을 취소합니다.");
        return;
      }
      // 등록된 이름을 입력하면 해당 이름을 owner에 넣고 반복문 나가기
      if (MemberHandler.findByName(name) != null) {
        project.owner = name;
        break;
      } 
      // 등록되지 않은 이름을 입력하면 다시 입력 받기
      System.out.println("등록된 회원이 아닙니다.");
    }
    project.members = Prompt.inputString("팀원? ");

    list[size++] = project;
  }
  
  public static void list() {
    System.out.println("[프로젝트 목록]");
    
    for (int i = 0; i < size; i++) {
      Project project = list[i];
      System.out.printf("%d, %s, %s, %s, %s\n",
          project.no, 
          project.title, 
          project.startDate, 
          project.endDate, 
          project.owner);
    }
  }
}

 

3단계 : 비슷한 방식으로 팀원을 입력받는 명령을 수정한다. 대신 빈 문자열을 입력할 때까지 다중의 이름을 입력받도록 한다. 이 과정에서 concat() 이라는 메서드를 사용하여 문자열을 수정할 것인데, String은 immutable이기 때문에 Stringbuilder을 사용한다.또한 list 메서드에서 입력받은 팀원들 이름을 추가적으로 출력하게 한다.

package com.eomcs.pms.handler;

import java.sql.Date;
import com.eomcs.util.Prompt;

public class ProjectHandler {

  // 프로젝트 데이터
  static class Project {
    int no;
    String title;
    String content;
    Date startDate;
    Date endDate;
    String owner;
    String members;
  }
  static final int LENGTH = 100;  // PLENGTH 를 LENGTH 로 변경한다.
  static Project[] list = new Project[LENGTH]; // projects 를 list 로 변경한다.
  static int size = 0; // psize 를 size 로 변경한다.

  //다른 패키지에서 이 메서드를 사용할 수 있도록 public 으로 사용 범위를 공개한다.
  public static void add() {
    System.out.println("[프로젝트 등록]");
    
    Project project = new Project();
    project.no = Prompt.inputInt("번호? ");
    project.title = Prompt.inputString("프로젝트명? ");
    project.content = Prompt.inputString("내용? ");
    project.startDate = Prompt.inputDate("시작일? ");
    project.endDate = Prompt.inputDate("종료일? ");
    while (true) {
      String name = Prompt.inputString("만든이?(종료하려면 빈 문자열을 입력) ");
      if (name.length() == 0) {
        System.out.println("등록을 취소합니다.");
        return;
      }
      if (MemberHandler.findByName(name) != null) {
        project.owner = name;
        break;
      } 
      System.out.println("등록된 회원이 아닙니다.");
    }
    // 팀원 입력 명령어 수정
    // mutable 한 StringBuilder 사용
    StringBuilder names = new StringBuilder();
    while (true) {
      String name = Prompt.inputString("팀원?(종료하려면 빈 문자열을 입력) ");
      if (name.length() == 0) {
      // return이 아닌 break로 바꾸기
        break;
      }
      if (MemberHandler.findByName(name) != null) {
      // 여러 이름 받아서 뒤에 계속 붙이기
        names.append(name + ",");
        project.members = names.toString();
        // else 블록에 넣기
      } else {
        System.out.println("등록된 회원이 아닙니다.");        
      }
    }

    list[size++] = project;
  }
  
  public static void list() {
    System.out.println("[프로젝트 목록]");
    
    for (int i = 0; i < size; i++) {
      Project project = list[i];
      // 팀원 출력
      System.out.printf("%d, %s, %s, %s, %s, [%s]\n",
          project.no, 
          project.title, 
          project.startDate, 
          project.endDate, 
          project.owner,
          project.members);
    }
  }
}

 

4 단계 : 같은 방식으로 작업 추가 메서드에 담당자 입력받는 명령어를 수정한다.

package com.eomcs.pms.handler;

import java.sql.Date;
import com.eomcs.util.Prompt;

public class TaskHandler {

  // 작업 데이터
  static class Task {
    int no;
    String content;
    Date deadline;
    int status;
    String owner;
  }
  static final int LENGTH = 100; // TLENGTH 를 LENGTH 로 변경한다.
  static Task[] list = new Task[LENGTH]; // tasks 를 list 로 변경한다.
  static int size = 0; // tsize 를 size 로 변경한다.

  //다른 패키지에서 이 메서드를 사용할 수 있도록 public 으로 사용 범위를 공개한다.
  public static void add() {
    System.out.println("[작업 등록]");
    
    Task task = new Task();
    task.no = Prompt.inputInt("번호? ");
    task.content = Prompt.inputString("내용? ");
    task.deadline = Prompt.inputDate("마감일? ");
    task.status = Prompt.inputInt("상태?\n0: 신규\n1: 진행중\n2: 완료\n> ");
    // 담당자 입력 명령어 수정
    while (true) {
      String name = Prompt.inputString("담당자?(종료하려면 빈 문자열을 입력) ");
      if (name.length() == 0) {
        System.out.println("등록을 취소합니다.");
        return;
      }
      if (MemberHandler.findByName(name) != null) {
        task.owner = name;
        break;
      } 
      System.out.println("등록된 회원이 아닙니다.");
    }

    list[size++] = task;
  }
  
  public static void list() {
    System.out.println("[작업 목록]");
    
    for (int i = 0; i < size; i++) {
      Task task = list[i];
      String stateLabel = null;
      switch (task.status) {
        case 1:
          stateLabel = "진행중";
          break;
        case 2:
          stateLabel = "완료";
          break;
        default:
          stateLabel = "신규";
      }
      System.out.printf("%d, %s, %s, %s, %s\n",
          task.no, 
          task.content, 
          task.deadline, 
          stateLabel, 
          task.owner);
    }
  }
}

 StringBuilder 와 StringBuffer 

StringBuilder와 StringBuffer은 둘 다 mutable 한 형태의 문자열 클래스로서의 기능은 동일하지만 차이점이 있다. StringBuilder은 비동기화된 객체이고, StringBuffer은 동기화된 객체이다.

 

 동기화(synchronization)와 비동기화(unsynchronization) 

동기화는 한 스레드가 특정 메서드에 진입하면 그 스레드가 메서드의 실행을 마칠 때까지 다른 스레드의 집입을 제한한다. 따라서 메서드를 동시간에 하나의 스레드만이 실행할 수 있다. 그에 반해 비동기화는 다중의 스레드가 한 메서드에 동시에 사용을 가능하게 하는 것이다.

 

수정된 프로젝트의 구조를 살펴보면 ProjectHandler 와 TaskHandler 는 MemberHandler의 메서드를 호출함으로써 MemberHandler에 의존하는 의존 관계임을 알 수 있다. 즉, MemberHandler 은 supplier 이고 ProjectHandler와 TaskHandler은 client이다.