스물아홉 번째 수업

정혅·2024년 3월 21일

더 조은 아카데미

목록 보기
34/76
post-thumbnail

오전 시험 문제

28일차 메모 참조

  1. 제네릭 메소드에 매개변수로 배열을 전달하는 형태로 정의및 호출해 보자.
package com.test.memo;

public class Practice2 {

    static <T> void method(T[] arr) {
        for (T t : arr) {
            System.out.println(t);
        }
    }

    public static void main(String[] args) {
        String[] strArr = { "안녕", "오늘은", "백암순댓국", "먹으러갈거야" };

        method(strArr);

    }
}

  1. 자료형을 결정짓는 제네릭 매개변수 T에 Fruit클래스를 포함하여, Fruit을 상속하는 클래스면 무엇이든 올 수 있음을 명시해서 예제소스코드를 작성하시오
package com.test.memo;

class Alchol {
    void showYou() {
        System.out.println("나는 술");
    }
}

class Beer extends Alchol {

    @Override
    void showYou() {
        System.out.println("나는 술 중에서 맥주");
    }
}

class AlcholBox<T> {
    T categ;

    void store(T categ) {
        this.categ = categ;
    }

    T pullOut() {
        return categ;
    }
}

public class Practice2 {

    static void openAndShow(AlcholBox<? extends Alchol> box) {
        Alchol alchol = box.pullOut();
        alchol.showYou();
    }

    public static void main(String[] args) {

        AlcholBox<Alchol> aBox = new AlcholBox<>();
        aBox.store(new Alchol());
        openAndShow(aBox);

        AlcholBox<Beer> bBox = new AlcholBox<>();
        bBox.store(new Beer());
        openAndShow(bBox);
    }
}
  • 상자에 넣고 빼는 개념을 자꾸 까먹는다. 메인 메서드에서 객체를 생성해서 해당 객체에 새로운 객체를 또 넣어야하는데, 해당 과정을 빼먹어서 NullPointException이 발생했다.


  1. DDBox\<U, D>라는 이름으로 하나 더 정의하여 DBox\<L, R> 인스턴스 둘을 이 상자에 저장하고자 한다. 그럼 다음 main 메소드를 기반으로 컴파일 및 실행이 가능하도록 DDBox\<U, D> 제네릭 클래스를 정의해보자.
package com.test.memo;

class DBox<L, R> {
    private L left;
    private R right;

    public void set(L o, R r) {
        left = o;
        right = r;
    }

    public String toString() {
        return left + " & " + right;
    }
}

class DDBox<U, D> {
    private U box1;
    private D box2;

    void set(U box1, D box2) {
        this.box1 = box1;
        this.box2 = box2;
    }

    @Override
    public String toString() {
        return box1 + "\n" + box2;
    }

}

public class Practice1 {

    public static void main(String[] args) {
        DBox<String, Integer> box1 = new DBox<>();
        box1.set("Apple", 25);
        DBox<String, Integer> box2 = new DBox<>();
        box2.set("Orange", 33);
        DDBox<DBox<String, Integer>, DBox<String, Integer>> ddbox = new DDBox<>();
        ddbox.set(box1, box2); // 두 개의 상자를 하나의 상자에 담음
        System.out.println(ddbox); // 상자의 내용물 출력
    }
}


3-1. 위의 내용에 해당하는 프로그램은 사실 별도의 클래스를 정의하지 않고 DBox 하나로 충분히 편성할 수 있다. 따라서 이번에는 문제 1의 내용과 결과를 보이는 프로그램을 작성하되 DBox 클래스 하나만 활용하여 작성해보자.(상자에 담긴 내용물의 출력 형태는 달라도 괜찮다. 내용물만 전부 출력이 되면 된다.)

package com.test.memo;

class DBox<L, R> {
    private L left;
    private R right;

    public void set(L o, R r) {
        left = o;
        right = r;
    }

    public String toString() {
        return (left + " & " + right) + System.lineSeparator();
    }
}

public class Practice1 {

    public static void main(String[] args) {
        DBox<String, Integer> box1 = new DBox<>();
        box1.set("Apple", 25);
        DBox<String, Integer> box2 = new DBox<>();
        box2.set("Orange", 33);

        DBox<DBox<String, Integer>, DBox<String, Integer>> boxx = new DBox<>();
        boxx.set(box1, box2);
        System.out.println(boxx);
    }
}


  1. 단 이때 Box 인스턴스의 T는 Number 또는 이를 상속하는 하위 클래스만 올 수 있도록 제한된 매개변수 선언을 하자.
    class Box {

    private T ob;

    public void set(T o) {

    ob = o;

    }

    public T get() {

    return ob;

    }

    }
    class BoxSwapDemo {

    // 이 위치에 swapBox 메소드 정의하자.
    public static void main(String[] args) {

    Box<Integer> box1 = new Box<>();
    box1.set(99);
    Box<Integer> box2 = new Box<>();
    box2.set(55);
    System.out.println(box1.get() + " & " + box2.get());
    swapBox(box1, box2);    // 정의해야 할 swapBox 메소드
    System.out.println(box1.get() + " & " + box2.get());

    }

    }

99 & 55
55 & 99

package com.test.memo;

class Box<T> {
    private T ob;

    public void set(T o) {
        ob = o;
    }

    public T get() {
        return ob;
    }
}

public class Practice1 {

    static <T extends Number> void swapBox(Box<T> box1, Box<T> box2) {
        T temp = box1.get();
        box1.set(box2.get());
        box2.set(temp);
    }

    public static void main(String[] args) {

        Box<Integer> box1 = new Box<>();
        box1.set(99);

        Box<Integer> box2 = new Box<>();
        box2.set(55);

        System.out.println(box1.get() + " & " + box2.get());
        swapBox(box1, box2); // 정의해야 할 swapBox 메소드
        System.out.println(box1.get() + " & " + box2.get());
    }
}
  • 메서드 제네릭에 extends Number을 해주지 않고 그냥 로 두고 인자에서도 T로 받았는데 그렇게하니 스왑이 되지 않았다. 메소드 제네릭에서는 제한을 주고 인자로 받을때 Box인스턴스를 인자로 받았어야 했는데

  1. 예제에는 프로그래머의 실수가 존재한다. 그러나 컴파일 과정에서는 이 실수가 드러나지 않는다. 실수가 컴파일 과정에서 발견될 수 있도록 매개변수 선언을 수정하자. 그리고 프로그래머의 실수를 바로잡자.

class Box {
private T ob;
public void set(T o) { ob = o; }
public T get() { return ob; }
}
class BoundedWildcardDemo {
public static void addBox(Box b1, Box b2, Box b3) {
b3.set(b1.get() + b2.get()); // 프로그래머의 실수가 있는 부분
}
public static void main(String[] args) {
Box box1 = new Box<>();
box1.set(24);
Box box2 = new Box<>();
box2.set(37);
Box result = new Box<>();
result.set(0);
addBox(result, box1, box2); // result에 24 + 37의 결과 저장
System.out.println(result.get()); // 61 출력
}
}

컴파일에서 실수 발견되도록

package com.test.memo;

class Box<T> {
    private T ob;

    public void set(T o) {
        ob = o;
    }

    public T get() {
        return ob;
    }
}

public class Practice1 {

    public static void addBox(Box<? super Integer> b1, Box<? extends Integer> b2, Box<? extends Integer> b3) {
        b3.set(b1.get() + b2.get()); // 프로그래머의 실수가 있는 부분 >> 컴파일 오류 발생 
    }

    public static void main(String[] args) {
        Box<Integer> box1 = new Box<>();
        box1.set(24);
        Box<Integer> box2 = new Box<>();
        box2.set(37);
        Box<Integer> result = new Box<>();
        result.set(0);

        addBox(result, box1, box2); // result에 24 + 37의 결과 저장
        System.out.println(result.get()); // 61 출력
    }
}
  • 인자값에서 와일드카드를 이용해 제한을 주면 b1은 Integer를 포함한 상위 클래스만 저장될 수 있게되고, b2, b3은 Integer의 하위 클래스만 저장될 수 있게 된다.

    • 그러므로 현재 개발자는 b3에 더해해서 set하고 있기때문에 b1에서는 상위클래스로 제한했기 때문에 Object타입을 반환할것이므로 오류가 나는것이다.

    • 이제 개발자가 실수한 부분을 고쳐주면 아래와 같다.

      b1.set(b2.get() + b3.get()); // 프로그래머의 실수가 있는 부분

  1. 컴파일 과정에서는 이 실수가 드러나지 않는다. 실수가 컴파일 과정에서 발견될 수 있도록 매개변수 선언을 수정하자. 그리고 프로그래머의 실수를 바로잡자.

class Box {
private T ob;
public void set(T o) { ob = o; }
public T get() { return ob; }
}
class BoundedWildcardGeneric {
// box에 con과 동일한 내용물이 들었는지 확인
public static boolean compBox(Box box, T con) {
T bc = box.get();
box.set(con); // 프로그래머의 실수로 삽입된 문장, 때문에 내용물이 바뀐다.
return bc.equals(con);
}
public static void main(String[] args) {
Box box1 = new Box<>();
box1.set(24);
Box box2 = new Box<>();
box2.set("Poly");
if(compBox(box1, 25))
System.out.println("상자 안에 25 저장");
if(compBox(box2, "Moly"))
System.out.println("상자 안에 Moly 저장");

     System.out.println(box1.get());
System.out.println(box2.get());
}

}

package com.test.memo;

class Box<T> {
    private T ob;

    public void set(T o) {
        ob = o;
    }

    public T get() {
        return ob;
    }
}

public class Practice2 {// compBox메소드는 비교만을 수행하는 메서드로 내용물을 변경하는 내용이 개발자의 실수로 들어간것을 가정
    public static <T> boolean compBox(Box<? extends T> box, T con) {
        T bc = box.get();
        box.set(con); // 프로그래머의 실수로 삽입된 문장, 때문에 내용물이 바뀐다. >> 현재 컴파일 에러 
        return bc.equals(con);
    }

    public static void main(String[] args) {
        Box<Integer> box1 = new Box<>();
        box1.set(24);
        Box<String> box2 = new Box<>();
        box2.set("Poly");
        if (compBox(box1, 25))
            System.out.println("상자 안에 25 저장");
        if (compBox(box2, "Moly"))
            System.out.println("상자 안에 Moly 저장");

        System.out.println(box1.get());
        System.out.println(box2.get());
    }
}

Java Collection Framework

 자료구조(데이터를 효율적으로 저장, 조작하기 위한 방법) 와 데이터를 처리하는 알고리즘을 구조화하여 클래스로 구현한 모음집

  • 데이터를 저장하는 자료 구조와 데이터를 처리하는 알고리즘을 구조화하여 클래스로 구현해 놓은 것

    •  자바의 인터페이스(interface)를 사용하여 구현된다.

Tip

  • 컬렉션 프레임워크에 저장할 수 있는 데이터는 오로지 객체 (Object)뿐이다.

    • primitive타입을 wrapper타입으로 변환하여 객체를 박싱(Boxing)하여 저장하여야 한다.

    • 객체를 담는 다는 것은 곧 주소값을 담는것이니 null도 저장이 가능하다.

컬렉션 프레임워크 종류

컬렉션 프레임워크는 크게 Collection 인터페이스 / Map 인터페이스(두개의 데이터를 묶어 한쌍으로 다룸)로 나뉜다.

Tip

대부분의 컬렉션 클래스들은 List, Set , Map 중의 하나를 구현하고 있으며, 구현한 인터페이스의 이름이 클래스 이름에 포함되는 특징이 있다. (ArrayList, HashSet, HashMap ... 등)

그러나 Vector, Stack, Hashtable등 의 클래스들은 컬렉션 프레임워크가 만들어지기 이전부터 존재하던 것이기 때문에 명명법을 따르지않고, Verctor(ArrayList의 구형버전)나 HashTable과 같은 기존의 컬렉션 클래스들은 호환을 위해 남겨진 것이므로 가급적 사용하지 않는 것이 좋다.


Iterable 인터페이스

컬렉션 인터페이스들의 가장 최상위 인터페이스

Iterator객체를 관리하는 인터페이스

Collection 인터페이스

List, Set, Queue에 상속하는 실질적인 최상위 컬렉션 타입

업캐스팅으로 다양한 종류의 컬렉션 자료형을 받아 자료를 삽입하거나 삭제, 탑색 기능을 할 수 있다.

  • java.util의 Interface에 존재

Info

Collection인터페이스의 메서드를 보면 요소(객체)에 대한 추가, 삭제, 탐색은 다형성 기능으로 사용이 가능하지만, 데이터를 get하는 메서드는 보이지 않는다.

왜냐하면 각 컬렉션 자료형 마다 구현하는 자료구조가 제각각 이기 때문에 최상위 타입으로 조회하기 까다롭기 때문이다.


Collection Framework의 주요 인터페이스

List, Set, Map

순서중복특징
ListOO순서가 보장되고 중복을 허용하는 데이터의 집합
SetXX순서를 유지하지 않고 중복 또한 허용하지 않는 데이터의 집합
MapXkey: X / value: O키(key)와 값(value)의 쌍으로 이루어진 데이터의 집합

