Posts 학원 #27일차: 자료구조(큐, 스텍, 반복자, HashSet, HashMap, Hashtable), 클래스 의존관계
Post
Cancel

학원 #27일차: 자료구조(큐, 스텍, 반복자, HashSet, HashMap, Hashtable), 클래스 의존관계

Data Structure

Queue

queue

  • FIFO
  • 큐가 비어있다면 null을 리턴한다.
  • java.util.concurrent.ArrayBlockingQueue 사용하기

Que 구현하기

1단계: Queue를 구현하기 위해 기존에 작성한 MyLinkedList를 상속받는다.

1
public class MyQueue extends MyLinkedList {}

2단계: Queue의 값을 추가하는 offer(Object)를 정의한다.

1
2
3
public boolean offer(Object e) {
    return add(e);
}

3단계: Queue에서 값을 꺼내는 poll()을 정의한다.

1
2
3
4
5
public Object poll() {
    if (this.size() == 0)
        return null;
    return remove(0);
}

4단계: Queue에서 제일 앞에 있는 값을 조회하는 peek()을 정의한다.

1
2
3
4
5
public Object peek() {
	if(this.size == 0)
        return null;
    return get(0);
}

Stack

stack

Stack 구현하기

1단계: Stack을 구현하기 위해 기존에 작성한 MyLinkedList를 상속받는다.

1
public class MyStack extends MyLinkedList {}

2단계: Stack에 값을 추가하는 push(Object)를 정의한다.

1
2
3
public boolean push(Object e) {
    return add(e);
}

3단계: Stack에서 값을 꺼내는 pop()을 정의한다.

1
2
3
4
5
public Object pop() {
    if (this.size() == 0)
        return EmptyStackException();
    return remove(0);
}

4단계: Stack에서 가장 마지막에 입력한 값을 조회하는 peek()을 정의한다.

1
2
3
4
5
public Object peek() {
    if (this.size() == 0)
        return EmptyStackException();
    return get(0);
}

try ~ catch문: 원래 예외가 발생하면 JVM은 그 시점에서 즉시 실행을 멈춘다. 그러나 try ~ catch문을 통해 예외가 발생하면 실행을 멈추지 않게 하고 대신 catch문이 구동 되게 만든다.

스레드: 프로그램 세계에서는 값을 하나하나 꺼내야한다. 동시에 꺼내면 같은 값이 복제되면서 꺼내진다. 그래서 동기화(synchronize)를 해야 하는데, 이는 한 스레드가 작업하는 동안 다른 스레드는 기다리게 만드는 것이다.

i18n: 국제화 vs L10n: 지역화: 위키

HashSet

특징

  • 값을 중복 저장하지 않는다.
    • Set은 집합을 의미한다.
    • 집합에서는 중복값을 허용하지 않는다.
    • 값의 중복 여부는 hashCode()의 리턴 값이 같고, equals()의 검사 결과가 true일 때 같은 값으로 취급한다. 즉 중복된 값을 저장하고 싶지 않을 때 HashSet을 사용한다.
  • 값을 순서대로 저장하지 않는다.
  • 값 객체의 hashCode()의 리턴 값으로 저장 위치를 계산하기 때문에 add() 한 순서대로 저장되지 않는다.
    • 그래서 값을 index를 이용하여 꺼낼 수 없다.
  • 검색 속도와 저장 속도가 빠르다.

ArrayList: 같은 인스턴스의 중복도 허용한다.

hashset

값을 꺼내는 방법

1) HashSet에 들어 있는 값을 배열로 받아 사용한다.

1
2
3
4
5
Object[] values = set.toArray();
for (Object value : values) {
  System.out.print(value + ", ");
}
System.out.println();

2) 창고에서 값을 꺼내주는 객체의 도움을 받는다.

1
2
3
4
5
6
7
8
9
// HashSet에서 값을 꺼내는 객체를 얻는다.
Iterator 컬렉션에서값을꺼내주는객체 = set.iterator();

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

