저장 순서가 유지되지 않고, 중복 객체도 저장하지 못하게 하는 자료 구조
- 순서를 유지하지 않음 (== 인덱스 없음)
- 중복을 허용하지 않음
-> null도 중복 불가능, 1개만 저장 가능- 구현 클래스 : HashSet, LinkedHashSet, TreeSet
Set의 대표적인 자식 클래스
- 사용 조건
1. 저장되는 객체에 equals() 오버라이딩 필수
2. 저장되는 객체에 hashCode() 오버라이딩 필수
-> Hash라는 단어가 붙은 컬렉션은 반드시 저장되는 객체에
equals(), hashCode()를 오버라이딩 해야 함
-> 이유: equals()의 결과가 true인 두 객체의 해시코드는 같아야 하기 때문!
HashSet과 거의 동일하지만 Set에 추가되는 순서를 유지한다는 점이 다름
이진 트리를 기반으로 한 Set컬렉션
- 왼쪽과 오른쪽 자식 노드를 참조하기 위한 두 개의 변수로 구성
Set.add(Object e)
: 추가
size()
: 저장된 데이터의 개수 반환
remove(Object e)
: Set에 저장된 객체 중에서 매개변수 e와 필드 값이 같은 객체를 제거
+ Hash라는 단어가 포함된 Set이면 hashCode()도 같아야 함
Set은 순서가 없어서 저장된 객체 하나를 얻어 올 수 있는 방법이 없다.
대신에 Set 전체의 데이터를 하나씩 반복적으로 얻어 올 수는 있다.
방법 : 1. Iterator / 2. 향상된 for문
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)
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);
}
}
}
.
.
.
@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;
}
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의 필드는 같은가?
// 서로 다른 객체지만 필드 값이 같다 == 동등
// 비교하려는 것이 정말 같은 하나의 객체이다 == 동일
}
.
.
.
@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;
}
*/
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());
}
기본 자료형을 객체로 포장하는 클래스
- 컬렉션에 기본 자료형 값을 저장할 때 사용
- 기본 자료형에 없던 추가 기능, 값을 이용하고 싶을 때 사용
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 );
// *********************************************************
}
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
}
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() 사용
-> 난수가 오름차순으로 정렬되어 저장됨.