Java

Java - Array - /Array vs. Class / array의 크기, 초기화, 기본값 / 2차원 배열과 다차원 배열 / Jagged array 가변 배열

wy-family 2024. 11. 22. 01:08

클래스 구조

개념

클래스는 사용자 정의 데이터 타입으로, 데이터(필드)와 데이터를 다루는 메서드(기능)를 포함합니다. 객체 지향 프로그래밍의 핵심이며, 데이터를 구조화하고 기능을 캡슐화하는 데 사용됩니다.

특징

  1. 데이터와 메서드의 조합:
    • 클래스는 여러 데이터 필드와 메서드를 포함할 수 있습니다.
    • 예: name, age와 같은 데이터와 이를 조작하는 메서드를 포함.
  2. 다양한 데이터 타입:
    • 클래스는 여러 데이터 타입의 필드를 포함할 수 있습니다.
    • 예: String, int, double, 심지어 다른 클래스도 포함 가능.
  3. 객체 단위로 동작:
    • 클래스는 객체를 생성하여 사용하며, 객체는 클래스의 인스턴스입니다.
  4. 구조화된 접근:
    • 클래스 내부의 필드와 메서드는 접근 제한자(private, public)로 보호할 수 있습니다.

예제

// 클래스 구조 예제
class Person {
    String name;  // 필드
    int age;      // 필드

    // 메서드
    void introduce() {
        System.out.println("안녕하세요, 제 이름은 " + name + "이고, 나이는 " + age + "살입니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Person(); // 객체 생성
        person.name = "Alice";
        person.age = 25;
        person.introduce(); // 메서드 호출
    }
}

배열 구조

개념

배열은 동일한 데이터 타입의 요소를 연속된 메모리 공간에 저장하는 구조입니다. 데이터를 간단히 나열하고 반복적으로 사용할 때 유용합니다.

특징

  1. 단일 데이터 타입:
    • 배열의 모든 요소는 동일한 데이터 타입이어야 합니다.
    • 예: int[], String[].
  2. 고정 크기:
    • 배열은 생성 시 크기가 고정되며, 변경할 수 없습니다.
  3. 순서 있는 데이터:
    • 배열의 요소는 0부터 시작하는 인덱스를 통해 접근 가능합니다.
  4. 단순한 데이터 저장:
    • 복잡한 기능보다는 동일 타입의 데이터를 저장하고 반복적으로 처리하는 데 초점.

예제

// 배열 구조 예제
public class Main {
    public static void main(String[] args) {
        int[] numbers = new int[5]; // 크기가 5인 배열 생성
        numbers[0] = 10;
        numbers[1] = 20;
        numbers[2] = 30;
        numbers[3] = 40;
        numbers[4] = 50;

        // 배열 요소 출력
        for (int i = 0; i < numbers.length; i++) {
            System.out.println("numbers[" + i + "] = " + numbers[i]);
        }
    }
}

클래스와 배열의 차이

특징클래스 구조배열 구조

데이터 타입 여러 데이터 타입 포함 가능 (다양한 필드) 단일 데이터 타입만 저장 가능
크기 객체의 필드 크기는 동적으로 결정 배열 크기는 고정됨
기능 데이터와 이를 처리하는 메서드 포함 단순 데이터 저장 및 반복 처리
사용 목적 복잡한 데이터 모델링 (객체 지향 프로그래밍) 동일 타입 데이터의 대량 저장 및 간단한 처리
메모리 구조 객체는 동적으로 메모리 할당 메모리 공간이 연속적이며 크기가 고정됨
접근 방법 필드와 메서드로 구조화된 접근 가능 인덱스를 통해 순서대로 접근 가능

클래스와 배열의 조합

클래스와 배열을 함께 사용할 수도 있습니다. 예를 들어, Person 객체를 저장하는 배열을 만들 수 있습니다.

예제

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    void introduce() {
        System.out.println("안녕하세요, 제 이름은 " + name + "이고, 나이는 " + age + "살입니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        Person[] people = new Person[3]; // Person 객체 배열 생성
        people[0] = new Person("Alice", 25);
        people[1] = new Person("Bob", 30);
        people[2] = new Person("Charlie", 20);

        // 배열의 각 객체 접근
        for (Person person : people) {
            person.introduce();
        }
    }
}

요약

