ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java 의 생성자 동작 ( GSON 을 통해 Class 를 만들었을 때 기본 값이 설정되지 않는 문제 )
    기술 2023. 8. 31. 20:44

    Java 에서 GSON 라이브러리를 사용해 JSON 데이터를 클래스 객체로 변환할 때 초기값이 설정이 예상대로 동작하지 않은 경우가 있었습니다. 이 문제를 파악한 내용을 공유하려 합니다.

    문제 상황

    다음과 같은 SomeClass 가 있다고 가정합니다.

    public class SomeClass {
        private Boolean isInvalid = false;
    }
    
    GSON.fromJson(json, SomeClass);

    GOSN 을 사용해 json 데이터를 SomeClass 로 바꿨을 때 isInvalid 가 false 일 것 같지만 실제로는 Null 로 되어 있었습니다.

    원인

    이 문제의 원인은 GSON 이 Unsafe 모듈의 allocateInstance 메서드를 사용해 객체를 생성하기 때문입니다. 이 메서드는 constructor 를 호출하지 않고 호출하지 않고 인스턴스를 우회적으로 생성합니다. 결과적으로 기본 값이 설정되지 않은 채로 필드가 초기화 됩니다.

    constructor 의 동작을 조금 더 깊게 살펴보기

    constructor 가 실행되지 않았다는 것이랑 필드에 정의한 기본 값이 사용되지 않는 것이랑 무슨 상관이 있을까 생각했습니다. constructor 라면 아래와 같이 아래 블럭에서 실행되는 것을 무시하는 것 아닌가 정도로 생각했습니다. 하지만 그게 아니니 isInvalid 가 Null 로 설정되었을 것입니다. 그래서 조금 더 깊게 찾아봤습니다.

    public class SomeClass {
        private Boolean isInvalid = false;
    
        public SomeClass() {
            // 이것만 무시되는 거 아닌가?
            isInvalid = true;
        }
    }

    Oracle 문서의 Constructor 의 동작 방식

    class Point {
        int x, y;
        Point() { x = 1; y = 1; }
    }
    
    class ColoredPoint extends Point {
        int color = 0xFF00FF;
    }
    
    class Test {
        public static void main(String[] args) {
            ColoredPoint cp = new ColoredPoint();
            System.out.println(cp.color);
        }
    }
    1. ColoredPoint 인스턴스가 생성됩니다. x, y, color 필드를 보유하기 위해 메모리에 모든 필드가 타입의 기본 값 ( int 의 기본 값은 0 ) 을 가진 채 초기화 됩니다.
    2. ColoredPoint 의 암시적 constructor 가 호출됩니다.
      ColoredPoint() { super(); }
    3. ColoredPoint 의 super() 메서드는 인수 없는 Point constructor 를 호출합니다.
      Point() { super(); x = 1; y = 1; }
    4. Point 의 super 는 인수 없는 Object 의 super 를 호출합니다.
      Object() {}
    5. Object 의 initializer 가 호출됩니다.
    6. Object constructor 의 본문이 실행됩니다.
    7. Point 의 인스턴스 변수에 대한 initializer 가 실행됩니다.
      이 때 x, y 의 기본 값이 제공되지 않았기 때문에 아무것도 실행되지 않습니다.
    8. Point 의 consturctor 의 본문이 실행됩니다.
    9. ColoredPoint 의 initializer 가 호출됩니다.
      이 때 색상에 0xFF00FF 값이 할당됩니다.
    10. ColoredPoint constructor의 본문이 실행됩니다.

    즉 constructor 가 initializer 를 호출하는 것이고 initializer 에서 필드의 기본 값이 설정되는 순서입니다.

    재미있는 예제

    constructor 동작을 이해했다면 이 재미있는 것도 이해할 수 있습니다.

    class Super {
        Super() { printThree(); }
        void printThree() { System.out.println("three"); }
    }
    
    class Test extends Super {
        int three = (int)Math.PI;
        void printThree() { System.out.println(three); }
    
        public static void main(String[] args) {
            Test t = new Test();
            t.printThree();
        }
    }
    0
    3
    1. Super 클래스의 constructor에서 printThree 를 호출하면 Sub 의 printThree 의 재정의된 메서드를 호출합니다. 이 때 three 값은 initializer 가 실행되지 않았기 때문에 타입의 기본 값을 가지고 있으므로 0 이 출력됩니다.
    2. main 메서드의 printThree 가 호출될 때는 initializer 가 호출되었으므로 3의 값이 인쇄됩니다.

    원문 링크: https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.5

    댓글

Designed by Tistory.