사실 모든 자바 클래스의 부모는 java.lang.Object클래스 이다.
extends 키워드가 없을때, 즉, 아무 클래스도 상속받지 않은 클래스는 모두 Object를 상속받은것이다. 그럼 extends 키워드가 붙은 클래스는 Object도 받고, 다른 클래스도 받은건가? 다중 상속은 안된다고 했던거 같은데..
다중 상속은 안되지만, 여러 단계로 상속받을수 있다. 아래 그림을 보자.
왼쪽처럼 Child가 Parent와 Object를 동시에 상속받을수 없다. 하지만 오른쪽과 같이 Parent가 Object를 상속받고, Child가 Parent를 상속받는 형태로 상속 받는 것은 가능하다. 결국 Child도 Object의 변수와 메소드를 상속받은것 처럼 사용할 수 있다.(public, protected)
왜? Object를 상속받을까?
가장 큰 이유는 Object 클래스에 있는 메소드를 통해서 클래스의 기본적인 행동을 정의할 수 있기 때문. 클래스라면 이정도의 메소드는 정의되어 있어야 하고, 처리해 주어야 한다는 것. 그 기본이 Object 이기 때문에 Object를 상속받음.
Object클래스에 선언된 메소드들은 두가지 목적을 가진다. Object 클래스는 java.lang 패키지아래 있다.
- 객체를 처리하기 위한 메소드
- public String toString()
- public boolean equals(Object obj)
- public native int hashcode()
- public final native getClass()
- protected native Object clone()
- protected void finalize() (@Deprecated(Since="9"), 9버전 이후 Deprecated)
- 쓰레드를 처리하기 위한 메소드
- public final native void notify()
- public final native void notifyAll()
- public final void wait()
- public final native void wait(long timeoutMillis)
- public final void wait(long timeoutMillis, int nanos)
꼭 한번씩 찾아보면 좋겠다.
쓰레드는 아직 잘 모르고,
객체를 처리하는 메소드 중에서 toString() 메소드를 가장 많이 사용한다.
toString()메소드는 그냥 쓰는것이 아니라 오버라이딩(Overriding)해야한다. 특히, DTO를 사용할때 꼭! toString()메소드를 오버라이딩 해둬야 한다. 아래와 같이 회원 정보를 담는 dto가 있다.
package com.molt.domain;
public class MemberDTO {
public String name;
public String phone;
public String email;
}
Main클래스에서 memberDTO 객체를 만들고, memberDTO객체에 toString()메소드를 호출해보자. toString()메소드는 이미 알겠지만, Object 클래스에 정의되어있기 때문에 모든 클래스에서 불러 쓸수는 있다.
import com.molt.domain.MemberDTO;
public class Main {
public static void main(String[] args) {
MemberDTO memberDTO = new MemberDTO();
memberDTO.name = "molt";
memberDTO.email = "molt@naver.com";
memberDTO.phone = "010-1234-5678";
String dtoInfo = memberDTO.toString();
System.out.println(dtoInfo);
}
}
//결과
com.molt.domain.MemberDTO@769c9116
??? 도통 알수없는 문자들이다. 이렇게 써서는 아무것도 알수 없다. 만약 이 상태에서 객체 정보를 확인하고 싶다면 출력문을 아래와 같이 변경해야 한다.
import com.molt.domain.MemberDTO;
public class Main {
public static void main(String[] args) {
MemberDTO memberDTO = new MemberDTO();
memberDTO.name = "molt";
memberDTO.email = "molt@naver.com";
memberDTO.phone = "010-1234-5678";
System.out.println("name="+memberDTO.name+", phone="+memberDTO.phone+", email="+memberDTO.email);
}
}
// 결과
name=molt, phone=010-1234-5678, email=molt@naver.com
뭐 약간은 봐줄만 하다? 그런데 객체 수가 많아지면 이런 방식으로는 힘들다.
아래와 같이 toString()메소드를 오버라이딩 하자.
package com.molt.domain;
public class MemberDTO {
public String name;
public String phone;
public String email;
@Override
public String toString() {
return "MemberDTO{" +
"name='" + name + '\'' +
", phone='" + phone + '\'' +
", email='" + email + '\'' +
'}';
}
}
출력하는 부분도 바꾸자.
import com.molt.domain.MemberDTO;
import java.lang.reflect.Member;
public class Main {
public static void main(String[] args) {
MemberDTO memberDTO = new MemberDTO();
memberDTO.name = "molt";
memberDTO.email = "molt@naver.com";
memberDTO.phone = "010-1234-5678";
String dtoInfo = memberDTO.toString();
System.out.println(dtoInfo);
}
}
// 결과
MemberDTO{name='molt', phone='010-1234-5678', email='molt@naver.com'}
알아보기 쉽게 출력된다. DTO를 만들때 바쁘더라도 항상 toString() 메소드를 오버라이딩 하는 습관을 가져야 한다.
특이하게 toString() 메소드가 자동으로 호출되는 경우가 2가지 있다.
- System.out.println() 메소드에 매개변수로 들어가는 경우.
- 객체에 더하기 연산을 사용하는 경우.
위에서 출력문을 다음과 같이 바꿔보자.
import com.molt.domain.MemberDTO;
import java.lang.reflect.Member;
public class Main {
public static void main(String[] args) {
MemberDTO memberDTO = new MemberDTO();
memberDTO.name = "molt";
memberDTO.email = "molt@naver.com";
memberDTO.phone = "010-1234-5678";
// String dtoInfo = memberDTO.toString();
System.out.println(memberDTO);
}
}
// 결과
MemberDTO{name='molt', phone='010-1234-5678', email='molt@naver.com'}
객체를 System.out.println()의 매개변수로 사용했다. 그런데 결과는 toString()메소드를 사용했을 때와 같다.
이번에는 아래와 같이 바꿔보자.
import com.molt.domain.MemberDTO;
import java.lang.reflect.Member;
public class Main {
public static void main(String[] args) {
MemberDTO memberDTO = new MemberDTO();
memberDTO.name = "molt";
memberDTO.email = "molt@naver.com";
memberDTO.phone = "010-1234-5678";
// String dtoInfo = memberDTO.toString();
System.out.println(memberDTO);
System.out.println("회원정보DTO : " + memberDTO);
}
}
// 결과
회원정보DTO : MemberDTO{name='molt', phone='010-1234-5678', email='molt@naver.com'}
"회원정보DTO"라는 스트링과 객체를 더하기 연산했다. 결과는 객체에 toString()메소드를 호출한결과와 스트링을 더하기 연산한 것과 같다. 위에서 설명한 두번째 경우이다.
객체가 같은지 비교할 수 있을까?
기본자료형은 연산자(==, !=)로 비교할 수 있다. 그러나, 참조자료형에 연산자를 사용하게 되면, 값이 아니라 주소값을 비교한다. 참조자료형은 Object클래스에 있는 equals() 메소드를 이용해 두 객체의 값이 같은지 비교할 수 있다. equals() 메소드를 오버라이딩 해둬야 제대로된 비교가 가능하다. 만약 equals()메소드를 오버라이딩 하지 않으면 equals()메소드는 hashCode() 값을 비교한다. hashCode()는 해당 객체의 주소값을 반환한다. 클래스의 인스턴스 변수값이 같더라도 다른 생성자를 사용해서 객체를 생성했으면 해시코드가 다르기 때문에 두 객체가 다르다는 결과가 나온다.
자바 API 문서에 equals() 메소드를 오버라이딩 할때 지켜야 하는 다섯가지 원칙이 있는데, 당연한 말 같긴하다. 자세한 내용은 검색이나 책을 한번 구입해서 보시길 권한다.
(재귀, 대칭, 타동적, 일관, null비교)
또, equals()메소드를 오버라이딩 할때는 hashCode() 메소드도 함께 오버라이딩 해야한다. equals() 메소드를 오버라이딩해서 객체가 같다고 해도, 그 객체의 주소값이 같지 않기때문에 hashCode()의 값이 다르게 나온다. 따라서 객체의 값이 같을때 hashCode()메소드의 결과가 동일할 수 있도록 hashCode()메소드도 오버라이딩 해줘야 한다.
hashCode()는 객체의 메모리 주소를 16진수로 리턴한다. 만약 어떤 두개의 객체가 서로 동일하면 hashCode() 값은 동일해야 한다. equals()메소드를 오버라이딩하면 hashCode()도 오버라이딩해서 동일한 결과가 나오도록 만들어 줘야 한다.
hashCode() 메소드를 구현할때 지켜야 하는 약속
- 자바 애플리케이션이 수행되는 동안 어떤 객체에 대해서 이 메소드가 호출될때 항상 동일한 int 값을 리턴해줘야한다. 단, 자바를 실행할 때마다 같은 값일 필요는 없다.
- 어떤 두개의 객체의 값이 동일할때 두 객체의 hashCode() 메소드의 결과가 동일한 int 값을 리턴해야 한다.
- 어떤 두 개의 객체의 값이 동일하지 않을 때, hashCode()의 int 값이 무조건 달라야 할 필요는 없지만, 다른 int 값을 제공해주면 hashtable의 성능을 향상시키는데 도움이 된다.
반드시 모든 클래스를 만들때 equals(), hashCode() 메소드를 오버라이딩해야하는것은 아니다. DTO를 만들때 객체 비교를 위해 필요한 것이다. 다른 기능위주의 클래스를 만들때는 굳이 만들 필요 없다.
'Java' 카테고리의 다른 글
[자바의 신] 예외 (0) | 2020.11.28 |
---|---|
[자바의 신] 인터페이스와 추상클래스 (0) | 2020.11.20 |
[자바의 신] 상속 (0) | 2020.10.06 |
[자바의 신] 접근제어자 (0) | 2020.10.02 |
[자바의 신] 패키지 (0) | 2020.10.01 |