Object 클래스

  • 자바의 Object 클래스는 모든 자바 클래스의 최상위 클래스로, 전체 이름은 java.lang.Object이다.
  • Object 클래스는 가장 최상위 클래스이므로 자바의 모든 클래스들은 Object 클래스를 상속받는데, 컴파일 과정에서 컴파일러가 자동적으로 extends 해준다. → 모든 클래스 extends Object
  • Object 클래스는 필드(멤버 변수)가 없고 메서드로 구성되어 있다.

 


Object 클래스의 주요 메서드

Java 공식 문서에서 Object 클래스가 제공하는 메서드들을 확인할 수 있다.

https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/Object.html

 

Object (Java SE 12 & JDK 12 )

Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup. The general contract of fi

docs.oracle.com

그 중 자주 쓰이는 메서드들을 알아보자.

 


equals() 메서드

public boolean equals(Object obj) {
    ...
}

 

equals()는 두 인스턴스의 주소값을 비교하여 같으면 true, 다르면 falseboolean 값을 리턴하는 메서드이다.

 

예시로 Person이라는 클래스를 만들어보자.

package day08;

public class Object_equal {
    public static class Person {
        String name;
        double height;

        public Person (String name, double height){
            this.name = name;
            this.height = height;
        }
    }

    public static void main(String[] args) {
        Person person1 = new Person("홍길동", 177.2);
        Person person2 = new Person("김철수", 168.7);
        Person person3 = person1; //person1의 주소값을 person3에 복사

        System.out.println(person1.equals(person2));
        System.out.println(person1.equals(person3));
    }
}

실행 결과

person1와 person2는 주소값이 서로 다르다. 따라서 두 인스턴스의 주소값을 비교하는 equals 메서드를 사용하면 false를 출력한다. 한편 person3에는 person1의 주소값이 복사돼서 person1과 person3는 같은 인스턴스를 가리킨다. 따라서 person1과 person3의 주소값을 비교하는 equals 메서드를 사용하면 true를 출력한다.

 


equals() 메서드 오버라이딩

이번에는 person1과 이름과 키가 같은 동일한 person4의 객체를 만들어보자.

Person person4 = new Person("홍길동", 177.2);

person1과 person4는 이름과 키가 같은 동일한 사람이지만, equals() 메서드는 내용이 아닌 주소값이 같은지를 확인하기 때문에 false가 출력된다.

package day08;

public class Object_equal {
    public static class Person {
        String name;
        double height;

        public Person (String name, double height){
            this.name = name;
            this.height = height;
        }
    }

    public static void main(String[] args) {
        Person person1 = new Person("홍길동", 177.2);
        Person person2 = new Person("김철수", 168.7);
        Person person3 = person1; //person1의 주소값을 person3에 복사
        Person person4 = new Person("홍길동", 177.2);

        System.out.println(person1.equals(person4)); //false
    }
}

 

만약 person1과 person4의 주소값이 아닌 내용이 동일한지 확인하고 싶으면 equals() 메서드를 오버라이딩하면 된다.

 

[equals() 메서드 오버라이딩]

//Object의 equals()를 오버라이딩해서 주소가 아닌 value를 비교하도록 만듦
@Override
public boolean equals(Object obj){
    if(obj instanceof Person){ //만약 매개변수 obj가 Person의 객체라면
        return name == ((Person)obj).name; //obj가 Object타입이므로 Person 타입으로 형변환해서
                                            //name값이 같은지 비교하고 불리언 값(true/false) 반환
    } else {
        return false; //매개변수 obj가 Person의 객체가 아니라면 비교X
    }
}

 

[전체 코드]

package day08;

public class Object_equal {
    public static class Person {
        String name;
        double height;

        public Person (String name, double height){
            this.name = name;
            this.height = height;
        }

        //Object의 equals()를 오버라이딩해서 주소가 아닌 value를 비교하도록 만듦
        /*Object obj => Object person2(매개변수로 들어옴)
		=> Person person2로 형변환해야 Person 클래스의 name에 접근할 수 있음*/
        @Override
        public boolean equals(Object obj){
            if(obj instanceof Person){ //만약 매개변수 obj가 Person의 객체라면
                return name == ((Person)obj).name; //obj가 Object타입이므로 Person 타입으로 형변환해서
                                                    //name값이 같은지 비교하고 불리언 값(true/false) 반환
            } else {
                return false; //매개변수 obj가 Person의 객체가 아니라면 비교X
            }
        }
    }

    public static void main(String[] args) {
        Person person1 = new Person("홍길동", 177.2);
        Person person2 = new Person("김철수", 168.7);
        Person person3 = person1;
        Person person4 = new Person("홍길동", 177.2);
		
        //person1인스턴스에 소속되어 있는 상태에서 person2,3,4와 각각 비교
        System.out.println(person1.equals(person2)); //이름이 다르므로 false
        System.out.println(person1.equals(person3)); //이름이 같으므로 true
        System.out.println(person1.equals(person4)); //이름이 같으므로 true
    }
}

 

 

