JAVA Object 클래스

Dev.Shinny·2022년 9월 28일
0

자바의 정석

목록 보기
13/18

java.lang 패키지

java.lang 패키지는 자바프로그래밍에 가장 기본이 되는 클래스들을 포함하고 있다. 때문에 import문 없이도 사용할 수 있다.

Object 클래스

모든 클래스의 최고 조상인 Object 클래스의 멤버들은 모든 클래스에서 바로 사용 가능하다. Object 클래스는 멤버 변수가 없고 오직 11개의 메서드만 가지고 있다. 재정의 할 수 없는 메서드는 final로 선언이 되어있다.

equals(Object obj)

  • 객체 자신(this)과 주어진 객체(obj)를 비교한다. 같으면 true, 다르면 false.
  • Object 클래스의 equals()는 객체의 주소(참조변수)를 비교한다.
  • 인스턴스 변수(iv)의 값을 비교하려면 equals()를 오버라이딩해야 한다.
class Value{
	int value;
    Value(int value){
    	this.value = value;
    }
    public boolean equals(Object obj) {
    	
    	if(!(obj instanceof Value)) return false;
    	
    	Value v = (Value)obj;
    	return this.value == v.value;
    }
}

class Value1{
	int value1;
	
	Value1(int value1){
		this.value1=value1;
	}
}

public class EqualsEx1 {

	public static void main(String[] args) {
		Value v1 = new Value(10);
		Value v2 = new Value(10);		
		System.out.println(v1.equals(v2)); // true
		
		Value1 v3 = new Value1(20);
		Value1 v4 = new Value1(20);
		System.out.println(v3.equals(v4)); // false	
	}
}

hashCode()

  • 객체의 해시코드(Hash code)를 반환하는 메서드
  • 객체의 주소를 int로 변환해서 반환
  • equals()를 오버라이딩하면, hashCode()도 오버라이딩해야 한다.
    equals()의 결과가 true인 두 객체의 해시코드는 같아야 하기 때문이다.
public class HashCode1 {
	public static void main(String[] args) {
		 String str1= new String("abc");
		 String str2= new String("abc");
		 
		 System.out.println(str1.equals(str2));
		 System.out.println(str1.hashCode());
		 System.out.println(str2.hashCode());
		 System.out.println(System.identityHashCode(str1));
		 System.out.println(System.identityHashCode(str2));		 		 
	}
}

String 클래스는 문자열의 내용이 같으면, 동일한 해시코드를 반환하도록 hashCode 메서드가 오버라딩되어 있기 때문에, 문자열의 내용이 같은 str1, str2에 대해 hashCode()를 호출하면 항상 동일한 해시코드값을 얻는다.

반면에 System.indentityHashCode(Object x)는 Object클래스의 hashCode 메서드처럼 객체의 주소값으로 해시코드를 작성하기 때문에 모든 객체에 대해 항상 다른 해시코드값을 반환할 것을 보장한다. 그래서 str1과 str2가 해시코드는 같지만 서로 다른 객체라는 것을 알 수 있다.

hashCode 메서드는 객체의 주소값으로 해시코드를 만들어 반환하기 때문에 32bit JVM에서는 서로 다른 두 객체의 주소값이 같을 수 없었지만 64bit JVM에서는 8byte 주소값으로 해시코드(4byte)르 만들기 때문에 해시코드가 중복될 수 있다.

toString()

객체를 문자열(String)으로 변환하기 위한 메서드

class Card{
	String kind;
	int number;
	
	Card(){
		this("SPADE", 1);
	}
	
	Card(String kind, int number){
		this.kind = kind;
		this.number=number;
	}
}

public class ToStringEx1 {
	public static void main(String[] args) {
	
		Card c1 = new Card();
		Card c2 = new Card();
		
		System.out.println(c1.toString());
		System.out.println(c2.toString());
	}
}
오버라이딩을 하지 않고 toString()을 호출하면, 클래스이름에 16진수의 해시코드를 얻게 된다.
public class ToStringEx2 {
	public static void main(String[] args) {
		String str = new String("KOREA");
		java.util.Date today = new java.util.Date();
		
		System.out.println(str);
		System.out.println(str.toString());
		System.out.println(today);
		System.out.println(today.toString());

	}
}

String 클래스와 Date클래스의 toString()을 호출하였더니 클래스이름과 해시코드 대신 다른 결과가 출력되었다. String 클래스의 toString()은 String인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩 되어있고, Date클래스의 경우, Date인스턴스가 갖고 있는 날짜와 시간을 문자열로 반환하여 반환하도록 오버라이딩되어 있기 때문이다.

class Card{
	String kind;
	int number;
	
	Card(){
		this("SPADE", 1);
	}
	
	Card(String kind, int number){
		this.kind = kind;
		this.number=number;
	}
	
	public String toString() {
		return "kind : "+kind+", number : "+number;
	}
}

public class ToStringEx3 {

	public static void main(String[] args) {

		Card c1 = new Card("Heart", 9);
		Card c2 = new Card();
		System.out.println(c1.toString());
		System.out.println(c2.toString());
	}
}

앞서 작생했던 코드의 toString을 오버라이딩하여 인스턴스 변수의 값을 문자열로 변환하여 반환하도록 하였다. Object 클래스에 정의된 toString()의 접근제어자가 public임으로 Card클래스의 toString()의 접근제어자도 public이어야한다.

