✔️ 문자배열
: 문자열을 다루기 위한 클래스
C언어에서는 문자열을 char형 배열로 표현하지만, 자바에서는 문자열을 위한 String이라는 클래스를 별도로 제공한다.
String 클래스에는 문자열과 관련된 작업을 할 때 유용하게 사용할 수 있는 다양한 메소드가 포함되어 있으며 이러한 String 클래스는 java.lang 패키지에 포함되어 제공된다.
❗️불변 객체(immutable object)
- String 인스턴스는 한 번 생성되면 그 값을 읽기만 할 수 있고, 변경할 수는 없다.
- 덧셈(+) 연산자를 이용하여 문자열 결합을 수행하면, 기존 문자열의 내용이 변경되는 것이 아니라 내용이 합쳐진 새로운 String 인스턴스가 생성된다.
그래서 덧셈 연사자(+)를 이용한 문자열 결합은 계속해서 해로운 인스턴스를 생성하기 때문에 성능이 떨어진다.
문자열의 결합이나 변경이 잦다면, 내용을 변경가능한 StringBuffer
를 사용해야 한다.
이 두 식을 비교해보자.
String str = "abc";
String str = new String("abc");
String str1 = "abc"; // 문자열 리터럴 "abc"의 주소가 str1에 저장됨
String str2 = "abc"; // 문자열 리터럴 "abc"의 주소가 str2에 저장됨
String str3 = new String("abc");// 새로운 String인스턴스를 생성
String str4 = new String("abc");// 새로운 String인스턴스를 생성
String literal로 생성한 객체는 내용이 같다면 같은 객체, 즉 동일한 메모리 주소를 가리키고 있다.
하지만 new 연산자로 생성한 String 객체는 내용이 같더라도 개별적인 객체임을 알 수 있다.
여기에는 어떤 원리가 숨어있는지, 밑의 JVM 구조와 함께 이해해 보도록 하자.
❗️ ' == ' 가 아닌 equals() 를 통해 내용을 비교
==
은 주소값을 비교한다.
- String literal은 같은 주소값을 가지고 있으므로
True
가 나온다.- new 연산자로 생성한 String 객체는 다른 주소값을 나타내기 때문에
False
가 나온다.
new 연산자로 String 객체를 생성하지 않는 것이 좋다는 말을 자주 한다.
그 이유는, String literal로 생성하면 해당 String 값은 Heap 영역 내 String Constant Pool
에 저장되어 재사용되지만, new 연산자로 생성하면 같은 내용이라도 여러 개의 객체가 각각 Heap 영역을 차지하기 때문이다.
(그림에는 생략되어 있지만) 생성된 String 객체는 Stack
영역에 저장된다.
Heap 영역에는 "Cat", "Dog"과 같은 '값'들이 들어가게 되는데, 그림의 우측을 보면 중요한 차이를 발견할 수 있다.
String literal로 생성한 객체는 String Pool
에 들어간다.
String literal로 생성한 객체의 값(ex. "Cat")이 이미 String Pool
에 존재한다면, 해당 객체는 String Pool의 reference를 참조한다. 그림에서 s1과 s2가 같은 곳을 가리키고 있는 것도 이 때문이다.
new 연산자로 생성한 String 객체는 같은 값이 String Pool에 이미 존재하더라도, Heap영역 내 별도의 객체를 가리키게 된다.
String str = ""; // Str을 빈 문자열로 초기화
char[] chArr = new char[0]; // 크기가 0인 char배열
int[] iArr = {}; // 크기가 0인 int배열
String s = null; // 별로
char c = '\u000'; // 별로
String s = ""; // 빈 문자열로 초기화
char c = ' '; // 공백으로 초기화
String str1 = ""; // String str1 = new String(""); X
String str2 = ""; // String str2 = new String(""); X
String str3 = ""; // String str3 = new String(""); X
char[] c = {'H','e','l','l','o'};
String s = new String(c); // Hello
반대로 String을 char로 바꿀때는 toCharArray()
를 쓴다.
StringBuffer
(내용변경 가능한 문자열) 인스턴스가 갖고 있는 문자열과 같은 내용의 String 인스턴스를 생성한다.
StringBuffer
➡️ String
StringBuffer sb = new StringBuffer("Hello");
String s = new String(sb);// Hello
IndexOutOfBoundsException
오류가 발생한다.String s = "Hello";
String n = "0123456"
char c = s.charAt(1); // e
char c2 = n.charAt(1); // 1
String str = new String("Java");
System.out.println("원본 문자열 : " + str);
for (int i = 0; i < str.length(); i++) {
System.out.print(str.charAt(i) + " ");
}
System.out.println("\ncharAt() 메소드 호출 후 원본 문자열 : " + str);
[결과값]
원본 문자열 : Java
J a v a
charAt() 메소드 호출 후 원본 문자열 : Java
❗️ 만약 문자열을 비교할 때 대소문자를 구분하지 않기를 원한다면,
compareToIgnoreCase()
메서드를 사용하자.
int i = "aaa".compareTo("aaa"); // 0
int i2 = "aaa".compareTo("bbb"); // -1
int i3 = "bbb".compareTo("aaa"); // 1
String str = new String("abcd");
System.out.println("원본 문자열 : " + str);
System.out.println(str.compareTo("bcef"));
System.out.println(str.compareTo("abcd") + "\n");
System.out.println(str.compareTo("Abcd"));
System.out.println(str.compareToIgnoreCase("Abcd"));
System.out.println("compareTo() 메서드 호출 후 원본 문자열 : " + str);
String s = "Hello";
String s1 = s.concat(" World"); // Hello World
String str = new String("Java");
System.out.println("원본 문자열 : " + str);
System.out.println(str.concat("수업"));
System.out.println("concat() 메소드 호출 후 원본 문자열 : " + str);
[결과값]
원본 문자열 : Java
Java수업
concat() 메소드 호출 후 원본 문자열 : Java
String s = "abcdefg";
boolean b = s.contains("bc"); // true
❗️
interface CharSequence
을 구현한 class들
: CharBuffer, Segment, String, StringBuffer, StringBuilder
String s = "java.lang.Object";
boolean b = s.startsWith("java"); // true
boolean b2 = s.starsWith("lang"); // false
String file = "Hello txt";
boolean b = file.endsWith("txt"); // true
String s = "Hello";
boolean b = s.equalsIgnoreCase("HELLO"); // true
boolean b2 = s.equalsIgnoreCase("hello"); // true
String s = "Hello";
int idx1 = s.indexOf('o'); // 4
int idx2 = s.indexOf('k'); // -1
String s = "Hello";
int idx1 = s.indexOf('e', 0); // 1
int idx2 = s.indexOf('e', 2); // -1 index가 2인 'l'을 시작기점으로 'e'를 찾음
String s = "java.lang.Object"
int idx1 = s.lastIndexOf('.'); // 9
int idx2 = s.indexOf('.'); // 4
s.lastIndexOf('.')
의 조심해야할 점은, 인덱스의 번호는 고정이기 때문에 뒤에서 셀때에 0부터 세면 안된다. s의 길이인 15부터 거꾸로 세야한다.
15(t) ,14(c) ,13(e), 12(j), 11(b), 10(O) ,9(.) ...
❗️ regex
: Regular Expression ➡️ 규칙이 있는 식 (정규식)
String animals = "dog,cat,bear";
String[] arr = animals.split(",");
// arr[0] = "dog"
// arr[1] = "cat"
// arr[2] = "bear"
String animals = "dog,cat,bear";
String[] arr = animals.split(",", 2); // 두 부분으로 나눈다.
//arr[0] = "dog"
//arr[1] = "cat,bear"
❗️ begin <= x < end
String s = "java.lang.Object";
String c = s.substring(10); // Object
String p = s.substring(5, 9) // lang
String s = "Hello";
String s1 = s.toLowerCase(); // hello
String s = "Hello";
String s1 = s.toUpperCase(); // HELLO
String s = " Hello World ";
String s1 = s.trim() // Hello World
String str = new String(" Java ");
System.out.println("원본 문자열 : " + str);
System.out.println(str + '|');
System.out.println(str.trim() + '|');
System.out.println("trim() 메소드 호출 후 원본 문자열 : " + str);
[결과값]
원본 문자열 : Java
Java |
Java|
trim() 메소드 호출 후 원본 문자열 : Java
toString()
을 호출한 결과를 반환한다.String b = String.valueOf(true); // "true"
String c = String.valueOf('a'); // "a"
java.util.Date dd = new java.util.Date();
String date = String.valueOf(dd); // "Wed Jan 27 21:26:29 KST 2022"
References
: https://starkying.tistory.com/entry/what-is-java-string-pool
: https://cafe.naver.com/javachobostudy