JAVA

배열, 향상된 for문

seungmin576 2024. 12. 18. 17:09

배열

학교에 학생이 100명이 있다.

학생들 100명의 학번을 어떻게 관리할 수 있을까?

학번의 자료형을 정수라고 하면 학생이 100명일 때 int studentID1, int studentID2 ...... int studentID100 이렇게 변수를 100개씩 선언해서 사용할 수 있지만 학번에 대한 여러 개의 변수들을 일일이 쓰는 것은 너무 힘들다.

이럴때 사용하는 자료형이 배열(array)이다.

배열은 자료 구조의 가장 기초 내용이다.

배열을 사용하면 자료형이 같은 자료 여러 개를 한 번에 관리할 수 있다.

배열은 자료가 연속으로 나열된 자료 구조이다.

 


배열 선언과 초기화

배열을 사용하려면 먼저 배열을 선언해야한다.

배열도 변수와 마찬가지로 자료형을 함께 선언한다.

 

  • 배열을 선언하는 문법
자료형[] 배열 이름 = new 자료형[개수];
자료형 배열 이름[] = new 자료형[개수];

배열을 이루는 각각의 자료를 배열 요소라고 한다.

배열 요소는 자료형이 모두 같다.

먼저 저장하려는 자료의 성격에 맞게 자료형을 정하고 선언하려는 배열 요소 개수만큼 [ ] 안에 적는다.

new 예약어는 배열을 새로 만들라는 의미이다.

 

  • 앞에서 이야기한 학생들의 학번을 배열로 선언하면
int[] studentIDs = new int[10];
int studentIDs[] = new int[10];

위 문장은 int형 요소가 10개인 배열을 선언한 것이다.

 

  • 위처럼 선언했을 때 메모리 상태

배열을 선언하면 선언한 자료형과 배열 길이에 따라 메모리가 할당된다.

위 그림을 보면 자료형이 int형이므로 배열 요소를 저장할 수 있는 공간의 크기는 전부 4바이트로 동일하다.

배열 요소를 저장할 수 있는 공간이 총 10개이므로 이 배열을 위해 총 40바이트의 메모리가 할당 되는 것이다.

 

배열 초기화하기

자바에서 배열을 선언하면 그와 동시에 각 요소의 값이 초기화된다.

배열의 자료형에 따라 정수는 0, 실수는 0.0, 객체 배열은 null로 초기화되며, 다음처럼 배열 선언과 동시에 특정값으로 초기화할 수도 있다.

 

  • 배열이 초기화 요소의 개수만큼 생성되므로 [ ] 안의 개수는 생략한다.
int[] studentIDs = new int[] {101, 102, 103};

 

 

  • 다음과 같이 값을 넣어 초기화할 때 [ ] 안에 개수를 쓰면 오류가 발생한다.
int [] studentIDs = new int[3] {101, 102, 103}; //오류 발생

 

 

  • 선언과 동시에 초기화할 때 다음과 같이 new int[ ] 부분을 생략할 수도 있다. int형 요소가 3개인 배열을 생성한다는 의미이므로 new int [ ] 를 생략해도 된다.
int studentIDs = {101, 102, 103};	//int형 요소가 3개인 배열 생성

 

 

  • 배열의 자료형을 먼저 선언하고 초기화하는 경우에는 new int [ ] 를 생략할 수 없다.
int [] studentIDs;	//배열형 자료형 선언
studentID = new int[] {101, 102, 103};	//new int[]를 생략할 수 없음

배열 사용하기

선언한 배열의 각 요소에 값을 넣을 때나 배열 요소에 있는 값을 가져올 때는 [ ]를 사용한다.

 

  • 만약 배열의 첫 번째 요소에 값 10을 저장한다면 다음처럼 코드를 작성한다.
studentIDs[0] = 10;	//배열의 첫 번째 요소에 값 10을 저장

 

인덱스 연산자 [ ]

[ ] 는 배열을 처음 선언할 때 사용한 연산자이다.

배열 이름에 [ ] 를 사용하는 것을 인덱스 연산이라고 한다.

인덱스 연산자의 기능은 배열 요소가 저장된 메모리 위치를 찾아 주는 역할이다.

