오늘은 자바의 String에 대해서 알아보자. 딱히 많이 중요한 이야기는 아니니 참고 정도만 하면 되겠다.

String 과 new String()

자바에서는 두가지 방법으로 String을 선언할 수 있다. 아래와 같이 말이다.

String a = "hello";
String b = new String("hello");

뭐 이건 자바의 왕기초이니 뭐 설명할 것도 없지만.. 한 가지는 String literal을 사용해서 선언가능하고 다른 한 가지는 new 키워드를 사용해서 생성할 수 있다. 그럼 두개의 차이점은 뭘까? 일단 가장 중요한 것은 메모리 구조가 다르다. 둘다 힙영역에 저장되지만 literal의 경우에는 힙영역 안의 string pool이라는 영역에 자리 잡게 된다.

좀 더 살펴보도록 하자.

String a = new String("String");
String b = "String";
System.out.println(a == b);

위와 같은 코드가 있다고 가정하자. 그럼 어떤 결과가 나올까? 뭐 누구나 알겠지만 당연히 false가 나온다. 자바를 처음 배울때 ==는 레퍼런스를 가르킨다고 배워 무조건 equals 를 사용해 값을 비교해야 한다고 대부분 그렇게 배웠을 것이다.

그럼 다음과 같은 코드가 있을 경우에는 어떨까?

String b = "String";
String c = "String";
System.out.println(b == c);

동일한 String 의 문자열이다. 하지만 둘 다 String literal을 사용해서 비교하고 있다. 결과는 참혹(?) 하다. 우리가 기대하고 있던 false가 나오지 않는다. (아닌가? 하긴 false가 나왔다면 이 글을 쓸일이 없겠지..) 두개는 정확히 동일한 래퍼런스를 가르키고 있다. 우리 똑똑한(?) 자바의 컴파일러가 최적화를 위해 동일한 String 문자열이 존재 한다면 재사용한다. 더 정확히는 string pool 에는 동일한 문자가 있다면 한개의 String이라는 문자열을 넣고 그것을 사용한다는 것이다.

물론 위와 같이 사용할 일은 없다. 왜냐하면? 저렇게 비교할 일이 없기 때문이다. 보통은 변수와 변수를 비교하기 때문에 꼭 equals 를 사용해야 한다. 혹은 상수를 비교한다고 하더라도 여타 프레임워크나 라이브러리들이 어떤 방식으로 줄지 모르니 그냥 마음 편하게 equals를 사용하도록 하자. 왠지 이글은 ==를 사용해도 된다는 말 같으니.. 만약 행여나 이런 경우가 있더라도 그냥 equals 를 사용하자. 괜한 버그를 만들지 말고..

그럼 또 해볼 수 있는게 있다. 아래와 같이 말이다.

String b = "String";
System.out.println(b == "String");

위와 같은 경우에는 어떨까? 한 번 생각해보자. 결과는 한번 생각해보고 코드를 돌려보도록 하자.

그리고 주니어 개발자를 위해 String의 참고할 만한 것들을 살펴보자.
객체형 String을 literal로 변환할 수 있는데 String의 intern() 메서드를 사용하면 된다.

String a = new String("String");
String c = "String";
System.out.println(a.intern() == c);

위 경우의 결과값은 true가 출력 된다. 근데 필자는 이 메서드를 사용한적이 거의 없어서.. 아니 한번도 없던거 같다. 아무튼 이런것도 있으니 참고하면 되겠다.

또 하나는 객체형 String은 되도록 이면 사용하지 말자. 아주아주 특별한 이유가 없다면.. 필자가 볼 때는 사용할 특별한 이유가 없다. 그러므로 무조건 String 은 literal 로 선언하자. IDEA 에서는 실제 힌트도 준다. 대충 해석하자면 String을 객체화 할필요 없다. 또한 자주 수행될 경우에는 성능에 문제가 있을 수 있다. 라고 하니 사용하지 말자!

String의 String.valueOf(Object obj) 메서드는 null safe 하다. 하지만 null일 경우에는 실제 문자열 "null"을 리턴한다. 그래서 원하는 값이 안나올 수 도 있다. 왜 "null"로 만들어 놨을까.. 흠흠

마지막으로 주니어 개발자들이 코드를 보면 자주 등장하는 부분이다. 매직 String이 자주 등장한다. 되도록이면 매직 String 을 사용하지 말자. String 뿐만아니라 Number 들도 그렇다. 유지보수에도 좋지 않고 실수할 여지가 있을 수 있다.

