자바 개발자 일기 6일차

Shy·2024년 1월 8일

아이티윌

목록 보기
6/22

배열 복사

package com.mystudy.array3_copy;

import java.util.Arrays;

public class ArrayCopy {

	public static void main(String[] args) {
		// 배열 복사
		int[] num1 = new int[5];
		int[] num2 = new int[5];
		System.out.println("num1 : " + num1);
		System.out.println("num2 : " + num2);
		System.out.println("--------------------------");
		printData("num1", num1);
		printData("num2", num2);
		
		System.out.println("=== 배열복사(주소값 복사, 얕은복사 ====");
		num2 = num1; // 주소값 복사
		num1[0] = 999; num1[1]= 20; num1[2]= 30; num1[3]=40; num1[4]= 50;
		System.out.println("참조형 데이터는 주소값이 같은 지 확인, num1 == num2 : " + (num1 == num2));
		printData("num1", num1);
		printData("num2", num2);
		
		System.out.println("=== 배열값 복사(물리적복사, 깊은복사(deep copy) ===");
		
		int[] num3 = new int[5];
		printData("num3", num3);
		
		for (int i = 0; i < num3.length; i++) {
			num3[i] = num1[i];
		}
		
		
		printData("num1", num1);
		printData("num3", num3);
		System.out.println("참조형 데이터는 주소값이 같은 지 확인, num1 == num3 : " + (num1 == num3));
		System.out.println("--------------------------");
		
		num1[0] = 888;
		System.out.println(">>> num1[0] = 888 변경 ");
		printData("num1", num1);
		printData("num2", num2);
		printData("num3", num3); // 3은 변경되지 않는다.
		
		num1[1] = 777;
		System.out.println(">>> num1[0] = 888 변경 ");
		printData("num1", num1);
		printData("num2", num2);
		printData("num3", num3); // 3은 변경되지 않는다.
		
		num1[2] = 666;
		System.out.println(">>> num1[0] = 888 변경 ");
		printData("num1", num1);
		printData("num2", num2);
		printData("num3", num3); // 3은 변경되지 않는다.
		System.out.println("==========================");
		
		System.out.println("=== System.arraycopy() ===");
		int[] num4 = new int[num1.length];
		printData("num4", num4);
		
		// System.arraycopy(num3, 0, num4, 0, 0); // 원본데이터, 시작위치, 타겟데이터, 대상데이터의 시작위치, 몇개를 복사할것인지
		System.arraycopy(num1, 0, num4, 0, num1.length); // num1의 전체 데이터 복사
		
		System.out.println(">> System.arraycopy 복사 ");
		
		printData("num1", num1);
		printData("num4", num4);
		System.out.println("참조형 데이터는 주소값이 같은 지 확인, num1 == num3 : " + (num1 == num4));
		
		System.out.println("=== 배열객체.clone() 복사 ===");
		int[] num5 = num4.clone();
		printData("num4", num4);
		printData("num5", num5);
		System.out.println("참조형 데이터는 주소값이 같은 지 확인, num4 == num5 : " + (num4 == num5));
		
		System.out.println("==== Array.copyOf(원복데이터, 갯수) 복사 ====");
		int[] num6 = Arrays.copyOf(num4, num4.length);
		System.out.println("참조형 데이터는 주소값이 같은 지 확인, num4 == num6 : " + (num4 == num6));
		
		System.out.println("==== Array.copyOfRange(원데이터, 부터, 끝) 복사 ====");
		int[] num7 = Arrays.copyOfRange(num4, 1, 3); // 3번 이전 까지 복사
		printData("num4", num4);
		printData("num7", num7);
		System.out.println("참조형 데이터는 주소값이 같은 지 확인, num4 == num7 : " + (num4 == num7));
	 

	}
	
	// 메소드 오버로딩
	// - 동일한 클래스 내에서 같은 이름의 메소드명 사용 메소드 중복선언 할 수 있다.
	// - 메소드명은 동일하게, 메소드의 파라미터 갯수, 타입, 순서를 다르게
	static void printData(String name, int[] num) {
		System.out.print(name + " : ");
		for (int i = 0; i < num.length; i++) {
			System.out.print(num[i] + " ");
		}
		System.out.println();
	}
	
