본문 바로가기
개발/Spring | Java | Error

JAVA Object

by 하얀 루돌프 2022. 3. 13.

📌 과제 설명

1. Object(객체) 탐구하기
- toString(), equals(), hashCode() 에 대해서 알아봅시다.

❔ 넌 어디서 왔니?

public class StringClassExam{
    public static void main(String[] args) {
        System.out.println("System.out.println은 어디서 왔길래 사용할수 있을까?");
    }
}

java를 쓰면서, 콘솔출력을 많이 사용하게 되는데 출력에 대한 기능을 만들지도 않았는데, 위 코드를 치면 손쉽게 콘솔출력을 할 수 있게 됩니다. 그럼 System.out.println은 어디서 왔을까요?


해당 코드 위에 Ctrl + 마우스를 올려보면, Java의 lang 패키지를 보게 됩니다.
자세히 살펴보면 extends Object로 코드상에는 보이지 않는데, 자동적으로 Object를 상속 받고 있는 모습을 볼 수 있습니다.


Object클래스는 java.lang 패키지의 최상위 클래스입니다.

이 덕분에 java.lang에 들어있는 기능들을 불러 사용 할 수 있습니다. 그러므로 System.out은 java.lang 패키지 안에 포함되어 있어 사용이 가능 했던 겁니다. 추가로 생성한 클래스는 Object 클래스의 메소드를 사용할 수 있고(상속받았기에), 기존의 Object가 가진 메소드를 오버라이딩(재정의)도 할 수 있습니다.


이 중 Object 클래스 메소드 중 toString(), equals(), hashCode() 에 대해서 알아봅시다!



👩‍💻 요구 사항과 구현 내용

❔ toString()에 대하여 알아보자!

1️⃣ 객체가 가지고 있는 정보나 값들을 문자열로 만들어 리턴하는 메소드

  • Object를 상속받았기 때문에, toString()을 override을 하지 않으면 '(패키지명)클래스풀네임@해시코드'가 출력됩니다.

    Object.java 에 정의되어 있는 toString
class Book {
    String title;
    String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

}
public class ObjectTest {
    public static void main(String[] args) {
        Book book = new Book("마시멜로 이야기", "엘런싱어 와 호아킴데포사다");
		
        System.out.println(book); // 📣 Book@7a81197d

    }
}



2️⃣ * override 작성 후 실행
public class ObjectTest {
    public static void main(String[] args) {
        Book book = new Book("마시멜로 이야기", "엘런싱어 와 호아킴데포사다");
        System.out.println(book); //  📣 Book{title='마시멜로 이야기', author='엘런싱어 와 호아킴데포사다'}

    }
}

class Book {
    String title;
    String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                '}';
    }
}



3️⃣ String 객체는 toString()을 안해도 문자열이 바로 반환됩니다.
String 클래스는 이미 toString()을 override 하고 있기 때문입니다.

String str = new String("Apple");
System.out.println(str); // 📣 Apple 📣 toString()을 안했는데 문자열이 출력됩니다.




❔ boolean equals(Object obj)에 대하여 알아보자!

equals는 내용이 같은지를 검사하는 메소드입니다.
다만 == 연산자와 다른 점은 완전히 같은 객체를 가르키지 않아도 개발자가 true로 만들 수 있습니다.
즉 주소는 달라도, 내용이 같으면 같다고 봐서 true를 반환 할 수 있게 만듭니다.

== 는 주소의 값을 비교합니다.(Call By Reference)

equal() 는 reference type끼리는 주소를 비교합니다.(Call By Reference),
primitive type과 String Class, wrapper Class(Integer, Double 등)은 내용(Value)을 비교하도록 오버라이딩 되어있습니다.

public class equal {
    public static void main(String[] args) {

        // 비교연산 ==
        String str1 = "abc";
        String str2 = str1;
        if (str1 == str2)
            System.out.println("str1과 str2는 동일한 주소를 가지고 있습니다."); // 📣 true로 출력됨
        else
            System.out.println("서로 다른 주소를 가지고 있습니다.");
        System.out.println(str1.equals(str2)); // 📣 true


        // 객체에서의 equal()와 hashcode()
        Test object1 = new Test("Na");
        Test object2 = new Test("Na");
        System.out.println(object1.equals(object2)); // 📣 false

        // String에서의 equal()와 hashcode()
        String string1 = new String("Na");
        String string2 = new String("Na");
        System.out.println(string1.equals(string2)); // 📣 true
    }
}

class Test {
    String name;
    Test(String name) {
        this.name = name;
    }
}