이 정도면 어느정도 팁을 준 듯 하다. 더 생각나지 않는다. 더 생각나면 다음에 기회에.. 다음으로 넘어가자!

String 의 final

이 글에서는 String만 관련지어서 이야기 한다. 다른 부분들은 다른 글을 참고하기 바란다. 필자가 쓴 글도 있으니 참고하면 되겠다.

필자는 final을 좋아한다. final은 변하지 않는 상수를 이야기한다. 변하지 않는다는 것은 안전하다는 뜻도 포함될 수 있다. 또 한 실수를 방지할 수 있다. 변하지 않아야 하는 값이 변한다면 버그가 나올 가능성이 높아진다.

private static boolean hello(String str) {

  something(str);
  //something
  // ...
  // ...
  if(str.equals("hello")){
    return true;
  }
  return false;
}

대략 위와 같은 코드가 있다고 가정하자. 현재 딱히 좋은 예제가 생각나지 않아서… 긴 메서드라고 생각하자!. 물론 긴메서드 자체를 만들면 안되겠지만 예제이니.. 만약 저 str 파라미터가 중간에 끼어서 사용되어 가공된다면 우리는 원하는 결과를 얻지 못할 수 도 있다. 물론 자신이 개발하고 자신이 유지보수를 한다면 그래도 다행이겠지만 다른 사람이 유지보수를 한다고 하면 긴 메서드를 단시간에 이해하기 힘들다. 그래서 중간에 그냥 str을 가공시킬지도 모른다. 버그가 안생기면 천만다행이겠지만 생긴다면 저 코드 하나로 인해 쓸때 없이 버그만 만들게 된 것이다. 아직은 필자도 final을 쓰는 습관이 안되어 있다. 차근차근 습관화 시켜야 겠다. 이건 뭐 그렇게 중요한 내용은 아니므로 그냥 그렇게 보면 되겠다. 사실 이걸 말할려고 한건 아닌데.. 예제가 좀 시원찮아도 이해해주시길..

final이 더 좋은 이유를 살펴보자.
다시한번 말하지만 final 키워드는 상수를 의미한다. 그러므로 변경되지 않는다. final로 선언되어 있는 변수를 변경하려고 하면 컴파일 에러가 난다.

final String a = "hello";
a = "world"; //컴파일 에러

컴파일러도 상수라는 것을 알고 있다. 다음 코드르 보자.

final String a = "hello";
String b = "hello";
System.out.println(a);
System.out.println(b);

a와 b의 변수의 다른점은 a는 상수이고 b는 변수이다. 아까도 말했듯이 컴파일러는 a가 상수인 것을 알고 있다. 그래서 컴파일러가 최적화를 통해 System.out.println(a) 이 부분에 System.out.println("hello") 로 바꾸어 버린다.

   L0
    LINENUMBER 10 L0
    LDC "hello"
    ASTORE 1
   L1
    LINENUMBER 11 L1
    LDC "hello"
    ASTORE 2
   L2
    LINENUMBER 12 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "hello"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L3
    LINENUMBER 13 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 17 L4
    RETURN

위는 해당 코드의 바이트코드의 일부분이다. L2 (LINENUMBER 12)를 보면 알 수 있다.

또 한 다음과 같이 사용할 일은 없겠지만 그래도 한번 살펴보자.

final String a = "hello";
final String b = "hello";
System.out.println("result : " + (a == b));

위와 같이 a,b 모두 상수로 선언하였다. 그리고 비교하면 컴파일러가 result : true 으로 바꿔 버린다. final을 쓰면 컴파일러가 좀 더 최적화를 하지만 그렇게 성능에 영향을 미칠 정도는 아닐 듯하다. 그렇지만 final을 사용하면 여러모로 좋으니 추천정도만 한다.

여타 언어들은 기본적으로 final을 사용하고 있다. 스칼라도 그렇고 코틀린도 그렇고 es6에도 상수의 개념으로 const라는 키워드가 생겼다. 누군가가 final로 도배를 한다면 뭐라하지 말고 칭찬해주자… 그렇지만 회사에서는 회사 코딩 컨벤션을 따르는게 맞다. 혹시나 이글을 보고 갑자기 회사 코드를 final 도배하는 일은 없도록 하자.

오늘은 이렇게 java의 String에 대해서 알아봤다. 물론 중요하면 중요할 수도 있겠지만 실제 개발을 할 경우에는 그렇게 많은 도움은 되지 않을 듯 하다.

오늘 내용은 그냥 이런게 있구나.. 생각하면 되겠다.
오늘은 이만!