List 인터페이스

  • 저장 순서가 유지되는 컬렉션을 구현하는데 사용

  • 같은 요소의 중복 저장을 허용

  • 배열처럼 인덱스를 사용하여 요소에 접근할 수 있다. >> get() , set()

  • 자료형 크기가 고정이 아닌, 양에 따라 동적으로 늘었다 줄었다 할 수 있다(가변적)

  • 요소 사이네 빈공간을 허용하지 않아 삽입/삭제 할때마다 배열 이동이 일어난다.

메소드

ArrayList 클래스

  • 가장 많이 사용하는 List 구현 클래스(전체 컬렉션 클래스 중에서도 가장 많이 사용)

    • 배열을 이용하여 만든 리스트

    • 데이터의 저장순서가 유지되고 중복을 허용

    • 데이터량에 따라 공간(capacity)이 자동으로 늘어나거나 줄어든다.

  • 장점

    1. 구조가 간단하고 데이터를 읽는데 걸리는 시간(접근시간, access time)이 짧다.

    2. 단방향 포인터 구조로 자료에 대한 순차적인 접근에 강점이 있어 조회가 빠르다

  • 단점

    1. 비순차적인 데이터의 추가, 삭제에 시간이 많이 걸린다.

      • 순차적인 데이터 추가(끝에 추가)와 삭제(끝부터 삭제)는 빠르다.

      • 데이터를 추가하거나 삭제하기 위해 다른 데이터를 옮겨야 한다.

List<String> arrayList = new ArrayList<>();

arrayList.add("Hello");
arrayList.add("World");

arrayList.get(0) // "Hello"


//ArrayList 선언
ArrayList list = new ArrayList();//타입 미설정 Object로 선언된다.
ArrayList<Student> members = new ArrayList<Student>();//타입설정 Student객체만 사용가능
ArrayList<Integer> num = new ArrayList<Integer>();//타입설정 int타입만 사용가능
ArrayList<Integer> num2 = new ArrayList<>();//new에서 타입 파라미터 생략가능
ArrayList<Integer> num3 = new ArrayList<Integer>(10);//초기 용량(capacity)지정
ArrayList<Integer> list2 = new ArrayList<Integer>(Arrays.asList(1,2,3));//생성시 값추가
  • 첫번째 예시 ArrayList list = new ArrayList();는 추천하지 않는다.

메소드

  • boolean add(Object o) : ArrayList의 마지막에 객체를 추가 > 성공하면 true
  • void add(int indexm Object element) : 지정된 위치(index)에 객체를 저장
  • boolean addAll(Collection c) : 주어진 컬렉션의 모든 객체를 저장한다.
  • boolean addAll(int index, Collection c) : 지정된 위치부터 주어진 컬렉션의 모든 객체를 저장한다.
  • void clear() : ArrayList를 완전히 비운다.
  • Object clone() : ArrayList를 복제한다.
  • boolean contains(Object o) : 지정된 객체(o)가 ArrayList에포함되어 있는지 확인
  • int indexOf(Object o) : 지정된 객체가 저장된 위치를 찾아 반환한다.
  • int lastindexOf(Object to) : 객체(o)가 저장된 위치를 끝부터 역방향으로 검색해서 반환한다.
  • boolean isEmpty() : ArrayList가 비어있는지 확인한다.
  • iterator iterator() : ArrayList의 iterator객체를 반환

예제 1

import java.util.*;

class ArrayListEx1{
	public static void main(String[] args) {
		ArrayList list1 = new ArrayList(10);
		list1.add(Integer.valueOf(5)); //valueOf()메소드는 해당 값을 객체로 반환한다. 
		list1.add(Integer.valueOf(4));
		list1.add(Integer.valueOf(2));
		list1.add(Integer.valueOf(0));
		list1.add(Integer.valueOf(1));
		list1.add(Integer.valueOf(3));

		ArrayList list2 = new ArrayList(list1.subList(1,4)); //list1의 인덱스 1~3까지 반환해 복사하는 것 
		print(list1, list2);
		// list1:[5, 4, 2, 0, 1, 3]
		// list2:[4, 2, 0]

		Collections.sort(list1);	// list1과 list2를 오름차순(기본)정렬한다.
		Collections.sort(list2);	// Collections.sort(List l)
		print(list1, list2);
		// list1:[0, 1, 2, 3, 4, 5]
		// list2:[0, 2, 4]

		System.out.println("list1.containsAll(list2):" + list1.containsAll(list2));	
		// list1.containsAll(list2):true > list1이 list2의 요소를 모두 포함하고 있는지 여부를 확인하는 메소드다.

		list2.add("B");
		list2.add("C");
		list2.add(3, "A");
		print(list1, list2); //list2의 인덱스 3위치에 "A"요소를 추가하는 것 > 덮어쓰기가 아닌 해당 인덱스에 넣고 그 뒤 배열은 뒤로 밀리는 형태 
		// list1:[0, 1, 2, 3, 4, 5]
		// list2:[0, 2, 4, A, B, C]		

		list2.set(3, "AA");//list2의 인덱스 3위치의 값을 "AA"로 변경하는 작업 > 덮어쓰기의 형태 
		print(list1, list2);
		// list1:[0, 1, 2, 3, 4, 5]
		// list2:[0, 2, 4, AA, B, C]
		
		// list1에서 list2와 겹치는 부분을 제외한 나머지는 삭제한다.
		System.out.println("list1.retainAll(list2):" + list1.retainAll(list2));
		// list1.retainAll(list2):true	
		// reatinAll의 호출결과로 list1이 변했다면 true를 반환한다.
		print(list1, list2);
		// list1:[0, 2, 4]
		// list2:[0, 2, 4, AA, B, C]
		
		//  list2에서 list1에 포함된 객체들을 삭제한다.
		for(int i= list2.size()-1; i >= 0; i--) {
			if(list1.contains(list2.get(i)))
				list2.remove(i);
		}
		print(list1, list2);
		// list1:[0, 2, 4]
		// list2:[AA, B, C]
	} // main의 끝

	static void print(ArrayList list1, ArrayList list2) {
		System.out.println("list1:"+list1);
		System.out.println("list2:"+list2);
		System.out.println();		
	}
} // class

/*
// 실행 결과
list1:[5, 4, 2, 0, 1, 3]
list2:[4, 2, 0]

list1:[0, 1, 2, 3, 4, 5]
list2:[0, 2, 4]

list1.containsAll(list2):true
list1:[0, 1, 2, 3, 4, 5]
list2:[0, 2, 4, A, B, C]

list1:[0, 1, 2, 3, 4, 5]
list2:[0, 2, 4, AA, B, C]

list1.retainAll(list2):true
list1:[0, 2, 4]
list2:[0, 2, 4, AA, B, C]

list1:[0, 2, 4]
list2:[AA, B, C]
*/
  1. Integer.valueOf(5)는 정수 값 5를 'Integer'객체로 변환해 넣는것이다. ArrayList의 경우, 제네릭으로 객체를 저장하기 때문에 래퍼 클래스로 래핑해서 추가 해야한다.

Singly LinkedList 클래스 - 단방향

중간중간 데이터를 insert를 해야할 경우가 많다면 LinkedList를 활용하는게 더 좋다.

  • ArrayList의 단점을 보완 : ArrayList와 달리 불연속적으로 존재하는 데이터를 연결(link)한다.

    • 노드를 연결하여 리스트처럼 만든 컬렉션 >> 배열이 아님

    • 자바의 LinkedList는 Doubly LinkedList(양방향 포인터 구조)로 이루어져 있다.

  • 장점

    : 데이터의 중간 삽입, 삭제가 빈번할 경우 빠른 성능을 보장한다.

  • 단점

    : 임의의 요소에 대한 접근 성능은 좋지 않다.

    * 참고
    ① Doubly Linked List
    : LinkedList의 단점을 보완하는 이중 연결리스트, 접근성 향상 (앞뒤로 이동)
    
    ② Doubly Circular Linked List
    : 이중 원형 연결리스트 (앞뒤로 이동 + 맨앞과 맨뒤도 연결되어 있음)
List<String> linkedList = new LinkedList<>();

linkedList.add("Hello");
linkedList.add("World");

linkedList.get(0); // "Hello"


//LinkedList선언
LinkedList list = new LinkedList();//타입 미설정 Object로 선언된다.
LinkedList<Student> members = new LinkedList<Student>();//타입설정 Student객체만 사용가능
LinkedList<Integer> num = new LinkedList<Integer>();//타입설정 int타입만 사용가능
LinkedList<Integer> num2 = new LinkedList<>();//new에서 타입 파라미터 생략가능
LinkedList<Integer> list2 = new LinkedList<Integer>(Arrays.asList(1,2));//생성시 값추가

LinkedList 값 추가

LinkedList<Integer> list = new LinkedList<Integer>();

list.addFirst(1);//가장 앞에 데이터 추가
list.addLast(2);//가장 뒤에 데이터 추가
list.add(3);//데이터 추가
list.add(1, 10);//index 1에 데이터 10 추가

LinkedList 값 검색

LinkedList<Integer> list = new LinkedList<Integer>(Arrays.asList(1,2,3));
System.out.println(list.contains(1)); //list에 1이 있는지 검색 : true
System.out.println(list.indexOf(1)); //1이 있는 index반환 없으면 -1

Doubly Linked List - 양방향

위에서 LinkedList는 앞에 'Single'이 생량되어 있는 셈이다.

✔️ 이중연결리스트(Doubly Linked List)
단일연결리스트는 삽입/삭제 시 반드시 이전 노드를 가리키는 레퍼런스를 추가로 알아내야 하고 역방향으로 노드 탐색이 불가함이중연결리스트는 이러한 단점을 보완
각 노드마다 한 개의 레퍼런스를 추가로 저장해야 한다는 단점이 있음

참조 변수를 하나 더 추가하여 다음 요소에 대한 참조 뿐 아니라 이전 요소에 대한 참조가 가능하도록 한다.

💡 이중연결리스트의 노드 구조
각 노드가 두 개의 레퍼런스로 각각 이전 노드와 다음 노드를 가리킴

1. 기본적인 이중연결리스트 클래스
💡 header와 trailer 초기화하는 이유
두 노드는 실제로 데이터 저장하지 않는 Dummy 노드, 빈 노드들을 의도적으로 남겨두어 리스트가 비어있는 경우가 발생하지 않게 함

public class DoublyLinkedList<E> {
    private static class Node<E> {
        private E element;
        private Node<E> prev; // 연결리스트의 이전 노드
        private Node<E> next; // 연결리스트의 다음 노드
        public Node(E e, Node<E> p, Node<E> n) {
            element = e;
            prev = p;
            next = n;
        }
        public E getElement() { return element; }
        public Node<E> getPrev() { return prev; }
        public Node<E> getNext() { return next; }
        public void setPrev(Node<E> p) { prev = p; }
        public void setNext(Node<E> n) { next = n; }
        private Node<E> header;
        private Node<E> trailer;
        private int size = 0;
        public void DoublyLinkedList() {
            header = new Node<E> (null, null, null);
            trailer = new Node<E> (null, header, null);
            header.setNext(trailer);
        }
        public int size() { return size; }
        public boolean isEmpty() { return size == 0;}
        // 첫번째 노드 값 리턴
        public E first() {
            if(isEmpty()) return null;
            return header.getNext().getElement();
        }
        // 마지막 노드 값 리턴
        public E last() {
            if(isEmpty()) return null;
            return trailer.getNext().getElement();
        }
    }
}

맨 앞에 노드 삽입

public void addFirst(E e) {
            addBetween(e, header, header.getNext());
}

맨 뒤에 노드 삽입

public void addLast(E e) {
            addBetween(e, trailer.getPrev(), trailer);
}

첫 번째 노드 삭제

public E deleteFirst() {
            if(isEmpty()) return null;
            return remove(header.getNext());
}

마지막 노드 삭제

public E deleteLast() {
            if(isEmpty()) return null;
            return remove(trailer.getPrev());
}

💡 addBetween/remove 메소드를 reuse하여 제일 앞과 뒤의 노드 추가 및 삭제 구현
💡 addBetween은 실제로는 앞 노드만 인자로 가지는 addNext로 remove는 removeNext로 바꾸는 것을 권장
두 노드 사이에 값 추가

private void addBetween(E e, Node<E> predecessor, Node<E> successor) {
            Node<E> newest = new Node(e, predecessor, successor);
            predecessor.setNext(newest);
            successor.setPrev(newest);
            size++;
}

특정 노드 뒤의 노드 삭제

private E remove(Node<E> node) {
            Node<E> predecessor = node.getPrev();
            Node<E> successor = node.getNext();
            predecessor.setNext(successor);
            successor.setPrev(predecessor);
            size--;
            return node.getElement();
}

Doubly Circular Linked List - 이중 원형 연결 리스트


ArrayList vs LinkedList 성능 비교

