(클래스로더 공부해서 좀 더 추가하기)
C언어에서는 문자열을 char형의 배열로 다루었으나 Java에서는 문자열을 위한 String클래스를 제공한다.
- String클래스는 문자열을 저장하고 이를 다루는데 필요한 메서드도 함께 제공
- 변경 불가능한(immutable) 클래스이다.
- 변경이 불가능한 클래스로 자바에서 불변 클래스의 종류로는 대표적으로 String, Boolean, Integer, Float, Long 등이 있다.
String클래스에는 문자열을 저장하기 위해서 문자형 배열 참조변수(byte[]) value를 인스턴스 변수로 정의해놓고 있다.
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
/*
@Stable값이 변경되지 않고 정확히 동일한 값을 반환한다고 컴파일러에 알리는 주석입니다.
이것은 항상 정적인 함수나 값에만 적용되어야 합니다.
*/
- 인스턴스 생성 시 생성자의 매개변수로 입력받는 문자열은 이 인스턴스변수(value)에 문자형 배열(byte[])로 저장되는 것이다.
💡 String클래스는 앞에 final이 붙어 있으므로 다른 클래스의 부모가 될 수 없다. (상속 불가)
변경 불가능한(immutable) 클래스라고 말한 이유!!!
- 한번 생성된 String인스턴스가 갖고 있는 문자열은 읽어 올 수만 있고, 변경할 수는 없다.
String str = "a";
str += 'b';
‘+’연산자를 이용해서 문자열을 결합한 경우 인스턴스 내의 문자열이 바뀌는 것이 아니라 새로운 문자열(”ab”)이 담긴 String인스턴스가 생성되는 것이다.
- ‘+’연산자를 사용해서 문자열을 결합하는 것은 매 연산 시 마다 새로운 문자열을 가진 String인스턴스가 생성되어 메모리 공간을 차지하게 되므로 가능한 결합횟수를 줄이는 것이 좋다.
- 문자열 간의 결합이나 추출등 문자열을 다루는 작업이 많이 필요한 경우는?
- String클래스 대신 StringBuffer클래스를 사용하자
- StringBuffer인스턴스에 저장된 문자열은 변경이 가능해 하나의 StringBuffer인스턴스만으로 문자열을 다루는 것이 가능하다.
문자열(String)의 비교
문자열을 만들 때는 문자열 리터럴을 지정하는 방법과 String클래스의 생성자를 사용해서 만드는 방법이 있다.
Stirng str1 = "abc";
String str2 = new String("abc");
- String클래스의 생성자를 이용한 경우 new연산자에 의해서 메모리할당이 이루어지면서 항상 새로운 String인스턴스가 생성된다.
- 문자열 리터럴은 이미 존재하는 것을 재사용하는 것이다.
public static void main(String[] args) {
String str1 = "abc";
String str2 = new String("abc");
String str3 = "abc";
String str4 = new String("abc");
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
System.out.println(System.identityHashCode(str3));
System.out.println(System.identityHashCode(str4));
}
/* 출력
453211571
796684896
453211571
757108857
*/
str1, str3은 “abc”가 저장되어 있는 메모리주소를 참조 (453211571번지)
str2, str4은 new연산자에 의해서 메모리할당이 이루어지면서 항상 새로운 객체의 주소를 참조변수가 참조한다.
- equals()를 사용하면 String에서 오버라이딩 했기 때문에 값만 비교하므로 전부 “abc” → true
- String인스턴스의 주소를 등가비교연산자 ‘==’로 비교했을 때는 결과가 다르다.
- System.*out*.println(str1 == str3 ? true : false); → true
- System.*out*.println(str2 == str4 ? true : false); → false
문자열 리터럴(String리터럴)
Java 소스파일에 포함된 모든 문자열 리터럴은 컴파일 시에 클래스 파일에 저장된다.
- 같은 내용의 문자열 리터럴은 한번만 저장된다.
- 문자열 리터럴도 String인스턴스이고, 한번 생성하면 내용을 변경할 수 없으니 하나의 인스턴스를 공유하면 되기 때문이다.
public static void main(String[] args) {
Scanner s1 = new Scanner(System.in);
String str1 = "abc";
String str2 = s1.nextLine();
String str3 = "a"+"bc";
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
System.out.println(System.identityHashCode(str3));
}
/* 출력
abc -> 사용자 입력
abc
abc
abc
356473385 "abc"
2136344592
356473385 "a" + "bc"
*/
- ‘+’연산자를 사용해서 문자열을 결합하는 것은 매 연산 시 마다 새로운 문자열을 가진 String인스턴스가 생성되어 메모리 공간을 차지하게 되니까 달라질거라고 생각했는데 같은 내용의 문자열 리터럴은 한번만 저장되니까 컴파일 때 찾아서 하나만 저장하는거 같다.
- scanner통해서 받으면 컴파일 이후 사용자 입력이 들어오면 그 때 채워지는걸로 알고 있어서 해봤는데 (C언어 링킹과정 느낌 아닐 수 있음) 생각해보니.. 리터럴도 아니고 Scanner클래스에서 받아오는거니까 당연히 아닌…
Scanner 보다는 성능상의 이유로 BufferedReader 사용하는 것이 좋다.
컴파일 → 클래스 파일 생성
클래스 파일이 클래스 로더에 의해 메모리에 올라갈 때, 클래스 파일의 리터럴들이 JVM내에 있는 ‘상수 저장소(constant pool)’에 저장된다. (클래스 로더 공부해서 올려볼게요..)
빈 문자열(empty String)
public static void main(String[] args) {
char[] test = new char[0];
//길이가 0인 배열을 생성할 수 있다.
int[] test1 = {};
}
길이가 0인 배열을 내부적으로 가지고 있는 문자열이 바로 빈 문자열이다.
💡String str = ""; 참조변수 s가 참조하고 있는 String인스턴스는 내부에 ‘new char[0]’과 같이 길이가 0인 char형 배열을 저장하고 있는 것이다.
String s = ""; //빈 문자열로 초기화 (Stirng s = null;)
char c = ''; //(x)char형 변수에는 반드시 하나의 문자를 지정해야 한다.
char c = ' '; //(O)공백은 가능하다.(char c = '\\u0000';
- 변수를 선언할 때, 각 타입의 기본값으로 초기화 하지만 String은 참조형 타입의 기본값인 null 보다는 빈 문자열로, char형은 기본값인 ‘\u0000’대신 공백으로 초기화 하는것이 보통이다.
join()과 StringJoiner
join()은 여러 문자열 사이에 구분자를 넣어서 결합한다. (구분자로 문자열을 가르는 split()과 반대 작업으로 이해)
public static void main(String[] args) {
String books = "자바의정석,클린코드,DDD,TDD";
String[] arr = books.split(","); //,구분자를 통해서 배열에 하나씩 넣음
String str = String.join("-",arr); // 배열하나 꺼내고 구분자 넣고 배열꺼내고
System.out.println(str);
}
// 자바의정석-클린코드-DDD-TDD
java.util.StringJoiner클래스로 문자열 결합
public static void main(String[] args) {
StringJoiner sj = new StringJoiner(",","[","]");
String[] books = { "자바의정석","클린코드","DDD","TDD" };
for (String s : books){
sj.add(s); //add(s.toUpperCase())사용 가능
}
System.out.println(sj); //[자바의정석,클린코드,DDD,TDD]
}
join(),StringJoiner클래스는 JDK1.8부터 추가
String클래스 자주사용하는 메서드 조그만.... 너무많..
메서드 | 설명 |
char charAt(int index) | 문자열에서 해당 위치(index)에 있는 문자를 반환 |
int length() | 문자열의 길이를 반환 |
String substring(int 원하는 시작 인덱스, int 원하는 마지막 인덱스 | 문자열에서 내가 지정한 범위의 문자열을 반환(원하는 마지막 인덱스 문자는 반환 안된다) |
boolean equals(Object obj) | 문자열의 내용이 같은지 확인(같으면 true, 틀리면 false) |
char[] toCharArray() | 문자열을 문자배열(char[])로 변환해서 반환 |
StringBffer
내부적으로 문자열 편집을 위한 Buffer를 가지고 있어 StringBuffer인스턴스를 생성할 때 그 크기를 지정할 수 있다.
- String은 인스턴스 생성하고 문자열 변경 불가
- 편집할 문자열의 길이를 고려하여 버퍼의 길이를 충분히 잡아주는 것이 좋다.
- 문자열이 버퍼의 길이를 넘으면 버퍼의 길이를 늘려주는 작업이 추가로 수행되기 때문
- StringBuffer클래스도 문자열을 저장하기 위한 char형 배열의 참조변수를 인스턴스 변수로 선언해 놓음
즉 StringBuffer클래스의 인스턴스 생성할 때, 적절한 길의의 char형 배열이 생성되고, 이 배열은 문자열을 저장하고 편집하기 위한 공간(buffer)으로 사용된다.
💡 StringBuffer인스턴스 생성할 때, 버퍼의 크기를 지정해주지 않으면 16개의 문자를 저장할 수 있는 크기의 버퍼를 생성한다.
버퍼의 크기가 작업하려는 문자열의 길이보다 작을경우 내부적으로 버퍼의 크기를 증가시키는 작업이 수행된다.
- 배열의 길이는 변경될 수 없으므로 새로운 길이의 배열을 생성한 후에 이전 배열의 값을 복사해야 한다.
//새로운 길이(newCapacity)의 배열을 생성한다. newCapacity는 정수값
char[] newValue = new char[newCapacity];
// 배열 value의 내용을 배열 newValue로 복사한다.
System.arraycopy(value,0,newValue,0,count);//count는 문자열의 길이
value = newValue //새로 생성된 배열의 주소를 참조변수 value에 저장.
- StringBuffer클래스의 인스턴스 변수 value는 길이가 증가된 새로운 배열을 참조하게 된다.
System.arraycopy
System.arraycopy( 배열1, 배열1의 복사시작번호, 배열2, 배열2의 복사시작번호, 복사길이)의 형태를 띈다.
예제)
A={a, b, c, d, e};
B={가, 나, 다, 라};
System.arraycopy(A, 1, B, 3, 2); //A[1]부터 B[3]에 길이 2만큼 복사된다는 의미다.
B[3]=A[1] //B[3]에 A[1]의 값이 복사됨
B[4]=A[2] //길이가 2니까 그 다음 값인 B[4]에 A[2]
B={ B[0], B[1], B[2], A[1], A[2] }; //배열값은 { 가, 나, 다, b, c }가 된다.
한국말을 못해서 죄송합니다.. 그래도 예제 보면 이해할 수 있을거라고 생각합니다. >_<
StringBuffer의 변경 - append()
매개변수로 입력된 값(숫자,boolean)들도 문자열로 변환하여 StringBuffer인스턴스가 저장하고 있는 문자열 뒤에 덧붙인다.
StringBuffer sb = new StringBuffer("java");
sb.append("의"); //반환타입이 StringBuffer인데 자신의 주소를 반환
StringBuffer sb2 = sb.append(" ").append("정석");
//sb.append == sb 이기 때문에 연속적으로 append()를 호출하는것이 가능하다.
System.out.println("sb = " + sb);
System.out.println("sb2 = " + sb2); //sb와 같은 인스턴스라 출력이 동일하다.
💡 StringBuffer클래스에는 append()처럼 객체 자신을 반환하는 메서드들이 많이 있다.
StringBuffer의 비교
String클래스에서는 equals메서드를 오버라이딩해서 문자열의 내용을 비교하도록 구현되어 있지만 StringBuffer클래스는 equals메서드를 오버라이딩하지 않았다.
StringBuffer sb = new StringBuffer("abc");
String sb1 = sb.toString();
String sb2 = sb.toString();
System.out.println(sb1.equals(sb2)); //true 반환
- StringBuffer클래스는 equals메서드를 사용해도 등가비교연산자(==)로 비교한 것과 같은 결과를 얻는다.
- toString()은 오버라이딩 되어 있어 담고있는 문자열을 String으로 반환한다.
- 비교할 때는 toString으로 String인스턴스를 얻은 다음 equlas메서드를 사용해서 비교해야 한다.
추가적으로 ~ StringBuilder 살짝 말하면
멀티쓰레드에 안전(thread safe)하도록 동기화되어 있다. (동기화 → StringBuffer의 성능 떨어뜨림)
- 멀티쓰레드로 작성된 프로그램이 아닌 경우, StringBuffer의 동기화는 불필요하게 성능만 떨어뜨린다.
- StringBuffer에서 쓰레드의 동기화만 뺀 StringBuilder 나온이유
StringBuilder는 StringBuffer와 완전히 똑같은 기능으로 작성되어 있다.
💡 성능향상이 반드시 필요한 경우를 제외하고는 기존에 작성한 코드에서 StringBuffer를 StringBuilder로 굳이 바꿀 필요는 없다.
'책 > Java의 정석' 카테고리의 다른 글
[java] 래퍼(wrapper)클래스 && Number클래스 (0) | 2023.02.10 |
---|---|
[java] Object클래스 (0) | 2023.02.09 |
[java] 예외처리(3) (0) | 2023.02.07 |
[java] 예외처리(2) (0) | 2023.02.07 |
[java] 예외처리(1) (0) | 2023.02.07 |