여기서 말하는 복사의 대상은 바로 '객체'다.

객체를 복사하는데 있어 객체의 '무엇'을 복사하느냐가 깊은 복사와 얕은 복사를 나누는 기준이 되는데 그 대상은 각각 다음과 같다.

Deep Copy Shallow Copy
객체 자체를 복제 (clone) 하여 새 객체 생성 객체의 주소값만 복사

그러면 코드 상에서는 각각 어떻게 다른 결과물을 만들어 내는지 한번 보기로 한다.

우선, Car라고 하는 클래스를 하나 정의하고, 다음과 같이 구현해 보자.


public class Car {
    private String name;
    private int price;
    private String color;

    public Car() {

    }

    public Car(String name) {
        this.name = name;
    }

    public Car(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public Car(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public Car(String name, int price, String color) {
        this.name = name;
        this.price = price;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

그리고 메인 함수에서 이 클래스의 인스턴스를 하나 생성한다.

Car c1 = new Car("Lamborghini", 1000000000, "Yellow");

Shallow copy (얕은 복사)

얕은 복사를 하려면 새 인스턴스를 생성하고, 그 인스턴스의 값으로 위에 생성한 c1 인스턴스를 할당해 주면 된다.

Car c2 = c1;

여기서 c2 인스턴스의 이름을 Lamborghini에서 Maserati로 바꿔 보고, 그 이후 결과값을 한번 조회해 본다.

c2.setName("Maserati");
System.out.println(c1.getName());
System.out.println(c2.getName());

// 두 인스턴스의 해시코드 값도 동일하게 출력되어 나온다. 즉 가리키는 주소값이 같은 객체라는 뜻.
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());

실행해 보니 c2 인스턴스의 이름이 바뀌긴 했는데, c1 인스턴스의 이름까지 바뀌는 현상이 발생했다. 왜일까?

그것은 바로, 맨 위에서 언급한 것처럼 이 경우에는 c1 객체의 주소값을 복사하여 c2 인스턴스를 만들었기 때문이다. 즉 두 인스턴스는 힙메모리 상에서 같은 메모리 주소를 참조하고 있으며, 따라서 그 메모리 주소가 갖는 값이 변경되면 그 영향이 c1, c2 둘에게 다 간다.

그렇다면 복사한 인스턴스의 값이 변경되어도 기존 인스턴스에는 영향이 없게 하려면 어떻게 해야 할까? 여기서 등장하는 개념이 '깊은 복사'다.

Deep copy (깊은 복사)

깊은 복사는 객체를 복사하되 이를 별개의 메모리 공간에 할당하는 것이다. 자바에서는 이를 clone (복제)한다고 표현하는데, 이를 위해서는 깊은 복사의 대상이 clone이 가능하도록 Cloneable 인터페이스를 구현하고 있어야 한다. 따라서 위에서 선언한 Car 클래스를 다음과 같이 수정해 본다.

public class Car implements Cloneable {
    ...

    @Override
    protected Car clone() throws CloneNotSupportedException {
        return (Car) super.clone();
    }
}

새로 추가된 메소드는, Cloneable 인터페이스가 갖고 있는 clone() 메소드를 오버라이딩 한 것으로, Car 타입의 인스턴스를 복제하여 리턴한다는 내용이다. 이 메소드를 호출하려면 다음과 같이 하면 된다.

// deep copy
try {
    Car c3 = c1.clone();
    c3.setName("BMW");

    // c1과 c3의 이름이 이번에는 각각 다르게 출력된다. 즉 한쪽의 변화가 다른 한쪽에 영향을 주지 않는다.
    System.out.println(c1.getName());
    System.out.println(c3.getName());

    // 두 해시코드의 값이 다르게 나온다. 즉 두 인스턴스는 각기 다른 주소값을 갖는다.
    System.out.println(c1.hashCode());
    System.out.println(c3.hashCode());
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

clone() 메소드가 CloneNotSupportedException을 던지기 때문에, 이 메소드 실행은 try-catch 구문 내에서 한다.

실행해 보면 한쪽 인스턴스의 값 변화가 다른 한쪽 인스턴스의 값에 영향을 주지 않고 있으며, 각 인스턴스가 가리키고 있는 주소값이 다른 것을 알 수 있다.

반응형

출처: https://projecteuler.net/problem=1


문제 내용


10 이하의 수 중에서 3 혹은 5의 배수인 수를 추려내 본다면 3, 5, 6, 9가 있다. 이들을 모두 합하면 123이 된다.

그렇다면 1000 이하의 수에서 3 혹은 5의 배수인 숫자들의 합은 얼마인가?


원문: 

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.


코드:

public class No001 {
public static void main(String[] args) {
int n1 = 3;
int n2 = 5;
int limit = 1000;
SumMultiple sMultiple = new SumMultiple();
int n3 = sMultiple.sumMultiples(n1, n2, limit);
System.out.println(n3);
}
static class SumMultiple {
int sumMultiples(int n1, int n2, int limit) {
int sum = 0;
int n3 = lcd(n1, n2);
for(int i=n1; i<limit; i++) {
if((i%n3) == 0) {
sum += i;
} else if((i%n2) == 0) {
sum += i;
} else if((i%n1) == 0) {
sum += i;
}
}
return sum;
}
int lcd(int n1, int n2) {
int lcd = 0;
if((n1 > n2) || (n1 <= 0) || (n2 <= 0)) return 0;
for(int i=n1;; i++) {
if((i%n1==0) && (i%n2==0)) {
lcd = i;
break;
}
}
return lcd;
}
}
}


반응형

javap는 한편으로 'Java dissembler'라고 불린다. 왜 이렇게 불리는지에 대해서는 우선 간단한 코드를 보면서 이해해 보도록 하자.


1. 우선, HelloWorld.java라는 이름의 파일이다.

1
2
3
4
5
public class HelloWorld {
    public static void main(String args[]) {
        System.out.println("Hello World!");
    }
}



2. 이 파일을 바이트코드로 변환하여 HelloWorld.class라는 파일을 생성한다.


3. 여기에서 javap 커맨드를 사용해 본다. 다음과 같은 명령어를 입력하여 실행해 보면 된다.

$javap HelloWorld


그러면 터미널에 다음과 같은 결과가 뜰 것이다.

Compiled from "HelloWorld.java"

public class HelloWorld {

  public HelloWorld();

  public static void main(java.lang.String[]);

}

이것이 의미하는 것은 무엇일까? 하나하나 뜯어보자면, 첫번째 줄에서 HelloWorld라는 파일은 HelloWorld.java라는 파일로부터 컴파일된 소스라는 것을 확인할 수 있다. 그 이하로 나오는 부분은 클래스의 기본생성자 및 메인함수와 메인함수가 갖는 파라미터의 데이터 타입이다. 


즉, javap 커맨드는 어떤 바이트코드 파일이 어디서부터 나왔으며, 어떠한 필드와 메소드를 갖고 있는 파일인지를 알려주는 기능을 수행한다고 정리할 수 있겠다.


(javap 커맨드에 대한 정보는 Oracle Docs (http://docs.oracle.com/javase/8/docs/technotes/tools/windows/javap.html)에서 더 확인할 수 있습니다. 추후 한국어로 정리하여 페이지를 업데이트 하겠습니다.)


반응형

+ Recent posts