본 글은 김영한 님의 『김영한의 실전 자바 - 중급 1편』 강의를 학습하며 정리한 내용입니다.
강의 자료에 포함된 일부 코드와 이미지를 참고하여 발췌·활용하였습니다.자바 기본기를 제대로 다지고 싶으시다면, 아래 링크에서 강의를 확인해 보세요
『김영한의 실전 자바 - 중급 1편』 보러 가기
본게시물은 파트너스 활동의 일환으로 작성되었으며, 구매 시 소정의 수수료를 받을 수 있습니다.
김영한의 실전 자바 - 중급 1편| 김영한 - 인프런 강의
현재 평점 5.0점 수강생 10,673명인 강의를 만나보세요. 실무에 필요한 자바의 다양한 중급 기능을 예제 코드로 깊이있게 학습합니다. 실무에 필요한 다양한 자바 중급 기능, Object, 불변 객체, String
www.inflearn.com
char , String
자바에서 문자를 다루는 대표적인 타입은 char,String 2가지가 있다.
char
- 문자 하나를 다룰 때 사용한다.
- 여러 문자를 나열하려면
char[]을 사용한다. - 하지만 배열을 직접 다루는 방법은 매우 불편하기 때문에 문자열을 편리하게 다룰 수 있는
String클래스를 제공한다.
String
String 클래스를 통해 문자열을 생성하는 방법은 2가지가 있다.
- 쌍따옴표 사용 : “hello”
- 객체 생성 : new String(”hello”)
String 은 클래스이기 때문에 참조형이다.
따라서 String str1 = "hello" 코드는 뭔가 어색하다.
문자열은 매우 자주 사용되기 때문에,
쌍따옴표로 문자열을 감싸면 자바 언어에서 new String("hello") 와 같이 변경해준다.
구조
String 클래스는 대략 다음과 같이 생겼다.
public final class String {
//문자열 보관
private final char[] value;// 자바 9 이전
private final byte[] value;// 자바 9 이후
//여러 메서드
public String concat(String str) {...}
public int length() {...}
...
}
클래스이므로 속성과 기능을 가진다.
private final char[] value;
여기에는 String 의 실제 문자열 값이 보관된다. 문자 데이터 자체는 char[] 에 보관된다.
String 클래스는 다루기 불편한 char[] 을 내부에 감추고 편리하게 문자열을 다룰 수 있도록 다양한 기능을 제공한다.
String 클래스와 참조형
String 은 클래스이기 때문에 참조형이다.
참조형은 변수에 계산할 수 없는 참조값이 들어있기 때문에 + 같은 연산을 사용할 수 없다.
public class StringConcatMain {
public static void main(String[] args) {
String a = "hello";
String b = " java";
String result1 = a.concat(b);
String result2 = a + b;
System.out.println("result1 = " + result1);
System.out.println("result2 = " + result2);
}
}
문자열을 더할 때는 String 이 제공하는 concat() 과 같은 메서드를 사용해야 한다.
하지만 문자열은 너무 자주 다루어지기 때문에 자바 언어에서 편의상 + 연산을 제공한다.
String 클래스 비교
String 클래스를 비교할 때는 == 비교가 아니라 항상 equals() 비교를 해야한다.
public class StringEqualsMain1 {
public static void main(String[] args) {
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("new String() == 비교: " + (str1 == str2));
System.out.println("new String() equals 비교: " + (str1.equals(str2)));
String str3 = "hello";
String str4 = "hello";
System.out.println("리터럴 == 비교: " + (str3 == str4));
System.out.println("리터럴 equals 비교: " + (str3.equals(str4)));
}
}
---실행결과---
new String() == 비교: false
new String() equals 비교: true
리터럴 == 비교: true
리터럴 equals 비교: true
위 코드의 실행결과를 살펴보자.
str1과str2는new String()을 사용해 각각 인스턴스를 생성했다.- 서로 다른 인스턴스이므로 동일성(==) 비교에 실패한다.
- 둘은 내부에 같은
"hello"값을 가지고 있기 때문에 논리적으로 같다.- 동등성(equals()) 비교에 성공한다.
- String 클래스는 내부 문자열 값을 비교하도록
equals()메서드를 재정의 해두었다.