배열에서 [ i ] 인덱스 연산을 하면 i번째 요소의 위치를 찾아 해당 위치의 메모리에 값을 넣거나 이미 저장되어 있는 값을 가져와서 사용할 수 있다.

 

  • 예를 들어 int형으로 선언한 num 배열의 네 번째 요소에 값 25를 저장하고, 그 값을 가져와 int형 변수 age에 저장한다면 다음 그림과 같다.

  • 배열의 물리적 위치와 논리적 위치는 같다.

물리적 위치란 배열이 메모리에서 실제 저장되는 곳을 의미하며, 논리적 위치란 이론상 배열 위치를 의미한다.

배열은 요소를 10개를 선언하면 사용하는 실제 값도 바로 이웃한 메모리에 놓인다.

즉 '5 다음에 10이 있다'는 논리적 순서와 실제 메모리를 살펴보면 값 4가 놓인 메무리 주소에서 4바이트(int형 크기) 다음 메모리 주소에 값 10이 놓인다.

 

배열 순서는 0번부터

배열 길이(처음에 선언한 배열 전체 요소 개수)가 n이라고 하면, 배열 순서는 0번부터 n-1번까지 이다.

0번 요소를 배열의 첫 번째 요소라고 한다.

 

  • 정수 10개를 저장할 배열을 선언하고 각 요소를 값 1부터 10까지 초기화한 후 for 반복문을 사용하여 배열 요소 값을 하나씩 출력
package array;

public class ArrayTest {
    public static void main(String[] args){
        int[] num = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        for(int i = 0; i < num.length; i++){
            System.out.println(num[i]);
        }
    }
}

자바의 배열은 배열 길이를 나타내는 length 속성을 가진다.

자바에서 배열 길이는 처음에 선언한 배열의 전체 요소 개수를 의미한다.

전체 길이를 알고 싶은 배열 이름 뒤에 도트연산자를 붙이고 length 속성을 쓰면 배열 길이를 반환한다.

 

전체 배열 길이와 유효한 요소 값

우리가 배열을 사용할 때 처음 선언한 배열 길이만큼 값을 저장해서 사용하는 경우는 많지 않다.

따라서 전체 배열 길이와 현재 배열에 유효한 값이 저장되어 있는 배열 요소 개수가 같다고 혼동하면 안된다.

 

package array;

public class ArrayTest2 {
    public static void main(String[] args){
        double[] data = new double[5];

        data[0] = 10.0;
        data[1] = 20.0;
        data[2] = 30.0;

        for (int i = 0; i < data.length; i++){
            System.out.println(data[i]);
        }
    }
}

double 형으로 길이가 5인 배열을 선언했다.

자바에서 정수 배열과 실수 배열을 별도로 초기화하지 않고 선언하면 배열의 요소 값은 0으로 초기화된다.

7~9행을 보면 배열의 첫번째 요소(data[0])부터 세 번째 요소(date[2])까지만 값을 저장했다.

11행 for문에서 i가 0부터 배열 길이인 data.length 미만까지 반복하며 배열에 저장된 요소 값을 출력한다.

배열의 네 번째 요소와 다섯 번째 요소에는 값을 저장하지 않았기 때문에 0이 출력된다.

즉 배열의 세번째 요소까지만 유효한 값이 저장된 것이다.

만약 유효한 값이 저장된 배열 요소만 정확히 출력하려면 새로운 변수를 선언하고 배열 요소 순서대로 값을 저장할 때마다 그 변수 값을 증가시킨다.

그리고 반복문 종료 조건으로 배열의 length 속성이 아닌 해당 변수를 사용한다.

 

  • 유효한 값이 저장된 배열 요소까지만 출력하는 프로그램
package array;

public class ArrayTest2 {
    public static void main(String[] args){
        double[] data = new double[5];
        int size = 0;

        data[0] = 10.0; size++;
        data[1] = 20.0; size++;
        data[2] = 30.0; size++;

        for (int i = 0; i < size; i++){
            System.out.println(data[i]);
        }
    }
}

6행에 유효한 값이 저장된 배열 요소 개수를 저장할 size 변수를 선언했다.

배열 요소에 순서대로 값을 저장할 때마다 size 변수의 값을 하나씩 증가시킨다.