Iterator, 반복자

iterator

각각의 컬렉션 리스트 타입에 따라 값을 꺼내는 방법이 다르다 (get(), poll(), pop()). 그러면 프로그램을 짤 때 이것을 고려해서 각각에 대해 짜야 한다. 그러나 값을 꺼내는 일을 대신 해주는 Iterator를 사용하면 next()라는 일관적인 메서드로 값을 꺼낼 수 있다. 즉 단일한 방법으로 조회할 수 있어 프로그램의 일관성을 유지할 수 있다.

HashMap

key로 사용할 객체 정하기

  • 값을 저장할 때 key로 사용할 객체를 지정한다.
  • 어떤 객체라도 key로 사용할 수 있지만 보통 key 객체로 String 객체를 많이 사용한다.
  • key는 중복될 수 없지만 객체는 중복될 수 있게 하기 위해 equals()hashCode()를 오버라이딩해야 한다. 많이 쓰이는 String이나 wrapper클래스의 경우 이미 해당 메서드를 오버라이딩했기 때문에 그냥 쓰일 수 있다. 그러나 사용자 지정 타입을 사용할 경우 equals()hashCode()를 오버라이딩한 후 HashMap을 사용하는 것이 필수적이다.

wrapper

1
2
3
4
map.put(new Integer(100), v1);
map.put(Integer.valueOf(200), v2);
map.put(300, v3); // auto-boxing
map.put(400, v4);

HashMap에서 값을 꺼내는 방법

1. key 목록을 사용하여 맵에서 값을 꺼내기

1
2
java.util.Set keys = map.keySet();
// key 객체들이 들어있는 집합(컬렉션)을 리턴한다.
1) Iterator 사용
1
2
3
4
5
6
Iterator 키를꺼내주는객체 = keys.iterator();
while (키를꺼내주는객체.hasNext()) {
  Object key = 키를꺼내주는객체.next();
  System.out.println(map.get(key)); // key를 사용하여 값을 꺼낸다.
}
System.out.println("---------------------");
2) 배열을 사용
1
2
3
4
Object[] keyArray = keys.toArray();
for (Object key : keyArray) {
  System.out.println(map.get(key)); // key를 사용하여 값을 꺼낸다.
}
3) for(:) 반복문 사용

만약 컬렉션 객체가 java.util.Collection 규칙을 따라 만든 클래스라면 for(:) 반복문응ㄹ 바로 사용할 수 있다.

1
2
3
for (Object key : keys/* 배열 또는 Collection 객체 */) {
  System.out.println(map.get(key));
}

컬렉션 객체: 컬렉션 인터페이스를 구현한 클래스로 만든 객체. 혹은 AbractionCollection를 상속받은 객체

2. key/value 한 쌍으로 묶어 꺼내기

entry-set

1
2
3
4
5
6
7
8
9
10
Set entrySet = map.entrySet();

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

value 목록만 꺼내기

1
2
3
4
5
6
7
8
9
// key 객체는 중복되어서는 안되기 때문에
// key 객체 목록은 Set 에 담아서 리턴한다.
// value 객체는 중복 저장될 수 있기 때문에
// value 객체 목록은 Collection 에 담아서 리턴한다.
//
Collection values = map.values();
for (Object value : values) {
  System.out.println(value);
}

key 객체는 중복되어서는 안 되기 때문에 key 객체 목록은 Set에 담아서 리턴한다.

value 객체는 중복 저장될 수 있기 때문에 value 객체 목록은 Collection에 담아서 리턴한다.

HashMap vs Hashtable

HashMap과 Hashtable은 동작은 같지만 다음 항목에서 차이를 보인다.

 HashMapHashtable
null을 key와 value로 사용 가능OX
동기화 지원XO

동기화를 지원하지 않으면 멀티스레드가 동시에 사용할 때 문제가 발생할 수 있다. 그대신 실행 속도가 빠르다는 장점이 있다.

