핑구

12. 컬렉션 본문

JAVA 웹 개발/1. JAVA

12. 컬렉션

코딩 펭귄 2022. 5. 13. 15:11
📅 2021.09.01 ~ 2021.09.02

컬렉션(Collection)

  • 메모리 상에서 자료를 구조적으로 처리하는 방법을 자료 구조라고 하는데, 컬렉션은 자바에서 제공하는 이러한 자료 구조를 담당하는 프레임워크이다.
  • 추가, 삭제, 정렬 등 복잡한 로직(기능) 처리가 간단하게 해결되어 자료 구조적 알고리즘을 구현할 필요가 없다.
  • java.util 패키지에 포함되며, 인터페이스를 통해 정형화된 방법으로 다양한 컬렉션 클래스를 이용 가능하다.
  • 배열의 문제점을 보완해 준다.
    배열의 문제점
    1. 한 번 크기를 지정하면 변경 불가능하기 때문에 필요에 따라 공간을 늘리거나 줄일 수 없다. 배열을 계속 추가할 경우 공간의 크기가 부족하면 에러가 발생하기 때문에 할당 시 넉넉한 크기로 할당하게 되는데, 따라서 메모리 낭비가 생길 수 있다.
    2. 중간 위치에 기록된 데이터를 추가, 삭제할 경우 마지막 기록된 데이터까지 하나씩 뒤로 밀어내는 작업을 거친 후 추가해야 하기 때문에 불편하다.
    3. 한 타입의 데이터만 저장 가능하다.
      Object 객체 배열을 사용하는 경우 여러 자료형의 데이터를 저장할 수 있으나, 해당 경우도 Object 객체만 저장하는 것이라고 말할 수 있다. 따라서 배열은 한 가지 타입만 저장 가능한 것으로 본다.
    컬렉션의 장점
    1. 저장하는 크기에 제약이 없다.
    2. 추가, 삭제, 정렬 등의 기능 처리를 하는 자료 구조가 내장되어 있기 때문에 알고리즘 구현이 필요 없이 간단하게 해결된다.
    3. 여러 타입의 데이터를 저장할 수 있다.
      객체만 저장 가능하기 때문에 기본 자료형을 저장할 경우에는 Wrapper 클래스를 사용하여 저장하여야 한다.
  • 컬렉션은 여러 가지 타입의 데이터를 한 번에 저장 가능하지만, 보통의 경우 한 타입의 데이터를 저장하는 형태로 많이 사용된다. 그 이유는 여러 가지 데이터를 한 번에 사용하는 경우 어떤 데이터를 넣었는지 알 수 없다면 데이터를 활용할 때 instanceof 연산자를 이용해 일일이 확인이 필요해 번거롭기 때문이다.
    한 가지 타입만 저장 가능하도록 제한하는 것을 제네릭이라고 하며, <>를 사용해 표시한다.
    제네릭에서 기본적으로 작성되어 있는 <E>는 element를 뜻하며, 사용자가 어떤 타입으로 제한할지 알 수 없기 때문에 미리 지정해두지 않고 해당 요소를 E라는 변수로 작성한 것이다.
    만약 제네릭을 작성하지 않는 경우 E에는 모든 객체를 받을 수 있는 Object 객체가 들어간다. 하지만 설정하지 않는 경우 경고 메시지가 발생한다.

 

자료 구조

  • 데이터(자료)를 메모리에서 구조적으로 처리하는 방법론을 말한다.

 

컬렉션의 주요 인터페이스

  • 인터페이스 분류
    • Collection : 객체 하나만 저장한다
      1. List 계열: 순서를 유지하고 저장하며 중복 저장이 가능하다.
      2. Set 계열: 순서를 유지하지 않고 저장하며, 중복 저장이 불가능하다.
    • Map 계열 : 키와 값을 쌍으로 저장하며, 키는 중복 저장이 되지 않는다.
      이때 키는 중복 저장이 되지 않기 때문에 Set 계열로 저장되는 것이며, 값은 중복 저장이 가능하기 때문에 List 계열로 저장되는 것이다.
      키가 메인이며, 키를 중심으로 한 인터페이스인데 키는 Set 계열이기 때문에 Set 계열의 특징을 따라 저장 순서가 유지되지 않는다.

 

