동등성(Identity)은 두 객체의 값이 같음을 의미하고,
동일성(Equality)은 두 객체의 메모리 주소가 같음을 의미한다.
동등성의 경우 객체의 주소값이 다르더라도 값, 생긴 것이 똑같으면 두 변수는 동등하고,
동일성의 경우 객체의 값, 생긴 것이 똑같더라도, 객체의 주소값이 다르면 두 변수는 동일하지 않다.
자바에서 동등성은
equals()
, 동일성은==
으로 확인이 가능하다.
자바 코드로 확인해보자!
void test() {
List<String> shape1 = List.of("square", "circle", "triangle");
List<String> shape2 = List.of("square", "circle", "triangle");
List<String> shape3 = shape1;
System.out.println(shape1 == shape3); // true
System.out.println(shape1 == shape2); // false
System.out.println(shape1.equals(shape2)); // true
System.out.println(shape1.equals(shape3)); // true
}
shape1 == shape3
shape3는 shape1을 직접 참조하고 있기 때문에 shape1과 shape3는 같은 객체를 가리킵니다.
-> True; 동일성 O
shape1 == shape2
shape1과 shape2는 각각 List.of로 생성한 리스트이므로, 같은 값을 가지더라도 서로 다른 객체입니다. 그래서 == 비교에서는 false가 출력됩니다.
-> False; 동일성 X
shape1.equals(shape2)
shape1과 shape2의 내용이 동일하기 때문에 equals 메서드는 두 리스트의 요소들을 비교하여 같음을 판단합니다. 따라서 true가 출력됩니다.
-> True; 동등성 O
shape1.equals(shape3)
shape3는 shape1을 참조하고 있으므로 내용이 완전히 같습니다. equals 메서드가 두 리스트의 요소들을 비교하여 같음을 판단하므로 true가 출력됩니다.
-> True; 동등성 O
String name1 = "hyunoi";
String name2 = "hyunoi";
System.out.println(name1 == name2);
위와 같이 코드를 작성하면 출력은?
True가 나온다!
==
가 True이니, 동일성이 성립한다는 뜻이다.
String의 경우 String pool에서 관리하기 때문에 new
를 이용해서 새로운 객체를 생성해주지 않으면 동일한 주소값을 가지게 된다.
name1과 name2가 같은 메모리 주소를 바라보게 되는 것
String name1 = "hyunoi";
String name2 = new String("hyunoi");
System.out.println(name1 == name2);
이렇게 하면 출력이 False가 나온다.
new로 새로운 객체를 만들어 주었기 때문에!
동등성 검사를 위해서 equals()
를 사용하는데,
사실 재정의를 해준 후의 equals()
가 우리가 아는 동등성 검사를 위해 쓰인다.
원래의 equals()
은 ==
와 동일한 의미로 사용된다.
내용을 비교하고, 주소 값까지 비교하여 동일성을 판단하는 것이다.
그래서 동등성 검사를 위해서는 equals()
의 오버라이딩이 필수이다.
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Alice", 25);
System.out.println(person1 == person2); // false
System.out.println(person1.equals(person2)); // false
}
}
위 코드는 오버라이딩하지 않은 경우로,
person1과 person2를 다른 객체로 보아 동등성과 동일성 둘 다 False를 출력한다.
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 같은 객체일 경우 true
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
// name과 age가 모두 같으면 true
return name.equals(person.name) && age == person.age;
}
}
public class Main {
public static void main(String[] args) {
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Alice", 25);
System.out.println(person1 == person2); // false
System.out.println(person1.equals(person2)); // true
}
}
이 코드는 오버라이딩을 한 코드이다.
equals()
가 ==
과 다르게 동등성의 기능을 하는 것을 볼 수 있다!
hashCode()
는 객체의 해시 코드를 반환하는 메소드이다.
해시 코드는 객체를 구분하기 위해 사용되는 값으로, 객체의 내용이 바뀌지 않았다면 일관된 값을 반환해야 한다.
위에서 equals()
를 오버라이딩한 것과 같이 hashCode()도 오버라이딩해서 동등한 객체의 경우 동일한 해시 코드를 가지게 해야 한다.
이렇게 해야 HashMap, HashSet, HashTable과 같은 컬렉션에서 객체를 올바르게 구분할 수 있다!
public class Main {
public static void main(String[] args) {
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Alice", 25);
Set<Person> people = new HashSet<>();
people.add(person1);
people.add(person2);
System.out.println(people.size()); // 2
}
}
hashCode()
를 오버라이딩하지 않은 코드이다.
HashSet은 person1과 person2를 각각의 해시 코드를 가진 객체로 판단하여 사이즈는 2가 된다.
{
...
@Override
public int hashCode() {
return Objects.hash(name, age);
}
'''
}
public class Main {
public static void main(String[] args) {
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Alice", 25);
Set<Person> people = new HashSet<>();
people.add(person1);
people.add(person2);
System.out.println(people.size()); // 1
}
}
hashCode()
를 오버라이딩한 코드이다.
name과 age가 같으면 같은 객체로 판단하여 동일한 해시 코드를 반환하도록 하였다.
그래서 HashSet은 person1과 person2를 같은 해시 코드를 가진 하나의 객체로 판단하여 사이즈는 1이 된다.
equals()
오버라이딩을 할 땐 혹시 모르니 hashCode()
까지 오버라이딩하자~