Verity's Daily Logs_

[JAVA]Object (equals, hashCode ...) 본문

JAVA

[JAVA]Object (equals, hashCode ...)

johye0 2022. 2. 8. 22:30
반응형

Class Object


"Class Object is the root of the class hierarchy. Every class has Object as a superclass. All objects, including arrays, implement the methods of this class."

java.lang 패키지 중에서도 가장 많이 사용되는 클래스로, 모든 자바 클래스의 최고 조상 클래스다.

따라서 자바의 모든 클래스는 Object 클래스의 모든 메서드를 바로 사용할 수 있는데, Object는 총 11개의 메서드로 구성되어 있다.

 

💡 java.lang 패키지

  • java.lang 패키지는 자바에서 가장 기본적인 동작을 수행하는 클래스들의 집합이다.
  • java.lang 패키지의 클래스들은 import문을 사용하지 않아도 클래스 이름만으로 바로 사용할 수 있도록 하고 있다.
  • java.lang 상속 클래스: String, StringBuffer, Boolean, Character, Number(Byte, Short, Integer, Long, Float, Double)
  • 참고: https://docs.oracle.com/javase/7/docs/api/java/lang/package-tree.html

 

Object Methods


1. toString()

  • toString() 메소드는 해당 인스턴스에 대한 정보를 문자열로 반환하는 메서드이다. 이때 반환되는 문자열은 클래스 이름과 함께 구분자로 '@'가 사용되며, 그 뒤로 16진수 해시 코드(hash code)가 추가된다.
  • 16진수 해시 코드 값은 인스턴스의 주소를 가리키는 값으로, 인스턴스마다 모두 다르게 반환된다.
  • 자바에서 toString() 메소드는 기본적으로 각 API 클래스마다 자체적으로 오버 라이딩을 통해 재정의되어 있다. 마찬가지로, 개발자는 자신이 만든 클래스에 대하여 toString 메서드를 재정의하여 사용할 수 있다.
    public class ObjectTests {
    
        public static void main(String[] args) {
            ObjectTests objectTests = new ObjectTests();
    
            objectTests.toStringRun();
            objectTests.toStringOverriding();
        }
    
        private void toStringRun() {
            /**
             * 출력:::
             * myName = Hailey
             * myName = Hailey
             */
            String myName = "Hailey";
            System.out.println("myName = " + myName);
            // 컴파일 경고 발생
            // This object (which is already a string!) is itself returned.
            System.out.println("myName = " + myName.toString());
    
    
    
            /**
             * 출력:::
             * numberArrays = [I@71f4dd
             * numberArrays = [1, 2, 3, 4, 5]
             */
            int[] numberArrays = new int[]{1,2,3,4,5};
            System.out.println("numberArrays = " + numberArrays);
            System.out.println("numberArrays = " + Arrays.toString(numberArrays));
        }
    
    
    
        private void toStringOverriding() {
            // OnlineClass 객체 생성
            OnlineClass class1 = new OnlineClass(1, "spring boot", true);
            OnlineClass class2 = new OnlineClass(2, "spring data jpa", true);
    
            // Class toString 메소드 호출
            // .toString() 하지 않아도 println 메소드가 String 으로 변환하여 출력하기 때문에 결과는 동일하다.
            System.out.println("class1 = " + class1.toString());
            System.out.println("class1 = " + class1);
            System.out.println("class2 = " + class2);
    
            /**
             * 출력:::
             * class1 = com.study.java8.object.ObjectTests$OnlineClass@df9f5f
             * class1 = com.study.java8.object.ObjectTests$OnlineClass@df9f5f
             * class2 = com.study.java8.object.ObjectTests$OnlineClass@5ede7b
             */
    
        }
    
        @Getter @Setter
        @AllArgsConstructor
        static class OnlineClass {
    
            private int id;
            private String title;
            private boolean closed;
    
        }
    }

💡 JAVA API Class

  • API(Application programming Interface), 개발에 자주 사용되는 클래스나 인터페이스의 모음으로, System,String 클래스 등도 모두 API에 속한다. 설치경로 : "jdk설치 폴더/jre/lib/rt.jar" 폴더 안에 class 형태로 저장돼있음을 확인 가능하다.
  • Object, String, StringBuffer, Math, Wrapper, Enum, Arrays, Calendar

 

