🟨 목 차 🟨
1. 모든 자바 클래스의 부모인 java.lang.Object 클래스
2. Object 클래스에서 제공하는 메소들의 종류
2-1. 객체 처리를 위한 메소드
2-2. 쓰레드 처리를 위한 메소드
3. Object 클래스에서 가장 많이 쓰이는 toString 메소드
3-1. eqauls()
3-2. hashCode()
◼️ 1. 모든 자바 클래스의 부모인 java.lang.Object 클래스 ◼️
main()메소드 외에는 선언되어 있지 않고 무것도 상속 받지 않은 평범한 클래스는 toString()이라는 메소드를 사용할 수 있다.
==> Object 클래스에 있는 메소드를 사용해서 가능하다.
자바는 한번에 이중 상속을 받을 수는 없지만, 여러 단계로 상속을 받을 수는 있다.
예시로 Parent 클래스의 자식인 Child 클래스가 있다. Parent 클래스는 아무런 상속을 받진 않았지만, 실제로는 Object 상속을 받았다.
그러한 Paret클래스를 Child 클래스가 상속받았으므로, Child 클래스는 자동으로 Object 클래스의 메소드들을 상속 받는다.
즉, Child 클래스는 Object 클래스의 자식의 자식이다.(손자)
그렇다면 왜 모든 클래스는 Object 클래스의 상속을 받을까?
==> 가장 큰 이유는 Object 클래스에 있는 메소드들을 통해서 클래스의 기본적인 행동을 정의할 수 있기 때문
◼️ 2. Object 클래스에서 제공하는 메소들의 종류 ◼️
Object 클래스에 선언되어 있는 메소드는 객체를 처리하기 위한 메소드와 쓰레드를 위한 메소드로 나뉜다.
[ *쓰레드: 프로그램이 실행되는 작은 단위 중 하나 ]
◼️ 2-1. 객체 처리를 위한 메소드
- protected Object clone() : 객체의 복사본을 만들어 리턴한다.
- public boolean equals(Object obj) : 현재 객체와 매개 변수로 넘겨받은 객체가 같은지 확인한다. 같으면 true, 다르면 false 리턴
- protected void finalize() : 현재 객체가 더 이상 쓸모가 없어졌을 때 가비지 컬랙터에 의해서 이 메소드가 호출된다.
- public Class<?> getClass() : 현재 객체의 Class 클래스의 객체를 리턴한다.
- public hin hashCode() : 객체에 대한 해시코드 값을 리턴한다. 해시코드 라는 것은 "16진수로 제공되는 객체의 메모리 주소"를 말한다.
- public String toString() : 객체를 문자열로 표현하는 값을 리턴한다.
[ *가비지 컬렉터 : 자바로 개발할 때에 필요없는 객체로 직접 지정해주고 싶을때 쓴다. 자바의 메모리에 있는 쓰레기를 청소하는 로봇이라고 생각하면 된다. ]
◼️ 2-2. 쓰레드 처리를 위한 메소드
- public void notify() : 이 객체의 모니터에 대기하고 있는 단일 쓰레드를 깨운다.
- public void notifyAll() : 이 객체의 모니터에 대기하고 있는 모든 쓰레드를 깨운다.
- public void wait() : 다른 쓰레드가 현재 객체에 대한 notify() 메소드나 notifyAll()메소드를 호출할 때까지 현재 쓰레드가 대기하고 있도록 한다.
- public void wait(long tiemout) : wait()메소드와 동일한 기능으 제공하며, 매개 변수에 지정한 시간만큼만 대기한다. 즉, 매개 변수 시간을 넘어 섰을 때에는 현재 쓰레드는 다시 깨어난다. 여기서의 시간은 밀리초로 1/1,000초 단위다. 만약 1초간 기다리게 할 경우에는 1000을 매개 변수로 넘겨주면 된다.
- public void wait(long timeout, int nanos) : wait()메소드와 동일한 기능을 제공한다. 하지만 wait(timeout)에서 밀리초 단위의 대기시간을 기다린다면, 이 메소드는 보다 자세한 밀리초 + 나노초(1/1,000,000,000초) 만큼만 대기한다. 뒤에 있는 나노초의 값은 0~999,999 사이의 값만 지정할 수 있다.
◼️ 3. Object 클래스에서 가장 많이 쓰이는 toString 메소드 ◼️
가장 기본적이고 꼭 기억하는 메소드들을 많이 사용하는 메소드부터 나열을 해보겠다.
- toString()
- equals()
- hashCode()
- getClass()
- clone()
- finalize()
◼️ 3-1. toString()
해당 클래스가 어떤 객체인지를 쉽게 나타낼 수 있는 중요한 메소드다.
이 메소드가 자동으로 호출되는 경우는 다음과 같다.
- System.out.println() 메소드에 매개 변수로 들어가는 경우
- 객체에 대하여 더하기 연산을 하는 경우
[EX]
package part12;
public class ToString {
public static void main(String[] args) {
ToString thisObject = new ToString();
thisObject.toStringMethod(thisObject);
}
public void toStringMethod(Object obj) {
System.out.println(obj);
System.out.println(obj.toString());
System.out.println("plus " + obj);
}
}
part12.ToString@58d25a40
part12.ToString@58d25a40
plus part12.ToString@58d25a40
==> 위의 코드를 보면 toStringMethod() 메소드의 가장 첫 줄에서는 객체를 그대로 출력했고, 두 번째 줄에서는 toString() 메소드를 불렀다.
첫번째 줄은 어떻게든 출력할 것이고, 두번째 줄의 toString()메소드는 ToString 클래스에 전혀 선언이 되어 있지 않다.
==> toString() 메소드는 Obejct클래스의 상속을 자동으로 받기때문에 사용 가능하다.
참조 자료형의 더하기 연산은 String만 가능하다. 하지만 예제는 객체를 더했다. 잘못배운건가.
==> 아니다. 객체를 그냥 출력하는것과 toString()메소드를 호출하는 것은 동일한 것을 볼 수 있다. 또한 가장 마지막에 "plus "뒤에 객체를 더했는데 toString()한 것과 동일한 결과가 출력됐다.
즉, 다시 말하면 String을 제외한 참조 자료형에 더하기 연산을 하면, 자동으로 toString()메소드가 호출되어 객체의 위치에는 String값이 놓이게 된다.
toString()으로 출력된 결과에 의문이 생길 것이다. 살펴보자.
==> 실제 Object 클래스에 구현되어 있는 toString()메소드는 다음과 같다.
getClass().getName() + '@' + Integer.toHexString(hashCod())
Object 클래스에 있는 getClass()의 결과에 getName() 메소드를 부르면 현재 클래스의 패키지 이름과 클래스 이름이 나온다.
그 다음에는 @가 붙단다.(앞과 뒤의 결과를 구분하기 위한 구분자)
그리고 마지막 부분에는 객체의 해시코드 값을 출력한다.(int 값 주소값 리턴, Integer 클래스에서 제공하는 toHexString()메소드 활용)
toString 메소드를 Overriding을 하여 직접 구현해보고 toString()의 원본 값과 비교해보자.
[EX]
package part12;
public class ToString {
public static void main(String[] args) {
ToString thisObject = new ToString();
thisObject.toStringMethod(thisObject);
}
public void toStringMethod(Object obj) {
System.out.println(obj);
System.out.println(obj.toString());
System.out.println("plus " + obj);
}
public String toString() {
return "ToString class";
}
}
ToString class
ToString class
plus ToString class
==> 위의 코드를 보게 되면 toString()메소드의 선언부를 보면 접근 제어자는 public이고, 리턴 타입은 String이다.
패키지를 포함한 클래스 이름과 골뱅이, hashCode()메소드(주소값)를 수행한 결과가 나오지 않는다. toString()메소드를 오버라이딩을 하여 나만의 함수로 만들었기 때문이다.
◼️ 3-1. eqauls()
두 개의 값이 같은지 다른지를 비교하는 연산자는 ==와 != 였다. ==는 같은지를 비교하고, !=다른지를 비교하는 연산자이다.
두 연산자의 결과는 모두 true나 faluse의 boolean 타입의 값이다. 하지만 이 연산자들은 기본 자료형에서만 사용 가능하다.
==> ==와 != 는 기본 자료형에서만 사용 가능하고 참조자료형에서는 사용 불가하다.
[EX]
package part12;
import part8.MemberDTO;
public class Equals {
public static void main(String[] args) {
Equals thisObject = new Equals();
thisObject.equalMethod();
}
public void equalMethod() {
MemberDTO obj1= new MemberDTO("Sangmin");
MemberDTO obj2= new MemberDTO("Sangmin");
if(obj1==obj2) {
System.out.println("obj1 and obj2 is same");
}
else {
System.out.println("obj1 and obj2 is different");
}
}
}
obj1 and obj2 is different
==> 결과를 보게 되면 두 객체는 다르다고 나온다.
그 이유는, 두 객체는 각각의 생성자를 사용하여 만들었기 때문에 주소값이 다르다. 하지만 그 안에 있는 속성값들은 name은 "Sangmin", phone과 email은 모두 null이므로 동일하다. 그래서 이와 같이 참조 자료형은 equals()라는 메소드를 사용하여 두 객체를 비교해야 한다.
[EX]
package part12;
import part8.MemberDTO;
public class Equals {
public static void main(String[] args) {
Equals thisObject = new Equals();
// thisObject.equalMethod();
thisObject.equalMethod2();
}
public void equalMethod() {
MemberDTO obj1= new MemberDTO("Sangmin");
MemberDTO obj2= new MemberDTO("Sangmin");
if(obj1==obj2) {
System.out.println("obj1 and obj2 is same");
}
else {
System.out.println("obj1 and obj2 is different");
}
}
public void equalMethod2() {
MemberDTO obj1= new MemberDTO("Sangmin");
MemberDTO obj2= new MemberDTO("Sangmin");
if(obj1.equals(obj2)) {
System.out.println("obj1 and obj2 is same");
}
else {
System.out.println("obj1 and obj2 is different");
}
}
}
obj1 and obj2 is different
==> equals() 메소드로 비교하면 된다고 했는데 왜 안될까?
비교 대상 객체인 MemberDTO 클래스에서는 아직 equals()메소드를 Overriding하지 않으면 equals() 메소드에서는 hashCode()값을 비교한다. [ hashCode() 값은 해당 객체의 주소 값을 리턴한다.]
즉, 클래스의 인스턴스 변수값들이 같다고 하더라도, 서로 다른 생성자로 객체를 생성했으면 해시 코드(주소값)가 다르니 두 객체는 다르다는 결과가 나온것이다.
주소값을 비교하는게 아닌 값을 비교하기 위해서 MemberDTO 클래스에 equals()메소드를 Overriding하자.
[EX]
package part8;
public class MemberDTO {
public String name;
public String phone;
public String email;
public MemberDTO() {
// TODO Auto-generated constructor stub
}
public MemberDTO(String name) {
//이름만 알 때
this.name = name;
}
public MemberDTO(String name, String phone) {
//이름과 전화번호만 알 때
this.name = name;
this.phone = phone;
}
public MemberDTO(String name, String phone, String email) {
//이름과 전화번호, 이메일 알 때
this.name = name;
this.phone = phone;
this.email = email;
}
public boolean equals(Object obj) {
if(this == obj) return true; //주소가 같으므로 당연히 true
if(obj == null) return false; //obj가 null이므로 당연히 false
if(getClass() != obj.getClass()) return false; //클래스의 ㅈㅇ류가 다르므로 false
MemberDTO other = (MemberDTO) obj; //같은 클래스 이므로 형 변환 실
//아래 코드들은 각 인스턴스 변수가 같은지 비교하는 작
if(name == null) { //name이 null일때
if(other.name != null) return false; //비교 대상의 name이 null 이 아니면 false
}
else if(!name.equals(other.name)) return false; { //두 개의 name 값이 다르면 false
}
if(email == null) {
if(other.email != null) return false;
}
else if(!email.equals(other.email)) return false; {
}
if(phone == null) {
if(other.phone != null) return false;
}
else if(!phone.equals(other.phone)) return false; {
}
return true;
}
}
==> 위의 코드를 보게 되면 너무 어렵다고 생각할 것이다. 이것은 이클립스에서 자동으로 생성한 equals()메소드 이다.
equals()메소드를 Overriding 할때는 반드시 다섯가지의 조건을 만족시켜야한다.(자바 API 문서 참고)
- 재귀 : nul이 아닌 x라는 객체의 x.equals(x) 결과는 항상 true여야만 한다.
- 대칭 : null이 아닌 x와 y 객체가 있을 때 y.equals(x)가 true를 리턴했다면 x.equals(y)도 반드시 true를 리턴해야만 한다.
- 타동적 : null이 아닌 x,y,z가 있을 때 x.equals(y)가 true를 리턴하고, y.equals(z)가 true를 리턴하면, x.equals(y)의 결과는 항상 true이거나 항상 false여야만 한다.
- null과의 비교 : null이 아닌 x라는 객체의 x.equals(null) 결과는 항상 false 여야만한다.
eqauls() 메소드를 Overriding할 때에는 hashCode() 메소드도 같이 Overriding해야만 한다는 것이다.
==> equals()메소드를 Overriding해서 객체가 서로 같다고 이야기 할 수는 있겠지만, 그 값이 같다고 해서 그 객체의 주소 값이 같지는 않기 때문이다. 즉, equals()메소드의 결과가 true인데도 불구하고 hashCode()메소드(주소값)의 값은 다르게 된다.
같은 hashCode()메소드 결과를 갖도록 하려면 hashCode() 메소드도 Object 클래스에서 제공하는 그대로 사용하면 안되고 Overriding해서 사용해야한다.
[EX]
public int hashCode() {
final int prime = 32;
int result = 1;
result = prime * result +((email == null) ? 0 : email.hashCode());
result = prime * result +((name == null) ? 0 : name.hashCode());
result = prime * result +((phone == null) ? 0 : phone.hashCode());
return result;
}
[정리]
equals() 메소드는 값을 비교하는게 아니라 hashCode() 메소드를 이용해 주소값을 비교하는것이다.
예를들어 같은 클래스에서 객체 2개를 이름 다르게 생성하고 생성자의 변수로 같은 값을 주었는데 override 하지 않은 equal()로 비교한다면 서로 같지 않다고 나올 것이다. 왜냐하면 서로 같은 클래스에서 나온 객체이면 같은 값을 가졌는데, 주소값이 다르기 때문이다.
그렇기에 equal()메소드를 원하는데로 Overriding하여 사용하는 것이다.
또한 우리가 반드시 equals() 메소드를 Overriding하는 것은 아니다. 꼭 필요할때만 Overriding하면 된다.
위의 예처럼 객체 비교를 위해서 반드시 필요한 것이지만, 그렇지 않은 메소드만 있는 기능 위주의 클래스를 만들때는 필요없다.
◼️ 3-2. hashCode()
hashCode() 메소드는 기본적으로 객체의 메모리 주소를 16진수로 리턴한다. 만약 어떤 두 개의 객체가 서로 동일하다면, hashCode() 값은 무조건 동일해야만 한다.
==> hashCode() 메소드를 override하면, hashCode() 메소드도 override해서 동일한 결과가 나오도록 만들어야 한다.
hashCode() 메소드를 구현할 때 지켜야 할 약속
- 자바 어플리케이션이 수행되는 동안에 어떤 객체에 대해서 이 메소드가 호출될 때에는 항상 동일한 int 값을 리턴 해주어야 한다. 하지만, 자바를 실행할 때마다 같은 값이어야 할 필요는 없다.
- 어떤 두 개의 객체에 대하여 equals() 메소드를 사용하면 비교한 결과가 true일 경우에, 두 객체의 hashCode() 메소드를 호출하면 동일한 int 값을 리턴해야만 한다.
- 두 객체를 equals() 메소드를 사용하여 비교한 결과 false를 리턴했다고 해서, hashCode()메소드를 호출한 Int 값이 무조건 달라야 할 필요는 없다. 하지만, 이 경우에 서로 다른 int 값을 제공하면 hashtable 성능을 향샹 시키는데 도움이 된다.
'JAVA > 자바의신 1' 카테고리의 다른 글
14장 다 배운 것 같지만, 예외라는 중요한 것이 있어요.(예외 처리 try-catch) (1) | 2022.09.09 |
---|---|
13장 인터페이스와 추상클래스, enum (0) | 2022.09.09 |
11장 매번 만들기 귀찮은데 누가 만들어 놓은 거 쓸 수 없나요? (0) | 2022.09.08 |
10장 자바는 상속이라는 것이 있어요 (1) | 2022.09.07 |
9장 자바를 배우면 패키지와 접근 제어자는 꼭 알아야 해요 (0) | 2022.09.07 |