Object 클래스 equals, clone

gustjtmd·2022년 2월 2일
0

Java

목록 보기
15/40

인스턴스의 비교: equals 메소드

== 연산자는 참조변수의 참조 값을 비교한다
따라서 서로 다른 두 인스턴스의 내용을 비교하려면 별도의 방법을 사용해야 한다.

class INum{
    private int num;

    public INum(int num){
        this.num = num;
    }

    @Override
    public boolean equals(Object obj){
        if(this.num == ((INum)obj).num)
            return true;
        else
            return false;
    }
}
public class ObjectEquality {
    public static void main(String[] args) {
        INum num1 = new INum(10);
        INum num2 = new INum(20);
        INum num3 = new INum(10);

        if(num1.equals(num2))
            System.out.println("num1 num2 내용 동일하다.");
        else
            System.out.println("num1 num2 내용 다르다");

        if(num1.equals(num3))
            System.out.println("num1 num3 내용 동일하다.");
        else
            System.out.println("num1 num3 내용 다르다");

        if(num1 == num3)
            System.out.println("num1 num3 내용 동일하다");
        else
            System.out.println("num1 num3 내용 다르다");
    }
}


num1 num2 내용 다르다
num1 num3 내용 동일하다.
num1 num3 내용 다르다


-------------------------------------------------------------------------

위 코드에서 INum 클래스는 Object 클래스의 equals 메소드를 다음과 같이 오버라이딩 하였다
클래스 내에 인스턴스 변수의 내용을 비교하여 결과에 따라 true 또는 false 반환하도록.

@Override
    public boolean equals(Object obj){
        if(this.num == ((INum)obj).num)
            return true;
        else
            return false;
    }
    
이렇게 두 인스턴스의 내용 비교 결과인 true, false 반환 조건은 해당 클래스를 정의하는
프로그래머가 결정해야 한다 

그리고 그 결정 사항을 equals 메소드의 오버라이딩을 통해서 반환해야한다.

Object 클래스의 equals 메소드는 == 연산자와 마찬가지로 참조변수의 참조값을 비교하도록 
정의되어 있는데 == 연산을 통해서 참조값 비교가 가능하니 

equals 메소드의 호출을 통해 참조 값을 비교할 필요는 없다
즉 equals 메소드는 내용 비교가 이뤄지도록 오버라이딩 하라고 존재하는 메소드이다.
자바에서 제공하는 표준 클래스의 경우 equals 메소드가 내용 비교를 하도록 이미 오버라이딩
되어있는 경우가 많은데
대표적인 예가 String 클래스이다.

public class StringEquality {
    public static void main(String[] args) {
        String str1 = new String("So Simple");
        String str2 = new String("So Simple");

        // 참조 대상을 비교하는 if ~ else 문
        if(str1 == str2)
            System.out.println("str1 str2 참조 대상 동일하다.");
        else
            System.out.println("str1 str2 참조 대상 다르다.");

        //두 인스턴스를 비교하는 if ~ else 문
        if(str1.equals(str2))
            System.out.println("str1 str2 참조 대상 동일하다.");
        else
            System.out.println("str1 str2 참조 대상 다르다");
    }
}

str1 str2 참조 대상 다르다.
str1 str2 참조 대상 동일하다.

-------------------------------------------------------------------------

결론으로 두 인스턴스의 내용 비교를 원한다면 Object 클래스의 equals 메소드를 오버라이딩 하자
그리고 단순히 참조변수의 참조 값을 비교하려면 == 연산을 하자.

인스턴스 복사 : clone 메소드

Object 클래스에는 인스턴스의 복사를 위한 메소드가 정의되어 있다.

protected Object clone() throws CloneNotSupportedException

이 메소드가 호출되면 호출된 메소드가 속한 인스턴스의 복사본이 생성되고 이렇게 만들어진
복사본의 참조 값이 반환된다.

단 다음 인터페이스를 구현한 인스턴스를 대상으로만 위의 메소드를 호출할 수 있다

interface Cloneable
	-> 이 인터페이스를 구현한 클래스의 인스턴스만 clone 메소드 호출 가능
    
만약 Cloneable 인터페이스를 구현하지 않은 클래스의 인스턴스를 대상으로 clone 메소드를
호출하면 CloneNotSupportedException 예외가 발생한다
Cloneable 인터페이스의 구현에는 어떤 의미가 있을까?

"이 클래스의 인스턴스는 복사해도 됩니다 clone 메소드 호출이 가능합니다"

Cloneable 인터페이스는 '마커 인터페이스'이다. 정의해야 할 메소드가 존재하지 않는
복사를 해도 된다는 표식의 인터페이스이다.
코드로 확인해보자

class Point implements Cloneable{
    private int xPos;
    private int yPos;

    public Point(int x, int y){
        xPos = x;
        yPos = y;
    }

