본문 바로가기
Java/Java

컬렉션/Set<E>) HashSet<E>, 주요 메소드, 중복 제거

by 박채니 2022. 3. 31.

안녕하세요, 코린이의 코딩 학습기 채니 입니다.

 

개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.


컬렉션이란?

- 동일한 타입을 묶어 관리하는 자료 구조

 

컬렉션 프레임워크란?

- 리스트, 스택, 큐, 트리 등의 자료구조에 정렬, 탐색 등의 알고리즘을 구조화해 놓은 프레임워크

- 여러 개의 데이터 묶음 자료를 효과적으로 처리하기 위해 구조화된 클래스 또는 인터페이스의 모음

 

컬렉션의 특성에 따라 구분하면 크게 List<E>, Set<E>, Map<K, V>로 나뉩니다.

메모리의 입출력 특성에 따라 기존 컬렉션 기능을 확장/조합한 Stack<E>, Queue<E>도 있습니다.(사진에선 누락)

 


☞ Set<E> 컬렉션 프레임워크

- 인덱스 정보를 포함하고 있지 않은, 집합의 개념

- 순서를 유지하지 않고 저장

- 중복 허용 하지 않음

- Null 값도 중복을 허용 하지 않으므로 하나의 Null 값만 저장

 

Set<E>의 주요 메소드 (인덱스 관련 메소드 X)

구분 리턴타입 메소드명 기능
데이터 추가 boolean add(E element) 매개변수로 입력된 원소를 리스트에 추가
boolean addAll(Collection<? Extends E> c) 매개변수로 입력된 컬렉션 전체를 추가
데이터 삭제 boolean removed(Object o) 원소 중 매개변수 입력과 동일한 객체 삭제
void clear() 전체 원소 삭제
데이터 정보
추출
boolean isEmpty() Set<E> 객체가 비어 있는 지 여부를 리턴
boolean contains(Object o) 매개변수로 입력된 원소가 있는지 여부를 리턴
int size() 리스트 객체 내에 포함된 원소의 개수
Iterator<E> iterator() Set<E> 객체 내의 데이터를 연속해
꺼내는 Iterator 객체 리턴
Set<E> 객체
배열 변환
Object[] toArray() 리스트를 Object 배열로 변환
T[] toArray(T[] t) 입력매개변수로 전달한 타입의 배열로 변환

 


☞ HashSet<E>

- Set에 객체를 저장할 때 hash를 사용하여 처리 속도가 빠름

- 동일 객체 뿐만 아니라 동등 객체도 중복하여 저장하지 않음

- 기본 생성자로 생성할 때 저장 용량의 기본 값은 16, 이후 데이터 개수가 많아지면 동적으로 증가

 

HashSet<E> 인터페이스 객체 생성

HashSet<제네릭 타입 지정> 참조변수 = new HashSet<>();
Set<제네릭 타입 지정> 참조변수 = new HashSet<>();
Collection<제네릭 타입 지정> 참조변수 = new HashSet<>();

HashSet<String> set1 = new HashSet<>();
Set<String> set2 = new HashSet<>();
Collection<String> set3 = new HashSet<>();

 

메소드 사용해보기

데이터 추가하기

//요소 추가 : 리턴된 boolean 요소 추가 여부 확인
set1.add("foo");
set1.add("koo");
set1.add("boo");
set1.add("hoo");

@콘솔출력값
[boo, foo, hoo, koo]

저장 순서를 유지 하지 않기 때문에 입력 순서와 다르게 출력된 것을 확인할 수 있습니다.

 

//요소 추가 : 리턴된 boolean 요소 추가 여부 확인
set1.add("foo");
set1.add("koo");
set1.add("koo");	//중복
set1.add("koo");	//중복
set1.add("boo");
set1.add("hoo");

@콘솔출력값
[boo, foo, hoo, koo]

"koo"를 중복하여 입력하였을 때 HashSet<E>은 중복을 허용하지 않으므로, 중복된 값은 제거되어 출력된 것을 확인할 수 있습니다.

 

//요소 추가 : 리턴된 boolean 요소 추가 여부 확인
System.out.println(set1.add("foo"));
System.out.println(set1.add("koo"));
System.out.println(set1.add("koo"));
System.out.println(set1.add("koo"));
System.out.println(set1.add("boo"));
System.out.println(set1.add("hoo"));

@콘솔출력값
true
true
false
false
true
true

요소 추가 여부를 확인하여 boolean으로 리턴해주기 때문에 boolean 값을 통해 중복을 확인하고, 추가 여부를 확인할 수 있습니다.

중복값들은 요소 추가가 되지 않았으므로 false가 출력되었네요.

 

//다른 Set객체와 병합
Set<String> other = new HashSet<>();
other.add("안녕");
other.add("잘가");
set1.addAll(other);

@콘솔출력값
[잘가, boo, 안녕, foo, hoo, koo]

 

데이터 삭제하기

//제거하기(인덱스로 제거는 불가) - 정확한 요소를 전달해서 제거!
set1.remove("안녕");

@콘솔출력값
[잘가, boo, foo, hoo, koo]

인덱스가 제공되지 않으므로 정확한 요소를 전달하여 제거해야 합니다.

 

//삭제 clear()
set1.clear();

@콘솔출력값
[]

 

데이터 정보 추출하기