	static void printdata(int[] num) {
		for (int i = 0; i < num.length; i++) {
			System.out.print(num[i] + " ");
		}
		System.out.println();
	}
	
//	num1 : [I@15db9742
//	        num2 : [I@6d06d69c
//	        --------------------------
//	        num1 : 0 0 0 0 0 
//	        num2 : 0 0 0 0 0 
//	        === 배열복사(주소값 복사, 얕은복사 ====
//	        참조형 데이터는 주소값이 같은 지 확인, num1 == num2 : true
//	        num1 : 999 20 30 40 50 
//	        num2 : 999 20 30 40 50 
//	        === 배열값 복사(물리적복사, 깊은복사(deep copy) ===
//	        num3 : 0 0 0 0 0 
//	        num1 : 999 20 30 40 50 
//	        num3 : 999 20 30 40 50 
//	        참조형 데이터는 주소값이 같은 지 확인, num1 == num3 : false
//	        --------------------------
//	        >>> num1[0] = 888 변경 
//	        num1 : 888 20 30 40 50 
//	        num2 : 888 20 30 40 50 
//	        num3 : 999 20 30 40 50 
//	        >>> num1[0] = 888 변경 
//	        num1 : 888 777 30 40 50 
//	        num2 : 888 777 30 40 50 
//	        num3 : 999 20 30 40 50 
//	        >>> num1[0] = 888 변경 
//	        num1 : 888 777 666 40 50 
//	        num2 : 888 777 666 40 50 
//	        num3 : 999 20 30 40 50 
//	        ==========================
//	        === System.arraycopy() ===
//	        num4 : 0 0 0 0 0 
//	        >> System.arraycopy 복사 
//	        num1 : 888 777 666 40 50 
//	        num4 : 888 777 666 40 50 
//	        참조형 데이터는 주소값이 같은 지 확인, num1 == num3 : false
//	        === 배열객체.clone() 복사 ===
//	        num4 : 888 777 666 40 50 
//	        num5 : 888 777 666 40 50 
//	        참조형 데이터는 주소값이 같은 지 확인, num4 == num5 : false
//	        ==== Array.copyOf(원복데이터, 갯수) 복사 ====
//	        참조형 데이터는 주소값이 같은 지 확인, num4 == num6 : false
//	        ==== Array.copyOfRange(원데이터, 부터, 끝) 복사 ====
//	        num4 : 888 777 666 40 50 
//	        num7 : 777 666 
//	        참조형 데이터는 주소값이 같은 지 확인, num4 == num7 : false


}

이 코드는 Java에서 배열을 복사하는 다양한 방법을 설명하고 있다.

각각의 방법은 배열 데이터를 다루는 방식에 따라 얕은 복사(shallow copy)깊은 복사(deep copy)로 구분될 수 있다.

  1. 얕은 복사 (Shallow Copy):
    • num2 = num1;: 이 코드는 num1 배열의 참조(주소)를 num2에 할당한다. 얕은 복사는 원본 배열과 복사된 배열이 동일한 데이터를 가리키게 되므로, 하나의 배열에서 이루어진 변경이 다른 배열에도 영향을 미친다는 특징이 있다.
  2. 깊은 복사 (Deep Copy):
    • for 루프를 사용한 복사: num3 배열에 num1의 각 요소를 하나씩 복사한다. 이 방법은 배열의 각 요소를 새로운 배열에 복사하기 때문에, 원본 배열과 복사된 배열은 서로 독립적이게 된다.
    • System.arraycopy(): 이 메소드는 원본 배열의 특정 부분을 대상 배열의 특정 위치에 복사할 수 있다. 예를 들어, System.arraycopy(num1, 0, num4, 0, num1.length);는 num1의 모든 요소를 num4로 복사한다.
    • clone(): num5 = num4.clone();는 num4 배열의 깊은 복사본을 생성해. clone() 메소드는 배열의 모든 요소를 새로운 배열로 복사하며, 원본 배열과 복사된 배열이 서로 독립적이게 됨을 보장한다.
    • Arrays.copyOf(): num6 = Arrays.copyOf(num4, num4.length);는 num4의 모든 요소를 새로운 배열 num6에 복사한다. 이 방법도 깊은 복사에 속하며, 원본 배열의 특정 길이만큼을 새 배열에 복사할 수 있다.
    • Arrays.copyOfRange(): num7 = Arrays.copyOfRange(num4, 1, 3);는 num4의 특정 범위(여기서는 인덱스 1부터 2까지)의 요소를 새로운 배열 num7에 복사한다. 이 방법은 배열의 일부분만을 복사할 때 유용하다.

실습예제

package com.mystudy.array3_copy;

public class ArrayCopy2 {