  • 클래스는 데이터를 구조화하고 기능을 포함하여 복잡한 데이터와 로직을 처리하는 데 적합합니다.
  • 배열은 동일한 타입의 데이터를 간단히 저장하고 처리할 때 유용합니다.
  • 복잡한 프로그램에서는 클래스와 배열을 조합하여 효율적으로 데이터를 관리합니다.

근데 array 에서, int[] a; 라고 하면 몇개의 int의 값을 담을지 정하지 않을 수도 있는건데, 왜 너는 크기가 정해진다고 말했어?

int[] a;와 같은 선언만으로는 배열의 크기를 정하지 않습니다. 이 코드는 단지 배열 변수를 선언한 것이고, 실제 배열 객체를 생성하지는 않았어요. 배열의 크기가 정해지는 순간은 배열 객체를 생성하는 시점입니다. 이 과정은 new 키워드를 사용하거나 초기화를 통해 이루어집니다.

* array도 어쨌든 object 객체로 생각되기 때문에, new 를 통해서 객체 생성을 해야 하고, 그걸 가리키는 것.


배열의 크기가 정해지는 시점

1. 배열 선언 (크기 미정)

int[] a; // 배열 변수를 선언. 아직 메모리를 할당하지 않음.
  • 이 코드는 단순히 배열 변수를 선언한 것입니다.
  • 아직 배열의 크기도, 값을 담을 공간도 존재하지 않습니다.
  • a는 null 상태입니다.

2. 배열 생성 (크기 결정)

a = new int[5]; // 크기가 5인 배열 생성.
  • 이 코드는 배열 객체를 생성하고, 크기를 5로 설정합니다.
  • 메모리에 크기가 고정된 배열 공간이 할당됩니다.
  • 이때 배열의 크기는 고정되고, 이후 변경할 수 없습니다.

3. 선언과 동시에 생성 (크기 결정)

int[] a = new int[5]; // 선언과 동시에 크기를 5로 설정.
  • 위 두 단계를 한 줄로 결합한 코드입니다.
  • 크기를 명시하지 않으면 컴파일 에러가 발생합니다.

왜 배열은 크기가 고정된다고 말했는가?

배열의 특성상, 생성 시점에서 크기가 반드시 정해지고, 이후에는 변경할 수 없기 때문입니다. 배열은 연속적인 메모리 공간을 사용하므로, 생성할 때 필요한 메모리 크기를 명확히 알아야 합니다.

예를 들어:

int[] a = new int[5];
a[0] = 1;
a[1] = 2;
// a[5] = 10; // 컴파일 에러 또는 런타임 에러 (배열 크기를 초과)

위에서 배열의 크기가 고정되었기 때문에, 인덱스 5에 접근하려고 하면 에러가 발생합니다.


동적으로 크기를 조정하려면?

만약 크기를 동적으로 변경할 필요가 있다면, 배열 대신 **리스트(ArrayList)**를 사용하는 것이 일반적입니다. 리스트는 크기가 동적으로 늘어나거나 줄어드는 특징을 가지고 있습니다.

예제: ArrayList 사용

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1); // 크기 1 증가
        list.add(2); // 크기 1 증가
        System.out.println(list); // 출력: [1, 2]
    }
}

요약

  • 배열: 크기가 고정. 생성 시점에 크기를 반드시 정해야 함.
  • int[] a;는 단지 선언이며, 크기를 정하지 않습니다. 배열 객체를 생성하는 시점에 크기가 고정됩니다.
  • 동적 크기가 필요하면 ArrayList와 같은 컬렉션을 사용하는 것이 적합합니다.

배열의 고정된 크기와 리스트의 동적 크기 특성을 이해하면, 적절한 상황에서 올바른 데이터 구조를 선택할 수 있습니다!

 