List

  • 자료들을 순차적으로 나열한 자료 구조로 인덱스로 관리된다. 인덱스로 관리되기 때문에 객체를 중복해서 저장 가능하며, 순서대로 저장이 가능하다.
  • 구현 클래스로 ArrayList, Vector, LinkedList가 있다.
    • ArrayList: List의 후손으로 초기 저장 가능 용량은 10이지만, 저장 용량을 초과하여 객체들이 들어오면 자동으로 용량이 증가한다.
      내부가 배열로 이루어져 있기 때문에 배열을 계속 만들고 지워주는 형태로 작동되며, 따라서 알고리즘은 제공하나 메모리적으로는 성능이 좋지 않다.
      동기화를 제공하지 않는다.
    • Vector: List의 후손으로 List의 이전 버전이다.
      동기화를 제공하며, List 객체들 중 가장 성능이 좋지 않다.
    • LinkedList: List의 후손으로 인접 참조를 링크해 체인처럼 관리한다. 따라서 새로운 객체가 중간에 추가될 경우 ArrayList처럼 새로운 배열을 만드는 것이 아니라, 연결을 끊고 새로운 연결을 만들어 준다.
      객체 삭제와 삽입이 빈번하게 일어나는 경우에는 ArrayList보다 성능이 좋다.
      연결이 쌍방으로 이루어진 것은 Double LinkedList라고 한다.
  • List의 메소드
    • add(int index, E e):void
      index번째 인덱스에 E를 추가한다.
    • remove(int index):E
      remove(Object o):boolean
      해당 index번째 객체를 삭제한다.
      해당 Object 객체를 찾아서 삭제한다.
      객체를 찾아서 삭제하는 remove를 사용하면 해당 객체의 주소 값을 가지고 비교하기 때문에 아래와 같이 new를 사용하는 경우 필드의 값이 같은 객체가 이미 존재하여도 주소 값이 달라 다른 객체로 판단하여 지워지지 않는다.
      list.remove(new Student("강건강", 40));
      

      따라서 주소 값이 아니라 필드의 값이 같은 객체를 삭제하려고 한다면 Object 클래스의 equals() 메소드를 오버라이딩해주어야 한다. remove() 메소드 내부에서 equals() 메소드를 사용하고 있기 때문에, 오버라이딩을 진행하는 경우 Object 클래스의 equals() 메소드가 아닌 내 클래스의 equals() 메소드를 사용하므로 원하는 대로 비교가 가능하다.
      // equals 오버라이딩 방법
      @Override
      public boolean equals(Object obj){
      	// 객체 측면
      	if(this == obj){
      		return true;
      	}
      	if(obj == null) {
      		return false;
      	}
      	if(getClass() != obj.getClass()) { 
      		return false;
      	}
      
      	// 필드(데이터) 측면
      	Student other = (Student)obj; 
      	if(name == null) { 
      		if(other.name != null) {
      			return false;
      		}
      	} else if(!name.equals(other.name)) { 
      			return false;
      	}
      	if(score != other.score) {
      		return false;
      	}
      		
      	return true; 
      }
      getClass() : 현재 참조하고 있는 클래스를 반환하는 메소드이다.

      equals() 메소드를 오버라이딩 한 경우에는 hashCode() 메소드도 같이 오버라이딩 해주어야 한다. equals() 메소드의 결과가 true라면 두 객체의 hashCode가 같아야 하기 때문이다.
      @Override
      public int hashCode() {
      	final int PRIME = 31; // 31이 컴퓨터가 계산할 때 적당히 크고 가장 가까운 소수이기 때문에 자주 애용됨
      	int result = 1;
      	result = PRIME * result + (name == null ? 0 : name.hashCode());
      	result = PRIME * result + score;
      
      	return result;
      }

      만약 equals() 메소드를 오버라이딩하지 않는 경우 아래와 같이 for문을 이용하여야 해당 필드를 포함한 객체를 삭제할 수 있다.
      for(int i = 0; i < list.size(); i++) {
      	Student std = list.get(i);
      	if(std.getName().equals("강건강") && std.getScore() == 40) {
      		list.remove(i);
      		break;
      	}
      }
    • sort(Comparator<? super E> c):void
      정렬을 하는 경우 사용한다. String, int 등의 경우 자바에서 미리 정렬 기준을 정해 제공하고 있기 때문에 바로 sort() 메소드를 사용 가능하지만, 객체의 경우 정렬을 위한 기준이 정해져 있지 않기 때문에 기준을 정해 주어야 한다.
      정렬 기준을 세우기 위해서는 Comparable과 Comparator를 사용한다.
    • set(int index, E e):E
      index번째에 있는 요소를 e로 수정한다.
    • get(int index):E
      index번째에 있는 요소를 반환한다.
    • contains(Object o):boolean
      o가 리스트에 존재하는지 확인한 후 있으면 true를 반환하고, 없으면 fasle를 반환한다.
    • indexOf(Object o):int
      o가 리스트에 위치하는 인덱스 번호를 반환한다.
    • clear():void
      안에 있는 요소를 전체 삭제한다.
    • isEmpty():boolean
      리스트가 비어 있는지 확인하고, 비어 있으면 true를 반환하고, 비어 있지 않으면 false를 반환한다.

 

