🟨 목 차 🟨
1. 자바에서 가장 많이 사용하는 String 클래스
1-1. String 클래스가 어떻게 선언되어 있는지 알아보기
1-2. String의 생성에는 이런 것들이 있다.
1-3. String 문자열을 byte로 변환하기 [ getBytes() ]
2. 객체의 널 체크는 반드시 필요하다
3. String의 내용을 비교하고 검색하는 메소드들도 있어요
3-1. 문자열의 길이를 확인하는 메소드 [ length() ]
3-2. 문자열이 비어 있는지 확인하는 메소드 [ isEmpty() ]
3-3. 문자열이 같은지 비교하는 메소드 [ equals(), equalsIgnoreCase(), compareTo, compareToIgnoreCase(), contentEquals() ]
3-4. 특정 조건에 맞는 문자열이 있는지를 확인하는 메소드 [ startsWith(), endsWith(), contain(), regionMatches() ]
3-5. String내에서 위치를 찾아내는 방법은 여러가지에요. [ idexOf(), lastIndexOf() ]
3-6. String의 값의 일부를 추출하기 위한 메소드들은 얘네들이다. [ charAt(), copyValuof(),substring(), subSequence() ]
3-6-1. 특정 위치의 char값을 추출하는 메소드 [ charAt(), getChars(), codPointAt() ]
3-6-2. char 배열의 값을 String으로 변환하는 메소드 [ copyValuof() ]
3-6-3. String 값을 char 배열로 변환하는 메소드 [ toCharArray() ]
3-6-4. 문자열의 일부 값을 잘라내는 메소드 [ substring(), subSequence() ]
3-6-5. 문자열을 여러 개의 String 배열로 나누는 split 메소드 [ split() ]
3-7. String 값을 바꾸는 메소드들도 있어요 [ trim(), replace(), replaceAll(), replaceFirst(), format(), toLowerCase(), toUpperCase(), valeOf() ]
3-7-1. 문자열을 합치는 메소드와 공백을 없애는 메소드 [ concat(), trim() ]
3-7-2. 내용을 교체(replace)하는 메소드 [ replace(), replaceAll(), replaceFirst() ]
3-7-3. 특정 형식에 맞춰 값을 치환하는 메소드 [ format() ]
3-7-4. 대소문자를 바꾸는 메소드 [ toLowerCase(), toUpperCase() ]
3-7-5. 기본 자료형을 문자열로 변환하는 메소드 [ valeOf() ]
4. 절대로 사용하면 안되는 메소드가 하나 있어요~
5. immutable한 String의 단점을 보완하는 클래스에는 StringBuffer와 StringBuilder가 있다.
5-1. String과 StringBuilder, StringBuffer 클래스의 공통점
5-2. StringBuilder와 StringBuffer 클래스를 언제 사용할까?
6. 정리
◼️ 1. 자바에서 가장 많이 사용하는 String 클래스 ◼️
시스템을 개발할때 String 클래스는 많이 사용하고 그만큼 String 클래스에 대해서 잘 알고 있어야한다.
◼️ 1-1. String 클래스가 어떻게 선언되어 있는지 알아보기
일단 String 클래스가 어떻게 선언되어 있는지 살펴보자.
public final calss String extends Object
이 클래스는 접근제어는 public이고, final로 선언되어 있다. 즉, 다른 패키지에서 사용 가능하고 다른 자식클래스에게 상속이 불가능한 클래스이고 Object 클래스를 확장 받는다.
implements Serializable, Comparable<String>, ChareSequence
implements(구현)으로는 Serializable, Comparable<String>, ChareSequence 라는 인터페이스를 구현해야한다.
- Serializable : 구현해야 하는 메소드가 하나도 없는 특이한 인터페이스다.[ Serializable을 구현한다고 선언해 놓으면, 해당 객체를 파일로 저장하거나 다른 서버에 전송 가능한 상태가 된다. 추후에 알게된다.]
- Comparable : 이 인터페이스는 compareTo()라는 메소드 하나만 선언되어 있다. 이 메소드는 매개 변수로 넘어가는 객체와 현재 객체가 같은지를 비교 하는데 사용한다.
[ equal() 메소드와 별 차이가 없다고 생각할 수 있겠지만, 이 메소드의 리턴 타입은 int이다. 같으면 0, 순서 상으로 앞에 있으면 -1, 뒤에 있으면 +1을 리턴한다. 즉, 객체의 순서 처리를 할 때 유용하게 사용될 수 있다. ]
그리고 선언문의 꺽쇠안에 String이라고 적어 주었는데 "제네릭"이라는 것을 의미한다.[ 제네릭은 추후에 배운다. ] - CharSequence : 해당 클래스가 문자열을 다루기 위한 클래스라는 것을 명시적으로 나타내는데 사용한다.
[ 추후에 배울 StringBuilder와 StringBuffer 클래스도 이 CharSequence 인터페이스를 구현해 놓는다.]
◼️ 1-2. String의 생성에는 이런 것들이 있다.
String의 생성자는 매우 많다. 생성자의 목록을 보기 전에 몇가지 용어에 대해 간단히 알아보자.
- 캐릭터 셋 : 문자의 집합을 의미하며, 쉽게 한글, 일본어와 같이 특정 나라의 글자를 말한다.
- 디코딩 : 일반적으로 암호화되어 있거나 컴퓨터가 이해할 수 있는 값들을 쉽게 변환하는 것을 말한다.
이제 String 클래스의 생성자에 대해서 알아보자.
위에 보이는 생성자들을 다 외울 필요는 없다. 각각의 생성자를 사용할 필요가 있을때 적당한 생성자를 API에서 찾아 사용하면 된다.
그나마 많이 사용하는 생성자를 추려 보면 아래와 같다.
- String(byte[] bytes)
- String(byte[] bytes, String charsetName)
위에 있는 생성자들은 한글을 사용하는 우리나라에서는 자주 사용할 수 밖에 없다. 왜냐하면 대부분의 언어에서는 문자열을 변환할 때 기본적으로 영어로 해석하려고 하기 때문이다.
"왜 위에 있는 2가지 밖에 안쓰지?" 라는 생각이 들 수 있는데 String 객체는 대부분 따옴표로 묶어 생성하기 때문에 굳이 어려운 생성자를 사용할 필요가 없기 때문이다.
◼️ 1-3. String 문자열을 byte로 변환하기 [ getBytes() ]
생성자의 매개 변수로 받는 byte배열은 어떻게 생성할까?
==> String 클래스에는 현재의 문자열 값을 byte 배열로 변환하는 getBytes()라는 메소드가 있다.
보통 캐릭터셋을 잘 알거나 같은 프로그램내에서 문자열을 byte 배열을 만들때는 getBytes()메소드를 사용하면 된다.
다른 시스템에서 전달 받은 문자열을 byte 배열로 변환할때는 나머지 2개의 메소드를 사용하는게 좋다. 왜냐하면 문자열이 다른 캐릭터 셋으로 되어 있을 수도 있기 때문이다.
캐릭터셋에 대해서 알파벳을 제외한 나라의 문자는 특수문자 취급을 받는다. ex) 특정 웹사이트에서 한글이 깨지는 이유가 있는데 이는 브라우저에서 생각하는 캐릭터 셋과 웹페이지에서 지정한 캐릭터셋이 다르기 때문
java.nio.Charset클래스 API에는 표준 캐릭터 셋이 정해져 있다.
한글을 처리하기 위해서 자바에서 많이 사용하는 캐릭터 셋은 UTF-16이다. [ 예전엔 UTF-8이나 EUC-KR을 많이 사용했지만, 요즘에는 UDF-16을 많이 사용 ]
String 클래스 예제
[EX]
package part15;
import java.util.StringTokenizer;
public class StringSample {
public static void main(String[] args) {
StringSample sample = new StringSample();
sample.convert();
}
public void convert() {
try {
String korean="한글"; //1
byte[] array1=korean.getBytes(); //2
for(byte data:array1) { //3
System.out.println(data + " ");
}
System.out.println();
String korean2 =new String(array1); //4
System.out.println(korean2);
}catch (Exception e) {
// TODO: handle exception
}
}
}
-19 -107 -100 -22 -72 -128
한글
위의 코드에서 주석처리 된 곳을 봐보자.
- : "한글"이라는 값을 String 객체인 Korean을 생성했다.
- : 방금 알아본 getBytes()라는 메소드를 이용하여 koean을 byte 배열로 만들었다.
- : 만들어진 byte 배여레 어떤 값들이 있는지 살펴보기 위해서 for 루프를 사용하여 각각의 byte값을 출력하도록 해 놓았다.
- : byte 배열을 갖고 String 객체를 만들기 위해서 byte 배열(array1)을 매개 변수로 갖는 String 객체를 생성하고, 그 문자열을 출력했다.
결과는 "한글"이라는 값이 출력되는것을 볼 수 있다.
==> getByte() 메소드는 플랫폼의 기본 캐릭터 셋으로 변한을 하고, String(byte[]) 생성자도 플랫폼의 기본 캐릭터 셋으로 변환하기 때문에 전혀 문제가 발생하지는 않았다.
중간에 있는 byte 배열의 값을 출력하는 부분은 자주 사용될 것 같으니 별도의 메소드로 만들어 놓자.
public void printByteArray(byte[] array) {
for(byte data:array) {
System.out.print(data + " ");
}
System.out.println();
}
이번에는 "UTF-16"이라는 캐릭터 셋으로 변환해보자.
[EX]
public void convertUTF16() {
try {
String korean ="한글";
byte[] array1=korean.getBytes("UTF-16");
printByteArray(array1);
String korean2 =new String(array1);
System.out.println(korean2);
}catch (Exception e) {
// TODO: handle exception
}
}
-2 -1 -43 92 -82 0
占쏙옙占?\占?
이상한 문자열로 나오는 것을 볼 수 있다. 즉, 잘못된 캐릭터 셋으로 변환을 하면 알아볼 수 없는 문자로 표시된다.
이렇게 글자가 깨지는 현상을 방지하기 위해 byte 배열로 생성할 때 사용한 캐릭터 셋을 문자열로 다시 전환할 때에도 동일하게 사용해야만 한다. 아래의 예제를 참고하자.
[EX]
public void convertUTF16() {
try {
String korean ="한글";
byte[] array1=korean.getBytes("UTF-16");
printByteArray(array1);
String korean2 =new String(array1, "UTF-16");
System.out.println(korean2);
}catch (Exception e) {
// TODO: handle exception
}
}
-2 -1 -43 92 -82 0
한글
String korean2를 생성하는 매개변수 부분에 캐릭터 셋을 지정한 것을 알 수 있다. 이렇게 전환할 캐릭터 셋을 지정하면 다음과 같이 정상적인 한글 문자열이 출력되는것을 알 수 있다.
EUC-KR의 경우는 한글 두 글자를 표현하기 위해 4 바이트를 사용하지만,
UTF-16의 경우는 6 바이트를 사용한다.
korean값을 변경해 보면서 확인해보니 글자수와 상관없이 무조건 2바이트의 차이가 발생한다는 것이다.
==> 자바에서 한글이 몇 바이트를 점유하는지 알아 두는것은 우리나라에서 개발하면서 매우 중요하다.
위에서 예제로 만든 convert()메소드는 왜 try-catch 블록으로 감싸놓았을까?
캐릭터 셋을 지정하는 메소드 및 생성자들 때문이다.
- byte 배열과 String 타입의 캐릭터 셋을 받는 생성자
- getbytes() 메소드 중에서 String 타입의 캐릭터 셋을 받는 메소드
위에 있는 메소드는 UnsupportedEncodingException을 발생 시킬 수 있다.
존재하지 않은 캐릭터 셋의 이름을 지정할 경우에는 이 예외가 발생하게 되므로 반드시 try-catch로 감싸주거나 메소드 선언시 throw 구문을 추가해 주어야만 한다.
[정리]
String의 클래스의 생성자와 getByte() 메소드에 알아보았다.
모든 생성자를 다 외우고 있을 필요는 없지만, 많이 사용되는 몇몇 생성자와 getByte() 메소드는 자바 개발을 목적으로 하는 사람은 꼭 기억해야 한다.
◼️ 2. 객체의 널 체크는 반드시 필요하다 ◼️
String뿐 만이 아니라 모든 객체를 처리할 때에는 널 체크를 반드시 해야만 한다.
널은 null로 표시하며 어떤 참조 자료형도 널이 될 수 있다.
객체가 널이라는 것은 객체가 아무런 초기화가 되어 있지 않으며, 클래스에 선언되어 있는 어떤 메소드도 사용할 수 없다는 것을 의미한다.
널체크를 하지 않으면 객체에 사용할 수 있는 메소드들은 모두 예외를 발생시킨다.
[EX] - nullcheck
package part15;
public class StringNull {
public static void main(String[] args) {
StringNull sample = new StringNull();
sample.nullCheck(null);
}
public boolean nullCheck(String text) {
int textLength=text.length();
System.out.println(textLength);
if(text==null) {
return true;
}
else {
return false;
}
}
}
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "text" is null
at part15.StringNull.nullCheck(StringNull.java:11)
at part15.StringNull.main(StringNull.java:7)
결과는 컴파일 오류는 안나오지만 실행시, NullPointException이 발생한다.
즉, 객체가 널인지 아닌지는 실행시에만 확인할 수 있다.
[EX] - nullcheck
public boolean nullCheck2(String text) {
if(text==null) {
return true;
}
else {
int textLength=text.length();
System.out.println(textLength);
return false;
}
}
NullPointException이 나오는 nullCheck 메소드를 수정한 nullCheck2()메소드이다.
이 메소드는 if를 통해 null값을 확인 후(Null 체크)에 결과값을 리턴 받기 때문에 NullPointException가 뜨지 않는다.
null 체크하는 것을 우습게 봐서는 안된다. 널 체크를 하지 않아서 애플리케이션이 비정상으로 작동하여 장애로 이어질 수도 있다. 아무리 강조해도 지나치지 않는 것이 바로 null 체크이고, 메소드의 매개 변수로 넘어오는 객체가 널이 될 확률이 조금이라도 있다면 반드시 한번씩 확인하는 습관을 가지자.
■ 3. String의 내용을 비교하고 검색하는 메소드들도 있어요 ■
String 클래스는 문자열을 나타낸다. 따라서, 문자열 내에 특정 위치를 찾거나 값을 비교하는 작업은 아주 빈번히 일어난다.
String 클래스 객체의 내용을 비교하고 검색하는 메소드들을 알아보자.
- 문자열의 길이를 확인하는 메소드
- 문자열이 비어 있는지 확인하는 메소드
- 문자열이 같은지 비교하는 메소드
- 특정 조건에 맞는 문자열이 있는지를 확인하는 메소드
■ 3-1. 문자열의 길이를 확인하는 메소드 [ length() ]
문자열의 길이를 확인할 때에는 lenth()메소드를 사용하면 된다. 배열도 객체이긴 하지만 메소드는 없는 특수한 객체이다.
그래서 배열의 크기를 확인할 때에는 괄호가 없는 lengh를 사용한다.
그 이외의 모든 클래스는 메소드를 호출해야 하며, String 객체의 길이를 확인하기 위해서는 length()라는 메소드를 사용해야만 한다.
[EX] - length()
package part15;
public class StringCompare {
public static void main(String[] args) {
// TODO Auto-generated method stub
StringCompare sample = new StringCompare();
sample.checkString();
}
public void checkString() {
String text = "Yout must know String class.";
System.out.println("text.leng()="+text.length());
}
}
text.leng()=28
■ 3-2. 문자열이 비어 있는지 확인하는 메소드 [ isEmpty() ]
문자열의 길이가 0인지 아닌지를 확인하는 것보다, 이 메소드를 사용하는 것이 훨씬 간단하다.
[EX] - isEmpty()
package part15;
public class StringCompare {
public static void main(String[] args) {
// TODO Auto-generated method stub
StringCompare sample = new StringCompare();
sample.checkString();
}
public void checkString() {
String text = "Yout must know String class.";
System.out.println("text.leng()="+text.length());
System.out.println("text.isEmpty()=" + text.isEmpty());
}
}
text.leng()=28
text.isEmpty()=false
text라는 객체는 27개의 char로 구성되어 있으므로 비어 있지 않다. 따라서 결과는 false를 리턴한다.
만약 text가 공백 하나로 되어 있는 문자열이라도, 이 메소드는 false를 리턴한다. [ 우리 눈으로 보기에 공백이나 빈칸이나 출력하면 같은 것처럼 느끼지만 프로그램은 다르게 인식한다. ]
■ 3-3. 문자열이 같은지 비교하는 메소드 [ equals(), equalsIgnoreCase(), compareTo, compareToIgnoreCase(), contentEquals(), ]
String 클래스에서 제공하는 문자열이 같은지 비교하는 메소드들은 매우 많다.
메소드들의 이름으로 분류하면 equals로 시작하는 메소드, compareTo로 시작하는 메소드, contentEquals메소드로 세가지 메소드로 분류할 수 있다. 이름들은 다르지만 모두 매개 벼수로 넘어온 값과 String 객체가 같은지를 비교하기 위한 메소드다.
[EX] - equals()
package part15;
public class StringCompare {
public static void main(String[] args) {
// TODO Auto-generated method stub
StringCompare sample = new StringCompare();
// sample.checkString();
sample.checkCompare();
}
public void checkString() {
String text = "Yout must know String class.";
System.out.println("text.leng()="+text.length());
System.out.println("text.isEmpty()=" + text.isEmpty());
}
public void checkCompare() {
String text="Check value";
String text2="Check value";
if(text==text2) {
System.out.println("text==text2 result is same.");
}else {
System.out.println("text==text2 result is different.");
}
if(text.equals("Check value")) {
System.out.println("text.equals(text2) result is same.");
}
}
}
text==text2 result is same.
text.equals(text2) result is same.
위의 코드를 보자.
일반적으로 생각하기에는 객체를 비교할때는 두번째 if문(equals사용)만 통과해서 결과가 출력될 것 같은데 아니다.
==> String 클래스도 기본적으로 == 비교가 아닌 equals()메소드를 사용해서 비교를 해야만 한다. 하지만 자바에 Constant Pool이라는 것이 존재하기 때문이다. Constant Pool 때문에 객체들을 재사용가능하며, String의 경우 동일한 값을 갖는 객체가 있으면, 이미 만든 객체를 재 사용한다.
만약 text2 생성 부분을 new를 이용하여 String 객체를 생성하면 어떻게 될까?
[EX] - equals()
public void checkCompare() {
String text="Check value";
String text2=new String("Check value");
if(text==text2) {
System.out.println("text==text2 result is same.");
}else {
System.out.println("text==text2 result is different.");
}
if(text.equals("Check value")) {
System.out.println("text.equals(text2) result is same.");
}
}
text==text2 result is different.
text.equals(text2) result is same.
==> 객체와의 비교이기때문에 equals로만 값을 비교가 가능한 것을 알 수 있다.
첫글자를 소문자로 시작하는 text3을 만들고 첫글자가 대문자인 text를 equalsIgnoreCase메소드를 사용해 비교해보자.
[EX] - equalsIgnoreCase()
package part15;
public class StringCompare {
public static void main(String[] args) {
// TODO Auto-generated method stub
StringCompare sample = new StringCompare();
// sample.checkString();
sample.checkCompare();
}
public void checkString() {
String text = "Yout must know String class.";
System.out.println("text.leng()="+text.length());
System.out.println("text.isEmpty()=" + text.isEmpty());
}
public void checkCompare() {
String text="Check value";
String text2=new String("Check value");
String text3 = "check value";
// if(text==text2) {
// System.out.println("text==text2 result is same.");
// }else {
// System.out.println("text==text2 result is different.");
// }
// if(text.equals("Check value")) {
// System.out.println("text.equals(text2) result is same.");
// }
if(text.equalsIgnoreCase(text3)) {
System.out.println("text.equalsIgnoreCase(text3) result is same.");
}
}
}
text.equalsIgnoreCase(text3) result is same.
==> 대소문자를 구분하지 않고 두 개자의 값이 같은지 다른지만을 확인하는 것을 볼 수 있다.
compareTo()메소드는 Comparable 인터페이스에 선언되어 있다.
[EX] - compareTo()
public void checkCompareTo() {
String text="a";
String text2="b";
String text3="c";
System.out.println(text2.compareTo(text));
System.out.println(text2.compareTo(text3));
System.out.println(text.compareTo(text3));
}
1
-1
-2
위의 코드를 보면 알 수 있듯이 compareTo()메소드는 보통 정렬을 할 때 사용한다. 따라서 true, false의 결과가 아니라, 비교하려는 매개 변수로 넘겨준 String 객체가 알파벳 순으로 앞에 있으면 양수를, 뒤에 있으면 음수를 리턴한다.
결과값은 외울 필요는 없지만, 각종 값드를 비교해서 어떤 결과가 나오는지 확인해 둘 필요가 있다.
compareToIgnoreCase()메소드는 equalsIgnoreCase()메소드와 마찬가지로, 대소문자 구분을 하지 않고 compareTo()메소드를 수행하는 것과 같다.
contentEquals()메소드는 매개변수로 넘어오는 CharSequence와 StringBuffer 객체가 String 객체촤 같은지를 비교하는 데 사용된다.
■ 3-4. 특정 조건에 맞는 문자열이 있는지를 확인하는 메소드 [ startsWith(), endsWith(), contain(), regionMatches() ]
이 중에서 가장 많이 사용하는 것은 startsWith()메소드이다. startsWith() 메소드는 이름 그대로 매개 변수로 넘겨준 값으로 시작하는지를 확인한다.
ex) "서울시 구로구 신도림동" 주소가 있을 시에 startsWith("서울시")를 호출하면 "서울시"의 주소를 갖는 모든 문자열을 쉽게 찾을 수 있다.
[indexOf()메소드를 사용하여 확인하는 것도 가능하지만 indexOf() 메소드의 단점은 문자열의 모든 내용을 다 확인해야 한다는 점이다.]
[EX] - startsWith()와 endsWith()
package part15;
public class StringCheck {
public static void main(String[] args) {
StringCheck sample = new StringCheck();
String addresses[] = new String[] {
"서울시 구로구 신도림동",
"경기도 성남시분당구 정자동 개발 공장",
"서울시 구로구 개봉동"
};
sample.checkAddress(addresses);
}
public void checkAddress(String[] addresses) {
int startCount=0, endCount=0;
String startText="서울시";
String endText="동";
for(String address:addresses) {
if(address.startsWith(startText)) {
startCount++;
}
if(address.endsWith(endText)) {
endCount++;
}
}
System.out.println("Starts with " +startText+ " count is " + startCount);
System.out.println("Ends with " +endText+ " count is " + endCount);
}
}
Starts with 서울시 count is 2
Ends with 동 count is 2
"서울시"로 시작하는 문자열은 두 개 있고, "동"으로 끝나는 주소도 두 개 이기 때문에 결과는 위와 같다.
첫번째 값과 끝에 있는 값들은 이제 확인할 수 있다. 그러면 중간에 있는 값들은 어떻게 확인 가능할까?
==> contains()메소드이다. 이 메소드는 매개변수로 넘어온 값이 문자열에 존재하는지를 확인한다. 그 다음에 있는 matches()메소드는 contains()메소드와 비슷하긴 하지만, 매개변수로 넘어오는 값이 "정규표현식"으로 되어 있어야만 한다.[ *정규표현식 : 이메일을 점검하거나, 웹 페이지의 URL을 점검하는 등의 작업을 쉽게 하기 위해서 공식에 따라 만든 식을 말한다. 자바에서는 정규 표현식을 제공하며, java.util.regex 패키지의 pattern 클래스 API에 있는 내용을 잘 읽어보면 많은 정보를 얻을 수 있다.]
[EX] - contain()
package part15;
public class StringCheck {
public static void main(String[] args) {
StringCheck sample = new StringCheck();
String addresses[] = new String[] {
"서울시 구로구 신도림동",
"경기도 성남시분당구 정자동 개발 공장",
"서울시 구로구 개봉동"
};
// sample.checkAddress(addresses);
sample.containsAddress(addresses);
}
public void checkAddress(String[] addresses) {
int startCount=0, endCount=0;
String startText="서울시";
String endText="동";
for(String address:addresses) {
if(address.startsWith(startText)) {
startCount++;
}
if(address.endsWith(endText)) {
endCount++;
}
}
System.out.println("Starts with " +startText+ " count is " + startCount);
System.out.println("Ends with " +endText+ " count is " + endCount);
}
public void containsAddress(String[] addresses) {
int containCount=0;
String containText="구로";
for(String address: addresses) {
if(address.contains(containText)){
containCount++;
}
}
System.out.println("Contais " + containText + " count is " + containCount);
}
}
Contais 구로 count is 2
위 코드를 보면 알겠지만 "구로"를 포함한 문자열의 개수는 두 개 이다. 따라서 이 메소드의 결과도 다음과 같이 나온다.
[ 지금은 달랑 3개의 문자열이자만, 몇 천개, 몇 만개 의 데이터를 확인하는 경우에 사용하기 위한 것이다. ]
regionMatches()라는 메소드는 문자열 중에서 특정 영역이 매개변수로 넘어온 문자열과 동일 한 지를 확인하는데 사용된다.
매개 변수 목록을 보면 알겠지만, 하나는 대소문자를 구분할지 여부를 지정할 수 있고, 다른 하나는 그런 지정 자체가 불가능하다.
이 regionMatches()메소드의 매개변수로 넘어오는 값이 많으니 어떤 값을 뜻하는지 확인하자.
예제를 통해 이해해보자.
[EX] - regionMatches()
public void checkMatch() {
String text = "This is a text";
String compare1= "is";
String compare2= "this";
System.out.println(text.regionMatches(2, compare1, 0, 1)); //매개변수가 4개인 메소드
System.out.println(text.regionMatches(5, compare1, 0, 2)); //매개변수가 4개인 메소드
System.out.println(text.regionMatches(true,2, compare2, 0, 4)); //매개변수가 5개인 메소드
}
true
true
true
위의 코드를 보면 text값과 비교할 compare1과 compare2가 있다. 이 메소드의 수행 결과가 제대로 나올지를 확인하려면, 각 char의 위치가 어디에 해당하는지 알아야만 한다.[ String의 char위치는 배열과 마찬가지로 1부터 시작하는 것이 아니라, 0부터 시작한다.]
text 문자에서 "is"라는 문자열이 시작되는 위치를 잘 학인해보자. "is"가 나오는 것은 2번째 위치와 5번째 위치다.
regionMatches(2, compare1, 0, 1)
위의 regionMatches()를 보게 되면 첫번째 매개 변수(시작할 위치)인 2라는 위치 값은 제대로 맞았다. 비교하려고 하는 compare1의 값이 "is"이다. 그런데 세번째 매개변수가 0이고, 네번째 매개변수(비교할 갯수)가 1이기 때문에 비교하려는 것은 "i"인지 아닌지만 확인하면 된다. 따라서 결과는 true이다.
regionMatches()메소드를 잘못 사용하면, 원하는 결과를 얻지 못할 수도 있다. 아래와 같은 결과는 "무조건 false"로 나온다.
- tooffset이 음수일 때
- ooffset이 음수일 때
- toffset+len이 비교 대상의 길이보다 클 때
- ooffset+len이 other 객체의 길이보다 클 때
- ignoreCase가 false인 경우에는 비교 범위의 문자들 중 같은 위치에 있는 char가 다를때
- ignoreCase가 true인 경우에는 비교 범위의 문자들을 모두 소문자로 변경한 후 같은 위치에 있는 chcar가 달라야한다.
String 문자열의 내용을 비교하고 검색하는 메소드들 꼭 외울 필요는 없다. 개발하다 보면 자연스레 외워질 것이다.
■ 3-5. String내에서 위치를 찾아내는 방법은 여러가지에요. [ idexOf(), lastIndexOf() ]
예를들어 "java god is good book" 이라는 문장에서 "is" 라는 단어가 시작하는 위치를 알고 싶을 때 어떻게 해야할까?
==> 자바의 String 클래스에서는 indexOf라는 단어가 포함되어 있는 메소드를 제공한다. 이 메소드를 사용하면 해당 객체의 특정 문자열이나 char가 있는 위치를 알 수 있다. 만약 찾고자 하는 문자열이나 char가 없으면 -1을 리턴한다.
위치를 찾는 메소드들
indexOf()메소드는 String 클래스의 가장 많이 사용되는 메소드 중 하나이다.
종류는 크게 indexOf()와 lastIndexOf()의 두가지로 나온다.
- indexOf()는 앞에서부터(가장 왼쪽) 문자열이나 char을 찾는다
- lastIndexOf()는 뒤에서부터(가장 오른쪽) 문자열이나 char을 찾는다.
String을 매개 변수로 갖는 메소드는 이해가 되는데 int를 매개변수로 int를 매개 변수로 갖는 메소드는 어떻게 사용해야 할까?
==> char는 정수형이다. 다행히 자동으로 형변환이 일어나기 때문에 걱정하지 않아도 된다.
[EX] - indexOf()
public void checkIndexOf() {
String text = "Java technology is both a programming language and a platform.";
System.out.println(text.indexOf('a')); //1
System.out.println(text.indexOf("a ")); //2
System.out.println(text.indexOf('a',20)); //3
System.out.println(text.indexOf("a ",20));
System.out.println(text.indexOf('z')); //4
}
1
3
24
24
-1
위 코드에 달린 주석 번호에 대해 알아보자.
- 'a'의 형태로 매개 변수를 넘겨주어도 컴파일 및 실행하는데 전혀 문제가 없다.
- String 타입의 매개변수를 넘겨 주었으며, a 뒤에는 공백이 하나 있다.
- 세번째와 네번째 출력문은 모두 text 문자열의 20번째 자리부터 값을 확인한다.
- 마지막에 있는 출력문은 이 문장에 없는 "z"를 찾은 결과를 출력한다.
결과를 확인해보자.
- 첫번째 출력문 : "a"는 1의 위치에 있어 결과는 1이다.
- 두번째 출력문 : a 다음에 공백이 있는 위치를 찾는것이므로 "java"단어 뒤에 공백이 있으므로 3이다.
- 세번째 네번째 출력문 : 20번째 자리부터 찾는 작업을 하고, 제일 처음 나온 관사 a의 위치가 24번째이므로 결과가 동일하다.
- 다섯번째 출력문 : z가 문장에 없으므로 -1을 출력했다.
문자열의 가장 뒤부터 검색을 하는 lastIndexOf()메소드도 이와 비슷한 원리이므로 생략을 하겠다.
■ 3-6. String의 값의 일부를 추출하기 위한 메소드들은 얘네들이다. [ charAt(), copyValuof(), substring(), subSequence() ]
문자열의 위치를 찾는 이유는 여러가지다. 보통은 그 위치부터 어떤 값을 추출해 내거나, 그 값이 존재하는지를 확인할 때 사용한다.
값을 추출하는 메소드의 종류
- char 단위의 값을 추출하는 메소드
- char 배열의 값을 String으로 변환하는 메소드
- String의 값을 char 배열로 변환하는 메소드
- 문자열의 일부 값을 잘라내는 메소드
- 문자열을 여러 개의 String 배열로 나누는 메소드
■ 3-6-1. 특정 위치의 char값을 추출하는 메소드 [ charAt(), getChars(), codPointAt() ]
마지막에 있는 offsetByCodePoints()메소드는 문자열 인코딩과 관련된 문제를 해결하기 위해서 사용된다.
여기에 있는 메소드들은 그리 많이 사용되지 않는다. 그나마 많이 사용하는 메소드는 charAt()메소스다.
■ 3-6-2. char 배열의 값을 String으로 변환하는 메소드 [ copyValuof() ]
copyValueOf()라는 메소드도 그리 많이 사용하지는 않는다. 이러한 메소드가 있는지 알고 있는 것은 많은 도움이 된다.
단, 이 메소드는 static 메소드 이기 때문에 현재 사용하는 문자열을 참조하여 생성하는 것이 아닌, static하게 호출하여 사용해야 한다.
[EX] - copyValueOf()
char values[] = new char[]{'J','a','v','a'};
String javaText=String.copyValueOf(values);
■ 3-6-3. String 값을 char 배열로 변환하는 메소드 [ toCharArray() ]
이 메소드도 많이 사용하지는 않는다.
어떤 String 객체를 만들더라도, 그 객체는 내부에 char 배열을 포함한다.
앞에서 본 copyValueOf() 메소드에서 "java"라는 값이 char 배열로 저장되어 있듯이, String 객체 내부에는 항상 이러한 char 배열이 포함되어 있다는 말이다.
■ 3-6-4. 문자열의 일부 값을 잘라내는 메소드 [ substring(), subSequence() ]
위에 있는 메소드들은 자바에서 문자열을 다룰 때 indexOf()메소드와 더불어 가장 많이 사용하는 메소드 중 하나다. 꼭 알아두자.
"Java technology"라는 문자열이 있을 때, "technology"라는 단어만 추출하려고 할 때 다음과 같이 substring() 메소드를 사용하면 된다.
[EX] - substring()
public void checkSubstring() {
String text="Java technology";
String technology=text.substring(5);
System.out.println(technology);
}
technology
text라는 문자열에서 5번째부터 text 문자열이 끝날 때까지 모두 잘라내어 technology를 추출하였디.
만약 tech라는 단어만 잘라내고 싶을때 어떻게 할까?
public void checkSubstring() {
String text="Java technology";
String technology=text.substring(5);
System.out.println(technology);
String tech = text.substring(5,9);
System.out.println(tech);
}
두번째 매개변수에 언제까지 자를지 정수를 입력해주면 원하는 문자열을 자를 수 있다.
즉, substring()메소드는 첫번째 매개변수 정수 위치에서부터 두번째 매개변수 정수 위치까지 문자열을 자르는것이다.
우리는 substring()메소드를 사용할 때에는 보통 단어가 시작하는 위치를 알 수 있는 indexOf()메소드와 같이 사용하여 문자열을 잘라내야만 한다.
■ 3-6-5. 문자열을 여러 개의 String 배열로 나누는 split 메소드 [ split() ]
자바에서 문자열을 여러 개의 문자열의 배열로 나누는 방법은 String 클래스에 선언된 split() 메소드를 사용하는 것과 java.util.StringTokenizer라는 클래스를 사용하는 것이다.
- 만약 정규표현식을 사용하여 문자열을 나누려고 한다면 String 클래스의 split() 메소드를 쓰면 된다.
- 그렇지 않고 그냥 특정 String으로 문자열을 나누려고 한다면 StringTokenizer 클래스를 사용하는 것이 편하다.
특정 알파벳이나 기호 하나로 문자열을 나누려고 한다면 String 클래스의 split()메소드를 스거나, StringTokenizer 클래스를 쓰거나 큰 상관 없다. [ StringTokenizer는 추후에 배운다. ]
[EX] - split()
public void checkSplit() {
String text = "Java technology is both a programming language and a platform.";
String[] splitArray=text.split(" ");
for(String temp:splitArray) {
System.out.println(temp);
}
}
Java
technology
is
both
a
programming
language
and
a
platform.
■ 3-7. String 값을 바꾸는 메소드들도 있어요 [ trim(), replace(), replaceAll(), replaceFirst(), format(), toLowerCase(), toUpperCase(), valeOf() ]
문자열의 값을 바꾸고, 변환하는 메소드도 다음과 같이 구분할 수 있다.
- 문자열을 합치는 메소드와 공백을 없애는 메소드
- 내용을 교체(replace)하는 메소드
- 특정 형식에 맞춰 값을 치환하는 메소드
- 대소문자를 바꾸는 메소드
- 기본 자료형을 문자열로 변환하는 메소드
■ 3-7-1. 문자열을 합치는 메소드와 공백을 없애는 메소드 [ concat(), trim() ]
concat()메소드 문자열을 더하는 것으로, + 로 문자열을 더할 수 있기 때문에 쓸 일 이 없다.
trim()메소드는 공백을 제거할 때 매우 유용하게 많이 사용된다. 문자열의 앞과 뒤에 있는 공백을 일일이 찾아서 지워버릴 필요가 없이 이 메소드만 사용하기 때문이다.
[EX] - trim()
public void checkTrim() {
String string[] = new String[] {
" a","b "," c", "d ","e f"," "
};
for(String strings:string) {
System.out.println("["+strings+"] ");
System.out.println("["+strings.trim()+"] ");
}
}
[ a]
[a]
[b ]
[b]
[ c]
[c]
[d ]
[d]
[e f]
[e f]
[ ]
[]
trim() 메소드를 적용하면 공백이 없어진것을 알 수 있다.
아래와 같이 Null체크를 하고 공백을 제외한 char 값이 하나라도 존재하는 String 검사를 하는 습관을 갖자.
String text=" a ";
if(text!=null && text.trim().length() > 0) {
System.out.println("OK"):
}
■ 3-7-2. 내용을 교체(replace)하는 메소드 [ replace(), replaceAll(), replaceFirst() ]
replace로 시작하는 메소드는 문자열에 있는 내용 중 일부를 변경하는 작업을 수행한다.
[ 대소문자를 구분한다. ]
[EX] - 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
}
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.
위의 코드에서 주석 번호에 대해 이야기 해보자.
- text 객체에 있는 char 's'를 'z'로 변환하는 작업을 수행한다. (소문자만 가능)
- replace() 메소드를 수행한 후에 기존의 값이 변경되는지를 확인하기 위해서 현재 값을 출력한다.
- CharSequence 타입의 매개변수를 사용하여 값을 변경한다.
- 정규표현식을 사용하는 replaceAll() 메소드의 예이며, 공백을 슬러시(/)로 변환한다.
- replaceFirst() 메소드를 사용하여 첫 번째 공백만 파이프로 변환한다.
■ 3-7-3. 특정 형식에 맞춰 값을 치환하는 메소드 [ format() ]
[ *Locale : 지역적으로 다른 표현 형식을 제공하기 위한 것이다. 보통 Locale을 지정하지 않으면 기본적으로 자바 프로그램이 수행되는 OS의 지역 정보를 기본으로 따른다. ]
format()메소드는 정해진 기준에 맞춘 문자열이 있으면, 그 기준에 있는 내용을 변환한다.
[ 자바에서 %s는 String을, %d는 정수형을, %f는 소수점이 있는 숫자, %%는 %를 의미한다. ]
[EX] - format()
public void checkFormat() {
String text="제 이름은 %s입니다. 지금까지 %d 권의 책을 썼고, "
+"하루에 %f %%의 시간을 책을 쓰는데 할애하고 있습니다.";
String realText=String.format(text, "이상민",7,10.5);
// String realText=String.format(text, "이상민",7);
System.out.println(realText);
}
제 이름은 이상민입니다. 지금까지 7권의 책을 썼고, 하루에 10.5000000 %의 시간을 책을 쓰는데 할애하고 있습니다.
위와 같이 출력만을 위해서 이 메소드를 사용하는 이유라면 System.out.format()이라는 메소드도 있기 때문이다.
주의해야할 사항은 대치해야 할 문자열이 3개 인데, format 뒤에 3개 이상의 매개 변수를 나열하는것은 상관없다. 하지만, 2개 이하로 매개변수만 명시하면 예외가 발생한다. 즉 대치해야 할 문자열보다 같거나 그 이상 이여야 한다.
■ 3-7-4. 대소문자를 바꾸는 메소드 [ toLowerCase(), toUpperCase() ]
toLower로 시작하는 메소드는 모든 대문자를 소문자로, toUpper로 시작하는 메소드는 모든 소문자를 대문자로 변경하는 메소드다.
■ 3-7-5. 기본 자료형을 문자열로 변환하는 메소드 [ valeOf() ]
여기에 나열된 메소드들은 기본 자료형을 String 타입으로 변환한다.
byte b=1;
String byte1=String.valueof(b);
String byte2=b+"";
기본 자료형을 바꾸는 방법은 byte1 처럼 valueof() 메소드를 사용해서 바꿔도 되고 byte2의 방법처럼 String과 합치면 된다.
==> String으로 변환만 해놓고 별도의 문자열과 합치는 과정이 없을 경우에는 valueOf() 메소드를 사용하는 것을 권장한다.
또한 valueof()메소드는 객체가 null이면 "null"이라는 문자열을 리턴해주기 때문에 NullPointerException이 나오는 경우에 유용하게 쓰일 수 있다.
■ 4. 절대로 사용하면 안되는 메소드가 하나 있어요~ ■
intern()이라는 메소드를 쓰지 말자. 이 메소드는 자바로 구현되지 않고 C로 구현되어 있는 native 메소드 중 하나다.
쓰지 말라고 하는 이유는 시스템의 심각한 성능 저하를 발생시킬 수도 있기 때문이다.
[EX]
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와 같이 객체를 생성하면, String 클래스에서 관리하는 문자열 풀에 해당값이 있으면 기존에 있는 객체를 참고하고, text3과 같이 String 객체를 생성하면 같은 문자열이 있든 말든 새로운 객체를 생성한다고 이야기 했다.
그래서 결과는 위와 같다. [ https://bj-turtle.tistory.com/61 여기서 배운 내용과 조금 상이해서 text1.equals(text3)의 결과가 왜 true인 지 잘 모르겠다. 나는 참조 자료형(객체)끼리의 equal 비교는 객체의 주소값을 비교하기 때문에 다른값으로 뜰 것이라고 생각한다.
위의 의문점은 풀렸다~^^ 아래 링크를 참고하자~ https://bj-turtle.tistory.com/65]
여기서 intern() 메소드를 추가해보자.
[EX] - intern()
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()메소드를 사용하여 억지로 문자열 풀에 값을 할당하도록 만들면, 저장되는 영역은 한계가 있기 때문에 그 영역에 대해서 별도로 메모리를 청소하는 단계를 거치게 된다. 따라서, 작은 연산 하나를 빠르게 하기 위해서 전체 자바 시스템의 성능에 악영향을 주게 된다.
우리가 만드는 애플리케이션에서 생성하는 문자열이 정해져 있고, 그 문자열에 대해서만 intern()메소드를 호출하여 사용할 경우에는 문제가 되지 않을 수도 있다. 하지만, 생성되는 문자열이 완전히 정해져 있는 시스템은 거의 없다. 따라서 interun()메소드는 절대로 사용하지 말아라.
결론은 intern()메소드를 사용해서는 안된다는 것이다.
■ 5. immutable한 String의 단점을 보완하는 클래스에는 StringBuffer와 StringBuilder가 있다. ■
String은 immutable한 객체다. immutable이라는 말은 사전적인 의미로 "불변의"라는 의미다.
즉, 다시 말해서 한번 만들어지면 더 이상 그 값을 바꿀 수 없다. "무슨 말이지? 더하기 하면 잘만 더해지는데..?" 아니다. 이 생각은 틀렸다.
즉, String 객체는 변하지 않는다. 만약 String 문자열을 더하면 새로운 String 객체가 생성되고, 기존 객체는 버려진다. 그렇기에 계속 하나의 String을 만들어 계속 더하는 작업을 한다면, 계속 쓰레기를 만들게 된다.
String text="Hello";
text=text+" world";
위의 같은 경우, "Hello"라는 단어를 갖고 있는 객체는 더 이상 사용할 수 없다. 즉 쓰레기가 되며, 나중에 가비지 컬렉션(GC)의 대상이된다.
이러한 String클래스의 단점을 보완하기 위해서 나온 클래스가 StringBuffer와 StringBuilder다.
두 클래스에서 제공하는 메소드는 동일하다.
하지만, StringBuffer는 Thread safe하다고 하며, StringBuilder는 Thread safe하지 않다고 한다.
아직 쓰레드라는 것을 배우지 않아서 이해가 되지 않겠지만 기능은 같지만 StringBuffer가 StringBuilder 보다 더 안전하다고만 기억하면 된다. 속도는 Thread safe하지 않은 클래스가 더 빠르다.
StringBuffer와 StringBuilder 클래스는 문자열을 더하더라도 새로운 객체를 생성하지 않는다.
==> 그렇기에 더하기(+) 기호를 사용하여 더하지 못하고 가장 많이 사용하는 append()메소드를 사용한다.
append()메소드는 매개 변수로 모든 기본 자료형과 참조 자료형을 모두 포함한다.
[EX] - StringBuffer 클래스, append()
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" world");
System.out.println(sb);
Hello world
[EX] - StringBuffer 클래스, append()
StringBuilder sb = new StringBuilder();
System.out.println(sb.append("Hello").append(" world"));
Hello world
위의 두가지 방법으로 append()메소드를 사용할 수 있다.
JDK 5 이상에서는 String 더하기 연산을 할 경우 컴파일 할 때 자동으로 해당 연산을 StringBuilder로 변환해준다.
따라서 일일이 더하는 작업을 변환해 줄 필요는 없으나, for 루프와 같이 반복 연산을 할 때에는 자동으로 변환을 해주지 않으므로, 꼭 필요하다.
■ 5-1. String과 StringBuilder, StringBuffer 클래스의 공통점
모두 문자열을 다룬다는 점이 공통점이다. 또 다른 공통점은 CharSequence 인터페이스를 구현했다는 점이다.
==> 이 세가지 중 하나의 클래스를 사용하여 매개 변수로 받는 작업을 할 때 String이나 StringBuilder 타입으로 받는 것보다는 CharSequence 타입으로 받는 것이 좋다.
■ 5-2. StringBuilder와 StringBuffer 클래스를 언제 사용할까?
일반적으로 하나의 메소드 내에서 문자열을 생성하여 더할 경우에는 StringBuilder를 사용해도 전혀 상관 없다.
그런데, 어떤 클래스에 문자열을 생성하여 더하기 위한 문자열을 처리하기 위한 인스턴스 변수가 선언되었고,여러 쓰레드에서 이 변수를 동시에 접근하는 일이 있을 경우에는 StringBuffer를 사용해야만 한다.
안전 : StringBuilder < StringBuffer
■ 6. 정리 ■
String 클래스를 잘 사용해야만 메모리를 효율적으로 사용할 수 있고, 여러 String을 더하는 연산이 존재할 경우에는 StringBuilder나 StringBuffer 클래스를 적절하게 선택하여 활용해야만 한다.
'JAVA > 자바의신 1' 카테고리의 다른 글
17장 어노테이션이란? (0) | 2022.09.13 |
---|---|
16장 클래스 안에 클래스가 들어갈 수도 있구나 (0) | 2022.09.12 |
14장 다 배운 것 같지만, 예외라는 중요한 것이 있어요.(예외 처리 try-catch) (1) | 2022.09.09 |
13장 인터페이스와 추상클래스, enum (0) | 2022.09.09 |
12장 모든 클래스의 부모 클래스는 Object에요 (1) | 2022.09.08 |