자료구조

Seung jun Cha·2022년 9월 30일
0

1. 자료구조

1-1 개념

  • 대량의 데이터를 효율적으로 관리할 수 있는 데이터의 구조이자 입력받거나 가지고 온 데이터를 메모리상에서 관리하는 방법들로 프로그램의 실행속도와 밀접한 관련이 있다. 따라서 프로그램에 맞는 최적의 자료구조를 활용할 필요가 있다

  • 논리적 위치 : 개념적인 관점에서 데이터의 위치를 말한다. 인덱스, 식별자, key 값 등을 의미한다.

  • 물리적 위치 : 데이터가 저장되어 있는 메모리 위치를 의미한다.

1-2 종류

1-2-1 선형자료구조

  • 데이터가 일렬로 나열되어 있는 구조를 선형 자료구조라고 한다.
  1. Array : 정해진 크기의 메모리를 먼저 할당받아 사용하며 중간에 빈 공간이 있을 수 없다. 하나의 배열에는 동일한 자료형만 저장할 수 있다. 원시형과 객체형 모두 들어갈 수 있다. 인덱스 기반의 엑세스 기능을 제공한다.

  2. ArrayList : 요소를 추가하거나 삭제할 때 동적으로 크기를 조정할 수 있으며, ArrayList는 요소 추가, 삭제, 검색, 정렬 등의 작업을 쉽게 수행할 수 있는 메서드를 제공한다.
    ArrayList는 제네릭을 사용하여 다양한 자료형의 요소를 저장할 수 있다. 원시형은 들어갈 수 없고 Object형만 넣을 수 있다.
    (내부적으로 배열의 크기를 검사하고 필요한 경우, 더 큰 크기의 새로운 배열을 생성하고 기존의 요소들을 복사한 다음, 새로운 요소를 추가하는 방식으로 동작)

  3. LinkedList :각 노드는 데이터와 다음 노드로 가는 링크를 가지고 있다. 여기서 노드는 연결리스트의 기본단위이다. 자료의 물리적 위치와 논리적 위치가 다를 수 있다. 중간에 데이터가 추가되거나 삭제되는 속도가 빠르다. 다음 노드의 위치를 이전 노드가 가지고 있기 때문에, 자료를 찾기 위해서는 처음부터 연결된 링크를 차례대로 타고 들어가야하므로 느리다.

  4. 스택 : 나중에 들어간 자료가 먼저 출력

  5. : 먼저 들어간 자료가 먼저 출력

1-2-2 비선형자료구조

  • 데이터가 계층적이거나 복잡한 형태로 구성되어 있는 구조로 데이터의 순서가 정해져 있지 않다.
  1. 트리 : 부모노드와 자식노드간의 연결로 이루어진 자료구조
  • 힙(heap) : 최댓값이나 최솟값을 빠르게 찾기 위한 자료구조로 힙도 이진트리임
    • Max heap : 부모노드가 자식노드보다 항상 크거나 같은 값을 가지는 경우
    • min heap : 부모노드가 자식노드보다 항상 작거나 같은 값을 가지는 경우
  • 이진검색트리 : 왼쪽 자식노드는 부모노드보다 작은 값, 오른쪽큰 값을 가짐, 각 노드는 중복 값을 가지지 않음
    최대값은 우 최하단, 최소값은 좌 최하단
    Inorder travelsal을 하면 자료가 크기순으로 졍렬되어 출력됨
    (작은 수 - 본인 - 큰 수 의 순으로 탐색)
  1. 그래프 : 노드간의 연결을 표현하는 자료구조로 네트워크 구조나 도로망 등을 나타낼 수 있다.

  2. Map : 키-값 쌍으로 이루어진 데이터를 저장한다.

    • HashMap
      가장 일반적으로 사용하는 Map. 중복성을 허용하지 않으며, 순서가 없다는 것이 특징
    • TreeMap
      Red-Black Tree 자료구조를 이용한 Map이다. Tree 구조이기 때문에 어느 정도 순서를 보장한다.
    • LinkedHashMap
      LinkedList로 구현된 HashMap이다. List로 구현되어있기 때문에 순서가 보장된다. 하지만 LinkedList 특성상 랜덤 접근에서 느릴 수 있다.
  3. Set

    • HashSet
      HashMap에서 Key값이 없는 자료형. 집합이라고 생각해도 무방하다. 값이 포함되어 있는지 아닌지만 관심이 있다. 순서를 보장하지 않으며, 중복값을 허용하지 않는다. Set중에는 가장 많이 사용된다.
    • TreeSet
      Red-Black Tree 자료구조를 사용한 Set.
    • LinkedHashSet
      LinkedList로 구현된 HashSet. 순서를 보장한다.

