컬렉션 (2) - Set (23.05.11)

·2023년 5월 11일
0

Java

목록 보기
28/35
post-thumbnail

📝 Set

저장 순서가 유지되지 않고, 중복 객체도 저장하지 못하게 하는 자료 구조

  • 순서를 유지하지 않음 (== 인덱스 없음)
  • 중복을 허용하지 않음
    -> null도 중복 불가능, 1개만 저장 가능
  • 구현 클래스 : HashSet, LinkedHashSet, TreeSet
  • Set이 중복을 확인하는 방법
    - 객체가 가지고 있는 필드 값이 모두 같으면 중복으로 판단
    -> 이때 필드 값이 모두 같은지 비교하기 위해서
    객체에 "equals()"가 반드시 오버라이딩 되어 있어야 한다.

💡 HashSet

Set의 대표적인 자식 클래스

  • 사용 조건
    1. 저장되는 객체에 equals() 오버라이딩 필수
    2. 저장되는 객체에 hashCode() 오버라이딩 필수
    -> Hash라는 단어가 붙은 컬렉션은 반드시 저장되는 객체에
    equals(), hashCode()를 오버라이딩 해야 함
    -> 이유: equals()의 결과가 true인 두 객체의 해시코드는 같아야 하기 때문!

💡 LinkedHashSet

HashSet과 거의 동일하지만 Set에 추가되는 순서를 유지한다는 점이 다름


💡 TreeSet

이진 트리를 기반으로 한 Set컬렉션

  • 왼쪽과 오른쪽 자식 노드를 참조하기 위한 두 개의 변수로 구성


✏️ Set 계열 주요 기능

  • Set.add(Object e)
    : 추가

  • size()
    : 저장된 데이터의 개수 반환

  • remove(Object e)
    : Set에 저장된 객체 중에서 매개변수 e와 필드 값이 같은 객체를 제거
    + Hash라는 단어가 포함된 Set이면 hashCode()도 같아야 함

Set은 순서가 없어서 저장된 객체 하나를 얻어 올 수 있는 방법이 없다.
대신에 Set 전체의 데이터를 하나씩 반복적으로 얻어 올 수는 있다.
방법 : 1. Iterator / 2. 향상된 for문

  • Iterator (반복자)
    : 컬렉션에서 제공하는 컬렉션 객체 반복 접근자
    (컬렉션에 저장된 데이터를 임의로 하나씩 반복적으로 꺼내는 역할)
Iterator<Object> it = set.iterator();
  • set.iterator()
    : Set을 Iterator가 하나씩 꺼내 갈 수 있는 모양으로 변환함

  • 변수.hashNext()
    : 다음 값이 있으면 true 반환

  • 변수.next()
    : 다음 값(객체)를 얻어 옴

  • Hash 함수
    : 입력된 단어를 지정된 길이의 문자열로 변환하는 함수 (중복 X)
    ex) 입력 : 111 -> "asdfg" (5글자)
    입력 : 1234567 -> "qwert" (5글자)

  • hashCode()
    : 필드 값이 다르면 중복되지 않는 숫자를 만드는 메소드
    -> 왜 만들까? 빠른 데이터 검색을 위해서(객체가 어디에 있는지 빨리 찾기 위해서)

  • HashSet()
    : 중복 없이 데이터를 저장(Set)하고 데이터 검색이 빠름(Hash)


✏️ 예제 1 - Set 알아보기