만약 equals를 오버라이딩한 부분을 지워버리면 name값을 비교하지 않고 주소값을 비교하여 결과가 달라진다.

package day08;

public class Object_equal {
    public static class Person {
        String name;
        double height;

        public Person (String name, double height){
            this.name = name;
            this.height = height;
        }
    }

    public static void main(String[] args) {
        Person person1 = new Person("홍길동", 177.2);
        Person person2 = new Person("김철수", 168.7);
        Person person3 = person1;
        Person person4 = new Person("홍길동", 177.2);

        System.out.println(person1.equals(person2)); //가리키는 주소가 다르므로 false
        System.out.println(person1.equals(person3)); //가리키는 주소가 같으므로 true
        System.out.println(person1.equals(person4)); //가리키는 주소가 다르므로 false
    }
}

 

 


equals() 메서드는 Object의 하위 클래스인 String에서 이미 오버라이딩 되어 있다.

package day08;

public class Equals {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "def";
        String str3 = new String("abc");

        System.out.println(str1.equals(str2)); //false
        System.out.println(str1.equals(str3)); //true
    }
}

Object의 하위 클래스인 String에서 equals() 메서드를 사용하는 경우, 주소값을 비교하는 것이 아닌 value를 비교한다.

 


문자열 비교 ==, equals() 차이점

String 변수 생성하는 방법

1. 리터럴을 이용한 방식
2. new 연산자를 이용한 방식
package day08;

public class Test1 {
    public static void main(String[] args) {
        //리터럴을 이용한 방식
        String str1 = "Good";
        String str2 = "Morning";
        
        //new 연산자를 이용한 방식
        String str3 = new String("hello");
        String str4 = new String("hi");
    }
}

String은 사실 클래스다. 따라서 String은 참조형이다.

다른 참조형 변수들과 마찬가지로 String string = new String(); 형태로 인스턴스를 생성할 수 있다.

new 연산자를 이용한 방식

그런데 기본형처럼 문자 값을 바로 대입할 수 있는데, 문자는 매우 자주 다루기 때문에 자바에서 특별하게 편의 기능을 제공하는 것이다.

리터럴을 이용한 방식

 

 

주소값 비교(==)와 값 비교(equals)

== 연산자는 비교하고자 하는 두 대상의 주소값을 비교하는데 반해, String 클래스의 equals() 메서드는 비교하고자 하는 두 대상의 값 자체를 비교한다.

String 타입은 클래스이기 때문에, String 타입을 선언하면 같은 값을 부여하더라도 주소값이 다르다.

 

두 객체가 같다는 표현 2가지
  1. 동일성(Identity): == 연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인 → 물리적
  2. 동등성(Equality): equals() 메서드를 사용하여 두 객체가 논리적으로 동등한지 확인 → 논리적

 

주소값 비교 (== 연산자)

package day08;

public class Test1 {
    public static void main(String[] args) {
        String str1 = "java"; //리터럴을 이용한 방식
        String str2 = new String("java"); //new 연산자를 이용한 방식

        if(str1==str2){
            System.out.println("두 주소값이 같아요.");
        } else {
            System.out.println("두 주소값이 달라요.");
        }
    }
}

 

실행 결과

 

값 비교(equals)

package day08;

public class Test1 {
    public static void main(String[] args) {
        String str1 = "java"; //리터럴을 이용한 방식
        String str2 = new String("java"); //new 연산자를 이용한 방식

        if(str1.equals(str2)){
            System.out.println("두 값이 같아요.");
        } else {
            System.out.println("두 값이 달라요.");
        }
    }
}

실행 결과

 

 


hashCode() 메서드

  • 객체의 해시코드(hash code)를 반환하는 메서드
  • Object 클래스의 hashCode()는 객체의 주소를 int로 변환해서 반환한다.
  • 해시코드: 객체의 지문. 객체마다 다른 값을 가짐
  • equals()를 오버라이딩하면, hashCode()도 오버라이딩해야 한다. equals()의 결과가 true인 두 객체의 해시코드는 같아야 하기 때문이다.
public int hashCode(){
    ...
}
//Object클래스의 hashCode()를 오버라이딩
//equals()를 오버라이딩하면 hashCode()도 오버라이딩 해야한다.
public int hashCode(){
    return Objects.hash(kind, number);
}
/*Objects클래스는 객체와 관련된 유용한 메서드를 제공하는 유틸 클래스
  Objects.hash(값1, 값2, ...) => 해시코드를 생성. 매개변수의 인자 개수가 정해져있지 않음
 */

