자바의 기본 구성 단위 객체

이 때까지 Python과 Javascript와 같은 다른 프로그래밍 언어를 다루어오다가 처음으로 Java를 다루게 되었습니다.
가장 차이가 나는 것은 바로 객체에 대한 상당한 의존성이 높다는 점입니다.
사소한 함수 하나를 사용하고 싶어도, 객체를 생성한 다음에 메소드로 구현해야한다는 점은 상당히 번거로운 일이었습니다.
우테코 프리코스 때에는 이러한 자바의 특성에 익숙해졌지만, 어려움을 겪었던 점이 있었습니다.



"원시 값을 포장하여 사용하라"



이게 어려웠던 점은 우리가 "같다" 라는 것을 정의하는 방법에는 많은 방법이 있는데, 자바의 객체로 구성되는 특성상 원시 값으로 포장할 경우에 String을 사용할 때 같음을 비교했던 "equals" 나 정수 값을 비교할 때 사용했던 "==" 이 통하지 않을 것을 알고 있었기 때문입니다.

@Override, 상속 등에 대한 개념도 너무나도 어려웠고, 이것을 왜 쓰는지에 대해서 감이 긴가민가 하던 시절이라 프리코스를 진행하면서는 원시 값 포장의 필요성만 깨닫고 넘겨버렸습니다. 하지만 자바를 본격적으로 다루면서 많은 원시 값 포장을 하게 되었고, 각 객체들의 동일성을 확인할 필요성이 생겼습니다.

객체의 구성요소 중 필드

 

저희는 객체에서 상태를 가질 수 있는 필드라는 값이 있습니다. 필드는 객체당 여러 항목들을 가질 수가 있는데요.
사람을 예시로 한 번 들어보겠습니다.

 

public class People {
    private String lastName; // 성
    private String firstName; // 이름
    private int age; //나이
    
    public String getLastName() {
    	return this.lastName;
    }
    
    public String getFirstName() {
    	return this.firstName;
    }
    
    public int getAge() {
    	return this.age;
    }
}

 

사람은 이렇게 이름으로써 성과 이름을 가지게 됩니다. 

이 때, 우리는 getter를 사용하여 어찌어찌 성과 이름을 비교를 할 수 있게 됩니다. 

 

KSJM.getLastName.equals(JMKS.getLastName)
KSJM.getFirstName.equals(JMKS.getLastName)
KSJM.getAge == JMKS.getAge

 

이렇게 String일 때 사용한다면 각각의 필드 값들은 같다고 나오게 될 것입니다. 

 

하지만 이러한 방식에는 몇 가지 문제점들이 예상이 되었습니다.

 

1. 객체들간의 비교를 할 시에는 하나만 비교하는 것이 아닌 다양한 필드 값들을 동시에 한꺼번에 비교해야만 한다.

우리가 사람을 비교할 때에는 성과 이름을 동시에 비교를 해야합니다. (물론 동명이인일수도 있지만)

하나 하나씩 비교하는 것은 이것을 수행하는 함수를 따로 만들어야하고, 이는 복잡함으로 이어집니다.

 

2. 각각의 필드 타입들의 경우에는 다양한 비교 기준을 가지고 있다.

예시에서도 볼 수 있듯이, Int와 String의 경우에는 동일성을 비교할 때, 다른 방법 "equals" vs "=="를 사용하고 있습니다. 만약 필드 값을 다 가져와서 비교를 하게 되면, 다른 비교 기준을 모두 맞추어 주어야합니다.

 

이러한 문제점들을 해결해주기 위해 사용되는 것이 바로 equals와 hashCode입니다.

 

객체의 단순 비교를 도와주는 equals

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        People people = (People) o;
        return age == people.age && Objects.equals(lastName, people.lastName)
                && Objects.equals(firstName, people.firstName);
    }

 

위에서도 볼 수 있듯이, @Override라는 Annotation을 통해서 상위 객체에서 메서드를 상속받아 재구현을 해준 것을 알 수가 있습니다. 이 때, Object는 모든 객체들의 정점에 있는 객체로써, 저희가 구현하는 객체나 가져오는 객체들의 첫 시작이라고 보시면 됩니다. 즉, 저희가 구현한 People도 코드에서는 작성을 하지 않았지만, 자동으로 위에서 상속받아 사용하게 됩니다. Object라는 객체는 상속하되, equals라는 메소드는 우리가 정의한 객체에 맞게 재정의하여 사용하겠다. 자바 코드 내에서도 equals를 사용해달라! 라는 의미를 담고 있습니다.

 

이 때 자세히 코드를 보시면 저희 People 객체의 여러 필드들이 보이게 됩니다.

 

1. int로 이루어진 age의 경우에는 기존의 == 를 사용하였습니다.

2. String으로 이루어진 firstName, lastName의 경우에는 equals를 사용하였습니다.

 

이를 통해서 객체들의 필드의 특성들을 고려해줌과 동시에 한꺼번에 모든 필드 값들을 비교할 수가 있게 되었습니다.

하지만 그럼에도 불구하고, 불안한 요소들이 있는데요. 

객체들이 인스턴스화될 경우에는 각 메모리에 저장이 되는데, 이러한 주소에 대해서는 신경을 쓰지 못한다는 점입니다.

즉, 동명이인에다가 나이가 같은 사람이 있으면 무조건 같은 사람으로 인식할 위험이 있습니다. 

이 점은 여러 자료 구조에서는 치명적인 결과를 낼 수가 있는데요. (중복이 안된다던가, 정렬이 힘들다던가)

이를 해결하기 위해서 같이 사용하는 것이 hashCode입니다.

 

동일한 메모리 주소를 인지를 확인해주는 hashCode

 

    @Override
    public int hashCode() {
        return Objects.hash(lastName, firstName, age);
    }

 

 

hashCode의 경우에는 객체가 동일한 메모리를 참조하고 있는지에 대한 내용입니다.

만약 김, 철수, 30세 라는 사람이 전국에 여러명 있을 경우에는 다르게 인식을 해주어야하는데, 자바에서는 캐싱을 사용하고 있지 않는 이상 필드는 같아도 다른 메모리에 저장이 되게 됩니다.

이 때문에 equals로 겉보기 필드를 비교하면서도 진짜 고유값이 맞는지를 확인해주는 메소드가 hashCode입니다.

저희가 사실 구현에서는 직접 볼 일이 없지만, 동일성을 확인하다가 일어날 수 있는 부작용을 확인하기 위해서 사용됩니다.

자료 구조에서는 HashMap 등 Hash를 사용하여 정렬하고 저장하는 자료구조에서 key로서 사용하게 됩니다. 이 때, 각각의 equals에서 필드의 동일성을 확인했음에도 불구하고, hashCode를 정의해놓지 않으면 다른 객체로 인식하는 문제가 생깁니다. equals를 정의를 해주셨다면, hashCode도 같이 정의를 해주는 것이 좋습니다.

+ Recent posts