[혼공자] 05-2. 배열

Benjamin·2023년 2월 28일
0

혼공자

목록 보기
13/27

05-2. 배열

변수는 1개의 데이터만 저장할 수 있다.
많은 데이터를 적은 코드로 손쉽게 처리할 수 있는 배열에 대해 알아보자.

배열이란?

배열은 같은 타입의 데이터를 연속된 공간에 나열하고, 각 데이터에 인덱스를 부여해놓은 자료구조이다.
배열의 각 인덱스는 각 항목의 데이터를 읽거나 저장하는 데 사용되며 배열 이름 옆에 대괄호[]에 기입된다.
인덱스는 0부터 시작한다.
배열이름[인덱스]

특징

  1. 배열은 같은 타입의 데이터만 저장할 수 있다.
    또한 선언과 동시에 저장할 수 있는 타입이 결정된다. 만약 다른 타입을 저장하려고 하면 타입 불일치 컴파일 에러가 발생한다.

  2. 한 번 생성된 배열은 길이를 늘리거나 줄일 수 없다.
    만약 크기를 늘리고싶으면, 새로운 배열을 생성하고 기존 배열 항목을 새 배열로 복사해야 한다.

배열 선언

배열을 사용하기 위해서는 우선 배열 변수를 선언해야한다.
선언에는 두 가지 방법이 있다.

  1. 형식 1
    타입[] 변수;

  2. 형식 2
    타입 변수[];

대괄호 []는 배열 변수를 선언하는 기호로 사용되는데, 타입 뒤에 붙을 수도있고 변수 뒤에 붙을 수도 있다.
타입은 배열에 저장될 데이터 타입을 말한다.

배열 변수는 참조 변수에 속한다.
배열도 객체이므로 힙 영역에 생성되고 배열 변수는 힙 영역의 배열 객체를 참조한다.
만일 참조할 배열 객체가 없다면 배열 변수는 null 값으로 초기화될 수 있다.
타입[] 변수 = null;

만약 배열 변수가 null값을 가진 상태에서 변수[인덱스]로 값을 읽거나 저장하게 되면 NullPointerException이 발생한다.
배열을 생성하고 배열 변수가 참조하는 상태에서 값을 저장하거나 읽어야한다.

배열 생성

배열 객체를 생성하려면 값 목록을 이용하거나 new 연산자를 이용하는 방법이 있다.

값 목록으로 배열 생성

타입[] 변수 = {값0, 값1, 값2, 값3, ...};

중괄호 {}는 주어진 값들을 항목으로 가지는 배열 객체를 힙에 생성하고, 배열 객체의 번지를 리턴한다.
배열 변수는 리턴된 번지를 저장함으로써 참조가 이루어진다.
인덱스는 0부터 차례로 부여된다.

값의 목록으로 배열 객체를 생성할 때 주의할 점이 있는데, 배열 변수를 이미 선언한 후에는 다른 실행문에서 중괄호를 사용한 배열 생성이 허용되지 않는다는것이다.

타입[] 변수;
변수 = {값0, 값1, 값2, ...}; //컴파일 에러

배열 변수를 미리 선언한 후 값 목록들이 나중에 결정되는 상황이라면 다음과 같이 new 연산자를 사용해서 값 목록을 지정해주면 된다.
new 연산자 바로 뒤에는 배열 변수 선언에서 사용한 "타입[]"를 붙여주고 중괄호 {}에는 값들을 나열해준다.

변수 = new 타입[] {값0, 값1, 값2, ...};

예를들어 names배열을 다음과 같이 생성할 수 있다.

String[] names = null;
names = new String[] {"신용권", "홍길동"};

메소드의 매개값이 배열일 경우에도 마찬가지이다.
아래와 같이 매개 변수로 int[] 배열이 선언된 add() 메소드가 있을 경우, 값 목록으로 배열을 생성함과 동시에 add() 메소드의 매개값으로 사용하고자 할 때는 반드시 new 연산자를 사용해야한다.

int add(int[] scores) {...};

int result = add({95,50}); //컴파일 에러
int result = add(new int[] {95,50});