컬렉션읽기(접근시간)추가/삭제비고
ArrayList빠름느림순차적인 추가삭제는 더 빠름. 비효율적인 메모리사용
LinkedList느림빠름데이터가 많을수록 접근성이 떨어짐
1. 순차적으로 데이터를 추가/삭제 - ArrayList가 빠름
2. 비순차적으로 데이터를 추가/삭제 - LinkedList가 빠름
3. 접근시간(access time) == 읽기 - ArrayList가 빠름

Set 인터페이스

  • 데이터의 중복을 허용하지 않고 순서를 유지하지 않는 데이터의 집합 리스트

  • 순서 자체가 없으므로 인덱스로 객체를 검색해서 가져오는 get(index) 메서드도 존재하지 않는다.

  • 중복 저장이 불가능하다. >> 심지어 null값도 하나만 저장할 수 있다.

메서드설명
boolean add(E e)주어진 객체를 저장 한 후 성공적이면 true, 중복 객체면 false를 리턴한다.
boolean contains(Object o)주어진 객체가 저장되어있는지 여부를 리턴한다.
Iterator\ iterator()저장된 객체를 한번씩 가져오는 반복자를 리턴한다.
isEmpty()컬렉션이 비어있는지 조사한다.
int Size()저장되어 있는 전체 객체 수를 리턴한다.
void clear()저장된 모든 객체를 삭제한다.
boolean remove(Object o)주어진 객체를 삭제한다.
  • Set 컬렉션은 인덱스를 통해 객체를 검색해서 가져오는 메소드가 없기 때문에, 전체 객체를 대상으로 한번씩 반복하여 가져오는 Iterator(반복자)를 제공한다.

    • 반복자는 Iterator 인터페이스를 구현한 객체를 말하는데 , iterator()를 호출하면 얻을 수 있다.

Iterator 생성 예제

Set<String> set = new HashSet<String>();
Iterator<String> iterator = set.iterator();

Iterator인터페이스에 선언된 메소드

타입메소드설명
booleanhasNext()가져올 객체가 있다면 true를 return, 없다면 false를 return 한다
Enext()하나의 객체를 가져온다
voidremove()객체를 제거한다
  • Iterator에서 하나의 객체를 가져올 떄는hasNext()를 통해서 가져올 객체가 있는지 확인한 후 next()메소드를 사용한다.

    • 만약 객체를 삭제하고 싶다면 remove()메소드를 실행하면 되는데, Iterator의 메소드지만, 실제 Set컬렉션에서 삭제된다.
Set <String> set = new HashSet<String>();
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()){
    String str = iterator.next();
    if(str.equals("AA"){
        //실제 set에서 "AA"객체가 삭제된다.
        iterator.remove();
    }
}
  1. HashSet
  • 가장 많이 사용하는 Set 구현 클래스 - 순서x, 중복x

    순서를 유지하려면 LinkedHashSet 클래스를 사용하면 된다.

  1. LinkedHashSet
  • 순서o, 중복 x

    입력된 순서대로 데이터를 관리

  1. TreeSet
  • 이진 탐색 트리(binary search tree) : 부모보다 작은 값은 왼쪽, 큰 값은 오른쪽에 저장

  • 저장되는 요소가 오름차순으로 정렬되어서 저장된다.

  • 단점 : 데이터가 많아 질수록 추가, 삭제에 시간이 더 걸린다. >> 비교횟수 증가


HashSet 클래스

Hashing 을 이용해서 구현한 컬렉션

  • 데이터를 중복 저장할 수 없고, 순서를 보장하지 않는다.

  • equals()나 hashCode()를 오버라이딩해, 인스턴스가 달라도 동일 객체를 구분해 중복저장을 막을 수 있다. >> Object클래스의 메소드

  • TreeSet이나LinkedHashSet보다 성능이 더 빠르고, 메모리를 적게 사용한다.

  • 배열과 연결 노드를 결합한 자료구조 형태

  • 가장 빠른 임의 검색 접근 속도를 가진다.

  • 추가, 삭제, 검색, 접근성이 모두 뛰어나지만 >> 순서를 예측할 수 없다.

Set<Integer> hashSet = new HashSet<>(); //HashSet생성 방

hashSet.add(10);
hashSet.add(20);
hashSet.add(30);
hashSet.add(10); // 중복된 요소 추가

hashSet.size(); // 3 - 중복된건 카운트 X

hashSet.toString(); // [20, 10, 30] - 자료 순서가 뒤죽박죽

//HashSet사용법
HashSet<Integer> set1 = new HashSet<Integer>();//HashSet생성
HashSet<Integer> set2 = new HashSet<>();//new에서 타입 파라미터 생략가능
HashSet<Integer> set3 = new HashSet<Integer>(set1);//set1의 모든 값을 가진 HashSet생성
HashSet<Integer> set4 = new HashSet<Integer>(10);//초기 용량(capacity)지정
HashSet<Integer> set5 = new HashSet<Integer>(10, 0.7f);//초기 capacity,load factor지정
HashSet<Integer> set6 = new HashSet<Integer>(Arrays.asList(1,2,3));//초기값 지정
  • HashSet을 기본으로 생성했을 때는 initial capacity(16), load factor(0.75)의 값을 가진 HashSet객체가 생성된다.
  • 저장공간보다 값이 추가로 들어오면 저장공간을 늘리는데 저장 용량을 약 두배로 늘린다. > 여기서 과부하가 많이 발생하기 때문에, 초기에 저장할 데이터 갯수를 알고 있다면 Set의 초기용량을 지정해주는게 좋다.

HashSet에 객체를 저장하고 가져오기 예제

package com.test.memo;

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

class Person { // Person클래스
    private String name; // 필드
    private String id;

    Person(String name, String id) {
        this.name = name;
        this.id = id;
    }

    String getName() {
        return name;
    }

    String getId() {
        return id;
    }
}

public class Practice2 {
    public static void main(String[] args) {
        Set<Person> person = new HashSet<>(); // 제네릭타입이 Person인 HashSet

        person.add(new Person("토니 스타크", "ironman")); // 객체 생성
        person.add(new Person("피터 파커", "spiderman")); // 객체 생성

        Iterator<Person> it = person.iterator(); // 반복자 생성

        while (it.hasNext()) {
            Person ps = it.next(); // person에 저장된 객체의 참조값 저장

            System.out.println("아이디: " + ps.getId()); // private으로 선언되어있어 메서드를 통해야 한다.
            System.out.println("이름: " + ps.getName());
            System.out.println();

        }

    }
}

HashSet의 hashCode() 와 equals()

  • HashSet은 같은 인스턴스가 아니더라도 동일 객체를 구분하여 중복 저장을 막을 수 있다.

    1. HashSet에 객체를 저장하기 전에, hashCode()를 호출해 해시코드를 얻어낸다.

    2. 저장되어있는 객체들의 해시코드와 비교해서, 동일한 해시코드가 있다면 다시 equlas()로 두 객체를 비교해 true가 나오면 동일한 객체로 판단해 중복 저장을 하지 않는 방식이다.


TreeSet 클래스

Lotto프로그램 만들때 등등 사용 가능

  • 중복을 허용하지 않고, 순서를 가지지 않는다. >> 대신 데이터를 오름차순 정렬하여 저장하고 있다는 특징이 있다.

  • 데이터 추가, 삭제에는 시간이 더 걸리지만, 검색과 정렬이 뛰어나다.

  • TreeSet 은 기본적으로 nature ordering을 지원하며 Comparator인터페이스를 구현하여 정렬방법을 임의로 지정해 줄 수 있다.

  • 이진 탐색 트리(binary search tree) 자료구조의 형태로 데이터를 저장

  • 정렬, 검색, 범위 검색에 높은 성능을 지닌다.

Set<Integer> treeSet = new TreeSet<>();

treeSet.add(7);
treeSet.add(4);
treeSet.add(9);
treeSet.add(1);
treeSet.add(1); //입력되는 값이 TreeSet내부에 존재하지 않는다면 그 값을 추가한 뒤 true를 반환하고 내부에 값이 존재하면 false를 반

treeSet.toString(); // [1, 4, 5, 7, 9] - 자료가 알아서 정렬됨

//TreeSet선언
TreeSet<Integer> set1 = new TreeSet<Integer>();//TreeSet생성
TreeSet<Integer> set2 = new TreeSet<>();//new에서 타입 파라미터 생략가능
TreeSet<Integer> set3 = new TreeSet<Integer>(set1);//set1의 모든 값을 가진 TreeSet생성
TreeSet<Integer> set4 = new TreeSet<Integer>(Arrays.asList(1,2,3));//초기값 지정

TreeSet을 생성하기 위해서는 저장할 객체 타입을 파라미터로 표기하고 기본 생성자를 호출하면 된다.

  • 선언 시 크기를 지정해줄 수 없다.

HashSet은 중복을 허용하지 않기 때문에 equals(), hashCode()메서드를 이용해 객체의 존재여부를 확인해 객체에 저장하는 것이다.
Comparable과 Comparator인터페이스는 객체의 정렬기준을 방식을 정의할 때 사용하는 인터페이스로 위와는 각각 다른 기능으로 존재한다.

LinkedHashSet클래스

  • 순서를 가지는 Set 자료

  • 추가된 순서 또는 가장 최근에 접근한 순서대로 접근 가능

  • 만일 중복을 제거하는 동시에 저장한 순서를 유지하고 싶다면, HashSet대신 LinkedHashSet을 사용하면 된다.

Set<Integer> linkedHashSet = new LinkedHashSet<>();

linkedHashSet.add(10);
linkedHashSet.add(20);
linkedHashSet.add(30);
linkedHashSet.add(10); // 중복된 수 추가

linkedHashSet.size(); // 3 - 중복된건 카운트 X

linkedHashSet.toString(); // [10, 20, 30] - 대신 자료가 들어온 순서대로 출력

Comparable 인터페이스

Java에서 제공하는 정렬 가능한 클래스들은 모두 Comparable인터페이스를 구현하고 있다.

기본 정렬기준을 구현하는데 사용

int/long오름차순, String 사전순 정렬

  • 정렬할 객체 클래스에 Comparable인터페이스를 구현(implements)한 후, compareTo()메서드를 오버라이딩하여 구현한다.

  • Integer, Double, String 모두 Comparable 인터페이스를 구현하고 있다.

    • 사용자 정의 클래스도 Comparable을 구현하면 자동 정렬이 가능하다.

      • int caompareTo(T o) : 자기 자신을 기준으로 주어진 객체와 같으면 0 리턴
        자기 자신을 기준으로 주어진 객체보다 적으면 음수 리턴
        자기 자신을 기준으로 주어진 객체보다 크면 양수 리턴
  • Arrays.sort( )는 String의 Comparable구현에 의해 정렬된 것이다.

    • Comparable을 구현하고 있는 클래스들은 같은 타입의 인스턴스끼리 서로 비교할 수 있어, 기본적으로 오름차순 형태로 구현된다.

    • Comparable을 구현한 클래스의 인스턴스들은 Collections.sort()메서드를 사용해 정렬할 수 있다.

예제 1 - 나이 오름차순으로 정렬

package com.test.memo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

class Person implements Comparable<Person> {
    private String name;
    private int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    String getName() {
        return name;
    }

    int getAge() {
        return age;
    }

    String getPrint() {
        return name + " " + age;
    }

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }

}

public class Practice2 {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();

        list.add(new Person("마우스", 23));
        list.add(new Person("노트", 28));
        list.add(new Person("물", 14));
        list.add(new Person("의자", 35));
        list.add(new Person("스크린", 54));
        list.add(new Person("가시오", 17));
        list.add(new Person("김유빈", 19));
        list.add(new Person("정현지", 24));

        Collections.sort(list); // 적용하지 않으면 compareTo()가 실행되지 않아 정렬 되지 않는다.
        // Arrays.sort(list);> 에러 >> 배열의 요소가 원시 자료형인경우 정렬 가능하기 때문에 Wrapper클래스는 사용 X
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).getPrint());
        }
    }
}

예제 2 - 이름 사전순 정렬

package com.test.memo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

class Person implements Comparable<Person> {
    private String name;
    private int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    String getName() {
        return name;
    }

    int getAge() {
        return age;
    }

    String getPrint() {
        return name + " " + age;
    }

    @Override
    public int compareTo(Person o) {
        return this.name.compareTo(o.name);
    }

}

public class Practice2 {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();

        list.add(new Person("가우스", 23));
        list.add(new Person("노트", 28));
        list.add(new Person("다", 14));
        list.add(new Person("사자", 35));
        list.add(new Person("마크린", 54));
        list.add(new Person("바시오", 17));
        list.add(new Person("아유빈", 19));
        list.add(new Person("정현지", 24));

        Collections.sort(list); // 적용하지 않으면 compareTo()가 실행되지 않아 정렬 되지 않는다.
        // Arrays.sort(list);> 에러 >> 배열의 요소가 원시 자료형인경우 정렬 가능하기 때문에 Wrapper클래스는 사용 X
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).getPrint());
        }
    }
}

예제 3 - 이름 순으로 정렬, 이름이 같을 경우, 나이 순으로 정렬 >> 보통 이렇게 정렬 조건을 추가하면 Comparator을 더 많이 사용

package com.test.memo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