String str3 = "hello"와 같이 문자열 리터럴을 사용하는 경우 자바는 메모리 효율성과 성능 최적화를 위해 문자열 풀을 사용한다.- 자바가 실행되는 시점에 클래스에 문자열 리터럴이 있으면 문자열 풀에
String인스턴스를 미리 만들어둔다. - 이때 같은 문자열이 있으면 만들지 않는다.
- 자바가 실행되는 시점에 클래스에 문자열 리터럴이 있으면 문자열 풀에
String str4 = "hello"의 경우"hello"문자열 리터럴을 사용하므로 문자열 풀에서str3과 같은x003참조를 사용한다.
문자열 풀 덕분에 같은 문자를 사용하는 경우 메모리 사용을 줄이고 문자를 만드는 시간도 줄어들기 때문에 성능도 최적화 할 수 있다.
따라서 문자열 리터럴을 사용하는 경우 같은 참조값을 가지므로 == 비교에 성공한다.
String 클래스 - 불변 객체
String 은 불변 객체이므로, 생성 이후에 절대로 내부의 문자열 값을 변경할 수 없다.
public class StringImmutable1 {
public static void main(String[] args) {
String str = "hello";
str.concat(" java");
System.out.println("str = " + str);
}
}
---실행결과---
str = hello
실행 결과를 보면 뭔가 이상하다. 문자가 합쳐지지 않았다.
String 은 불변 객체이기 때문에 변경이 필요한 경우 기존 값을 변경하지 않고, 새로운 결과를 만들어서 반환한다.
public class StringImmutable2 {
public static void main(String[] args) {
String str1 = "hello";
String str2 = str1.concat(" java");
System.out.println("str1 = " + str1);
System.out.println("str2 = " + str2);
}
}
String.concat()은 내부에서 새로운String객체를 만들어서 반환한다.
불변으로 설계된 이유
String 이 불변으로 설계된 이유는 문자열 풀에 있는 String 인스턴스의 값이 중간에 변경되면 같은 문자열을 참고하는 다른 변수의 값도 함께 변경되기 때문이다.
주요 메서드
String 클래스의 메서드는 너무 많기 때문에 외우기 모다 주로 사용하는 메서드가 이런 것이 있다는 것을 알아두고, 필요할 때 검색해서 찾는 것이 좋다.
문자열 정보 조회
- length() : 문자열의 길이를 반환
- isEmpty() : 문자열이 비어 있는지 확인(길이가 0)
- isBlank() : 문자열이 비어 있는지 확인(길이가 0 이거나 공백(Whitespace)만 있는 경우, 자바11)
- charAt(int index) : 지정된 인덱스에 있는 문자를 반환
문자열 비교
- equals(Object anObject) : 두 문자열이 동일한지 비교
- equalsIgnoreCase(String anotherString) : 두 문자열을 대소문자 구분 없이 비교
- compareTo(String anotherString) : 두 문자열을 사전 순으로 비교
- compareToIgnoreCase(String str) : 두 문자열을 대소문자 구분 없이 사전적으로 비교
- startsWith(String prefix) : 문자열이 특정 접두사로 시작하는지 확인
- endsWith(String suffix) : 문자열이 특정 접미사로 끝나는지 확인
문자열 검색
- contains(CharSequence s) : 문자열이 특정 문자열을 포함하고 있는지 확인
- indexOf(String ch) / indexOf(String ch, int fromIndex) : 문자열이 처음 등장하는 위치를 반환
- lastIndexOf(String ch) : 문자열이 마지막으로 등장하는 위치를 반환
문자열 조작 및 변환
- substring(int beginIndex) / substring(int beginIndex, int endIndex) : 문자열의 부분 문자열을 반환
- concat(String str) : 문자열의 끝에 다른 문자열을 붙임
- replace(CharSequence target, CharSequence replacement) : 특정 문자열을 새 문자열로 대체
- replace(String regex, String replacement) : 문자열에서 정규 표현식과 일치하는 부분을 새 문자열로 대체
- replaceFirst(String regex, String replacement) : 문자열에서 정규 표현식과 일치하는 첫 번째 부분을 새 문자열로 대체
- toLowerCase() / toUpperCase() : 문자열을 소문자나 대문자로 변환
- trim() : 문자열 양쪽 끝의 공백을 제거(단순 Whitespace 만 제거)
- strip() : Whitespace, 유니코드 공백을 포함해서 제거
문자열 분할 및 조합
- split(String regex) : 문자열을 정규 표현식을 기준으로 분할
- join(CharSequence delimiter, CharSequence… element) : 주어진 구분자로 여러 문자열을 결합
기타
- valueOf(Object obj) : 다양한 타입을 문자열로 변환
- toCharArray() : 문자열을 문자 배열로 변환
- format(String format, Object .. args) : 형식 문자열과 인자를 사용하여 새로운 문자열을 생성
- matches(String regex) : 문자열이 주어진 정규 표현식과 일치하는지 확인
참고 - CharSequence
CharSequence 는 String, StringBuilder 의 상위 타입이다.
문자열을 처리하는 다양한 객체를 받을 수 있다.
StringBuilder - 가변 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");
String str = new String("ABC") + String("D");
String str = new String("ABCD");
이 경우 총 3개의 String 클래스가 추가로 생성된다.
즉, 중간에 만들어진 2개의 클래스는 제대로 사용되지 않고, GC 의 대상이 된다.
불변인 String 클래스의 단점은 문자를 더하거나 변경할 때 마다 계속해서 새로운 객체를 생성해야 한다는 점이다.
결과적으로 컴퓨터의 CPU, 메모리 자원을 더 많이 사용하게 된다.
StringBuilder
위 문제를 해결하는 방법은 불변이 아닌 가변 String 이 존재하면 된다.
자바는 StringBuilder 라는 가변 String 을 제공한다.
내부에 final 이 아닌 변경할 수 있는 byte[] 을 가지고 있다.
public final class StringBuilder {
char[] value;// 자바 9 이전
byte[] value;// 자바 9 이후
//여러 메서드
public StringBuilder append(String str) {...}
public int length() {...}
...
}
어떻게 사용하는지 확인해보자.
public class StringBuilderMain1_1 {
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);
//StringBuilder -> String String string = sb.toString();
System.out.println("string = " + string);
}
}
---실행결과---
sb = ABCD
insert = ABCDJava
delete = ABCD
reverse = DCBA
string = DCBA
동작은 아래와 같다.
- StringBuilder 객체를 생성
- append() 메서드를 사용해 여러 문자열 추가
- insert() 메서드로 특정 위치에 문자열을 삽입
- delete() 메서드로 특정 범위의 문자열 삭제
- reverse() 메서드로 문자열을 뒤집음
- toStrint() 메서드로 StringBuilder 결과를 기반으로 String 을 생성해서 반환
StringBuilder 는 보통 문자열을 변경하는 동안만 사용하다가 문자열 변경이 끝나면 안전한 String 으로 변환하는 것이 좋다.
String 최적화
자바의 String 최적화
문자열 리터럴
자바 컴파일러는 다음과 같이 문자열 리터럴 더하는 부분을 자동으로 합쳐준다.
# 컴파일 전
String helloWorld = "Hello, " + "World!";
# 컴파일 후
String helloWorld = "Hello, World!";
따라서 런타임에 별도의 문자열 결합 연산을 수행하지 않기 때문에 성능이 향상된다.
String 변수
String result = str1 + str2;
문자열 변수의 경우 그 안에 어떤 값이 들어있는지 컴파일 시점에 알 수 없기 때문에 단순하게 합칠 수 없다.
위와 같은 경우 예를 들어 다음과 같이 최적화를 수행한다.
String result = new StringBuilder().append(str1).append(str2).toString();
# 자바 9부터는 StringConcatFactory 를 사용해 최적화
이렇듯 자바가 최적화를 처리해주기 때문에 간단한 경우 더하기 연산을 사용하면 충분하다.
String 최적화가 어려운 이유
문자열을 루프안에서 더하는 경우 최적화가 이루어지지 않는다.
public class LoopStringMain {
public static void main(String[] args) { long startTime = System.currentTimeMillis();
String result = "";
for (int i = 0; i < 100000; i++) {
result += "Hello Java ";
}
long endTime = System.currentTimeMillis();
System.out.println("result = " + result);
System.out.println("time = " + (endTime - startTime) + "ms");
}
}
최적화 하면 아래와 같다.
String result = "";
for (int i = 0; i < 100000; i++) {
result = new StringBuilder().append(result).append("Hello Java ").toString();
}
반복문의 루프 내부에서는 최적화가 되는 것 처럼 보이지만, 반복 횟수만큼 객체를 생성해야 한다.
반복문 내에서의 문자열 연결은, 런타임에 연결할 문자열의 개수와 내용이 결정된다.StringBuilder 는 물론이고, 반복 횟수인 100,000 번의 String 객체를 생성했을 것이다.
위와 같을 때, StringBuilder 를 사용하면 된다.
public class LoopStringBuilderMain {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("Hello Java ");
}
String result = sb.toString();
long endTime = System.currentTimeMillis();
System.out.println("result = " + result);
System.out.println("time = " + (endTime - startTime) + "ms");
}
}
StringBuilder 를 직접 사용하는 것이 좋은 경우
- 반복문에서 반복해서 문자를 연결할 때
- 조건문을 통해 동적으로 문자열을 조합할 때
- 복잡한 문자열의 특정 부분을 변경해야 할 때
- 매우 긴 대용량 문자열을 다룰 때
메서드 체이닝 - Method Chaining
예제를 통해 메서드 체이닝에 대해 알아보자.
public class ValueAdder {
private int value;
public ValueAdder add(int addValue) {
value += addValue;
return this;
}
public int getValue() {
return value;
}
}
public class MethodChainingMain1 {
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);
}
}
public class MethodChainingMain2 {
public static void main(String[] args) {
ValueAdder adder = new ValueAdder();
ValueAdder adder1 = adder.add(1);
ValueAdder adder2 = adder1.add(2);
ValueAdder adder3 = adder2.add(3);
int result = adder3.getValue();
System.out.println("result = " + result);
}
}
---실행결과---
result = 6
이번에는 위 방식에서 반환된 참조값을 새로운 변수에 담아서 보관하지 않고, 대신에 바로 메서드 호출에 사용해보자.
public class MethodChainingMain3 {
public static void main(String[] args) {
ValueAdder adder = new ValueAdder();
int result = adder.add(1).add(2).add(3).getValue();
System.out.println("result = " + result);
}
}
실행 순서를 살펴보자.
add() 메서드를 호출하면 ValueAdder 인스턴스 자신의 참조값이 반환된다.
이 반환된 참조값을 변수에 담아두지 않고, 반환된 참조값을 즉시 사용해서 바로 메서드를 호출할 수 있다.
adder.add(1).add(2).add(3).getValue() //value=0
x001.add(1).add(2).add(3).getValue() //value=0, x001.add(1)을 호출하면 그 결과로
x001을 반환한다.
x001.add(2).add(3).getValue() //value=1, x001.add(2)을 호출하면 그 결과로 x001을 반환
한다.
x001.add(3).getValue() //value=3, x001.add(3)을 호출하면 그 결과로 x001을 반환한다.
x001.getValue() //value=6
6
메서드 호출의 결과로 자기 자신의 참조값을 반환하면, 반환된 참조값을 사용해서 메서드 호출을 계속 이어갈 수 있다.
코드를 보면 . 을 찍고 메서드를 계속 연결해서 사용한다.
마치 메서드가 체인으로 연결된 것 처럼 보이는 기법을 메서드 체이닝이라고 한다.
메서드 체이닝 기법은 코드를 간결하고 읽기 쉽게 만들어준다.
StringBuilder 와 메서드 체인
StringBuilder 는 메서드 체이닝 기법을 제공한다.
append 메서드를 보면 자기 참조값을 반환한다.
public StringBuilder append(String str) {
super.append(str);
return this;
}
StringBuilder 에서 문자열을 변경하는 대부분의 메서드도 메서드 체이닝 기법을 제공하기 위해 자기 자신을 반환한다.
아래와 같이 사용할 수 있다.
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);
}'인프런 > 김영한 자바' 카테고리의 다른 글
| [김영한의 실전 자바 중급 1편] - 5. 열거형,ENUM (0) | 2025.10.14 |
|---|---|
| [김영한의 실전 자바 중급 1편] - 4. 래퍼,Class 클래스 (0) | 2025.10.12 |
| [김영한의 실전 자바 중급 1편] - 2. 불변 객체 (0) | 2025.10.10 |
| [김영한의 실전 자바 중급 1편] - 1. Object 클래스 (0) | 2025.09.20 |
| [김영한의 실전 자바 기본편] - 10. 다형성(추상화,인터페이스) (1) | 2025.09.16 |