Java: Cloneable에 대한 고찰

suky·2021년 12월 22일
0
post-thumbnail

Cloneable은 어떻게 작동할까?

Cloneable이란?

이펙티브 자바에서 Item 13. clone 재정의는 주의해서 진행하라라는 아이템을 읽기 위해 책을 폈습니다. 초반 부분을 요약하자면 다음과 같은데요.

clone은 Object에 정의되어 있는데 쓰고 싶으면 Cloneable을 구현해야해!

Cloneable이란 Object에 정의되어있는 clone을 사용하기 위한 인터페이스라고 볼 수 있습니다.

그래서 왜 고찰하게 되었는가?

현재 이펙티브 자바에는 코드가 없는 부분이나 설명이 부실한 경우가 많아, 이를 notion에 이해가 되게끔 정리하면서 공부하고 있습니다.
코드가 없으면 코드를 작성하고, 이해가 안되면 java 소스 코드를 조금씩 보는데요. 이번에 코드는 간단했지만 난해한 녀석이 나타났습니다.

왜 고찰하게 되었는지 소스코드를 보면서 제 의식의 흐름을 말씀드리겠습니다.

// Object 내 clone 구현
public class Object {
	...
	protected native Object clone() throws CloneNotSupportedException;
	...
}

처음 봤을 때 Object clone은 단순히 CloneNotSupportedException만 던지는 객체라고 봤습니다. 그래서 CloneNotSupportedException에서 Cloneable을 검증하는 로직이 있는건가? 라고 생각했었죠.

// CloneNotSupportedException 구현
public class CloneNotSupportedException extends Exception {
    private static final long serialVersionUID = 5195511250079656443L;

    /**
     * Constructs a <code>CloneNotSupportedException</code> with no
     * detail message.
     */
    public CloneNotSupportedException() {
        super();
    }

    /**
     * Constructs a <code>CloneNotSupportedException</code> with the
     * specified detail message.
     *
     * @param   s   the detail message.
     */
    public CloneNotSupportedException(String s) {
        super(s);
    }
}

근데 CloneNotSupportedException에는 검증하는 로직이 없는 것을 확인할 수 있습니다!! 그럼 Cloneable에서 clone에 대하여 overriding을 진행하는 로직이 있나?라고 생각했는데 그 다음 Cloneable을 보았을 땐 제 멘탈이 남아나지 않았죠 😂

// Cloneable 구현
public interface Cloneable {
}

너무 간단했습니다. 제 지식상으로는 소스코드는 거짓말을 하지 않는데 거짓말을 하는 느낌이었습니다;; 🤣

한 가지 간과하고 있던 점 - native

구글에 how to cloneable check in java, cloneable clone check 등등 몇 시간 동안 찾아봐도 명확한 답이 안 나왔었습니다. (영어를 못해서 그런것일지도 ㅎㅎ;;;)
그러다 몇 시간 후 답변을 들었고, 제가 놓치고 넘어간게 있었습니다. 바로 native였습니다.

제가 자바린이라서 static, final 말고도 native라는게 있구나!하고 넘어갔습니다. 이게 큰 실착이었죠...

오픈 톡방에 이와 관련해서 질문을 올렸고, 아주 속이 뻥 뚫리는 명답을 들을 수 있었습니다!

바로 openjdk 소스코드 내 jvm.cpp에서 그 해답을 찾는 것이었습니다!
openjdk의 jvm.cpp를 보면 다음과 같이 나옵니다. (소스코드를 전체 해석하려고 하지는 않았습니다 ㅎㅎ;;)

JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
  Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
  Klass* klass = obj->klass();
  JvmtiVMObjectAllocEventCollector oam;

#ifdef ASSERT
  // Just checking that the cloneable flag is set correct
  if (obj->is_array()) {
    guarantee(klass->is_cloneable(), "all arrays are cloneable");
  } else {
    guarantee(obj->is_instance(), "should be instanceOop");
    bool cloneable = klass->is_subtype_of(vmClasses::Cloneable_klass());
    guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");
  }
#endif

  // Check if class of obj supports the Cloneable interface.
  // All arrays are considered to be cloneable (See JLS 20.1.5).
  // All j.l.r.Reference classes are considered non-cloneable.
  if (!klass->is_cloneable() ||
      (klass->is_instance_klass() &&
       InstanceKlass::cast(klass)->reference_type() != REF_NONE)) {
    ResourceMark rm(THREAD);
    THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
  }

  // Make shallow object copy
  const size_t size = obj->size();
  oop new_obj_oop = NULL;
  if (obj->is_array()) {
    const int length = ((arrayOop)obj())->length();
    new_obj_oop = Universe::heap()->array_allocate(klass, size, length,
                                                   /* do_zero */ true, CHECK_NULL);
  } else {
    new_obj_oop = Universe::heap()->obj_allocate(klass, size, CHECK_NULL);
  }

  HeapAccess<>::clone(obj(), new_obj_oop, size);

  Handle new_obj(THREAD, new_obj_oop);
  // Caution: this involves a java upcall, so the clone should be
  // "gc-robust" by this stage.
  if (klass->has_finalizer()) {
    assert(obj->is_instance(), "should be instanceOop");
    new_obj_oop = InstanceKlass::register_finalizer(instanceOop(new_obj()), CHECK_NULL);
    new_obj = Handle(THREAD, new_obj_oop);
  }

  return JNIHandles::make_local(THREAD, new_obj());
JVM_END

위 코드가 java에서 Object 내 clone 메소드의 정체였습니다. 이 코드에서 제가 제일 궁금했던 Cloneable을 체크하는 로직을 찾을 수 있었습니다.

if (!klass->is_cloneable() ||
      (klass->is_instance_klass() &&
       InstanceKlass::cast(klass)->reference_type() != REF_NONE)) {
    ResourceMark rm(THREAD);
    THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
  }

klass가 뭐하는 구조체인지 몰라도 !klass->is_cloneable()THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name())를 보면 is_cloneable하지 않으면 CloneNotSupportedException을 뱉는구나라고 어렴풋이 알 수 있었습니다. 정말 아름다운 코드네요 💕.

마무리

이번 아이템을 고찰하면서 자바에서 Cloneable이 어떻게 동작하는지 살펴볼 수 있었습니다. 물론 이 지식을 Cloneable에서 끝나지 않고 마커 인터페이스라는 더 큰 범주에 대해서 적용할 수 있는데요. 마커 인터페이스는 비어있는데 어떻게 동작할까?라는 물음에 대해서 일반화 할 수 있겠네요.
드디어! 아이템 13장을 다시 읽을 수 있게 되었습니다. Good Bye~ 😁

profile
도전하고 성장하는 개발자입니다 :)

2개의 댓글

comment-user-thumbnail
2021년 12월 24일

영수님 저 어제 개발바닥 오픈채팅방에서 영수님이 질문하시는거 봤어요!!!!!!!!!ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 부캠끝나고도 넘나 열심이십니다 멋져요 화이팅!!!!🔥🔥🔥🔥🔥🔥🔥🔥

1개의 답글