	public static void main(String[] args) {
		/*
		 * 문제1: int 타입의 데이터 4개를 저장할 수 있는 배열(num1)
		 * 1. 10, 20, 30, 40 숫자를 입려하고 화면 출력
		 * 2. 세번재 데이터 100으로 바꾸고 화면 출력
		 * 3. 20 숫자를 찾아서 99로 변경
		 */
		
		int[] num1 = {10, 20, 30, 40};
		num1[2] = 100;
		findAndChange(num1, 20, 99);
		
		printData("num1", num1);
		
		/*
		 * 문제2: num1과 크기가 같은 배열 num1Copy를 만들고
		 * num1Copy에 num 데이터를 복사하고 화면 출력
		 * 주소값 복사인지 깊은복사(물리적복사) 여부 확인
		 */
		int[] num1Copy = new int[4];
		System.arraycopy(num1, 0, num1Copy, 0, 4);
		
		printData("num1Copy", num1Copy);
		
		System.out.println("--- 주소 복사 여부 확인 num1 == num1Copy : " + (num1 == num1Copy));
		
		// 개선된 for문(forEach형태)
		// for (타입 변수명 : 집합객체) {}
		for (int num : num1) {
			System.out.print(num + " ");
		}
		System.out.println();
		
		// ===========================
		// 맨 뒤에 데이터 1개 추가
		// 맨 앞에 데이터 1개 추가
		// 특정 위치에 데이터 추가
		// 특정 위치에 데이터 삭제
		
		int[] num = {11, 12, 13, 14, 15, 16, 17};
		printData("원본", num);
		System.out.println();
		
		System.out.println("데이터 앞에 10을 삽입");
		num = addFront(num, 10);
		printData("num", num);
		System.out.println();
		
		System.out.println("데이터 끝에 18을 삽입");
		num = addBehind(num, 18);
		printData("num", num);
		System.out.println();
		
		System.out.println("2번째 인덱스에 333삽입");
		num = addData(num, 2, 333);
		printData("num", num);
		System.out.println();
		
		System.out.println("앞 데이터 삭제");
		num = deleteFront(num);
		printData("num", num);
		System.out.println();
		
		System.out.println("뒤 데이터 삭제");
		num = deleteBehind(num);
		printData("num", num);
		System.out.println();
		
		System.out.println("1번째 인덱스 333 삭제");
		num = deleteData(num, 1);
		printData("num", num);
	}
	
	static void findAndChange(int[] num, int srcNum, int changeNum) {
		for (int i = 0; i < num.length; i++) {
			if (num[i] == srcNum) {
				num[i] = changeNum;
			}
		}
	}
	
	static int[] addFront(int[] arr, int Data) {
		int[] newArr = addData(arr, 0, Data);
		return newArr;
	}
	
	static int[] addBehind(int[] arr, int Data) {
		int[] newArr = addData(arr, arr.length, Data);
		return newArr;
	}
	
	static int[] addData(int[] arr, int srcNum, int Data) {
		int[] copyData = arr.clone();
		int size = arr.length + 1;
		int [] newArr = new int[size];
		for (int i = 0; i < newArr.length; i++) {
			if (i == srcNum) {
				newArr[i] = Data;
			} else if (i < srcNum) {
				newArr[i] = copyData[i];
			} else if (i > srcNum) {
				newArr[i] = copyData[i-1];
			}
		}
		
		arr = newArr.clone();
		return arr;
	}
	
	static int[] deleteFront(int[] arr) {
		int[] newArr = deleteData(arr, 0);
		return newArr;
	}
	
	static int[] deleteBehind(int[] arr) {
		int[] newArr = deleteData(arr, arr.length - 1);
		return newArr;
	}
	
	static int[] deleteData(int[] arr, int srcNum) {
		int [] copyData = arr.clone();
		int size = arr.length - 1;
		int [] newArr = new int[size];
		for (int i = 0; i <= newArr.length; i++) {
			if (i < srcNum) {
				newArr[i] = copyData[i];
			} else if (i > srcNum){
				newArr[i-1] = copyData[i];
			} else if (i == srcNum) {
				continue;
			}
		}
		
		arr = newArr.clone();
		return arr;
	}
	
