본문 바로가기

PROGRAMMING/JAVA

[ JAVA 정복 ] java.lang 패키지와 유용한 클래스

오늘은 자바의 정석 Chapter 9장에 있는 내용 중 몇가지만 다뤄볼 예정이다.


1. String, StringBuffer, StringBuilder 차이

(1) String

: String은 new 연산자를 통해 생성되면 그 인스턴스의 메모리 공간은 절대 변하지 않는다.

+나 concat을 이용해 문자열을 변경하는 경우가 있는데 그럴 경우 메모리 공간에 저장된 값이 변하는게 아니라 매번 새로운 String 객체를 new 연산자로 만들어서 새로운 메모리 공간을 만드는 것이다.


* 여기서 잠깐! : 왜 아래 [ 예제 코드 1 ] 에서 String class의 hashCode() 메서드가 아닌 System 클래스의 identityHashCode() 메서드를 사용했을까?


String 클래스는 문자열의 내용이 같으면, 동일한 해시코드를 반환하도록 hashCode 메서드가 오버라이딩되어 있기 때문에 문자열의 내용이 같은 String 객체에 대해서는 동일한 해시코드를 반환한다.

반면에 System.identityHashCode(Object obj)는 Object 클래스의 hashCode 메서드처럼 객체의 주소값으로 해시코드를 생성하기 때문에 모든 객체에 대해 항상 다른 해시코드값을 반환하는 것을 보장한다.


** System.identityHashCode(Object obj)의 호출결과는 실행할 때마다 달라질 수 있다.