HashMap/Hashtable과 Iterator

Iterator 객체를 생성할 때, 현재 목록 객체(keys)를 마탕으로 생성한다. 따라서 Iterator를 생성한 후에 목록의 값을 변경하면 기존 목록에서 뽑은 Iterator는 무효한 객체가 된다.

HashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Member v1 = new Member("Ross", 20);
Member v2 = new Member("Rachel", 26);
Member v3 = new Member("Phoebe", 23);
Member v4 = new Member("Joey", 20);
Member v5 = new Member("Chandler", 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 keys = map.keySet();
Iterator iterator = keys.iterator();
// Iterator 객체를 생성할 때,
// 현재 목록 객체(keys)를 바탕으로 생성한다.
//
// 따라서 다음과 같이 Iterator를 생성한 후에 목록의 값을 변경하면,
// 기존 목록에서 뽑은 Iterator는 무효한 객체가 된다.
map.remove("s01");
map.remove("s02");
map.remove("s03");

// 무효한 Iterator를 사용하면 실행오류가 발생할 것이다.
// ConcurrentModificationException
while (iterator.hasNext()) {
  System.out.println(iterator.next());
}

Hashtable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Hashtable table = new Hashtable();
table.put("s01", v1);
table.put("s02", v2);
table.put("s03", v3);
table.put("s04", v4);
table.put("s05", v5);

Set keys = table.keySet();
Iterator iterator = keys.iterator();

table.remove("s01");
table.remove("s02");
table.remove("s03");

// Set 객체를 통해 key 를 꺼낼 때,
// 그 순간의 HashSet에 있는 key를 꺼낸다.
// 즉 keySet()을 호출할 때 모든 key를 미리 목록을 만들어 리턴하지 않는다.
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

mini-pms v10

UML

  • 개발자 사이의 의사소통 도구

UML

클래스 의존관계

  • 소스 코드 관리를 위해 메서드를 여러 클래스로 분리하게 되면 반드시 클래스 간에 의존 관계(dependency relationship)을 형성하게 된다. 즉 메서드를 사용하는 클래스( 클라이언트)와 메서드를 제공하는 클래스(공급자, supplier)가 생겨난다.
  • 예) App 클래스에서 MemberHandler 클래스로 회원 데이터 처리와 관련된 메서드를 분리한 순간 App클래스와 MemberHandler 클래스는 의존 관계를 맺게 된다. App은 회원 데이터를 처리하기 위해 MemberHandler의 메서드를 사용하는 클라이언트 입장이 되고 MemberHandlerApp에게 필요 메서드를 공급하는 공급자 입장이 된다.

  • 프로그래밍에서는 UML의 관계를 통틀어서 의존관계라고 말하곤 한다.
  • 의존 객체는 사용을 당하는 것을 말한다. (ex: 핸드폰, 안경, 마스크, 프로젝터)

구현

1단계: 프로젝트 정보를 등록할 때 만든이 이름을 회원 정보에서 조회한다.

MemberHandler: 이름으로 회원 정보를 찾는 findByName() 메서드를 추가한다.

1
2
3
4
5
6
7
8
9
public static Member findByName(String name) {
    for (int i = 0; i < size; i++) {
        Member member = list[i];
        if (member.name.equals(name)) {
            return member;
        }
    }
    return null;
}

ProjectHandler: ProjectHandler.add()에서 MemberHandler.findByName() 메서드를 사용한다.

1
2
3
4
5
6
7
8
while (true) {
    String name = Prompt.inputString("만든이? ");
	if (MemberHandler.findByName(name) != null) {
        p.owner = name;
        break;
    }
    System.out.println("등록된 회원이 아닙니다.");
}

2단계: 프로젝트의 만든이 이름을 입력하지 않으면 프로젝트 등록을 취소한다.

ProjectHandler: 만든이의 이름이 빈 문자열인지 여부를 검사하는 조건문을 추가한다.

