Search
Duplicate
📒

[Java Study] 04-2. String 클래스

상태
완료
수업
Java Study
주제
4 more properties
참고

String 클래스

NOTE
불변 객체에 대해 설명하다가 갑자기 String으로 넘어온 이유는 String이 불변 객체이며, 불변 객체를 이해하는 데 가장 적합한 클래스이기 때문입니다.
public static void main(String[] args) { char[] chars = {'h', 'e', 'l', 'l', 'o'}; // char 배열 System.out.println(chars); String hello = "hello"; // String 방식(문자열 리터럴 방식) System.out.println("hello = " + hello); }
Java
복사
문자열 타입 char, String
public final class String { //문자열 보관 private final char[] value;// 자바 9 이전 private final byte[] value;// 자바 9 이후 //여러 메서드 public String concat(String str) {...} public int length() {...} ... }
Java
복사
String 클래스 구조
String은 char와 달리 클래스이며, 기본형이 아닌 참조형입니다. 그럼 어떻게 문자열 리터럴 방식처럼 할당하는 걸까요?
자바에서 문자열은 자주 사용되므로 “”로 감싸면 자바 언어가 자동으로 변환해주기 때문입니다.

String 클래스 비교

NOTE
String을 생성할 때 new String() 방식과 문자열 리터럴을 할당하는 방식은 내부 동작에서 큰 차이가 있습니다. 이에 대한 개념은 각 방식으로 생성한 String을 비교하면서 이해할 수 있습니다.
public static void main(String[] args) { // new String 비교, ==, equals String str1 = new String("hello"); String str2 = new String("hello"); System.out.println("new String() == 비교: " + (str1 == str2)); // false System.out.println("new String() equals 비교: " + (str1.equals(str2))); // true // 문자열 리터럴 비교, == , equals String str3 = "hello"; String str4 = "hello"; System.out.println("리터럴 == 비교: " + (str3 == str4)); // true System.out.println("리터럴 equals 비교: " + (str3.equals(str4))); // true }
Java
복사
new String() vs 문자열 리터럴

new String()

new String() 비교
str1str2는 각각 생성자를 통해 만들어진 서로 다른 인스턴스입니다. 따라서 동일성(==) 비교에 실패합니다.
str1str2는 논리적으로 동일한 값인 “hello”를 가지고 있습니다. 따라서 동등성(equals()) 비교에 성공합니다.

문자열 리터럴

문자열 리터럴 비교
str3str4는 문자열 리터럴을 통해 생성되었습니다. 이 경우, 자바는 메모리 효율성과 성능 최적화를 위해 문자열 풀을 사용하여 미리 할당된 String 객체를 사용합니다. 따라서 동일성(==) 비교에서 같다고 판단됩니다.
str3str4는 동일한 값인 “hello”를 가지므로 논리적으로 같습니다. 그렇기 때문에 동등성(equals()) 비교에서도 같다고 판단됩니다.
문자열 풀은 필요한 String 인스턴스를 미리 생성하여 여러 곳에서 재사용할 수 있게 해줍니다. 이는 동일한 문자를 사용하는 경우 메모리 사용량을 줄이고, 문자를 생성하는 시간을 단축시켜 성능을 최적화할 수 있게 합니다.

String 불변객체

NOTE
String은 기본적으로 불변객체이므로 값을 변경할 수 없습니다. 따라서 값을 수정하려면 새로운 객체를 반환해주어야 합니다.
public static void main(String[] args) { String str1 = "hello"; str1.concat(" java"); System.out.println("str1 = " + str1); // str1 = hello String str2 = str1.concat(" java"); System.out.println("str2 = " + str2); // str2 = hello java }
Java
복사
str1이 값이 그대로인 이유는 새로운 객체를 받지 않았기 때문.
불변객체는 언제나 값을 변경할때 새로운 객체를 만들어서 반환합니다.

String 주요메서드

NOTE
String에서는 매우 많은 기본 메서드를 제공해주고 있습니다. 외우지는 말고 어떠한 메서드가 있는지 알아보는정도로 넘어가시면 됩니다.
public static void main(String[] args) { String str = "Hello World"; String anotherStr = "hello world"; String emptyStr = ""; String whitespaceStr = " "; // 문자열 정보 조회 System.out.println("Length " + str.length()); System.out.println("isEmpty " + emptyStr.isEmpty()); System.out.println("isBlank " + whitespaceStr.isBlank()); // java 11 System.out.println("charAt " + str.charAt(1)); // 문자열 비교 System.out.println("equals " + str.equals(anotherStr)); System.out.println("equalsIgnoreCase " + str.equalsIgnoreCase(anotherStr)); System.out.println("compareTo " + str.compareTo(anotherStr)); System.out.println("compareToIgnoreCase " + str.compareToIgnoreCase(anotherStr)); System.out.println("startWith " + str.startsWith("Hell")); System.out.println("endsWith " + str.endsWith("orld")); // 문자열 검색 System.out.println("contains " + str.contains("World")); System.out.println("indexOf " + str.indexOf("o")); System.out.println("lastIndexOf " + str.lastIndexOf("o")); // 문자열 조작 및 변환 System.out.println("substring " + str.substring(6)); System.out.println("concat " + str.concat(" Again")); System.out.println("replace " + str.replace("World", "Java")); System.out.println("replaceAll " + str.replaceAll("o", "O")); System.out.println("replaceFirst" + str.replaceAll("o", "O")); System.out.println("toLowerCase " + str.toLowerCase()); System.out.println("toUpperCase " + str.toUpperCase()); System.out.println("trim " + whitespaceStr.trim()); System.out.println("strip " + whitespaceStr.strip()); // java 11 // 문자열 분할 및 조합 String[] parts = str.split(" "); for (String part : parts) { System.out.println("split " + part); } System.out.println("join " + String.join("-", "2024", "03", "31")); System.out.println("join " + String.join(" ", parts)); // 기타 유틸리티 System.out.println("valueOf " + String.valueOf(123)); char[] charArray = str.toCharArray(); System.out.println("toCharArray " + Arrays.toString(charArray)); System.out.println("format: " + String.format("Name: %s, Age: %d", "Alice", 30)); System.out.println("matcher " + "12345".matches("\\d+")); // 정규표현식 일치확인 }
Java
복사

StringBuilder - 가변 String

NOTE
불변인 String 클래스는 값이 변경될 때마다 새로운 객체를 생성하는 불변 클래스의 단점을 가지고 있습니다.
String str = "A" + "B" + "C" + "D"; String str = String("A") + String("B") + String("C") + String("D"); String str = new String("AB") + String("C") + String("D"); // 1 String str = new String("ABC") + String("D"); // 2 String str = new String("ABCD"); // 3
Java
복사
+연산이 수행될 때마다 객체가 생성되므로, 총 3개의 String 클래스가 추가로 생성됩니다.
하지만 최종적으로는 마지막의 new String("ABCD")만 사용됩니다.
결과적으로 중간에 만들어진 객체들은 제대로 사용되지 않고 GC에 의해 처리됩니다.
이 문제를 해결하는 방법은 가변 String을 사용하는 것입니다. 가변의 경우 단순히 값을 변경하면 되므로, 새로운 객체를 생성할 필요가 없습니다.
public static void main(String[] args) { StringBuilder sb = new StringBuilder(); sb.append("A"); sb.append("B"); sb.append("C"); sb.append("D"); System.out.println("sb = " + sb); sb.insert(4, "Java"); // 추가 System.out.println("insert = " + sb); sb.delete(4, 8); // 제거 System.out.println("delete = " + sb); sb.reverse(); // 뒤집기 System.out.println("reverse = " + sb); String string = sb.toString(); //StringBuilder -> String System.out.println("string = " + string); }
Java
복사
String은 불변합니다. 즉, 한 번 생성되면 그 내용을 변경할 수 없습니다. 따라서 문자열에 변화를 주려면 매번 새로운 String 객체가 생성되고, 기존 객체는 버려집니다. 이 과정에서 메모리와 처리 시간을 더 많이 소모합니다.
StringBuilder는 가변적입니다. 하나의 StringBuilder 객체 안에서 문자열을 추가, 삭제, 수정할 수 있으며 이때마다 새로운 객체를 생성하지 않습니다. 이로 인해 메모리 사용을 줄이고 성능을 개선할 수 있습니다.

String 최적화

NOTE
자바는 컴파일러에서 문자열과 관련되어서 최적화 자동을 자동으로 수행줍니다.
String helloWorld = "Hello, " + "World!"; // 컴파일 전 String helloWorld = "Hello, World!"; // 컴파일 후
Java
복사
문자열 리터럴의 경우 자동으로 합친다.
String result = str1 + str2; // 컴파일 전 String result = new StringBuilder() .append(str1) .append(str2) .toString(); // 컴파일 후
Java
복사
문자열 변수의 경우 버전마다 최적화가 달라질 수 있습니다.
자바에서 이미 최적화를 다 해결해주는데, 그럼에도 불구하고 가변 String을 별도로 사용해야 하는 상황이 있을까요? 이런 생각이 들 수 있지만, 최적화가 제대로 이루어지지 않는 상황도 발생합니다.
public static void main(String[] args) { long startTime = System.currentTimeMillis(); String result = ""; // 원본 코드 for (int i = 0; i < 100000; i++) { result += "Hello Java "; } // 자동 최적화 코드(버전마다 다름) => 결국 i 범위만큼 StringBuilder를 생성한다. for (int i = 0; i < 100000; i++) { result = new StringBuilder().append(result).append("Hello Java ").toString(); } // StringBuilder를 직접 사용하여 최적화하는 경우 StringBuilder sb = new StringBuilder(); for (int i = 0; i < 100000; i++) { sb.append("Hello Java "); } long endTime = System.currentTimeMillis(); System.out.println("result = " + result); System.out.println("time = " + (endTime - startTime) + "ms"); }
Java
복사
반복문에서 문자를 반복해서 연결하는 경우
조건문을 통해 문자열을 동적으로 조합하는 경우
복잡한 문자열의 특정 부분을 변경해야 하는 경우
매우 긴 대용량 문자열을 다루는 경우

StringBuilder vs StringBuffer

StringBuilder와 동일한 기능을 수행하는 StringBuffer 클래스도 있습니다.
StringBuffer: 내부 동기화가 가능하여 멀티 스레드 상황에서 안전하지만, 동기화 오버헤드로 인해 성능이 느립니다.
StringBuilder: 멀티 스레드에서는 안전하지 않지만, 동기화 오버헤드가 없기 때문에 속도가 빠릅니다.

메서드 체이닝

NOTE
메서드 체이닝은 메서드 반환값으로 자기 자신을 반환해 체이닝을 할 수 있도록 해주는 기법입니다.
public class ValueAdder { private int value; public ValueAdder add(int addValue) { value += addValue; return this; // 자기 자신을 반환한다. } public int getValue(){ return value; } }
Java
복사
메서드 체이닝 기법은 자기 자신을 반환한다.
public static void main(String[] args) { ValueAdder adder = new ValueAdder(); // 일반적인 방법 adder.add(1); adder.add(2); adder.add(3); int result = adder.getValue(); System.out.println("result = " + result); // 체이닝 방법 int result2 = adder.add(1).add(2).add(3).getValue(); System.out.println("result2 = " + result2); }
Java
복사
체이닝 방식으로 더 보기 좋은 코드가 만들어진다.
public static void main(String[] args) { StringBuilder sb = new StringBuilder(); String string = sb.append("A").append("B").append("C").append("D") .insert(4, "Java") .delete(4, 8) .reverse() .toString(); System.out.println("string = " + string); }
Java
복사
StringBuilder 역시 메서드 체이닝을 지원한다.