[ 예제 코드 1 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class StringAddress {
    public static void main(String[] args) {  
        String abc = "abc";
        System.out.println(abc);
        System.out.println(System.identityHashCode(abc)); 
        System.out.println(); 
 
        abc += "de";
        System.out.println(abc);
        System.out.println(System.identityHashCode(abc));
        System.out.println();
 
        abc = abc.concat("fg");
        System.out.println(abc);
        System.out.println(System.identityHashCode(abc)); 
        System.out.println();
 
        System.out.println("\n---\n");
 
        StringBuffer sb = new StringBuffer();
        
        sb.append("abc");
        System.out.println(sb.toString());
        System.out.println(System.identityHashCode(sb));
        System.out.println();
 
        sb.append("de");
        System.out.println(sb.toString());
        System.out.println(System.identityHashCode(sb));
        System.out.println();
    }
}
cs


[ 예제 코드 1 - 실행 결과 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abc
366712642
 
abcde
1829164700
 
abcdefg
2018699554
 
---
 
abc
1311053135
 
abcde
1311053135
cs


(1-1) String의 장점

: 불변하는 문자열 객체이기 때문에 단순하게 선언하고 읽는 조회 로직에서는 타 클래스보다 빠르게 읽을 수 있음.

(1-2) String의 단점

: 문자열 연산(+나 concat 이용)이 계속 생기게 되면 내부적으로 char 배열을 사용하기 때문에 객체를 만드는 오버헤드가 발생하므로 성능이 떨어짐.

그리고 기존의 문자열은 가비지콜레터에 의해 제거되어야 하기 때문에 언제 제거될 지 몰라 이 자체가 메모리 낭비가 될 수 있는 단점이 있음.


(2) StringBuffer

: StringBuffer는 내부적으로 문자열 편집을 위한 버퍼(buffer)를 가지고 있으며, StringBuffer 인스턴스를 생성할 때 그 크기를 지정할 수 있다.

StringBuffer 클래스의 인스턴스를 생성할 때, 적절한 길이의 char형 배열이 생성되고 이 배열은 문자열을 저장하고 편집하기 위한 공간(buffer)로 사용된다.

생성 시 버퍼의 크기를 지정해주지 않으면 16개의 문자를 저장할 수 있는 크기의 버퍼를 생성하며 StringBuffer 인스턴스로 분자열을 다루는 작업을 할 때, 버퍼의 크기가 작업하려는 문자열의 길이보다 작을 때는 내부적으로 버퍼의 크기를 증가시키는 작업이 수행된다.


(2-1) String과 StringBuffer의 비교

: String 클래스에서는 eqeuals 메서드를 오버라이딩해서 주소값에 상관없이 문자열의 내용을 비교하도록 구현되어 있지만, StringBuffer 클래스는 equals 메서드를 오버라이딩하지 않아서 StringBuffer 클래스의 equals 메서드를 사용해도 등가비교연산자(==)로 비교한 것과 같은 결과를 얻는다.


[ 예제 코드 2 ]

1
2
3
4
5
6
7
8
9
public class StringAddress {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("abc");
        StringBuffer sb2 = new StringBuffer("abc");
        System.out.println(sb == sb2); // false
        System.out.println(sb.equals(sb2)); // false
        System.out.println(sb.toString().equals(sb2.toString())); // true
    }
}
cs


(3) StringBuilder

: StringBuffer는 멀티쓰레드 환경에서 synchronized 키워드가 가능하므로 멀티쓰레드에 안전(thread safe)한 특성을 갖고 있다.

따라서 단일쓰레드 환경에서는 StringBuffer를 사용할 경우 StringBuffer의 동기화 지원때문에 불필요하게 성능만 떨어뜨리게 된다.

이런 부분에서 사용할 수 있도록 StringBuilder가 나온 것이다. StringBuffer에서 쓰레드의 동기화만 구현하지 않는 클래스이기 때문에 완전히 똑같은 기능으로 작성되어 있어 환경에 따라 참조 변수 선언부와 생성자 부분만 달리 적용해주면 된다.


결론을 내자면 문자열 연산이 없는 환경에서는 String을, 문자열 연산이 일어나는 환경에서는 단일쓰레드일 경우 StringBuilder를, 멀티쓰레드일 경우 StringBuffer를 사용해주면 되는 것이다. 사실 StringBuffer 클래스 자체가 충분히 성능이 좋기 때문에 성능 향상이 반드시 필요한 경우를 제외하고는 기존에 작성한 코드에서 StringBuffer를 StringBuilder로 굳이 바꿀 필요는 없다.


출처 : 자바의 정석, http://jeong-pro.tistory.com/85


2. String 클래스의 split과 StringTokenizer

(1) String 클래스의 split(String regex)

: 특정 구분자로 자를 문자열과 구분자를 매개 변수로 넘겨주면 구분자로 문자열을 나눠 String 배열로 반환하는 String 클래스의 메서드이다.

매개변수가 정규식 표현이 가능하므로 복잡한 형태의 구분자로 문자열을 나누어야 할 때 사용하기 좋다.


(2) StringTokenizer 클래스

: 위에서 설명한 split과 똑같은 기능이나 단순한 하나의 문자로 구분자를 둔 경우에만 사용 가능한 클래스이다.


생성자/메서드 

설명 

StringTokenizer(String str, String delim) 

문자열(str)을 지정된 구분자(delim)로 나누는 StringTokenizer를 생성한다.(구분자는 토큰으로 간주되지 않음) 

StringTokenizer(String str, String delim, boolean returnDelims)

문자열(str)을 지정된 구분자(delim)로 나누는 StringTokenizer를 생성한다. retrnDelims의 값을 true로 함녀 구분자도 토큰으로 간주된다. 

int countTokens() 

전체 토큰의 수를 반환한다. 

boolean hasMoreTokens() 

토큰이 남아있는지 알려준다. 

String nextToken() 

다음 토큰을 반환한다. 


(3) split(String regex)와 StringTokenizer 클래스의 차이

(3-1) 반환 타입의 차이

: split() 의 경우 데이터를 토큰으로 잘라낸 결과를 배열에 담아서 반환하고 StringTokenizer 는 데이터를 토큰으로 바로바로 잘라서 반환한다.

사실 데이터의 양이 현저하게 많은 경우가 아니라면 별 문제가 되지 않지만 구현된 방식으로 보자면 새 객체에 담아야 하는 split이 StringTokenizer보다 성능이 떨어질 수 밖에 없다.


(3-2) 구분자

: 다음의 예제코드를 보면 비슷해 보이지만 둘이 전혀 다른 실행 결과를 나타내는데 그 이유는 구분자이다.

아까 말했다시피 StringTokenizer는 단순한 문자 하나의 구분자로 나누는 것이기 때문이다.


[ 예제 코드 3 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Difference {
    public static void main(String[] args) {
        String expression = "x=100*(200+300)/2";
 
// split의 매개변수가 정규식이기 때문에 정규식 표현에 해당하는 것은 이스케이프 문자를 통해 문자열이라는 걸 인식시켜줘야 함.
        String[] delimArr = expression.split("\\+-\\*/=\\(\\)");
        StringTokenizer st = new StringTokenizer(expression, "+-*/=()"true);
        
        for (String splited : delimArr) {
            System.out.println(splited);
        }
 
        System.out.println("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
 
        while (st.hasMoreTokens()) {
            System.out.println(st.nextToken());
        }
    }
}
cs


[ 예제 코드 3 - 실행 결과 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
x=100*(200+300)/2
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
x
=
100
*
(
200
+
300
)
/
2
cs


(3-3) 빈 문자열 인식

: split()은 빈 문자열도  토큰으로 인식하는 반면 StringTokenizer는 빈 문자열을 토큰으로 인식하지 않기 때문에 인식하는 토큰의 개수가 서로 다름.

하지만 마지막의 빈 문자열은 둘 다 인식하지 않음.


[ 예제 코드 4 ]

1
2
3
4
5
6
7
8
9
10
11
public class StringAddress {
    public static void main(String[] args) { 
       String expression = "a,b,c,,,d,e,";
       
       String[] delimArr = expression.split(",");
       StringTokenizer st = new StringTokenizer(expression, ",");
 
       System.out.println("split() 결과 : " + delimArr.length); // split() 결과 : 7
       System.out.println("StringTokenizer 결과 : " + st.countTokens()); // StringTokenizer 결과 : 5
    }
}
cs


출처 : 자바의 정석, http://library1008.tistory.com/16


3. 정규식