ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA/DataType & String] 데이터 타입과 String에 대해
    Mhwan's Study/JAVA & Kotlin 2021. 1. 21. 03:31

    # Primitive Type VS Reference Type

    Primitive Type (원시형) : 자바의 기본적인 자료형으로 Stack 영역에 저장됩니다.

    - 정수형 : byte (1byte), short (2byte), int (4byte, 약 20억까지 표현가능), long (8byte)
    - 실수형 : float (4byte), double (8byte)
    - 논리형 : boolean (1byte)
    - 문자형 : char (2byte)

    Reference Type : 원시형을 제외한 모든 자료형, new키워드를 이용해 생성되는 데이터로, 모두 Heap 영역에 저장됩니다. (GC의 대상)

     

    # Wrapper class

    byte (Byte), short (Short), int (Integer), long (Long), float (Float), double (Double), char (Character), boolean (Boolean)

    어떤 경우 기본형의 데이터를 객체로 취급해야 하는 경우가 있다. ArrayList같은 것에 담을 자료형인 Generic에 사용되기도 하며, 원시형과 달리 null값을 넣을 수도 있습니다.

    이렇게 원시형 데이터를 객체로 포장하는 클래스를 Wrapper class라고 부릅니다. 원시형의 데이터를 래퍼 클래스로 변환하는 것을 Boxing, 래퍼클래스를 기본 타입으로 변환하는 것은 UnBoxing으로 부르는데, JDK1.5부터 박싱과 언박싱을 자바 컴파일러가 자동으로 처리해줍니다. (AutoBoxing, AutoUnBoxing)

    그럼 원시형 대신 항상 Wrapper Class를 쓸 수 있지 않나 의문이 생깁니다. 만약 이렇게 되면 프로그래밍에서 사용되는 모든 자료 공간은 힙에 저장이 되고, 그러면 객체 생성, 참조 등 비용적인 문제가 훨씬 심해지며 그만큼 GC도 자주 발생해서 성능적으로 떨어지게 될 것입니다.

     

    # == VS equals()

    == 연산자를 보통 값이 같은지 비교하기 위해서 사용합니다. 하지만 == 연산은 Reference Type에는 적용이 안되는데, 왜냐하면 두 객체가 같은 메모리 주소를 갖는지 비교하는 연산입니다. 이 때문에 두 개의 값이 같은 int형 변수는 우리가 원하는대로 true가 나오지만, Integer라는 Wrapper Class를 비교하면 둘은 서로 다른 힙 메모리에 저장되기에 false가 나옵니다.

    이 때문에 객체의 실제 내용을 비교하기 위해서는 모든 객체가 갖고 있는 equals()메소드를 이용해야 합니다.

     

    # String

    자바에서 String은 특별한 자료형입니다. 기본적으로 ""를 통해 생성하기도 하지만, new 키워드를 통해 생성할 수도 있습니다. 이 때문에 String은 모두 힙 영역에 저장되지 않나 생각하지만 이는 반은 맞고 반은 틀립니다.

    - ""을 통한 생성 (String str = "hello") : 자바에는 String Constant pool이 있습니다. 이는 힙 영역에 있는 별도의 공간으로 String Constant Pool에 저장되는 문자열은 같은 문자열일 경우 새로 생성되지 않습니다. (String a = "hello"라고 생성해도 별도의 공간에 생성되지 않고 같은 공간을 가리킵니다. 이 때문에 ==으로 비교해도 true가 return 됩니다.)

    - new를 통한 생성 : 이것은 String Constant Pool에 생성되지 않고, heap영역에 저장됩니다. 즉, 같은 문자열이어도 heap 내의 서로 다른 공간에 만들어지게 되는 것이고 ==으로 비교하면 false가 return되며, GC의 대상이 됩니다.

     이렇게 String Constant Pool에서 관리할 수 있는 이유는 String은 immutable한 객체이기 때문입니다. 불변하는 객체이므로 String Constant pool에서 같은 메모리를 참조하게 할 수 있으며, 메모리도 절약됩니다. 만약 String a = "hello"로 생성한 뒤 a = "bye"로 수정하면 String Constant Pool에는 기존에 "hello"가 있는 메모리가 변하는게 아니라 "bye"가 저장된 공간이 새로 생성되고 a가 이곳을 가르키게 하는 것입니다. 이는 String의 연산에서 +연산을 제공하는데, 여기서도 같은 방식으로 변경이 됩니다. (아래 이미지 참고)

    또한 immutable하기 때문에 multi Thread환경에서 안전하고, 더욱 빠르게 String의 hashCode를 생성할 수 있습니다. 왜냐하면 JAVA에서는 String의 hashcode를 생성단계에서 부터 캐싱하는 단계를 거치는데, 이 때문에 Hashmap에서 String을 key로 두면 더 빠르게 사용할 수도 있습니다. (만약 객체가 불변하지 않다면 hashcode를 캐싱할 수 없을 것 입니다.)

     

    # StringBuilder VS StringBuffer

    위에서 말한 String의 불변하는 성격때문에 +와 같은 합치는 연산을 하면 성능저하가 발생할 것입니다. 이 때문에 문자열의 연산을 빠르게 하기 위해 JAVA에서는 버퍼를 통해 문자열을 관리하다가 toString()을 통해서 최종적으로 생성하도록 합니다. 이것이 바로 StringBuilder와 StringBuffer입니다. 이 둘의 차이는 아래와 같습니다.

    - StringBuilder : 동기화를 보장하지 않는 특징이 있습니다. 이 때문에 multi Thread환경에서는 문제가 발생하지만, single Thread환경이라면 안전하게 사용할 수 있고, 더 빠르게 생성할 수 있습니다.

    - StringBuffer : 동기화를 보장합니다. 이 때문에 multi Thread환경에서 안전하며, StringBuilder보다 약간 느립니다. (어떤 Thread환경이냐에 따라 둘중 하나를 고르면 됩니다.)

    댓글

Designed by Mhwan.