도도한 개발자

[Java] #15-4. String - 값을 바꾸는 메소드 본문

Backend/Java

[Java] #15-4. String - 값을 바꾸는 메소드

Kiara Kim 2022. 3. 23. 18:00

* String 값을 바꾸는 메소드

 

String API 설명의 마지막으로, String 값을 바꾸는 메소드를 살펴보자. 문자열의 값을 바꾸고 변환하는 메소드도 다음과 같이 구분할 수 있다.

 

· 문자열을 합치는 메소드와 공백을 없애는 메소드

· 내용을 교체(replace)하는 메소드

· 특정 형식에 맞춰 값을 치환하는 메소드

· 대소문자를 바꾸는 메소드

· 기본 자료형을 문자열로 변환하는 메소드

 

 

** 문자열을 합치는 메소드와 공백을 없애는 메소드

 

리턴 타입 메소드 이름 및 매개 변수 설명
String concat(String str) 매개 변수로 받은 str을 기존 문자열의 우측에 붙인 새로운 문자열 객체를 생성하여 리턴한다.
String trim() 문자열의 맨 앞과 맨 뒤에 있는 공백들을 제거한 문자열 객체를 리턴한다.

 

자바에서는 +로 문자열을 더할 수 이씩 때문에 concat() 메소드를 쓸 일이 없다. concat() 메소드를 사용하여 문자열을 계속 더할 일이 있다면 StringBuffer나 StringBuilder 클래스를 사용하여 더하는 것이 낫다.

trim() 메소드는 공백을 제거할 때 매우 유용하게 사용된다. 문자열에 앞뒤에 잇는 공백을 하나하나 찾아 지울 필요 없이 이 메소드만 사용하면 되기 때문이다. trim() 메소드의 용도는 매우 많지만, 작업하려는 문자열의 공백만으로 이루어진 값인지, 아니면 공백을 제외한 값이 있는지 확인하기 매우 편리하다. 만약 null 체크를 하지 않으면, 값이 null인 객체의 메소드를 호출하면 NullPointerException이 발생한다. 따라서 String을 조작하기 전에 null 체크하는 습관을 갖자.

 

 

** 내용을 교체(replace)하는 메소드

 

리턴 타입 메소드 이름 및 매개 변수 설명
String replace(char oldChar, char newChar) 해당 문자열에 있는 oldChar의 값을 newChar로 대치한다.
String replace(CharSequence target, CharSequence replacement) 해당 문자열에 있는 target과 같은 값을 replacement로 대치한다.
String replaceAll(String regex, String replacement) 해당 문자열의 내용 중 regex에 표현된 정규 표현식에 포함되는 모든 내용을 replacement로 대치한다.
String replaceFirst(String regex, String replacement) 해당 문자열의 내용 중 regex에 표현된 전규 표현식에 포함되는 첫번째 내용을 replacememt로 대치한다.

 

replace로 시작하는 메소드는 문자열에 있는 내용 중 일부를 변경하는 작업을 수행한다. 참고로, 이 메소드를 수행한다 해서 기존 문자열의 값이 바뀌는 것은 아니다. 예제를 살펴보자.

 

public void checkReplace() {
	String text = "The String class represents character strings";
	System.out.println(text.replace('s', 'z')); 		// @1
	System.out.println(text);				// @2
	System.out.println(text.replace("tring", "trike"));	// @3
	System.out.println(text.replaceAll(" ", "|"));		// @4
	System.out.println(text.replaceFirst(" ", "|"));	// @5
}

 

@1 : text 객체에 있는 char 's'를 'z'로 변환하는 작업을 수행한다.

@2 : replace() 메소드를 수행한 후에 기존의 값이 변경되는지를 확인하기 위해 현재 값을 출력한다.

@3 : CharSequence 타입의 매개 변수를 사용하여 값을 변경한다.

@4 : 정규 표현식을 사용하는 replaceAll() 메소드의 예이며, 공백을 파이프(|)로 변환한다.

@5 : replaceFirst() 메소드를 사용하여 첫 번째 공백만 파이프로 변환한다.

 

결과는 다음과 같다.

 

The String clazz reprezentz character ztringz
The String class represents character strings
The Strike class represents character strikes
The|String|class|represents|character|strings
The|String class represents character strings

 

 

** 특정 형식에 맞춰 값을 치환하는 메소드

 

리턴 타입 메소드 이름 및 매개 변수 설명
static String format(String format, Object... args) format에 있는 문자열의 내용 중 변환해야 하는 부분을 args의 내용으로 변경한다.
static String format(Locale l, String format, Object... args) format에 있는 문자열의 내용 중 변환해야 하는 부부분을 args의 내용으로 변경한다. 단 첫 매개 변수인 Locale의 타입의 l에 선언된 지역에 맞춰 출력한다.

 