1-2-3 해시함수

  • 임의의 길이의 데이터를 고정된 길이의 인덱스로 만드는 함수이다. 해시 함수를 통해 생성된 해시값을 인덱스로 사용하여 데이터를 저장하고 검색하게 된다. hashMap과 HashTable 모두 해시함수를 사용한다.

  • 해시테이블 : 해시 함수를 사용하여 키를 해시 값으로 변환하고 이 해시코드를 배열의 인덱스로 사용하여 데이터를 저장한다. 따라서, 해시 테이블은 키의 순서에 따라 저장되는 것이 아니라 해시 함수를 통해 결정된 인덱스에 데이터를 저장하므로 순차적인 저장 구조를 갖지 않는다.

  • HashCode와 해시함수의 차이

    • 해시 코드(hashCode())는 객체의 메모리 주소를 사용하여 계산되며, 모든 객체가 유일한 해시 코드를 갖도록 보장됩니다. 객체의 해시 코드를 반환하여 객체가 해시 테이블 등에서 고유하게 식별될 수 있도록 합니다.

    • 해시 함수는 데이터의 내용에 따라 해시 값이 결정되며, 만들어진 해시값과 매핑하여 검색 속도를 높이는 등의 목적으로 사용

1-2-4 해시맵과 해시테이블의 차이점

  1. HashMap은 동기화를 지원하지 않는다. ConcurrentHashMap는 동기화를 지원하기 때문에 멀티 스레드에서 사용하기 적합하다.
  2. HashMap은 key나 value에 null이 들어갈 수 있다.

헤시테이블은 동기화를 지원하여 멀티스레드에 안정적이고 key와 value에 null이 들어갈 수 없다.

2. 제네릭

  • 타입을 외부의 클래스나 메서드에서 사용할 때 정할 수 있게하여 타입의 안정성과 유연성을 확보하는 방법이다. 사용할 때 들어갈 수 있는 타입을 제한하여 컴파일 시 오류를 체크할 수 있다. 제네릭에 동시에 여러타입을 지정할 수 있고, 와일드 카드<?> 를 사용하여 타입의 유연성을 강화할 수도 있다. 와일드카드는 쓰기 작업은 불가능하다
  1. E - element, 주로 자바 API Collection에 사용
  2. K - key, 주로 Map 자료구조 키을 나타내는데 사용
  3. V - value, 주로 Map 자료구조 값을 나타내는데 사용
  4. N - number, 주로 숫자를 나타내는데 사용
  5. T - type, 첫번째 매개변수에 사용
  6. S, U, V - 2nd, 3rd, 4th types
class Sample<T> {

    private T data; // 데이터의 타입은 제네릭 T

    public void setData(T data){
        this.data = data;
    }



이렇게 외부에서 사용하는 시점에 타입을 지정하여 안정성을 확보할 수 있다.

2-1 클래스, 인터페이스

