Java Category/Java

[JAVA] 배열(Array)

ReBugs 2022. 12. 27.

혼자 공부하는 자바 (저자 : 신용권)의 내용을 개인적으로 정리하는 글임을 알립니다.


참조 타입과 메모리 관련된 설명이 많이 나옵니다.

참조 타입과 메모리와의 연관성을 모르시면 아래의 글을 읽고 오시는 것을 추천드립니다.

2022.12.26 - [Language/JAVA] - [JAVA] 참조 타입과 참조 변수

 

[JAVA] 참조 타입과 참조 변수

데이터 타입은 기본 타입과 참조 타입으로 나눠지며, 기본 타입은 이전에 정리한 바가 있다. 2022.12.22 - [Language/JAVA] - [JAVA] 기본 타입 [JAVA] 기본 타입 변수를 선언할 때 주어지는 타입에 따라 변수

rebugs.tistory.com


배열 개요

배열은 같은 타입의 데이터를 연속된 공간(메모리)에 나열하고, 각 데이터에 인덱스를 부여해 놓은 자료구조이다.

  • 배열은 같은 타입의 데이터만 저장할 수 있다.
  • 한 번 생성된 배열은 길이를 늘이거나 줄일 수 없다.

배열은 객체이므로 힙 영역에 생성되고, 배열 변수는 힙 영역의 배열 객체를 참조하게 된다.

배열 선언

배열 변수 선언은 다음과 같이 두 가지이다.

  1. 타입[ ] 변수;
  2. 타입 변수 [ ];
//1번째 배열 변수 선언 방법
int[] intArray;
double[] doubleArray;
String[] strArray;
//2번째 배열 변수 선언 방법
int intArray[];
double doubleArray[];
String strArray[];

 

배열 생성

배열 객체를 생성하려면 다음과 같은 방법이 있다.

  1. 값 목록으로 배열 생성
  2. new 연산자로 배열 생성

값 목록으로 배열 생성

타입[ ] 변수 = { 값1, 값2, 값3, 값4, 값5 } 와 같은 방법으로 배열 객체를 생성할 수 있다.

String[] Value = {"값0", "값1", "값2", "값3", "값4"};

위에서 말했듯이 Value라는 배열 변수는 "값 0~4"를 가지는 배열 객체를 참조하고 있는 것이다.

이해를 위해 임의로 주소를 100으로 설정한 것이다. 실제는 다르다.

※주의※
배열 변수를 이미 선언한 후에는 다른 실행문에서 중괄호를 사용한 배열 생성이 허용되지 않음
int[] value;
value = {1, 2, 3}; //자바에선 이런 문법을 허용하지 않음​
배열 변수를 미리 선언한 후 값 목록들이 나중에 결정되는 상황이라면 new연산자를 사용해서 값 목록을 지정해 줘야 함

메소드의 매개 변수값이 배열일 경우에도 마찬가지로 값 목록으로 배열을 생성함과 동시에 메소드의 매개값으로 사용하고자 할 때는 new 연산자를 사용해야 한다.
int add(int[] scores) {...}
int result = add({95, 85 ,90}); //컴파일 에러
int result = add(new int[]{95, 85,90}); //new 연산자를 이용한 값 목록 지정

아래의 예제를 참고하자
package TestPakage;
public class Test {
	public static void main(String[] args) {
		int[] scores;
		scores = new int[] { 83, 90, 87 };
		int sum1 = 0;
		for(int i=0; i<3; i++) {
			sum1 += scores[i];
		}
		System.out.println("총합 : " + sum1);	
		
		int sum2 = add( new int[] { 83, 90, 87 } );
		System.out.println("총합 : " + sum2);	
		System.out.println();
	}
	public static int add(int[] scores) {
		int sum = 0;
		for(int i=0; i<3; i++) {
			sum += scores[i];
		}
		return sum;
	}
}
/*
총합 : 260
총합 : 260
*/​

 

new 연산자로 배열 생성

값의 목록을 가지고 있지 않지만, 향후 값들을 저장할 배열을 미리 만들고 싶다면 new 연산자로 다음과 같이 배열 객체를 생성할 수 있다.

타입[ ] 변수 = new 타입[길이];

길이는 배열이 저장할 수 있는 값의 개수를 말함. 이미 배열 변수가 선언된 경우에도 new 연산자로 배열을 생성할 수 있다.