	static void printData(String name, int[] num) {
		System.out.print(name + " : ");
		for (int i = 0; i < num.length; i++) {
			System.out.print(num[i] + " ");
		}
		System.out.println();
	}

}
// num1 : 10 99 100 40 
// num1Copy : 10 99 100 40 
// --- 주소 복사 여부 확인 num1 == num1Copy : false
// 10 99 100 40 
// 원본 : 11 12 13 14 15 16 17 
//
// 데이터 앞에 10을 삽입
// num : 10 11 12 13 14 15 16 17 
//
// 데이터 끝에 18을 삽입
// num : 10 11 12 13 14 15 16 17 18 
//
// 2번째 인덱스에 333삽입
// num : 10 11 333 12 13 14 15 16 17 18 
//
// 앞 데이터 삭제
// num : 11 333 12 13 14 15 16 17 18 
//
// 뒤 데이터 삭제
// num : 11 333 12 13 14 15 16 17 
//
// 1번째 인덱스 333 삭제
// num : 11 12 13 14 15 16 17 

2차원 배열

2차원 배열 생성 및 초기화

int[][] nums = { {10, 20}, {30, 40}, {50, 60}, {70, 80}, {90, 100, 110} };

여기서 nums는 2차원 배열이며, 각 배열 요소는 다시 정수 배열을 가리킨다. 배열의 각 행은 다른 길이를 가질 수 있다, 예를 들어 nums[4]는 세 개의 요소를 갖고 있다.

배열 요소 접근 및 출력

  • System.out.println("nums[0][0] : " + nums[0][0]);와 같은 방식으로 특정 요소에 접근해 값을 출력할 수 있다.
  • nums[0], nums[1]과 같이 배열의 특정 행을 참조할 수도 있다.

반복문을 사용한 배열 요소 출력

for (int k = 0; k < nums.length; k++) {
    for (int i = 0; i < nums[k].length; i++) {
        System.out.print(nums[k][i] + " ");
    }
    System.out.println();
}
  • 이중 for문을 사용해 모든 배열 요소를 순회하고 출력하는 방법이 있다. 이 방법은 2차원 배열의 모든 요소를 차례대로 접근할 수 있게 해준다.

향상된 for문을 사용한 배열 요소 출력

for (int[] intArr : nums) {
    for (int num : intArr) {
        System.out.print(num + " ");
    }
    System.out.println();
}
  • 배열의 각 행을 나타내는 int[] 배열에 대한 향상된 for문과, 각 행 내 요소에 대한 향상된 for문을 사용해 모든 값을 출력할 수 있다. 이 방법은 그냥 반복문을 사용하는 것 보다 코드가 간결하다.

예제

package com.mystudy.array4_2dim;

public class ArrayTwoDimension {

