Java Object(최상위 클래스) 와 오버라이딩
자바스터디
studyJava 스터디 - week1
목차
- java란 뭘까 ?
- 오버라이딩
- hashCode
이번시간에는 자바라는 언어가 어떤 언어인지 간략하게 알아보며 오버라이딩, 해시코드가 언제 사용이 되는지 알아보겠습니다 !
Java란
간단하게 Java에 대해서 알아보겠습니다.
Java - 객체지향 언어 (프로그래밍 언어 + 객체지향 개념)
객체지향 프로그래밍 - 모든 데이터를 객체(object)로 취급, 이러한 객체가 프로그래밍의 중심
객체 - 실제로 존재하는 것, 사물 또는 개념
클래스 - 이 객체를 만들기 위한 설계도와 같은 개념이 클래스입니다.
이 객체들의 상태(state)와 행동(behavior)을 구체화하는 형태의 프로그래밍이 객체 지향 프로그래밍입니다.
java.lang 패키지는 자바에서 가장 기본적인 동작을 수행하는 클래스들의 집합입니다.
자바에서는 java.lang 패키지의 클래스들은 import 문을 사용하지 않아도 클래스 이름만으로 바로 사용할 수 있도록 하고 있습니다.
java.lang 패키지 중에서도 가장 많이 사용되는 클래스는 바로 Object 클래스입니다.
Object 클래스는 모든 자바 클래스의 최고 조상 클래스가 됩니다.
따라서 자바의 모든 클래스는 Object 클래스의 모든 메소드를 바로 사용할 수 있습니다.
이러한 Object 클래스는 필드를 가지지 않으며, 총 11개의 메소드만으로 구성되어 있습니다.
Object 클래스의 메소드는 아래와 같습니다.
이번시간에는 이중 3가지 메소드(equals, toString, hashCode)를 다뤄보겠습니다.
equals - 객체의 값을 비교 비교 (하도록 오버라이딩)
toString - 객체가 가진 값을 문자열로 반환
hashCode - 객체의 해시코드 값 반환
위 메서드를 목적에 맞게 오버라이딩 합니다.
오버라이딩이란?
부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 목적에 맞게 사용하기 위해 다시 재정의 하는 것, 부모클래스의 메소드를 자식클래스가 동일한 형태(입출력이 동일)로 또다시 구현하는 행위를 메소드 오버라이딩(Method Overriding)이라고 한다. (※ 메소드 덮어쓰기)
equals 의 경우 어떤 속성을 통해 hashCode를 얻을 것인지를 정할 수 있습니다.
예를 들어 학생 객체가 있다고 할때 학번만 같으면 같은 객체라고 설정할 수 있습니다.
equals는 동일한 객체일 경우에만 true가 나오도록 해야합니다.
Object 클래스의 equals() 함수는 객체의 주소를 비교합니다.
같은 값을 가지더라도 따로 생성되었다면 결과는 False입니다.
String 클래스의 equals() 함수는 String 객체에서 equals는 주소가 다르더라도 문자열이 같으면 true를 리턴합니다.
그 이유는 equals를 String이 같으면 true가 되도록 overriding 하였기 때문입니다.
비교대상이 String 객체라면 문자열을 비교하여 같으면 true 반환합니다.
package programmers;
public class Equals {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
System.out.println(str2.equals(str3));
}
}
Integer 클래스의 equals 함수도 비교대상을 기준으로 주소가 다르더라도 동일한 값을 가진다면 true를 반환합니다.
hashCode 란?
객체를 식별하는 하나의 값
Object의 hashCode() 메서드는 객체 메모리 번지를 이용하여 해시코드를 만들기 때문에 객체 고유 값으로 객체마다 다른 값을 리턴합니다.
객체 1 과 객체 2의 equals 결과가 true라면 hashCode값은 반드시 같아야 하지만, hashCode값이 같다고 하여 반드시 equals 값이 true일 필요는 없음
여기서 추가적으로 equals와 hashCode를 함께 재정의(오버라이딩) 해야하는 이유가 어떤건지 아래 예시코드를 통해 알아보겠습니다.
package programmers.intermediate;
import java.util.*;
public class Car {
private final String name;
public Car(String name) {
this.name = name;
}
// intellij Generate 기능 사용
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Car)) return false;
Car car = (Car) o;
return Objects.equals(name, car.name);
}
public static void main(String[] args){
Car car1 = new Car("foo");
Car car2 = new Car("foo");
// true 출력
System.out.println(car1.equals(car2));
// List<Car> cars = new ArrayList<>();
// cars.add(new Car("foo"));
// cars.add(new Car("foo"));
//
// System.out.println(cars.size());
Set<Car> cars = new HashSet<>();
cars.add(new Car("foo"));
cars.add(new Car("foo"));
System.out.println(cars.size());
}
}
equals만 재정의 할 경우 Car객체의 name이 같으니 논리적으로 같은 객체로 판단됩니다.
List 의 경우에도 객체를 List에 넣어주었으니 출력결과는 2입니다.
이번엔 Collection에 중복되지 않는 Car객체만 넣어야 한다는 조건이 있을때 Set으로 로직을 바꾸어 출력을 해보면 어떤 결과가 나올까요 ?
Car 객체의 이름이 같으므로 논리적으로 같아서 HashSet의 size가 1이 나올거라 생각했지만 2가 출력됩니다. 그 이유는 hashCode를 같이 재정의하지 않아서 입니다. 정확히 말하면 hash값을 사용하는 (HashSet, HashMap, HashTable)을 사용할 때 문제가 됩니다.
그리고 String 객체의 경우에는 불변(immutable) 이라 말합니다. 즉 변하지 않는것을 의미합니다.
Java의 String 객체는 String constant pool에서 따로 관리가 됩니다. 이는 스택 메모리에 직접 저장되는 것이 아닌 Heap영역 중 String constant pool의 메모리를 할당받으며 String은 그 주소값을 참조하게 됩니다.
따라서 아래와 같은 현상이 발생합니다.
String str1 = "hello";
String str2 = "hello";
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
//99162322
//99162322
분명 객체를 각각 생성하였는데 주소값은 같다고 나옵니다. 이 이유는 String이 생성될때 동일한 String 값일 경우 같은 String constant pool의 메모리를 할당받기 때문입니다.
객체가 논리적으로 같은지 비교할 때 아래 그림의 과정을 거치게 됩니다.
Object 클래스의 hashCode 메서드(오버라이딩하기 전)는 객체의 고유한 주소 값을 정수로 변환하기 때문에 객체마다 다른 값을 리턴한다. 두 개의 Car 객체는 equals로 비교도 하기 전에 서로 다른 hashCode 메서드의 리턴 값으로 인해 다른 객체로 판단된 것입니다.
Q : 동일한 hashCode를 가진다면 동일한 객체라고 할 수 있을까요 ?
A : 아닙니다. hashCode가 동일하더라도 그 객체는 다를 수 있음, equals()의 리턴값이 다를 경우
그럼 왜 hashCode를 사용하나
- 객체를 비교할 때 드는 비용을 낮추기 위해서, equals()를 사용하면 integer 를 비교하는 것에 비해 많은 시간이 소요되므로
- 객체 비교시 hashCode가 다르면 두 객체는 같을 수 없으므로 먼저 hashCode를 비교하여 1차적으로 필터한 후 equals로 비교함
자바에서 제시하는 hashCode규약
equals(Object)메소드가 true일때 두 객체의 hashCode 값은 같아야 한다. equals(Object)메소드가 false일때 두 객체의 hashCode가 꼭 다를 필요는 없다. 하지만 서로 다른 hashCode 값이 나오면 해시 테이블(hash table)의 성능이 향상될 수 있다는 점은 이해하고 있어야 한다.
아래 예제소스코드를 통해 알아보겠습니다.
package programmers.intermediate;
import java.util.Objects;
public class Student {
String name;
String number;
int birthYear;
@Override
public boolean equals(Object o) {
if (this == o) return true; // 같은 객체참조값을 가지고 있는지 체크 만약 같다면 true 반환
if (!(o instanceof Student)) return false; // 같은 클래스인지를 상속을 통해 확인, 같은 클래스가 아니라면 비교할 필요가 없으므로
Student student = (Student) o; // 객체 형변환(다형성)
return number.equals(student.number); // Student형 변환을 하여 student 객체의 number값이 같다면 true 반환
}
@Override
public int hashCode() { // hashCode가 구현되는 것은 일종의 수학식, 31과 특정수를 곱함 ...
return Objects.hash(number); // number를 기준으로 number가 같으면 동일한 hashCode를 가지도록 hashCode를 구현하였음
}
@Override
public String toString() { // toString 을 사용하는 경우는 속성값을 원할때가 많으므로 속성값을 반환하도록 오버라이딩 한다
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
", birthYear=" + birthYear +
'}';
}
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "river";
s1.number = "9297";
s1.birthYear = 1234;
Student s2 = new Student();
s2.name = "river";
s2.number = "9297";
s2.birthYear = 1234;
if(s1.equals(s2)) {
System.out.println("s1==s2");
} else {System.out.println("s1 != s2");}
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s1.toString());
System.out.println(s1);
System.out.println(s2);
}
}
참고링크 - https://tecoble.techcourse.co.kr/post/2020-07-29-equals-and-hashCode