//기본적 new연산자로 배열 생성
int[] intArray = new int[5];
//값 목록 생성으론 안되었던 문법
int[] intArray;
intArray = new int[5]; //new연산자를 사용하면 컴파일 에러 안남

new 연산자로 배열을 처음 생성할 경우 배열은 자동적으로 기본값으로 초기화된다.

분류 타입 초기값
기본 타입(정수) byte[ ]
char[ ]
short[ ]
int[ ]
long[ ]
0
'\u0000'
0
0
0L
기본 타입(실수) float[ ]
double[ ]
0.0F
0.0
기본 타입(논리) boolean[ ] false
참조 타입 클래스[ ] null
인터페이스[ ] null
package TestPakage;
public class Test {
	public static void main(String[] args) {
		int[] arr1 = new int[3];
		for(int i=0; i<3; i++) {
			System.out.println("arr1[" + i + "] : " + arr1[i]);
		}
		arr1[0] = 10;
		arr1[1] = 20;
		arr1[2] = 30;
		for(int i=0; i<3; i++) {
			System.out.println("arr1[" + i + "] : " + arr1[i]);
		}
		
		double[] arr2 = new double[3];
		for(int i=0; i<3; i++) {
			System.out.println("arr2[" + i + "] : " + arr2[i]);
		}
		arr2[0] = 0.1;
		arr2[1] = 0.2;
		arr2[2] = 0.3;
		for(int i=0; i<3; i++) {
			System.out.println("arr2[" + i + "] : " + arr2[i]);
		}
		
		String[] arr3 = new String[3];
		for(int i=0; i<3; i++) {
			System.out.println("arr3[" + i + "] : " + arr3[i]);
		}
		arr3[0] = "1월";
		arr3[1] = "2월";
		arr3[2] = "3월";
		for(int i=0; i<3; i++) {
			System.out.println("arr3[" + i + "] : " + arr3[i]);
		}
	}
}
/*
arr1[0] : 0
arr1[1] : 0
arr1[2] : 0
arr1[0] : 10
arr1[1] : 20
arr1[2] : 30
arr2[0] : 0.0
arr2[1] : 0.0
arr2[2] : 0.0
arr2[0] : 0.1
arr2[1] : 0.2
arr2[2] : 0.3
arr3[0] : null
arr3[1] : null
arr3[2] : null
arr3[0] : 1월
arr3[1] : 2월
arr3[2] : 3월
*/

 

배열 길이

int[ ] arr = {1,2,3,4,5}; 에서 arr의 길이는 5개인 것을 쉽게 알 수 있다.

하지만 데이터가 너무 많아서 알기 힘든 경우는 분명히 생긴다. 이때 유용한 것이 length필드이다.

int[] array = {1,2,3,4,5};
System.out.println(array.length); //5

밑은 응용 예제

package TestPakage;
public class Test {
	public static void main(String[] args) {
int[] scores = { 83, 90, 87 };
		
		int sum = 0;
		for(int i=0; i<scores.length; i++) {
			sum += scores[i];
		}
		System.out.println("총합 : " + sum);
		
		double avg = (double) sum / scores.length;
		System.out.println("평균 : " + avg);
	}
}
/*
총합 : 260
평균 : 86.66666666666667
*/

 

다차원 배열

지금까지 살펴본 배열은 1차원 배열이다. 이와 달리 값들이 행과 열로서 구성된 배열을 2차원 배열이라고 한다. 2차원 배열은 수학의 행렬을 떠올리면 된다.

2차원 배열은 실무에서 자주 사용되지만, 3차원 이상은 실무에서 잘 쓰이지 않을뿐더러 매우 복잡하므로 다루지 않겠다.

int[][] scores = new int[2][3];

위의 코드는 2행 3열의 행렬을 생성하고 scores 변수가 참조하도록 한 것이다.

이를 스택과 힙 영역에서 보면 아래와 같다.

 

위 그림에서 보는 것과 같이 메모리 상에는 2행 3열의 행렬이 생성되는 것이 아니라 1차원 배열 3개가 생성되고

길이가 2인 배열이 각각 길이가 3인 배열 객체의 주소값을 참조하는 형식이다.

 