new 연산자로 배열 생성

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

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

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

new 연산자로 배열을 처음 생성할 경우 배열은 자동적으로 기본값으로 초기화된다.
만약, int타입 배열이라면 모든 값이 0으로, String 타입 배열이라면 모두 null값으로 초기화된다.
String[] names = new String[30];

다음은 타입별로 배열의 초기값을 보여준다.

분류타입초기값
기본 타입(정수)byte[]0
기본 타입(정수)char[]'\u0000'
기본 타입(정수)short[]0
기본 타입(정수)int[]0
기본 타입(정수)long[]0L
기본 타입(실수)float[]0.0F
기본 타입(실수)double[]0.0
기본 타입(논리)boolean[]false
참조타입클래스[]null
참조타입인터페이스[]null
  • \u0000?
    문자형 char 의 기본값인 '\u0000'은 Unicode Character 'NULL'을 의미합니다.

배열이 생성되고 나서 특정 인덱스 위치에 새로운 값을 저장하려면 대입 연산자를 사용하면된다.
변수[인덱스] = 값;

배열 길이

배열의 길이란 배열에 저장할 수 있는 전체 항목의 개수를 말한다.
코드에서 배열의 길이를 얻으려면 배열 객체의 length필드를 읽는다.
참고로 필드는 객체 내부의 데이터를 말한다.

배열의 length 필드를 읽기 위해서는 배열 변수에 도트(.) 연산자를 붙이고 length를 적으면 된다.
배열변수.length;

int[] intArray = {10,20,30};
int num = intArray.length; //3

length 필드는 읽기 전용 필드이기 때문에 값을 바꿀 수 없다.따라서 다음과 같이 작성하면 안된다.
intArray.length = 10;

배열의 length 필드는 for문을 사용해서 배열 전체를 루핑할 때 매우 유용하다.

배열의 인덱스 범위는 0~(길이-1)인데, 만약 인덱스를 초과해서 사용하면 ArrayIndexOutOfBoundsException이 발생한다.

명령 라인 입력

우리는 이제 프로그램 실행을 위해 main()메소드가 필요하다는 것을 알고있다.
하지만 main()메소드의 매개값인 String[] args가 왜 필요할까?
public static void main(String[] args) {...}

명령 라인(명령 프롬프트)에서 위 코드를 java 명령어로 실행하면 JVM은 길이가 0인 String 배열을 먼저 생성하고 main() 메소드를 호출할 때 매개값으로 전달한다.

만약 다음과 같이 공백으로 구분된 문자열 목록을 주고 실행하면, 문자열 목록으로 구성된 String[]배열이 생성되고 main()메소드를 호출할 때 매개값으로 전달된다.

main() 메소드는 String[] args 매개 변수를 통해 명령 라인에서 입력된 데이터의 수(배열의 길이)와 입력된 데이터(배열의 항목 값)를 알 수 있게 된다.

다음 예제는 실행할 때 2개의 문자열을 주지않으면 "값의 수가 부족합니다."를 출력하고 강제 종료한다. 만약 2개의 문자열의 정확하게 입력되었다면 2개의 문자열을 정수로 변환하고 덧셈 연산을 수행한다.

public static void main(String[] args) {
	if(args.length != 2) {
    	System.out.println("값의 수가 부족합니다.");
        System.exit(0);
    }
    
    String strNum1 = args[0]; // 첫 번째 데이터 얻기
    String strNum2 = args[1]; // 두 번째 데이터 얻기
    
    int num1 = Integer.parseInt(strNum1);
    int num2 = Integer.parseInt(strNum2);
    
    int result = num1 + num2;
    System.out.println(result);
}

프로그램 종료 = System.exit()

강제적으로 JVM을 종료시키고 싶을 때는 System 클래스의 exit() 메소드를 호출하면 됩니다. exit() 메소드는 현재 실행하고 있는 프로세스를 강제 종료시키는 역할을 합니다. exit() 메소드는 int 매개값을 지정하도록 되어 있습니다. 이 값을 종료 상태값이라고 합니다. 일반적으로 정상 종료일 경우 0으로 지정하고 비정상 종료일 경우 0이외의 다른값을 줍니다.

  • System.exit(0); //정상종료
  • System.exit(1); //비정상종료

