List와 Set

mongBrown·2026년 4월 17일

List와 Set — 같은 데이터를 두 번 넣으면 어떻게 되는가


중복을 넣으면 어떻게 다른가

List<String> list = new ArrayList<>();
list.add("Java");
list.add("Spring");
list.add("Java");

System.out.println(list);      // [Java, Spring, Java]
System.out.println(list.size()); // 3
Set<String> set = new HashSet<>();
set.add("Java");
set.add("Spring");
set.add("Java");

System.out.println(set);      // [Java, Spring]
System.out.println(set.size()); // 2

List는 중복을 그대로 저장한다. Set은 두 번째 "Java"를 무시한다.


HashSet은 중복을 어떻게 판단하는가

HashSet.add()의 반환값이 힌트다.

Set<String> set = new HashSet<>();

boolean first  = set.add("Java"); // true  — 새로 추가됨
boolean second = set.add("Java"); // false — 중복으로 추가 안 됨

내부적으로 두 단계를 거친다. 먼저 hashCode()로 버킷 위치를 결정하고, 해당 버킷에 이미 값이 있으면 equals()로 한 번 더 비교한다. 둘 다 같아야 중복으로 판단한다.

String a = "Java";
String b = new String("Java"); // 다른 객체

System.out.println(a == b);                       // false — 다른 참조
System.out.println(a.equals(b));                  // true  — 내용 같음
System.out.println(a.hashCode() == b.hashCode()); // true  — 내용 기준으로 계산

Set<String> set = new HashSet<>();
set.add(a);
set.add(b);
System.out.println(set.size()); // 1 — 중복으로 잡힘

String은 equals()hashCode() 모두 내용 기준으로 재정의되어 있어서, 다른 객체여도 내용이 같으면 중복으로 판단한다.


equals()를 재정의하지 않으면 어떻게 되는가

class Tag {
    String name;
    Tag(String name) { this.name = name; }
}

Set<Tag> set = new HashSet<>();
set.add(new Tag("Java"));
set.add(new Tag("Java"));

System.out.println(set.size()); // 2 — 중복을 못 잡음

Tag 클래스는 equals()를 재정의하지 않았다. 기본 equals()는 참조(주소) 비교라서, 내용이 같아도 new로 만든 서로 다른 객체는 다르다고 판단한다. 결과적으로 둘 다 저장된다.

의도한 대로 동작하려면 equals()hashCode()를 함께 재정의해야 한다.

class Tag {
    String name;
    Tag(String name) { this.name = name; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Tag)) return false;
        return name.equals(((Tag) o).name);
    }

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

Set<Tag> set = new HashSet<>();
set.add(new Tag("Java"));
set.add(new Tag("Java"));

System.out.println(set.size()); // 1 — 중복 잡힘

equals()만 재정의하고 hashCode()를 빠뜨리면, 논리적으로 같은 객체인데 다른 버킷에 배정되어 HashSet이 중복을 못 잡는 버그가 생긴다.


Set 구현체별로 순서가 어떻게 다른가

Set<String> hashSet = new HashSet<>();
hashSet.add("Banana");
hashSet.add("Apple");
hashSet.add("Cherry");
System.out.println(hashSet); // [Apple, Cherry, Banana] — 순서 보장 안 됨

Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Banana");
linkedHashSet.add("Apple");
linkedHashSet.add("Cherry");
System.out.println(linkedHashSet); // [Banana, Apple, Cherry] — 삽입 순서 유지

Set<String> treeSet = new TreeSet<>();
treeSet.add("Banana");
treeSet.add("Apple");
treeSet.add("Cherry");
System.out.println(treeSet); // [Apple, Banana, Cherry] — 자동 정렬

중복 없는 데이터만 필요하면 HashSet, 입력 순서를 유지해야 하면 LinkedHashSet, 정렬된 상태가 필요하면 TreeSet을 선택한다. 그리고 커스텀 객체를 Set에 넣을 때는 반드시 equals()와 hashCode()를 함께 재정의해야 의도한 대로 동작한다.

profile
화이팅!

0개의 댓글