List의 정렬 기준

  • Comparable : java.lang 패키지에 존재하며, compareTo() 메소드를 사용한다.
      • 정렬하고자 하는 객체에 Comparable를 상속받아 compareTo() 메소드를 오버라이딩해 기존의 정렬 기준을 재정의한다.
      • 한 개의 정렬만 가능하다.
      • compareTo 메소드의 경우 비교 주체와 비교 대상을 비교할 때 비교 주체가 비교 대상보다 크면 1을 반환, 비교 주체가 비교 대상과 같으면 0을 반환, 비교 주체가 비교 대상보다 작으면 -1을 반환한다.
      • 기본적으로 오름차순 정렬을 따른다.
        @Override
        public int compareTo(Student o) {
        		return name.compareTo(o.name);
        //	return -name.compareTo(o.name); -> 내림차순 정렬 
        }
  • Comparator: java.util 패키지에 존재하며, compare() 메소드를 사용한다. 
    • vo 패키지 안에 필요한 정렬 기준에 맞춘 클래스들을 생성한 후 Comparator를 상속받아 compare() 메소드를 오버라이딩 해 기존의 정렬 기준을 재정의한다.
    • 여러 개의 정렬이 가능하다.
      @Override
      public int compare(Student o1, Student o2) {
      	// 비교 주체 : o1
      	int standard = o1.getScore();
      	
          // 비교 대상 : o2
      	int object = o2.getScore();
      			
      	if(standard > object) {
      		return 1;
      	} else if(standard == object) {
      		return 0;
      	} else {
      		return -1;
      	}
      }
      if문을 이용해 위와 같이 작성하면 구현 가능하지만, 이미 Integer 객체 내부에 compareTo() 메소드가 구현되어 있기 때문에 아래와 같이 간단하게도 작성이 가능하다.
      @Override
      public int compare(Student o1, Student o2) {
      	Integer standard = o1.getScore();
      	Integer object = o2.getScore();
      	
      	return standard.compareTo(object);
      }
      만약 점수가 같은 경우 이름에 대한 오름차순으로 정렬하고 싶은 경우(다른 기준으로 한 번 더 정렬하고 싶은 경우) 이름으로 정렬하는 코드를 compare() 메소드에 추가해 주면 된다.

 

