[김영한의 실전 자바 - 중급 2편] 10. 컬렉션 프레임워크 - 순회, 정렬, 전체 정리

Turtle·2024년 7월 9일
0
post-thumbnail

🏷️순회1 - 직접 구현하는 Iterable, Iterator

순회
자료구조에서 순회는 자료구조에 들어있는 데이터를 차례대로 접근해서 처리하는 것을 말한다.
다양한 자료구조가 존재하며 각각의 자료구조마다 데이터를 접근하는 방법은 모두 다르다.

하지만 자료구조의 구현과 관계 없이 모든 자료구조를 동일한 방법으로 순회할 수 있는 일관성 있는 방법이 있다면 자료구조를 사용하는 개발자 입장에서는 매우 편리할 것이다.

자바는 이런 문제를 해결하기 위해 IterableIterator 인터페이스를 제공한다.

✔️Iterable, Iterator

public interface Iterable<T> {
	Iterator<T> iterator();
}
public interface Iterator<E> {
	boolean hasNext();
    E next();
}
public class MyArrayIterator implements Iterator<Integer> {

	private int currentIndex = -1;
	private int[] targetArr;

	public MyArrayIterator(int[] targetArr) {
		this.targetArr = targetArr;
	}

	// 다음 항목이 있는지 검사한다.
	@Override
	public boolean hasNext() {
		return currentIndex < targetArr.length - 1;
	}

	// 다음 항목을 반환한다.
	@Override
	public Integer next() {
		return targetArr[currentIndex++];
	}
}
public class MyArray implements Iterable<Integer> {

	private int[] array;

	public MyArray(int[] array) {
		this.array = array;
	}
	
	@Override
	public Iterator<Integer> iterator() {
		return new MyArrayIterator(array);
	}
}

🏷️순회2 - 향상된 for문

for-each문이라 불리는 향상된 for문은 자료구조를 순회하는 것이 목적이다. 자바는 Iterable 인터페이스를 구현한 객체에 대해 향상된 for문을 사용할 수 있게 해준다.

🏷️순회3 - 자바가 제공하는 Iterable, Iterator

자바 컬렉션 프레임워크는 다양한 자료구조를 제공한다.
자바는 컬렉션 프레임워크를 사용하는 개발자가 편리하고 일관된 방법으로 자료구조를 순회할 수 있도록 Iterable 인터페이스를 제공하고 이미 각각의 구현체에 맞는 Iterator도 다 구현해두었다.
자바 Collection 인터페이스 상위에 Iterable이 있다는 것은 모든 컬렉션을 Iterable, Iterator로 순회할 수 있다는 것을 말한다.
Map의 경우 키-값 구조이기 때문에 바로 순회를 할 수 없다. 대신 키나 값을 정해서 순회할 수 있는데 이 때, keySet()이나 values()를 사용한다. 물론 Entry를 반환하는 entrySet() 역시 순회가 가능하다.

public class JavaIterableMain {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<>();
		list.add(1);
		list.add(2);
		list.add(3);
		printAll(list.iterator());
		foreach(list);

		Set<Integer> set = new HashSet<>();
		set.add(1);
		set.add(2);
		set.add(3);
		printAll(set.iterator());
		foreach(set);
	}

	private static void foreach(Iterable<Integer> iterable) {
		System.out.println("iterable : " + iterable.getClass());
		for (Integer integer : iterable) {
			System.out.println("integer : " + integer);
		}
	}

	private static void printAll(Iterator<Integer> iterator) {
		System.out.println("iterator : " + iterator.getClass());
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
	}
}

Iterator(반복자) 디자인 패턴 : 객체 지향 프로그래밍에서 컬렉션 요소들을 순회할 때 사용되는 디자인 패턴이다. 이 패턴은 컬렉션 내부 표현 방식을 노출하지 않으면서 그 안의 각 요소에 순차적으로 접근할 수 있게 해준다. Iterator 패턴은 컬렉션 구현과는 독립적으로 요소들을 탐색할 수 있는 방법을 제공하며, 이로 인해 코드 복잡성을 줄이고 재사용성을 높일 수 있다.

🏷️정렬1 - Comparable, Comparator

  • ✔️Comparable(비교의 대상) : 자신과 다른 객체를 비교
    • 자신과 비교하려는 다른 대상을 비교
      • 결과값 -1 : 자신이 비교하고자 하는 다른 대상보다 작다.
      • 결과값 0 : 자신과 비교하고자 하는 다른 대상이 같다.
      • 결과값 1 : 자신이 비교하고자 하는 다른 대상보다 크다.