💡  toString() 오버라이딩

  • @Override 어노테이션으로 Object 클래스의 toString 메서드를 재정의 할 수 있다. Lombok의 @ToString 어노테이션을 통하여 재정의를 편하게 할 수도 있다.
    public class ObjectTests {
    
        public static void main(String[] args) {
            ObjectTests objectTests = new ObjectTests();
    
            objectTests.toStringOverriding();
    
        }
    
        private void toStringOverriding() {
            // OnlineClass 객체 생성
            OnlineClass class1 = new OnlineClass(1, "spring boot", true);
            OnlineClass class2 = new OnlineClass(2, "spring data jpa", true);
    
            // Class toString 메소드 호출
            System.out.println("class1 = " + class1.toString());
            System.out.println("class1 = " + class1);
            System.out.println("class2 = " + class2);
    
            /**
             * 출력1 ::: 기본 toString()
             * class1 = com.study.java8.object.ObjectTests$OnlineClass@df9f5f
             * class1 = com.study.java8.object.ObjectTests$OnlineClass@df9f5f
             * class2 = com.study.java8.object.ObjectTests$OnlineClass@5ede7b
             */
    
            /**
             * 출력2 ::: 사용자 재정의 toString()
             * class1 = com.study.java8.object.ObjectTests$OnlineClass@71f4dd@ id: 1, title: spring boot
             * class1 = com.study.java8.object.ObjectTests$OnlineClass@71f4dd@ id: 1, title: spring boot
             * class2 = com.study.java8.object.ObjectTests$OnlineClass@df9f5f@ id: 2, title: spring data jpa
             */
    
            /**
             * 출력3 ::: Lombok 제공 toString()
             * class1 = ObjectTests.OnlineClass(id=1, title=spring boot, closed=true)
             * class1 = ObjectTests.OnlineClass(id=1, title=spring boot, closed=true)
             * class2 = ObjectTests.OnlineClass(id=2, title=spring data jpa, closed=true)
             */
        }
    
        @Getter @Setter
        @AllArgsConstructor
        @ToString
        static class OnlineClass {
    
            private int id;
            private String title;
            private boolean closed;
    
            /*
            @Override
            public String toString() {
                return super.toString() + "@ id: " + id + ", title: " + title;
            }
            */
        }
    }

 

2. equals()

  • equals() 메서드는 해당 인스턴스를 매개변수로 전달받는 참조 변수와 비교하여, 그 결과를 반환하는 메서드이다. 이때 참조 변수가 가리키는 값을 비교하므로, 서로 다른 두 객체는 언제나 false를 반환한다.
  • equals() 메서드도 마찬가지로 기본적으로 각 API 클래스마다 자체적으로 오버 라이딩을 통해 재정의되어 있다. equals()는 사용자가 어떻게 재정의하느냐에 따라서 원하는 기준으로 비교가 가능하다.

💡 equals()와 항등 연산자(==)의 차이점

   

==

  • 참조 비교(Reference Comparison)로, 두 객체가 같은 메모리 공간을 가리키는지 확인한다. = 객체의 동일성
  • 모든 기본 유형(Primitive Types)에 대해서는 값을 비교하는 것으로 보이는데, primitive 역시 Constant Pool에 있는 특정 상수를 참조하는 것이기 때문에 결국 주소 값을 비교하는 것으로 볼 수 있다. 

equals()

  • 객체 내용 비교(Content Comparison) 메서드로, 두 객체의 값이 같은지 확인한다. 즉, 문자열의 데이터/내용을 기반으로 비교한다. = 논리적 동일성 Primitive Types에는 적용이 불가능하다.
public class ObjectTests {

    public static void main(String[] args) {
        ObjectTests objectTests = new ObjectTests();

        objectTests.equalsRun();
    }

    private void equalsRun() {
        Member member1 = new Member("Hailey");
        Member member2 = new Member("Hailey");

        System.out.println("member1 = " + member1);
        System.out.println("member2 = " + member2);
        System.out.println(member1 == member2);
        System.out.println(member1.equals(member2));

        /**
         * 출력1 ::: 기본 equals()
         * member1 = com.study.java8.object.ObjectTests$Member@71f4dd@name: Hailey
         * member2 = com.study.java8.object.ObjectTests$Member@df9f5f@name: Hailey
         * false
         * false
         */

        /**
         * 출력2 ::: 사용자 재정의 equals()
         * member1 = com.study.java8.object.ObjectTests$Member@71f4dd@name: Hailey
         * member2 = com.study.java8.object.ObjectTests$Member@df9f5f@name: Hailey
         * false
         * true
         */

    }