  1. 클래스나 인터페이스의 자료형을 특정하지 않고 추후에 클래스를 사용할 때 지정할 수 있도록 선언하는 것으로 코드의 재사용성을 높일 수 있다.
public void printValue(T value)

매개변수로 받아야하는 타입이 다를 때, 메서드를 새로 만드는 것이 아니라
위의 메서드 하나만으로 다른 타입의 매개변수를 모두 받을 수 있다
public void printValue(int value)
public void printValue(String value)
public void printValue(MyValue value)
  1. T extends 클래스, <타입> : 들어갈 수 있는 타입을 제한하여 잘못된 타입이 선언될 수 있는 문제를 컴파일 과정에서 제거할 수도 있다. 제네릭으로 선언된 T에 모든 타입이 들어가면 문제가 발생할 수 있으므로 정해진 타입만 들어갈 수 있게 선언한다.

  2. 제네릭 클래스에서 static 변수static 메서드는 사용할 수 없다. 클래스가 인스턴스가 되기 전에 static은 메모리에 올라가는데 이 때 타입인 T가 결정되지 않기 때문에 위와 같이 사용할 수 없는 것이다.

2-2 메서드

  • 주의해야 할 점은 Student 클래스에 지정한 제너릭 타입 T와 제너릭 메소드에 붙은 T는 같은 T를 사용하더라도 전혀 별개이다.
    클래스에 표시하는 T는 인스턴스 변수라고 생각하자. 인스턴스가 생성될 때 마다 지정되기 때문이다. 그리고 제너릭 메소드에 붙은 T는 지역변수를 선언한 것과 같다고 생각하자.
class Student<T>{

    public T getOneStudent(T id){ return id; }  // 1
    
    public <T> T getId(T id){return id;} // 2 제네릭 클래스의 T와 다름  
    
    public <S> T toT1(S id){return id; }  // 3
    
    public static <S> T toT2(S id){return id;}  // 4 에러 
    
    public (T[]) new Item[size] // 5
}
  1. 1번의 경우 클래스의 제너릭 타입 T를 그대로 사용하는 경우다.

  2. 2번의 경우 클래스의 제너릭 타입 T와 제너릭 메소드 타입 T는 다르다.

  3. 3번의 경우 static 메소드가 아닌 일반메소드기 때문에 클래스의 타입과 제너릭 메소드의 타입을 같이 사용가능하다.

  4. 4번의 경우 static 메소드기 때문에 클래스의 제너릭 타입 T를 사용하기 때문에 에러가 발생한다.

  5. 5번은 제네릭타입으로 배열을 생성하는 경우로 제네릭으로 배열을 생성할 수 없기 때문에 상위타입으로 배열을 생성하고 제네릭으로 형변환을 한다

2-3 와일드카드 타입

  • 타입이 아직 지정되지 않아서 모든 타입을 대신할 수 있는 타입으로 제네릭 클래스, 메서드, 인터페이스의 파라미터 타입에 사용된다.
<? extends T>: "T와 T의 하위 클래스"를 나타냅니다. 제한적인 상한 바운드(upper bounded)라고도 합니다. 주로 읽기 작업에 사용됩니다. 예를 들어, List<? extends Number>는 Number 클래스와 그 하위 클래스들의 리스트를 나타냅니다.

<? super T>: "T와 T의 상위 클래스"를 나타냅니다. 제한적인 하한 바운드(lower bounded)라고도 합니다. 주로 쓰기 작업에 사용됩니다. 예를 들어, List<? super Integer>는 Integer 클래스와 그 상위 클래스들의 리스트를 나타냅니다.

**<?>**: "모든 타입"을 나타냅니다. 제한이 없는 와일드카드라고도 합니다. 읽기와 쓰기 모두에 사용될 수 있습니다. 예를 들어, `List<?>`는 어떤 타입의 리스트든지 나타낼 수 있습니다.

2-3-1 Object타입과의 차이점

  • 사용하는 시점에 타입을 지정하기때문에 타입안정성이 보장된다
  • Object 타입을 사용하면 타입 정보가 사라지기 때문에 타입 검사나 형변환을 직접 처리해야 합니다. 이는 런타임 에러의 가능성을 높이고, 컴파일러가 타입 안전성을 확인하지 못하게 됩니다.
 		// 와일드카드 사용 예제
        List<?> wildcardList = new ArrayList<>();
        wildcardList = new ArrayList<String>();
        wildcardList = new ArrayList<Integer>();