Set

  • 인덱스가 없으며, 따라서 저장 순서가 유지되지 않고, 중복 객체 또한 저장되지 않는다.
    null도 중복을 허용하지 않기 때문에 1개의 null만 저장 가능하다.
  • List와 동일하게 Collection을 상속받기 때문에 겹치는 메소드가 많다.
  • 구현 클래스로 HashSet, LinkedHashSet, TreeSet이 있다.
    • HashSet : Set에 객체를 저장할 때 hash 함수를 사용하여 처리 속도가 빠르다. 동일 객체 뿐 아니라 동등 객체도 중복하여 저장하지 않는데, 이때 동등 객체는 equals가 오버라이딩 된 경우에만 중복 저장되지 않는다.
      • 동일 객체: 주소 값이 같은 객체
      • 동등 객체: 같은 값을 가지고 있는 객체
    • LinkedHashSet : HashSet과 거의 동일하지만 추가되는 순서를 유지한다.
    • TreeSet : 검색 기능을 강화시킨 컬렉션으로 계층 구조를 활용해 이진 트리 자료 구조를 구현하여 제공하며, 사용 시 자동으로 정렬이 된다.
      사용 시 Comparable를 이용하여 정렬 기준을 만들어 주어야 하며, 해당 정렬 기준 필드가 기준이 되기 때문에 해당 필드의 값이 중복되는 경우 다른 필드의 값이 중복되지 않아도 객체가 추가되지 않는다. 따라서 만약 정렬 기준과 같은 필드 값을 가진 다른 객체를 추가하고자 할 경우에는 Comparator를 이용하여 다른 정렬 기준을 생성해 주어야 한다.
  • Set의 경우 인덱스가 존재하지 않기 때문에 get() 메소드가 존재하지 않는다. 따라서 다음의 방법으로 요소를 가져와야 한다.
    1. Set을 List로 변환 후 for문 사용
      ArrayList<Dog> list = new ArrayList<Dog>(set2);
      	for(int i = 0; i < list.size(); i++) {
      	System.out.println(list.get(i));
      }
    2. iterator() 이용
      Iterator의 종류는 다음과 같다.
      • Enumeration: Iterator의 구버전
      • Iterator: 컬렉션에 저장된 요소에 접근하는 데 사용되는 인터페이스로 단방향이기 때문에 이전에 접근했던 요소에 다시 접근할 수 없다.
      • ListIterator: Iterator의 후손 클래스로 양방향으로 사용 가능하지만, List에서만 사용이 가능하다.
      Iterator<Dog> it = set2.iterator();
      while(it.hasNext()) { // 만약 Iterator 안에 다음 값이 존재한다면
      	Dog d = it.next(); // 다음 값을 가지고 와라
      	System.out.println(d);
      }