class Person implements Comparable<Person> {
    private String name;
    private int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    String getName() {
        return name;
    }

    int getAge() {
        return age;
    }

    String getPrint() {
        return name + " " + age;
    }

    @Override
    public int compareTo(Person o) {
        int result = this.name.compareTo(o.name);
        if (result == 0)
            result = this.age - o.age;
        return result;
    }

}

public class Practice2 {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();

        list.add(new Person("가시오", 23));
        list.add(new Person("나비야", 28));
        list.add(new Person("마", 14));
        list.add(new Person("가시오", 35));
        list.add(new Person("다이슨", 54));
        list.add(new Person("마", 17));
        list.add(new Person("아야", 19));
        list.add(new Person("자자 윀업", 24));

        Collections.sort(list); // 적용하지 않으면 compareTo()가 실행되지 않아 정렬 되지 않는다.
        // Arrays.sort(list);> 에러 >> 배열의 요소가 원시 자료형인경우 정렬 가능하기 때문에 Wrapper클래스는 사용 X
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).getPrint());
        }
    }
}

compareTo()메소드 반환은 1, 0, -1로 했을 경우 문제점

  • 뺼셈 과정에서 자료형의 범위를 넘어버리는 경우가 발생할 수 있다.

    • EX) o1 = 1, o2 = -2,147,483,648일 경우
  • 권장사항은 if문을 통한 대소 비교

mport java.util.*;

public class Test {
    public static void main(String[] args) {
        Student hong = new Student("홍길동", 20);
        Student kim = new Student("김철수", 22);

        // 우리가 재정의 했던 compareTo() 메서드를 호출하고
        // 매개변수로 kim 객체를 전달해주면
        // compareTo() 의 내부에 작성한 비교 코드를 통해
        // 양수, 음수, 0 값중 하나를 반환해준다.
        // 1: 자기 자신, 즉 hong이 더 크다.
        // 0: hong과 kim의 크기는 같다.
        // -1: kim의 크기가 더 크다.
        int compareValue = hong.compareTo(kim);

        if (compareValue > 0)
            System.out.println("hong 객체가 kim 객체보다 크다.");
        else if (compareValue < 0)
            System.out.println("hong 객체가 kim 객체보다 작다.");
        else
            System.out.println("hong 객체 kim 객체의 크기는 같다.");
    }
}


class Student implements Comparable<Student>{
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Student o) {
        if (this.age > o.age)
            return 1;
        else if (this.age < o.age)
            return -1;
        else
            return 0;
    }
}

정리

  1. 자기 자신과 compareTo()의 매개변수로 받은 객체를 비교
  1. compareTo()를 반드시 구현해야 함

Comparator 인터페이스

기본 정렬기준 외에 다른 기준으로 정렬하고자 할때 사용

int/long 내림차순, String사전역순, 여러개의 기준으로 정렬

Comparable인터페이스를 구현하지 않은 클래스의 인스턴스들을 비교하거나, 기존의 정렬 기준과 다른 정렬 기준을 사용하고자 할 때 사용된다.

  • Comparator인터페이스를 구현한 별도의 클래스를 정의하거나, 람다식을 사용하여 정렬기준을 지정할 수 있다.

📌 매개변수로 받은 두개의 객체를 서로 비교

// Student 클래스는 Comparator<T> 인터페이스를 구현 받는다.
class Student implements Comparator<Student>{
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Comparator<T>에서 정의된 compare 재정의
    @Override
    public int compare(Student o1, Student o2) {
       if (o1.age > o2.age)
               return 1;
        else if (o1.age == o2.age)
            return 0;
        else
            return -1;
    /*
        return o1.age - o2.age;
    */
    }
}
  • Comparable 과 비교 방법도 같다. 차이점은 자기 자신과의 비교
  • compare() 또한 주석처리된 부분처럼 작성 가능
import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        Student hong = new Student("홍길동", 20);
        Student kim = new Student("김철수", 22);

        int compareValue = hong.compare(hong, kim);
        if (compareValue > 0)
            System.out.println("hong 객체가 kim 객체보다 크다.");
        else if (compareValue < 0)
            System.out.println("hong 객체가 kim 객체보다 작다.");
        else
            System.out.println("hong 객체 kim 객체의 크기는 같다.");
    }
}


class Student implements Comparator<Student>{
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compare(Student o1, Student o2) {
        if (o1.age > o2.age)
            return 1;
        else if (o1.age == o2.age)
            return 0;
        else 
            return -1;
    }
}
  • hong.compare(hong, kim) 을 보면 객체에 상관 없이 독립적으로 두 객체를 매개변수로 전달에 비교한다.

특정 클래스 내부에 Comparator인터페이스를 상속받아, compare() 를 구현 해준다면, 해당 compare()를 사용하기 위해선 해당 클래스 타입의 객체를 생성해 주어야한다.

public class Main {
    public static void main(String[] args) {
        Student hong = new Student("홍길동", 20);
        Student kim = new Student("김철수", 22);
        Student lee = new Student("이발수", 24);

        int compareValue = hong.compare(hong, lee);
        int compareValue = hong.compare(kim, kim);
        int compareValue = hong.compare(kim, lee);
    }
}
  • 독립된 객체 두 개를 비교하는데, 굳이 객체의 메서드로 들어가 객체를 통해 호출하는것은 효율이 좋지 않아보인다.

익명 클래스 사용

예제 1

public class Main {
    public static void main(String[] args) {
        Student hong = new Student("홍길동", 20);
        Student kim = new Student("김철수", 22);
        Student choi = new Student("최치김", 35);
        Student lee = new Student("이문복", 28);
        Student jung = new Student("정순하", 50);

        // 리스트에 학생 객체들 저장.
        Student[] list = new Student[5];
        list[0] = hong;
        list[1] = jung;
        list[2] = choi;
        list[3] = lee;
        list[4] = kim;

        // 정렬 전
        System.out.println(Arrays.toString(list));

        Comparator<Student> comp = new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                if (o1.age > o2.age)
                    return 1;
                else if (o1.age == o2.age)
                    return 0;
                else
                    return -1;
            }
        };

        // 정렬
        Arrays.sort(list, comp);

        // 정렬 후
        System.out.println(Arrays.toString(list));
    }
}


class Student {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return this.name + " " + this.age;
    }
}
  • main() 내부에 Comparator<Student>를 구현한 익명 클래스를 생성한뒤 Arrays.sort()의 두번째 매개변수로 전달
  • 오버로딩된 sort()를 통해 Student 클래스 내부에 정의된 Comparable<>의 추상 메서드 compareTo 존재 유무 상관 없이, Comparator 익명 클래스의 compare() 메서드를 통해 배열 요소의 정렬 수행.

예제 2

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Main {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("Alice", 30));
        personList.add(new Person("Bob", 25));
        personList.add(new Person("Charlie", 35));

        // Comparator 인터페이스를 직접 구현하여 나이를 기준으로 내림차순으로 정렬합니다.
        Comparator<Person> ageComparator = new Comparator<Person>() {
            @Override
            public int compare(Person p1, Person p2) {
                return Integer.compare(p2.getAge(), p1.getAge()); // 내림차순으로 정렬
            }                                                    //오름차순으로 하려면 p1,p2로
        };

        // Collections.sort() 메서드를 사용하여 리스트를 정렬합니다.
        Collections.sort(personList, ageComparator);

        // 정렬된 리스트를 출력합니다.
        for (Person person : personList) {
            System.out.println(person);
        }
    }
}

  • compare()메서드에서 return을 Integer.compare()~한 이유는(호출한 이유) 기본적으로 Integer클래스가 두 정수 값을 비교하는 정적 메서드 compare를 제공하기 때문에 Integer.compare를 사용하여 두 정수를 비교하는 것이 일반적이고 편리하다.

Comparable / Comparator

  • 클래스의 정렬 기준이 고유하게 존재하고, 클래스 자체에 논리적으로 포함되어 있는 경우 >> Comparable

    • ex) String클래스는 이미 Comparable을 구현하여 알파벳 순서로 정렬된다.
    • 보통 클래스 내부에 한번만 재정의 한다. 그래서 보통 해당 클래스의 가장 기본(Default) 비교로 사용
  • 정렬 기준이 클래스 외부에서 정의되어야 하거나 다양한 정렬 기준이 필요한 경우 >> Comparator

    • 나이 순서대로 정렬하거나, 급여 순서등 새로운 정렬 기준을 제공할 때 사용한다.
    • 보통 Comparable을 사용하다가, 특별한 정렬이 필요하다면 그때 쓰이는 경우가 많다.

List예제

예제 1

import java.util.ArrayList;

class IntroArrayList
{
    public static void main(String[] args)
    {
        ArrayList<Integer> list=new ArrayList<Integer>();

        /* 데이터의 저장 */
        list.add(11); //오토박싱 오토언박싱 진
        list.add(Integer.valueOf(22));
        list.add(Integer.valueOf(33));
        //list의 길이는 현재 3인
        /* 데이터의 참조 */
        System.out.println("1차 참조");
        for(int i=0; i<list.size(); i++)
            System.out.println(list.get(i));

        /* 데이터의 삭제 */
        list.remove(0);//0번째 인덱스를 삭제 
        System.out.println("2차 참조");
        for(int i=0; i<list.size(); i++)
            System.out.println(list.get(i));    
    }
}

예제 2

import java.util.LinkedList;

class IntroLinkedList
{
    public static void main(String[] args)
    {
        List<Integer> list = new LinkedList<Integer>(); //상위클래스로 사용하는 이유는 상
        //LinkedList<Integer> list=new LinkedList<Integer>();
        // ArrayList<Integer> list = new ArrayList<Integer>();

        /* 데이터의 저장 */
        list.add(Integer.valueOf(11));
        list.add(Integer.valueOf(22));
        list.add(Integer.valueOf(33));

        /* 데이터의 참조 */ //> 아래와 같이 반복문을 써서 출력하는것보다 foreach나 변수에 담아서 출력하는게 좋다. 
        System.out.println("1차 참조");
        for(int i=0; i<list.size(); i++)
            System.out.println(list.get(i));

        for(Integer num : list)//이걸 더 선호 간단 
            System.out.println(num);

        /* 데이터의 삭제 */
        list.remove(0);
        System.out.println("2차 참조");
        for(int i=0; i<list.size(); i++)
            System.out.println(list.get(i));    //0번째를 삭제했기 때문에 0번쨰에 22가 들어가있는
    }
}
  • 상위클래스 List로 사용하는 이유는, 호환성 때문이다.

    • List list = new LinkedList(); 이와같이 상위클래스에서 하위클래스를 사용할 수 있기 때문이다.

예제 3 SingleLinkedList

단점 : 단방향 - 참조가 어렵다. >> 선형 Linked List == SingleLinkedList

장점: 삽입 삭제가 쉽다.

class Box<T>
{
    public Box<T> nextBox;
    T item;

    public void store(T item) { this.item=item; }
    public T pullOut() { return item; }
}

class SoSimpleLinkedListImpl
{
    public static void main(String[] args)
    {
        Box<String> boxHead=new Box<String>();
        boxHead.store("First String");

        boxHead.nextBox=new Box<String>();
        boxHead.nextBox.store("Second String");

        boxHead.nextBox.nextBox=new Box<String>();
        boxHead.nextBox.nextBox.store("Third String");

        Box<String> tempRef;

        /* 두 번째 박스에 담긴 문자열 출력 과정 */
        tempRef=boxHead.nextBox;
        System.out.println(tempRef.pullOut());

        /* 세 번째 박스에 담긴 문자열 출력 과정 */
        tempRef=boxHead.nextBox;
        tempRef=tempRef.nextBox;
        System.out.println(tempRef.pullOut());
    }
}
  • boxHead.nextBoxBox<String> 형식의 새로운 상자 객체를 생성하게 됩니다. 그 다음에 store() 메서드를 사용하여 해당 상자에 문자열 "Second String"을 저장합니다. 그 후에 다음 상자를 위해 boxHead.nextBox.nextBox에 새로운 Box<String> 객체를 생성하고, 이를 이용하여 다음 문자열을 저장할 수 있습니다.

    • nextBox에 new Box\(); 객체를 할당하면, 그것은 새로운 상자를 가리키는 참조로업데이트 된다.

예제 4

Iterable인터페이스 안에 iterator()메서드를 가지고 있다.(collection이 이를 상속)

import java.util.Iterator;
import java.util.LinkedList;

class IteratorUsage
{
    public static void main(String[] args)
    {
        LinkedList<String> list=new LinkedList<String>();
        list.add("First");
        list.add("Second");
        list.add("Third");
        list.add("Fourth");

        Iterator<String> itr=list.iterator();//iterator();는 일회용, 커서가 마지막이되면 끝 

        System.out.println("반복자를 이용한 1차 출력과 \"Third\" 삭제");
        while(itr.hasNext())
        {
            String curStr=itr.next();//First객체를 반환하고 다음으로 커서가 이동한다.
            System.out.println(curStr);
            if(curStr.compareTo("Third")==0)
                itr.remove();
        }

        System.out.println("\n\"Third\" 삭제 후 반복자를 이용한 2차 출력 ");        
        itr=list.iterator();//위에서 끝났으니까 다시 가져와야한
        while(itr.hasNext())
            System.out.println(itr.next());
    }
}