        // Object 타입 사용 예제
        List<Object> objectList = new ArrayList<>();
        objectList = new ArrayList<String>();  // 컴파일 에러
        objectList = new ArrayList<Integer>(); // 컴파일 에러

        // 와일드카드로 원소 추가하기
        List<?> wildcardList2 = new ArrayList<>();
        // wildcardList2.add("Hello"); // 에러 발생

        // Object 타입으로 원소 추가하기
        List<Object> objectList2 = new ArrayList<>();
        objectList2.add("Hello");
        objectList2.add(42);

        // 와일드카드로 원소 읽기
        List<String> stringList = new ArrayList<>();
        stringList.add("Java");
        wildcardList2 = stringList;
        Object element = wildcardList2.get(0); // 컴파일 에러

        // Object 타입으로 원소 읽기
        List<Object> objectList3 = new ArrayList<>();
        objectList3.add("Java");
        element = objectList3.get(0); // 타입 캐스팅 필요
        String str = (String) element; // 타입 캐스팅
 List<Object> objectList = new ArrayList<>();
        objectList.add("Hello");
        objectList.add(42);

        for (Object element : objectList) {
            // 타입 검사와 형변환을 직접 처리해야 함
            if (element instanceof String) {
                String strElement = (String) element;
                System.out.println("String: " + strElement);
            } else if (element instanceof Integer) {
                int intElement = (int) element;
                System.out.println("Integer: " + intElement);
            }
        }
    }

3 컬렉션 프레임워크

  • 데이터를 효율적으로 저장하고 관리하기 위해 표준화한 인터페이스의 집합

3-1 List

  1. 객체를 순서에 따라 인덱스에 저장하고 관리하는데 필요한 메서드가 선언된 인터페이스로 리스트는 원소의 중복을 허용함

  2. 인터페이스라서 객체생성이나 추가, 삭제, 수정 연산 불가능
    (상수와 특징이 비슷함) -> ex)List.of{1,2,3,4}'

  3. List는 인터페이스로 List list = new ArrayList() 이런식으로 다형성 적용가능

3-1-1 ArrayList

  1. 배열의 크기가 부족해지면 자동으로 크기를 늘려줌

  2. 비동기화 : 하나의 자원에 동시에 접근 불가능 -> 멀티쓰레드 접근을 막고 순서를 지킴

  3. 순서대로 주소를 할당하기 때문에 검색은 빠르지만 데이터를 추가, 삭제할 때는 느리다.

  • 주요 메서드

    • .add((index), val): 인덱스 값을 주지않으면 순서대로 리스트를 추가, 배열 사이즈 초과 시 초기 설정된 사이즈만큼 자동으로 사이즈가 증가함, 인덱스를 추가로 지정해주면 해당 인덱스에 값을 삽입

    • addAll(Collection c) : 주어진 컬렉션의 모든 객체를 저장

    • .get(index): 해당 인덱스의 객체를 반환

    • .set(index, val): 해당 인덱스의 객체를 변경

    • .indexOf(val): 지정된 객체의 위치를 반환(순방향)

    • .lastindexOf(val): 지정된 객체의 위치를 반환(역방향)

    • .remove(index or val): 해당 인덱스의 값 or 해당 값 중 첫번째 값 삭제

    • .contains(val): 해당 객체가 배열에 있는지 검색해서 true / false 반환

    • .containsAll(val1, val2...): argument로 제공한 컬렉션의 모든 값이 포함되어 있는지 여부를 true / false로 반환

    • .toArray(): ArrayList 에 저장된 모든 인스턴스들을 일반 배열 타입으로 반환, 저장할 배열 타입에 맞춰 자동 형변환, 배열 크기 또한 자동으로 맞춰서 바꿔줌

    • .clear(): 값 모두 삭제

    • .isEmpty(): 비었으면 true, 하나라도 값이 있으면 false 반환

    • .addAll(arr2): 두 컬렉션을 합침

    • .retainAll(arr2): argument로 제공한 컬렉션 내에 들어있는 값을 제외하고 모두 지워줌(교집합만 남김)

    • .removeAll(arr2): argument로 제공한 컬렉션 내에 들어있는 값과 일치하는 값을 모두 지워줌(교집합인 부분을 삭제), retainAll() 메소드와 반대

    • .size(): List에 들어있는 요소의 개수 반환

    • sort(Comparator c) : 지정된 비교자로 List를 정렬

    • subList(from ,to) : from부터 to까지의 객체를 반환(List를 새로 만드는 것이 아닌 읽기전용으로 보여줌)