1
2
3
4
5
6
7
8
9
10
11
while (true) {
    String name = Prompt.inputString("만든이? ");
    if (name.length == 0) {
        System.out.println("프로젝트 등록을 취소합니다.");
        return;
    } else if (MemberHandler.findByName(name) != null) {
        p.owner = name;
        break;
    }
    System.out.println("등록된 회원이 아닙니다.");
}

3단계: 프로젝트 팀원을 등록할 때 회원 정보에서 조회한다.

ProjectHandler: 팀원의 이름이 유효할 경우 팀원 이름을 추가하고, 무효하면 오류를 알린다. 팀원의 이름이 빈 문자열이면 팀원 입력을 완료한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
StringBuilder names = new StringBuilder();
while (true) {
    String name = Prompt.inputString("팀원? (완료: 빈 문자열)");
    if (name.length == 0) {
        System.out.println("프로젝트 등록을 취소합니다.");
        return;
    } else if (MemberHandler.findByName(name) != null) {
        if (names.length() > 0) {
            names.append(", ");
        }
        names.append(name);
    } else {
        System.out.println("등록된 회원이 아닙니다.");
    }
}
p.members = names.toString();
  • String 클래스는 immutable이기 때문에 연산이 많은 프로그래밍이 필요할 때 계속해서 인스턴스를 생성하므로 성능이 떨어진다. 조회가 많은 환경, 멀티스레드 환경에서 성능적으로 유리하다.
  • 반면 StringBufferStringBuilder는 객체 한 번만 만들고 메모리의 값을 변경시켜서 문자열을 변경한다. 따라서 문자열 연산이 자주 있을 때 사용하면 성능이 좋다.
  • StringBuffer는 동기화지원을 하고 StringBuilder는 동기화 지원을 하지 않는다. 따라서 멀티 스레드 환경에서는 StringBuffer를 사용하는 것이, 싱글 스레드 환경이나 멀티스레드여도 굳이 동기화가 필요 없는 경우에는 StringBuilder를 사용하는 것이 좋다.

위의 코드와 같은 경우에는 로컬변수를 StringBuilder타입으로 선언하였다. 로컬 변수는 여러 스레드가 절대 동시에 사용할 수 없기 때문에 StringBuilder를 사용하여도 무방하다.

인스턴스 변수의 경우 동시에 사용할 수 있다.

4단계: 프로젝트 목록을 출력할 때 팀원 이름도 포함한다.

ProjectHandler: list()에서 프로젝트 목록 출력할 때 팀원 항목을 추가한다.

1
2
3
4
5
6
7
8
9
public static void list() {
    System.out.println("[프로젝트 목록]");
    
    for (int i = 0; i < size; i++) {
        Project p = list[i];
        System.out.printf("%d, %s, %s, %s, %s, [%s]\n",
                         p.no, p.title, p.startDate, p.endDate, p.owner, p.members);
    }
}

5단계: 작업 정보를 등록할 때 담당자 이름을 회원 정보에서 조회한다. 담당자의 이름이 유효할 경우에는 다음 입력으로 넘어가고, 무효할 경우에는 오류를 알리고 다시 입력받는다. 담당자의 이름이 빈 문자열일 경우에는 등록을 취소한다.

TaskHandler: TaskHandler.add()에서 MemberHandler.findByName()메서드를 사용하여 이름의 유효 여부를 검사한다.

1
2
3
4
5
6
7
8
9
10
11
while (true) {
    String name = Prompt.inputString("담당자? ");
    if (name.length == 0) {
        System.out.println("작업 등록을 취소합니다.");
        return;
    } else if (MemberHandler.findByName(name) != null) {
        t.owner = name;
        break;
    }
    System.out.println("등록된 회원이 아닙니다.");
}
This post is licensed under CC BY 4.0 by the author.

학원 #26일차: LinkedList 구현

학원 #28일차: 인스턴스 멤버, 생성자, 캡슐화

Loading comments from Disqus ...