    public void showPosition(){
        System.out.printf("[%d, %d]",xPos,yPos);
        System.out.println();
    }

    @Override
    public Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
}
public class InstanceCloning {
    public static void main(String[] args) {
        Point org = new Point(3,5);
        Point cpy;

        try{
            cpy = (Point)org.clone();
            org.showPosition();
            cpy.showPosition();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
    }
}


[3, 5]
[3, 5]

-----------------------------------------------------------------------

실행 결과는 인스턴스의 복사가 정상적으로 이뤄졌음을 보여준다.
그리고 오버라이딩 과정에서 clone 메소드를 호출한 것이 전부인데 중요한건

오버라이딩 하면서 접근범위 protectedpublic으로 바꿔주었따

이렇듯 메소드 오버라이딩을 통해서 접근 범위를 넓히는 것은 가능하다

protected에서 public은 가능하지만

public에서 protected는 불가능하다.

이렇게 public으로 변경해준 이유는 

Object는 java.lang의 다른 패키지이기 떄문에 public으로 변경해주는 것이다.

clone 메소드의 얕은 복사와 깊은 복사

코드로 확인해보자

class Point implements Cloneable{
    private int xPos;
    private int yPos;

    public Point(int x, int y){
        xPos = x;
        yPos = y;
    }
    public void showPosition(){
        System.out.printf("[%d, %d]",xPos,yPos);
        System.out.println();
    }
    public void changePos(int x, int y){
        xPos = x;
        yPos = y;
    }

    @Override
    public Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
}

class Rectangle implements Cloneable{
    private Point upperLeft;    //좌측 상단 좌표
    private Point lowerRight;   //우측 하단 좌표

    public Rectangle(int x1, int y1, int x2, int y2){
        upperLeft = new Point(x1,y1);
        lowerRight = new Point(x2,y2);
    }

    public void changePos(int x1, int y1, int x2, int y2){   //좌표 정보 수정
        upperLeft.changePos(x1,y1);
        lowerRight.changePos(x2,y2);
    }

    @Override
    public Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
    public void showPosition(){ //직사각형 좌표 정보 출력
        System.out.print("좌측 상단 : ");
        upperLeft.showPosition();

        System.out.printf("우측 하단 : ");
        lowerRight.showPosition();
        System.out.println();
    }
}

public class ShallowCopy {
    public static void main(String[] args) {
        Rectangle org = new Rectangle(1,1,9,9);
        Rectangle cpy;

        try{
            cpy = (Rectangle)org.clone();  //인스턴스의 복사
            org.changePos(2,2,7,7);	//인스턴스의 좌표 정보 수정
            org.showPosition();
            cpy.showPosition();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
    }
}

좌측 상단 : [2, 2]
우측 하단 : [7, 7]

좌측 상단 : [2, 2]
우측 하단 : [7, 7]

------------------------------------------------------------------------------

위 코드에서 다음과 같이 인스턴스를 복사 한 후에 참조변수 org가 참조하는 인스턴스의 값을
수정하였다.

Rectangle org = new Rectangle(1,1,9,9);
        Rectangle cpy;

        try{
            cpy = (Rectangle)org.clone();  //인스턴스의 복사
            org.changePos(2,2,7,7);	//인스턴스의 좌표 정보 수정
            org.showPosition();
            cpy.showPosition();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        
따라서 두 인스턴스의 저장된 값이 달라야 하는데 같은 값이 나왔다

그 이유는Rectangle 인스턴스의 멤버는 다음과 같디 두 개의 참조변수로 이뤄져있다.

class Rectangle implements Cloneable{
    private Point upperLeft;    //좌측 상단 좌표
    private Point lowerRight;   //우측 하단 좌표
}

따라서 복사 과정에서 참조변수가 지니는 참조 값이 그대로 새 인스턴스에 복사가 된다
결국 clone의 호출로 만들어진 원본과 복사본의 관계는 다음과 같고 이를 '얕은 복사'라 한다

그러면 upperLeft와 lowerRight가 참조하는 Point 인스턴스까지 복사가 이뤄진 형태의
복사를 하려면 어떻게 해야할까?

clone 메소드를 다음과 같이 오버라이딩 해줘야 한다.

    @Override
    public Object clone() throws CloneNotSupportedException{
        //Object 클래스의 clone 메소드 호출을 통한 복사본 생성
        Rectangle copy = (Rectangle)super.clone();
        
        //깊은 복사의 형태로 복사본을 완성
        copy.upperLeft = (Point) upperLeft.clone();
        copy.lowerRight = (Point) lowerRight.clone();
        
        //완성된 복사본의 참조 값 반환
        return copy;
    }
    
결과

좌측 상단 : [2, 2]
우측 하단 : [7, 7]

좌측 상단 : [1, 1]
우측 하단 : [9, 9]
profile
반갑습니다

0개의 댓글