본문 바로가기
Language/Java

[Java] Number와 String 특징 정리 - Primitive, Autoboxing, String, StringBuilder, StringBuffer

by 돈코츠라멘 2019. 9. 8.

Java Numbers and String

개발 시 성능에 영향을 줄 수 있으므로 꼭 알아야 할 Number와 String의 특징들을 정리해두었다.


Numbers

Primitive Type & Boxed Primitive Type

Primitive type Wrapper class
boolean Boolean
byte Byte
char Character
float Float
int Integer
long Long
short Short
double Double

Java에는 기본 자료형인 Primitive type과 객체로 제공되는 Wrapper class(=Boxed Primitive Type)가 있다. Primitive에서 Boxed Primitive로의 전환(boxing), Boxed Primitive에서 Primitive로의 전환(Unboxing)은 Java 1.5 이상부터 자동으로 지원된다.

  • 명시적으로 null을 반환해야 하는 경우: Primitive Type은 default value(int는 0, boolean은 false 등)를 가지고 있기 때문에 원하는 값이 반환되지 않을 수도 있다.
  • Collection의 key/value로 사용되는 경우: Collection에는 Primitive를 넣을 수 없다.
  • Parameterized Type의 경우

Autoboxing and Unboxing

성능

public static void main(String[] args) {
    Long sum = 0L;
    for (long i=0; i<Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

위 코드는 오류를 발생시키지 않는다. 하지만 sum += i 수행 시 Long이 계속해서 AutoUnboxing되어 매번 Primitive로 변환된다. 이로 인해 성능 저하가 발생할 수 있다.

  • 실험
long start = System.currentTimeMillis();
Long sum1 = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
    sum1 += i;
}
System.out.println("Long: " + (System.currentTimeMillis() - start));

start = System.currentTimeMillis();
long sum2 = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
    sum2 += i;
}
System.out.println("long: " + (System.currentTimeMillis() - start));
  • 결과 (약 7배 차이)
Long: 9417
long: 1389

Caching

Boxed Primitive에서는 자주 사용되는 범위의 값에 대해 미리 생성해놓은(=Caching 된) 객체를 가지고 있다. 이로 인해 아래와 같이 예상치 못한 결과를 낼 수 있으니 주의해야 한다. Caching 되는 범위는 JVM option으로 조절할 수 있다.

Integer caching1 = 127;
Integer caching2 = 127;
System.out.println(caching1 == caching2); // true

Integer outOfCaching1 = 128;
Integer outOfCaching2 = 128;
System.out.println(outOfCaching1 == outOfCaching2); // false

(참고) 객체에서의 == 비교는 객체의 값의 비교가 아닌 레퍼런스의 비교이다. 따라서 Caching 되는 범위 값 내에서는 미리 생성해놓은 객체를 반환하므로 같은 레퍼런스라서 true를 반환한다.


Strings

String은 특별한 참조 자료형이다. new 생성자를 이용해서 인스턴스를 생성하고 Heap 영역에서 관리 된다는 사실은 다른 참조 자료형과 같다. 하지만 String은 immutable 하다는 특징을 가진다.

String Pool

String s1 = "Hi";
String s2 = new String("Hi");
String s3 = new String("Hi").intern();

// s1, s2 비교
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true

// s2, s3 비교
System.out.println(s2 == s3); // false
System.out.println(s2.equals(s3)); // true


// s1, s3 비교
System.out.println(s1 == s3); // true
System.out.println(s1.equals(s3)); // true

위에서 == 비교는 객체의 레퍼런스의 비교라고 설명하였다. 위 코드의 결과로 String s1 = "Hi"로 생성한 String과 String s2 = new String("Hi")로 생성한 String이 다른 레퍼런스를 가진다는 것을 알 수있다.

(참고) Heap 영역이 뭔지 모른다면 먼저 Java 메모리 구조로!

String s1 = "Hi"로 String 객체를 생성하면 intern() 메소드가 내부적으로 호출되면서 Heap 영역의 Permanent 영역에 있는 String Pool에 객체가 생성된다. String Pool에 등록되면 프로세스가 종료될 때까지 유지된다. 그 이후부터 새로운 String이 선언되면 String Pool에 존재하는지 체크하고 같은 값이 존재하면 처음 등록된 String을 계속 사용한다. 반면 String s2 = new String("Hi")로 생성된 String은 새로운 메모리 영역에 할당된다. 그러므로 같은 값을 String s5 = new String("Hi")으로 다시 선언하더라도 새로운 메모리 영역에 할당되므로 s2 == s5 연산으로 비교하면 결과가 false가 나올 것이다. 하지만 new를 통해 생성하더라도 명시적으로 intern() 메소드를 사용하면 String Pool을 사용하게 된다.

String vs StringBuffer vs StringBuilder

모두 문자열을 저장하고 관리하는 클래스이지만 차이점이 있다.

String StringBuffer StringBuilder
immutable mutable mutable
Thread Safe Thread Safe  

String은 immutable 하므로 한번 생성되면 할당된 메모리 공간이 변하지 않는다. + 연산자나 concat 메소드로 기존에 생성된 String에 다른 문자열을 붙여도 기존 값 뒤에 추가되는 게 아니라 새로운 String 객체를 만든 후 새 String 객체에 연결된 문자열을 저장하고 그 객체를 참조하도록 하는 방식이다. 그러므로 문자열 연산이 많은 경우 성능이 좋지 못하다. 하지만 Thread Safe 하므로 내부 데이터를 자유롭게 공유할 수 있다.
반면 StringBuffer와 StringBuilder는 mutable 하며 두 클래스가 제공하는 메소드는 같다. 이 두 클래스 사이에는 "동기화 여부"라는 차이점이 존재한다. StringBuffer는 메소드마다 synchronized keyword가 존재하여 Multi Thread 환경에서 동기화를 지원한다. 하지만 StringBuilder는 동기화를 지원하지 않는다(그래서 연산 성능만 비교하면 StringBuilder>StringBuffer>>>String).

따라서 위 세 개의 클래스를 상황에 맞게 골라서 사용해야 한다.

  • String: 문자열 연산이 적은 Multi Thread 환경
  • StringBuffer: 문자열 연산이 많은 Multi Thread 환경
  • StringBuilder: 문자열 연산이 많은 SingleThread 환경

 


Reference

  1. https://docs.oracle.com/javase/tutorial/java/data/numberclasses.html
  2. http://www.jpstory.net/2013/02/07/primitive-vs-boxed-primitives/

댓글