Java 프로그래밍, Object 클래스
java.lang.Object 클래스는 자바의 모든 클래스의 최고 조상 클래스이며 자바의 모든 오브젝트(배열 객체 포함)는 직간접적으로 Object 클래스의 속성과 메소드를 상속하고 있다. 개발자는 Object 클래스의 메소드를 반드시 사용해야 하는 것은 아니지만 만약 사용해야 한다면 작성하고 있는 클래스에서 오버라이딩해야 하는 경우가 많다. 여기서는 Object 클래스에 정의되어 있는 주요 메소드는 어떤 것이 있으며 그 기능은 무엇인지 그리고 오버라이딩은 어떻게 할 것인지를 알아보기로 한다
Object 클래스의 주요 메소드 https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html
- protected Object clone() throws CloneNotSupportedException
- public boolean equals(Object obj)
- protected void finalize() throws Throwable
- public final Class<?> getClass()
- public int hashCode()
- public String toString()
위의 메소드 외에 Object 클래스에는 아래와 같이 쓰레드(Thread)와 관련한 메소드도 있는데, 이들은 쓰레드 부분에서 다룬다
- public final void notify()
- public final void notifyAll()
- public final void wait()
- public final void wait(long timeout)
- public final void wait(long timeout, int nanos)
protected Object clone() throws CloneNotSupportedException
clone 메소드를 호출하면 호출된 객체와 동일한 객체를 한개 더 복사하여 내용이 동일한 객체를 생성하고 그 참조를 리턴한다.
clone 메소드가 실행되도록 하려면 복사대상 객체의 클래스를 정의할 때 Cloneable 인터페이스를 구현해야 한다. 그렇지 않은 상태에서 clone 메소드를 호출하면 CloneNotSupportedException 오류가 발생한다.
또 한가지 알아야 할 점은 clone 메소드를 이용하여 객체를 복사할 때 깊은 복사(Deep Copy)가 아닌 얕은 복사(Shallow copy)가 수행된다는 점이다. 객체의 내부에 또 다른 객체의 참조가 멤버변수로 포함되어 있는 경우에 얕은 복사를 하면 객체 내부에 포함된 객체의 참조만 복사되므로 완전한 복사라고 할 수가 없다. 참조복사가 아닌 객체 내용 복사를 원한다면 clone 메소드를 오버라이드하여 깊은 복사가 되도록 코드를 변경해야 한다
clone 메소드를 이용한 얕은 복사(Shallow Copy)의 예
public class ObjectCloneDemo implements Cloneable // clone() 호출시 필수 { int num; int price; int qty; ObjectCloneDemo(int num, int price, int qty) { this.num = num; this.price = price; this.qty = qty; } public static void main(String[] args) { ObjectCloneDemo original = new ObjectCloneDemo(10, 300000, 5); ObjectCloneDemo newObj = null; try { newObj = (ObjectCloneDemo)original.clone(); // 객체 복사 (얕은 복사) } catch (CloneNotSupportedException e) { e.printStackTrace(); } //newObj.num = 20; //newObj.price = 20000; //newObj.qty = 3; System.out.printf("원본객체 번호:%d, 가격:%d, 수량:%d %n", original.num, original.price, original.qty); System.out.printf("사본객체 번호:%d, 가격:%d, 수량:%d %n", newObj.num, newObj.price, newObj.qty); } }
clone 메소드를 이용한 깊은 복사(Deep Copy)의 예
public class ObjectCloneDemo implements Cloneable { // 참조변수를 복사하면 참조만 복사(Shallow Copy)되므로 깊은 복사를 할 필요가 있다 // 깊은 복사(Deep Copy)를 하려면 clone 메소드를 오버라이드해야 한다 Item item; ObjectCloneDemo(){} ObjectCloneDemo(Item item) { this.item = item; } @Override // 깊은 복사가 되도록 오버라이딩 protected Object clone() throws CloneNotSupportedException { ObjectCloneDemo ocd = new ObjectCloneDemo(); ocd.item = new Item(this.item.num, this.item.name); return ocd; } public static void main(String[] args) { Item item = new Item(10, "헤드셋 Gold"); ObjectCloneDemo original = new ObjectCloneDemo(item); ObjectCloneDemo newObj = null; try { newObj = (ObjectCloneDemo)original.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } System.out.printf("원본객체 번호:%d, 이름:%s %n", original.item.num, original.item.name); System.out.printf("사본객체 번호:%d, 이름:%s %n", original.item.num, original.item.name); } } class Item { int num; String name; Item(int num, String name) { this.num = num; this.name = name; } }
public boolean equals(Object obj)
Object 클래스의 equals() 메소드는 2개의 객체의 동일성 여부를 확인하여 두 객체가 동일한면 true, 아니면 false를 리턴한다.
주의할 점은 Object 클래스 안에 정의된 equals() 메소드는 동등비교 연산자(==)를 사용하여 두 객체의 참조를 비교한다는 것이다. 그러므로 객체의 참조가 동일한 경우에만 true 가 리턴된다. 객체 내부가 동일한지는 확인하지 않고 객체의 참조가 동일한지 비교하는 것은 실제의 내용비교가 아니라고 할 수 있다. 객체의 내용비교를 위해서는 개발자가 equals() 메소드를 오버라이딩해야 한다.
Java 스펙에는 equals() 메소드로 비교하여 true가 리턴되는 경우에는 두 객체의 hashCode() 메소드도 동일한 정수를 리턴해야 한다고 규정하고 있기 때문에 equals() 메소드를 오버라이드할 때는 hashCode() 메소드도 오버라이드해야 한다. 자바의 이러한 스펙때문에 Eclipse 같은 개발환경에서는 equals()와 hashCode()를 동시에 자동으로 오버라이드할 수 있는 방법을 제공하고 있다
https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#equals-java.lang.Object-
equals() 메소드를 이용하여 두 객체의 참조를 비교하는 경우
public class ObjectEqualsDemo implements Cloneable { public static void main(String[] args) { // 2개의 객체의 내용이 완전히 동일한 경우 Item item1 = new Item(10, "Tablet PC"); Item item2 = new Item(10, "Tablet PC"); // Object 클래스의 equals()는 두 객체의 동일성을 확인할 때 동등비교 연산자(==)를 이용한다 boolean res = item1.equals(item2); System.out.printf("동일성 여부 : %b %n", res); // false } } class Item { int num; String name; Item(){} Item(int num, String name) { this.num = num; this.name = name; } }
equals() 메소드를 이용하여 두 객체의 내용을 비교하는 경우 (equals() 오버라이딩)
equals()로 비교하여 true 가 리턴되는 두 객체는 hashCode()도 동일한 정수를 리턴해야 한다는 스펙을 따르기 위해서 Eclipse 가 제공하는 equals() hashCode() 자동 오버라이딩 기능을 사용하여 아래와 같은 코드를 작성할 수 있다
public class ObjectEqualsDemo implements Cloneable { public static void main(String[] args) { // 2개의 객체의 내용이 완전히 동일한 경우 Item item1 = new Item(10, "Tablet PC"); Item item2 = new Item(10, "Tablet PC"); // Item 클래스의 equals()는 두 객체의 내용을 비교하도록 오버라이드되었다 boolean res = item1.equals(item2); System.out.printf("동일성 여부 : %b %n", res); // true } } class Item { int num; String name; Item(){} Item(int num, String name) { this.num = num; this.name = name; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + num; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Item other = (Item) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (num != other.num) return false; return true; } }
protected void finalize() throws Throwable
finalize() 메소드는 가비지 컬렉터에 의해서 호출된다. 프로그램 내에 더 이상 해당 객체의 참조가 없다면 그 객체는 더 이상 사용되지 않는다는 의미이므로 Heap Memory 를 점유하고 존재할 필요가 없다. 그래서 가비지 컬렉터를 해당 객체를 메모리에서 제거할 때 해당 객체의 finalize()를 호출한다.
또 객체가 사용하던 리소스를 해제하기 위해서라면 개발자에 의해서도 명시적으로 호출할 수도 있는데 이런 경우에는 finalize()를 오버라이드하여 어떤 리소스를 해제하는 작업을 정의할 수도 있다
public final Class<?> getClass()
getClass() 메소드는 호출된 객체의 클래스에 대한 수많은 정보를 Class 라는 클래스의 인스턴스에 저장하여 리턴한다. 클래스의 이름, 메소드의 이름과 파라미터, 생성자에 대한 정보 등을 약 50여가지의 메소드를 통해 제공한다.
public int hashCode()
hashCode() 메소드는 객체가 저장되어 있는 메모리의 주소를 10진수로 리턴한다
Java 스펙에는 equals()를 이용하여 두 객체를 비교했을 때 true 라면 이들 두 객체는 hashCode()를 호출했을 때 동일한 정수가 리턴되어야 한다는 규칙이 있으며 이를 지키지 않으면 객체의 동일성 비교시에 기대와 다른 결과가 나올 수 있다. 특히 HashMap, Hashtable 등 'Hash' 라는 이름이 포함된 클래스를 사용할 때는 이 규칙을 지키지 않으면 객체의 내용 비교시에 내용이 완전히 동일한 객체라 할지라도 다른 객체로 인식되는 것을 경험할 수 있다
public String toString()
Object 클래스의 toString() 메소드는 클래스명과 객체의 메모리 주소를 16진수 문자열 형태로 리턴한다. 개발자에게는 toString()의 디폴트 기능이 별로 쓸모있는 기능이 아니라서 디버깅 목적으로 오버라이드하는 경우가 많다.
toString() / hashCode()의 출력 예
public class ObjectEqualsDemo implements Cloneable { public static void main(String[] args) { Item item1 = new Item(10, "Tablet PC"); // hashCode는 객체의 메모리 주소를 10진수로 리턴하고, // toString() 은 클래스명과 객체의 주소를 16진수로 리턴한다 // hashCode():505887646, toString():Item@1e273b9e System.out.printf("hashCode():%d, toString():%s %n", item1.hashCode(), item1.toString()); } }