	public static void main(String[] args) {
		// 2차원 배열 선언 및 객체 생성
		// 타입[][] 변수명 = new 타입[크기값][크기값];
		// 타입 변수명[][] = new 타입[크기값][크기값];
		// 타입[][] 변수명 = { {10,20}, {..}, {..} };
		int[][] nums = { {10, 20},  // 10 : nums[0][0], nums[0][1]
						 {30, 40},  // 30 : nums[1][0], nums[1][1]
						 {50, 60},
						 {70, 80},
						 {90, 100, 110} };
		System.out.println("nums : " + nums);
		System.out.println("nums[0] : " + nums[0]);
		System.out.println("nums[0][0] : " + nums[0][0]);
		System.out.println("nums[0][1] : " + nums[0][1]);
		System.out.println("-------------");
		
		//배열 데이터 출력
		//{10, 20}
		System.out.println("nums[0][0] : " + nums[0][0]);
		System.out.println("nums[0][1] : " + nums[0][1]);
		System.out.println();
		
		//{30, 40}
		System.out.println("nums[1][0] : " + nums[1][0]);
		System.out.println("nums[1][1] : " + nums[1][1]);
		System.out.println("==============");
		
		//첫번째 배열 데이터 반복문으로 출력
		for (int i = 0; i < 2; i++) {
			System.out.print(nums[0][i] + " ");
		}
		System.out.println();
		
		//두번째 배열 데이터 반복문으로 출력
		for (int i = 0; i < 2; i++) {
			System.out.print(nums[1][i] + " ");
		}
		System.out.println();
		System.out.println("===============");
		System.out.println("==== 이중 for문 사용 배열데이터 출력 ===");
		for (int k = 0; k < nums.length; k++) {
			for (int i = 0; i < nums[k].length; i++) {
				System.out.print(nums[k][i] + " ");
			}
			System.out.println();
		}
		System.out.println("=================");
		for (int[] intArr : nums) {
			System.out.print(intArr + " ");
		}
		System.out.println();
		
		System.out.println("------------");
		for (int num : nums[0]) {
			System.out.print(num + " ");
		}
		System.out.println();
		
		for (int num : nums[1]) {
			System.out.print(num + " ");
		}
		System.out.println();

		System.out.println("============");
		for (int[] intArr : nums) {
			for (int num : intArr) {
				System.out.print(num + " ");
			}
			System.out.println();
		}
		System.out.println();
		
	}

}
nums : [[I@15db9742
nums[0] : [I@6d06d69c
nums[0][0] : 10
nums[0][1] : 20
-------------
nums[0][0] : 10
nums[0][1] : 20

nums[1][0] : 30
nums[1][1] : 40
==============
10 20 
30 40 
===============
==== 이중 for문 사용 배열데이터 출력 ===
10 20 
30 40 
50 60 
70 80 
90 100 110 
=================
[I@6d06d69c [I@7852e922 [I@4e25154f [I@70dea4e [I@5c647e05 
------------
10 20 
30 40 
============
10 20 
30 40 
50 60 
70 80 
90 100 110 

실습예제2

package com.mystudy.array4_2dim;

public class ArrayTwoDimensiton_exam {

	public static void main(String[] args) {
		/*
		 * 문제 : 2차원 배열을 사용하여
		 * int[][] num2dim = { {10, 20}, {30, 40}, {50, 60}};
		 * 1. 2차원 배열 num2dim 값을 확면 출력: 10, 20, 30 ...
		 * 2. num2dim의 세 번재 값 30을 100으로 변경하고 화면 출력
		 * 3. num2dim의 크기만큼 num2Copy 배열변수 선언하고
		 * num ----> num2Copy 데이터 복사 후
		 * num2Copy화면 출력
		 * num2Copy 데이터 복사 형태 확인(주소값 복사아님)
		 */

		int[][] num2dim = { {10, 20}, {30, 40}, {50, 60}};
		printData("num2dim : ", num2dim);
		num2dim[1][0] = 100;
		printData("num2dim : ", num2dim);
		
		int[][] num2Copy = num2dim.clone();
		printData("num2Copy : ", num2Copy);
		
		System.out.println("num2Copy has eqaul address? : " + (num2Copy == num2dim));
		
		
	}
	
	static void printData(String name, int[][] array) {
		System.out.println(name);
		for(int[] intArr : array) {
			System.out.print("{ ");
			for(int num : intArr) {
				System.out.print(num + " ");
			}
			System.out.print("} ");
		}
		System.out.println();
	}

}
num2dim : 
{ 10 20 } { 30 40 } { 50 60 } 
num2dim : 
{ 10 20 } { 100 40 } { 50 60 } 
num2Copy : 
{ 10 20 } { 100 40 } { 50 60 } 
num2Copy has eqaul address? : false

String, StringBuffer, StringBuilder

String, StringBuffer, StringBuilder는 Java에서 문자열을 처리하기 위한 클래스들이다.

String

  • String 클래스는 불변(immutable) 문자열을 나타낸다. 한 번 생성된 String 객체의 내용은 변경할 수 없다. 문자열을 변경하려고 하면 새로운 String 객체가 생성되고 기존 객체는 변경되지 않는다.
  • 메모리 효율성과 성능을 고려하지 않을 때 주로 사용된다. 예를 들어, 문자열 연산이 적고 읽기만 많은 경우에 적합하다.
String str = "Hello";
str = str + " World"; // 새로운 String 객체 생성

StringBuffer

  • StringBuffer는 가변(mutable) 문자열을 나타낸다. 이 클래스는 문자열을 추가, 수정, 삭제할 때 기존 객체를 재사용한다.
  • StringBuffer는 동기화(synchronized)되어 있어서 멀티스레드 환경에서 안전(thread-safe)하다. 여러 스레드가 동시에 StringBuffer 객체에 접근해도 문제가 발생하지 않는다.
  • 하지만 단일 스레드 환경에서는 StringBuilder에 비해 성능이 떨어질 수 있다.
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 기존 객체에 문자열 추가

StringBuilder

  • StringBuilder도 StringBuffer처럼 가변 문자열을 나타내지만, 동기화되어 있지 않다. 그래서 StringBuffer보다 빠른 성능을 제공한다, 특히 단일 스레드 환경에서 유리하다.
  • 멀티스레드 환경에서는 StringBuilder의 사용이 안전하지 않을 수 있다. 여러 스레드가 동시에 접근하면 문제가 발생할 수 있다.
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 기존 객체에 문자열 추가

요약

  • String: 불변, 문자열 연산이 적은 경우에 적합.
  • StringBuffer: 가변, 동기화, 멀티스레드 환경에 적합.
  • StringBuilder: 가변, 비동기화, 단일 스레드 또는 동기화 처리가 필요 없는 환경에 적합.

사용 상황에 맞게 적절한 클래스를 선택해서 사용하는 것이 중요하다!

멀티스레드? 싱글스레드?

단일스레드 (Single-threaded)

  • 정의: 프로그램이나 프로세스가 한 번에 하나의 작업(스레드)만 처리하는 방식이다.
  • 장점:
    • 구현이 간단하고 이해하기 쉽다.
    • 자원 공유에 대한 걱정이 없어서 데이터 일관성과 상태 관리가 쉽다.
  • 단점:
    • 한 번에 하나의 작업만 처리할 수 있어서, 작업이 막히면 전체 프로세스가 지연될 수 있다.
    • CPU의 멀티코어 성능을 충분히 활용하기 어렵다.

멀티스레드 (Multi-threaded)

  • 정의: 프로그램이나 프로세스가 동시에 여러 작업(스레드)을 처리할 수 있는 방식이다.
  • 장점:
    • 여러 작업을 동시에 처리할 수 있어서 효율성과 처리 속도가 향상된다.
    • CPU의 멀티코어 성능을 효과적으로 활용할 수 있다.
  • 단점:
    • 구현이 복잡하고, 자원 공유에 따른 동기화 문제를 신경 써야 한다.
    • 스레드 간의 상호작용과 경쟁으로 인한 문제(예: 데드락)가 발생할 수 있다.

문자열 비교 '==', 'equals()'

'== 연산자 사용'

  • == 연산자는 두 객체의 참조가 같은지 확인해. 즉, 두 객체가 메모리 상에서 동일한 위치를 가리키고 있는지를 검사한다.
  • 문자열의 경우, ==는 두 문자열 객체가 동일한 메모리 주소를 가리키고 있는지 확인한다. 따라서, 문자열의 내용이 아니라 객체의 참조를 비교한다.
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2); // false, 다른 메모리 주소를 가리키고 있기 때문

'equals()'

  • equals() 메소드는 두 객체의 내용이 같은지 확인한다. 이 메소드는 객체 내부의 값을 비교하여 두 객체가 '논리적으로' 동등한지 판단한다.
  • 문자열에서 equals()는 두 문자열의 내용이 같은지를 비교한다. 즉, 두 문자열이 같은 문자들을 순서대로 포함하고 있는지를 확인한다.
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2)); // true, 내용이 같기 때문

결론

  • 문자열의 내용을 비교할 때는 equals() 메소드를 사용해야 해야한다. 이는 문자열이 같은 값을 가지고 있는지 정확하게 판단할 수 있게 해준다.
  • == 연산자는 문자열이 같은 객체를 참조하고 있는지만을 확인하므로, 내용이 같더라도 다른 객체일 경우 false를 반환할 수 있다.

따라서, 일반적으로 문자열의 '값'을 비교할 때는 항상 equals() 메소드를 사용하는 것이 좋다. 이는 문자열뿐만 아니라 다른 객체들을 비교할 때도 마찬가지다. equals() 메소드는 객체의 내용이 동일한지를 정확하게 비교해준다.

예제

package com.mystudy.string;

public class Ex01_String_basic {

	public static void main(String[] args) {
		/*
		 * String 클래스 : 문자열 저장, 처리하기 위한 클래스
		 * String 데이터 자체는 불변임(immutable)
		 * 사용형식 2가지
		 * String str = '홍길동'; // 기본 데이터 타입처럼 사용 가능 - 리터럴(literal) 값
		 * String str = new String("홍길동"); // 객체(인스턴스) 생성
		 */
		String str1 = "홍길동";
		String str2 = "홍길동";
		System.out.println(str1);
		System.out.println(str2);
		System.out.println("str1 == str2 : " + (str1 == str2)); // 동일객체 여부 : true

		// == : 객체(인스턴스, 주소값) 비교(동일 객체 여부 확인)
		String strObj1 = new String("홍길동");
		String strObj2 = new String("홍길동");
		System.out.println("str1 == str2 : " + (strObj1 == strObj2)); // 동일객체 여부 : false
		
		System.out.println("=== 문자열 값 비교 equals() 사용할 것 ===");
		System.out.println("strObj1.equals(strObj2) : " + strObj1.equals(strObj2)); // 동일 문자열 여부 : True
		
	}

}

이 코드에서 str1str2가 동일한 객체를 참조하는 이유는 자바의 문자열 상수 풀(string constant pool) 때문이다. 자바에서 문자열은 String 클래스를 사용하여 표현되며, 문자열 리터럴은 문자열 상수 풀에 저장된다.

문자열 상수 풀은 메모리 내에 유지되는 특별한 저장 영역으로, 모든 문자열 리터럴이 이곳에 저장된다. 자바는 새로운 문자열 리터럴을 생성할 때 먼저 문자열 상수 풀을 확인한다. 이미 상수 풀에 동일한 내용의 문자열이 존재한다면, 새로운 객체를 생성하지 않고 기존의 객체를 재사용한다.

위 코드에서 String str1 = "홍길동"; 이 실행되면, "홍길동"이라는 문자열 리터럴이 문자열 상수 풀에 저장된다. 이어서 String str2 = "홍길동"; 이 실행될 때, 문자열 상수 풀에 이미 "홍길동"이 존재하기 때문에 새로운 객체를 생성하지 않고, str1이 참조하고 있는 같은 객체를 str2에도 할당한다.

따라서 str1 == str2는 true가 된다. 이는 두 참조 변수가 메모리의 동일한 위치, 즉 같은 객체를 가리키고 있음을 의미한다.

이러한 특성은 메모리 사용을 최적화하고, 문자열 처리 효율을 높이기 위한 자바의 설계 방식이다. 하지만, 모든 문자열 연산이 이 방식으로 처리되는 것은 아니다. new 키워드를 사용하여 명시적으로 새로운 String 객체를 생성하면, 문자열 상수 풀과 관계없이 항상 새로운 객체가 메모리에 생성된다.

IgnoreCase

str1 = "JAVA";
str2 = "java";
System.out.println("str1 : " + str1);
System.out.println("str2 : " + str2);
System.out.println("str1.equals(str2) : " + str1.equals(str2));
System.out.println("str1.equalsIgnoreCase(str2) : " + str1.equalsIgnoreCase(str2)); // 대소문자 구문x : True

API

API(Application Programming Interface)는 소프트웨어나 서비스가 다른 프로그램과 상호작용할 수 있게 해주는 인터페이스이다. 쉽게 말해, API는 서로 다른 소프트웨어 시스템들이 서로 대화하고, 데이터를 교환하며, 기능을 공유할 수 있도록 도와준다.

예를 들어, 자바에서는 다양한 API를 제공하여, 개발자가 네트워크 통신, 파일 시스템 접근, 그래픽 인터페이스 구축 등의 기능을 쉽게 구현할 수 있도록 돕는다. 이러한 API는 자바 표준 라이브러리의 일부로서, java.net, java.io, javax.swing 같은 패키지들에 포함되어 있다.

API는 개발자에게 큰 이점을 제공한다.

  1. 시간 절약과 효율성: 이미 잘 만들어진 기능을 재사용할 수 있어서, 개발 시간과 노력을 줄일 수 있다.
  2. 표준화: API는 일관된 방식으로 기능을 사용할 수 있게 하여, 코드의 가독성과 유지 관리가 쉬워진다.
  3. 보안: API를 통해 데이터와 기능에 대한 접근을 제어할 수 있어, 보안성을 강화할 수 있다.

자바에서 API를 사용하는 방법은 간단하다. 필요한 API가 포함된 패키지를 임포트하고, 해당 API의 클래스와 메소드를 활용하여 원하는 기능을 구현하면 된다.

체이닝 기법

체이닝 기법(Chaining)은 여러 메소드 호출이나 함수를 연속적으로 연결하는 프로그래밍 패턴을 말한다. 이 기법은 코드를 간결하고 읽기 쉽게 만들며, 유연하고 효율적인 코드 작성을 가능하게 한다.

자바에서 체이닝 기법은 주로 객체 지향 프로그래밍에서 사용되며, this 키워드를 사용하여 구현된다. 객체의 메소드가 객체 자신(this)을 반환하면, 다른 메소드 호출을 연속해서 수행할 수 있다. 이러한 방식은 주로 빌더 패턴(Builder Pattern), 메소드 체이닝(Method Chaining) 또는 플루언트 인터페이스(Fluent Interface)라고도 불린다.

public class MyClass {
    private int value;

    public MyClass setValue(int value) {
        this.value = value;
        return this;
    }

    public void display() {
        System.out.println("Value: " + value);
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.setValue(10).display();
    }
}

이 코드에서 setValue 메소드는 MyClass의 인스턴스를 반환한다. 따라서 setValue 메소드 호출 후에 바로 display 메소드를 연속적으로 호출할 수 있다. 이러한 체이닝은 코드를 더욱 직관적이고 선언적으로 만들어 준다.

체이닝 기법은 다음과 같은 장점을 가진다.

  • 가독성 향상: 연속된 메소드 호출은 코드를 읽고 이해하기 쉽게 만든다.
  • 유지보수 용이: 메소드를 연쇄적으로 호출함으로써, 코드의 수정 및 확장이 용이해진다.
  • 코드 간결성: 여러 줄에 걸쳐야 할 코드를 한 줄로 간결하게 표현할 수 있다.

하지만, 과도한 체이닝은 코드의 복잡성을 증가시킬 수 있으므로 적절한 균형을 유지하는 것이 중요하다.

예제

String a = "Hello";
String b = "World";
String c = "~~~~~";
		
// chainning 기법(체이닝 기법)
String result = a.concat(b).concat(c).concat("!!!");
System.out.println("a.concat(b).concat(c).concat(\"!!!\") : " + result); // 이스케이프 문자(역슬래쉬)

// a.concat(b).concat(c).concat("!!!") : HelloWorld~~~~~!!!

String str1 = "홍길동";, str1 = "asdf"; ?

String str1 = "홍길동";
str1 = "asdf";

str1 = "asdf";로 선언할 수 있으며, 값이 바뀐다.

자바에서 String 타입의 변수는 참조 타입이기 때문에, 변수는 실제 문자열 데이터가 저장된 메모리의 위치를 가리키는 참조를 저장한다. 초기에 String str1 = "홍길동";으로 선언하면, str1은 메모리 상의 "홍길동"이라는 문자열을 가리키게 된다.

이후 str1 = "asdf";로 선언하면, str1의 참조가 변경되어 이제 "asdf"라는 새로운 문자열을 가리키게 된다. 이전에 "홍길동" 문자열은 여전히 메모리에 존재하지만, str1은 더 이상 그 문자열을 가리키지 않게 된다.

이러한 특성은 문자열 불변성(immutable)과 관련이 있다. 자바에서 String 객체는 한 번 생성되면 그 내용을 변경할 수 없다. 따라서 str1에 새로운 문자열을 할당하면, 기존 문자열을 수정하는 것이 아니라 새로운 문자열 객체가 생성되어 str1에 할당되는 것이다. 기존의 "홍길동" 문자열 객체는 더 이상 참조되지 않게 되며, 가비지 컬렉션에 의해 메모리에서 제거될 수 있다.

그 밖의 메서드

toString()

System.out.println("a : " + a);
System.out.println("a.toString() : " + a.toString());
System.out.println("=========================================");

해당 객체를 문자열로 바꿔준다.

valueOf()

valueOf 메소드는 자바에서 주로 기본 데이터 타입의 값을 문자열(String)로 변환하는 데 사용되는 정적(static) 메소드이다. 이 메소드는 String 클래스 뿐만 아니라, 각각의 래퍼 클래스(Wrapper Class)에서도 제공된다. 래퍼 클래스란 기본 데이터 타입을 객체로 다루기 위한 클래스들을 말하는데, 예를 들어 Integer, Float, Boolean 등이 있다.

String.valueOf 메소드의 주요 기능은 다음과 같다.

  1. 기본 데이터 타입을 문자열로 변환: String.valueOf 메소드는 인자로 받은 기본 데이터 타입(예: int, long, float, double, char, boolean 등)의 값을 문자열로 변환한다. 이는 데이터를 문자열로 쉽게 변환하고자 할 때 유용하다.
  2. 객체를 문자열로 변환: 이 메소드는 객체를 인자로 받아 해당 객체의 toString() 메소드를 호출하여 그 결과를 문자열로 반환한다. 객체가 null인 경우에는 문자열 "null"을 반환한다.
int number = 123;
String strNumber = String.valueOf(number); // "123"으로 변환

boolean flag = true;
String strFlag = String.valueOf(flag); // "true"로 변환

// 래퍼 클래스에서의 valueOf 사용 예
Integer intValue = 123;
String strIntValue = Integer.valueOf(intValue).toString(); // "123"으로 변환

String.valueOf 메소드는 데이터를 문자열로 변환할 때 매우 자주 사용되며, 특히 데이터를 문자열로 로깅하거나 사용자 인터페이스에 표시할 때 유용하다. 또한, 널(null) 값을 안전하게 문자열로 변환할 수 있다는 장점이 있다.

profile
신입사원...

0개의 댓글