회사에서 개발을 하면서, null이 들어간 String을 다른 String과 +연산을 하는 테스트를 하게 되었다.
해당 부분에 대해서 자세히 알고있지 못했던 나로서는 예상과 다른 결과를 보게 되었는데 이에 대해 기록을 하려고한다.
이야기에 앞서 먼저 null이 무엇인지에 대해 정리를 하려고 한다.
null은 값이 없음을 나타내는 영단어로, 독일에서는 숫자 0이라는 단어로 쓰이지만 우리가 다루는 프로그래밍의 관점에서는 값이 없으며 0조차 아니라는 뜻이다. (TMI: 영미권에서는 널, 독어식으로는 눌이라고 발음한다)
값이 없다는 것은 무슨 의미이며, 왜 존재할까?
우리가 프로그래밍에서 사용하는 변수들은 메모리에 저장된 어떠한 값을 참조하고 있는 형태로 이루어진다. 그런데 값을 참조하지 않는 변수들은 어떤 값을 가지고 있어야 할까?? 이때 사용되는게 바로 null이다.
NPE는 개발자들이 가장 무서워하는 예외이다. 로직 내의 예외의 경우, 개발자가 의도하거나 예상을 하거나 그것도 아니라면 대비하여 개발을 하지만 NPE는 다르기 때문이다.
NPE는 값을 참조하는 변수가 없는 경우 발생하므로, 개발자가 구현하는 로직이 잘못되었음을 뜻한다.
또한 발생했을 경우의 처리도 어려워진다. A의 값이 들어있어야하는 변수의 값이 B가 들어있었다면, 로직의 잘못됨을 유추할 가능성이 있지만 NPE는 예측할 방법이 없다.
위의 null과 NPE의 이야기는 해당 목차를 말하기위한 준비물이다.
개발자 A는 아래와 같은 코드를 작성했다.
String nullString = null;
String tempString = "PointerException";
String concatString = nullString + tempString;
개발자A: 이렇게 코드를 짜면 어떻게 될까????
개발자B는 비웃었다.
개발자B: 야 그렇게 하면 NPE가 발생하잖아...
결과는 개발자B의 예상과는 달랐다.
System.out.println("출력값: %s", concatString);
출력값: nullPointerException
왜 예상을 깨고 위와 같은 결과가 나타났을까??
이유는 간단했다. String을 +
연산을 하면, String class 내부의 valueOf()
를 호출하게 된다. 메소드의 정의를 확인해보자.
메소드의 주석과 내용을 확인해보면 String 자기자신을 toString()
으로 반환하고, 만약 null인 경우에는 "null"
을 반환하게 되어있다. 즉 valueOf
와 연관성이 있는 연산일 경우 NPE가 발생할 수 없게 된다.
그렇다면 null인 경우는 NPE를 발생시키고 이에 따른 예외를 처리하고 싶다면 어떻게 해야할까??
concat()
의 결과값 또한 함수정의를 통해서 알아볼 수 있다. 해당 메소드에서도 null에 대한 처리를 하고 있을까??
우리는 크게 두가지의 경우를 고려해볼 수 있다.
먼저 첫번째의 경우를 고려해보자. 위의 정의를 보면, concat()
의 경우 인스턴스가 null인 경우의 처리는 존재하지 않는 것을 볼 수 있다. 즉, 다음과 같은 결과를 가지게 된다.
String nullString = null;
String tempString = "PointerException";
nullString.concat(tempString);
null인 인스턴스가 메소드를 호출했으니 당연히 다음과 같이 NPE가 발생하게 된다.
java.lang.NullPointerException: Cannot invoke "String.concat(String)" because "nullString" is null
그렇다면 두번째의 경우는 어떨까??
String nullString = null;
String tempString = "PointerException";
tempString.concat(nullString);
두번째 경우에도 크게다르지 않다. concat()
내부적으로 isEmpty
를 호출하므로 null인 인스턴스가 메소드를 호출하게 된다.
java.lang.NullPointerException: Cannot invoke "String.isEmpty()" because "str" is null
String에 대해 크게 공부해보지 않았지만, concat()
이라는 메소드가 있기때문에 +
연산을 통해 문자열을 결합할 때 해당 메소드를 쓸거라고 생각했었다.
위와 같이 실제 메소드의 호출을 확인하고 나니 테스트와 디버깅이 중요하다는 점을 다시 한번 느낄 수 있었다.