package edu.kh.collection.model.service;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SetService {

	// Set(집합)
	// - 순서를 유지하지 않음(== 인덱스 없음)
	// - 중복을 허용하지 않음 (+ null도 중복 X, 1개만 저장 가능)
	
	// *** Set이 중복을 확인하는 방법 ***
	// -> 객체가 가지고 있는 필드 값이 모두 같으면 중복으로 판단
	// --> 이때 필드 값이 모두 같은지 비교하기 위해서
	//	   객체에 "equals()"가 반드시 오버라이딩 되어 있어야 한다.

	public void ex1() {
		
		Set<String> set = new HashSet<String>();
		
		// HashSet : Set의 대표적인 자식 클래스
		// 사용 조건 1 : 저장되는 객체에 equals() 오버라이딩 필수
		// 사용 조건 2 : 저장되는 객체에 hashCode() 오버라이딩 필수

		// "참고" : Hash라는 단어가 붙은 컬렉션은
		//		   반드시 저장되는 객체에 equals(), hashCode()를 오버라이딩 해야 함

		// Set.add(String e) : 추가
		set.add("네이버");
		set.add("카카오");
		set.add("라인");
		set.add("쿠팡");
		set.add("배달의민족");
		set.add("배달의민족");
		set.add("배달의민족");
		set.add(null);
		set.add(null);
		set.add(null);
		
		System.out.println(set);
		// 확인할 것 : 1. 순서 / 2. 중복 X / 3. null 중복 X
		
		// size() : 저장된 데이터의 개수 반환
		System.out.println("저장된 데이터의 수 : " + set.size());
		
		// remove(String e) : Set에 저장된 객체 중에서 매개변수 e와
		//					  필드 값이 같은 객체를 제거
		//					  + Hash라는 단어가 포함된 Set이면 hashCode()도 같아야 함
		
		System.out.println(set.remove("라인"));
		System.out.println(set.remove("야놀자"));
		
		System.out.println(set); // 제거 확인
		
		// Set은 순서가 없어서 저장된 객체 하나를 얻어 올 수 있는 방법이 없다.
		// -> 대신에 Set 전체의 데이터를 하나씩 반복적으로 얻어 올 수는 있다.
		
		// 1. Iterator (반복자)
		// - 컬렉션에서 제공하는 컬렉션 객체 반복 접근자
		// (컬렉션에 저장된 데이터를 임의로 하나씩 반복적으로 꺼내는 역할)
		
		// Iterator가 얻어 온 데이터의 타입은 모두 String임을 알려 줌
		Iterator<String> it = set.iterator();
		
		// set.iterator() : Set을 Iterator가 하나씩 꺼내 갈 수 있는 모양으로 변환
		
		while(it.hasNext()) { // 하나씩 꺼냈을 때 다음 값이 없는 경우 == 끝
			// -> 다음 값이 있으면 반복해야 한다.
		
			// it.hashNext() : 다음 값이 있으면 true 반환
			// it.next() 	 : 다음 값(객체)를 얻어 옴
		
			String temp = it.next();
			System.out.println(temp);
		
		}

		System.out.println("--------------------------------------------------");
		
		// 2. 향상된 for문 사용
		// for( 하나씩 꺼내서 저장할 변수 : 컬렉션 ) 
		for(String temp : set) {
			System.out.println(temp);
		}
	}
}
  • 출력 화면

✏️ 예제 2 - Object의 equals(), hashCode() 오버라이딩

  • Member 클래스
.
.
.
	@Override
	public boolean equals(Object obj) {

		// 매개변수 다운캐스팅
		Member other = (Member)obj;
		
		// 필드 값 비교
		return this.id.equals(other.id) && this.pw.equals(other.pw) && this.age == other.age;
	}
  • SetService 클래스
	public void ex2() {
		// Object의 equals(), hashCode() 오버라이딩
		
		// A.equals(B) : A와 B가 가지고 있는 필드 값이 모두 같으면 true, 아니면 false
		
		// Hash 함수 : 입력된 단어를 지정된 길이의 문자열로 변환하는 함수 (중복 X)
		// ex)  입력 : 111 -> "asdfg" (5글자)
		// ex)  입력 : 1234567 -> "qwert" (5글자)
		
		// hashCode() : 필드 값이 다르면 중복되지 않는 숫자를 만드는 메소드
		
		// -> 왜 만들까? 빠른 데이터 검색을 위해서(객체가 어디에 있는지 빨리 찾기 위해서)
		
		// HashSet() : 중복 없이 데이터를 저장(Set)하고 데이터 검색이 빠름(Hash)
		
		Member mem1 = new Member("user01", "pass01", 30);
		Member mem2 = new Member("user01", "pass01", 30);
		Member mem3 = new Member("user02", "pass02", 20);
		
		// mem1과 mem2가 같은지 비교
		System.out.println(mem1 == mem2); // 주소 비교
										  // 얕은 복사 경우가 아니라면 다 false
		
		// mem1과 mem2가 가지고 있는 필드 값이 같은지 비교
		if( mem1.getId().equals( mem2.getId() ) ) { // 아이디가 같을 경우
			
			if( mem1.getPw().equals( mem2.getPw() ) ) { // 비밀번호도 같은 경우
				
				if( mem1.getAge() == mem2.getAge() ) { // 나이까지도 같은 경우
					System.out.println("같은 객체입니다. (true)");
				}
			}
		}
		
		// -> 매번 이렇게 비교하기 힘들다 ... 비교 코드를 작성해서 재활용하자!
		// == equals() 메소드 오버라이딩
		
		System.out.println("--------------------------------------------------");
		System.out.println( mem1.equals(mem2) ); // mem1과 mem2의 필드는 같은가?
		System.out.println( mem1.equals(mem3) ); // mem1과 mem2의 필드는 같은가?
		
		// 서로 다른 객체지만 필드 값이 같다 == 동등
		// 비교하려는 것이 정말 같은 하나의 객체이다 == 동일
	}
  • 출력 화면