//Object클래스의 equals()를 오버라이딩
@Override
public boolean equals(Object obj){
    if(obj instanceof Card){
        return this.kind == ((Card)obj).kind && this.number == ((Card)obj).number;
    } else {
        return false;
    }
}

 

  • Objects.hash(Object... values): 매개 값으로 주어진 값들을 이용해서 해시 코드를 생성함. 매개변수의 인자 개수가 정해져 있지 않음 (여러 개가 들어가도 상관 없음)
  • Objects.hashCode(Object o): 매개 값으로 주어진 객체의 해시 코드를 리턴함 == o.hashCode()

 

[전체 코드]

package study0206;

import java.util.Objects;

class Card{
    String kind;
    int number;

    Card(){
        this("SPADE", 1);
    }

    Card(String kind, int number){
        this.kind = kind;
        this.number = number;
    }
    //Object클래스의 hashCode()를 오버라이딩
    //equals()를 오버라이딩하면 hashCode()도 오버라이딩 해야한다.
    public int hashCode(){
        return Objects.hash(kind, number);
    }
    /*Objects클래스는 객체와 관련된 유용한 메서드를 제공하는 유틸 클래스
      Objects.hash(값1, 값2, ...) => 해시코드를 생성. 매개변수의 인자 개수가 정해져있지 않음
     */

    //Object클래스의 equals()를 오버라이딩
    @Override
    public boolean equals(Object obj){
        if(obj instanceof Card){
            return this.kind == ((Card)obj).kind && this.number == ((Card)obj).number;
        } else {
            return false;
        }
    }

    //Object클래스의 toString()을 오버라이딩
    @Override
    public String toString(){
        return "kind:" + kind + ", number:" + number;
    }
}
public class ObjectTest {
    public static void main(String[] args) {
        Card c1 = new Card();
        Card c2 = new Card();

        //kind와 number가 같으면 동일한 것으로 인식하도록 equals()를 오버라이딩
        System.out.println("c1.equals(c2): "+c1.equals(c2));

        //kind와 number의 조합으로 만들어진 해시코드를 반환
        System.out.println("c1의 해시코드: " + c1.hashCode()); //첫 번째 방법
        System.out.println("c1의 해시코드: " + Objects.hashCode(c1)); //두 번째 방법
        System.out.println("c2의 해시코드: " + c2.hashCode());
        System.out.println("c2의 해시코드: " + Objects.hashCode(c2));

        System.out.println(c2.toString()); //오버라이딩: kind와 number값 반환
    }
}

실행 결과

 

 

equals와 hashcode를 함께 정의해야 하는 이유

 

equals만 오버라이딩하고 hashcode를 오버라이딩하지 않는다면 두 객체의 equals를 통한 비교는 같다고 판단되어도, 두 객체는 다른 해시값을 가지게 된다. 이는 훗날 HashMap, HashSet, HashTable 같은 해시값을 사용하는 컬렉션을 이용할 때 문제가 된다.


toString() 메서드

public String toString() {
    ...
}

 

toString() 메서드는 객체의 정보를 String(문자열)형으로 변환해준다. 

 

Object 클래스의 toString() 메서드를 살펴보자. 예시로 위에 썼던 Person 클래스를 가져와보자.

package day08;

public class Object_toString {
    public static class Person {
        String name;
        double height;

        public Person (String name, double height){
            this.name = name;
            this.height = height;
        }
    }

    public static void main(String[] args) {
        Person person1 = new Person("홍길동", 177.2);

        System.out.println(person1); //toString()이 생략되어 있음
        System.out.println(person1.toString());
    }
}

실행 결과

person1을 그대로 출력한 결과와 person1.toString()을 출력한 결과가 같다. System.out.println에 참조 변수(Object형)를 넣으면 toString()이 자동으로 호출된다.

 

출력결과를 살펴보면, @를 기준으로 좌측은 클래스의 이름을 나타내고, 우측은 해시코드 값을 나타낸다. 해시코드란, 해시 함수에 의해 자동으로 생성된 값으로, 객체를 유일하게 식별할 수 있는 정수 값이다. person1이라는 같은 객체이므로 둘의 해시코드는 동일하다.

 


toString() 메서드 오버라이딩

toString() 메서드를 사용했을 때 해시코드 값이 아닌 사용자가 원하는 문자열을 출력하도록 toString() 메서드를 오버라이딩해보자.

package day08;

public class Object_toString {
    public static class Person {
        String name;
        double height;

        public Person (String name, double height){
            this.name = name;
            this.height = height;
        }
        
        @Override
        public String toString(){
            String str = "---------정보---------\n";
            str += "이름: " + name + ", 키: " + height;
            return str;
        }
    }

    public static void main(String[] args) {
        Person person1 = new Person("홍길동", 177.2);

        System.out.println(person1); //toString()이 생략되어 있음
        System.out.println(person1.toString());
    }
}

실행 결과

toString() 메서드를 오버라이딩하고 다시 출력해보면 해시코드 대신에 String이 출력되는 것을 확인할 수 있다.

+ Recent posts