예제 5

List는 저장순서가 존재

import java.util.Iterator;
import java.util.LinkedList;

class PrimitiveCollection
{
    public static void main(String[] args)
    {
        LinkedList<Integer> list=new LinkedList<Integer>();
        list.add(10);        // Auto Boxing
        list.add(20);        // Auto Boxing
        list.add(30);        // Auto Boxing

        Iterator<Integer> itr=list.iterator();

        while(itr.hasNext())
        {
            int num=itr.next();        // Auto Unboxing
            System.out.println(num);
        }
    }
}

HashSet예제

컬렉션 프레임워크에 set이 들어가면 저장 순서가 없고, 중복이 불가능하다.

중복의 기준을, Object의 hashCode()와 equals()메서드를 이용해준다.

HashSet은 해시 테이블을 기반으로 구현되어 있어 빠른 검색, 삽입, 삭제 연산을 제공한다.

예제 1

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

class UsefulIterator
{
    public static void main(String[] args)
    {
        HashSet<String> set=new HashSet<String>();
        set.add("First");
        set.add("Second");
        set.add("Third");
        set.add("Fourth");

        Iterator<String> itr=set.iterator();

        System.out.println("반복자를 이용한 1차 출력과 \"Third\" 삭제");
        while(itr.hasNext())
        {
            String curStr=itr.next();
            System.out.println(curStr);
            if(curStr.compareTo("Third")==0)
                itr.remove();
        }

        System.out.println("\n\"Third\" 삭제 후 반복자를 이용한 2차 출력 ");        
        itr=set.iterator();
        while(itr.hasNext())
            System.out.println(itr.next());
    }
}

예제 2

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

class SetInterfaceFeature
{
    public static void main(String[] args)
    {
        HashSet<String> hSet=new HashSet<String>();

        hSet.add("First");
        hSet.add("Second");
        hSet.add("Third");
        hSet.add("First");

        System.out.println("저장된 데이터 수: "+hSet.size());

        Iterator<String> itr=hSet.iterator();
        while(itr.hasNext())
            System.out.println(itr.next());
    }
}
  • 중복이 허용되지 않으므로, hSet.size()는 3이 된다.

예제 3

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

class SimpleNumber
{
    int num;
    public SimpleNumber(int n)
    {
        num=n;
    }
    public String toString()
    {
        return String.valueOf(num);
    }

}

class HashSetEqualityOne
{
    public static void main(String[] args)
    {
        HashSet<SimpleNumber> hSet=new HashSet<SimpleNumber>();
        hSet.add(new SimpleNumber(10));
        hSet.add(new SimpleNumber(20));
        hSet.add(new SimpleNumber(20));

        System.out.println("저장된 데이터 수: "+hSet.size());//3

        Iterator<SimpleNumber> itr=hSet.iterator();
        while(itr.hasNext())
            System.out.println(itr.next());
    }
}
  • SimpleNumber는 개발자가 만든 클래스이므로, 판별하는 기준이 없기때문에 저렇게 20이 두개여도 같은 객체로 보지않는다.

예제 4

Hashset에서 중복이 있는지 없는지 비교하기위해

Object클래스의 hashCode()와 equals()를 오버라이딩 해야한다.

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

class SimpleNumber
{
    int num;
    public SimpleNumber(int n)
    {
        num=n;
    }
    public String toString()
    {
        return String.valueOf(num);
    }
    public int hashCode()
    {
        return num%3;
    }
    public boolean equals(Object obj)
    {
        SimpleNumber comp=(SimpleNumber)obj;
        if(comp.num==num)
            return true;
        else
            return false;        
    }
}

class HashSetEqualityTwo
{
    public static void main(String[] args)
    {
        HashSet<SimpleNumber> hSet=new HashSet<SimpleNumber>();
        hSet.add(new SimpleNumber(10));
        hSet.add(new SimpleNumber(20));
        hSet.add(new SimpleNumber(20));

        System.out.println("저장된 데이터 수: "+hSet.size());

        Iterator<SimpleNumber> itr=hSet.iterator();
        while(itr.hasNext())
            System.out.println(itr.next());
    }
}
  • 객체를 생성하기 전에 hashCode()를 먼저 호출해 값의 나머지를 반환하고, equals메서드를 호출해서 해당 나머지들의 값들과 비교해 반환한다.

    • 그렇게 객체를 생성해 비교를해서, True가 반환되면 객체가 생성되지 않는다.

TreeSet클래스

요소를 정렬된 순서로 저장하고, 중복을 허용하지 않는다. >> 추가해도 내부적으로 정렬되어 저장된다.

예제 1

import java.util.Iterator;
import java.util.TreeSet;

class SortTreeSet
{
    public static void main(String[] args)
    {
        TreeSet<Integer> sTree=new TreeSet<Integer>();
        sTree.add(1);
        sTree.add(2);
        sTree.add(4);
        sTree.add(3);
        sTree.add(2);

        System.out.println("저장된 데이터 수: "+sTree.size());//4

        Iterator<Integer> itr=sTree.iterator();
        while(itr.hasNext())
            System.out.println(itr.next());
    }
}
  • 정렬된상태로 출력된다 >> 1 2 3 4

예제 2

import java.util.Iterator;
import java.util.TreeSet;

class Person implements Comparable<Person>
{
    String name;
    int age;

    public Person(String name, int age)
    {
        this.name=name;
        this.age=age;
    }
    public void showData()
    {
        System.out.printf("%s %d \n", name, age);
    }
    public int compareTo(Person p)//나이를 기준으로 오름차순 
    {
        if(age>p.age)
            return 1;
        else if(age<p.age)
            return -1;
        else    
            return 0;
    }//return age - p.age; 라고 표현하는게 더 좋다(같은 식)
}

class ComparablePerson 
{
    public static void main(String[] args)
    {
        TreeSet<Person> sTree=new TreeSet<Person>();
        sTree.add(new Person("Lee", 24));
        sTree.add(new Person("Hong", 29));
        sTree.add(new Person("Choi", 21));

        Iterator<Person> itr=sTree.iterator();
        //iterator는 컬렉션내의 요소들을 차례로 접근 , 다음요소의 존재여부를 확인하고, 다음요소를 가져온  
        while(itr.hasNext())
            itr.next().showData();
    }
}
  • Comparable인터페이스를 구현해 compareTo()메서드를 이용해 정렬시켜야한다.

예제 2-1

문자의 사전적 순이 아닌 문자열 길이로 비교하고싶다면?

import java.util.TreeSet;
import java.util.Iterator;

class MyString implements Comparable<MyString>
{
    String str;

    public MyString(String str)
    {
        this.str=str;
    }

    public int getLength()
    {
        return str.length();
    }

    public int compareTo(MyString mStr)
    {
        if(getLength()>mStr.getLength())
            return 1;
        else if(getLength()<mStr.getLength())
            return -1;
        else
            return 0;

        /*
         * return getLength()-mStr.getLength();
         */
    }

    public String toString()
    {
        return str;
    }
}

class ComparableMyString
{
    public static void main(String[] args)
    {
/*
        TreeSet<String> tSet=new TreeSet<String>();        
        tSet.add("Orange");
        tSet.add("Apple");
        tSet.add("Dog");
        tSet.add("Individual");

        Iterator<String> itr=tSet.iterator();
        while(itr.hasNext())
            System.out.println(itr.next());
*/
        TreeSet<MyString> tSet=new TreeSet<MyString>();        
        tSet.add(new MyString("Orange"));
        tSet.add(new MyString("Apple"));
        tSet.add(new MyString("Dog"));
        tSet.add(new MyString("Individual"));

        Iterator<MyString> itr=tSet.iterator();
        while(itr.hasNext())
            System.out.println(itr.next());

    }
}

예제3

Comparator인터페이스를 구현해 compare()메서드를 이용한다.

두개의 객체를 비교할 때 비교 규칙을 정의해서 비교할 수 있다.

import java.util.TreeSet;
import java.util.Iterator;
import java.util.Comparator;

class StrLenComparator implements Comparator<String>
{
    public int compare(String str1, String str2)
    {
        if(str1.length()> str2.length())
            return 1;
        else if(str1.length()< str2.length())
            return -1;
        else
            return 0;

        /*
         * return str1.length()-str2.length();
         */
    }
}

class IntroComparator
{
    public static void main(String[] args)
    {
        TreeSet<String> tSet=new TreeSet<String>(new StrLenComparator());//얘를 기준으로 정렬         
        tSet.add("Orange");
        tSet.add("Apple");
        tSet.add("Dog");
        tSet.add("Individual");

        Iterator<String> itr=tSet.iterator();
        while(itr.hasNext())
            System.out.println(itr.next());
    }
}

StrLenComparator()클래스에서 정의한 비교 규칙을 가지고 비교하는 것이다.

"문자열을 사전편찬 순서가 아닌, 길이 순으로 정렬해서 TreeSet에 저장하고 싶다."

이를 위해서 "ComparableMyString.java" 소스에서 MyString이라는 String의 Wrapper 클래스를 정의했는데, TreeSet의 정렬 기준을 변경하기 위해서 MyString이라는 별도의 클래스를 정의한다는 것이, 사실 이치에는 맞지 않는다. 오히려 다음과 같이 요구할 수 있어야 정상 아니겠는가?

"야!TreeSet 인스턴스! 사전편찬 순서 말고, 길이 순으로 문자열을 졍렬해라!"

이러한 유형의 요구를 위해 정의된 것이 Comparator인터페이스이다. 이 인터페이스는 다음과 같이
정의되어 있다.

interface Comparator
{
int compare(T obj1, T obj2);
boolean equals(Object obj);
}

위의 인터페이스 중에서 equals 메소드는 신경 쓰지 않아도 된다. 이 인터페이스를 구현하는 모든 클래스는
Object 클래스를 상속하기 때문에, Object 클래스의 equals 메소드가 위의 equals 메소드를 구현하는 꼴이
되기 때문이다. 따라서 compare 메소드만 신경을 쓰면 된다. compare 메소드의 구현방법은 앞서 소개한
compareTo 메소드의 구현방법과 유사하다. obj1이 크면 양수를, obj2가 크면 음수를, obj1과 obj2가 같으면
0을 반환하면 된다. 물론 크고 작음에 대한 기준은 여러분이 결정할 몫이다.


ArrayList 문제 - 전화번호부

  1. 전화번호부 배열을 ArrayList로 변경하시오.
package com.test.memo;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

class MenuChoiceException extends Exception {
    private int choice;

    MenuChoiceException(int choice) {
        super("유효하지 않은 메뉴 값입니다.");
        this.choice = choice;
    }

    public void showWrongMenu() {
        System.out.println(choice + "에 해당하는 선택은 존재하지 않습니다.");
        System.out.println("메뉴 선택을 처음부터 다시 진행합니다.");
    }
}

class PhoneInfo {
    private String name;
    private String phone;

    PhoneInfo(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }

    public String getName() {
        return name;
    }

    public void showPhoneInfo() {
        System.out.println("이름 : " + name);
        System.out.println("전화번호 : " + phone);
    }
}

class PhoneUnivInfo extends PhoneInfo {
    private String major;
    private int year;

    PhoneUnivInfo(String name, String phone, String major, int year) {
        super(name, phone);
        this.major = major;
        this.year = year;
    }

    public void showPhoneInfo() {
        super.showPhoneInfo();
        System.out.println("전공 : " + major);
        System.out.println("학년 : " + year);
    }
}

class PhoneCompanyInfo extends PhoneInfo {
    private String company;

    PhoneCompanyInfo(String name, String phone, String company) {
        super(name, phone);
        this.company = company;
    }

    public void showPhoneInfo() {
        super.showPhoneInfo();
        System.out.println("회사 : " + company);
    }
}

class PhoneBook {
    private static PhoneBook pb;
    private List<PhoneInfo> pInfo;
//    private int cntOfPhone;
//    private int sizePhoneInfo;

    private PhoneBook(int sizePhoneInfo) {
        pInfo = new ArrayList<>(sizePhoneInfo);// 배열로 이루어진 List
//        cntOfPhone = 0;
//        this.sizePhoneInfo = sizePhoneInfo;
    }

    public static PhoneBook getPhoneBookInst(int sizePhoneInfo) {
        if (pb == null)
            pb = new PhoneBook(sizePhoneInfo);
        return pb;
    }

    public void inputPhoneInfo(PhoneInfo pInfo) {
        int i = 0, j = 0;
//        if (cntOfPhone >= sizePhoneInfo) {
//            System.out.println("더 이상 저장할 수 없습니다.");
//            return;
//        }
        int cnt = this.pInfo.size();
        for (i = 0; i < cnt; i++) {
            if (this.pInfo.get(i).getName().compareTo(pInfo.getName()) > 0) {
//                for (j = cntOfPhone - 1; j >= i; j--) {
//                    pInfo.add(j + 1) = pInfo.get(j);
//                }
                break;
            }
        }
//        this.pInfo[i] = pInfo;
//        cntOfPhone++;
        this.pInfo.add(i, pInfo);
    }