* array의 크기는 length 를 통해서 구할 수 있음.

int[] a = new int[5];

라고 하고, a.length 를 출력해보면, 5가 나옴.


array를 초기화하는 방법은?

Java에서 배열을 초기화하는 방법은 여러 가지가 있습니다. 배열을 선언한 후 값을 지정하거나, 선언과 동시에 초기화할 수 있습니다. 아래에 배열 초기화의 주요 방법들을 자세히 설명하겠습니다.


1. 배열 선언 후 개별 초기화

배열을 먼저 선언한 뒤, 배열의 각 요소를 개별적으로 초기화하는 방법입니다.

예제

int[] numbers = new int[5]; // 크기 5의 배열 선언
numbers[0] = 10; // 첫 번째 요소 초기화
numbers[1] = 20; // 두 번째 요소 초기화
numbers[2] = 30; // 세 번째 요소 초기화
numbers[3] = 40; // 네 번째 요소 초기화
numbers[4] = 50; // 다섯 번째 요소 초기화

결과

  • numbers 배열의 값은 [10, 20, 30, 40, 50].

2. 배열 선언과 동시에 값으로 초기화

배열을 선언하면서 동시에 초기값을 지정하는 방법입니다.

예제

int[] numbers = {10, 20, 30, 40, 50}; // 선언과 동시에 초기화

특징

  • 배열 크기를 따로 지정할 필요가 없습니다. 초기화한 값의 개수에 따라 배열 크기가 결정됩니다.
  • 위 코드는 int[] numbers = new int[]{10, 20, 30, 40, 50};의 축약 형태입니다.

3. 배열 선언 후 new를 사용한 초기화

new 키워드를 사용하여 배열을 선언하고 값을 초기화할 수 있습니다.

예제

int[] numbers = new int[]{10, 20, 30, 40, 50}; // new를 사용한 초기화

특징

  • 배열의 크기를 명시하지 않고 값만 지정합니다.
  • 선언과 초기화가 분리되지 않을 때는 {}를 직접 사용할 수 없으므로, 반드시 new 키워드를 사용해야 합니다.

4. 기본값으로 초기화

배열을 선언하고 크기만 지정하면, 배열의 각 요소는 기본값으로 초기화됩니다.

기본값

  • int, long, short, byte: 0
  • float, double: 0.0
  • char: '\u0000' (널 문자)
  • boolean: false
  • 객체 타입 (String, 사용자 정의 클래스 등): null

예제

int[] numbers = new int[5]; // 크기 5의 배열 선언
System.out.println(numbers[0]); // 출력: 0 (기본값)

5. 반복문을 이용한 초기화

반복문을 사용하여 배열을 동적으로 초기화할 수 있습니다.

예제

int[] numbers = new int[5];
for (int i = 0; i < numbers.length; i++) {
    numbers[i] = i * 10; // 0, 10, 20, 30, 40으로 초기화
}

결과

  • numbers 배열의 값은 [0, 10, 20, 30, 40].

6. 다차원 배열 초기화

(1) 선언 후 초기화

int[][] matrix = new int[2][3]; // 2행 3열 배열 선언
matrix[0][0] = 1;
matrix[0][1] = 2;
matrix[0][2] = 3;
matrix[1][0] = 4;
matrix[1][1] = 5;
matrix[1][2] = 6;

(2) 선언과 동시에 값 초기화

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6}
};

(3) 가변 길이 배열 초기화

int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[]{1, 2};       // 1행에 2개의 열
jaggedArray[1] = new int[]{3, 4, 5};    // 2행에 3개의 열
jaggedArray[2] = new int[]{6};          // 3행에 1개의 열

7. 스트림 API를 사용한 초기화

Java 8 이상에서는 Stream API를 사용하여 배열을 초기화할 수 있습니다.

예제

import java.util.stream.IntStream;

int[] numbers = IntStream.range(0, 5).toArray(); // 0부터 4까지의 값을 배열로 생성

결과

  • numbers 배열의 값은 [0, 1, 2, 3, 4].

요약