//isEmpty()	
System.out.println(set1.isEmpty());

@콘솔출력값
true

 

//요소 포함 여부
System.out.println(other.contains("안녕"));
System.out.println(other.contains("하이"));

@콘솔출력값
true
false

 

//요소 개수
System.out.println(other.size());

@콘솔출력값
2

 

모든 요소 열람

//열람하기
//for문 사용 불가(인덱스가 없기 때문)
		
//1. for-each 사용
for(String s : other) {
	System.out.print(s + " ");
}
System.out.println();
		
//2. iterator
Iterator<String> iter = other.iterator();
while(iter.hasNext()) {
	String s = iter.next();
	System.out.print(s + " ");
}

@콘솔출력값
잘가 안녕 
잘가 안녕

hasNext() : 다음으로 가리킬 원소의 존재 여부를 boolean으로 리턴

next() : 다음 원소 위치로 가서 읽은 값을 리턴


☞ List → Set / Set → List 변환

- List는 중복을 허용하기 때문에 중복 제거를 원한다면 Set으로 변환해줌

- Set은 순서 유지를 해주지 않기 때문에 순서 유지(정렬)를 원한다면 List로 변환해줌

 

List → Set 변환

//1. List -> Set 중복 제거
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(3);
intList.add(1);
intList.add(2);
intList.add(2);
System.out.println(intList);
		
Set<Integer> intSet = new HashSet<>(intList);
System.out.println(intSet);

@콘솔출력값
[1, 3, 1, 2, 2]
[1, 2, 3]

List에서 Set으로 변환하니 중복 제거가 된 것을 확인할 수 있습니다.

Integer 타입이라 자동으로 오름차순 정렬도 된 것을 볼 수 있지만, 완전히 정렬이 된다는 보장은 없기 때문에.. 신뢰는 하지 않음!

 

Set → List 변환

//2. Set -> List
List<Integer> otherList = new ArrayList<>(intSet);
System.out.println(otherList);
System.out.println(otherList.get(0));

@콘솔출력값
[1, 2, 3]
1

결과 값만 보면 변환이 된 것인지 아닌 건지 와닿지는 않지만, Set에는 인덱스가 생겨난 것을 확인할 수 있습니다.

인덱스를 통해 추출할 수 있는 get() 메소드를 사용할 수 있습니다.

 

☞ 중복 제거

VO Class

public class Student {
	private int no;
	private String name;
	
	public Student() {
		super();
	}
	public Student(int no, String name) {
		super();
		this.no = no;
		this.name = name;
	}
	public int getNo() {
		return no;
	}
	public void setNo(int no) {
		this.no = no;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Student [no=" + no + ", name=" + name + "]";
	}
}

 

Set<Student> set = new HashSet<>();
set.add(new Student(1, "홍길동"));
set.add(new Student(2, "신사임당"));
set.add(new Student(3, "장보고"));
set.add(new Student(1, "홍길동"));
		
System.out.println(set);

@콘솔출력값
[Student [no=3, name=장보고], Student [no=2, name=신사임당], Student [no=1, name=홍길동], 
Student [no=1, name=홍길동]]

no가 1이고 이름이 "홍길동"인 객체를 중복으로 만들어 Set에 추가하였습니다.

Set은 중복을 허용하지 않는다고 하였는데, 이는 객체의 equals와 hashCode를 비교하여 중복인지 아닌지를 판별합니다.

 

다만, "홍길동"의 객체는 내용은 완벽히 동일하지만 사실은 다른 객체입니다.

새로운 객체를 new Student 하여 생성하였기 때문에 각자 다른 주소값을 갖고 있게 되겠죠.

 

이러한 상황에서 Object가 가지고 있는 equals와 hashCode를 가져와 비교를 하니 당연히 다른 객체라고 인식을 하게 되는 것입니다.
(Object의 equals는 this == obj; 주소값은 비교)

따라서 Student 클래스에서 equals() 메소드와 hashCode() 메소드를 오버라이딩해줘야 합니다.

(내용이 같다면 중복이라고 인식하고 싶을 때)

 

//Student 클래스

@Override
public int hashCode() {
	return Objects.hash(name, no);
}

@Override
public boolean equals(Object obj) {
	//주소값 비교
	if (this == obj)
		return true;
	//null 체크
	if (obj == null)
		return false;
	//클래스 객체 비교, obj instanceof Student와 동일
	if (getClass() != obj.getClass())
		return false;
	Student other = (Student) obj;	//같은 타입이라면 downcasting
	return Objects.equals(name, other.name) && no == other.no;
}

name과 no의 실제 값이 동일하다면 equals는 true를 반환, hashCode도 동일해야 하기 때문에 name과 no가 같으면 동일한 hashCode를 재생성하는 메소드로 오버라이드 하였습니다.

 

Set<Student> set = new HashSet<>();
set.add(new Student(1, "홍길동"));
set.add(new Student(2, "신사임당"));
set.add(new Student(3, "장보고"));
set.add(new Student(1, "홍길동"));
		
System.out.println(set);

@콘솔출력값
[Student [no=3, name=장보고], Student [no=1, name=홍길동], Student [no=2, name=신사임당]]

no와 name이 같은 "홍길동"의 객체는 중복으로 인식이 되어 제거 된 것을 확인할 수 있습니다.