+ Locale은 지역적으로 다른 표현 형식을 제공하기 위한 것이다. 보통 Locale을 지정하지 않으면 기본적으로 자바 프로그램이 수행되는 OS의 지역 정보를 기본으로 따른다.

 

format() 메소드는 정해진 기준에 맞춘 문자열이 있으면 그 기준에 있는 내용을 변환한다. 자바에서 %s는 String을, %d는 정수형을, %f는 소수점이 있는 숫자, %%는 %를 의미한다. 간단한 예를 통해 format() 메소드의 사용법을 알아보자.

 

public void checkFormat() {
	String text = "제 이름은 %s입니다. 나이는 만으로 %d살이고, " 
       + "하루에 %f%%의 시간을 공부하는데 할애하고 있습니다.";
	String printText = String.format(text, "Kiara", 23, 10.0);
	System.out.println(printText);
}

 

이렇게 format() 메소드를 사용하면 다음과 같은 결과가 출력된다.

 

제 이름은 Kiara입니다. 나이는 만으로 23살이고, 하루에 10.000000%의 시간을 공부하는데 할애하고 있습니다.

 

 

** 대소문자를 바꾸는 메소드

 

리턴 타입 메소드 이름 및 매개 변수 설명
String toLowerCase() 모든 문자열의 내용을 소문자로 변경한다.
String toLowerCase(Locale locale) 지정한 지역 정보에 맞춰 모든 문자열의 내용을 소문자로 변경한다.
String toUpperCase 모든 문자열의 내용을 대문자로 변경한다.
String toUpperCase(Locale locale) 지정한 지역 정보에 맞춰 모든 문자열의 내용을 대문자로 변경한다.

 

 

** 기본 자료형을 문자열로 변환하는 메소드

 

리턴 타입 메소드 이름 및 매개 변수
static String valueOf(boolean b)
static String valueOf(char c)
static String valueOf(char[] data)
static String valueOf(char[] data, int offset, int count)
static String valueOf(double d)
static String valueOf(float f)
static String valueOf(int i)
static String valueOf(long l)
static String valueOf(Object obj)

 

여기에 나열된 메소드들은 기본 자료형을 String 타입을 변환한다. 이 valueOf() 메소드를 사용하여 기본 자료형 값들을 문자열로 변경해도 되지만 다음과 같이 변환해도 된다.

 

byte b = 1;
String byte1 = String.valueOf(b);
String byte2 = b + "";

 

byte1이나 byte2처럼 변환하나 출력해 보면 동일한 값이 출력된다. 대부분 기본 자료형을 String 타입으로 변환할 필요가 있을 때에는 String과 합치는 과정을 거친다. 그럴 경우 별도로 valueOf() 메소드를 사용할 필요까진 없다. 그러나 String으로 변환만 해놓고 별도의 문자열과 합치는 과정이 없을 경우 valudOf() 메소드를 사용하는 것을 권장한다.

 

또, valudOf() 메소드의 매개 변수로 객체(Object)가 넘어왔을 경우에 toString()을 구현한 객체나, 정상적인 객체를 메소드에 넘겨주면 toString()의 결과를 리턴해준다. 그러나 null인 객체의 경우엔 다르다. null 객체는 toString() 메소드를 사용할 수 없다. 이 경우 NullPointerException이 발생해버린다. 이를 방지하기 위해 객체를 출력할 때 valueOf() 메소드를 사용하면 좋다. 이 메소드는 객체가 null이면 "null"이라는 문자열을 리턴해주기 때문이다. null이 아닐 경우 toString() 메소드를 호출한 결과가 리턴된다. System.out.print()나 System.out.println() 메소드에서 null인 객체를 출력했을 때 NullPointerException이 발생하지 않는 이유도 이 때문이다.

 

 

* 이 메소드는 절대 사용하지 말자!

 

String 클래스에 있는 여러 메소드 중 입문자가 함부로 사용하면 안되는 메소드가 있다. 바로 intern()이라는 메소드다. 이는 C로 구현되어 있는 native 메소드 중 하나인데, 시스템의 심각한 성능 저하를 발생시킬 수도 있기 때문이다. 다음과 같이 String 객체를 생성해보자.

 

public void internCheck() {
	String text1 = "Java Basic";
	String text2 = "Java Basic";
	String text3 = new String("Java Basic");
	System.out.println(text1 == text2);
	System.out.println(text1 == text3);
	System.out.println(text1.equals(text3));
}

 

결과는 다음과 같다.

 

true
false
true

 

text1과 text2와 같이 객체를 생성하면 문자열 풀에 해당 값이 있으면 기존에 있는 객체를 참조하고, text3과 같이 String 객체를 생성하면 같은 문자열 출에 있는 말든 새로운 객체를 생성하기 때문에 위와 같은 결과가 나온 것이다. 네 번째 주에 다음과 같이 한 줄을 추가해보자.

 