    @Getter @Setter
    @AllArgsConstructor
    static class Member {
        private String name;

        @Override
        public boolean equals(Object obj) {
            // 1. equals 의 인자가 자기 자신인지 확인
            if (this == obj)
                return true;

            // 2. equals 의 인자가 null 인지 확인
            if (obj == null)
                return false;

            // 3. instanceof 연산자로 인자의 자료형이 정확한지 확인
            if (!(obj instanceof Member))
                return false;

            Member m = (Member) obj;
            // 4. 값이 null 일 경우도 고려하여 리턴
            return (name == null ? m.getName() == null : name.equals(m.getName()));
        }

        @Override
        public String toString() {
            return super.toString() + "@name: " + name;
        }
    }
}

 

3. hashCode()

hashCode()는 객체의 hashCode를 리턴하는데, hashCode는 일반적으로 각 객체의 주소 값을 변환하여 생성한 객체의 고유한 정수 값이다. 따라서 두 객체가 동일 객체인지 비교할 때 사용할 수 있다.

public class ObjectTests {

    public static void main(String[] args) {
        ObjectTests objectTests = new ObjectTests();

        objectTests.hashCodeRun();
    }

    private void hashCodeRun() {
        Member member1 = new Member("Hailey");
        Member member2 = new Member("Hailey");

        System.out.println("member1 = " + member1.toString());
        System.out.println("member2 = " + member2);
        System.out.println("member1 = " + member1.hashCode());
        System.out.println("member2 = " + member2.hashCode());
        System.out.println("member1 = " + Integer.toHexString(member1.hashCode()));
        System.out.println("member2 = " + Integer.toHexString(member2.hashCode()));


        /**
         * 출력1 ::: 기본 hashCode 메서드
         * member1 = com.study.java8.object.ObjectTests$Member@71f4dd@name: Hailey
         * member2 = com.study.java8.object.ObjectTests$Member@df9f5f@name: Hailey
         * member1 = 7468253
         * member2 = 14655327
         * member1 = 71f4dd
         * member2 = df9f5f
         */

        /**
         * 출력2 ::: 사용자 재정의 hashCode
         * member1 = com.study.java8.object.ObjectTests$Member@1ab384@name: Hailey
         * member2 = com.study.java8.object.ObjectTests$Member@1ab384@name: Hailey
         * member1 = 1749892
         * member2 = 1749892
         * member1 = 1ab384
         * member2 = 1ab384
         */

        /**
         * 출력3 ::: Lombok 제공 @EqualsAndHashCode 어노테이션
         * member1 = com.study.java8.object.ObjectTests$Member@1ab3a0@name: Hailey
         * member2 = com.study.java8.object.ObjectTests$Member@1ab3a0@name: Hailey
         * member1 = 1749920
         * member2 = 1749920
         * member1 = 1ab3a0
         * member2 = 1ab3a0
         */
    }

    @Getter @Setter
    @AllArgsConstructor
    @EqualsAndHashCode
    static class Member {
        private String name;

        /*
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + (name==null ? 0 : name.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            // 1. equals 의 인자가 자기 자신인지 확인
            if (this == obj)
                return true;

            // 2. equals 의 인자가 null 인지 확인
            if (obj == null)
                return false;

            // 3. instanceof 연산자로 인자의 자료형이 정확한지 확인
            if (!(obj instanceof Member))
                return false;

            Member m = (Member) obj;
            // 4. 값이 null 일 경우도 고려하여 리턴
            return (name == null ? m.getName() == null : name.equals(m.getName()));
        }
        */
        @Override
        public String toString() {
            return super.toString() + "@name: " + name;
        }
    }

 

💡 Hash관련 Collections

