자바 정수형 Wrapper 타입의 값 비교 2016. 09. 14. 12:48 dev/java#java#wrapper#compare (티스토리 블로그에 작성했던 글을 옮긴 글입니다.) 자바를 배울 때 String의 비교를 다루면서 == 연산자를 통한 비교와 String 클래스의 equals 메서드를 통한 비교로 객체 레퍼런스 비교와 값 비교, 그리고 상수 풀(Constant Pool)에 대해 꽤나 심도있게 공부한다. 그리고 원시(primitive) 타입의 래퍼 타입인 Boolean, Byte, Short, Integer, Long 등에 대해서도 배우고 Java 5 이상을 대상으로 하는 서적에서는 꼭 빠지지 않는 Autoboxing과 Unboxing에 대해서도 다룬다. 이 두 가지 기본적인 것들과 관련하여 업무 코드에서 조금은 위험한 헤프닝이 발생했다. DB 테이블에서의 id 타입은 INT이고, 이를 자바에서는 long의 래퍼타입인 Long으로 담아서 사용하고 있었는데, 이 id를 비교할 때 동등 연산자(==)를 이용해서 id가 동일한지 다른지를 판단했는데, 생각과는 다르게 동작하는 것을 동료분이 발견하셨다. 이를테면 아래와 같다. boolean compareUserId(User user, Long id) { ... if(user.getId() == id) { ... } else { ... } } 위 코드에서 분명 user 객체의 id와 id 파라미터의 값이 같은데도 불구하고 분기는 else 구문으로 빠진다. 문제는 Long 타입의 값을 비교할 때 동등비교 연산자를 사용했기 때문이다. 어쩌면 당연할지도 모를 결과지만 당시 코드를 봤을 땐 단체 멘붕이었다. 그 때 간단하게 테스트를 위해서 아래와 같은 코드로 결과를 확인 해봤었다. Long a = 5L; Long b = 5L; System.out.println(a == b); 결과는 예상한대로 true가 출력되었다. 그럼 위와 다른 건 없지만 값이 5L이 아닌 그보다 큰 값이라면 어떨까? Long a = 128L; Long b = 128L; System.out.println(a == b); 결과는 false였다. 구글링으로 문제해결 백과사전 StackOverflow의 글 중에 위와 같은 문제를 질문한 글이 있었고, 친절한 답변이 달려있었다. http://stackoverflow.com/questions/1700081/why-does-128-128-return-false-but-127-127-return-true-when-converting-to-integ?noredirect=1&lq=1 http://stackoverflow.com/questions/13098143/why-does-the-behavior-of-the-integer-constant-pool-change-at-127 http://stackoverflow.com/questions/8968376/java-long-datatype-comparison 그렇다면 자바는 어떻게 Long 타입으로 boxing할까? 디컴파일링한 결과를 보자. public class Test { public static void main(String[] arrstring) { Long l; Long l2 = 130; System.out.println(l2 == (l = Long.valueOf(129))); } } a, b에 대입하는 값을 바꿔도 동일하게 컴파일된다. 추측컨대 최적화를 위해 l을 실제로 사용할 때 값을 대입하게 되는데, 이 때 129라는 값을 Autoboxing하기 위해 Long.valueOf 메서드를 사용한다. (왜 l2에는 130을 그대로 대입하고, l에는 valueOf로 사용하는지는 좀 더 확인이 필요하다.) valueOf 메서드의 구현은 아래와 같다. 속도 향상을 위해 캐싱한다. 이는 자바 실행 시 JVM 옵션으로 전달할 수 있다. Effective 자바에서는 어떻게 말하고 있을까? Autoboxing, Unautoboxing 기능 때문에 당연히 long 값으로 비교를 하겠지 했는데, 이게 왠걸 (어쩌면 당연하게도) 값 비교가 아니라 레퍼런스 비교를 한다. 다만 하나 다른 부분은 String이 상수 Pool에 컴파일 타임에 문자열을 담아놓듯, Long 역시 -128 ~ 127의 값은 상수풀에 값을 담아놓기 때문에 == 연산자를 통해 비교하면 127까진 true이고 128부터는 false다. 아마 byte 타입의 값 범위는 상수풀에 담아두고 사용하는 듯 하다. 따라서 long이 아닌 long의 래퍼 타입 Long의 경우에는 값 비교 시 compare 또는 equals 메서드를 사용해서 비교해야 한다. equals 메서드는 내부적으로 Long 객체가 가지고 있는 long value를 직접 비교하므로 원하는 대로 값의 비교를 하게 된다. 그럼 그냥 id를 담기 위해 Long이 아닌 long을 쓰면 되지 않느냐라는 질문을 할 수도 있겠지만 이 테이블의 id는 null인 경우가 없지만, 외래키로 참조하고 있는 id는 nullable이므로 null과 정수를 모두 다루기 위해서는 Long을 사용해야 한다. 그렇지 않으면 NullPointerException이 발생한다.(맞나..? 확인필요) 그래도 자바의 기본적인 부분은 알고 있다고 생각했는데, 다시금 기초를 탄탄히 해야겠다는 생각이 들었다. Java Specification을 찾아봐야겠다.