    public void searchPhoneInfo(String name) {
        int result = search(name);
        if (result != -1)
            pInfo.get(result).showPhoneInfo();
        else
            System.out.println("찾으시는 데이터가 없습니다.");
    }

    public void deletePhoneInfo(int idx) {
        // int i = 0;
//        for (i = idx; i < cntOfPhone - 1; i++)
//            pInfo[i] = pInfo[i + 1];
//        pInfo[i] = null;
//        cntOfPhone--;
        pInfo.remove(idx);
        System.out.println("삭제가 완료되었습니다.");
    }

    public int search(String name) {
        for (int i = 0; i < pInfo.size(); i++) {
            if (pInfo.get(i).getName().compareTo(name) == 0)
                return i;
        }
        return -1;
    }

    public void showAllPhoneInfo() {
        for (int i = 0; i < pInfo.size(); i++)
            pInfo.get(i).showPhoneInfo();
    }
}

interface PhoneMenuString {
    int INPUT_PHONEINFO = 1;
    int SEARCH_PHONEINFO = 2;
    int DELETE_PHONEINFO = 3;
    int SHOW_ALL_PHONEINFO = 4;
    int PROGRAM_QUIT = 5;

    int GENERAL = 1;
    int UNIVERCITY = 2;
    int COMPANY = 3;

    int YES = 1;
    int NO = 2;
}

class PhoneUI {
    private static final int MAX_CNT = 100;
    public static Scanner sc = new Scanner(System.in);
    private static PhoneBook pb = PhoneBook.getPhoneBookInst(MAX_CNT);

    private PhoneUI() {
    }

    public static void mainMenu() {
        System.out.println("선택하세요...");
        System.out.println("1. 데이터 입력");
        System.out.println("2. 데이터 검색");
        System.out.println("3. 데이터 삭제");
        System.out.println("4. 모든 데이터 보기");
        System.out.println("5. 프로그램 종료");
        System.out.print("선택 : ");
    }

    public static void inputMenu() {
        System.out.println("1. 일반, 2. 대학, 3. 회사");
    }

    public static void inputMenuChoice() throws MenuChoiceException {
        int choice = 0;
        choice = sc.nextInt();
        sc.nextLine();
        if (choice < PhoneMenuString.GENERAL || choice > PhoneMenuString.COMPANY)
            throw new MenuChoiceException(choice);
        switch (choice) {
        case PhoneMenuString.GENERAL:
            inputGeneralPhoneInfo();
            break;
        case PhoneMenuString.UNIVERCITY:
            inputUniversityPhoneInfo();
            break;
        case PhoneMenuString.COMPANY:
            inputCompanyPhoneInfo();
            break;
        }
    }

    public static void inputGeneralPhoneInfo() {
        String name;
        String phone;

        System.out.println("데이터 입력을 시작합니다.");
        System.out.print("이름 : ");
        name = sc.nextLine();
        System.out.print("전화번호 : ");
        phone = sc.nextLine();
        System.out.println("데이터 입력이 완료되었습니다.");
        pb.inputPhoneInfo(new PhoneInfo(name, phone));
    }

    public static void inputUniversityPhoneInfo() {
        String name;
        String phone;
        String major;
        int year;

        System.out.println("데이터 입력을 시작합니다.");
        System.out.print("이름 : ");
        name = sc.nextLine();
        System.out.print("전화번호 : ");
        phone = sc.nextLine();
        System.out.print("전공 : ");
        major = sc.nextLine();
        System.out.print("학년 : ");
        year = sc.nextInt();
        sc.nextLine();
        System.out.println("데이터 입력이 완료되었습니다.");
        pb.inputPhoneInfo(new PhoneUnivInfo(name, phone, major, year));
    }

    public static void inputCompanyPhoneInfo() {
        String name;
        String phone;
        String company;

        System.out.println("데이터 입력을 시작합니다.");
        System.out.print("이름 : ");
        name = sc.nextLine();
        System.out.print("전화번호 : ");
        phone = sc.nextLine();
        System.out.print("회사 : ");
        company = sc.nextLine();
        System.out.println("데이터 입력이 완료되었습니다.");
        pb.inputPhoneInfo(new PhoneCompanyInfo(name, phone, company));
    }

    public static void searchPhoneInfo() {
        String name;
        System.out.println("데이터 검색을 시작합니다.");
        System.out.println("검색하시고자 하는 이름을 입력하세요.");
        name = sc.nextLine();
        pb.searchPhoneInfo(name);
    }

    public static void deletePhoneInfo() {
        String name;
        int result = 0;
        int answer = 0;
        System.out.println("검색하시고자 하는 이름을 입력하세요.");
        name = sc.nextLine();
        result = pb.search(name);
        if (result != -1) {
            System.out.println("정말 삭제하시겠습니까? 1. Yes 2. No");
            answer = sc.nextInt();
            sc.nextLine();
            switch (answer) {
            case PhoneMenuString.YES:
                pb.deletePhoneInfo(result);
                break;
            case PhoneMenuString.NO:
                break;
            default:
                System.out.println("잘못 누르셨습니다.");
            }
        } else
            System.out.println("삭제하시려는 데이터가 없습니다.");
    }

    public static void showAllPhoneInfo() {
        pb.showAllPhoneInfo();
    }
}

class Practice {
    public static void main(String[] args) {
        int choice = 0;

        while (true) {
            try {
                PhoneUI.mainMenu();
                choice = PhoneUI.sc.nextInt();
                PhoneUI.sc.nextLine();
                if (choice < PhoneMenuString.INPUT_PHONEINFO || choice > PhoneMenuString.PROGRAM_QUIT)
                    throw new MenuChoiceException(choice);

                switch (choice) {
                case PhoneMenuString.INPUT_PHONEINFO:
                    PhoneUI.inputMenu();
                    PhoneUI.inputMenuChoice();
                    break;
                case PhoneMenuString.SEARCH_PHONEINFO:
                    PhoneUI.searchPhoneInfo();
                    break;
                case PhoneMenuString.DELETE_PHONEINFO:
                    PhoneUI.deletePhoneInfo();
                    break;
                case PhoneMenuString.SHOW_ALL_PHONEINFO:
                    PhoneUI.showAllPhoneInfo();
                    break;
                case PhoneMenuString.PROGRAM_QUIT:
                    return;

                }
            } catch (MenuChoiceException e) {
                System.out.println(e.getMessage());
                e.showWrongMenu();
            }
        }

    }
}
  • Vector와 비슷

ArrayList\ 클래스의 용량 설정 메서드

ArrayList 클래스는 저장되는 데이터의 수가 증가함에 따라서, 용량(데이터 저장 가능한 용량)이 자동으로 증가하는 클래스이다. >> 용량을 늘리기위해 기존 배열보다 큰 배열을 만들어 복사하는 원리기 때문에 용량이 자동으로 증가하는 것이다.

ArrayList의 메소드 중에는 저장용량의 설정을 위한 메소드가 존재한다. 따라서 여러분은 API 문서를 참조하여 이 메소드를 찾기 바란다. 그리고 다음 조건을 만족시키는 코드를 각각 구성해 보기 바란다.

  1. Integer 인스턴스를 저장할 수 있는 ArrayList를 생성하고 저장용량을 500으로 늘린다.

  2. ArrayList에 저장되어 있는 인스턴스 수의 두 배로 저장용량을 늘린다.

ArrayList<Integer> list = new ArrayList<Integer>();
list.ensureCapacity(500);
list.ensureCapacity(list.size()*2);


HashSet클래스의 중복 확인 방법

중복을 비교하기 위해서는 Object클래스의 hashCode()와 equals메서드를 오버라이딩 해야한다.

  1. 클래스의 두 인스턴스를 HashSet에 저장할 때, 두 인스턴스의 데이터(name & age)가 완전히 동일하다면, 하나만 저장되도록 hashCode 메소드와 equals 메소드를 오버라이딩 하자.

내가 푼 풀이

package com.test.memo;

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

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return name + "(" + age + "세)";
    }

    @Override
    public int hashCode() {
        return age % 3;
    }

    @Override
    public boolean equals(Object obj) {
        Person per = (Person) obj;
        //if (per.age == age && per.name.equals(name)) { 이렇게 하는게 더 좋당..
        if (per.age == age && per.name == name) {
            return true;
        }
        return false;
    }

}

public class Practice1 {

    public static void main(String[] args) {
        HashSet<Person> hSet = new HashSet<Person>();
        hSet.add(new Person("이진호", 10));
        hSet.add(new Person("이진호", 20));
        hSet.add(new Person("김명호", 20));
        hSet.add(new Person("김명호", 15));
        hSet.add(new Person("이진호", 20));
        hSet.add(new Person("김명호", 20));

        System.out.println("저장된 데이터 수 : " + hSet.size());

        Iterator<Person> itr = hSet.iterator();
        while (itr.hasNext())
            System.out.println(itr.next());

    }
}
  • name을 비교할때는 == 이 아닌 .equals()로 비교하는게 좋다.

선생님 풀이

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

class Person
{
    String name;
    int age;