public class Main {
	public static void main(String[] args) {
		Integer int1 = Integer.valueOf(1);
		Integer int2 = Integer.valueOf(2);
		Integer int3 = Integer.valueOf(3);

		int i1 = int1.compareTo(3);
		int i2 = int2.compareTo(1);
		int i3 = int3.compareTo(3);

		System.out.println("i1 = " + i1);
		System.out.println("i2 = " + i2);
		System.out.println("i3 = " + i3);
	}
}
  • ✔️Comparator(외부에서의 비교) : 외부에서 두 객체를 동일한 기준에서 비교
    • 외부에서 두 객체를 비교
      • 앞의 객체가 뒤의 객체보다 작으면? → 음수(오름차순)
      • 앞의 객체가 뒤의 객체보다 크면? → 양수(내림차순)

🏷️정렬2 - Comparable, Comparator

객체의 비교1 - Arrays.sort(array) : 기본 정렬(오름차순)
객체의 비교2 - Arrays.sort(array) : 특정 기준 정렬

public class MyUser implements Comparable<MyUser> {
	private String id;
	private int age;

	public MyUser(String id, int age) {
		this.id = id;
		this.age = age;
	}

	// 나이 오름차순 정렬 구현해보기
	@Override
	public int compareTo(MyUser myUser) {
		return (this.age < myUser.age) ? -1 : ((this.age == myUser.age) ? 0 : 1);
	}

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

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}
public class SortMain3 {
	public static void main(String[] args) {
		MyUser myUser1 = new MyUser("a", 30);
		MyUser myUser2 = new MyUser("b", 20);
		MyUser myUser3 = new MyUser("c", 10);

		MyUser[] array = {myUser1, myUser2, myUser3};
		System.out.println("정렬 전 데이터");
		System.out.println(Arrays.toString(array));

		System.out.println("정렬 후 데이터");
		Arrays.sort(array);
		System.out.println(Arrays.toString(array));
	}
}

실행 결과

정렬 전 데이터
[MyUser{id='a', age=30}, MyUser{id='b', age=20}, MyUser{id='c', age=10}]
정렬 후 데이터
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]

객체의 비교 기준이 없는 경우 발생하는 예외 : java.lang.ClassCastException: class collection.compare.MyUser cannot be cast to class java.lang.Comparable

해결1 : Comparable을 구현하여 해결하는 경우

How to use the Comparable CompareTo on Strings in Java

public class MyUser1 implements Comparable<MyUser1> {
	private String id;
	private int age;

	public MyUser1(String id, int age) {
		this.id = id;
		this.age = age;
	}

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

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public int compareTo(MyUser1 o) {
		return this.getId().compareTo(o.getId());
	}
}
public class SortMain3 {
	public static void main(String[] args) {
		MyUser1 myUser1 = new MyUser1("a", 30);
		MyUser1 myUser2 = new MyUser1("b", 20);
		MyUser1 myUser3 = new MyUser1("c", 10);

		MyUser1[] array = {myUser3, myUser2, myUser1};
		System.out.println("정렬 전 데이터");
		System.out.println(Arrays.toString(array));

		System.out.println("정렬 후 데이터");
		Arrays.sort(array);
		System.out.println(Arrays.toString(array));
	}
}

실행 결과

정렬 전 데이터
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]
정렬 후 데이터
[MyUser{id='a', age=30}, MyUser{id='b', age=20}, MyUser{id='c', age=10}]

해결2 : Comparator을 구현하여 해결하는 경우

public class MyUser2 {
	private String id;
	private int age;

	public MyUser2(String id, int age) {
		this.id = id;
		this.age = age;
	}

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

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}
public class IdComparator implements Comparator<MyUser2> {
	@Override
	public int compare(MyUser2 o1, MyUser2 o2) {
		return (o1.getId().compareTo(o2.getId()));
	}
}
public class SortMain4 {
	public static void main(String[] args) {
		MyUser2 myUser1 = new MyUser2("a", 30);
		MyUser2 myUser2 = new MyUser2("b", 20);
		MyUser2 myUser3 = new MyUser2("c", 10);

		MyUser2[] array = {myUser3, myUser2, myUser1};
		System.out.println("정렬 전 데이터");
		System.out.println(Arrays.toString(array));

		System.out.println("정렬 후 데이터");
		Arrays.sort(array, new IdComparator());
		System.out.println(Arrays.toString(array));
	}
}

실행 결과

정렬 전 데이터
[MyUser{id='c', age=10}, MyUser{id='b', age=20}, MyUser{id='a', age=30}]
정렬 후 데이터
[MyUser{id='a', age=30}, MyUser{id='b', age=20}, MyUser{id='c', age=10}]

