티스토리 뷰

programming language/Java

[자바] Object 클래스의 메서드

무니웜테일패드풋프롱스 2020. 4. 4. 09:39

* 본문은 자바의 정석 chapter 9장을 정리한 내용입니다

Object 클래스는 모든 클래스의 최고 조상이다. 따라서 Object 클래스의 멤버들은 모든 클래스에서 바로 사용 가능하다.

그렇다면 Object 클래스의 메서드에는 어떠한 것들이 있는지 알아보자!

 

 

protected Object clone() //객체 자신의 복사본 반환
public boolean equals(Object obj) // 객체 자신과 객체 obj가 같은 객체인지 알려준다
protected void finalize() //객체 소멸 시 가비지 컬렉터에 의해 자동 호출.
public Class getClass() //객체 자신의 클래스 정보를 담고 있는 Class 인스턴스 반환
public int hashCode() //객체 자신의 해시코드 반환
public String toString() //객체 자신의 정보를 문자열로 반환
public void notify() //객체 자신을 사용하려고 기다리는 쓰레드를 하나만 깨운다
public void notifyAll() //객체 자신을 사용하려고 기다리는 모든 쓰레드를 깨운다.

public void wait()           //다른 쓰레드가 notify()나 notifyAll()을 호출할 때 까지 현재 쓰레드를
public void wait(long timeout) //무한히 혹은 지정된 시간(timeout,nanos) 동안 기다리게 한다.
public void wait (long timeout, int nanos)  

 

이 중 중요한 것 몇개를 살펴보도록 하자

 

equals(Object obj)

매개변수로 객체의 참조변수를 받아 비교하여 그 결과를 boolean값으로 알려준다.

 

- Object 클래스에 정의된 equals 메서드의 내용  : 즉, 두 객체를 참조변수의 값으로 비교한다. 

public boolean equals(Object obj) {
    return (this==obj);  }

 

public class test {
	public static void main(String[] args) {
		Value v1 = new Value(10);
		Value v2= new Value(10);
		if(v1.equals(v2)) {
			System.out.println("v1==v2");
		}
		else {
			System.out.println("v1!=v2");
		}
		v2=v1;
		if(v1.equals(v2)) {
			System.out.println("v1==v2");
		}
		else {
			System.out.println("v1!=v2");
		}	
	}
    
	static class Value {
		int value;	
		Value(int value){
			this.value=value;
		}
	}
}

이 코드의 결과에서 알 수 있듯 아무리 멤버 변수의 값이 같더라도 서로 다른 인스턴스라면, equals에서 false의 결과가 나온다. 즉 두개의 참조변수가 같은 객체를 참조하고 있는지 (주소값 비교) 만 판단하는 것이다.

만약 이를 주소값의 비교가 아닌 저장내용의 비교로 바꾸고 싶다면 오버라이딩을 하면 된다.

 

-equal 메서드 오버라이딩

public boolean equals(Object obj) {
		if(obj!=null && obj instanceof Value) {
			return value == ((Value)obj).value; //vaule값 참조를 위해 형변환 
		}
		else
			return false;
	}

String 클래스의 equal 메서드 역시, 이런식으로 오버라이딩 되어있는 것이다.

 

hashCode() 

이 메서드는 해싱기법에 사용되는 해시 함수를 구현한 것이다. 해시함수는 찾고자 하는 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시코드를 반환한다. 

Object 클래스에 정의된 hashCode 메서드는, 객체의 주소값을 이용하여 해시코드를 만들어 반환하므로, 서로 다른 두 객체는 다른 해시코드를 가지게 된다.

만약 두 객체에 대하여 같은 해시코드를 부여하고 싶다면, 오버라이딩을 해야한다 

String 클래스는 문자열의 내용이 같으면, 동일한 해시코드를 반환하도록 hashCode메서드가 오버라이딩 되어있다. 

String str1 = new String(abc);
String str2 =new String(abc);
		
System.out.println(str1.hashCode()); //String 문자열의 내용이 동일하면 
System.out.println(str2.hashCode()); //동일한 해시코드 반환 
System.out.println(System.identityHashCode(str1)); //객체의 주소값으로 해시코드 반환
System.out.println(System.identityHashCode(str2)); 
96354  //결과 
96354
366712642
1829164700

 

toString()

이 메서드는 인스턴스에 대한 정보를 문자열(String)로 제공할 목적으로 정의되었다. 여기서 인스턴스에 대한 정보라는 것은, 인스턴스 변수에 저장된 값들을 문자열로 표현한다는 뜻이다.

 

Object클래스에 저장된 toString() 메서드는 이러한데, 이는 16진수의 해시코드를 반환한다. 따라서 오버라이딩이 필요하다.

public String toString() {
	return getClass().getName()+"@"+Integer.toHexString(hashCode());}

String 클래스의 toString()는 String 인스턴스가 갖고있는 문자열을 반환하도록 오버라이딩 되어있다. 

Date 클래스의 toString() 은 Date인스턴스가 가지고 있는 날짜와 시간을 문자열로 변환하여 반환하도록 오버라이딩 되어있다.

String str = new String("JAVA");
java.util.Date today = new java.util.Date();
System.out.println(str.toString());
System.out.println(today.toString());

주의할 점은, toString() 메서드를 오버라이딩 할 때 접근제어자는 무조건 public 이어야 한다. 왜냐하면, Object 클래스에 정의된 toString 메서드의 접근제어자가 public 이기 때문이다.

 

clone()