    public Person(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
    public String toString()
    {
        return name + "(" + age + "세)";
    }
//    @Override
//    public int hashCode()
//    {
//        return name.hashCode() + age%7;
//    }
    @Override
    public int hashCode()
    {
        return Objects.hash(name, age);        // 전달 인자 name, age 기반 해쉬 값 반환
    }
    @Override
    public boolean equals(Object obj)
    {
        Person cmp = (Person)obj;
        if(cmp.name.equals(name) && cmp.age == age)
            return true;
        else
            return false;
    }
}

class PersonMain
{
    public static void main(String[] args)
    {
        HashSet<Person> hSet = new HashSet<Person>();
        hSet.add(new Person("이진호", 10));
        hSet.add(new Person("이진호", 20));
        hSet.add(new Person("김명호", 20));
        hSet.add(new Person("김명호", 15));
        hSet.add(new Person("이진호", 20));
        hSet.add(new Person("김명호", 20));

        System.out.println("저장된 데이터 수 : " +  hSet.size());

        Iterator<Person> itr = hSet.iterator();
        while(itr.hasNex

클래스를 정의할 때마다 이렇듯 hashCode 메소드를 정의하는 것은 번거로운 일이므로 아래와같이 입력하는게 좋다.

  • public static int hash(Object...values) >> java.util.Objects에 정의된 메소드, 전달된 인자 기반의 해쉬 값 반환
    • 위 메소드의 매개변수 선언에는 '가변 인자 선언'이 포함되어 있는데, 이는 전달되는 인자의
      수를 메소드 호출 시마다 달리할 수 있는 선언이다.

TreeSet으로 Lotto 프로그램 만들기

package com.test.memo;

import java.util.Set;
import java.util.TreeSet;

public class Practice1 {

    public static void main(String[] args) {
//        TreeSet<Integer> set = new TreeSet<Integer>();
        Set<Integer> set = new TreeSet<Integer>();

        while (set.size() < 6) {
            int num = (int) (Math.random() * 45) + 1;
            set.add(num);
        }
        System.out.println(set);
    }
}

TreeSet은 중복되지않고 정렬도 해주니까 편리하게 로또 프로그램을 구현할 수 있는것이다.

  1. 아무 생각없이 방금 배운 비교 클래스 Comparator구현받아서 하고있었다.
  2. TreeSet과 Set을 제대로 import해오지않아서 오류가 났었남.. 생각보다 허무


Comparator이용해서 새로운 정렬기준 만들기

  1. Person클래스는 기본적으로 나이를 기준으로 정렬하게끔 되어 있다.
    이를 이름을 기준으로 정렬하게끔 바꿔보자.
    주어진 소스코드에서 Person의 코드가 바뀌면 안된다.
package com.test.memo;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

class Person implements Comparable<Person> {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void showData() {
        System.out.printf("%s %d \n", name, age);
    }

    public int compareTo(Person p) {
        if (age > p.age)
            return 1;
        else if (age < p.age)
            return -1;
        else
            return 0;
    }

    public String getName() {
        return name;
    }
}
class Compare implements Comparator<Person> {

    @Override
    public int compare(Person o1, Person o2) {
        return o1.getName().compareTo(o2.getName());//앞에가 크면 양수, 뒤에가 크면 음수 

    }
}

public class Practice1 {

    public static void main(String[] args) {
        TreeSet<Person> sTree = new TreeSet<Person>();
        sTree.add(new Person("Lee", 24));
        sTree.add(new Person("Hong", 29));
        sTree.add(new Person("Choi", 21));

        Iterator<Person> itr = sTree.iterator();
        while (itr.hasNext())
            itr.next().showData();
    }
}
  • getName()메서드를 만들어서 이름을 받아와야했는데, 그것을 안넣었다..

이를 익명 클래스

package com.test.memo;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

class Person implements Comparable<Person> {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void showData() {
        System.out.printf("%s %d \n", name, age);
    }

    public int compareTo(Person p) {
        if (age > p.age)
            return 1;
        else if (age < p.age)
            return -1;
        else
            return 0;
    }

    public String getName() {
        return name;
    }

}

class Compare implements Comparator<Person> {

    @Override
    public int compare(Person o1, Person o2) {
        return o1.getName().compareTo(o2.getName());

    }
}

public class Practice1 {

    public static void main(String[] args) {
        TreeSet<Person> sTree = new TreeSet<Person>(new Comparator<Person>() {

            @Override
            public int compare(Person o1, Person o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        sTree.add(new Person("Lee", 24));
        sTree.add(new Person("Hong", 29));
        sTree.add(new Person("Choi", 21));

        Iterator<Person> itr = sTree.iterator();
        while (itr.hasNext())
            itr.next().showData();

//        Comparator com = new Comparator<Person>() {
//
//            @Override
//            public int compare(Person o1, Person o2) {
//                return o1.getName().compareTo(o2.getName());
//            }
//            
//        };
    }
}
  • 나는 새로 생성했는데, 그럴 필요 없이 TreeSet배열 만들때, 인터페이스 이름을 적고 제네릭 타입을 적고, compare()메소드를 구현해주면 되는 것이였다.

HashSet클래스를 이용해 전화번호부 배열

  1. 삭제가 제대로 안됌

    • 메인 메서드에서 이름을 전달해야하는데, 엉뚱한 객체를 전달하려해서 자료형이 맞지않아 오류가 났다.
  2. 검색이 제대로 안됌

    • 되는데 마지막에 찾으시는 데이터가 없다고 나옴 >>else에 안넣어서

내가 푼 풀이

public boolean searchPhoneInfoByName(String name)
    {
        PhoneInfo pInfo = null;
        Iterator<PhoneInfo> itr = set.iterator();
        boolean result = false;

        while(itr.hasNext())
        {            
            pInfo = itr.next();
            if(pInfo.getName().equals(name))
            {
                pInfo.printCurrentState();
                result = true;
            }
        }

        return result;
    }package com.test.memo;

import java.util.HashSet;
import java.util.Scanner;

class PhoneInfo {
    private String name;
    private String phone;

    PhoneInfo(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }

    String getName() {
        return name;
    }

    void showPhoneInfo() {
        System.out.println("이름: " + name);
        System.out.println("번호: " + phone);
    }

    @Override
    public int hashCode() {
        return phone.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)    //현재 객체와 매개변수로 전달된 객체가 동일한 객체인지 확인 
            return true;
        if (!(obj instanceof PhoneInfo))
            return false; // PhoneInfo클래스나, 그 하위 클래스의 인스턴스인지 확인
        PhoneInfo other = (PhoneInfo) obj;
        return phone.equals(other.phone);
    }

}

class PhoneUnivInfo extends PhoneInfo {
    private String major;
    private int year;

    PhoneUnivInfo(String name, String phone, String major, int year) {
        super(name, phone);
        this.major = major;
        this.year = year;
    }

    void showPhoneInfo() {
        super.showPhoneInfo();
        System.out.println("전공: " + major);
        System.out.println("학년: " + year);
    }
}

class PhoneCompanyInfo extends PhoneInfo {
    private String company;

    PhoneCompanyInfo(String name, String phone, String company) {
        super(name, phone);
        this.company = company;
    }

    void showPhoneInfo() {
        super.showPhoneInfo();
        System.out.println("회사: " + company);
    }
}

class PhoneBook {
    private static PhoneBook pb;
    private HashSet<PhoneInfo> pInfo;
    //private Set<PhoneInfo>pInfo; 이 형식으로 해야 호환성이 좋다.
    PhoneBook(int size) {
        pInfo = new HashSet<>(size);
    }

    static PhoneBook getPhoneBookInst(int sizePhoneInfo) {//이 부분은 없어도 되는 부분인
        if (pb == null) {
            pb = new PhoneBook(sizePhoneInfo);
        }
        return pb;
    }

    void inputPhoneInfo(PhoneInfo pInfo) {

            this.pInfo.add(pInfo);

    }

    PhoneInfo search(String name) {
        for (PhoneInfo ph : pInfo) {
            if (ph.getName().equals(name)) {
                return ph;
            }
        }
        return null;
    }

    void searchPhoneInfo(String name) {
        PhoneInfo result = search(name);
        if (result != null) {
            result.showPhoneInfo();
        }else {
            System.out.println("찾으시는 데이터가 없습니다.");

        }
    }

    void deletePhoneInfo(String name) {
        PhoneInfo result = search(name);
        if (result != null) {
            pInfo.remove(result); // pInfo객체에서 result값을 삭제
            System.out.println("삭제가 완료되었습니다.");
        }
        System.out.println("삭제하시려는 데이터가 없습니다.");

//        Iterator<PhoneInfo> iter = pInfo.iterator();
//        while (iter.hasNext()) {
//            PhoneInfo ph = iter.next();
//            if (ph.getName().equals(name)) {
//                iter.remove();
//                System.out.println("삭제가 완료되었습니다.");
//            }
//        }
    }

    void showAllPhoneInfo() {
        for (PhoneInfo pp : pInfo) {
            pp.showPhoneInfo();
        }
    }

}

interface PhoneMenuString {
    int INPUT_PHONEINFO = 1;
    int SEARCH_PHONEINFO = 2;
    int DELETE_PHONEINFO = 3;
    int SHOW_ALL_PHONEINFO = 4;
    int PROGRAM_QUIT = 5;

    int GENERAL = 1;
    int UNIVERCITY = 2;
    int COMPANY = 3;

    int YES = 1;
    int NO = 2;
}

class MenuChoiceException extends Exception {
    private int choice;

    MenuChoiceException(int choice) {
        super("유효하지 않은 메뉴 값입니다.");
        this.choice = choice;
    }

    public void showWrongMenu() {
        System.out.println(choice + "에 해당하는 선택은 존재하지 않습니다.");
        System.out.println("메뉴 선택을 처음부터 다시 진행합니다.");
    }
}

class PhoneUI {
    private static final int MAX_CNT = 100;
    public static Scanner sc = new Scanner(System.in);
    private static PhoneBook pb = PhoneBook.getPhoneBookInst(MAX_CNT);

    private PhoneUI() {
    }

    public static void mainMenu() {
        System.out.println("선택하세요...");
        System.out.println("1. 데이터 입력");
        System.out.println("2. 데이터 검색");
        System.out.println("3. 데이터 삭제");
        System.out.println("4. 모든 데이터 보기");
        System.out.println("5. 프로그램 종료");
        System.out.print("선택 : ");
    }

    public static void inputMenu() {
        System.out.println("1. 일반, 2. 대학, 3. 회사");
    }

    public static void inputMenuChoice() throws MenuChoiceException {
        int choice = 0;
        choice = sc.nextInt();
        sc.nextLine();
        if (choice < PhoneMenuString.GENERAL || choice > PhoneMenuString.COMPANY)
            throw new MenuChoiceException(choice);
        switch (choice) {
        case PhoneMenuString.GENERAL:
            inputGeneralPhoneInfo();
            break;
        case PhoneMenuString.UNIVERCITY:
            inputUniversityPhoneInfo();
            break;
        case PhoneMenuString.COMPANY:
            inputCompanyPhoneInfo();
            break;
        }
    }

    public static void inputGeneralPhoneInfo() {
        String name;
        String phone;

        System.out.println("데이터 입력을 시작합니다.");
        System.out.print("이름 : ");
        name = sc.nextLine();
        System.out.print("전화번호 : ");
        phone = sc.nextLine();
        System.out.println("데이터 입력이 완료되었습니다.");
        pb.inputPhoneInfo(new PhoneInfo(name, phone));
    }

    public static void inputUniversityPhoneInfo() {
        String name;
        String phone;
        String major;
        int year;

        System.out.println("데이터 입력을 시작합니다.");
        System.out.print("이름 : ");
        name = sc.nextLine();
        System.out.print("전화번호 : ");
        phone = sc.nextLine();
        System.out.print("전공 : ");
        major = sc.nextLine();
        System.out.print("학년 : ");
        year = sc.nextInt();
        sc.nextLine();
        System.out.println("데이터 입력이 완료되었습니다.");
        pb.inputPhoneInfo(new PhoneUnivInfo(name, phone, major, year));
    }

    public static void inputCompanyPhoneInfo() {
        String name;
        String phone;
        String company;

        System.out.println("데이터 입력을 시작합니다.");
        System.out.print("이름 : ");
        name = sc.nextLine();
        System.out.print("전화번호 : ");
        phone = sc.nextLine();
        System.out.print("회사 : ");
        company = sc.nextLine();
        System.out.println("데이터 입력이 완료되었습니다.");
        pb.inputPhoneInfo(new PhoneCompanyInfo(name, phone, company));
    }

    public static void searchPhoneInfo() {
        String name;
        System.out.println("데이터 검색을 시작합니다.");
        System.out.println("검색하시고자 하는 이름을 입력하세요.");
        name = sc.nextLine();
        pb.searchPhoneInfo(name);
    }

    public static void deletePhoneInfo() {
        String name;
        int answer = 0;
        System.out.println("검색하시고자 하는 이름을 입력하세요.");
        name = sc.nextLine();
        if (pb.search(name) != null) {
            System.out.println("정말 삭제하시겠습니까? 1. Yes 2. No");
            answer = sc.nextInt();
            sc.nextLine();
            switch (answer) {
            case PhoneMenuString.YES:
                pb.deletePhoneInfo(name);
                break;
            case PhoneMenuString.NO:
                break;
            default:
                System.out.println("잘못 누르셨습니다.");
            }
        } else
            System.out.println("삭제하시려는 데이터가 없습니다.");
    }

    public static void showAllPhoneInfo() {
        pb.showAllPhoneInfo();
    }
}

public class Practice1 {

    public static void main(String[] args) {
        int choice = 0;

        while (true) {
            try {
                PhoneUI.mainMenu();
                choice = PhoneUI.sc.nextInt();
                PhoneUI.sc.nextLine();
                if (choice < PhoneMenuString.INPUT_PHONEINFO || choice > PhoneMenuString.PROGRAM_QUIT)
                    throw new MenuChoiceException(choice);

                switch (choice) {
                case PhoneMenuString.INPUT_PHONEINFO:
                    PhoneUI.inputMenu();
                    PhoneUI.inputMenuChoice();
                    break;
                case PhoneMenuString.SEARCH_PHONEINFO:
                    PhoneUI.searchPhoneInfo();
                    break;
                case PhoneMenuString.DELETE_PHONEINFO:
                    PhoneUI.deletePhoneInfo();
                    break;
                case PhoneMenuString.SHOW_ALL_PHONEINFO:
                    PhoneUI.showAllPhoneInfo();
                    break;
                case PhoneMenuString.PROGRAM_QUIT:
                    return;

                }
            } catch (MenuChoiceException e) {
                System.out.println(e.getMessage());
                e.showWrongMenu();
            }
        }

    }
}

선생님 - 아예 다른 형태

package com.test.memo;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

class MenuChoiceException extends Exception {

	public MenuChoiceException(int menu) {
		super(menu + "에 해당하는 선택은 존재하지 않습니다.\n" + "메뉴 선택을 처음부터 다시 진행합니다.");
	}
}

class PhoneUnivInfo extends PhoneInfo {
	private String major;
	private int year;

	public PhoneUnivInfo(String name, String phoneNumber, String major, int year) {
		super(name, phoneNumber);
		this.major = major;
		this.year = year;
	}

	@Override
	public void printCurrentState() {
		super.printCurrentState();
		System.out.println("전공 : " + major);
		System.out.println("학년 : " + year);
	}
}

class PhoneCompanyInfo extends PhoneInfo {

	private String company;

	public PhoneCompanyInfo(String name, String phoneNumber, String company) {
		super(name, phoneNumber);
		this.company = company;
	}

	@Override
	public void printCurrentState() {
		super.printCurrentState();
		System.out.println("회사 : " + company);
	}

}

class PhoneInfo {
	private String name;
	private String phoneNumber;

	public PhoneInfo(String name, String phoneNumber) {
		this.name = name;
		this.phoneNumber = phoneNumber;
	}

	public String getName() {
		return name;
	}

	public String getPhoneNumber() {
		return phoneNumber;
	}

	public void printCurrentState() {
		System.out.println("이름 : " + name);
		System.out.println("전화번호 : " + phoneNumber);
	}

	@Override
	public boolean equals(Object obj) {
		return phoneNumber.equals(((PhoneInfo) obj).phoneNumber);
	}

	@Override
	public int hashCode() {
		return phoneNumber.hashCode();
	}
}

class PhoneBook {

	private static PhoneBook pb;
	private Set<PhoneInfo> set;

	private PhoneBook() {
		set = new HashSet<PhoneInfo>();
	}

	public static PhoneBook getPhoneBook() {
		if (pb == null)
			pb = new PhoneBook();
		return pb;
	}

	public boolean insertPhoneInfo(PhoneInfo phoneInfo) {
		return set.add(phoneInfo);
	}

	public boolean searchPhoneInfoByName(String name) {
		PhoneInfo pInfo = null;
		Iterator<PhoneInfo> itr = set.iterator();
		boolean result = false;

		while (itr.hasNext()) {
			pInfo = itr.next();
			if (pInfo.getName().equals(name)) {
				pInfo.printCurrentState();
				result = true;
			}
		}

		return result;
	}

	public boolean deletePhoneInfoByPhoneNumber(String phoneNumber) {
		PhoneInfo pInfo = null;
		Iterator<PhoneInfo> itr = set.iterator();

		while (itr.hasNext()) {
			pInfo = itr.next();
			if (pInfo.getPhoneNumber().equals(phoneNumber)) {
				itr.remove();
				return true;
			}
		}
		return false;
	}

	public void printAllPhoneInfo() {
		Iterator<PhoneInfo> itr = set.iterator();
		while (itr.hasNext()) {
			itr.next().printCurrentState();
		}
//		for(PhoneInfo info: set)
//			info.printCurrentState();
	}
}

class PhoneBookUI {
	private PhoneBook pb;
	public static Scanner sc = new Scanner(System.in);

	public PhoneBookUI() {
		this.pb = PhoneBook.getPhoneBook();
	}

	public void printMenu() {
		System.out.println("선택하세요...");
		System.out.println("1. 데이터 입력");
		System.out.println("2. 데이터 검색");
		System.out.println("3. 데이터 삭제");
		System.out.println("4. 모든 데이터 보기");
		System.out.println("5. 프로그램 종료");
		System.out.println("선택 : ");
	}

	public void inputMenu() {
		System.out.println("데이터 입력을 시작합니다.");
		System.out.println("1. 일반, 2. 대학, 3. 회사");
		System.out.print("선택 >>");
	}

	public void inputPhoneInfo(int menu) {
		String name, phoneNumber, major, company;
		int year = 0;
		boolean result;
		PhoneInfo phoneInfo = null;

		System.out.println("데이터 입력을 시작합니다.");
		System.out.println("이름 : ");
		name = sc.nextLine();
		System.out.println("전화번호 : ");
		phoneNumber = sc.nextLine();

		if (menu == 1) // 추가
			phoneInfo = new PhoneInfo(name, phoneNumber);
		else if (menu == 2) {
			System.out.println("전공 : ");
			major = sc.nextLine();
			System.out.println("학년 : ");
			year = sc.nextInt();
			phoneInfo = new PhoneUnivInfo(name, phoneNumber, major, year);
		} else if (menu == 3) {
			System.out.println("회사 : ");
			company = sc.nextLine();
			phoneInfo = new PhoneCompanyInfo(name, phoneNumber, company);
		}
		result = pb.insertPhoneInfo(phoneInfo);
		if (result == false)
			System.out.println("이미 등록된 데이터 입니다.");
		else
			System.out.println("데이터 입력이 완료되었습니다.");
	}

	public void searchPhoneInfoByName() {
		String name;
		System.out.println("검색하시고자 하는 이름을 입력해 주세요.");
		name = sc.nextLine();
		System.out.println("사용자 검색을 시작합니다.");
		if (!pb.searchPhoneInfoByName(name))
			System.out.println("찾으시는 사용자가 없습니다.");
	}

	public void deletePhoneInfoByPhoneNumber() {
		String phoneNumber;
		System.out.println("삭제하시고자 하는 전화번호를 입력해 주세요.");
		phoneNumber = sc.nextLine();
		boolean result = pb.deletePhoneInfoByPhoneNumber(phoneNumber);
		if (result)
			System.out.println("삭제가 완료되었습니다.");
		else
			System.out.println("삭제하시고자 하는 전화번호 정보가 없습니다.");
	}

	public void printAllPhoneInfo() {
		System.out.println("모든 사용자 정보를 출력합니다.");
		pb.printAllPhoneInfo();
	}

	public void quitProgram() {
		System.out.println("프로그램을 종료합니다.");
		sc.close();
	}
}

public interface Menu {
	int INSERT_PHONE_INFO = 1; // 상수로 준 이유 : 가독성을 높이기 위해서
	int SEARCH_PHONE_INFO = 2;
	int DELETE_PHONE_INFO = 3;
	int SHOW_ALL_PHONE_INFO = 4;
	int QUIT_PHONE_INFO = 5;
}

class Practice {
	public static void main(String[] args) {

		int menu = 0;
		PhoneBookUI pbUI = new PhoneBookUI();
		Scanner sc = PhoneBookUI.sc;

		while (true) {
			pbUI.printMenu();
			try {
				menu = sc.nextInt();
				sc.nextLine();

				if (menu < Menu.INSERT_PHONE_INFO || menu > Menu.QUIT_PHONE_INFO) {
					throw new MenuChoiceException(menu);
				}
				switch (menu) {
				case Menu.INSERT_PHONE_INFO:
					pbUI.inputMenu();
					menu = sc.nextInt();
					sc.nextLine();
					if (menu < 1 || menu > 3) {
						throw new MenuChoiceException(menu);
					}
					pbUI.inputPhoneInfo(menu);
					break;
				case Menu.SEARCH_PHONE_INFO:
					pbUI.searchPhoneInfoByName();
					break;
				case Menu.DELETE_PHONE_INFO:
					pbUI.deletePhoneInfoByPhoneNumber();
					break;
				case Menu.SHOW_ALL_PHONE_INFO:
					pbUI.printAllPhoneInfo();
					break;
				case Menu.QUIT_PHONE_INFO:
					pbUI.quitProgram();
					return;
				}
			} catch (MenuChoiceException e) {
				System.out.println(e.getMessage());
			}
		}
	}
}

TreeSet클래스를 이용해 전화번호부

Comparable사용

선생님

package com.test.memo;

import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;

class MenuChoiceException extends Exception {

	public MenuChoiceException(int menu) {
		super(menu + "에 해당하는 선택은 존재하지 않습니다.\n" + "메뉴 선택을 처음부터 다시 진행합니다.");
	}
}

class PhoneUnivInfo extends PhoneInfo {
	private String major;
	private int year;

	public PhoneUnivInfo(String name, String phoneNumber, String major, int year) {
		super(name, phoneNumber);
		this.major = major;
		this.year = year;
	}

	@Override
	public void printCurrentState() {
		super.printCurrentState();
		System.out.println("전공 : " + major);
		System.out.println("학년 : " + year);
	}
}

class PhoneCompanyInfo extends PhoneInfo {

	private String company;

	public PhoneCompanyInfo(String name, String phoneNumber, String company) {
		super(name, phoneNumber);
		this.company = company;
	}

	@Override
	public void printCurrentState() {
		super.printCurrentState();
		System.out.println("회사 : " + company);
	}

}

class PhoneInfo implements Comparable<PhoneInfo> {
	private String name;
	private String phoneNumber;

	public PhoneInfo(String name, String phoneNumber) {
		this.name = name;
		this.phoneNumber = phoneNumber;
	}

	public String getName() {
		return name;
	}

	public String getPhoneNumber() {
		return phoneNumber;
	}

	public void printCurrentState() {
		System.out.println("이름 : " + name);
		System.out.println("전화번호 : " + phoneNumber);
	}

	@Override
	public int compareTo(PhoneInfo pInfo) {
		return name.compareTo(pInfo.name);
	}
}

class PhoneBook {

	private static PhoneBook pb;
	private Set<PhoneInfo> set;

	private PhoneBook() {
		set = new TreeSet<PhoneInfo>();
	}

	public static PhoneBook getPhoneBook() {
		if (pb == null)
			pb = new PhoneBook();
		return pb;
	}

	public boolean insertPhoneInfo(PhoneInfo phoneInfo) {
		return set.add(phoneInfo);
	}

	public boolean searchPhoneInfoByName(String name) {
		PhoneInfo pInfo = null;
		Iterator<PhoneInfo> itr = set.iterator();
		boolean result = false;

		while (itr.hasNext()) {
			pInfo = itr.next();
			if (pInfo.getName().equals(name)) {
				pInfo.printCurrentState();
				result = true;
			}
		}

		return result;
	}

	public boolean deletePhoneInfoByPhoneNumber(String phoneNumber) {
		PhoneInfo pInfo = null;
		Iterator<PhoneInfo> itr = set.iterator();

		while (itr.hasNext()) {
			pInfo = itr.next();
			if (pInfo.getPhoneNumber().equals(phoneNumber)) {
				itr.remove();
				return true;
			}
		}
		return false;
	}

	public void printAllPhoneInfo() {
		Iterator<PhoneInfo> itr = set.iterator();
		while (itr.hasNext()) {
			itr.next().printCurrentState();
		}
//		for(PhoneInfo info: set)
//			info.printCurrentState();
	}
}

class PhoneBookUI {
	private PhoneBook pb;
	public static Scanner sc = new Scanner(System.in);

	public PhoneBookUI() {
		this.pb = PhoneBook.getPhoneBook();
	}

	public void printMenu() {
		System.out.println("선택하세요...");
		System.out.println("1. 데이터 입력");
		System.out.println("2. 데이터 검색");
		System.out.println("3. 데이터 삭제");
		System.out.println("4. 모든 데이터 보기");
		System.out.println("5. 프로그램 종료");
		System.out.println("선택 : ");
	}

	public void inputMenu() {
		System.out.println("데이터 입력을 시작합니다.");
		System.out.println("1. 일반, 2. 대학, 3. 회사");
		System.out.print("선택 >>");
	}

	public void inputPhoneInfo(int menu) {
		String name, phoneNumber, major, company;
		int year = 0;
		boolean result;
		PhoneInfo phoneInfo = null;

		System.out.println("데이터 입력을 시작합니다.");
		System.out.println("이름 : ");
		name = sc.nextLine();
		System.out.println("전화번호 : ");
		phoneNumber = sc.nextLine();

		if (menu == 1)
			phoneInfo = new PhoneInfo(name, phoneNumber);
		else if (menu == 2) {
			System.out.println("전공 : ");
			major = sc.nextLine();
			System.out.println("학년 : ");
			year = sc.nextInt();
			phoneInfo = new PhoneUnivInfo(name, phoneNumber, major, year);
		} else if (menu == 3) {
			System.out.println("회사 : ");
			company = sc.nextLine();
			phoneInfo = new PhoneCompanyInfo(name, phoneNumber, company);
		}
		result = pb.insertPhoneInfo(phoneInfo);
		if (result == false)
			System.out.println("이미 등록된 데이터 입니다.");
		else
			System.out.println("데이터 입력이 완료되었습니다.");
	}

	public void searchPhoneInfoByName() {
		String name;
		System.out.println("검색하시고자 하는 이름을 입력해 주세요.");
		name = sc.nextLine();
		System.out.println("사용자 검색을 시작합니다.");
		if (!pb.searchPhoneInfoByName(name))
			System.out.println("찾으시는 사용자가 없습니다.");
	}

	public void deletePhoneInfoByPhoneNumber() {
		String phoneNumber;
		System.out.println("삭제하시고자 하는 전화번호를 입력해 주세요.");
		phoneNumber = sc.nextLine();
		boolean result = pb.deletePhoneInfoByPhoneNumber(phoneNumber);
		if (result)
			System.out.println("삭제가 완료되었습니다.");
		else
			System.out.println("삭제하시고자 하는 전화번호 정보가 없습니다.");
	}

	public void printAllPhoneInfo() {
		System.out.println("모든 사용자 정보를 출력합니다.");
		pb.printAllPhoneInfo();
	}

	public void quitProgram() {
		System.out.println("프로그램을 종료합니다.");
		sc.close();
	}
}

interface Menu {
	int INSERT_PHONE_INFO = 1; // 상수로 준 이유 : 가독성을 높이기 위해서
	int SEARCH_PHONE_INFO = 2;
	int DELETE_PHONE_INFO = 3;
	int SHOW_ALL_PHONE_INFO = 4;
	int QUIT_PHONE_INFO = 5;
}

class Practice {
	public static void main(String[] args) {

		int menu = 0;
		PhoneBookUI pbUI = new PhoneBookUI();
		Scanner sc = PhoneBookUI.sc;

		while (true) {
			pbUI.printMenu();
			try {
				menu = sc.nextInt();
				sc.nextLine();

				if (menu < Menu.INSERT_PHONE_INFO || menu > Menu.QUIT_PHONE_INFO) {
					throw new MenuChoiceException(menu);
				}
				switch (menu) {
				case Menu.INSERT_PHONE_INFO:
					pbUI.inputMenu();
					menu = sc.nextInt();
					sc.nextLine();
					if (menu < 1 || menu > 3) {
						throw new MenuChoiceException(menu);
					}
					pbUI.inputPhoneInfo(menu);
					break;
				case Menu.SEARCH_PHONE_INFO:
					pbUI.searchPhoneInfoByName();
					break;
				case Menu.DELETE_PHONE_INFO:
					pbUI.deletePhoneInfoByPhoneNumber();
					break;
				case Menu.SHOW_ALL_PHONE_INFO:
					pbUI.printAllPhoneInfo();
					break;
				case Menu.QUIT_PHONE_INFO:
					pbUI.quitProgram();
					return;
				}
			} catch (MenuChoiceException e) {
				System.out.println(e.getMessage());
			}
		}
	}
}

0개의 댓글