✏️ 예제 3 - equals()가 오버라이딩된 Member를 Set에 저장

  • Member 클래스
.
.
.
	@Override
	public String toString() {
		return "Member [id=" + id + ", pw=" + pw + ", age=" + age + "]";
	}

	@Override
	public int hashCode() {
		return Objects.hash(age, id, pw);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Member other = (Member) obj;
		return age == other.age && Objects.equals(id, other.id) && Objects.equals(pw, other.pw);
	}
	
	
	
	/*
	// Object.equals() 오버라이딩
	// - 현재 객체와 매개변수로 전달받은 객체의 필드가 같은지 비교하는 형태로 오버라이딩
	@Override
	public boolean equals(Object obj) {

		// 매개변수 다운캐스팅
		Member other = (Member)obj;
		
		// 필드 값 비교
		return this.id.equals(other.id) && this.pw.equals(other.pw) && this.age == other.age;
	}

	// Object.hashCode() 오버라이딩
	@Override
	public int hashCode() {
		// 필드 값이 같은 객체는 같은 정수를 반환해야 한다.
		// == 필드 값을 이용해서 정수를 만들면 된다.
		
		final int PRIME = 31; // 31이 연산 속도가 빠른 소수 중 하나
				// 소수
		
		int result = 1; // 결과 저장용 변수
		
		result = result * PRIME * age;
		
		result = result * PRIME * ( id == null ? 0 : id.hashCode() );
		
		result = result * PRIME * ( pw == null ? 0 : pw.hashCode() );
	
		return result;
	}
	*/
  • SetService 클래스
	public void ex3() {
		
		// equals()가 오버라이딩된 Member를 Set에 저장
		
		// [Key Point!] : 중복이 제거가 되는가?
		
		Set<Member> memberSet = new HashSet<Member>();
		
		memberSet.add( new Member("user01", "pass01", 30) );
		memberSet.add( new Member("user02", "pass02", 40) );
		memberSet.add( new Member("user03", "pass03", 20) );
		memberSet.add( new Member("user04", "pass04", 25) );
		memberSet.add( new Member("user04", "pass04", 25) );
		
		for( Member mem : memberSet ) {
			System.out.println(mem);
		}
		
		// hashCode() 오버라이딩 전
		// -> equals()가 오버라이딩 되어 있지만 중복 제거가 되지 않음
		// -> 왜? HashSet은 hashCode()도 오버라이딩 해야 한다.
		
		Member mem1 = new Member("user01", "pass01", 30);
		Member mem2 = new Member("user01", "pass01", 30);
		Member mem3 = new Member("user01", "pass01", 31);
		
		System.out.println(mem1.hashCode());
		System.out.println(mem2.hashCode());
		System.out.println(mem3.hashCode());
		
	}
  • 출력 화면

📝 Wrapper 클래스

기본 자료형을 객체로 포장하는 클래스

  • 컬렉션에 기본 자료형 값을 저장할 때 사용
  • 기본 자료형에 없던 추가 기능, 값을 이용하고 싶을 때 사용

💡 Wrapper 클래스의 종류

  • int -> Integer
  • double -> Double
  • Boolean, Byte, Short, Long, Float, Character