즉 유효한 값을 저장하고 있는 배열 요소 개수를 알 수 있다.

따라서 12행 반복문은 전체 배열 길이만큼 반복하는 게 아니라 유효한 요소 개수만큼만 반복한다.


문자 저장 배열 만들기

문자 자료형 배열을 만들고 알파벳 대문자를 A부터 Z까지 저장한 후 각 요소 값을 알파벳 문자와 정수 값(아스키 코드 값)으로 출력해 본다.

 

package array;

public class CharArray {
    public static void main(String[] args){
        char[] alphabets = new char[26];
        char ch = 'A';

        for(int i = 0; i < alphabets.length; i++, ch++){
            alphabets[i] = ch;
        }

        for(int i = 0; i < alphabets.length; i++){
            System.out.println(alphabets[i] + ", " + (int)alphabets[i]);
        }
    }

}

 

5행에서 대문자 알파벳 26개를 저장하기 위해 문자형 배열을 선언하고, 8행에서 for문을 사용해 각 배열 요소에 알파벳 문자를 저장하였다.

각 알파벳 문자는 실제 메모리에 아스키 코드 값으로 저장되기 때문에 ch 값에 1을 더하면(ch++) 1만큼 증가한 값이 배열에 저장된다.

12행의 for문은 alphabets 배열에 저장된 알파벳 문자와 그 문자에 해당하는 아스키 코드 값을 반복하여 출력한다.


객체 배열 사용하기

참조 자료형으로 선언하는 객체 배열에 대해 알아본다.

동일한 기본 자료형(int 등) 변수 여러 개를 배열로 사용할 수 있듯이 참조 자료형 변수도 여러 개를 배열로 사용할 수 있다.

객체 배열은 int나 char 등 기본 자료형 배열과 사용 방법이 조금 다르다.

 

  • Book 클래스를 만들고 이 클래스로 객체 배열을 만들어 보기
package array;

public class Book {
    private String bookName;
    private String author;

    Book(){}

    public Book(String bookName, String author){
        this.bookName = bookName;
        this.author = author;
    }

    public String getBookName(){
        return bookName;
    }
    public void setBookName(String name){
        bookName = name;
    }

    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }

    public void showInfo(){
        System.out.println(bookName + ", " + author);
    }
}

Book 클래스는 책 이름과 저자를 멤버 변수로 가지는 클래스이다.

디폴트 생성자 외에도 책 이름과 저자 이름을 매개변수로 받는 생성자를 하나 더 구현했다.

다른 코드에서 이 클래스를 사용할 때 멤버 변수 값을 가져오거나 지정할 수 있도록 get(), set() 메서드도 구현한다.

마지막으로 책의 정보를 출력해 주는 showBookInfo() 메서드까지 구현했다.

 

  • 도서관에 책이 5권 있다고 가정하고 Book 클래스를 사용하여 책 5권을 객체 배열로 만든다.
package array;

public class BookArray {
    public static void main(String[] args){
        Book[] library = new Book[5];

        for(int i = 0; i < library.length; i++){
            System.out.println(library[i]);
        }
    }
}

5행 Book[ ] library = new Book[5]; 문장의 내용을 보면 인스턴스 5개가 생성된 것처럼 보인다.

하지만  Book 인스턴스 5개가 바로 생성되는 것은 아니다.

그러면 이때 만들어지는 것은 무엇이냐?

인스턴스를 생성하면 그 인스턴스를 가리키는 주소 값이 있다.

5행의 문장은 각각의 Book 인스턴스 주소 값을 담을 공간 5개를 생성하는 문장이다.

즉 이 문장을 실행하면 다음 그림처럼 Book 주소 값을 담을 공간이 5개 만들어지고 자동으로 각 공간은 비어 있다는 의미의 null 값으로 초기화 된다.

 

library[0]

null
library[1]

null
library[2]

null
library[3]

null
library[4]

null

 

  • 각 배열 요소에 인스턴스를 생성해 넣어 보기. Book 클래스에 구현한 생성자를 사용함
package array;