public void internCheck() {
	String text1 = "Java Basic";
	String text2 = "Java Basic";
	String text3 = new String("Java Basic");
	text3 = text3.intern();
	System.out.println(text1 == text2);
	System.out.println(text1 == text3);
	System.out.println(text1.equals(text3));
}

 

결과는 다음과 같다.

 

true
true
true

 

왜 이렇게 될까? new String(String)으로 생성한 문자열 객체라고 할지라도, 풀에 해당 값이 있으면 풀에 있는 값을 참조하는 객체를 리턴한다. 만약 동일한 문자열이 존재하지 않으면 풀에 해당 값을 추가한다. 따라서 intern() 메소드를 수행한 뒤 문자열은 equals()메소드가 아닌, ==으로 동일한지 비교할 수 있다. equals() 메소드로 비교하는 것과 ==으로 비교하는 것의 성능 차이는 많다. ==으로 비교하는 것이 훨씬 빠르다. 그러나 왜 사용하면 안되냐는 질문에는, 이렇게 답할 수 있다. 만약 새로운 문자열을 쉴새 없이 만드는 프로그램에서 intern() 메소드를 사용하여 억지로 문자열 풀에 값을 할당하도록 만들면, 저장되는 영역은 한계가 있기 때문에 그 영역에 대해 별도의 메모리를 청소하는 단계를 거치게 된ㄷ.ㅏ 따라서 작은 연산 하나는 빠르게 하기 위해서 전체 자바 시스템의 성능에 악영향을 주게 된다.

 

 

* immutable한 String의 단점을 보완하는 클래스에는 StringBuffer와 StringBuilder가 있다.

 

String은 immutable(=불변의)한 객체다. 즉, 한 번 만들어지면 더 이상 그 값을 바꿀 수 없다. 더하기로 더해지니 바꿀 수 있는 것처럼 보이지만 String 객체는 변하지 않는다. 만약 String 문자열을 더하면 새로운 String 객체가 생성되고, 기존 객체는 버려진다. 만약 하나의 String을 만들어 계속 더하는 작업을 한다면 계속 쓰레기를 만들게 된다. 예로 살펴보자.

 

String text = "Hello";
text = text + " world";

이 경우 "Hello"라는 단어를 갖고 있는 객체는 더 이상 사용할 수 없다. 쓰레기가 되어 나중에 가비지 컬렉션의 대상이 된다. 이러한 단점을 보완하기 위해 나온 클래스가 StringBuffer와 StringBuilder다. 두 클래스에서 제공하는 메소드는 동일하지만 StringBuffer는 Thread safe하다 하며, StringBuilder는 Thread safe하지 안다고 한다. 기능은 갖지만 StringBuffer가 StringBuilder보다 더 안전하며, 속도는 Thread safe하지 않는 클래스가 더 빠르다.

StringBuffer와 StringBuilder 클래스는 문자열을 더하더라도 새로운 객체를 생성하지 않는다. 그렇다고 + 를 사용하여 더할 수 있다는 말은 아니고 append() 라는 메소드를 사용한다. 예로 보자.

 

StringBuildersb = new StringBuilder();
sb.append("Hello");
sb.append(" world");

 

append() 메소드에 넘어가는 매개 변수가 이처럼 정해져 있는 문자열이라면 상관 없지만 매개 변수가 변수로 받은 값이라면 이야기는 달라진다. 메소드는 다음과 같이 append() 메소드를 여러 개 붙여서 사용해도 무방하다.

 

StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" world");

append() 메소드를 수행한 후에는 해당 StringBuilder객체가 리턴되므로, 그 객체에 계속 붙이는 작업을 하기 때문이다.

 

마지막으로 String과 StringBuilder, StringBuffer 클래스의 공통점에 대해 알아보자. 공통점이라면 모두 문자열을 다룬다는 점이고 CharSequence 인터페이스를 구현했다는 점이다. 이 세가지 중 하나의 클래스를 사용하여 매개 변수로 받는 작업을 할 때 String이나 StringBuilder 타입으로 받는 것보다는 CharSequence 타입으로 받는 것이 좋다. 

그러면 언제 StringBuilder를 사용하고 언제 StringBuffer 클래스를 사용해야 할까? 일반적으로 하나의 메소드 내에서 문자열을 생성하여 더할 경우 StringBuilder를 사용해도 전혀 상광없지만 어떤 클래스에 문자열을 생성하여 더하기 위한 문자열을 처리하기 위한 인스턴스 변수가 선언되었고, 여거 쓰레드에서 이 변수를 동시에 접근하는 일이 있을 경우에는 반드시 StringBuffer를 사용해야 한다. 

 

 

* 실습

 

public class UseStringMethod {

