자바를 사용하면서 List나 Map, Set을 많이 사용했는데 지금까지 아무 생각 없이 그냥 사용해 왔던 것 같다.
그래서 어떤 기능을 가지고, 어떤 특징을 가졌는지 정리해보고자 한다.
Collections 프레임워크는 자료구조와 관련된 인터페이스, 클래스를 지칭하는 용어다.
Collections에는 대표적으로 List, Set이 있다. (Map은 Collections에 포함되지 않음)
List의 특징
1. 순서를 가진다.
2. 크기가 정해져 있지 않다.(동적)
3. 중복된 값을 넣을 수 있다.
Set의 특징
1. 순서를 가지지 않는다.
2. 크기가 정해져있지 않다.(동적)
3. 중복된 값을 넣을 수 없다.
아래는 List에서 사용 가능한 인터페이스 메서드이다.
ArrayList
제일 많이 사용하는 List라고 생각한다.
알고리즘을 풀 때 리스트를 만들면 아무 생각 없이 ArrayList를 만들곤 했다.
ArrayList는 값 자체를 저장하는 것이 아니라, 값을 가리키는 주소값을 저장한다. 그래서 기본 타입으로는 사용이 불가능하다.
ArrayList에서 값을 add()를 통해 추가하면 0번 인덱스부터 하나하나 쌓이는데, remove()로 제거하게 되면 index가 하나씩 당겨지게 된다.
그렇기 때문에 자주 생성과 삭제가 이루어지는 구조에서는 ArrayList는 느릴 수밖에 없다.
(중간에 삭제하면 모든 index를 앞당기기 때문에)
예시로 추가와 삭제를 진행해 보자.
public static void main(String[] args) throws IOException {
// add(추가 메서드)
List<String> str = new ArrayList<>();
str.add("사슴");
str.add("토끼");
str.add("해마");
System.out.println(str);
// remove(삭제 메서드)
str.remove("토끼");
str.remove(1);
System.out.println(str);
str.add("사자");
System.out.println(str);
// size() 크기 조회
System.out.println(str.size());
// get() 메서드를 통해 해당 인덱스 값 조회 가능
System.out.println(str.get(1));
}
이렇게 add와 remove를 이용했다.
List 인터페이스에 remove 추상 메서드가 2개가 있다.
맨 위에는 Object 타입을 파라미터로 받는다. 즉 객체를 넣어서 일치하는 값을 삭제한다.
맨 아래는 int 타입으로 값을 받아서 해당 인덱스에 있는 값을 삭제한다.
LinkedList
ArraysList와 사용 방법은 동일하지만 내부 구조가 완전 다르다.
ArraysList는 내부 배열에 객체를 저장하지만, LinkedList는 인접 객체를 체인처럼 연결해서 관리한다.
LinkedList는 객체를 삽입하거나 삭제하면 바로 앞 뒤 링크만 변경되기 때문에 빈번하게 삽입, 삭제가 이루어지는 곳에서는
ArrayList보다 좋은 성능을 발휘한다.
그래서 지금부터 ArrayList와 LinkedList의 생성과 삭제를 진행했을 때 시간 차이를 측정해보려고 한다.
총 20000번의 생성을 진행했다.
public static void main(String[] args) throws IOException {
List<Integer> arraylist = new ArrayList<>();
List<Integer> linkedlist = new LinkedList<>();
// 시간 측정 시작
long start = System.nanoTime();
for(int i=0; i<20000; i++){
arraylist.add(0, i);
}
long time = System.nanoTime() - start ;
System.out.println("ArrayList 걸린 시간 : " + time + "ns");
// 시간 측정 시작
start = System.nanoTime();
for(int i=0; i<20000; i++){
linkedlist.add(0, i);
}
time = System.nanoTime() - start ;
System.out.println("LinkedList 걸린 시간 : " + time + "ns");
}
단순하게 add(i)를 통해서 진행하면 뒤에서만 삽입하기 때문에 시간 차이가 발생하지 않는다.
그래서 0번째 인덱스에 고정으로 값을 추가하는 방법으로 진행했다.
ArrayList는 LinkedList에 비해 10배가 걸리는 것을 확인할 수 있다.
Set
반면에 Set은 List와 다르게 순서가 없고 중복이 허용되지 않는다는 특징이 있다.
Set 인터페이스에서 사용 가능한 추상 메서드이다.
HashSet
Set 컬렉션에서 가장 많이 사용하는 게 HashSet이다.
HashSet은 동일한 객체는 중복 저장하지 않는다. (여기서 동일한 객체는 동등 객체를 의미)
HashSet은 다른 객체라도 hashCode() 메서드의 리턴값이 같고, equals() 메서드가 true를 리턴하면 동일한 객체라 판단해서 중복 저장하지 않는다.
만약에 동일한 값을 HashSet에 저장한다면 동등한 객체로 간주한다.
(hashCode()의 리턴값이 같고, equals()의 리턴 값이 true가 나오기 때문)
public static void main(String[] args) throws IOException {
HashSet<String> temp = new HashSet<>();
temp.add("A");
temp.add("B");
temp.add("C");
temp.add("D");
temp.add("A");
System.out.println(temp.size());
}
그래서 해당 코드를 실행하면 결과 값은 4가 나온다.
이번엔 클래스를 생성해서 객체로 HashSet에 저장해 보자.
public class Member {
private String name;
private int age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode(){
return this.name.hashCode() + this.age;
}
@Override
public boolean equals(Object obj){
if(obj instanceof Member target){
return target.name.equals(name) && (target.age == age);
}else{
return false;
}
}
}
우선 이름과 나이를 가지는 Member 클래스를 만들고 hashCode()와 equals()를 재정의 했다.
왜 equals()를 재정의 하면 hashCode()를 재정의 해야 할까??
그 이유는 동일한 객체에 대해서는 동일한 해시코드를 가져야 하기 때문이다.
equals가 true가 나오는데 다른 hashCode를 가지게 된다면 해시를 기반으로 하는 컬렉션은 두 개를 다른 객체로 판단하게 된다.
그래서 중복 저장이 불가능하기에 hashCode() 메서드도 재정의를 진행.
만약에 hashCode() 메서드를 재정의 하지 않는다면 결과는 어떨까?
public static void main(String[] args) throws IOException {
HashSet<Member> temp = new HashSet<>();
temp.add(new Member("박기현", 25));
temp.add(new Member("박기현", 25));
temp.add(new Member("박기현", 27));
for(Member m : temp){
System.out.println(m.hashCode());
}
}
해당 코드의 결과는
이렇게 나온다.
즉 첫 번째와 두 번째 객체가 서로 다른 해시값을 가지게 된다. 분명 객체의 값은 같은데 다르다고 판단돼서 중복 저장이 발생하게 됐다.
그래서 우리는 hashCode() 메서드를 재정의 함으로써 동일한 객체라는 것을 판단해야 한다.(그래야 중복 저장을 막는다.)
hashCode() 메서드를 재정의 함으로써 2개의 객체만 저장된 것을 확인할 수 있다.
'JAVA' 카테고리의 다른 글
자바 실행 과정 및 JVM 개념 정리 (1) | 2024.03.01 |
---|---|
equals()와 hashcode() 정리(재정의) (2) | 2024.02.24 |
Class에 @Builder 붙이면 안되는 이유가 무엇일까? (0) | 2024.02.07 |
자바 Record (자바 16에서 채택된 새로운 클래스) (0) | 2024.01.10 |