🏷️정렬3 - Comparable, Comparator

Collections.sort(list)
리스트는 순서가 있는 컬렉션이므로 정렬할 수 있다.
이 메서드를 사용하면 기본 정렬이 적용된다.
하지만 이 방식보다는 객체 스스로 정렬 메서드를 가지고 있는 list.sort() 사용을 더 권장

list.sort(null)
별도의 비교자가 없으므로 Comparable로 비교해서 정렬한다.
자연적 순서로 비교한다.

Collections.sort(list, new IdCompartor())
별도의 비교자로 비교하고 싶다면 두 번째 인자에 비교자를 넘긴다.
하지만 이 방식보다는 객체 스스로 정렬 메서드를 가지고 있는 list.sort() 사용을 더 권장

list.sort(new IdCompartor())
전달한 비교자로 비교한다.

🏷️컬렉션 유틸

public class CollectionsSortMain {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<>();
		list.add(1);
		list.add(2);
		list.add(3);
		list.add(4);
		list.add(5);

		Integer max = Collections.max(list);
		Integer min = Collections.min(list);
		System.out.println("min = " + min);
		System.out.println("max = " + max);

		System.out.println("list = " + list);
		Collections.shuffle(list);
		System.out.println("list = " + list);
		Collections.sort(list);
		System.out.println("list = " + list);
		Collections.reverse(list);
		System.out.println("list = " + list);
	}
}

✔️불변 컬렉션 생성

public class OfMain {
	public static void main(String[] args) {
		// 편리한 불변 컬렉션 생성(List.of 사용 권장)
		List<Integer> list = List.of(1, 2, 3);
		Set<Integer> set = Set.of(1, 2, 3);
		Map<String, Integer> map = Map.of("One", 1, "Two", 2, "Three", 3);

		System.out.println("map = " + map);
		System.out.println("set = " + set);
		System.out.println("list = " + list);
		System.out.println(map.getClass());  // class java.util.ImmutableCollections$MapN
		System.out.println(set.getClass());  // class java.util.ImmutableCollections$SetN
		System.out.println(list.getClass()); // class java.util.ImmutableCollections$ListN
	}
}

❗불변 컬렉션은 말 그대로 불변이므로 변경이 불가능하다. 변경을 시도하게 되면 다음과 같은 예외가 발생한다. → Exception in thread "main" java.lang.UnsupportedOperationException

  • ✔️불변 컬렉션 ↔ 가변 컬렉션 전환
    • 불변 리스트 → 가변 리스트 : new ArrayList<>() 사용
    • 가변 리스트 → 불변 리스트 : Collections.unmodifiableList() 사용
public class ImmutableMain {
	public static void main(String[] args) {
		List<Integer> list = List.of(1, 2, 3);
		System.out.println("list = " + list);
		System.out.println(list.getClass());

		List<Integer> mutableList = new ArrayList<>(list);
		mutableList.add(4);
		System.out.println("mutableList = " + mutableList);
		System.out.println(mutableList.getClass());

		List<Integer> unmodifiableList = Collections.unmodifiableList(mutableList);
		System.out.println(unmodifiableList.getClass());
	}
}

실행 결과

list = [1, 2, 3]
class java.util.ImmutableCollections$ListN
mutableList = [1, 2, 3, 4]
class java.util.ArrayList
class java.util.Collections$UnmodifiableRandomAccessList

✔️빈 리스트 생성

public class EmptyListMain {
	public static void main(String[] args) {
		// 빈 가변 리스트
		ArrayList<Integer> list1 = new ArrayList<>();
		LinkedList<Integer> list2 = new LinkedList<>();

		System.out.println(list1.getClass());
		System.out.println(list2.getClass());

		// 빈 불변 리스트
		List<Integer> list3 = Collections.emptyList();
		List<Integer> list4 = List.of();

		System.out.println(list3.getClass());
		System.out.println(list4.getClass());
	}
}
  • ✔️멀티쓰레드 동기화
    • 동기화 작업으로 인해 일반 리스트보다 성능은 느리다.
public class SyncMain {
	public static void main(String[] args) {
		ArrayList<Integer> list = new ArrayList<>();
		list.add(1);
		list.add(2);
		list.add(3);
		System.out.println("list class = " + list.getClass());

		List<Integer> synchronizedList = Collections.synchronizedList(list);
		System.out.println(synchronizedList.getClass());
	}
}

🏷️컬렉션 프레임워크 전체 정리

실무 선택 가이드
List : ArrayList
Set : HashSet
Map : HashMap
Queue : ArrayDeque

0개의 댓글