Map

  • Collection 클래스의 상속을 받고 있지 않기 떄문에 List, Set과 구성이 다르다.
  • 키와 값으로 구성되며, 키와 값의 묶음을 entry라고 부른다. 만약 키가 중복되는 경우 기존에 있는 키에 값을 덮어 씌운다.
  • 구현 클래스로 HashMap, Hashtable, LinkedHashMap, Propertied, TreeMap이 있다.
    • HashMap: 키의 경우 중복될 수 없기 때문에 키 객체는 equals()와 hashCode()를 재정의해 동등 객체 조건을 정해 주어야 한다. 하지만 equals()를 매번 재정의하는 것은 번거롭기 때문에 대부분의 경우 key는 이미 equals가 재정의되어 있는 String 타입으로 사용한다.
    • HashMap의 메소드
      • put(K key, V value):V
        키와 값을 추가한다.
      • containsKey(Object key):boolean
        해당 키가 있는지 확인한다.
      • containsValue(Object value):boolean
        해당 value가 있는지 확인한다. 해당 메소드의 경우에도 equals() 메소드를 이용하여 비교를 진행하는데, 이때 Object의 equals() 메소드를 이용하기 때문에 주소 값만 비교하여 결과를 출력한다. 따라서 값 자체를 비교하기 위해서는 equals() 메소드의 오버라이딩이 필요하다.
      • get(Object key):V
        키에 해당하는 값을 반환한다. 만약 해당하는 값이 없는 경우 null을 반환한다.
      • values():Collection<v>
        모든 value를 Collection에 담아 반환한다.
      • keySet():Set<k>
        map에 포함된 key들을 Set에 담아 반환한다. 이때 반환값을 담는 Set 객체를 만들어주는 경우 key의 자료형으로 Set의 제네릭을 설정해 주어야 한다.
        Set으로 반환하였기 때문에 key를 iterator를 이용해 하나씩 불러올 수 있고, 해당 key 값으로 value 값도 불러올 수 있다.
      • entrySet():Set<Map.Entry<K, V>>
        map 안에 있는 key와 value들(entry들)을 모아 Set으로 반환한다.
        해당 메소드를 사용하는 경우 key와 value를 같이 가지고 오기 때문에 key 값을 따로 가지고 value를 출력하지 않아도 된다.
      • putAll(Map<? extends K,? extends V> m):void
        다른 Map의 요소를 한 번에 추가 가능하다.
      • remove(Object key):V
        해당 key의 entry를 삭제 후 삭제된 값을 반환한다.
      • remove(Object key, Object value):boolean
        해당 key와 value가 모두 맞는 경우 삭제하고 true를 반환한다. 만약 둘 중 하나라도 다른 경우 삭제되지 않고, false를 반환한다.
      • clear():void
        모든 entry를 삭제한다.
      • isEmpty():boolean
        map이 비어 있는지 확인하고, 비어 있으면 true를 반환한다.
    • Hashtable: 키 객체를 만드는 법은 HashMap과 동일하나, 스레드 동기화가 된 상태이기 때문에 복수의 스레드가 동시에 접근해 객체를 추가, 삭제하더라도 스레드에 안전하다. HashMap의 구버전이다.
    • Properties: 키와 값을 String 타입으로 제한한 Map 컬렉션이다. HashMap에서도 key를 String으로 사용할 것을 권장하나, 필수가 아니지만, Properties에서는 필수적으로 String으로 사용하여야 한다.
      주로 프로퍼티(*.properties) 파일을 읽어 들일 때 주로 사용한다.

      프로퍼티(*.properties) 파일
      • 옵션 정보, 데이터베이스 연결 정보, 국제화(다국어) 정보를 기록하여 텍스트 파일로 활용하는 파일이다.
      • 애플리케이션에서 주로 변경이 잦은 문자열을 저장하여 관리하기 때문에 유지보수를 편하게 만들어 준다.
      • 키와 값이 '=' 기호로 연결되어 있는 텍스트 파일로 ISO 8895-1 문자셋으로 저장되고, 한글은 유니코드로 변환되어 저장된다.
      이미 타입을 String으로 제한하고 있기 때문에 제네릭을 사용하지 않아도 경고가 발생하지 않는다.

      Properties에서 put() 메소드를 사용하는 경우 해당 클래스에는 put() 메소드가 존재하지 않기 때문에 Hashtable에서 상속받은 put() 메소드를 사용한다. 따라서 매개 변수가 Object이기 때문에 타입을 제한하는 Properties에서 사용하는 것은 바람직하지 않으며, Properties에서 값을 넣고자 하는 경우 setProperty() 메소드를 사용한다.
    • Properties의 메소드
      • setProperty(String key, String value):Object
        Properties에 데이터를 추가한다.
      • getProperty(String key):String
        key를 통해 value를 반환한다
      • getProperty(String key, String defaultValue):String
        key를 통해 value를 반환하지만, 만약 해당하는 key가 없는 경우 defaultValue를 반환한다.
    • TreeMap: 이진 트리를 기반으로 한 Map 컬렉션으로 키와 값이 저장된 Map.Entry를 저장하고, 왼쪽과 오른쪽 자식 노드를 참조하기 위한 두 개의 변수로 구성되어 있다.
      Map을 정렬이 가능하게 해 주며, key 값을 기준으로 정렬해 준다. 따라서 key의 정렬 기준이 없다면 만들어 주어야 한다. key 값을 기준으로 정렬되기 때문에 Comparator를 implements하여 사용할 때 제네릭으로 key의 타입을 사용하여야 한다.

 

 

 

'JAVA 웹 개발 > 1. JAVA' 카테고리의 다른 글

MVC 패턴  (0) 2022.05.13
13. 네트워크  (0) 2022.05.13
11. 입출력(IO)  (0) 2022.05.13
10. 예외 처리  (0) 2022.05.13
09. 기본 API  (0) 2022.05.10