String, StringBuffer, StringBuilder)자바에서는 문자열을 다룰 때 String, StringBuffer, StringBuilder 세 가지 클래스를 사용할 수 있다.
각 클래스는 메모리 관리 방식과 동기화 처리 방식이 다르기 때문에 적절하게 선택해야 한다.
| 클래스 | 특징 | 수정 가능 여부 | 멀티 스레드 안전성 | 성능 |
|---|---|---|---|---|
String | 불변(Immutable) 객체 | ❌ (변경 불가, 새로운 객체 생성) | ✅ (자동으로 동기화됨) | 낮음 (많이 변경되면 성능 저하) |
StringBuffer | 가변(Mutable) 객체 | ✅ (내용 변경 가능) | ✅ (멀티 스레드 안전) | 보통 (멀티 스레드 환경에서 안정적) |
StringBuilder | 가변(Mutable) 객체 | ✅ (내용 변경 가능) | ❌ (동기화 X, 단일 스레드 전용) | 높음 (빠른 문자열 처리) |
String의 문제점String str = "abc";
str = str.concat("123"); // 새로운 객체가 생성됨
위 코드는 "abc123"이라는 새로운 객체를 생성하는 방식이다. 기존 "abc" 객체는 변경되지 않고 새로운 객체가 메모리에 생성되므로 성능이 저하될 수 있다.
StringBuffer와 StringBuilder 사용StringBuffer sb = new StringBuffer("abc");
sb.append("123"); // 기존 객체 자체를 변경
위 코드는 객체를 새로 생성하지 않고 기존 객체 자체를 변경하기 때문에 메모리 효율이 높고 성능이 향상됨.
자바에서 문자열을 분리하는 방법에는 StringTokenizer와 split() 메서드가 있다.
| 메서드 | 반환 타입 | 설명 |
|---|---|---|
split(String regex) | String[] | 지정된 정규 표현식을 기준으로 문자열을 분리하여 배열 반환 |
split(String regex, int limit) | String[] | limit 개수만큼 문자열을 분리하여 배열 반환 |
StringTokenizer | StringTokenizer | 구분자를 이용해 문자열을 하나씩 가져오는 방식 |
StringTokenizer vs split())import java.util.StringTokenizer;
public class StringSplitExample {
public static void main(String[] args) {
String accessLog = "2017,03,11,,,USER123,GROUP1";
// 1. StringTokenizer 사용
System.out.println("***** StringTokenizer *****");
StringTokenizer accessTokens = new StringTokenizer(accessLog, ",");
System.out.println("토큰 개수: " + accessTokens.countTokens());
while (accessTokens.hasMoreTokens()) {
System.out.println(accessTokens.nextToken());
}
// 2. split() 사용
System.out.println("\n***** split() *****");
String[] splits = accessLog.split(",");
System.out.println("배열 개수: " + splits.length);
for (String str : splits) {
System.out.println(str);
}
// 3. split()에서 개수 제한
System.out.println("\n***** split() (limit = 3) *****");
String[] splits2 = accessLog.split(",", 3);
System.out.println("배열 개수: " + splits2.length);
for (String str : splits2) {
System.out.println(str);
}
}
}
StringTokenizer vs split() 비교| 구분 | StringTokenizer | split() |
|---|---|---|
| 분리 방식 | 특정 구분자를 기준으로 하나씩 가져옴 | 정규 표현식 사용 가능 |
| 반환 타입 | StringTokenizer 객체 | String[] (배열 반환) |
| 공백 포함 여부 | 연속된 구분자를 무시 | 빈 문자열도 배열 요소로 포함 |
| 정규 표현식 지원 | ❌ (단순 구분자만 사용 가능) | ✅ (복잡한 패턴 매칭 가능) |
String 대신 StringBuffer 또는 StringBuilder를 사용해야 성능이 향상됨.StringTokenizer는 단순한 구분자로 문자열을 나눌 때 유용하지만, 정규 표현식을 활용하려면 split()을 사용하는 것이 더 효율적임.split()은 빈 문자열도 배열 요소로 포함하지만, StringTokenizer는 연속된 구분자를 자동으로 무시함.정규 표현식(Regular Expressions, regex)은 특정 패턴을 가진 문자열을 찾거나 수정할 때 사용하는 표준화된 텍스트 패턴이다.
이를 활용하면 문자열 검색, 유효성 검사, 데이터 변환 등 다양한 작업을 수행할 수 있다.
횟수를 지정하는 메타 문자는 특정 패턴이 몇 번 반복되는지를 정의할 때 사용된다.
| 메타 문자 | 설명 | 예시 |
|---|---|---|
* | 0회 이상 반복 | a* → "a", "aa", "" |
+ | 1회 이상 반복 | a+ → "a", "aa" (공백 불가) |
? | 0회 또는 1회 | a? → "a" 또는 "" |
{n} | 정확히 n회 반복 | a{3} → "aaa" |
{n,} | 최소 n회 반복 | a{2,} → "aa", "aaa", "aaaa" |
{n,m} | n~m회 반복 | a{2,4} → "aa", "aaa", "aaaa" |
| 메타 문자 | 설명 | 예시 |
|---|---|---|
. | 임의의 단일 문자 | a.b → "acb", "a0b" |
\d | 숫자 (0~9) | \d+ → "123", "98765" |
\w | 영문자, 숫자, _ | \w+ → "abc", "A1_B" |
\s | 공백 문자 | "hello\sworld" → "hello world" |
\b | 단어의 경계 | \bcat\b → "cat"(단독) |
^ | 문자열의 시작 | ^abc → "abc"로 시작하는 문자열 |
$ | 문자열의 끝 | xyz$ → "xyz"로 끝나는 문자열 |
| ` | ` | OR 연산 |
[] | 문자셋 지정 | [abc] → "a", "b", "c" |
() | 그룹 지정 | (abc)+ → "abc", "abcabc" |
Java의 Pattern과 Matcher 클래스를 사용하여 정규 표현식을 활용할 수 있습니다. 가장 많이 쓰이는 메서드 중 하나가 find()로, 주어진 문자열에서 패턴과 일치하는 부분을 찾을 때 사용됩니다.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexMatcherExample {
public static void main(String[] args) {
// 검색할 문자열 (고객 정보)
String customerInfo =
"홍길동은 30세이며 서울시 강남구에 거주합니다. " +
"그의 집 전화번호는 02-234-5678이며 " +
"휴대폰 번호는 011-234-5678입니다.";
// 정규 표현식: 전화번호 형식 (xx-xxx-xxxx 또는 xxx-xxxx-xxxx)
String pattern = "\\d{2,3}-\\d{3,4}-\\d{4}";
// 정규 표현식 패턴을 컴파일
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(customerInfo);
int count = 0;
// find()를 이용하여 패턴과 일치하는 부분 찾기
while (m.find()) {
count++;
System.out.print("찾은 위치 : " + m.start() + "\t"); // 시작 위치 출력
System.out.println("전화번호 : " + m.group()); // 찾은 전화번호 출력
}
System.out.println("발견된 전화번호 수 : " + count);
}
}
Pattern.compile(pattern): 정규 표현식을 컴파일하여 Pattern 객체 생성p.matcher(customerInfo): customerInfo 문자열에서 패턴을 찾을 Matcher 객체 생성m.find(): 일치하는 패턴이 있으면 true 반환하며, 여러 개 존재할 경우 반복 실행m.start(): 일치하는 패턴의 시작 위치 반환m.group(): 찾은 패턴(전화번호)을 반환m.matches(): 문자열 전체가 패턴과 일치하는지 확인m.replaceAll("변경할 문자열"): 패턴과 일치하는 모든 문자열을 변경Java의 Object 클래스는 모든 클래스의 최상위 부모 클래스이며, 모든 객체는 Object를 직접 또는 간접적으로 상속받습니다.
객체를 비교하는 방법에는 == 연산자와 equals() 메서드가 있습니다.
public class ObjectComparisonExample {
public static void main(String[] args) {
String str1 = new String("Hello");
String str2 = new String("Hello");
// == 연산자는 객체의 주소값을 비교
System.out.println("주소 비교 (==) : " + (str1 == str2)); // false
// equals() 메서드는 객체의 내용 비교
System.out.println("내용 비교 (equals) : " + str1.equals(str2)); // true
}
}
== 연산자: 두 객체가 같은 메모리 주소를 가리키는지 비교 → 즉, 동일한 객체인지 확인 (false)equals(): 객체가 같은 내용을 가지고 있는지 비교 → 대부분의 클래스에서는 equals()를 재정의하여 비교 (true)모든 객체는 hashCode()와 equals() 메서드를 가지고 있으며, 두 메서드는 서로 연관이 있습니다.
import java.util.Objects;
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// equals() 메서드 오버라이드
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
// hashCode() 메서드 오버라이드
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class HashCodeEqualsExample {
public static void main(String[] args) {
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
Person p3 = new Person("Bob", 30);
System.out.println("p1과 p2 비교 (equals): " + p1.equals(p2)); // true
System.out.println("p1과 p3 비교 (equals): " + p1.equals(p3)); // false
System.out.println("p1의 해시코드: " + p1.hashCode());
System.out.println("p2의 해시코드: " + p2.hashCode());
System.out.println("p3의 해시코드: " + p3.hashCode());
}
}
equals() 메서드를 재정의하면 객체의 내용을 비교 가능 (name과 age가 동일하면 true)hashCode()는 객체의 해시값을 반환하며, 같은 내용일 경우 동일한 해시코드가 생성됨hashCode()가 필수적으로 사용됨객체의 toString() 오버라이드
Object 클래스의 toString()을 재정의하면 객체의 정보를 쉽게 출력 가능class Employee {
String name;
int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Employee{name='" + name + "', age=" + age + "}";
}
}
public class ToStringExample {
public static void main(String[] args) {
Employee emp = new Employee("John", 30);
System.out.println(emp.toString()); // Employee{name='John', age=30}
}
}
객체 비교 시 주의할 점
equals()를 오버라이드하면 반드시 hashCode()도 오버라이드할 것 (HashSet 사용 시 필수)== 사용, 참조형 객체 비교는 equals() 사용 추천Pattern과 Matcher 클래스를 사용해 정규 표현식 패턴을 매칭 가능 (find() 활용)Object 클래스는 모든 클래스의 부모이며 equals(), hashCode(), toString() 등을 오버라이드 가능== 연산자는 객체의 주소를 비교, equals()는 객체의 내용을 비교hashCode()를 올바르게 구현하면 해시 기반 자료구조에서 객체 관리 가능record 선언 시 컴파일러가 private 멤버 변수, 생성자, hashCode(), equals(), toString() 및 필드 이름과 동일한 getter 메서드를 자동으로 추가.getEmpId() 등) 방식과 혼동될 가능성이 있음.setter/getter, hashCode()/equals(), toString() 등을 자동 생성.lombok.jar 파일을 Eclipse 디렉토리에 복사eclipse.ini 파일 맨 아래에 javaagent:lombok.jar 추가Build Path -> Configure Build Path -> Libraries 탭에서 lombok.jar 추가| 어노테이션 | 설명 |
|---|---|
@Data | getter, setter, hashCode(), equals(), toString() 자동 추가 |
@Getter | getter 메서드 추가 |
@Setter | setter 메서드 추가 (final 필드 제외) |
@EqualsAndHashCode | hashCode() 및 equals() 추가 |
@ToString | toString() 메서드 추가 |
@NoArgsConstructor | 기본 생성자 추가 (final 필드가 있으면 force=true 필요) |
@AllArgsConstructor | 모든 필드를 초기화하는 생성자 추가 |
@RequiredArgsConstructor | final 필드 및 @NonNull 필드 초기화하는 생성자 추가 |
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
@NoArgsConstructor(force=true)
public class Employee {
private final int employeeId;
@NonNull private String name;
@NonNull private String email;
private String phoneNumber;
@NonNull private String jobId;
}