위 예제를 그냥 실행하면 "값의 수가 부족합니다"의 결과를 얻는다. 실행할 때 매개값을 주지않으면 길이 0인 String 배열이 매개값으로 전달되기 때문이다.

  1. 이클립스에서 프로그램을 실행할 때 매개값을 주고 실행하려면 위 예제 코드를 입력하고 [Run] - [Run Configurations] 메뉴를 선택한다.

  2. [Run Configurations] 대화 상자의 [Main] 탭에서 [Project] 와 [Main Class]를 확인한다.

  3. [Arguments] 탭을 클릭하고 [Program arguments] 입력란에 10을 입력한 후 빈 칸을 띄우고 다시 20을 입력한다.
    [Apply]버튼을 클릭한다.

  1. [Run]아이콘을 클릭한다.

이것은 명령 프롬프트에서 다음과 같이 실행하는 것과 동일하다.
java -p bin -m Practice/array.keyCode 10 20 (본인 컴퓨터 폴더구조에 해당)
-> '30'의 출력이 나옴

이렇게 실행하면 args는 {"10","20"}배열을 참조하게 되고 args[0]은 "10", args[1]은 "20"을 얻을 수 있다.
문자열은 산술 연산할 수 없기떄문에, 문자열들을 Integer.parseInt()메소드를 이용해 정수로 변환한다.
int 변수 = Integer.parseInt("정수로 변환 가능한 문자열");

만약 정수로 변환할 수 없는 문자열이 주어졌을 경우 NumberFormatException이 발생한다.

다차원 배열

지금까지 살펴본 배열은 값 목록으로 구성된 1차원 배열이다.
이와 달리 값들이 행과 열로서 구성된 배열을 2차원 배열이라고 한다. 2차원 배열은 수학의 행렬을 떠올리면 되는데 가로 인덱스와 세로 인덱스를 사용한다.

자바는 2차원 배열을 중첩 배열 방식으로 구현한다.

예를 들어, 2(행)x3(열) 행렬을 만들기 위해 다음과 같은 코드를 사용한다.

int[][] scores = new int[2][3];
이 코드는 메모리에 다음과 같이 3개의 배열 객체를 생성한다.

배열 변수인 scores는 길이가 2인 배열 A를 참조한다.
배열 A의 scores[0]은 다시 길이가 3인 배열 B를 참조한다. 그리고 scores[1]은 길이가 3인 배열 C를 참조한다.
scores[0], scores[1]은 모두 배열을 참조하는 변수 역할을 한다.
따라서 각 배열의 길이는 다음과 같이 얻을 수 있다.

scores.length // 2(배열 A의 길이)
scores[0].length // 3(배열 B의 길이)
scores[1].length // 3(배열 C의 길이)

scores[0][1]은 배열 B의 인덱스 1값을 뜻하고, scores[1][0]은 배열C의 인덱스 0 값을 뜻한다.
이처럼 자바는 1차원 배열이 서로 연결된 구조로 다차원 배열을 구현하기 때문에 수학 행렬 구조가 아닌 계단식 구조를 가질 수 있다.

위 코드는 메모리에 다음과 같이 배열 객체를 생성한다.

이 경우 배열 항목의 수를 조사하면 다음과 같다.

scores.length // 2(배열 A의 길이)
scores[0].length // 2(배열 B의 길이)
scores[1].length // 3(배열 C의 길이)

이런 형태의 배열에서 주의할 점은 배열의 정확한 길이를 알고 인덱스를 사용해야 한다는 것이다.

만약 그룹화된 값 목록을 갖고있다면 다음과 같이 중괄호 안에 다시 중괄호를 사용해서 값 목록을 나열한다.

예를들어, 그룹화 된 성적 점수를 이용해서 다음과 같은 배열을 만들 수 있다.
scores[0]에는 {95,80} 배열을 참조하고, scores[1]에는 {92,96,80} 배열을 참조한다.
int[][] scores = {{95,80}, {92,96,80}};

