사이먼's 코딩노트
[Java] 배열 / toString / equals / StringBuilder 본문
[배열]
- 배열에 대해서 알아봅시다.
- 아래는 배열을 사용하지 않았을 때, 단순히 메서드를 이용해서 값을 하나씩 저장하고 추출하는 코드입니다.
public class Main {
public static void main(String[] args) {
IntArr intArr1 = new IntArr1();
System.out.println("배열의 길이 : " + intArr1.length);
// 출력 => 배열의 길이 : 1
intArr1.setData(0, 20);
System.out.println(intArr1.getData(0));
// 출력 => 20
IntArr intArr3 = new IntArr3();
System.out.println("배열의 길이 : " + intArr3.length);
// 출력 => 배열의 길이 : 3
intArr3.setData(0, 20);
intArr3.setData(1, 40);
intArr3.setData(2, 60);
System.out.println(intArr3.getData(0));
// 출력 => 20
System.out.println(intArr3.getData(1));
// 출력 => 40
System.out.println(intArr3.getData(2));
// 출력 => 60
}
}
abstract class IntArr {
int length;
abstract void setData(int index, int value);
abstract int getData(int index);
}
class IntArr1 extends IntArr {
int data0;
IntArr1() {
length = 1;
}
void setData(int index, int value) {
if(index == 0) {
this.data0 = value;
}
}
int getData(int index) {
if(index == 0) {
return this.data0;
}
return 0;
}
}
class IntArr3 extends IntArr {
int data0;
int data1;
int data2;
IntArr3() {
length = 3;
}
void setData(int index, int value) {
if(index == 0) {
this.data0 = value;
}
else if(index == 1) {
this.data1 = value;
}
else if(index == 2) {
this.data2 = value;
}
}
int getData(int index) {
if(index == 0) {
return this.data0;
}
else if(index == 1) {
return this.data1;
}
else if(index == 2) {
return this.data2;
}
return 0;
}
}
- 코드가 길어보이지만 사실 굉장히 단순한 코드이다.
- 간단하게 말해 배열을 사용하지 않고 IntArr1과 IntArr3이라는 객체 생성과 함께 setData()와 getData() 메서드로 값을 저장하고 추출하는 동작을 한다.
- setData() 메서드는 매개변수로 index와 value 값을 넘겨받고 전역변수 value 값을 저장한다.
- 이 때 각 index별로 다른 value 값이 담겨야 되기 때문에 조건문을 사용한다.
- getData() 메서드는 매개변수로 index 번호를 넘겨받고 조건문을 사용하여 각 index에 저장된 전역변수 data를 return한다.
- 위와 같은 코드는 배열을 사용하지 않은 상태이고, 지금처럼 index의 개수가 적을 때는 문제가 없겠지만, 만약 100개의 숫자를 저장하고 꺼낸다했을 떄는 같은 코드를 여러면 반복해서 써야되기 때문에 비효율적이다.
- 아래 코드는 배열을 사용하여 해당 코드를 다시 작성한 것이다.
public class Main {
public static void main(String[] args) {
int[] intArr1 = new int[1];
System.out.println("배열의 길이 : " + intArr1.length);
// 출력 => 배열의 길이 : 1
intArr1[0] = 20;
System.out.println(intArr1[0]);
// 출력 => 20
int[] intArr3 = new int[3];
System.out.println("배열의 길이 : " + intArr3.length);
// 출력 => 배열의 길이 : 3
intArr3[0] = 20;
intArr3[1] = 40;
intArr3[2] = 60;
System.out.println(intArr3[0]);
// 출력 => 20
System.out.println(intArr3[1]);
// 출력 => 40
System.out.println(intArr3[2]);
// 출력 => 60
}
}
- 일단 확실히 육안으로 봤을 때, 배열을 사용하지 않았을 코드보다는 다이어트 된 코드를 볼 수 있다.
- IntArr라는 클래스를 따로 만들지 않고도 new int[1]과 new int[3]을 통해 길이 1과 3의 int 타입의 배열 객체를 생성하였다.
- length를 사용하여 해당 배열의 길이를 구할 수 있고, [ ] 안에 index번호 0번 부터 차례대로 넣어주면 setData() 메서드의 역할을 하게된다.
- 마찬가지로 출력문을 통해 원하는 배열의 위치를 [ ] 안에 index번호를 작성하면 getData() 메서드의 역할을 하게된다.
- 누가봐도 이런 상황에서는 배열을 사용하는 것이 좋다는 것을 알 수 있다.
[toString()]
- toString() 메서드는 Java의 최상위 클래스인 Object 클래스가 가진 메서드이다.
- toString() 메서드는 객체가 가지고 있는 정보나 값들을 문자열(String)로 리턴한다.
- 객체 생성 후 toString() 메서드를 사용하면 결과값으로는 이상한 정보가 담기게 되는데 그 값은 순수 Object의 toString 결과 값으로 크게 의미는 없는 기본값이라고 생각하면된다.
- 그렇기 때문에 사용자가 원하는 결과값을 보기 위해서 toString() 메서드를 오버라이딩하여 사용할 수도 있다.
- 먼저 오버라이딩 하지 않은 toString() 메서드를 출력했을 때의 코드와 출력문이다.
public class Main {
public static void main(String[] args) {
사람 a사람1 = new 사람("홍길동", 22);
사람 a사람2 = new 사람("홍길순", 23);
System.out.println(a사람1);
System.out.println(a사람2.toString());
}
}
class 사람 extends Object {
String 이름;
int 나이;
사람(String 이름, int 나이) {
this.이름 = 이름;
this.나이 = 나이;
}
}
- 여기서 주의할 점은 사람 클래스는 최상위 클래스인 Object 클래스를 상속 받기 때문에 extends Object를 작성하였다.
- 총 2개의 사람 객체가 생성되고, a사람1, a사람2에는 사실 사람 객체와 연결된 리모콘의 정보가 들어가있다.
- 그렇기 때문에 출력문을 통해 toString()을 이용한 래퍼런스 변수를 출력해보면 해당 리모콘의 정보를 문자열화 해서 출력이 된다.
- 하지만 출력결과를 보면 우리는 "홍길동"과 "22"을 보고싶은데 막상 출력되는 건 클래스 이름과 해시코드뿐이다.
- 다음은 toString() 메서드를 오버라이딩 했을 때의 코드와 출력문이다.
public class Main {
public static void main(String[] args) {
사람 a사람1 = new 사람("홍길동", 22);
사람 a사람2 = new 사람("홍길순", 23);
System.out.println(a사람1);
System.out.println(a사람2.toString());
}
}
class 사람 extends Object {
String 이름;
int 나이;
사람(String 이름, int 나이) {
this.이름 = 이름;
this.나이 = 나이;
}
@Override
public String toString() {
return "[이름 = " + 이름 + ", 나이 = " + 나이 + "]";
}
}
- 첫 번째와 비교했을 때 다른 점은 toString() 메서드를 오버라이딩한 것 뿐이다.
- 메서드에 @Override는 사용자가 직접 오버라이딩을 했다는 주석과 같은 표식이라고 생각하면 좋다.
- 위 코드처럼 오버라이딩을 통해 내가 원하고 싶은 정보만 담아 return을 하게 되면 출력문을 통해 해당 정보만 출력된다.
- 하지만 Object 타입이 아닌 String 타입으로 객체를 생성했더라면 지금과 같은 toString() 오버라이딩 작업은 굳이 할 필요는 없다.
[equals()]
- equals() 메서드는 Java의 최상위 클래스인 Object 클래스가 가진 메서드이다.
- equals() 메서드는 문자열 간의 비교를 통해 boolean 타입으로 참과 거짓을 리턴한다.
- 우리가 보통 두 변수 간의 비교를 할 때 연산자 "==" 을 많이 사용하게 되는데 사실 String 타입에선 "==" 연산자를 통해 두 문자열을 비교하지 않는다.
- 보다 쉽게 설명하자면 "==" 연산자는 int나 boolean과 같은기본 원시 타입에 대해서는 값을 비교할 수 있지만, String과 객체와 같은 참조 타입에 대해서는 값이 아니라 주소값을 비교하는 것이다.
- 아래 코드를 통해 해당 메서드에 대해 더 깊이 알아봅시다.
public class Main {
public static void main(String[] args) {
사람 a사람1 = new 사람("홍길동", 22);
사람 a사람2 = new 사람("홍길동", 22);
if ( a사람1.equals(a사람2) ) {
System.out.println("참");
}
else {
System.out.println("거짓");
}
}
}
class 사람 extends Object {
String 이름;
int 나이;
사람(String 이름, int 나이) {
this.이름 = 이름;
this.나이 = 나이;
}
@Override
public boolean equals(Object o) {
if ( o instanceof 사람 == false ) {
return false;
}
사람 other = (사람)o;
if ( !이름.equals(other.이름) ) {
return false;
}
if ( this.나이 != other.나이 ) {
return false;
}
return true;
}
}
- Main 메서드를 살펴보면 사람이라는 객체를 총 2개 생성한 것을 알 수 있다.
- 사람 객체 생성과 동시에 생성자를 통해 이름과 나이를 저장했고, a사람1과 a사람2 리모콘에 연결된 이름과 나이는 모두 "홍길동"과 "22"로 동일하다.
- 이 때 연산자 "==" 를 통해 두 래퍼런스 변수를 비교한다면 false(거짓)의 결과값을 얻을 수 있다.
- 그 이유는 사람의 모양이 즉, 속성과 요소만 같을 뿐 두 래퍼런스는 결국 서로 다른 객체를 연결시키기 때문에 비교했을 때 같지 않다는 것이다.
- 하지만 equals()를 통해 두 래퍼런스 변수를 비교한다면 true(참)의 결과값을 얻을 수 있다.
- 그 이유는 서로 다른 사람 객체와 연결은 됐지만, 해당 사람이 가진 속성과 요소가 같기 때문에 두 객체가 서로 동등한 지를 비교했을 때 같다는 것이다.
- 이처럼 객체끼리 또는 문자열, 문자끼리 비교할 때는 연산자 대신 무조건 equals()를 사용해야한다.
- 마지막으로 equals() 메서드도 toString()과 같이 Object 클래스의 메서드이기 때문에 사용자가 임의로 오버라이딩하여 원하는 대로 메서드를 커스텀할 수 있다.
[StringBuilder]
- StringBuilder는 아래 코드를 보면서 살펴봅시다.
public class Main {
public static void main(String[] args) {
System.out.println("String의 같은 듯 다른 선언");
String str1 = "안녕";
String str2 = new String("안녕");
String str3 = "안녕";
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println("별 100개를 만들기위한 아주 아주 안좋은 방법");
String str = "*"; // *
str += "*"; // *, **
str += "*"; // *, **, ***
str += "*"; // *, **, ***, ****
System.out.println(str);
System.out.println("별 100개를 만들기위한 아주 아주 좋은 방법");
StringBuilder sb = new StringBuilder();
for(int i = 0; i < 100; i++) {
sb.append("*");
}
System.out.println(sb.toString());
}
}
- 위 코드 중에서 첫 번째 세션인 "String의 같은 듯 다른 선언" 코드를 살펴봅시다.
- str1과 str3은 선언과 동시에 바로 문자열을 저장하였고, str2는 new String()을 통해 문자열을 저장한 것을 볼 수 있다.
- 출력문을 통해 3개의 변수를 연산자 "==" 을 통해 주소값을 비교해보면 str1과 str3은 같지만, str1과 str2는 다르다는 것을 확인할 수 있다.
- 그 이유는 str1, str3의 객체와 str2의 객체가 서로 완전히 다르기 때문이다. 그래서 엄밀히 따지면 String 타입에 바로 문자열을 넣는 것과 new String()을 사용해서 문자열을 넣는 것은 다르다.
- 또한 str1은 최초에 값을 "안녕"이라고 저장하고 str3에서도 같은 값인 "안녕"이라고 저장한다면 해당 변수가 재활용된다고 생각하면 좋다.
- 반면에 str2는 아예 다른 객체를 생성했기 때문에 재활용의 개념과는 맞지않다.
- 간단하게 말하면 str1, str3는 메모리 절약모드가 켜져있는 변수이고, str2는 메모리 절약모드가 꺼져있는 변수라고 생각하면 좋다.
- 다음은 두번째 코드인 "별 100개를 만들기위한 아주 아주 안좋은 방법" 코드를 살펴봅시다.
- 일단 str 변수는 메모리 절약모드가 켜져있는 변수로서 최초에 "*"이 문자열로 저장되어 있고, [str += "*"] 라는 코드를 통해 별을 하나씩 추가하고 있다.
- 우리가 상식적으로 생각했을 때 "*" + "*" 와 같이 하나씩 문자열을 붙여가면서 별이 추가되는 것처럼 보이지만 사실은 그렇지 않다.
- 해당 코드는 최초에 별을 하나 저장하고, 그 다음은 두개인 별을 따로 저장하고, 그 다음은 세개인 별을 따로 저장하고, 이런식으로 쓰레기 값들이 계속 str에 쌓이게 되는 것이다.
- 물론 이런식으로 100번을 작성하면 마지막엔 별 99개까지 생성된 곳에 별 100개짜리를 또 추가하고 저장되어 출력문에는 우리가 원하는 데로 나오겠지만 이는 매우 좋지 않은 방법이다.
- 그래서 사용되는 것이 세번째 코드인 StringBuiler을 이용한 "별 100개를 만들기위한 아주아주 좋은 방법" 이다.
- sb라는 래퍼런스 변수를 통해 StringBuiler라는 객체를 생성해주고, 반복문을 통해 sb.append("*") 을 작성하면 두번째 코드처럼 쓰레기 값들이 남는 것이 아니고 우리가 생각하는 계속 연결되어 별이 붙는 형식으로 동작을 한다.
- 코드를 보면 알 수 있겠지만 여기서 append() 메서드는 StringBuiler 클래스의 주요 메서드이며, 문자열을 추가할 때 사용한다.
반응형
'Java > Java' 카테고리의 다른 글
[Java] 제네릭 / HashMap / 정리 (0) | 2024.03.18 |
---|---|
[Java] ArrayList (2) | 2024.03.18 |
[Java] 형변환 문제풀이 (0) | 2024.03.13 |
[Java] 형변환(래퍼클래스 / Object) (0) | 2024.03.13 |
[Java] 문자열을 다루는 String 클래스 메서드 (2) | 2024.03.13 |