  • Hash관련 Collection에는 데이터를 넣을 때마다 hashCode()를 호출해 중복되는 키 값이 있는지 확인한다.
  • 만약 String처럼 hashCode값이 유일한 값이 아니라 중복된 값이라면 오류가 나지 않을까 싶지만 hashMap은 서로 다른 객체의 같은 hashCode에 대해서도 잘 처리되도록 구성되어 있다. equals()의 결과가 false이고 사용자 입장에서는 직관적으로 서로 다른 문자열인 것을 인지할 수 있기 때문이다.
  • public class ObjectTests {
    
        public static void main(String[] args) {
            ObjectTests objectTests = new ObjectTests();
    
            objectTests.hashCodeForCollectionMap();
        }
    
        private void hashCodeForCollectionMap() {
            String s1 = "Z@S.ME";
            String s1_2 = "Z@S.ME";
            String s2 = "Z@RN.E";
            System.out.println("s1 = " + s1.hashCode());
            System.out.println("s2 = " + s2.hashCode());
            System.out.println(s1.equals(s1_2));
            System.out.println(s1.equals(s2));
    
            HashMap<String, Integer> hashMap = new HashMap<>();
            hashMap.put(s1, 30);
            hashMap.put(s1_2, 50);
            hashMap.put(s2, 40);
            System.out.println("hashMap.size() = " + hashMap.size());
            for (String s : hashMap.keySet()) {
                System.out.println(s + " = " + hashMap.get(s));
            }
    
            /**
             * 출력 :::
             * s1 = -1656719047
             * s2 = -1656719047
             * true
             * false
             * hashMap.size() = 2
             * Z@S.ME = 50
             * Z@RN.E = 40
             */
    
    
        }
    
    }
  •  
  • 위 예제와 반대의 경우, equals()의 결과도 true이고 사용자가 봤을 때도 같은 형태를 하고 있지만 hashCode 값만 false인 다른 객체라면 사용자의 혼란을 가져올 수 있다. 따라서 equals()가 true 이면 hashCode()도 true가 될 수 있도록 재정의 해야 한다.
public class ObjectTests {

    public static void main(String[] args) {
        ObjectTests objectTests = new ObjectTests();

        objectTests.hashCodeForCollectionSet();
    }

    private void hashCodeForCollectionSet() {
        HashSet<PlainMember> plainMemberHashSet = new HashSet<>();
        PlainMember plainMember1 = new PlainMember("Hailey");
        PlainMember plainMember2 = new PlainMember("Hailey");
        PlainMember plainMember3 = new PlainMember("Jayce");
        System.out.println("plainMember1 = " + plainMember1.hashCode());
        System.out.println("plainMember2 = " + plainMember2.hashCode());
        System.out.println("plainMember3 = " + plainMember3.hashCode());
        System.out.println(plainMember1.equals(plainMember2));

        plainMemberHashSet.add(plainMember1);
        plainMemberHashSet.add(plainMember2);
        plainMemberHashSet.add(plainMember3);
        plainMemberHashSet.stream().forEach(System.out::println);

        /**
         * plainMember1 = 7468253
         * plainMember2 = 14655327
         * plainMember3 = 6217339
         * false
         * com.study.java8.object.ObjectTests$PlainMember@df9f5f@name: Hailey
         * com.study.java8.object.ObjectTests$PlainMember@5ede7b@name: Jayce
         * com.study.java8.object.ObjectTests$PlainMember@71f4dd@name: Hailey
         */

        HashSet<Member> memberHashSet = new HashSet<>();
        Member member1 = new Member("Hailey");
        Member member2 = new Member("Hailey");
        Member member3 = new Member("Jayce");
        System.out.println("member1 = " + member1.hashCode());
        System.out.println("member2 = " + member2.hashCode());
        System.out.println("member3 = " + member3.hashCode());
        System.out.println(member1.equals(member2));

        memberHashSet.add(member1);
        memberHashSet.add(member2);
        memberHashSet.add(member3);
        memberHashSet.stream().forEach(System.out::println);

        /**
         * member1 = 1749920
         * member2 = 1749920
         * member3 = 1583563
         * true
         * com.study.java8.object.ObjectTests$Member@1829cb@name: Jayce
         * com.study.java8.object.ObjectTests$Member@1ab3a0@name: Hailey
         */

    }

    @Getter @Setter
    @AllArgsConstructor
    static class PlainMember {
        private String name;

        @Override
        public String toString() {
            return super.toString() + "@name: " + name;
        }
    }

    @Getter @Setter
    @AllArgsConstructor
    @EqualsAndHashCode
    static class Member {
        private String name;

        @Override
        public String toString() {
            return super.toString() + "@name: " + name;
        }
    }
}

반응형

'JAVA' 카테고리의 다른 글

[JAVA]String의 equals(), hashCode()  (0) 2022.02.08
[JAVA]JAVA Annotaion (+Lombok제공 Annotaion 정리)  (0) 2022.02.06
[JAVA]배열 (Array)  (0) 2021.02.04
Comments