✏️ 예제 4 - Wrapper 클래스와 Parsing 알아보기

  • SetService 클래스
	public void ex4() {
		// Wrapper 클래스 : 기본 자료형 -> 객체로 포장하는 클래스
		
		// - 컬렉션에 기본 자료형 값을 저장할 때 사용
		// - 기본 자료형에 없던 추가 기능, 값을 이용하고 싶을 때 사용
		
		// <Wrapper 클래스의 종류>
		// int		-> Integer
		// double	-> Double
		// Boolean, Byte, Short, Long, Float, Character
		
		int iNum = 10;
		double dNum = 3.14;
		
		// 기본 자료형 -> 포장
		Integer w1 = new Integer(iNum); // int가 Integer로 포장
		Double w2 = new Double(dNum); // double이 Double로 포장
		
		
		// Wrapper 클래스 활용
		System.out.println("int 최대값 : " + w1.MAX_VALUE);
		System.out.println("double 최소값 : " + w2.MIN_VALUE);
									// 기울어진 글씨 == static
									// static은 클래스명.필드명 / 클래스명.메소드명() 호출 가능
		
		System.out.println("w1 값 : " + w1);
		System.out.println("w2 값 : " + w2);
		
		System.out.println("--------------------------------------------------");
		System.out.println("static 방식으로 Wrapper 클래스 사용하기");
		
		System.out.println("int 최소값 : " + Integer.MIN_VALUE);
		System.out.println("double 최대값 : " + Double.MAX_VALUE);
	
		// *********************************************************
		// parsing : 데이터의 형식을 변환
		
		
		// ! String 데이터를 기본 자료형으로 변환 !
		
		int num1 = Integer.parseInt("100"); // 문자열 "100"을 int 형식으로 변환
		double num2 = Double.parseDouble("1.23456"); // 문자열 "1.23456"을 double 형식으로 변환
		
		System.out.println( num1 + num2 );
		
		// *********************************************************
	}
  • 출력 화면

✏️ 예제 5 - Wrapper 클래스의 AutoBoxing / AutoUnboxing

  • SetService 클래스
	public void ex5() {
		
		// Wrapper 클래스의 AutoBoxing / AutoUnboxing
		
		Integer w1 = new Integer(100);
		// 삭제선 == deprecated == 해당 구문은 삭제될 예정이다
		// ==> 사용을 권장하지 않는다
		
		Integer w2 = 100;
		Integer w3 = 100;
	//	(Integer)	 (int -> Integer) 자동 포장
					// AutoBoxing
		
		// w2와 100은 원래 연산이 안 되어야 하지만
		// Integer는 int의 포장 형식이라는 것을 java가 인식하고 있어서
		// 위와 같은 경우 int를 Integer로 자동 포장 해 준다.
		
		System.out.println("w2 + w3 = " + (w2+ w3));
		
		// w2 (Integer 객체)
		// w3 (Integer 객체)
		// w2 + w3 == 객체 + 객체 --> 원래는 불가능
		
		// 하지만 Integer는 int의 포장 형태라는 것을 Java가 인식하고 있어서
		// + 연산 시 포장을 자동으로 벗겨냄
		
		// Integer + Integer -> int + int (자동 포장 해제)
							// AutoUnBoxing
	}
  • 출력 화면

✏️ 예제 6 - 로또 번호 생성기 Version.2

  • SetService 클래스
	public void lotto() {
		
		// 로또 번호 생성기 Version.2
		
		// Set<int> lotto = new HashSet<int>();
		// int로 타입 제한을 할 수 없다!
		// 왜? int 기본 자료형이기 때문에 객체만 저장하는 컬렉션에는 들어갈 수 없다.
		
		// -> 해결 방법 : Wrapper Class를 이용해서 기본 자료형을 객체로 포장한다.
        
        // Set<Integer> lotto = new HashSet<Integer>();
		// Set<Integer> lotto = new LinkedHashSet<Integer>();
		Set<Integer> lotto = new TreeSet<Integer>();
		
		// Integer는 equals(), hashCode() 오버라이딩 완료 상태
		
		while(lotto.size() < 6) {
			// lotto에 저장된 값이 개수가 6개 미만이면 반복
			
			int random = (int)(Math.random()*45 + 1); // 1 ~ 45 사이 난수
			
			System.out.println(random);
			
			lotto.add(random);
			// int 값이 자동으로 Integer로 포장(AutoBoxing)되어 lotto에 추가
			
		}
		
		System.out.println("로또 번호 : " + lotto);
	}
  • 출력 화면

(1) HashSet() 사용


-> 난수가 생성된 순서가 아닌 랜덤으로 HashSet()에 저장되고 있음.

(2) LinkedHashSet() 사용


-> 난수가 생성된 순서대로 LinkedHashSet()에 저장되고 있음. 그러나 오름차순으로 정렬되지 않음!

(3) TreeSet() 사용

-> 난수가 오름차순으로 정렬되어 저장됨.

profile
풀스택 개발자 기록집 📁

0개의 댓글