clone()

  • 자신을 복제하여 새로운 인스턴스 생성
  • 단순 인스턴스 값만 복사하여, 배열의 경우 같은 배열의 주소를 갖기 때문에 기존 인스턴스에 영향을 미친다.
    → clone 메서드를 오버라이딩하여 해결
  • Cloneable 인터페이스를 구현한 클래스에서만 호출할 수 있다.(구현 클래스의 인스턴스만 복제 가능)
  • 반드시 예외처리를 해주어야 한다.
class Point implements Cloneable{
	int x, y;
	Point(int x, int y){
		this.x = x;
		this.y = y;
	}
	public String toString() {
		return "x = "+x+", y = "+y;
	}
	
	// 접근제어자 public
	public Object clone() {
		Object obj = null;
		try {
			// 부모클래스의 clone()을 호
			obj=super.clone();
		} catch (CloneNotSupportedException e) {}
		return obj;
	}
}

public class CloneEx1 {
	public static void main(String[] args) {
		Point original = new Point(3,5);
		Point copy = (Point)original.clone();
		System.out.println(original);
		System.out.println(copy);		
	}
}

공변 반환타입

JDK1.5부터 '공변 반환타입'이 추가되었다.
오버라이딩 시 부모 메서드의 반환타입을 자식 클래스의 타입으로 변경을 허용한다.

// 위 코드를 공변 반환타입을 이용해 변경
// 반환 타입 Object → Point
public Point clone(){
	Object obj = null;
    try{
    	obj = super.clone();
    } catch ( CloneNotSupportedException e ) {}
    return (Point) obj; // Point 타입으로 형변환 
}
...
//Point copy = (Point)original.clone();
Point copy = original.clone();
...

얕은 복사와 깊은 복사

clone()은 단순히 객체에 저장된 값을 그대로 복제할 뿐, 객체가 참조하고 있는 객체까지는 복제하지 않는다.

import java.util.Arrays;

public class CloneEx2 {
	public static void main(String[] args) {
		int arr [] = {1,2,3,4,5};
		int arrCopy [] = arr.clone();
		arrCopy[0] = 6;
		
		System.out.println(Arrays.toString(arr));
		System.out.println(Arrays.toString(arrCopy));
	}
}

Tip. 배열을 복사하는 또 다른 방법
int [] arrClone = new int[arr.length];
System.arraycopy(arr, 0, arrClone, 0, arr.length);
        

위 코드처럼 기본형 배열인 경우 아무런 문제가 없지만, 객체배열을 clone()으로 복제하는 경우 원본과 복제본이 같은 객체를 공유하므로 완전한 복제라 하기 어렵다. 이러한 복제를 '얕은 복사(shallow copy)'라고 한다. 얕은 복사에서는 원본을 변경하면 복사본도 영향을 받는다.

반면에 원본이 참조하는 객체까지 복제하는 것을 '깊은 복사(deep copy)'라고 하며, 깊은 복사에서는 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않는다.

class Point {
	int x,y;
    
	Point(int x, int y){
		this.x=x;
		this.y=y;
	}
}

class Circle implements Cloneable{
	Point p;
	double r;
	
	Circle(Point p, double r){
		this.p =p;
		this.r=r;
	}
	(1) 얕은 복사
	public Circle shallowCopy() {
		
		Object obj=null;
		
		try {
			obj = super.clone();
		} catch (CloneNotSupportedException e) {}
		return (Circle)obj;
	}
	(2) 깊은 복사
	public Circle deepCopy() {
		Object obj =null;
		
		try {
			obj = super.clone();
		} catch (CloneNotSupportedException e) {}
		
		Circle c = (Circle)obj;
		c.p=new Point(this.p.x, this.p.y);
		
		return c;
	}
}

getClass()

  • 자신이 속한 클래스의 Class 객체를 반환하는 메서드
  • Class 객체는 이름이 'Class'인 클래스의 객체이다.
  • 파일 형태로 저장되어 있는 클래스를 읽어서 Class 클래스에 정의된 형식으로 변환.

Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스당 1개만 존재한다. 클래스 파일이 '클래스 로더(ClassLoader)'에 의해서 메모리에 올라갈 떄, 자동으로 생성된다. 클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드한다. 먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고, 있으면 객체의 참조를 반환하고 없으면 클래스 패스(classpath)에 지정된 경로를 따라 클래스 파일을 찾는다. 못 찾으면 'ClassNotFoundException'이 발생하고, 찾으면 해당 클래스 파일을 읽어 Class 객체로 변환한다.

final class Cards{
	String kind;
	int num;
	
	Cards(){
		this("SPADE", 2);
	}
	
	Cards(String kind, int num){
		this.kind = kind;
		this.num = num;
	}
	
	public String toString() {
		return kind+":"+num;
	}
}

public class ClassEx1 {
	public static void main(String[] args) throws Exception {
		
		Cards c = new Cards("HEART", 3);
		Cards c2 = Cards.class.newInstance();
		
		Class cObj = c.getClass();
		
		System.out.println(c);
		System.out.println(c2);
		System.out.println(cObj.getName());
		System.out.println(cObj.toGenericString());
		System.out.println(cObj.toString());					
	}
}
profile
Hello I'm Shinny. A developer who try to enjoy the challenge.

0개의 댓글