방법코드특징
선언 후 개별 초기화 numbers[0] = 10; 간단하지만 코드가 길어질 수 있음
선언과 동시에 값으로 초기화 int[] numbers = {10, 20, 30}; 초기화 값을 직접 명시 가능
new를 사용한 초기화 new int[]{10, 20, 30}; 선언과 분리 시 사용 가능
기본값 초기화 int[] numbers = new int[5]; 기본값(0, 0.0, null 등)으로 초기화
반복문 초기화 for (int i = 0; i < n; i++) 동적 초기화, 대량 데이터 처리에 유용
다차원 배열 초기화 { {1, 2}, {3, 4} } 2차원 또는 가변 배열 초기화 가능
스트림 API 초기화 IntStream.range(0, 5).toArray(); Java 8 이상에서 동적 초기화에 유용

각 초기화 방법은 특정 상황에서 더 적합합니다. 초기 값을 명확히 알고 있을 때는 선언과 동시에 초기화, 동적으로 값을 생성해야 할 때는 반복문 초기화를 많이 사용합니다. 😊

 
 

왜 기본값이 설정되나요?

Java는 안전성을 보장하기 위해, 배열이나 객체를 생성하면 그 내부의 값을 자동으로 초기화합니다. 이는 Java의 메모리 관리와 관련된 설계 철학으로, 명시적으로 값을 할당하지 않아도 **"쓰레기 값"**이 남지 않도록 하기 위함입니다.


다른 데이터 타입의 배열 기본값

배열의 데이터 타입에 따라 기본값이 다릅니다.

데이터 타입기본값
int, long 0
float, double 0.0
char '\u0000' (널 문자)
boolean false
참조 타입 (String, 클래스 등) null

예제: 다양한 데이터 타입의 배열 기본값

public class Main {
    public static void main(String[] args) {
        int[] intArray = new int[3];
        double[] doubleArray = new double[3];
        char[] charArray = new char[3];
        boolean[] boolArray = new boolean[3];
        String[] stringArray = new String[3];

        System.out.println("int 기본값: " + intArray[0]);      // 0
        System.out.println("double 기본값: " + doubleArray[0]); // 0.0
        System.out.println("char 기본값: " + charArray[0]);     // '\u0000'
        System.out.println("boolean 기본값: " + boolArray[0]);  // false
        System.out.println("String 기본값: " + stringArray[0]); // null
    }
}

출력 결과

int 기본값: 0
double 기본값: 0.0
char 기본값: 
boolean 기본값: false
String 기본값: null

 

결론

int[] a = new int[5];처럼 배열을 선언하고 크기만 지정하면, 모든 요소는 자동으로 0으로 초기화됩니다. 따라서, 명시적으로 값을 할당하지 않고 출력하더라도 0이 출력됩니다.


2차원 배열과 다차원 배열

Java에서 2차원 배열다차원 배열은 배열이 배열을 포함하는 구조로, 여러 개의 값을 행(Row)과 열(Column) 또는 다차원 공간에서 저장할 때 사용됩니다.


1. 2차원 배열

2차원 배열은 **행(row)**과 **열(column)**로 데이터를 저장하는 배열입니다. 이는 배열의 배열이라고도 볼 수 있습니다.

구조

  • 배열의 각 요소가 또 다른 배열을 가리킵니다.
  • 정사각형, 직사각형, 또는 가변적인 형태로 생성 가능합니다.

2차원 배열 생성 및 초기화

1. 정사각형/직사각형 배열

int[][] array = new int[3][4]; // 3행 4열 배열 생성
  • 메모리에는 다음과 같이 저장됩니다:
array[0] -> [0, 0, 0, 0]
array[1] -> [0, 0, 0, 0]
array[2] -> [0, 0, 0, 0]

2. 초기값을 지정하여 생성

int[][] array = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
  • 메모리 상태:
array[0] -> [1, 2, 3]
array[1] -> [4, 5, 6]
array[2] -> [7, 8, 9]

3. 가변 길이 배열 (Jagged Array)

행마다 열의 크기가 다른 배열입니다.