이 메서드는 자신을 복제하여 새로운 인스턴스를 생성하는 일을 한다. 어떤 인스턴스에 대해 작업을 할 때, 원래의 인스턴스는 보존하고, clone 메서드를 통해 새로운 인스턴스를 생성하여 작업을 하면, 작업 이전의 값이 보존되어 편리하다.

Object 클래스에 정의된 clone()은 단순히 인스턴스 변수의 값을 복사하기 때문에, 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다. (얕은 복사) 

예를들어 배열의 경우, 복제된 인스턴스도 같은 배열의 주소를 갖기 때문에, 복제된 인스턴스의 작업이 원래의 인스턴스에 영향을 미치게 된다. 

이를 위해서는 clone 메서드를 오버라이딩 하여, 새로운 배열을 생성 후, 배열의 내용을 복사하도록 해야한다. 

또한 Cloneable 인터페이스를 구현한 클래스에서만 clone()을 호출할 수 있으며,  clone()을 오버라이딩 하면서 접근제어자를 protected에서 public으로 변경해야한다. 그래야만 상속관계가 없는 다른 클래스에서 clone()을 호출할 수 있다.

아래는 얕은 복사의 예시이다.

public class test {
	public static void main(String[] args) {
		Circle c1 = new Circle(new Point(1,2),3);
		Circle c2 = c1.clone();
		c2.p.x=5; //클론된 c2의 인스턴스 변수인 p의 값을 바꾸면
		c2.p.y=10; //c1의 p의 값도 바뀐다는 것을 알 수 있다. 
		System.out.println(c1.toString());
	}
}

class Point implements Cloneable{ //Cloneable을 구현해야 clone이 가능 
	int x, y;
	Point(int x, int y){
		this.x=x;
		this.y=y;
	}
	public Point clone() { //상속관게가 없는 다른 class에서 호출 가능하도록 
		Object obj=null;
		try {
			obj=super.clone(); //object class의 clone메서드 결과값 반환 
		}catch(CloneNotSupportedException e) {}; //예외처리 필수 
		return (Point)obj; //공변환타입
	}
		
	public String toString() {
		return "x : " + x + ", y: "+y;
	}
}

class Circle implements Cloneable{
	Point p; 
	double r;
	Circle(Point p, double r){
		this.p= p;
		this.r=r;
	}
	public Circle clone() { //얕은 복사 
	    Object obj = null;
		try {
			obj=super.clone();
		}catch(CloneNotSupportedException e) {};
		return (Circle) obj;
	}
	public String toString() {
		return p.toString()+ ", r: "+ r;
	}
}

위의 예시를 통해, 참조변수를 Point를 가지고 있는 인스턴스를 복제했을 때, 복제된 인스턴스와 본체 인스턴스 모두 같은 Point 인스턴스를 가리키고 있음을 알 수 있다. 따라서 복제된 인스턴스의 값에 대한 처리를 했을 때 본체 인스턴스의 값도 변경되는 것이다. 

이를 위해서는 '깊은 복사'가 필요하다. 

public Circle clone() { //깊은 복사 
	Object obj = null;
	try {
		obj=super.clone();
	}catch(CloneNotSupportedException e) {};
	Circle c = (Circle) obj;
	c.p=new Point(p.x, p.y);
	return c // return (Circle) obj 해도 같은 결과 (깊은 복사) 가 나온다.
             // 위에서 c = (Circle)obj 를 했기 때문에, obj의 p 값도 c처럼 변경된다.
}

 이렇게 깊은 복사를 통해서 복제 인스턴스에 새로운 참조면수 p를 선언하고, 본체 인스턴스의 참조변수 p의 변수값을 저장한다. 따라서 본체 인스턴스와 복제된 인스턴스가 같은 참조변수 p를 가지지 않도록 하여,

복제된 인스턴스의 Point 참조변수를 처리해도, 본체 인스턴스의 Point 참조변수의 값은 변함이 없다.

 

getClass()

이 메서드는 자신이 속한 클래스의 Class 객체를 반환하는 메서드이다. 

Class 객체는 아래와 같이 정의되어 있다.

public final Class<?> getClass(){ ...생략}

Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스당 1개만 존재한다. 그리고, 클래스 파일이 'Class Lpader'에 의해 메모리에 올라갈 때, 자동으로 생성된다. 

'클래스 로더' 는 , 실행 시 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다. 먼저

1)기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고, 

2)있으면 객체의 참조를 반환 하고

3)없으면 클래스 패스(classPath)에 지정된 경로를 따라서 클래스 파일을 찾는다.

4)찾으면 해당 클래스 파일을 읽어서 Class 객체로 변환하고 

5)못찾으면 ClassNotFoundException이 발생한다.

 즉, 파일의 형태로 저장되어있는 클래스를  읽어서 Class클래스에 정의된 형식으로 변환한다.

따라서 Class 객체란, 클래스 파일을 읽어서 사용하기 편한 형태로 저장해 놓은 것이다. 

Class cObj0 = new Circle().getClass(); //생성된 객체로부터 얻기
Class cObj1 = Circle.class; // 클래스 리터럴(*.class)로부터 얻기
Class CObj2 = Class.forName("Circle"); // 클래스 이름으로부터 얻기 
System.out.println(cObj0.getName()); //패키지 이름, 클래스 이름 반환 

위와 같은 방법들로 클래스 객체에 대한 참조를 얻을 수 있다.

Circle c3 = Circle.class.newInstance(); //예외처리 필수 

클래스 객체에 대한 참조를 반환하므로, 이렇게 인스턴스를 생성 할 수 도 있다. 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함