객체를 참조하는 배열

기본 타입(byte,char,short,int, long, float,double, boolean) 배열은 각 항목에 직접 값을 갖고있지만, 참조타입(클래스, 인터페이스) 배열은 각 항목에 객체의 번지를 갖고있다.

예를들어, String은 클래스이므로 String[] 배열은 각 항목에 문자열이 아니라 String 객체의 번지를 가지고있다.
즉 String[] 배열은 String 객체를 참조하게 된다.

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

위 코드는 배열 변수를 선언하고 3개의 문자열을 참조하는 배열을 생성한다.
그림으로 표현하면 다음과 같다.

따라서 String[] 배열의 항목도 결국 String 변수와 동일하게 취급되어야 한다.
예를들어 String[]배열 항목간에 문자열을 비교하기 위해서는 == 연산자 대신 equals() 메소드를 사용해야한다.
==는 객체의 번지를 비교하기 때문에 문자열을 비교하는 데는 사용할 수 없다.

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

System.out.println(strArray[0] == strArray[1]); // true (같은 객체를 참조)
System.out.println(strArray[0] == strArray[2]); // false (다른 객체를 참조)
System.out.println(strArray[0].equals(strArray[2])); // true (문자열이 동일)

위 코드르 실행하면 메모리에 다음과 같이 배열 객체가 생성된다.

new 연산자로 객체를 생성하면 무조건 새로운 객체가 생성되기 때문에 strArray[0]와 strArray[2]를 ==연산하면 false가 나온다.

배열 복사

배열은 한 번 생성하면 크기를 변경할 수 없기때문에 더 많은 저장공간이 필요하다면 더 큰 배열을 새로 만들고 이전 배열로부터 항목 값들을 복사해야한다.
배열 간의 항목 값들을 복사하려면 for문을 사용하거나 System.arraycopy() 메소드를 사용한다.

System.arraycopy() 메소드를 사용해서 배열을 복사해보자.
호출방법은 다음과 같다.
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

src 매개값은 원본 배열이고, srcPos는 원본 배열에서 복사할 항목의 시작 인덱스이다.
dest 매개값은 새 배열이고, destPos는 새 배열에서 붙여넣을 시작 인덱스이다.
length는 복사 할 개수이다.

복사 코드를 작성했다고 생각하고 살펴보자.

예를들어 아래와 같은 상황이 발생한다면, 복사되지 않은 항목은 배열의 타입 기본 초기값이 그대로 유지된다.(int의 경우 0, String의 경우 null)

참조 타입 배열이 복사되면 복사되는 값이 객체의 번지이므로 새 배열의 항목은 이전 배열의 항목이 참조하는 객체와 동일하다.

향상된 for문

자바는 배열이나 컬렉션을 좀 더 쉽게 처리하기 위해 향상된 for문을 제공한다.
이는 반복 실행을 하기 위해 루프 카운터 변수와 증감식을 사용하지 않는다. for문의 괄호 ()에는 배열에서 꺼낸 항목을 저장할 변수 선언과 콜론(:) 그리고 배열을 나란히 작성한다.
배열 및 컬렉션 항목의 개수만큼 반복하고, 자동적으로 for문을 빠져나간다.

다음은 작성형식과 실행흐름이다.

  1. for문이 처음 실행될 때 배열(1)에서 가져올 첫번째 값이 존재하는지 평가한다.
  2. 가져올 값이 존재하면 해당 값을 변수(2)에 저장한다.
  3. 실행문(3)을 실행한다.
  4. 블록 내부의 실행문(3)이 모두 실행되면 다시 루프를 돌아 배열(1)에서 가져 올 다음 값이 존재하는지 평가한다.
  5. 만약 다음 항목이 존재하면 2->3->4 순서로 루프를 다시 진행하고, 가져올 다음 항목이 없으면 for문이 종료된다.

따라서 for문의 반복 횟수는 배열의 항목수가 된다.


출처
혼자 공부하는 자바

0개의 댓글