string1과 string2는 서로 다른 주소를 가지고 있지만,
String클래스는 이미 equals메서드가 오버라이드(재정의) 되어있어, 주소가 아닌 객체에 저장된 값을 비교하기 때문에 true가 나왔습니다. 다만 Test객체에서는 name만 같으면 같게 볼지, 모르기 때문에 false가 나왔습니다.

public class equal {
    public static void main(String[] args) {
        
        // 객체에서의 equal()와 hashcode()
        Test object1 = new Test("Na");
        Test object2 = new Test("Na");
        System.out.println(object1.equals(object2)); // 📣 true

        // String에서의 equal()와 hashcode()
        String string1 = new String("Na");
        String string2 = new String("Na");
        System.out.println(string1.equals(string2)); // 📣 true
    }
}

class Test {
    String name;
    Test(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        Test anotherObj = (Test) obj;
        return (this.name.equals(anotherObj.name));
    }
}

위 코드처럼 equals()을 @Override 해주면 내용을 같은지 비교하여 true를 반환합니다.


❔ hashCode()에 대하여 알아보자!

  • equals(Object) 메서드에 따라 두 객체가 같은 경우 두 객체 각각에 대해 hashCode 메서드를 호출하면 동일한 정수 결과가 생성되어야 합니다
  • hashCode()가 같으면 equals()는 true다 라는 역은 성립하지 않습니다.
import java.util.Objects;

public class equal {
    public static void main(String[] args) {

        // 객체에서의 equal()와 hashcode()
        Test object1 = new Test("Na");
        Test object2 = new Test("Na");
        System.out.println(object1.equals(object2)); // 📣 true
        System.out.println(object1.hashCode()); // 📣 2546
        System.out.println(object2.hashCode()); //📣 2546

        // String에서의 equal()와 hashcode()
        String string1 = new String("Na");
        String string2 = new String("Na");
        System.out.println(string1.equals(string2)); // 📣 true
        System.out.println(string1.hashCode()); // 📣 2515
        System.out.println(string2.hashCode()); // 📣 2515
    }
}

class Test {
    String name;
    Test(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) { // equals() 메소드 재정의
        Test anotherObj = (Test) obj;
        return (this.name.equals(anotherObj.name));
    }
    
    @Override
    public int hashCode() { // equals() 메소드를 재정의 하게되면 hashCode()메소드도 재정의를 해줘야 합니다.
        return Objects.hash(name);
    }


}

즉 equals(Object)가 서로 같은 객체(true)라면, hashcode()도 동일해야 합니다.
그 이유는 hash를 사용하는 Collection(HashSet, HashMap, ...)등에서 hash가 다르면 같은 값이라도 서로 다르게 인식하기 때문에, 반드시 equals()와 hashcode()는 함께 재정의 해야 합니다.


🔰 알쓸신잡 - String Hashcode 계산하는 방법

  • s[i]는 문자열의 개별 문자를 나타냅니다.
  • ^ 거듭제곱을 의미합니다.
  • n 은 문자열의 길이를 나타냅니다.
  • String에서 hashcode()의 반환값이 어떻게 나오는지에 대한 설명입니다.
    hashcode는 int형으로 반환하는데, int는 4바이트는 31비트로 표현합니다. 31을 쓰는 이유는 홀수이고, 비트연산으로도 대체 가능하기 때문입니다. (비트연산은 곱셈보다 더 빠르게 처리할수있습니다.)
  • (31 * i) == (i << 5) - i 는 동일합니다. (i << 5) - i 비트연산은 2^32 -i 의미입니다.
import java.io.*;
  
class GFG {
    public static void main(String[] args)
    {
        String str = "GFG";
        System.out.println(str);
  
        int hashCode = str.hashCode(); //GFG
        System.out.println(hashCode); // 70472
    }
}

🛑 문자열 3개일 시
s[] = {'G', 'F', 'G'}
n = 3

⭕ 문자열 해시코드값 구하기
s[0] x 31^(2) + s[1] x 31^1 + s[2]
= G x 31^2 + F x 31 + G
= (G의 ASCII 값 = 71 그리고 F = 70)
71 x 31^2 + 70 x 31 + 71
= 68231 + 2170 + 71
= 70472

🛑 빈 문자열의 해시코드값 구하기
s[] = {}
n = 0

⭕ 해시코드 계산
s[0]*31^(0)
= 0


Reference
https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
https://velog.io/@foeverna/Java%EC%9E%90%EB%B0%94-%EA%B8%B0%EB%B3%B8-%ED%81%B4%EB%9E%98%EC%8A%A4-Object-%ED%81%B4%EB%9E%98%EC%8A%A4