3-1-2 Vector

  • 벡터보다는 동기화가 필요할 때마다 Collections.syscronizedList 또는 concurrentqueue를 사용

3-1-3 LinkedList

  • 노드에 저장할 데이터와 다음에 이어지는 노드의 주소(포인터)를 삽입
    비연속적인 주소할당으로 추가나 삭제는 빠르지만 한번에 여러개의 데이터를 건너뛸 수 없기 때문에 검색은 느리다

3-2 set

  1. 순서는 유지되지 않지만, 중복은 허용하지 않는 유일한 값을 관리하는데 필요한 메서드가 선언됨

  2. Set의 메서드는 Collection의 메서드와 거의 동일하다

  3. 인덱스가 없으므로 반복하기 위해서는 iterator가 필요

3-2-1 HashSet, LinkedHashSet

public static void main(String[] args){
	Set hashSet=new HashSet();
	hashSet.add("F");
	hashSet.add("B");
	hashSet.add("D");
	hashSet.add("A");
	hashSet.add("C");
	
	/* 위와 같은 데이터들을 다시 add */
	hashSet.add("F");
	hashSet.add("B");
	hashSet.add("D");
	hashSet.add("A");
	hashSet.add("C");
    // 원소 값이 같으면 동일한 해시코드가 발급되므로 중복되지 않음
    

여기서 두번째로 들어온 값들이 중복값이라는 것을 알기 위해서는
equals(Object o), hashCode() 메서드가 필요하다

	/* HasSet의 "C"라는 원소 삭제 */
	hashSet.remove("C");
    hashSet.remove("1"); // hashSet은 인덱스를 가지고 있지 않으므로 원소 1을 삭제
	
	/* HashSet 모든 원소 출력 */
	System.out.println("HashSet 원소 출력");
	Iterator it=hashSet.iterator();
	while(it.hasNext()){
		System.out.print(it.next()+" ");
	}
	
	/* HashSet의 모든 원소를 ArrayList로 전달 */
	List arrayList=new ArrayList();
	arrayList.addAll(hashSet);
	
	/* ArrayList의 모든 원소 출력 */
    for문을 사용하거나 iterator를 사용
	System.out.println();
	System.out.println("ArrayList 원소 출력");
	for(int i=0;i<arrayList.size();i++){
		System.out.print(arrayList.get(i)+" ");
	}
}
  • LinkedHashSet : 들어온(입력) 순서 유지
  • TreeMap : 크기의 순서 유지

3-2-2 TreeSet

객체의 정렬에 사용하는 클래스로 이진검색트리(보통 레드-블랙트리)로 구현된다. 역시 크기의 순서를 유지하며, 비교대상이 되는 객체에 Comparable이나 Comparator인터페이스를 구현해야 TreeSet에 추가될 수 있다. String 등 기존의 객체들은 Comparable이 이미 구현이 되어 있어 인터페이스를 만들 필요가 없고 정렬이 이루어진다.
검색, 정렬은 성능이 좋지만 추가 ,삭제는 오래걸린다. 범위 검색은 불가능하고 지역성을 유지하기 쉽다