public class BookArray2 {
    public static void main(String[] args){
        Book[] library = new Book[5];

        library[0] = new Book("태백산맥", "조정래");
        library[1] = new Book("데미안", "헤르만 헤세");
        library[2] = new Book("어떻게 살 것인가", "유시민");
        library[3] = new Book("토지", "박경리");
        library[4] = new Book("어린왕자", "생텍쥐페리");

        for(int i = 0; i < library.length; i++){
            library[i].showInfo();
        }
        for(int i = 0; i < library.length; i++){
            System.out.println(library[i]);
        }
    }
}

배열의 각 요소에 Book 인스턴스를 만들어 직접 저장했다.

16 17행의 출력 내용은 각 배열 요소가 가지고 있는 인스턴스 주소 값이다.


배열 복사하기

기존 배열과 자료형 및 배열 크기가 똑같은 배열을 새로 만들거나 배열의 모든 요소에 자료가 꽉 차서 더 큰 배열을 만들어 기존 배열에 저장된 자료를 가져오려 할 때 배열을 복사한다.

 

  • 배열을 복사하는 방법

1. 기존 배열과 배열 길이가 같거나 더 긴 배열을 만들고 for문을 사용하여 각 요소 값을 반복해서 복사하는 방법.

2. System.arraycopy() 메서드를 사용하는 방법이다.

 

System.arraycopy(src, srcPos, dest, destPos, length) 메서드에서 각 매개변수의 의미는 다음과 같다.

매개변수 설명
src 복사할 배열 이름
srcPos 복사할 배열의 첫 번째 위치
dest 복사해서 붙여 넣을 대상 배열 이름
destPos 복사해서 대상 배열에 붙여 넣기를 시작할 첫 번째 위치
length src에서 dest로 자료를 복사할 요소 개수

 

  • System.arraycopy() 메서드를 사용한 예제
package array;

public class ArrayCopy {
    public static void main(String[] args){
        int[] array1 = {10, 20, 30, 40, 50};
        int[] array2 = {1, 2, 3, 4, 5};

        System.arraycopy(array1, 0, array2, 1, 4);
        for(int i = 0; i < array2.length; i++){
            System.out.println(array2[i]);
        }
    }
}

array1 배열에서 array2 배열로 요소 값을 복사한다.

array1 배열의 요소 0번부터 4개를 복사해서 대상 배열 array2의 요소 1번부터 붙여 넣는다.

출력 결과를 보면 array2 배열의 첫 번째 요소 값인 1을 제외하고 나머지 요소 값만 변경된 것을 알 수 있다.

이때 복사할 대상 배열의 전체 길이가 복사할 요소 개수보다 작다면 오류가 난다.

 

객체 배열 복사하기

package array;

public class ObjectCopy1 {
    public static void main(String[] args){
        Book[] bookArray1 = new Book[3];
        Book[] bookArray2 = new Book[3];

        bookArray1[0] = new Book("귀찬항", "자바");
        bookArray1[1] = new Book("그러게", "스프링");
        bookArray1[2] = new Book("아닌데?", "DB");
        System.arraycopy(bookArray1, 0, bookArray2, 0, 3);

        for (int i = 0; i < bookArray2.length; i++) {
            bookArray2[i].showInfo();
        }
    }
}

잘 복사되었으나 의문이 생긴다.

bookArray2 배열의 인스턴스를 따로 만들지 않았는데 각 요소 값이 잘 출력된다.

객체 배열을 사용하려면 꼭 인스턴스를 생성해서 넣어야 한다고 했는데, 이 경우는 어떻게 된 것일까?

 

얕은 복사

앞 예제에서 배열을 복사해 출력하기 전 bookArray1 배열 요소 값 하나를 변경

package array;

public class ObjectCopy2 {
    public static void main(String[] args) {
        Book[] bookArray1 = new Book[3];
        Book[] bookArray2 = new Book[3];

        bookArray1[0] = new Book("귀찬항", "자바");
        bookArray1[1] = new Book("그러게", "스프링");
        bookArray1[2] = new Book("아닌데?", "DB");
        System.arraycopy(bookArray1, 0, bookArray2, 0, 3);

        for (int i = 0; i < bookArray2.length; i++) {
            bookArray2[i].showInfo();
        }

        bookArray1[0].setBookName("이쿠");
        bookArray1[0].setAuthor("리신");

        System.out.println("=== bookArray1 ===");
        for (int i = 0; i < bookArray1.length; i++) {
            bookArray1[i].showInfo();
        }

        System.out.println("=== bookArray2 ===");
        for (int i = 0; i < bookArray2.length; i++) {
            bookArray2[i].showInfo();
        }
    }
}

