프로젝트를 진행하면서 새로운 사실을 발견했다~

java에서 String 타입의 null 값 더하기 String 타입의 null은 String 타입 nullnull이 된다....
찾아본 결과 아래의 글을 찾아볼 수 있었는데...
String 타입의 편의성을 위해 그렇게 된다고 하는데....

자바를 오랜 기간 다루었던 사람들은 알지도 모르는 것이지만 
다룬 기간이 짧은 나로서는 새로우면서도 조금은 쏘킹했다.

오오 널널~

===========================================================================================

[ null String value in Java ] null String problem


자바에서 String 을 다룰때 조심해야할 사항들이 있다.

junit으로 테스트 하면 다음의 테스트 클래스는 성공적으로 실행된다.

public class Test_NullString extends TestCase {
    
    public void testNullString () {
        String nullString = null;
        String nullString2 = null;
        assertNull(nullString); // null 이다.
        nullString += "?";
        assertEquals("null?", nullString);
        assertEquals("nullnull", nullString + nullString );   
}

    
    public void testSameString() {
        assertTrue("home" == "home");
        assertFalse("home" == new String("home"));
        assertFalse( new String("home")== new String("home"));
        assertTrue(getJane() == getTom());
    }
    public void testSameObject() {
        assertFalse(new TestObj() == new TestObj());
    }
    private String getJane(){ return "Hi";}
    private String getTom() { return "Hi";}
    
    private class TestObj{
        public boolean equals(Object obj) {
            if ( obj instanceof TestObj ) return true;
            else return false;
        }
    }
}

testNullString() 에서 볼 수 있듯이 null 로 초기화된 string의 경우 이어지는 string 값을 더할 경우 + 연산에 의해서 "null" 이 붙게 된다. null 로 초기화된 두개의 string 을 더할때에도 마찬가지로 그 결과는 "nullnull" 이 된다.

이런 결과는 null 에 대한 보통의 상식과 전혀 다른 결과이다. null 인 스트링과 null 이 아닌 스트링을 더했을때 당연히 null 이 아닌 스트링만 나오기를 기대했었는데 의외의 결과...

왜 일반의 통념과 다르게 
null에 대한 + 연산을 이렇게 구현해야 했을까?

우선 + 연산이 스트링에 대해서 아주 빈번하게 사용된다는 점을 들 수 있는데, + 연산이 구체적으로 어떻게 실행되는지 api 의 코드에는 나와있지 않아서 알 수 없지만 내부적으로 StringBuffer 와 동일한 규칙이 적용되고 있다고 짐작할 따름이다.(StringBuffer의 경우도 append( null ); 이 들이오면 "null" 을 붙이도록 구현되어 있다. 뭐냐 이거.. -_-;;)

만일 일반의 생각대로 ((String)null) + "a" 의 결과가 "a" 라고 하자.

그러면 결과는 "a"가 나올 수 없는데, null 이기 때문에 NullPointerException 이 던져져야 한다. null 로 초기화된 객체는 아무런 연산 행위도 갖지 못한다. 무의식 중에 스트링에 대해서 + 연산을 실행하지만 이런 편의를 위해서 위와같이 null 스트링을 처리하는 것이 아닌가...짐작할 뿐이다.

예를 들어 ((Object)null) + ((Object)null) 을 하면 컴파일 에러가 던져진다. Object 형에 대해서는 + 연산이 정의되어있지 않기 때문... 결국 String 객체에 + 연산을 적용하기 위해서 null에 대해 특별한 처리를 한 것 같다.

두번째 예제도 나름 흥미롭다.

    public void testSameString() {
        assertTrue("home" == "home");
        assertFalse("home" == new String("home"));
        assertFalse( new String("home")== new String("home"));
        assertTrue(getJane() == getTom());
    }

보통 == 와 equals(Object o) 에 대한 닳고 닳은 설명들과 달리 위 테스트도 모두 성공한다. 첫번째와 달리 두번째, 세번째 테스트가 false로 나오는 것에 주목할 필요가 있다.

자바소스 코드 내에서 "...." 로 둘러싸인 부분들은 모두 ConstantPool에 저장된다. ConstantPool이란 자바 클래스 파일에 상수값들만 따로 뽑아놓은 값들인데 동일한 문자열들은 모두 하나의 스트링 값으로 참조되므로 첫번째 "home" == "home"은 true가 된다.(동일한 물리 주소를 참조)

두번째 세번째 assert 에서 new 로 새로운 스트링객체를 만드는데 이것들은 ConstantPool에 있는 스트링을 통해서 새로 만들어진 객체라는게 중요하다.(서로 다른 물리주소를 참조하므로 false이다.)

상식과 어긋나 보이는 이런 규칙들은 대부분의 경우 편리하고 좋지만 안좋을 때도 있다. 일례로 이 글을 쓰게 된 것도 Servlet 에서 getQueryString() 이 반환하는 null 값이 예상치 못한 행동을 하고(위에 설명한...) 그 이유를 알아보다 위와같은 테스트까지 하게 된 것이다.

어찌보면 null의 문제라기보다는 null을 반환하도록 코딩된 서블릿의 구현이 문제가 있는게 아닌가 싶다. 사실 모든 메소드가 인스턴스를 반환할때 null을 피하는 것이 좋다.특히 + 연산이 허용된 java.lang.String 의 경우는 더욱 그러하다. 하지만 스펙에는 아주 용감하게 "쿼리 스트링이 없을 경우 null 을 반환한다" 라고 쓰여져 있다.

덕분에 두시간 넘게 헤매고 다녔다. -_-;;

웬만하면 스트링을 초기화할때는 null 이 아니라 "" 로 초기화해야한다.

           String query = "":

물론 String을 반환하는 메소드의 경우 ""과 null 이 서로 다른 의미를 갖는게 아니라면 무조건!!! "" 을 반환하도록 하는게 옳다고 생각한다. 

+ Recent posts