LinkedIn

자바 원시(Primitive) 타입과 참조(Reference) 타입, 그리고 Reflection

2021. 1. 1. 21:38 | 자바 개발자되기

발단

Request 클래스의 Primitive type으로 선언된 변수에 대하여 Jackson Deserialize가 정상적으로 처리되지 않음

 

How to Deserializing?

Spring에서 @ReuqestBody를 사용하는 경우
body에 있는 data를 HttpMessageConverter 이용,
특히 Json Data의 경우 MappingJackson2HttpMessageConverter 통하여 Jackson ObjectMapper readValue() 호출하고,
그 결과 개발자가 지정한 Request Object를 만들어 반환한다.

요약하자면 결국 JSON 요청 데이터를 Java Reflection을 사용하여 개발자가 지정한 Request Object에 매핑해준다.

  • obj는 개발자가 지정한 Request Object
  • value는 deserialize 된 request data 중 일부

 


예제 코드

@RestController
public class PrimitiveTestController {
    @PostMapping(value = "test/primitive")
    public ResponseEntity<String> finalTest(@RequestBody final Request request){
        final long number = request.number;
        final Long number2 = request.number2;
        return ResponseEntity.ok().body(request.toString());
    }
}

class Request {
    public static final long DEFAULT = 100L;
    public final long number = DEFAULT;
    public final Long number2 = DEFAULT;
}

호출 결과

Request{number=10, number2=10} 을 기대하였지만 결과는 위와 같다 😨

 

 

Why?

결과로 기대하였던 것은 number, number2 모두 Request Body로 Deserealize 된 Request{number=10, number2=10} 이다.

하지만 왜 예상한 결과대로 처리되지 않았을까? 🤔

 

final로 초기화된 원시 타입

Chapter 4. Types, Values, and Variables

A variable of primitive type or type String, that is final and initialized with a compile-time constant expression, is called a constant variable.

Chapter 13. Binary Compatibility

References to fields that are constant variables are resolved at compile time to the constant value that is denoted. No reference to such a field should be present in the code in a binary file (except in the class or interface containing the field, which will have code to initialize it). Such a field must always appear to have been initialized the default initial value for the type of such a field must never be observed.

final로 초기화 된 원시 타입은 complie-time에 inline code화 된다?

 

무슨 말인지 모르겠으니 다시 예시 코드를 봐보자 🥱

PrimitiveTestController.java

@RestController
public class PrimitiveTestController {
    @PostMapping(value = "test/primitive")
    public ResponseEntity<String> finalTest(@RequestBody final Request request){
        final long number = request.number;
        final Long number2 = request.number2;
        return ResponseEntity.ok().body(request.toString());
    }
}

제일 처음 보았던 Controller의 Java 코드이다.

이 코드를 컴파일 한 .class 파일을 확인해보자!

 

PrimitiveTestController.class 🙀

@RestController
public class PrimitiveTestController {
    public PrimitiveTestController() {
    }

    @PostMapping({"test/primitive"})
    public ResponseEntity<String> finalTest(@RequestBody final Request request){
        request.getClass();
        long number = 100L;
        Long number2 = request.number2;
        return ResponseEntity.ok().body(request.toString());
    }
}

long number = 100L;

 

원래 코드의 의도는 Json Deserialize 된 Request Object의 number를 반환하는 것이었다.

final long number = request.number;

 

하지만 컴파일된 코드에서는 위와 같이 초기화에 사용된 constant value가 코드에 들어가 버렸다.

 

또한 Wrapper Class로 사용된 number2의 경우 primitive type과 별개로 의도하고자 했던 대로 컴파일되었다.

 

 

정리

  • Primitive type, String의 경우 final & initialized 된다면 compile-time에 상수 표현식이 inline code화 된다.
  • 그러므로 primitive type & String에 대하여 final 키워드를 사용한다면 해당 변수가 초기화되는 시점을 잘 고려해야 할 것이다.

참고