목차
- Object
1-1. toString()
1-2. equals()
1-3. hashCode() - String
2-1. String 제공 메소드들
2-2. String 인스턴스 생성 방법
2-3. 구분자 사용한 String 분리
2-4. 이스케이프(escape) 문자 - StringBuilder와 StringBuffer
3-1. String과 StringBuilder 비교
3-2. StringBuilder의 toString()과 hashCode()
3-3. StringBuilder제공 메소드
API(Application Programming Interface)
- 운영체제나 프로그래밍 언어가 제공하는 기능들을 응용 프로그램에서 사용할 수 있도록 제어 가능하게 만든 인터페이스를 말한다.
'추상클래스와 인터페이스' 단원에서 다뤘던 것과는 다른 용어이니 혼용하지 말자.
자바 API
- 자바 플랫폼 위에서 동작하는 애플리케이션 개발 시 활용된다.
- Math, Scanner 등처럼 JDK 설치하면 시스템 제어 또는 편의를 위해 제공된 유용한 클래스 및 인터페이스들이 있다.
1. Object
- Object는 java.lang 패키지에 존재한다.
- String 등 java.lang.*;에 해당하는 것은 별도 import 구문 없이 사용해왔다. 컴파일러가 자동으로 추가해주기 때문이다.
- 모든 클래스는 Object 클래스의 후손이다.
- 관례상 많이 오버라이딩 해서 사용하는 Object 클래스의 메소드에는 다음과 같은 것들이 있다.
1-1. toString()
Object 클래스의 toString()
주소값 반환
Book bookA = new Book(1, "홍길동전", "허균", 50000);
System.out.println("bookA.toString() : " + bookA.toString());
==========================
bookA.toString() : com.reminder.object.Book@4926097b
- java.lang.Object 클래스의 toString() 메소드는 인스턴스가 생성될 때 사용한 full class name과 @ 그리고 16진수 해시코드를 문자열로 반환한다.
- 16진수 해시코드는 인스턴스의 주소를 가리키는 값으로 인스턴스마다 모두 다르게 반환된다.
System.out.println("bookA : " + bookA);
==========================
bookA : com.reminder.object.Book@4926097b
- 레퍼런스 변수만 출력하는 경우도 toString() 메소드가 자동 호출돼 같은 결과가 나온다.
❗ 앞으로는 getInformation 메소드를 별도 정의하는 대신에
toString()을 오버라이딩하여 필드값들을 하나의 문자열로 재정의해 사용할 수 있게 된 것이다.
@Override
toString()
필드값 반환
@Override
public String toString() {
return "Book [number=" + this.number
+ ", title=" + this.title
+ ", author=" + this.author
+ ", price=" + this.price
+ "]";
}
System.out.println("bookA : " + bookA);
==========================
bookA : Book [number=1, title=홍길동전, author=허균, price=50000]
- 메소드를 위와 같이 오버라이딩 하고 → 출력문에 레퍼런스 변수명만 담으면 이제는 주소값이 아닌 설정된 필드값이 출력된다.
❗ toString() 자동 완성
우클릭 → Source → Generate toString()
1-2. equals()
Object 클래스의 equals()
동일객체 판단
주소값 기준 true or false
- java.lang.Object의 equals()는 동일 인스턴스인지를 판단한다.
- Object의 equals() 메소드는 주소값을 기준 삼아 매개변수로 전달 받은 인스턴스와 == 연산하여 true 또는 false 반환한다.
❗ 동일객체 : 주소가 동일한 인스턴스
❗ 동등객체 : 주소는 다르지만 필드값이 동일한 객체
public boolean equals(Object obj) {
return (this == obj);
}
- 매개변수 선언부에 Object가 들어가는 건 모든 클래스는 Object의 후손이기 때문이다.
- String 클래스 역시 Object를 상속 받고 있다.
Book bookA = new Book(1, "홍길동전", "허균", 50000);
Book bookB = new Book(1, "홍길동전", "허균", 50000);
System.out.println("두 인스턴스의 == 연산 비교 : " + (bookA == bookB));
System.out.println("두 인스턴스의 equals() 연산 비교 : " + (bookA.equals(bookB)));
==========================
두 인스턴스의 == 연산 비교 : false
두 인스턴스의 equals() 연산 비교 : false
@Override
equals()
동등객체 판단
필드값 기준 true or false
- 주소는 다르되 같은 필드값을 가지고 있는지 판단하고자 할 때는 equals() 메소드를 오버라이딩해 사용할 수 있다.
- 이때는 각각의 필드가 동일한 값을 가지는지 확인하고 모든 필드값이 같을 때 true, 아니라면 false를 반환한다.
❗ equals() 자동 완성
우클릭 → Source → Generate hashCode() and equals()
/* equals -------------------------------------------------------------------- */
@Override
public boolean equals(Object obj) {
/* 두 인스턴스의 주소가 같으면 이후 다른 내용을 비교할 것 없이 true 반환 */
if (this == obj)
return true;
/* this는 인스턴스가 생성되면 주소값이 저장된다. null일 수 없다.
* 따라서 전달 받은 레퍼런스 변수에 null 값이 저장되어 있다면
* 비교하려는 두 개의 인스턴스는 서로 다른 인스턴스이다.
* */
if (obj == null)
return false;
/* this와 obj의 클래스가 같지 않다면 필드값을 비교할 필요가 없다. */
if (getClass() != obj.getClass())
return false;
/* 현재 Object이므로 자식이 가지고 있는 멤버에 접근하려면 다운캐스팅이 필요하다.
* 전달 받은 레퍼런스 변수를 Book 타입으로 형변환하여 각 필드별로 비교를 시작한다.
* */
Book other = (Book) obj;
/* String 타입의 경우 null 여부 먼저 확인 */
if (author == null) {
if (other.author != null)
return false; //this는 null, other는 not null이므로 달라서 false
/* 여기서 equals는 클릭해보면 String 클래스의 메소드를 가리킨다.
* this가 null이 아닌 경우 String 클래스의 equals를 호출해서 값 비교
* */
} else if (!author.equals(other.author))
return false; //다르다 false 나오면 여기서 끝낸다
/* 숫자값은 바로 값 비교 가능 */
if (number != other.number)
return false;
if (price != other.price)
return false;
/* title도 String 타입이므로 author와 동일 */
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
/* 모든 조건을 통과하면 두 인스턴스의 모든 필드는 같은 값을 가지므로 true 반환 */
return true;
}
Book bookA = new Book(1, "홍길동전", "허균", 50000);
Book bookB = new Book(1, "홍길동전", "허균", 50000);
System.out.println("두 인스턴스의 == 연산 비교 : " + (bookA == bookB));
System.out.println("두 인스턴스의 equals() 연산 비교 : " + (bookA.equals(bookB)));
==========================
두 인스턴스의 == 연산 비교 : false
두 인스턴스의 equals() 연산 비교 : true
- 이처럼 오버라이딩을 마친 equals() 메소드는 필드값을 기준으로 판단해 true라는 값을 반환하게 된 것이다.
1-3. hashCode()
❗ equals()와 hashCode()는 동등성 판단을 위해 함께 오버라이딩해야 한다!
두 메소드는 자동 생성할 때도 같이 출력된다.
❗ hashCode() 자동 완성
우클릭 → Source → Generate hashCode() and equals()
Object 클래스 명세에 작성된 일반 규약에 따르면, equals() 메소드를 재정의 하는 경우 반드시 hashCode() 메소드도 재정의하도록 되어 있다. 만약 hashCode()를 재정의하지 않으면 '같은 값을 가지는 동등 객체는 해시코드 역시 같은 값을 가져야 한다'는 규약에 위반된다. 강제성은 없지만 규약대로 작성하는 것이 좋다.
Object 클래스의 hashCode()
동등객체라도 다른 주소값
Book bookA = new Book(1, "홍길동전", "허균", 50000);
Book bookB = new Book(1, "홍길동전", "허균", 50000);
System.out.println("bookA의 hashCode : " + bookA.hashCode());
System.out.println("bookB의 hashCode : " + bookB.hashCode());
==========================
bookA의 hashCode : 1227229563
bookB의 hashCode : 971848845
- 필드값이 같은 동등객체를 유도했으나, java.lang.Object의 hashCode()는 인스턴스 주소를 각각 다른 값으로 나타내고 있다.
@Override
hashCode()
동등객체에게 같은 주소값
/* hashCode -------------------------------------------------------------------- */
@Override
public int hashCode() {
/* 필드마다 곱해줄 소수값 선언
* 31은 소수이기 때문에 연산 시 동일한 hashCode 값이 나오지 않을 확률을 증가시킨다.
* 31이 통상적인 관례이며 String 클래스의 hashCode에도 사용한 값이다.
* */
final int prime = 31;
/* 곱셈 연산을 누적시킨 값 선언 */
int result = 1;
/* String 클래스의 hashCode 메소드는 이미 재정의 되어 있다.
* 같은 값을 가지는 문자열은 동일한 hashCode 값을 반환한다.
* */
result = prime * result + ((author == null) ? 0 : author.hashCode());
result = prime * result + number;
result = prime * result + price;
result = prime * result + ((title == null) ? 0 : title.hashCode());
/* 동등객체는 동일한 hashCode 값을 반환하게 된다. */
return result;
}
Book bookA = new Book(1, "홍길동전", "허균", 50000);
Book bookB = new Book(1, "홍길동전", "허균", 50000);
System.out.println("bookA의 hashCode : " + bookA.hashCode());
System.out.println("bookB의 hashCode : " + bookB.hashCode());
==========================
bookA의 hashCode : 2010084336
bookB의 hashCode : 2010084336
- 마침내 필드값이 같은 동등객체들로부터 같은 주소값이 반환된 모습이다.
❗ Object 클래스의 equals()와 hashCode() 메소드는 동일객체 판단,
오버라이딩한 뒤의 equals와 hashCode()는 객체의 동등성 비교(동등객체 판단)에 쓰인다.
2. String
2-1. String 제공 메소드들
- String 클래스는 java.lang 패키지에 존재하며, 문자열을 처리하는 여러 유용한 메소드들을 제공하고 있다.
- 그 중에서도 자주 사용되는 String 클래스의 메소드들을 살펴보자.
charAt()
String strA = "apple";
for(int i=0; i < strA.length(); i++) {
System.out.println("charAt(" + i + ") : " + strA.charAt(i));
}
==========================
charAt(0) : a
charAt(1) : p
charAt(2) : p
charAt(3) : l
charAt(4) : e
- 해당 문자열의 특정 인덱스에 해당하는 문자를 반환한다.
- 인덱스는 0부터 시작하는 숫자 체계를 의미한다. 인덱스를 벗어난 정수를 인자로 전달하는 경우에는 IndexOutOfBoundsException 에러가 발생한다.
compareTo()
- 인자로 전달된 문자열과 사전순으로 비교를 진행한다.
- 결과값에 있어서는 만일 두 문자열이 같다면 0, 인자로 전달된 문자열보다 작으면 음수, 크면 양수를 반환한다.
/* 값이 같으면 0을 반환한다. */
String strB = "java";
String strC = "java";
String strD = "JAVA";
String strF = "oracle";
==========================
System.out.println("compareTo() : " + (strB.compareTo(strC)));
compareTo() : 0
/* 대문자와 소문자는 32만큼 차이가 난다. */
System.out.println("compareTo() : " + (strB.compareTo(strD)));
System.out.println("compareTo() : " + (strD.compareTo(strB)));
==========================
compareTo() : 32
compareTo() : -32
/* jklmno j부터 o까지 5만큼 차이가 난다. */
System.out.println("compareTo() : " + (strB.compareTo(strF)));
System.out.println("compareTo() : " + (strF.compareTo(strB)));
==========================
compareTo() : -5
compareTo() : 5
- 예시에서처럼 comparetTo() 메소드는 대소문자를 구분해 쓴다.
- 때문에 대소문자를 구분하지 않고 비교하기 위해서는 아래와 같이 compareToIgnoreCase()라는 별도 메소드를 사용해야 한다.
System.out.println("compareToIgnoreCase() : " + strC.compareToIgnoreCase(strF));
==========================
compareToIgnoreCase() : 0
- compareTo() 메소드는 구체적으로 어떤 값이 나오는지보다 양수인지 음수인지를 판단할 목적으로 주로 사용된다.
concat()
- 인자로 전달된 문자열간에 합치기해서 새로운 문자열을 반환한다.
- 원본 문자열에는 영향을 주지 않는다.
String strA = "java";
System.out.println("concat() : " + (strA.concat("oracle")));
System.out.println("strA : " + strA);
==========================
concat() : javaoracle
strA : java
indexOf()
- 문자열에서 특정 문자를 탐색하여 처음 일치하는 인덱스 위치를 정수형으로 반환한다.
- 일치하는 문자가 없는 경우 -1을 반환한다.
String indexOf = "java oracle";
System.out.println("indexOf('a') : " + indexOf.indexOf('a'));
System.out.println("indexOf('z') : " + indexOf.indexOf('z'));
System.out.println("indexOf('a') : " + indexOf.lastIndexOf('a'));
==========================
indexOf('a') : 1
indexOf('z') : -1
indexOf('a') : 7
- 반대로 문자열 탐색을 뒤에서부터 진행할 때는 lastIndexOf() 사용한다. 이때도 처음 일치하는 위치의 인덱스가 반환된다.
trim()
- 문자열 앞뒤에 자리한 공백을 제거한 뒤 문자열로 반환한다.
String trimStr = " java ";
System.out.println("trimStr : # " + trimStr + "#");
System.out.println("trim() : #" + trimStr.trim() + "#");
==========================
trimStr : # java #
trim() : #java#
- 원본에 영향을 주지는 않는다.
System.out.println("trimStr : # " + trimStr + "#");
trimStr : # java #
toLowerCase() / toUpperCase()
- toLowerCase() 메소드는모든 문자를 소문자로 변환시킨다.
- toUpperCase()는 모든 문자를 대문자로 변환시킨다.
String caseStr = "JavaOracle";
System.out.println("toLowerCase() : " + caseStr.toLowerCase());
System.out.println("toUpperCase() : " + caseStr.toUpperCase());
==========================
toLowerCase() : javaoracle
toUpperCase() : JAVAORACLE
- 원본에는 영향을 주지 않는다.
System.out.println("caseStr : " + caseStr);
caseStr : JavaOracle
substring()
- 문자열의 일부를 잘라내 새로운 문자열을 반환한다.
- (int beginindex, int endindex)을 말한다. endindex - 1까지 추출한다.
String javaoracle = "javaoracle";
System.out.println("substring(0, 4) : " + javaoracle.substring(0, 4));
==========================
substring(0, 4) : java
- endindex 생략 가능하다. 생략하고 쓴다면 beginindex부터 마지막 자리까지를 출력한다.
System.out.println("substring(4) : " + javaoracle.substring(4));
==========================
substring(4) : oracle
- 원본에는 영향을 주지 않는다.
System.out.println("javaoracle : " + javaoracle);
javaoracle : javaoracle
replace()
- 기존 문자열에서 대체할 문자열로 변경해 반환한다.
- 소괄호 안에는 각각 (target, replacement)를 기재한다.
String javaoracle = "javaoracle";
System.out.println("replace() : " + javaoracle.replace("java", "python"));
==========================
replace() : pythonoracle
- 원본에 영향을 주지 않는다.
System.out.println("javaoracle : " + javaoracle);
length()
- 문자열의 길이를 정수형으로 반환한다.
System.out.println("length() : " + javaoracle.length());
System.out.println("빈 문자열 길이 : " + ("".length()));
==========================
length() : 10
빈 문자열 길이 : 0
isEmpty()
- 문자열의 길이가 0이면 true, 아니면 false를 반환한다.
- 길이가 0인 문자열은 null과는 다르다.
❗ null 값은 isEmpty()로 물을 수 없다!
String nullTest = null;인 경우에
nullTest == null 로 판단해야 true/false로 결과를 얻는다.nullTest.isEmpty()는 nullpointerException 오류 대상이다.
System.out.println("isEmpty() : " + "".isEmpty());
System.out.println("isEmpty() : " + "abc".isEmpty());
==========================
isEmpty() : true
isEmpty() : false
2-2. String 인스턴스 생성 방법
- 문자열 String은 불변이라는 특징을 가진다.
- 기존 문자열에 + 연산자를 수행하는 경우 문자열은 해당 값 수정 대신 새로운 문자열을 할당한다.
- 문자열 객체 사용하는 방법에는 두 가지가 있다: "" 리터럴 형태, new String("문자열")
String strA = "java";
String strB = "java";
String strC = new String("java");
String strD = new String("java");
A. "" 리터럴 형태
- 동일한 값을 가지는 인스턴스를 단일 인스턴스로 관리한다. 싱글톤(singleton) 패턴에 해당한다.
- 따라서 주소값을 비교하는 == 연산으로 비교하고, 서로 동일한 주소인지 확인해 true를 반환한다.
"java" == "java"
System.out.println("strA == strB : " + (strA == strB));
==========================
strA == strB : true
B. new String("문자열")
- new 연산자와 함께 새로운 인스턴스를 생성한다. 이는 즉 기존 인스턴스를 두고 새로운 인스턴스를 할당한 것이다.
- 때문에 == 연산으로 비교 시 서로 다른 주소값을 가지고 있어 결과값은 false가 된다.
"java" == new String("java")
System.out.println("strB == strC : " + (strB == strC));
==========================
strB == strC : false
- 동일한 방식으로 인스턴스를 생성하고 값 또한 같더라도 새로운 인스턴스를 생성한다는 그 특성상 서로 다른 주소를 가지고 있어 결국 false인 경우이다.
new String("java") == new String("java")
System.out.println("strC == strD : " + (strC == strD));
==========================
strC == strD : false
❗ 하지만 인스턴스 생성 방법에 상관 없이 위 4개의 문자열은 모두 동일한 hashCode 값을 가진다.
동일한 문자열은 동일한 hashCode 값을 반환하도록 재정의(@Override, 오버라이딩)되어 있기 때문이다.
- 기존 문자열에 + 연산자를 수행하는 경우 문자열은 해당 값 수정 대신 새로운 문자열을 할당한다.
- strA과 strB는 최초에 동일한 인스턴스였지만, + 연산자를 수행한 후에는 전혀 다른 인스턴스가 된 것을 알 수 있다.
String strA = "java";
String strB = "java";
strB += "oracle";
==========================
strA == strB : false
stack | heap |
strA (String pool 0x123) strB (String pool 0x123) → (String pool 0x456) |
"java" (String pool 0x123) "javaoracle" (String pool 0x456) |
@Override
String 클래스의 equals()
- String 클래스의 equals() 메소드는 인스턴스가 아닌 문자열 값을 비교한다.
- 동일한 값을 가지는 경우 true, 다른 값을 가지는 경우 false를 반환하도록 Object 클래스의 equals()로부터 재정의된 사례이다.
- 따라서 문자열 인스턴스 생성 방식에 상관 없이 동일한 문자열 값인지를 비교하기 위해서는 == 연산 대신 equals() 메소드를 써야 한다.
❗ 문자열 값 비교에는 equals() 사용한다.
"java".equals(new String("java"))
System.out.println("strA.equals(strC) : " + strA.equals(strC));
==========================
strA.equals(strC) : true
❗ Scanner로 입력 받은 문자열 비교에도 equals()를 써야 한다.
Scanner의 nextLine() 등을 이용해 문자열을 읽어온 경우 new String()으로 인스턴스를 생성한 것과 동일한 것으로 볼 수 있다. substring으로 잘라내 새로운 문자열 생성 후 반환했기 때문이다. 구분해 사용하기 힘든 경우엔 문자열 모두 equals()로 비교하는 것이 가장 좋은 방법이다.
2-3. 구분자 사용한 String 분리
- 문자열을 특정 구분자로 분리하도록 하는 메소드에는 크게 두 가지가 있다: split(), StringTokenizer
A. split()
String empA = "100/홍길동/서울/영업부"; //모든 값 존재
String empB = "200/유관순//총무부"; //주소 없음
String empC = "300/이순신/경기도/"; //부서 없음
String[] empArrA = emp1.split("/");
String[] empArrB = emp2.split("/");
String[] empArrC = emp3.split("/");
- 사번/이름/주소/부서를 의미하는 각 문자열이 있을 때를 가정하자.
- 먼저 split을 이용해서 3명의 문자열을 정보별로 분리한다. 여기서 구분자로 / 정의했다.
for(int i=0; i < empArrA.length; i++) {
System.out.println("empArrA[" + i + "] : " + empArr1[i]); //정상 출력
}
for(int i=0; i < empArrB.length; i++) {
System.out.println("empArrB[" + i + "] : " + empArr2[i]); //중간 값 빈 문자열
}
for(int i=0; i < empArrC.length; i++) {
System.out.println("empArrC[" + i + "] : " + empArr3[i]); //마지막 값 출력 안 됨
}
==========================
empArrA[0] : 100
empArrA[1] : 홍길동
empArrA[2] : 서울
empArrA[3] : 영업부
empArrB[0] : 200
empArrB[1] : 유관순
empArrB[2] :
empArrB[3] : 총무부
empArrC[0] : 300
empArrC[1] : 이순신
empArrC[2] : 경기도
- 몇 개의 토큰으로 분리할 것인지 한계치를 두 번째 인자로 넣어줄 수 있다.
- 이때 음수를 넣게 되면 마지막 구분자 뒤의 값이 존재하지 않는 경우 빈 문자열로 토큰이 생성된다.
String[] empArrD = empC.split("/", -1);
for(int i=0; i < empArr4.length; i++) {
System.out.println("empArr4[" + i + "] : " + empArr4[i]); //마지막 값 출력됨
}
==========================
empArrD[0] : 300
empArrD[1] : 이순신
empArrD[2] : 경기도
empArrD[3] :
B. StringTokenizer
- 한편 StringTokenizer의 경우 공백으로 존재하는 값을 무시한다.
- StringTokenizer 사용을 위해서 import 구문 작성해야 한다: import java.util.StringTokenizer;
StringTokenizer stA = new StringTokenizer(empA, "/");
StringTokenizer stB = new StringTokenizer(empB, "/");
StringTokenizer stC = new StringTokenizer(empC, "/");
- while 반복문 조건식으로 hasMoreToken()을 사용해 묻고, 출력문에서 nextToken()을 출력한다.
- 예시에서 100/홍길동/서울/영업부 하나하나가 토큰인 것이다.
while(st1.hasMoreTokens()) {
System.out.println("stA : " + stA.nextToken());
}
while(st2.hasMoreTokens()) {
System.out.println("stB : " + stB.nextToken());
}
while(st3.hasMoreTokens()) {
System.out.println("stC : " + stC.nextToken());
}
==========================
stA : 100
stB : 홍길동
stC : 서울
stD : 영업부
stA : 200
stB : 유관순
stC : 총무부 *중간 공백 생략됨*
stA : 300
stB : 이순신
stC : 경기도 *마지막 구분자 생략됨*
❗ 여기서 한 번 더 출력하고자 한대도 앞서 nextToken()으로 모든 토큰을 꺼냈기 때문에 해당 StringTokenizer의 토큰은 재사용 불가 상태이다. 에러 코드 없으며 콘솔 창에 변화조차 없다.
- split은 정규표현식 이용(문자열 패턴), StringTokenizer는 구분자 문자열 이용한다.
split() | StringTokenizer |
정규표현식 이용해 문자열 분리 | 문자열의 모든 문자들을 분리 |
상대적으로 느림 | split보다 빠름 |
구분자 생략 가능 기본 구분자는 공백 |
- 여러 종류의 구분자를 혼용해 썼을 때는 아래와 같이 진행한다.
String colorStr = "red*orange#blue/yellow green";
String[] colors = colorStr.split("*#/ ");
- "*#/ " 이라는 문자열이 구분자로 존재하지 않아서 에러 발생한다: java.util.regex.PatternSyntaxException
- 여기서 regex는 regular expression을 말한다.
String[] colors = colorStr.split("[*#/ ]");
for(int i=0; i < colors.length; i++) {
System.out.println("colors[" + i + "] : " + colors[i]);
}
==========================
colors[0] : red
colors[1] : orange
colors[2] : blue
colors[3] : yellow
colors[4] : green
- split은 대괄호 []에 담아준다. 대괄호 안에 기재된 순서가 바뀌는 것도 상관 없다!
- 대괄호로 묶은 문자열은 문자열이 아닌 각 문자들의 패턴으로 볼 수 있다.
- 따라서 순서에 관여치 않고 존재하는 대괄호 안의 문자 하나하나를 구분자로 사용할 수 있다.
StringTokenizer colorStringTokenizer = new StringTokenizer(colorStr, "*#/ ");
- StringTokenizer의 두 번째 인자 문자열 자체는 연속된 문자열이 아닌 하나하나를 구분자로 이용하겠다는 의미이다.
while(colorStringTokenizer.hasMoreTokens()) {
System.out.println("colorStringTokenizer : " + colorStringTokenizer.nextToken());
}
==========================
colorStringTokenizer : red
colorStringTokenizer : orange
colorStringTokenizer : blue
colorStringTokenizer : yellow
colorStringTokenizer : green
2-4. 이스케이프(escape) 문자
특수문자 | 문자 리터럴 | 비고 |
tab | \t | 정해진 공간만큼 띄어쓰기함 |
new line | \n | 출력하고 다음 라인으로 옮겨감 |
역 슬래시 | \\ | 특수문자 사용시 백 슬래시(\) 작성하고 특수문자 씀 |
작은 따옴표 | \' | |
큰 따옴표 | \" | |
유니코드 | \u | 유니코드 표시할 때 사용 |
❗ split시 이스케이프 문자를 함께 써야 하는 특수문자도 존재한다.
이스케이프 문자 사용 않는 특수문자
~ ` ! @ # % & - _ = ; : ' \ " , < > /
이스케이프 문자를 사용하는 특수문자(\\)
$ ^ * ( ) + | { } [ ] . ?
예시.
String str = "java$oracle$jdbc";
String[] sarr = str.split("\\$");
for(String s : sarr) {
System.out.println(s);
}
==========================
java
oracle
jdbc
3. StringBuilder와 StringBuffer
3-1. String과 StringBuilder 비교
- 지금까지 다뤄온 String은 불변이라는 특성을 가지고 있다.
- 문자열에 + 연산으로 합치기 하는 경우, 기존 인스턴스를 수정하는 것이 아닌 새로운 인스턴스를 반환한다.
- 변하지 않는 문자열을 자주 읽어 들이는 경우에는 좋은 성능을 기대할 수 있지만, 문자열 변경이 자주 일어나는 경우에는 결국 변경 횟수만큼 새로운 인스턴스를 늘려가는 것이라 성능면에서 좋지 못하다.
String | StringBuilder | StringBuffer |
불변 | 가변 | 가변 |
- | 스레드 동기화 기능 X | 스레드 동기화 기능 O |
- 반면 StringBuilder는 가변적이다.
- 문자열에 append() 메소드를 이용하여 합치기한다면 기존 인스턴스를 수정하는 것이 되기 때문에 새로운 인스턴스가 생겨나지 않는다.
- 따라서 잦은 문자열 변경이 일어나는 경우 String보다 StringBuilder를 사용하는 것이 효율적이다..
❗ String 값이 바뀌는 상황마다 새로운 인스턴스를 만드는 대신
StringBuilder나 StringBuffer를 통해 값 변경만 하도록 한다. 보다 효율적인 방안이다.
단, JDK 1.5 버전부터 문자열의 + 연산이 StringBuilder의 append()로 컴파일 된다. 따라서 큰 차이를 보이지는 않게 됐다. 하지만 반복문에서 문자열의 + 연산을 수행하는 경우 StringBuilder 인스턴스를 반복 루프 시마다 생성하기 때문에 역시 성능에는 좋지 않은 영향을 준다.
❗ StringBuilder와 StringBuffer는 기능적으로는 똑같이 작동하며, 스레드 동기화 유무만 다르다.
스레드(thread)란 프로그램 진행 흐름을 말한다. 현재 학습 과정은 싱글스레드(single thread)이다. 반대로 두 개 이상인 여러 개의 흐름이 있는 환경을 멀티스레드(multi theread)라고 한다. 동시 진행되는 작업이 있을 때 하나의 스레드만 접근하도록 잠금 처리하는 기능(thread safe)들이 필요해질 것이다. StringBuilder와 StringBuffer는 바로 여기서 이를 제어하는 데 차이가 있다.
3-2. StringBuilder의 toString()과 hashCode()
@Override
toString()
StringBuilder stringBuilder = new StringBuilder("java");
System.out.println("sb : " + stringBuilder);
System.out.println("stringBuilder의 hashCode : " + stringBuilder.hashCode());
==========================
stringBuilder : java
stringBuilder의 hashCode : 1101288798
- StringBuilder 인스턴스 생성하고 레퍼런스를 출력했을 때, 주소 대신 실제 값이 나온다.
- toString이 오버라이딩 되어 있다는 뜻이다. → 사실은 stringBuilder.toString()
@Override
hashCode()
- hashCode는 오버라이딩 되어 있지 않다. 클릭해서 눌러보면 Object.class의 메소드를 참조하고 있음을 알 수 있다.
- 따라서 Object 클래스의 hashCode() 특성 따라 같은 해시코드를 반환하는 경우는 동일객체일 때로 제한된다.
- 즉 같은 값을 가지고 있는 동등객체의 경우 같은 해시코드를 반환하지 못한다.
stringBuilder.append("oracle");
System.out.println("stringBuilder : " + stringBuilder ); //값 변경
System.out.println("stringBuilder의 hashCode : " + stringBuilder.hashCode()); //주소 그대로
==========================
stringBuilder : javaoracle
stringBuilder의 hashCode : 1101288798
- 문자열 수정이 일어난다면, 값은 변하지만 주소는 같은 주소를 그대로 가진 객체임을 확인할 수 있다.
- 즉, 문자열을 변경했다고 해서 새로운 인스턴스가 생성된 것은 아니다.
3-3. StringBuilder제공 메소드
StringBuilder sbA = new StringBuilder();
- StringBuilder 기본 생성자로 인스턴스 생성해 예시로 들자.
- 아래 작성한 주요 메소드들은 기본값을 변경시킨다는 특징이 있다.
capacity()
System.out.println(sbA.capacity());
- 현재 버퍼의 크기, 즉 용량을 정수형으로 반환하는 메소드이다.
- (문자열 길이 + 16)이 기본 용량에 해당한다.
append()
for(int i=0; i < 50; i++) {
sbA.append(i);
System.out.println("sbA : " +sbA);
System.out.println("capacity : " +sbA.capacity());
System.out.println("hashCode : " + sbA.hashCode());
- 인자로 전달된 값을 문자열로 변환하여 기존 문자열의 마지막에 추가한다.
- 기본 용량을 초과하는 경우 (기존 문자열 + 1) * 2를 하여 용량을 늘린다. 16 → 34 → 70...처럼 (2n + 2)씩 증가한다.
- 출력문이 반복되는 동안 해시코드는 전혀 변하지 않는다. 즉 동일 인스턴스 안에서 값 변경만 계속 발생하는 것이다.
delete()
StringBuilder sbB = new StringBuilder("javaoracle");
System.out.println("delete() : " + sbB.delete(2, 5));
System.out.println("deleteCharAt() : " + sbB.deleteCharAt(0));
==========================
delete() : jaracle
deleteCharAt() : aracle
- 시작 인덱스와 종료 인덱스를 이용해서 문자열에서 원하는 부분의 문자열을 제거한다. 이때 end index - 1까지 지워진다.
- 문자열 인덱스를 이용해서 문자 하나를 제거할 때는 deleteCharAt() 메소드를 사용한다.
- 두 경우 모두 원본 값이 변하는 것이다. 즉 원본에 영향을 미친다고 해석한다.
- 예시에서도 한 번의 delete()가 실행된 다음 → deleteCharAt()까지 적용했기 때문에 두 결과가 누적된 모습을 확인할 수 있다.
insert()
- 인자로 전달된 값을 문자열로 변환 후 지정한 인덱스 위치에 추가한다.
- (offset, str)에서 몇 자리 띄고 추가할지 그리고 어떤 값을 추가할지를 지정한다.
- 원본 값을 바꾸는 것이다. 이는 즉 원본에 영향을 미친다.
System.out.println("insert() : " + sbB.insert(1, "vao"));
System.out.println("insert() : " + sbB.insert(0, "j"));
==========================
insert() : avaoracle
insert() : javaoracle
System.out.println("insert() : " + sbB.insert(sbB.length(), "jdbc"));
System.out.println("sbB : " + sbB);
==========================
insert() : javaoraclejdbc
- offset 값을 문자열 길이로 설정한다면, 마지막 자리에 값을 추가하던 append()의 역할을 수행할 수 있다.
reverse()
System.out.println("reverse() : " + sbB.reverse());
System.out.println("sbB : " + sbB);
==========================
reverse() : cbdjelcaroavaj
sbB : cbdjelcaroavaj *계속 바뀐 값 유지
- 문자열 인덱스 순번을 역순으로 재배열한다.
- 원본에 영향을 미친다.
- 이밖에 String 클래스와 동일한 메소드도 있다: charAt(), indexOf()/lastIndexOf(), length(), replace(), substring(), toString()
- JVM이 제공하는 모든 기능을 암기할 수는 없을 것이다. 다만 이러한 주요 기능들이 있음을 기억했다가 필요에 따라 찾아 쓸 수 있도록 하자!
'Java' 카테고리의 다른 글
[JAVA] 11. 제네릭 클래스 | 와일드카드 (0) | 2022.01.08 |
---|---|
[JAVA] 10-2. Wrapper | 파싱(parsing) | Date | Calendar (0) | 2022.01.07 |
[JAVA/수업 과제 practice] 다형성 Lv. 1~2 (0) | 2022.01.06 |
[JAVA/수업 과제 practice] 상속 Lv. 1~2 (0) | 2022.01.06 |
[JAVA] 9. 다형성 | 추상클래스 | 인터페이스 (1) | 2022.01.05 |