이처럼 자바는 1차원 배열이 서로 연결된 구조로 다차원 배열을 구현하기 때문에 수학 행렬 구조가 아닌 계단식 구조를 가질 수 있음.

 

위 코드는 아래와 같이 메모리에 배열 객체를 생성

 

 

위와 같은 계단식 배열을 아무 생각 없이 for문을 돌리면 인덱스 오류가 난다.

각 1차원 배열의 정확한 길이를 알고 코딩하는 것이 중요한데, length 필드를 이용하면 편하게 할 수 있다

 

package TestPakage;
public class Test {
	public static void main(String[] args) {
		int[][] mathScores = new int[2][3];
		for(int i=0; i<mathScores.length; i++) {
			for(int k=0; k<mathScores[i].length; k++) {
				System.out.println("mathScores["+i+"]["+k+"]="
						+mathScores[i][k]);
			}
		}
		System.out.println();
		
		int[][] englishScores = new int[2][];
		englishScores[0] = new int[2];
		englishScores[1] = new int[3];
		for(int i=0; i<englishScores.length; i++) {
			for(int k=0; k<englishScores[i].length; k++) {
				System.out.println("englishScores["+i+"]["+k+"]=" + englishScores[i][k]);
			}
		}
		System.out.println();		
		
		int[][] javaScores = { {95, 80}, {92, 96, 80} };
		for(int i=0; i<javaScores.length; i++) {
			for(int k=0; k<javaScores[i].length; k++) {
				System.out.println("javaScores["+i+"]["+k+"]="
						+javaScores[i][k]);
			}
		}
	}
}
/*
mathScores[0][0]=0
mathScores[0][1]=0
mathScores[0][2]=0
mathScores[1][0]=0
mathScores[1][1]=0
mathScores[1][2]=0

englishScores[0][0]=0
englishScores[0][1]=0
englishScores[1][0]=0
englishScores[1][1]=0
englishScores[1][2]=0

javaScores[0][0]=95
javaScores[0][1]=80
javaScores[1][0]=92
javaScores[1][1]=96
javaScores[1][2]=80
*/

 

다차원 배열은 new연산자 말고도 값 목록으로도 생성할 수 있다.

int[][] scores = {{95,80},{92,96}}; //값 목록을 이용한 2행 2열 배열 생성

 

객체를 참조하는 배열

기본 타입(byte, int등등) 배열은 각 항목에 직접 값을 갖고 있지만, 참조타입(클래스, 인터페이스) 배열은 각 항목에 객체의 번지를 가지고 있다. 예를 들어 String은 클래스이므로 String[]배열은 각 항목에 문자열이 아니라, String 객체의 번지를 가지고 있다. 즉 String[] 배열은 String 객체를 참조하게 된다.

String[] array = new String[3];
array[0] = "Java";
array[1] = "C++";
array[2] = "C#";

위 코드는 배열 변수 array를 선언하고 3개의 문자열을 참조하는 배열을 생성

그림으로 표현하면 아래와 같다

 

자바에서 String은 new연산자로 생성한 것이 아니고 문자열이 같다면 같은 객체를 참조하게 되어있다.

String[] array = new String[3];
array[0] = "Java";
array[1] = "Java";
array[2] = new String("Java");

따라서 문자열 자체를 비교하려면 equals()메소드를 사용하여 비교해야 한다.

package TestPakage;
public class Test {
	public static void main(String[] args) {
		String[] strArray = new String[3];
		strArray[0] = "Java";
		strArray[1] = "Java";
		strArray[2] = new String("Java");

		System.out.println( strArray[0] == strArray[1]);
		System.out.println( strArray[0] == strArray[2] );    
		System.out.println( strArray[0].equals(strArray[2]) );
	}
}
/*
true
false
true
*/

 

배열 복사

배열을 복사하는 방법은 3가지이다.

  1. for문을 이용하여 복사(참조 복사)
  2. System.arraycopy() 메소드를 이용하여 복사(참조 복사)
  3. clone() 메소드를 이용하여 복사(배열 자체 복사)

 

for문을 이용하여 복사

package TestPakage;
public class Test {
	public static void main(String[] args) {
		int[] oldIntArray = { 1, 2, 3 };
		int[] newIntArray = new int[5];
		
		for(int i=0; i<oldIntArray.length; i++) {
			newIntArray[i] = oldIntArray[i];
		}
		
		for(int i=0; i<newIntArray.length; i++) {
			System.out.print(newIntArray[i] + ", ");
		}
	}
}
/*
1, 2, 3, 0, 0, 
*/

 