int[][] array = new int[3][]; // 행만 초기화
array[0] = new int[2];        // 첫 번째 행은 2열
array[1] = new int[3];        // 두 번째 행은 3열
array[2] = new int[1];        // 세 번째 행은 1열
  • 메모리 상태:
array[0] -> [0, 0]
array[1] -> [0, 0, 0]
array[2] -> [0]

2차원 배열의 값 접근

int[][] array = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 개별 요소에 접근
System.out.println(array[0][0]); // 출력: 1
System.out.println(array[1][2]); // 출력: 6

// 이중 for문을 이용한 전체 출력
for (int i = 0; i < array.length; i++) {
    for (int j = 0; j < array[i].length; j++) {
        System.out.print(array[i][j] + " ");
    }
    System.out.println();
}
  • 출력: 
1 2 3
4 5 6
7 8 9

2. 다차원 배열

다차원 배열은 배열이 배열을 여러 단계로 포함하는 구조입니다. 2차원 이상의 배열이 모두 다차원 배열에 해당합니다.

3차원 배열 예제

3차원 배열은 **행(row), 열(column), 깊이(depth)**의 개념으로 데이터를 저장합니다.

int[][][] array = new int[2][3][4]; // 깊이 2, 행 3, 열 4

3차원 배열 초기화

int[][][] array = {
    {
        {1, 2, 3, 4},    // 1번째 깊이, 1번째 행
        {5, 6, 7, 8},    // 1번째 깊이, 2번째 행
        {9, 10, 11, 12}  // 1번째 깊이, 3번째 행
    },
    {
        {13, 14, 15, 16},  // 2번째 깊이, 1번째 행
        {17, 18, 19, 20},  // 2번째 깊이, 2번째 행
        {21, 22, 23, 24}   // 2번째 깊이, 3번째 행
    }
};

값 접근

System.out.println(array[0][0][0]); // 출력: 1
System.out.println(array[1][2][3]); // 출력: 24

반복문을 이용한 접근

for (int i = 0; i < array.length; i++) {
    for (int j = 0; j < array[i].length; j++) {
        for (int k = 0; k < array[i][j].length; k++) {
            System.out.print(array[i][j][k] + " ");
        }
        System.out.println();
    }
}

2차원 배열과 다차원 배열의 차이점

특징2차원 배열다차원 배열
구조 행(Row)과 열(Column)로 구성 행(Row), 열(Column), 깊이(Depth) 등 여러 차원
사용 예시 행렬(Matrix), 테이블 데이터 처리 3D 좌표, 다차원 그래프 등 복잡한 데이터
표현 int[][] int[][][], int[][][][] 등

다차원 배열의 제한

  • 메모리 사용량: 차원이 증가할수록 메모리 사용량이 급격히 증가합니다.
  • 가독성: 차원이 많아지면 코드가 복잡해지고 읽기 어려워질 수 있습니다.
  • 성능: 반복문을 통한 값 접근이 많아지면 성능이 저하될 가능성이 있습니다.

결론

  • 2차원 배열은 데이터가 행과 열의 형태로 구성될 때 사용합니다.
  • 다차원 배열은 더 복잡한 데이터(예: 3D 좌표, 게임 보드 상태 등)를 다룰 때 유용합니다.
  • Java에서 다차원 배열은 사실 "배열의 배열"로 동작하며, 유연한 데이터 구조를 제공합니다.

 

혹시, jagged array에서, int [][][] a = new int[2][3][4]; 이렇게 하는거고 int [][][] a = new int[2][3][]; 맨 마지막을 비워서 저기를 나중에 정할 수 있다고 이해했어. 그런데, int [][][] a = new int[2][][4]; 이런식으로 중간에 있는 걸 비울 수 있어? int [][][] a = new int[][3][4]; 아니면 맨 앞에 있는 걸 비운다던지?

Java에서는 **Jagged Array(가변 배열)**를 만들 때, 맨 마지막 차원부터 채워 나가는 방식만 허용됩니다. 따라서, 중간 차원이나 첫 번째 차원을 비워둘 수는 없습니다. 아래에서 왜 그런지 자세히 설명하겠습니다.


