목차
- 배열의 복사
1-1. 얕은 복사
1-2. 깊은 복사
1-3. 향상된 for문 - 배열의 정렬
2-1. 순차정렬
2-2. Arrays.sort() 사용
1. 배열 복사
1-1. 얕은 복사(shallow copy)
- stack 영역에서 레퍼런스 변수간에 배열 주소값 복사만 발생한 경우를 말한다.
- 같은 주소값을 참조하는 레퍼런스 변수가 하나 더 생겼을 뿐이다.
int[] originArr = {1, 2, 3, 4, 5};
int[] copyArr = originArr;
stack | heap | static |
originArr (주소값 0x123) copyArr (주소값 0x123) |
{1, 2, 3, 4, 5} (주소값 0x123) |
- 레퍼런스 주소값 확인을 통해 원본과 복사본이 동일한 주소를 가지고 있음을 알 수 있다. 주소값 확인은 hashCode() 메소드를 활용한다!
System.out.println(originArr.hashCode()); → 123961122
System.out.println(copyArr.hashCode()); → 123961122
- 따라서 저장된 주소값을 가지고 있는 배열의 내용을 수정하게 되면, 원본과 복사본 레퍼런스 변수 모두 변경된 값을 반영한다. 배열 값 출력은 for문을 활용한다!
copyArr[0] = 99;
System.out.print(originArr[i] + " "); → 99 2 3 4 5
System.out.print(copyArr[i] + " "); → 99 2 3 4 5
얕은 복사의 활용
A. 메소드 매개변수에서 전달인자로 활용
- 같은 주소를 참조하는 레퍼런스 변수 sarr이 생겨난 예시이다.
- print 메소드에 sarr이라는 매개변수를 설정했으며, 다시 main 메소드로 돌아가 students를 전달인자로 보냈다.
- 이렇게 출력했을 때 students와 sarr은 같은 hashCode를 가진다.
students hashCode : 123961122
sarr의 hashCode : 123961122
김학생 이학생 박학생
- main 메소드에 print(students);를 작성함으로써 print 메소드에서 설정해둔 출력문들을 실행한 것이다.
package com.reminder.array;
public class ArrayCopy01 {
public static void main(String[] args) {
String[] students = {"김학생", "이학생", "박학생"};
System.out.println("students hashCode : " + students.hashCode());
print(students);
}
public static void print(String[] sarr) {
System.out.println("sarr의 hashCode : " + sarr.hashCode());
for(int i=0; i < sarr.length; i++) {
System.out.print(sarr[i] + " ");
}
System.out.println();
}
}
B. 리턴값으로 활용
- getSeasons() 메소드에서 seasons을 리턴값으로 내보낸 예시이다.
- main 메소드의 리턴값 fourSeaons 또한 seasons와 같은 주소값을 가진다.
package com.reminder.array;
public class ArrayCopy02 {
public static void main(String[] args) {
String[] fourSeasons = getSeasons();
System.out.println();
System.out.println("리턴 받은 fourSeasons의 hashCode : " + fourSeasons.hashCode());
}
public static String[] getSeasons() {
String[] seasons = new String[] {"봄", "여름", "가을", "겨울"};
System.out.println("seasons hasshCode : " + seasons.hashCode());
for(int i=0; i < seasons.length; i++) {
System.out.print(seasons[i] + " ");
}
return seasons;
}
}
seasons hasshCode : 123961122
봄 여름 가을 겨울
리턴 받은 fourSeasons의 hashCode : 123961122
stack | heap | static |
③ String[] getSeasons 메소드 String[] seasons (주소값 0x123) ② main 메소드 String[] fourSeasons = getSeasons(); (주소값 0x123) ① main 메소드 |
{"봄", "여름", "가을", "겨울"} (주소값 0x123) |
- 이렇듯 메소드 매개변수와 전달인자로 활용되는 경우, 그리고 리턴 받은 결과값의 경우 얕은 복사가 일어남을 알 수 있다.
- 얕은 복사는 사실 사용자(개발자)가 직접 활용할 일은 상대적으로 드물다. 위와 같은 과정에서 생겨난 것이 얕은 복사라는 정도만 이해하면 된다.
✅ 얕은 복사의 개념을 이해할 수 있다.
✅ 메소드 호출 인자나 리턴값으로 얕은 복사를 활용할 수 있다.
1-2. 깊은 복사(deep copy)
- heap 영역에서 새로운 배열 객체를 생성해 기존 데이터를 복사하는 행위이다.
- 서로 같은 값을 가지지만, 각각 별개의 배열이기에 변경사항이 연동되거나 영향을 주지 않는다.
- 깊은 복사는 사용자(개발자)가 직접 활용하게 되는 경우들을 말한다. 작업 중 배열 길이가 짧아 공간이 모자라게 됐거나, 원본을 유지하되 다른 정렬이 필요할 때 등등을 가정해 볼 수 있다.
깊은 복사 방법
- 깊은 복사 방법에는 총 4가지가 있다.
- 원본 배열을 아래와 같이 할당 및 초기화 했을 때를 예시로 살펴보겠다.
int[] originArr = new int[] {10, 20, 30, 40, 50};
====================
iarr hashCode : 123961122
10 20 30 40 50
A. for문을 이용
- new 연산자를 이용해 heap 영역에 필요한 길이의 새로운 배열을 만들 수 있다. 즉, 사용자(개발자)가 복사본 배열의 길이를 필요에 따라 조절할 수 있다.
- 반복문에서 조건식은 i < originArr.length;임에 주의하자. 복사하고자 하는 기준은 어디까지나 원본 배열에 있기 때문이다. 만일 이를 copyArrA 배열만큼으로 설정하면 범위를 벗어났다는 ArrayIndexOutOfBoundsException 오류 메시지가 나타난다.
int[] copyArrA = new int[10];
for(int i=0; i < originArr.length; i++) {
copyArr1[i] = originArr[i];
}
====================
iarr hashCode : 942731712
10 20 30 40 50 0 0 0 0 0
- 출력 결과 originArr.length 만큼을 모두 복사한 뒤 나머지 길이는 int형의 기본값으로 채워진 것을 확인할 수 있다.
- 중요한 건 originArr과 copyArr1이 참조하고 있는 주소값이 다른 별개의 배열이라는 점이다. 여기서는 원본 배열의 데이터가 변경되더라도 복사본 값은 변함이 없게 된다.
for문으로 깊은 복사 | |
stack | heap |
originArr (주소값 0x123) copyArr1 (주소값 0x456) |
{1, 2, 3, 4, 5} (주소값 0x123) {1, 2, 3, 4, 5, 0, 0, 0, 0, 0} (주소값 0x456) |
B. Object의 clone()을 이용한 복사
- 똑같은 값, 똑같은 길이로만 복제할 수 있다.
- clone()은 원본 배열과 같은 배열밖에 만들 수 없다는 특징이 있다.
- 완벽한 복제본이지만 참조하고 있는 주소값은 전혀 다르다. 즉, 원본 배열과는 별개의 배열이 heap 영역에 새롭게 생겨났음을 말한다.
int[] copyArrB = originArr.clone();
print(copyArrB);
====================
iarr hashCode : 971848845
10 20 30 40 50
C. System의 arraycopy()를 이용한 복사
- 순수 배열의 복사를 위해 만들어진 arraycopy() 메소드는 가장 효율적인 깊은 복사 방법이다.
- new 연산자를 이용해 heap 영역에 필요한 길이의 새로운 배열을 만들 수 있다.
int[] copyArrC = new int[10];
- 원본배열, 복사를 시작할 인덱스, 복사본 배열, 복사를 시작할 인덱스, 복사할 길이를 지정할 수 있다.
❗ System.arraycopy(src, srcPos, dest, destPos, length);
src → 원본 배열
srcPos → 원본 배열 복사 시작 index
dest → 복사본 배열
destPos → 복사본 배열 복사 시작 index
length → 복사할 길이
- 원본 배열 0번 index부터 복사본 배열 3번 index에 붙여넣도록 한다면 아래와 같다.
System.arraycopy(originArr, 0, copyArrC, 3, originArr.length);
print(copyArrC);
====================
iarr hashCode : 1910163204
0 0 0 10 20 30 40 50 0 0
D. Arrays의 copyOf()를 이용한 복사
- 가장 간결하고 유연한 깊은 복사 방식이 바로 copyOf() 메소드이다.
- Arrays.class 파일을 살펴보면 사실 copyOf도 System.arraycopy() 메소드를 적용하고 있다. 해당 기능들을 참조하여 보다 간편한 구성을 갖춘 것이다.
❗ Arrays.copyOf(original, newLength);
original → 원본 배열
newLength → 복사본 배열의 길이
int[] copyArrD = Arrays.copyOf(originArr, 10);
print(copyArrD);
====================
iarr hashCode : 305623748
10 20 30 40 50 0 0 0 0 0
- 시작 index부터 원하는 길이만큼만 복사한다. 즉, 시작 index가 정해져 있어 사용자(개발자)가 임의로 복사 시작 index를 지정할 수 없다.
- 다만, 원본 배열 길이보다 적은 값을 복사하겠다고 입력할 경우 뒤에 원본 배열 데이터가 더 남아있다 하더라도 지정한 길이만큼만 복사된다: Arrays.copyOf(originArr, 3); → 10 20 30
✅ 깊은 복사의 개념을 이해할 수 있다.
✅ 깊은 복사 방법 4가지를 숙지하고 적용할 수 있다.
1-3. 향상된 for문
- 향상된 for문은 배열 index에 하나씩 차례로 접근해 담겨 있는 값을 임시로 사용할 변수에 복사하고, 반복문을 실행한다.
- 이처럼 반복 출력할 값을 가진 대상이 있을 때 사용 가능하다.
- 배열, 컬렉션 등 index에 순차적으로 접근해 담긴 값들을 임시 변수로 복사, 출력해오는 기능만 가진 것이다.
❗ for(임시변수 : 반복할값) {}
for(int i : arr) {
i += 10;
}
- 위와 같이 작성할 경우 i[0]의 값은 11이 되었지만, arr[0]의 값은 그대로 1이다.
- 즉, 배열이 가진 index의 값을 직접 변경하지는 못한다.
package com.reminder.array;
public class ArrayCopy04 {
public static void main(String[] args) {
int[] arr1 = new int[] {1, 2, 3, 4, 5};
int[] arr2 = new int[] {1, 2, 3, 4, 5};
/* for문 */
for(int i=0; i < arr1.length; i++) {
arr1[i] += 10;
System.out.print(arr1[i] + " ");
}
System.out.println();
/* 향상된 for문 */
for(int i : arr2) {
i += 10;
}
for(int i : arr2) {
System.out.print(i + " ");
}
}
}
11 12 13 14 15
1 2 3 4 5
- 일반 for문은 배열의 index에 직접 접근해 10씩 증가한 값을 출력했다. 반대로 향상된 for문은 index를 복사해 임시 변수로 끌어왔기 때문에 값 증가가 반영되지 않았음을 확인할 수 있다.
- 이와 같은 이유에서 향상된 for문은 값을 변경 아닌 사용하는 목적을 위해서라면 일반 for문보다 간결하게 사용할 수가 있다.
✅ 향상된 for문을 이해하고 활용할 수 있다.
2. 정렬
2-1. 순차정렬
스왑(swap)
- 변수나 배열에서 가진 index 값별로 위치를 바꾸는 것을 말한다.
- 스왑을 위해서는 임시 변수를 만들고 → 기존 값을 따로 보관한 뒤에 → 대입 작업을 마친다.
int[] arr = {2, 1, 3};
int tmp;
tmp = arr[0];
arr[0] = arr[1];
arr[1] = tmp;
for(int num : arr) {
System.out.print(num + " ");
}
====================
1 2 3
- 순차정렬이란, 배열의 처음과 끝을 탐색하며 순차대로 정렬하는 가장 기초적인 정렬 알고리즘이다.
- 순서가 뒤섞인 숫자나 알파벳 등을 더 작은 값(왼) → 더 큰 값(오)으로 자리하도록 만드는 예시이다.
- for문을 이용해 index 값을 각각 [i]와 [j]로 명시한다.
- 외부 반복문에서 for(int i=1; i < carr.length; i++) 초기식은 int i=1;이다. index [0]은 저보다 왼쪽에 있는 비교 대상이 없으므로 생략한 셈이다.
- 내부 반복문 for(int j=0; j < i; j++)의 경우 index가 증가할 때마다 0~[i]-1까지의 값을 비교한다. 처음 인덱스부터 기준 인덱스 -1 자리까지를 대소비교 하는 것이다.
- 기준값은 carr[i]로, [i]보다 앞에 있는 index가 [j]인데 여기서 carr[i] < carr[j]인 경우 [j]가 오른쪽으로 밀려나게끔 설정한 것이다.
iarr[i] < iarr[j] 오름차순
iarr[i] > iarr[j] 내림차순
- 값 위치 변경은 임시 변수인 char tmp;를 활용한다.
package com.reminder.array;
import java.util.Arrays;
public class ArraySort02 {
public static void main(String[] args) {
char[] carr = {'d', 'e', 'a', 'f', 'b', 'c', 'g'};
for(int i=1; i < carr.length; i++) {
for(int j=0; j < i; j++) {
if(carr[i] < carr[j]) {
char tmp;
tmp = carr[i];
carr[i] = carr[j];
carr[j] = tmp;
}
}
}
for(int i=0; i < carr.length; i++) {
System.out.print(carr[i] + " ");
}
System.out.println();
}
}
a b c d e f g
- 정렬은 이밖에도 선택정렬, 버블정렬, 삽입정렬 등 다양한 종류가 있다.
2-2. Arrays.sort() 메소드 활용
- 정렬 기능은 Arrays 클래스에 속한 기본 메소드를 활용해 쉽게 구현할 수가 있다.
package com.reminder.array;
import java.util.Arrays;
public class ArraySort02 {
public static void main(String[] args) {
char[] carr = {'d', 'e', 'a', 'f', 'b', 'c', 'g'};
Arrays.sort(carr);
System.out.println("carr : " + Arrays.toString(carr));
}
}
carr : [a, b, c, d, e, f, g]
- Arrays.sort();를 통해 정렬한다.
- 출력 시에는 Arrays.toString()라고 명시한다.
- 이처럼 기본 제공되는 기능을 활용할 수 있는 만큼 정렬이 실행되는 과정에 대해서만 이해하도록 하자.
✅ 변수에 저장된 두 값을 교환할 수 있다.
✅ 배열 인덱스에 저장된 값들을 순차정렬 할 수 있다.
'Java' 카테고리의 다른 글
[JAVA] 6-1. 클래스, 사용자 정의 자료형 (0) | 2021.12.29 |
---|---|
[자바/수업 Quiz] 배열 (0) | 2021.12.28 |
[JAVA/수업 과제 practice] 배열 | 다차원 배열 Lv. 1~2 (0) | 2021.12.27 |
[JAVA] 5-1. 배열의 선언, 할당, 초기화 (0) | 2021.12.27 |
[자바의 정석] Ch 4. 제어문 예제 응용 학습 (0) | 2021.12.26 |