System.arraycopy() 메소드를 이용하여 복사

System.arraycopy() 메소드의 매개 값은 다음과 같다

System.arraycopy(원본 배열, 원본 배열의 복사를 시작할 인덱스, 대상배열, 대상 배열에 복사를 할 인덱스, 몇 개를 복사할지)무슨 소리인지 모르겠다면 아래 예제를 참고하면 된다.

package TestPakage;
public class Test {
	public static void main(String[] args) {
		String[] oldStrArray = { "java", "array", "copy" };
		String[] newStrArray = new String[5];
		
		System.arraycopy( oldStrArray, 0, newStrArray, 0, oldStrArray.length);
		
		for(int i=0; i<newStrArray.length; i++) {
			System.out.print(newStrArray[i] + ", ");
		}
	}
}
/*
java, array, copy, null, null, 
*/

 

배열을 복사하는 for문을 이용한 방법과 System.arraycopy() 모두 복사되는 값이 객체의 주소이므로 새 배열의 항목은 이전 배열의 항목이 참조하는 객체와 동일하다.

 

clone() 메소드를 이용하여 복사

public class Main{
	public static void main(String[] args) {
		int []a = {1,2,3,4,5};
		int []b = a.clone();
		
		b[3] = 0;
		
		System.out.print("a = ");
		for(int i = 0; i < 5; ++i)
		{
			System.out.print(" " + a[i]);
		}
		System.out.println();
		System.out.print("b = ");
		for(int i = 0; i < 5; ++i)
		{
			System.out.print(" " + b[i]);
		}
	}
}
/*
a =  1 2 3 4 5
b =  1 2 3 0 5
*/

위 코드와 같이 '배열변수이름.clone()' 으로 함수를 호출할 수 있으며, 배열을 복제하고 복제한 배열에 대한 참조를 생성한다.

clone() 메소드를 이용한 복제는 단순히 객체의 주소만 복사하는 것이 아니라, 배열 자체를 복사하고 복사된 배열의 주소값을 리턴한다는 것이다.

즉, a와 b는 서로 다른 배열 객체의 주소값을 가리키고 있는 것이다.

하지만 다차원 배열의 복제는 최상위 1레벨만 수행한다. 아래 코드에서 c[0]과 c[1]만 복제되고, 그 아래 레벨의 배열은 복제되지 않고 공유된다.

int [][] c = {{1,2,3,4}, {5,6,7}}; //2차원 배열
int [][] d = c.clone(); //2차원 배열의 복제

public class Main{
	public static void main(String[] args) {
		int[] a = {1,2,3,4}; //1차원 배열
		int[] b = a.clone(); //1차원 배열의 복제
		int [][] c = {{1,2,3,4}, {5,6,7}}; //2차원 배열
		int [][] d = c.clone(); //2차원 배열의 복제
		
		System.out.println(a == b); //a와 b가 같은 객체를 참조하는지 비교
		System.out.println(c == d); //c와 d가 같은 객체를 참조하는지 비교
		for(int i = 0; i < c.length; ++i)
		{
			for(int j = 0; j < c[i].length; ++j)
			{
				System.out.println(c[i][j] == d[i][j]); //각 배열의 요소가 같은 객체를 참조하는지 비교
			}
		}
		
	}
}
/*
false
false
true
true
true
true
true
true
true
*/

위 코드의 실행결과와 마찬가지로 배열변수 c와 d의 최상위 1레벨 참조는 다르지만 그 아래 레벨의 배열의 참조는 같은 것을 확인할 수 있다.

따라서 다차원 배열을 복사하기 위해서는 for문을 돌리면서 일일이 복사해주어야 한다.

'Java Category > Java' 카테고리의 다른 글

[JAVA] 객체 지향 프로그래밍(OOP)  (0) 2022.12.29
[JAVA] 열거 타입(enum)  (0) 2022.12.28
[JAVA] 참조 타입과 참조 변수  (0) 2022.12.26
[JAVA] 연산자  (2) 2022.12.25
[JAVA] 시스템 입출력  (0) 2022.12.24

댓글