	public static void main(String[] args) {
		UseStringMethod sample = new UseStringMethod();
		String text = "The String class represents character strings.";
		sample.printWords(text);
		sample.findString(text, "string");
		sample.findAnyCaseString(text, "string");
		sample.countChar(text, 's');
		sample.pritContainWords(text, "ss");
	}
	
	public void printWords(String str) {
		String[] strArr = str.split(" ");
		for(String data : strArr) {
			System.out.println(data);
		}
	}
	
	public void findString(String str, String findStr) {
		System.out.println("string is appeared at " + str.indexOf(findStr));
	}
	
	public void findAnyCaseString(String str, String findStr) {
		str = str.toLowerCase();
		System.out.println("string is appeared at " + str.indexOf(findStr));
	}
	
	public void countChar(String str, char c) {
		char[] chrArr = str.toCharArray();
		int cnt = 0;
		for(char data : chrArr) {
			if(data == c)
				cnt++;
		}
		System.out.println("char 's' count is " + cnt);
	}
	
	public void pritContainWords(String str, String findStr) {
		String[] strArr = str.split(" ");
		for(String data : strArr) {
			if(data.contains(findStr))
				System.out.println(data + " contains " + findStr);
		}
	}
}

 

 

* 정리

 

String 클래스는 final 클래스인가요? 만약 그렇다면, 그 이유는 무엇인가요?
자바의 String클래스는 final로 선언되어 있으며, 더 이상 확장해서는 안된다. 


String 클래스가 구현한 인터페이스에는 어떤 것들이 있나요?
String 클래스는 Serializable, Comparable, CharSequence 인터페이스를 구현(implements) 했다.

String 클래스의 생성자 중에서 가장 의미없는 (사용할 필요가 없는) 생성자는 무엇인가요?
new String() 생성자는 가장 의미가 없는 String 클래스의 생성자이다. 왜냐하면, 생성된 객체는 해당 변수에 새로운 값이 할당되자마자 GC의 대상이 되어버리기 때문이다. 

String 문자열을 byte 배열로 만드는 메소드의 이름은 무엇인가요?
String클래스의 getBytes() 메소드는 문자열을 바이트의 배열로 전환한다.

String 문자열의 메소드를 호출하기 전에 반드시 점검해야 하는 사항은 무엇인가요?
String 객체의 메소드를 호출하기 전에 반드시 null인지 체크를 하는 습관을 가져야 한다. 그렇지 않으면, 실행시에 생각지도 못한 예외가 발생할 수 있다.

String 문자열의 길이를 알아내는 메소드는 무엇인가요?
length() 메소드를 사용하면 문자열의 길이를 알아낼 수 있다.

String 클래스의 equals() 메소드와 compareTo() 메소드의 공통점과 차이점은 무엇인가요? 
equals()메소드와 compareTo()메소드의 공통점은 두 개의 문자열을 비교한다는 것이고, 다른 점은 리턴 타입이 다르다는 것이다.
equals() 메소드는 boolean 타입의 리턴을, compareTo() 메소드는 int 타입의 리턴값을 제공한다.

문자열이 "서울시"로 시작하는지를 확인하려면 String의 어떤 메소드를 사용해야 하나요? 
startsWith()메소드를 사용하면 해당 문자열이 원하는 문자열로 시작하는지를 확인할 수 있다. 

문자열에 "한국"이라는 단어의 위치를 찾아내려고 할 때에는 String의 어떤 메소드를 사용해야 하나요?
contains()나 matches()메소드를 사용하여 원하는 문자열이 존재하는지 확인할 수 있다. 고전적인 방법으로는 indexOf() 메소드를 사용할 수도 있다.

위의 문제의 답에서 "한국"이 문자열에 없을 때 결과값은 무엇인가요? 
contains()나 matches() 메소드의 리턴타입은 boolean 이다.

문자열의 1번째부터 10번째 위치까지의 내용을 String으로 추출하려고 합니다. 어떤 메소드를 사용해야 하나요? 
substring()이나 subSequence()메소드를 사용하면 원하는 위치에 있는 문자열을 자를 수 있다. 

문자열의 모든 공백을 * 표시로 변환하려고 합니다. 어떤 메소드를 사용하는 것이 좋을까요?

replace()나 replaceAll()메소드를 사용하면 문자열의 특정 부분을 바꿀 수 있다. 여기서 중요한 것은 원본 문자열은 변경되지 않는다는 것이다. 변경된 값을 사용하려면 해당 메소드의 리턴값을 사용해야만 한다.


String의 단점을 보완하기 위한 두개의 클래스는 무엇인가요?
String의 단접을 보완하기 위한 클래스로 StringBuilder와 StringBuffer가 있다.


문제의 답에서 문자열을 더하기 위한 메소드의 이름은 무엇인가요?
StringBuilder와 StringBuffer클래스의 append() 메소드를 사용하여 문자열을 더할 수 있다. 

 

 

그럼 이만.