<보충> 컬랙션 추가 공부 (2) Map, Set, Queue
1.
public class MapTest1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
Random r = new Random();
System.out.print("보 바위 가위 선택하세요 : ");
int user = sc.nextInt();
int com = r.nextInt(3); // 0~2
HashMap<Integer, String> comMap = new HashMap<>();
comMap.put(0,"보");
comMap.put(1,"바위");
comMap.put(2,"가위");
System.out.println("User : " + comMap.get(user)+", Com "+comMap.get(com));
}
}
Hash 함수
- 어떤 인풋이 왔을 때 정해진 길이의 아웃풋으로 바꿔주는 역할.
Map도 결국엔 배열의 일환
배열은 어디에 저장을 한 지 인덱스를 알아야 함.
put(2,"가위"); 일 때, 2 자리에 숫자 대신에 문자가 들어올 수도 있다.
이 때 어떻게든 이 자리에 오는 것을 칸에 매핑해야 한다.
그런데 hash함수는 이 변환 작업이 아주아주 빠르다.
값을 넣음과 동시에 자리를 찾아감. 순식간. 그래서 HashMap이 빠른 것.
HashMap은 두 개의 원소가 하나의 쌍을 이뤄서 작동.
앞의 0, 1, 2는 인덱스와는 별로 상관이 없다.
언제든 다른 값이 들어갈 수 있다.
get은 무조건 key를 기준으로.
key는 절대 중복되지 않아야 함.
class C {
int k;
}
class D{
String str;
}
public class MapTest2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashMap<C, D> map = new HashMap<>();
C c = new C();
C c1 = new C();
map.put(c, new D());
map.get(c1); // null
map.get(c);//객체 D를 리턴
System.out.println(map.get(c1));
System.out.println(map.get(c));
}
}
데이터베이스는 특이하게도 들어갈 때와 나올 때의 값이 다르게 판단이 되는데
우리는 이 값이 "사실상 동일"하다는 것을 인지하고 있음.
그래서 이것을 같다고 판단 되게 만들고 싶음.(동등성을 판단하고 싶다면)
현재 이 코드에서는 동일성을 판단하고 있다.
어떻게 하면 되려나?
HashMap 내부적으로 equals에 대한 검사를 하면 된다.
=> C의 equals를 내가 재정의 하면 된다.
class C {
int k;
// 추가한 부분
public boolean equals(Object other) {
C c = (C) other;
return this.k = c.k;
}
class D{
String str;
}
public class MapTest2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashMap<C, D> map = new HashMap<>();
C c = new C();
C c1 = new C();
map.put(c, new D());
map.get(c1); // null
map.get(c);//객체 D를 리턴
System.out.println(map.get(c1));
System.out.println(map.get(c));
}
}
=> 이렇게 만들면 물리적 주소를 비교하는 게 아니라,
물리적 주소가 다르더라도 가지고 있는 k 가 같으면 같다고 판단하게 만든 것.
클래스의 필드는 의도적 초기화를 시키지 않으면 기본값으로 0으로 초기화 되게 되어있음.
그래서 k는 0일 것이고, 만든 객체 c와 c1은 둘 다 int 0이 들어가 있을 것.
따라서 이 때는 0과 0을 비교하게 되므로 같다고 나올 것.
// 하지만 약간의 한계가 있음. Hash코드까지 완전하게 구현해줘야 원하는 결과가 정확하게 나오게 됨.
class C {
int k;
// 추가한 부분
//public boolean equals(Object other) {
// C c = (C) other;
// return this.k = c.k;
// 요렇게 완전체
@Override
public boolean equals(Object ob) {
if(this == ob) {
return true;
}
if(ob == null || getClass() != ob.getClass()) {
return false
}
C c = (C) ob;
return k == c.k;
}
@Override
public int hashCode() {
return Object.hash(k);
}
}
class D{
String str;
}
public class MapTest2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashMap<C, D> map = new HashMap<>();
C c = new C();
C c1 = new C();
map.put(c, new D());
map.get(c1); // null
map.get(c);//객체 D를 리턴
System.out.println(map.get(c1));
System.out.println(map.get(c));
}
}
아까 그림을 보면
hash코드는
'유진'이 들어오면 항상 같은 곳을 가리키게 해야 되는데,
해시코드가 구현이 안 되어 있으면 엉뚱한 데 가서 매핑이 됨. 그래서 문제
equals는 어느정도 충돌을 허용해줌.
'유진'이 어떤 칸(b3)으로 들어왔다고 가정했을 때,
b3칸에는 유진, 이서, 원영 등 여러 이름들이 있다.(충돌허용)
이 때 b3에서 '유진'을 찾을 때 equals()를 쓰는 것.
그래서 hash코드와 equals가 쌍으로 정확하게 구현이 있어야 제대로 작동한다.
2.
public class SetTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashSet<String> a = new HashSet<>();
HashSet<Integer> b = new HashSet<>();
String[] color = {"빨간색","주황색","노란색","초록색","파란색","남색","보라색"};
for(int i = 0;i<color.length;i++) {
a.add(color[i]);
b.add(i);
}
a.add("빨간색");
boolean checkChange = a.add("무지개색");
System.out.println(checkChange);
System.out.println("b의 가장 큰값은 ?" + Collections.max(b));
System.out.println("b의 가장 최소값은 ?" + Collections.min(b));
Iterator<String> it = a.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
얘도 원리는 동일
hash함수를 통해
"빨간색" 이 들어왔을 때 그것과 맞는 버킷으로 안내.
얘는 Key만 있기 때문에 역시 이것은 중복이 되면 안 된다.
Set은 중복을 허용하지 않기 때문에
데이터의 중복 제거 할 때만 특히 사용.
순서 개념도 없다.
그래서 안에 있는 원소를 가져오려면 내부를 돌아다닐
Iterator가 필요.
(거의 모든 컬랙션이 이터레이터를 가지고 있다.)
이터레이터의 역할
1. 끝인지 아닌지
2. 지금 가리키고 있는 게 뭔지
를 판단.
따라서
hasNext()는 끝인지 아닌지를 판단하고,
끝이 아니라면
지금 가지고 있는 거 줘 next()
가 되는 것.
next() 하는 일도 두 가지
1. 지금 있는 것 가져오기
2. 뒤로 한 칸 이동시기
이렇게.
사실 더 간단한 방법은 Iterator를 굳이 쓰지 않고
향상된 for문(for-each문)을 사용해서 가져오면 됨.
public class Sample {
public static void main(String[] args) {
Set<String> setA = new HashSet<>();
setA.add("가");
setA.add("나");
setA.add("다");
setA.add("라");
setA.add("마");
for(String element : setA) {
System.out.plintln(element);
}
}
}
Set은 index가 없으니까 for문을 돌리기도 어렵다.
반복문은 위와 같은 형태만 일단 짜야한다.
내부적으로는 정렬을 하고 있음(아마 기본적으로 배열 형태여서 그런 듯.)
하지만 Set자체는 정렬을 보장하진 않는다(by.클린코드)
3.
public class QueueTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
LinkedList<String> q = new LinkedList<String>();
System.out.println("큐 offer : " + q.offer("한국"));
System.out.println("큐 offer : " + q.offer("중국"));
System.out.println("큐 offer : " + q.offer("미국"));
System.out.println("큐 offer : " + q.offer("러시아"));
System.out.println("큐 offer : " + q.offer("우크라이나"));
System.out.println("큐 offer : " + q.offer("북한"));
System.out.println("===============================");
int index = q.indexOf("우크라이나");
if(index != -1) {
System.out.println("큐에서 숫자 \"우크라이나\"의 위치는 : " + index + "번째 입니다.");
}
else {
System.out.println("큐에서 숫자 \"우크라이나\"가 없습니다.");
}
System.out.println("===============================");
while(!q.isEmpty()) {
String obj = q.poll();
System.out.println("poll : "+ obj);
}
}
}
String obj = q.poll();는
pop처럼 가지고 있는 것 그냥 달라는 명령어
리스트는 어디서 달라고 지정을 해야하는데
Stack이나 Queue는 넣는 위치와 빼는 위치가 정해져 있기 때문에 지정하지 않아도 된다.
큐는 인터페이스이기 때문에
Queue<String> q = new Queue<String>();
이게 안 된다.
무조건 구현체가 와야함.
~011430