출력 화면을 보면 17 18행에서 bookArray1 배열 요소 값을 변경했는데 bookArray2 배열 요소 값도 변경됐다.

그 이유는 객체 배열의 요소에 저장된 값은 인스턴스 자체가 아니고 인스턴스의 주소 값이기 때문이다.

따라서 객체 배열을 복사할 때 인스턴스를 따로 생성하는 게 아니라 기존 인스턴스의 주소 값만 복사한다.

결국 두 배열의 서로 다른 요소가 같은 인스턴스를 가리키고 있으므로 복사되는 배열의 인스턴스 값이 변경되면 두 배열 모두 영향을 받는 것이다.

 

이와 같은 복사를 주소 값만 복사한다고 해서 '얕은 복사' 라고 한다.

 

깊은 복사

인스턴스를 따로 관리하고 싶다면 직접 인스턴스를 만들고 그 값을 복사해야 한다.

이를 깊은 복사라고 한다.

 

다음 예제는 복사할 배열에 인스턴스를 따로 생성한 후 요소 값을 복사한다.

이렇게 하면 복사한 배열 요소는 기존 배열 요소와 서로 다른 인스턴스를 가리키므로 기존 배열의 요소값이 변경되어도 영향을 받지 않는다.

package array;

public class ObjectCopy3 {
    public static void main(String[] args) {
        Book[] bookArray1 = new Book[3];
        Book[] bookArray2 = new Book[3];

        bookArray1[0] = new Book("귀찬항", "자바");
        bookArray1[1] = new Book("그러게", "스프링");
        bookArray1[2] = new Book("아닌데?", "DB");

        bookArray2[0] = new Book();
        bookArray2[1] = new Book();
        bookArray2[2] = new Book();

        for (int i = 0; i < bookArray1.length; i++) {
            bookArray2[i].setBookName(bookArray1[i].getBookName());
            bookArray2[i].setAuthor(bookArray1[i].getAuthor());
        }

        for (int i = 0; i < bookArray2.length; i++) {
            bookArray2[i].showInfo();
        }

        bookArray1[0].setBookName("이쿠");
        bookArray1[0].setAuthor("리신");

        System.out.println("===== bookArray1 ========");
        for (int i = 0; i < bookArray1.length; i++) {
            bookArray1[i].showInfo();

        }

        System.out.println("======= bookArray2 =======");
        for (int i = 0; i < bookArray2.length; i++) {
            bookArray2[i].showInfo();

        }
    }
}


향상된 for문과 배열

자바 5부터 제공되는 향상된 for문(enhanced for loop)은 배열의 처음에서 끝까지 모든 요소를 참조할 때 사용하면 편리한 반복문이다.

향상된 fro문은 배열 요소 값을 순서대로 하나씩 가져와서 변수에 대입한다.

따로 초기화와 종료 조건이 없기 때문에 모든 배열의 시작 요소부터 끝 요소까지 실행한다.

//for(변수 : 배열){
//	반복 실행문;
//}

package array;

public class EnhancedForLoop {
    public static void main(String[] args){
        String[] strArray = {"자바", "안드로이드", "C", "자바스크립트", "파이썬"};

        for(String lang : strArray){
            System.out.println(lang);
        }
    }
}

 

향상된 for문을 사용해서 String형 lang 변수에 strArray 배열 요소 값을 순서대로 가져와 대입한다.

lang 변수를 출력하면 strArray 배열에 저장된 값이 순서대로 출력된다.

증가하는 인덱스 값이 필요하다면 사용할 수 없다.

그럴 땐 그냥 for 문의 i를 선언하고 i를 활용해야함.

'JAVA' 카테고리의 다른 글

ArrayList  (0) 2024.12.19
다차원 배열  (0) 2024.12.18
싱글톤 패턴  (1) 2024.12.17
변수 유효 범위  (0) 2024.12.17
static 변수  (0) 2024.12.16