배열 선언 규칙

  1. Java의 배열은 배열의 배열 형태로 동작합니다.
    • 예를 들어, int[][][]는 2차원 배열의 배열로 볼 수 있습니다.
    • 각 차원의 배열은 그 상위 배열의 요소로 저장됩니다.
  2. 배열 선언 시 규칙:
    • 맨 앞 차원은 고정되어야 함: 최상위 배열의 크기는 반드시 지정해야 합니다.
    • 그 외 차원은 뒤에서부터 동적으로 결정 가능: 중간 차원을 비우는 것은 허용되지 않습니다.

허용되는 선언

1. 마지막 차원을 비워둠 (Jagged Array 가능)

int[][][] a = new int[2][3][]; // 2행 3열의 배열, 깊이(마지막 차원)는 나중에 정할 수 있음.

이 경우:

  • a[0][0]은 아직 초기화되지 않았지만, 이후 배열을 할당할 수 있습니다.
a[0][0] = new int[4]; // 특정 위치에 크기 4의 배열 할당
a[0][1] = new int[2]; // 다른 위치에 크기 2의 배열 할당

2. 1차원만 고정

int[][][] a = new int[2][][];
  • 여기서 a[0], a[1]는 각각 2차원 배열이지만, 초기화되지 않은 상태입니다.
  • 이후에 각 2차원 배열의 크기를 따로 지정할 수 있습니다.
a[0] = new int[3][4]; // 첫 번째 2차원 배열: 3행 4열
a[1] = new int[2][5]; // 두 번째 2차원 배열: 2행 5열

허용되지 않는 선언

1. 중간 차원을 비워둠

int[][][] a = new int[2][][4]; // 컴파일 에러!
  • 이유: 배열은 상위 차원의 크기가 결정된 상태에서 하위 차원의 크기를 설정해야 합니다.
    중간 차원을 비우면 배열의 메모리 레이아웃이 불명확해지기 때문에 Java에서는 허용하지 않습니다.

2. 첫 번째 차원을 비워둠

int[][][] a = new int[][3][4]; // 컴파일 에러!
  • 이유: 최상위 배열의 크기가 결정되지 않으면 Java는 배열의 기본 구조를 만들 수 없습니다.
    최상위 차원은 메모리에 배열을 할당하기 위한 필수 정보이므로 반드시 지정해야 합니다.

배열 초기화 규칙 요약

배열 선언 형태허용 여부설명
int[][][] a = new int[2][3][4]; ✅ 허용 완전히 고정된 크기의 배열 (정사각형 배열)
int[][][] a = new int[2][3][]; ✅ 허용 마지막 차원을 가변적으로 설정 가능
int[][][] a = new int[2][][]; ✅ 허용 중간 차원과 마지막 차원을 나중에 설정 가능
int[][][] a = new int[2][][4]; ❌ 불허 중간 차원을 비울 수 없음
int[][][] a = new int[][3][4]; ❌ 불허 첫 번째 차원을 비울 수 없음

왜 중간 차원을 비울 수 없을까?

Java에서 배열은 상위 배열이 하위 배열의 참조값을 저장하는 구조로 동작합니다. 즉:

  • a[0]은 2차원 배열에 대한 참조.
  • a[0][0]은 1차원 배열에 대한 참조.
  • a[0][0][0]은 실제 값.

중간 차원을 비우게 되면 하위 배열을 참조할 위치가 명확하지 않기 때문에 메모리 관점에서 배열을 생성할 수 없습니다. 따라서 Java는 맨 뒤에서부터 차원을 유동적으로 결정하는 방식만 허용합니다.


결론

  • Java에서 배열은 최상위 차원부터 순차적으로 크기를 설정해야 합니다.
  • 중간 차원을 비워두는 것은 허용되지 않지만, 마지막 차원을 비워 두는 방식으로 가변 배열을 생성할 수 있습니다.
  • 최상위 차원의 크기는 항상 필수적으로 지정해야 합니다.