3-3 Map

  • 인덱스가 없으므로 반복하기 위해서는 iterator가 필요
  1. HashMap : 가장 많이 사용되는 Map으로 key를 이용하여 값을 지정하고 값을 꺼내오는 방식. key는 중복될 수 없고 유일성을 비교하기 위해 equals()와 hashCode()메서드를 구현해야 함
    null도 key로 사용 가능하다.
    JSON을 사용할 때 HashMap가 계속 사용되고 삭제됨

    (1)put(K, V) : Map에 넣기 / putAll(Map m) : Map의 모든 key-value 넣기
    (2)get(K) : key로 value를 가지고 오기
    (3)keySet() : 저장되어 있는 모든 key를 가지고 오기
    (key는 중복을 허용하지 않기 때문에 Set타입으로 반환한다)
    (4)values() : 저장되어 있는 모든 values를 가지고 오기
    (5)containsKey, containsValue : 입력받은 key, value와 일치하는 객체가 있는지 확인, true, false 로 반환한다.
    (6)remove(key) : 입력받은 key와 일치하는 key-value를 삭제

  2. TreeMap

3-4 Hashing

  1. 해시함수(hash function)를 이용해서 데이터를 해시테이블(hash table)에 저장하고 검색하는 기법으로 hashSet, hashMap 등에서 사용한다.
    입력한 key해시함수를 통해 해시코드로 변환하고 그에 맞는 데이터를 찾아옴

  2. 해시함수는 데이터가 저장되어 있는 곳을 알려주며, 다량의 데이터 중에서 원하는 데이터를 빠르게 찾을 수 있다.

3-4-1 해시충돌, 해결방안

  • 서로 다른 두 key가 동일한 해쉬값을 반환

  • 해결방안

    • Chaining
    • Open Addressing

3-5 Stack, Queue

  • stack은 클래스로 구현하여 제공하지만 Queue를 구현하는 클래스는 LinkedList, PriorityQueue 등이 있다. 필요한 상황에 맞게 선택하면 된다.
Stack<> stack = new Stack():

Queue<> queue = new LinkedList<>(); 
// LinkedList는 queue인터페이스를 내부에서 구현하고 있다
add(), offer() / poll(), remove()
  1. stack : 수식 계산, 웹브라우저의 뒤로가기/앞으로가기
  2. queue : 최근사용문서

3-5-1 Priority Queue

  • 들어간 순서에 관계없이 주어진 조건에 따라 우선순위를 정하고 데이터를 보여주는 큐


2. Queue

3-6 Iterator

  • 컬렉션에 저장된 요소에 접근하기위해 사용되는 Collection인터페이스이다. List와 Set 컬렉션에 맞게 작성되어 있다.
    그런데 Map는 key와 value가 쌍으로 저장되어 있어서 각자의 값을 따로 얻어와야 한다.
for( String key : map.keySet() ){
            System.out.println( 
            String.format("키 : %s, 값 : %s",  key, map.get(key)) 
            );
        }

4. Comparable, Comparator

  • compareTo(T o) : Comparable의 구현메서드로 자기자신과 매개변수 객체를 비교하여 정렬한다.
    오름차순으로 정렬하려면 왼쪽 값이 더 클때 자리바꿈, 내림차순으로 정렬하려면 오른쪽 값이 더 클 때 자리바꿈
  • String, Integer, Date와 같은 기본 데이터 타입 클래스들은 이미 Comparable을 구현하고 있어서 기본적으로 정렬이 가능
class Student implements Comparable<Student> {
 
	int age;			// 나이
	int classNumber;	// 학급
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
	
