java.lang 패키지에는 자바에서 가장 기본이 되는 클래스들을 포함하고 있다.
이 패키지는 import 없이 사용이 가능하며 지금까지 System이나 String 클래스를 import 없이 사용 가능했던 것도 이 클래스들이 java.lang에 속하기 때문이다.
java.lang에는 굉장히 많은 클래스들이 있기 때문에 자주 사용되는 대표적인 클래스들 위주로 정리해보자.
모든 클래스들의 최상위 조상 클래스로 Object 클래스의 멤버는 모든 클래스에서 바로 사용이 가능하다.
두 객체가 같은지를 boolean형으로 return 하는 메서드이다.
두 객체의 비교를 참조변수의 값으로 판단하는 데 즉, 주소값을, 어떤 인스턴스를 참조하는(가리키는)지를 비교한다.
class objectExample{
int value;
objectExample(int value){
this.value = value;
}
}
public class Example {
public static void main(String[] args) {
objectExample o1 = new objectExample(3);
objectExample o2 = new objectExample(3);
System.out.println(o1.equals(o2));
}
}
o1과 o2는 value 값은 같지만 서로 다른 객체로 인식된다. objectExample의 인스턴스가 필드값이 같아도 주소값이 다르기 때문에 o1과 o2는 초기화 순간부터 서로 다른 주소값을 가리킨다.
equals() 메서드는 참조변수로 객체를 비교하기 때문에 당연히 다르다고 인식한다.
만약, 두 객체가 같으려면 다음과 같이 코드를 짜야한다.
objectExample o1 = new objectExample(3);
objectExample o2 = o1;
이렇게 하면 서로 같은 주소값을 가리키게 된다.
equals() 메서드가 주소값이 아닌 인스턴스 변수의 값을 통해 비교하는 기능으로 만들고 싶다면 결국 오버라이딩 밖에 방법이 없다.
class objectExample{
int value;
objectExample(int value){
this.value = value;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof objectExample) {
return this.value == ((objectExample)obj).value;
}
else
return false;
}
}
public class Example {
public static void main(String[] args) {
objectExample o1 = new objectExample(3);
objectExample o2 = new objectExample(3);
System.out.println(o1.equals(o2));
}
}
해싱기법에 사용되는 해시함수를 구현한 메서드로 해시코드를 반환한다.
Object 클래스에 정의된 hashCode()는 객체의 주소값으로 해시코드를 만들어 반환한다.
32 bit JVM에서는 서로 다른 두 객체는 절대로 같은 해시코드를 가질 수 없지만, 64 bit JVM에서는 8 byte 주소값을 만들기 때문에 중복될 수 있다.
objectExample o1 = new objectExample(3);
objectExample o2 = new objectExample(3);
System.out.println(o1.hashCode());
System.out.println(o2.hashCode());
2083562754
1239731077
하지만, Object 클래스가 아닌 String 클래스에서 호출할 경우는 다르다.
String str1 = new String("equalsHash");
String str2 = new String("equalsHash");
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
387366253
387366253
String 클래스에는 hashCode()가 오버라이딩 되어 있어 같은 문자열의 내용이 같을 경우 같은 해시코드를 반환하도록 되어 있다.
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
93122545
2083562754
System 클래스의 identityHashCode(Object obj)는 객체의 주소값으로 해시코드를 반환하기 때문에 모든 객체에 대해 항상 다른 해시코드를 반환하며, 그 해시코드값은 실행마다 달라질 수 있다.
인스턴스에 대한 정보를 문자열로 제공하는 메서드로, "클래스이름@16진수 해시코드" 를 반환한다.
class objectEx{
int value;
objectEx(int value){
this.value = value;
}
}
public class Example {
public static void main(String[] args) {
objectEx o1 = new objectEx(3);
objectEx o2 = new objectEx(3);
System.out.println(o1.toString());
System.out.println(o2.toString());
}
}
example.objectEx@7c30a502
example.objectEx@49e4cb85
같은 패키지, 같은 클래스이기 때문에 @ 앞에는 같지만 뒤에는 주소값을 나타내는 16진수 해시코드이므로 서로 다른 값을 가진다.
하지만, String 클래스나 Date 클래스의 경우 toString()이 오버라이딩 되어 있기 때문에 다른 것이 출력된다.
import java.util.*;
public class Example {
public static void main(String[] args) {
String str = new String("String클래스의 경우 문자열이 출력됩니다.");
Date today = new Date();
System.out.println(str.toString());
System.out.println(today.toString());
}
}
String클래스의 경우 문자열이 출력됩니다.
Tue Jul 02 00:17:32 KST 2024
String 클래스에서는 생성자에 입력한 문자열이, Date 클래스는 현재 날짜와 시간이 반환되도록 오버라이딩 되어 있다.
clone()은 자신을 복제하여 새로운 인스턴스를 생성하는 기능인 메서드이다.
Object클래스의 메서드로 Cloneable 인터페이스를 구현해야만 사용가능하다.
class Person implements Cloneable{
int height;
int weight;
int age;
String name;
Person(int height, int weight, int age, String name){
this.height = height;
this.weight = weight;
this.age = age;
this.name = name;
}
public Object clone() {
Object obj;
try {
obj = super.clone();
}
catch(CloneNotSupportedException e) {
obj = null;
}
return obj;
}
public String toString() {
return "height : " + height + "/ weight : " + weight + "/ age : " + age + "/ name : " + name;
}
}
사람의 신장,체중,나이,이름이라는 인스턴스 변수를 가지는 Person 클래스를 예시로 생성하였는데 이 객체를 복사하는 clone()을 사용하기 위해 Cloneable 인터페이스를 구현하였다.
Object 클래스에 선언되어 있는 clone() 메서드는 선언부에서 CloneNotSupportedException을 throws하기 때문에 이를 호출하여 사용하는 경우 예외처리를 반드시 해주어야 한다.(Checked 예외이기 때문)
Catch 블럭에서 null값을 초기화하는 이유는 만약 예외가 발생하여 Catch 블럭이 실행될 경우 obj에는 어떠한 값도 대입되지 않아 return 할 값이 없어지기 때문이다.
Catch 블럭이 아니라면 가장 처음에 Object obj = null; 이런식으로 선언과 동시에 초기화를 해주어야 한다.
public class Example {
public static void main(String[] args) {
Person p1 = new Person(179,97,27,"Kim");
Person p2;
p2 = (Person)p1.clone();
System.out.println(p1.toString());
System.out.println(p2.toString());
p1.height = 180;
p1.name = "Park";
System.out.println(p1.toString());
System.out.println(p2.toString());
}
}
height : 179/ weight : 97/ age : 27/ name : Kim
height : 179/ weight : 97/ age : 27/ name : Kim
height : 180/ weight : 97/ age : 27/ name : Park
height : 179/ weight : 97/ age : 27/ name : Kim
인스턴스를 복제하였기 때문에 인스턴스 변수 값이 동일하게 출력된다. 인스턴스 변수 값을 변경하여도 복제본에는 영향을 주지 않는다.
단, 인스턴스 변수에 한해서이다. 참조타입의 인스턴스 변수의 경우에는 그렇지 않다. 이는 잠시후 다시 다루겠다.
clone()을 사용하지 않고 생성자를 이용한 인스턴스 복사도 가능하다. 앞부분에서 다루었던 내용인데 다시 복습해보자.
class Person{
int height;
int weight;
int age;
String name;
Person(int height, int weight, int age, String name){
this.height = height;
this.weight = weight;
this.age = age;
this.name = name;
}
Person(Person person){
this.height = person.height;
this.weight = person.weight;
this.age = person.age;
this.name = person.name;
public String toString() {
return "height : " + height + "/ weight : " + weight + "/ age : " + age + "/ name : " + name;
}
}
동일한 클래스인데 clone()메서드를 없애고 파라미터가 Person 타입의 변수인 생성자를 추가하였다.
public class Example {
public static void main(String[] args) {
Person p1 = new Person(179,97,27,"Kim");
Person p2 = new Person(p1);
System.out.println(p1.toString());
System.out.println(p2.toString());
p1.height = 180;
p1.name = "Park";
System.out.println(p1.toString());
System.out.println(p2.toString());
}
}
new 연산자에 파라미터로 p1 인스턴스를 대입하면 동일한 출력값이 나온다.
height : 179/ weight : 97/ age : 27/ name : Kim
height : 179/ weight : 97/ age : 27/ name : Kim
height : 180/ weight : 97/ age : 27/ name : Park
height : 179/ weight : 97/ age : 27/ name : Kim
JDK 1.5부터 가능한 기능인데 조상 메서드의 반환 타입을 자손 클래스의 타입으로 변경을 허용하는 것이다.
방금 전 Person 클래스에서 clone()을 오버라이딩 한 경우를 생각해보자.
public Object clone() {
Object obj = null;
try {
obj = super.clone();
}
catch(CloneNotSupportedException e) {}
return obj;
}
조상 클래스인 Object 타입으로 반환되기 때문에 자손타입의 참조변수에 대입할 경우 형변환을 해주어야 한다.
Person copiedP = (Person)originalP.clone();
다형성을 공부할 때 다뤘었지만, 자손타입의 변수로 조상타입의 인스턴스를 참조할 수 없다. 그래서 자손타입으로 형변환을 해주어야 한다.
여기서 공변 반환타입 기능을 이용한다면 더 효율적으로 코드를 사용할 수 있다.
public Person clone() {
Object obj = null;
try {
obj = super.clone();
}
catch(CloneNotSupportedException e) {}
return (Person)obj;
}
오버라이딩 한 clone()의 반환 타입을 Person으로 선언하고 대입 시 형변환 없이 사용이 가능하다.
Person copiedP = originalP.clone();
지금이야 별 실용성을 못느끼겠지만 만약 복사해야 할 객체가 수 백개 있을 경우 일일히 형변환을 해줄 때마다 비효율적이라는 것을 느낄 것이다. 이때 공변 반환타입을 사용하면 된다.
어떠한 클래스가 객체를 참조하고 있을 때 원본에서 그 객체의 내용을 변경하면 복사본에 영향을 줄까?
영향을 준다면 얕은 복사인 것이고 독립적이라면 깊은 복사가 된 것이다.
간단한 예시로 이해해보자.
class army implements Cloneable{
int unit;
int equipment;
weapon w;
army(int unit, int equipment, weapon w){
this.unit = unit;
this.equipment = equipment;
this.w = new weapon();
}
public army clone() {
Object obj;
try {
obj = super.clone();
}
catch(CloneNotSupportedException e) {
obj = null;
}
return (army)obj;
}
public String toString() {
return "unit : " + unit + " equipment : " + equipment + " weapon = " + "rifle : " + w.rifle + " /" + " canon : " + w.canon;
}
}
class weapon{
String rifle = "K2";
String canon = "K9";
}
public class Example {
public static void main(String[] args) {
army ROKarmy = new army(50,30,new weapon());
army USarmy = ROKarmy.clone();
ROKarmy.unit = 40;
System.out.println(ROKarmy);
System.out.println(USarmy);
}
}
army 클래스는 weapon 객체를 참조하고 USarmy는 ROKarmy를 복제한 인스턴스이다.
이때 ROKarmy의 인스턴스 변수 값을 변경해도 USarmy에는 변화가 없다.
unit : 40 equipment : 30 weapon = rifle : K2 / canon : K9
unit : 50 equipment : 30 weapon = rifle : K2 / canon : K9
자, 그럼 이번에는 weapon을 바꿔보자. 미군 무기체계에 맞게 소총은 M4로 화포는 M777으로 바꿔보겠다.
ROKarmy.unit = 40;
USarmy.w.rifle = "M4";
USarmy.w.canon = "M777";
unit : 40 equipment : 30 weapon = rifle : M4 / canon : M777
unit : 50 equipment : 30 weapon = rifle : M4 / canon : M777
ROKarmy의 weapon까지 바뀌어버렸다.
왜 이런 결과가 나올까? 원본인 ROKarmy가 참조하는 weapon 클래스의 인스턴스를 인스턴스1이라고 해보자. 이때 복사본도 인스턴스1을 참조한 상태로 복사된다.
즉, army 인스턴스는 2개인데 같은 weapon 인스턴스를 참조하게 되어 변경할 경우 서로 영향을 미치는 것이다.

그렇다면 깊은 복사로 인스턴스를 복제하려면 어떻게 해야할까?
일단 얕은 복사의 기능을 가진 메서드와 깊은 복사의 기능을 가진 메서드 2개를 만들어보자.
//얕은 복사
public army shallowCopy() {
Object obj;
try {
obj = super.clone();
}
catch(CloneNotSupportedException e) {
obj = null;
}
return (army)obj;
}
//깊은 복사
public army deepCopy() {
Object obj;
try {
obj = super.clone();
}
catch(CloneNotSupportedException e) {
obj = null;
}
army a = (army)obj;
a.w = new weapon();
return a;
}
두 메서드 간의 차이점은 새로운 인스턴스를 참조하는지 여부이다.
복사한 객체의 참조변수에 새로운 인스턴스를 대입하면 서로 다른 인스턴스를 참조하게 되므로 독립적이게 된다.

자바에서는 문자열을 위한 String 클래스를 제공한다. String 클래스에서는 문자형 배열 참조변수를 인스턴스 변수로 정의한다.
String 인스턴스 생성 시 생성자 매개변수에 입력하는 문자열이 이 문자형 배열에 저장되는 것이다.
public final class String implements java.io.Serializable, Comparable{
private char[] value;
String 클래스는 final 접근 제어자를 가지기 때문에 한 번 저장한 문자열을 읽어올 수만 있고 변경할 수는 없다.
String str1 = "Sentence1";
String str2 = "Sentence2";
str1 = str1 + str2;
문자열을 "+" 연산자를 사용하여 결합하는 것은 실제로 결합되는 것이 아니라 결합된 문자열의 내용을 가진 새로운 인스턴스를 생성하는 것이다.
예제에서 str1은 "Sentence1"이라는 문자열을 가진 인스턴스를 참조하다가 "Sentence1Sentence2"라는 문자열을 가진 새로운 인스턴스를 참조하게 되는 것이다.
문자열을 만들 때에는 문자열 리터럴을 지정하는 방법과 String 인스턴스 생성자를 사용하는 방법이 있다.
두 방법에는 차이점이 있다.
String str1 = "Example";
String str2 = "Example";
String str3 = new String("Example");
String str4 = new String("Example");
str1과 str2는 서로 같은 주소값을 가리킨다. 이게 무슨 말이냐면 문자열 리터럴을 사용할 경우 이미 존재하는 것을 재사용하기 때문에 str2는 이미 존재하는 "Example" 문자열 리터럴의 주소값을 가리키게 되는 것이다.
하지만, str3, str4의 경우에는 new 연산자에 의해 메모리 할당이 이루어지기 때문에 서로 다른 주소값을 가리킨다.
해시코드 값으로 확인을 해보면 바로 이해할 수 있다.
String str1 = "Example";
String str2 = "Example";
String str3 = new String("Example");
String str4 = new String("Example");
//문자열 내용이 같으면 hashcode()의 결과값이 같게 나오므로
//identityHashCode()를 사용
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
System.out.println(System.identityHashCode(str3));
System.out.println(System.identityHashCode(str4));
93122545
93122545
2083562754
1239731077
문자열 리터럴에 대해 추가적으로 설명을 하자면, str1과 str2의 경우 "Example"을 저장하고 있는 String 인스턴스가 하나 생성된 후 참조변수들이 모두 이 인스턴스를 참조하게 된다.
즉, 같은 내용을 가리킬 경우 같은 주소값을 가리키게 되는 것이다.
String 클래스는 빈 문자열을 저장할 수 있다. 즉, 생성자에 아무것도 입력하지 않고 인스턴스 생성이 가능하다.
이게 가능한 이유는 길이가 0인 char형 배열을 만들 수 있기 때문에 이를 인스턴스 변수로 가지는 String 클래스에서 빈 문자열을 저장할 수 있는 것이다.
String str1 = "";
//char c = ''; //불가능
char c = ' '; //기본형으로 사용
이 경우에는 new char[0]이라고 생각하면 된다.
하지만 char 형의 경우에는 빈 문자가 불가능하다. char형 변수에는 반드시 하나의 문자를 지정해야 하기 때문이다. 그래서 기본값으로 공백으로 초기화하는 것이 보통이다.
String 클래스의 생성자와 자주 사용되는 메서드가 있는데 꽤 많기 때문에 표로 간략하게 정리하겠다.
- String(String s) : 주어진 문자열을 갖는 인스턴스 생성하는 생성자
- String(char[] value) : 문자열 배열을 갖는 인스턴스 생성하는 생성자
- String(StringBuffer buf) : StringBuffer 인스턴스가 가지고 있는 문자열과 같은 내용의 인스턴스를 생성하는 생성자
- char charAt(int index) : 지정된 index에 있는 문자를 알려준다
- int compareTo(String str) : 문자열 str과 사전순서로 비교, 같으면 0, 사전순으로 이전이면 음수, 이후면 양수 반환
- String concat(String str) : 문자열 str을 뒤에 결합한다
- boolean contains(CharSequence s) : 지정된 문자열 s가 있는지 확인(true/false)
- boolean endWith(String suffix) : 지정된 문자열 suffix로 끝나는지 검사
- boolean equals(Object obj) : 매개변수로 받은 문자열 obj와 String 인스턴스의 문자열을 비교한다. obj가 String 타입이 아니거나 문자열이 다르면 false 반환
- boolean equalsIgnoreCase(String str) : 문자열 str과 String 인스턴스의 문자열을 대소문자 구분없이 비교
- int indexOf(int ch) : 주어진 문자 ch가 문자열에 존재하는지 확인하여 index 값을 반환, 못찾으면 -1을 반환한다
- int indexOf(int ch, int pos) : 지정된 위치 pos부터 주어진 문자 ch가 문자열에 존재하는지 확인하여 index 값을 반환하고 못찾으면 -1을 반환한다
- int indexOf(String str) : 주어진 문자열 str이 존재하는지 확인하여 index 값 반환하고 없으면 -1을 반환한다
- int lastIndexOf(int ch) : 지정된 문자 ch를 문자열의 오른쪽 끝에서부터 찾아서 index 값 반환하고 없으면 -1을 반환한다
- int lastIndexOf(String str) : 지정된 문자열 str을 문자열의 오른쪽 끝에서부터 찾아서 index 값 반환하고 없으면 -1을 반환한다
- int length() : 문자열의 길이를 반환
- String replace(char old, char new) : 문자열 중 문자 old를 새로운 문자 new로 바꾸어 반환
- String replace(CharSequence old, CharSequence new) : 문자열 중 문자열 old를 새로운 문자열 new로 바꾸어 반환
- String replaceAll(String regex, String replacement) : 문자열 지정된 문자열 regex를 새로운 문자열 replacement로 바꾸어 반환
- String[] split(String regex) : 문자열을 지정된 분리자 regex로 나누어 문자열 배열에 담아 반환
- String[] split(String regex, int limit) : 문자열을 지정된 분리자 regex로 나누어 문자열 배열에 담아 반환. 단, 문자열 전체를 지정된 수 limit만큼 자른다
- boolean startWith(String prefix) : 주어진 문자열 prefix로 시작하는지 확인하여 true/false 반환
- String substring(int begin) : 주어진 시작 위치 인덱스 begin부터의 문자열을 얻는다
- String substring(int begin, int end) : 주어진 시작 위치 인덱스 begin부터 주어진 끝 위치 인덱스 end까지 문자열을 얻는다. 단, 끝 위치 end번째 인덱스는 포함하지 않는다
- String toLowerCase() : 인스턴스에 저장되어 있는 문자열을 소문자로 변환하여 반환
- String toUpperCase() : 인스턴스에 저장되어 있는 문자열을 대문자로 변환하여 반환
- String toString() : 인스턴스에 저장되어 있는 문자열 반환
- String trim() : 인스턴스에 저장되어 있는 문자열의 좌우 공백을 제거하여 반환
- static String valueOf(parameter) : 파라미터의 값을 문자열로 변환하여 반환. 참조변수의 경우 toString()을 호출한 결과를 반환
*parameter에는 boolean, char, int, long, float, double, Object 포함
굉장히 많아 보이지만 오버로딩된 메서드들이 많기 때문에 상황에 따라 적절하게 사용하면 된다.
split() 메서드를 사용하면 구분자를 기준으로 문자열을 분리해 반환할 수 있다. 이와 반대로 구분자를 사이에 추가하여 문자열을 결합하여 반환도 가능하다.
join() 메서드를 사용하면 구분자와 인스턴스를 통해 문자열 결합이 가능하다.
예시를 보자.
String[] strArray = {"sen1","sen2","sen3"};
String str = String.join("/", strArray);
System.out.println(str);
구분자를 "/"로 하고 String 배열 strArray의 요소들을 결합하여 반환한 값을 str 참조변수에 저장하면 구분자가 사이에 추가된 문자열을 출력할 수 있다.
sen1/sen2/sen3
join() 외에도 StringJoiner 클래스를 사용하여 결합이 가능하다.
StringJoiner sj = new StringJoiner("/","[","]");
String[] strArray = {"sen1","sen2","sen3"};
for(int i = 0; i < strArray.length; i++) {
sj.add(strArray[i]);
}
System.out.println(sj);
우선 StringJoiner 인스턴스를 생성후 new 연산자에 파라미터로 구분자로 들어갈 문자열, 좌측 끝에 들어갈 문자열, 우측 끝에 들어갈 문자열 순으로 입력한다.
String 배열을 인덱스 순으로 StringJoiner 인스턴스에 add하면 된다.
출력 결과를 확인해보자.
sen1/sen2/sen3
자주 사용하는 String 메서드 중 valueOf()가 있었다. 기본형 값을 문자열로 변환하여 반환하는 기능으로 파라미터에 기본형 값을 넣어주면 되었다.
valueOf 외에도 방법이 있다. 기본형 값에 빈 문자열을 더해주면 된다.
int i = 4;
String str = i + "";
System.out.println(str);
반대로 String 문자열을 기본형 값으로 변환하는 방법이 있다.
parse를 사용하는데 변환하려는 형마다 메서드가 다르다.
- Boolean.parseBoolean(string s)
- Byte.parseByte(String s)
- short.parseShort(String s)
- Integer.parseInt(String s)
- Long.parseLong(String s)
- Float.parseFloat(String s)
- Double.parseDouble(String s)
여기서 첫 글자가 대문자인 것은 래퍼 클래스를 의미한다. 래퍼 클래스는 뒤에서 다루겠다.
StringBuffer는 String과 다르게 문자열 내용의 변경이 가능한 클래스이다.
내부적으로 크기(Buffer) 지정이 가능하며 인스턴스 생성 시 크기 지정이 가능하다.
- 인스턴스 생성 시 크기 지정 가능
- 크기를 지정하지 않고 기본 생성자로 생성할 경우 크기는 16으로 고정
- 인스턴스 생성 시 문자열을 입력할 경우 지정된 문자열보다 16 크게 크기가 지정
public StringBuffer(int length){
value = new char[length];
shared = false;
}
public StringBuffer(){
this(16);
}
public StringBuffer(String str){
this(str.length() + 16);
append(str);
}
위에서부터 Buffer의 크기를 지정한 경우, 기본 생성자 사용, 문장열을 입력할 경우 실행되는 생성자들이다.
문자열을 입력할 경우 입력한 문자열에 길이에 16만큼 더해지는 크기가 할당되고 문자열은 append를 통해 추가되는 것을 알 수 있다.
public class review {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("String");
StringBuffer sb2;
sb2 = sb.append("Buffer");
System.out.println(sb);
System.out.println(sb2);
}
}
StringBuffer
StringBuffer
append는 반환 타입이 StringBuffer인데 자신의 주소값을 반환한다. 여기서 참조변수 sb에 append를 통해 추가된 내용의 주소값을 sb2에 대입하였더니 출력값이 같은 것을 알 수 있다.
즉, 주소값을 반환하기 때문에 sb,sb2는 같은 주소값을 가리키고 있고 한쪽에서 내용을 변경할 경우 서로에게 영향을 준다는 의미이다.
반환 타입이 StringBuffer이기 때문에 이런 것도 가능하다.
sb.append("abc").append("def");
반환 타입이 void일 경우에는 불가능 할 것이다.
StringBuffer는 equals메서드가 오버라이딩 되어 있지 않아 등가비교연산자와 같은 기능을 하게 된다.
public class review {
public static void main(String[] args) {
String str1 = new String("String");
String str2 = new String("String");
StringBuffer sb1 = new StringBuffer("StringBuffer");
StringBuffer sb2 = new StringBuffer("StringBuffer");
System.out.println(str1.equals(str2)); //true
System.out.println(sb1.equals(sb2)); //false
}
}
String 클래스에서는 equals가 문자열의 내용을 비교하여 결과값을 출력하도록 오버라이딩 되어 있기 때문에 문자열 내용만 같으면 true를 반환한다.
하지만, StringBuffer는 오버라이딩 되어 있지 않아 주소값을 비교하기 때문에 false가 나올 수 밖에 없다.
그렇다면 StringBuffer의 문자열 내용으로 비교를 하려면 어떻게 해야할까?
바로 toString()을 사용하면 된다.
toString()은 인스턴스가 담고 있는 문자열을 String 인스턴스로 반환하기 때문에 equals를 사용하면 문자열 내용으로 비교가 가능하다.
public class review {
public static void main(String[] args) {
String str1 = new String("String");
String str2 = new String("String");
StringBuffer sb1 = new StringBuffer("StringBuffer");
StringBuffer sb2 = new StringBuffer("StringBuffer");
System.out.println(str1.equals(str2));
System.out.println(sb1.toString().equals(sb2.toString()));
}
}
StringBuilder는 StringBuffer와 기능적으로는 동일하다. 하지만, 멀티쓰레드에 안전하도록 동기화된 StringBuffer 특성 상 멀티쓰레드로 작성된 프로그램이 아닌 싱글쓰레드인 경우 오히려 동기화가 성능을 저하시킨다.
이럴 때 StringBuffer에서 동기화 기능만 뺀 StringBuilder를 사용하는 것이 성능면에서 더 효율적이다.
StringBuilder sb;
sb = new StringBuilder();
sb.append("StringBuilder);
단순히 참조변수 타입과 생성자만 StringBuilder로 변경해주면 사용이 가능하다. 하지만, 성능 향상이 반드시 필요한 경우 외에눈 StringBuffer 자체도 성능이 좋기 때문에 굳이 StringBuilder를 사용할 필요는 없다.
자주 사용되는 StringBuffer 클래스의 메서드들이다.
외우지는 말고 필요할 때 찾아서 적용하면 좋을 것 같다.
- Stringbuffer append(parameter) : 매개변수로 입력된 값을 문자열로 변환하여 인스턴스가 저장하고 있는 문자열 뒤에 추가(parameter에 객체가 올 경우 해시코드 값을 추가함)
- int capacity() : StringBuffer 인스턴스에 버퍼크기를 반환
- char charAt(int index) : 인덱스 번째에 있는 문자를 반환
- StringBuffer delete(int start, int end) : start번째부터 end-1 번째 인덱스까지 문자 제거
- StringBuffer deleteCharAt(int index) : 인덱스 번째에 있는 문자 제거
- StringBuffer insert(int pos, parameter) : pos번째 인덱스에 매개변수로 받은 값 추가
- StringBuffer replace(int start, int end, String str) : start번째부터 end-1번째 인덱스까지 주어진 문자열로 변경
- int length() : 인스턴스에 저장되어 있는 문자열의 길이 반환
- StringBuffer reverse() : 인스턴스에 저장되어 있는 문자열 순서 뒤집기
- void setCharAt(int index, char ch) : 해당 인덱스의 문자를 매개변수로 받은 값으로 변경
- void setLength(int newLength) : 지정된 길이로 문자열의 길이 변경. 길이를 늘리는 경우 나머지 빈 공간을 null문자 '\u0000'으로 채움
- String toString() : StringBuffer인스턴스의 문자열을 String 인스턴스로 반환
- String substring(int start) : start번째 인덱스부터(포함) 문자열을 뽑아서 String으로 반환
- String substring(int start, int end) : start번째부터 end-1번째 인덱스까지 문자열을 뽑아서 String으로 반환
Math 클래스는 기본적인 수학계산에 유용한 메서드로 구성되어 있는 클래스로 클래스 내에 인스턴스 변수가 없다. 모든 메서드가 static이며 상수는 오직 2개만 존재한다.
public static final double E ; // 자연로그의 밑
public static final double PI; // 원주율
따라서, 인스턴스 생성 없이 Math 클래스의 메서드 사용이 가능하다.
예시를 통해 설명하겠다.
import static java.lang.Math.*;
public class Example {
public static void main(String[] args){
double value = 90.7552;
System.out.println(ceil(value)); //올림
System.out.println(floor(value)); //버림
System.out.println(round(value)); //반올림
System.out.println(rint(value)); //반올림
}
}
round를 제외하고는 모두 반환형이 double이다.
여기서 round와 rint은 소수점 첫째자리에서 항상 반올림을 하는데, round의 경우 흔히 산수에서 사용하는 반올림을 하지만, rint는 가장 가까운 짝수 정수를 반환한다.
예를 들어, 1.5일 경우 2.0을, 2.5일 경우 2.0을 반환한다.
두 점의 좌표를 알고 그 때 두 점 사이의 거리와 x축, y축 상의 거리로 끼인각을 구할 수 있다.
import static java.lang.Math.*;
public class Example {
public static void main(String[] args){
double x1 = 3.0, y1 = 4.0;
double x2 = 5.5, y2 = 6.7;
double distance = sqrt(pow(x2-x1,2) + pow(y2-y1,2));
double a = x2 - x1;
double b = y2 - y1;
double angle1 = atan2(a,b);
double angle2 = atan2(b,a);
System.out.println("두 점 사이의 거리 : " + distance);
System.out.println("x축 상의 거리 : " + a);
System.out.println("y축 상의 거리 : " + b);
System.out.println("끼인각1 : " + angle1);
System.out.println("끼인각2 : " + angle2);
}
}
sqrt()를 사용하면 제곱근을 구할 수 있고 pow()를 사용하면 n제곱이 가능하다.
atan2()는 두 변의 길이를 통해 직각 삼각형에서의 끼인각이 라디안 값으로 반환된다.
그렇다면, 반대로 끼인각을 통해 x,y축 상의 거리인 두 변의 길이를 구할 수 있다.
cos,sin을 사용하면 된다.
double x1 = 3.0, y1 = 3.0;
double x2 = 4.5, y2 = 4.5;
double distance = sqrt(pow(x2-x1,2) + pow(y2-y1,2));
double a = distance*cos(PI/4);
double b = distance*sin(PI/4);
System.out.println(a);
System.out.println(b);
PI/4는 45도를 라디안으로 나타낸 것이다. 만약, 각도로 사용할 경우 toRadians()를 사용해야 한다. 반대로, 라디안을 각도로 변환할 경우 toDegrees()를 사용한다.
int addExact(int x, int y) // x + y
int subtractExact(int x, int y) // x - y
int multiplyExact(int x, int y) // x * y
int incrementExact(int a) // a++
int decrementExact(int a) // a--
int negateExact(int a) // -a
int toIntExact(long value) //(int)value
위에 메서드는 기존에 연산자 기능을 하는 메서드와 같은 기능을 가지지만 별도로 오버플로우 감지가 가능하여, 오버플로우가 발생할 경우 ArithmeticException을 발생시킨다.
따라서, 저 메서드들은 사용할 경우 예외처리를 해주어야 한다.
- static parameterType abs(parameter) : 매개변수의 절대값 반환
- static double ceil : 올림하여 반환
- static double floor : 버림하여 반환
- static parameterType max(parameter1, parameter2) : 주어진 매개변수를 비교하여 큰 값을 반환
- static parameterType min(parameter1, paremeter2) : 주어진 매개변수를 비교하여 작은 값을 반환
- static double random() : 0.0 ~ 1.0 범위의 임의의 double 값 반환. 이때, 1.0은 범위에 포함되지 않음
- static double rint() : 주어진 매개변수와 가장 가까운 정수값을 반환(두 정수 사이의 가운데 값일 경우 짝수 정수 반환)
Integer, Double...처럼 기본형 8개 타입과 비슷하게 생겼지만 다른 타입을 여러 번 보았지만 예전에는 이게 Wrapper 클래스라는 것을 모르고 있었다.
기본형 변수가 있는데 굳이 Wrapper 클래스라는 것을 만들었을까?
우선, 값을 변경할 경우 차이가 있다. 기본형 변수의 경우 Stack 영역에 저장되므로 값에 의한 변경이지만 Wrapper 타입의 변수는 객체이므로 참조에 의한 변경 즉, Heap 영역에 저장이 된다.
두 번째로 나중에 다룰 내용이지만 Java.util 패키지의 클래스들은 객체만 처리하므로 Wrapper 클래스 사용이 유용하다.
마지막으로 Collection 프레임워크의 데이터 구조는 기본 타입이 아닌 객체를 저장하기 때문에 기본형 대신 Wrapper 클래스를 사용할 수 밖에 없다
선언타입은 char, int을 제외하고는 기본형의 첫 글자만 대문자인 차이가 있고 이름은 동일하다.
boolean -> Boolean
char -> Character
byte -> Byte
short -> Short
int -> Integer
long -> Long
float -> Float
double -> Double
Boolean,Character 클래스를 제외한 나머지 6개는 전부 Number 클래스의 자손 클래스들이다.
Boolean b = new Boolean(true);
Character c = new Character('a');
Byte bt = new Byte((byte)10);
Short st = new Short((short) 10);
Integer i = new Integer(10);
Long l = new Long(10);
Float f = new Float(1.0);
Double d = new Double(1.0);
인스턴스 생성 시 생성자에 매개변수를 입력해야 하며 ""를 사용하여 문자열 형태로 입력하는 것도 가능하다.
Wrapper 클래스에는 equals(), toString()이 오버라이딩 되어 있으며 equals()는 주소값이 아닌 객체가 가지고 있는 값을 비교한다.
toString()은 객체가 가지고 있는 값을 문자열로 변환하여 반환한다.
Wrapper 객체에는 비교연산자(>,==,< 등) 사용이 불가하다. 비교연산자를 사용하면 객체 내부의 주소값을 비교하기 때문에 false가 나올 수 밖에 없다.
(-128 ~ 127 사이의 값은 IntegerCache에서 관리하기 때문에 true가 나올 수 있음)
그래서 값을 비교하려면 compareTo()를 사용해야 한다. 비교하는 객체보다 작으면 -1, 같으면 0, 크면 1을 반환한다. equals()도 마찬가지로 값을 비교하여 boolean 값을 반환한다.
public class Example {
public static void main(String[] args){
Integer i = new Integer(10);
Integer i2 = new Integer(20);
System.out.println(i.equals(i2));
System.out.println(i.toString());
System.out.println(i.compareTo(i2));
Long l1 = new Long(128L);
Long l2 = new Long(128L);
System.out.println(l1 == l2); //false
}
}
String 클래스에서 valueOf(parameter)라는 메서드가 있었다. 매개변수 값을 문자열로 변환해주는 기능을 가진다.
이와 반대로 문자열을 숫자로 변환하는 메서드가 있다.
int i = Integer.parseInt("100"); // 타입.parse타입(String str)
Integer i2 = Integer.valueOf("100"); // 타입.valueOf(String str)
두 메서드의 차이점은 선언 타입에서 알 수 있듯이 반환타입이 각각 기본형, Wrapper 클래스라는 것이다.
autoboxing 기능으로 인해 차이는 없지만 성능에 있어서 valueOf 가 조금 느리다.
Wrap 의 의미가 "감싸다","포장하다" 인데 말 그대로 기본형을 Boxing 하면 Wrapper 클래스가 된다. 반대의 경우는 UnBoxing이라고 한다.
AutoBoxing & UnBoxing은 JDK 1.5 이후 생긴 기능으로 컴파일러가 자동으로 기본형과 Wrapper 클래스 타입을 변환해주는 기능이다.
int i = 5;
Integer i2 = new Integer(3);
int sum = i + i2;
//int sum = i + i2.intvalue();
//Integer sum2 = new(i) + i2;
기존에는 기본형과 Wrapper 클래스 타입을 연산 시 형을 예시에 있는 주석처럼 변환해야했지만 AutoBoxing과 UnBoxing 기능이 도입되면서 자동으로 변환이 된다.
ArrayList 클래스에 기본형 값을 저장하거나 꺼내는 것도 이 기능 덕분에 편리해졌다.
ArrayList<Integer> list = new ArrayList<Integer>;
list.add(10); // AutoBoxing : 기본형 -> 참조형
int i = list.get(0); // UnBoxing : 참조형 -> 기본형