	@Override  // 오름차순과 내림차순을 변경하고 싶으면 양수와 음수를 바꾸어 주면 됨
	public int compareTo(Student o) {
    
		// 자기자신의 age가 o의 age보다 크다면 양수
		if(this.age > o.age) {
			return 1;
		}
		// 자기 자신의 age와 o의 age가 같다면 0
		else if(this.age == o.age) {
			return 0;
		}
		// 자기 자신의 age가 o의 age보다 작다면 음수
		else {
			return -1;
            
            return this.age - o.age;
            // 그냥 깔끔하게 이렇게 해도 한방에 해결됨 , * (-1)
            문제는 결과값이 자료형의 범위를 넘어가는 경우 문제
		}
	
  • compare(T o1, T o2) : 비교 로직을 외부에 분리하여 정렬 기준을 독립적으로 정의할 수 있도록 한다.
  1. Comparator의 구현메서드로 두 매개변수 객체를 비교하여 정렬
  2. 만약 자신과 비교하고 싶다면 자신을 매개변수로 넘겨주면 됨
  3. TreeSet(new Student())의 생성자 안에 compare가 구현된 객체를 넣어 주어야 함.
  4. 일반적으로는 Comparable의 compareTo를 사용하지만 String 등 이미 compareTo가 구현되어 있는 경우 차순을 바꾸기 위해 사용
public int compare(String s1, String s2){
	return s1.compareTo(s2);
    } // 여기까지는 String에서 원래 자동으로 구현되어 있던 것인데 *(-1)을 해서 
    역순으로 출력 또는 .reversed() 함수 사용
class Student implements Comparator<Student> {
 
	int age;			// 나이
	int classNumber;	// 학급
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
  }
	
	@Override // 오름차순과 내림차순을 변경하고 싶으면 양수와 음수를 바꾸어 주면 됨
	public int compare(Student o1, Student o2) {
    
		// o1의 학급이 o2의 학급보다 크다면 양수
		if(o1.classNumber > o2.classNumber) {
			return 1;
		}
		// o1의 학급이 o2의 학급과 같다면 0
		else if(o1.classNumber == o2.classNumber) {
			return 0;
		}
		// o1의 학급이 o2의 학급보다 작다면 음수
		else {
			return -1;
            
            // 마찬가지로 이렇게 해도 한방에 해결 *(-1)
            return o1.classNumber - o2.classNumber;
		}
	}

문제는 비교해야 하는 객체가 다수일 때, 이런 문제가 발생한다.

public class Test {
	public static void main(String[] args)  {
 
		Student a = new Student(17, 2);	// 17살 2반
		Student b = new Student(18, 1);	// 18살 1반
		Student c = new Student(15, 3);	// 15살 3반
			
		//          ⋁
		int isBig = a.compare(a, b);
        //           ⋁
		int isBig2 = a.compare(b, c);
		//           ⋁
		int isBig3 = b.compare(a, c);
		
	}
}

해결 방법은 익명 객체(클래스)를 사용한다는 것이다. 익명객체는 특정 타입이 존재하는 것이 아니기 때문에 반드시 extends 또는 implement할 대상이 있어야 한다.
익명 객체의 경우 필요에 따라 main함수 밖에 정적(static) 타입으로 선언해도 되고, main안에 지역변수처럼 non-static으로 생성해도 된다.

public class Test {
	public static void main(String[] args) {
    
		// 익명 객체 구현방법 1 (non-static)
		Comparator<Student> comp1 = new Comparator<Student>() {
			@Override // Comparator의 메서드 구현을 익명클래스에서 진행
			public int compare(Student o1, Student o2) {
				return o1.classNumber - o2.classNumber;
			}
		};
	}
 
	// 익명 객체 구현 2 (static)
	public static Comparator<Student> comp2 = new Comparator<Student>() {
		@Override  // Comparator의 메서드 구현을 익명클래스에서 진행
		public int compare(Student o1, Student o2) {
			return o1.classNumber - o2.classNumber;
		}
	};
}
 
 
// 외부에서 익명 객체로 Comparator가 생성되기 때문에 클래스에서 
Comparator을 구현 할 필요가 없어진다.
class Student {
 
	int age;			// 나이
	int classNumber;	// 학급
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
 
}

0개의 댓글