Array(배열)

2025. 4. 23. 17:18JAVA/JAVA 기초 문법

🖥️ Array(배열)란?

배열은 단일 데이터 타입의 일정한 개수의 값을 보관하는 컨테이너 객체다.
배열이 생성될 때 배열의 길이가 결정된다. 생성 후에는 그 길이가 고정된다.
자바에서 배열은 클래스 객체의 인스턴스 취급 받으며, new 키워드로 생성된다.

int[] anArray = new int[10]; // 자바에서 배열을 만드는 문법

💬 배열은 동일한 데이터 타입의 엘리먼트들을 가지는 데이터 구조

⌨️ 배열의 element란?

 

An array of 10 elements

 

배열의 각 아이템을 element라고 하며, 각 엘리먼트는 숫자 인덱스[index]로 엑세스됩니다.
인덱스 넘버링은 0부터 시작된다. 예를 들어, 9번째 엘리먼트틑 인덱스 8에서 엑세스된다.
쉽게 말하면 배열은 일렬로 정렬된 값들의 박스이고, 엘리먼트는 그 박스 안에 담긴 하나하나의 값이다

 

엘리먼트는 배열을 이루는 핵심 단위이며, 배열에서 엘리먼트 하나하나는 데이터 단위(동일한 데이터 단위)
또한 배열 엘리먼트를 다룰 때 인덱스 관리가 정확하지 않으면 에러가 발생하기 쉽다.

🖱️ element의 특징

특징 설명
인덱스 접근 엘리먼트틑 [0], [1] 등으로 접근. 인덱스 넘버링은 0부터 시작
값 저장소 엘리먼트는 배열이 저장하는 실제 데이터
타입 일관성 모든 엘리먼트는 배열에 선언된 자료형과 동일한 데이터 타입
초기값 존재 배열 생성 시, 엘리먼트는 자동 초기화됨(기본값)
수정 가능 배열[index] = 값 으로 언제든 수정 가능
public class ArrayDemo {
    public static void main(String[] args) {
        int[] anArray = new int[5]; // 각 엘리멘트의 데이터 타입은 int이고
                                    // 엘리먼트 개수가 5개인 배열을 만드는 문법
         // 배열의 각각의 엘리먼트는 인덱스가 주어진다
         anArray[0] = 100; // 배열의 각 엘리먼트 '값'을 설정하는 문법
         anArray[1] = 200;
         anArray[2] = 300;
         anArray[3] = 400;
         anArray[4] = 500;
         
         System.out.println("Element at index 0: "
                           + anArray[0]);
         System.out.println("Element at index 1: "
                           + anArray[1]);
         System.out.println("Element at index 2: "
                           + anArray[2]);
         System.out.println("Element at index 3: "
                           + anArray[3]);
         System.out.println("Element at index 4: "
                           + anArray[4]);
}

💬 각 엘리먼트의 start address 정보를 따로 유지할 필요는 없다.
     첫 번째 엘리먼트의 start address만 안다면 offset을 적용할 수 있다

⌨️ 배열의 Offset(오프셋)이란?

배열의 오프셋은 배열의 시작 위치(start address, base address)로부터 특정 요소 까지의 거리(간격)을 의미한다.
즉, 오프셋은 배열[인덱스] 에서 인덱스가 실제 메모리 주소에서 얼마나 떨어져 있는지를 말한다.

자바 배열은 메모리상에 같은 데이터 타입의 엘리먼트들을 연속적으로 배치한다.
각 엘리먼트는 고정된 크기(예 : int = 4byte)를 가지므로, 인덱스를 곱해 오프셋을 계산 할 수 있다. 
즉, 첫 엘리먼트의 주소만 알면 전체 배열 엘리먼트의 위치 계산이 가능하다.

🏸 Quiz) 엘리먼트 인덱스가 [3]인 엘리먼트 start address는?
  Answer) 0x1000+(4x3) → 0x100C

즉, 배열의 인덱스 = 오프셋 / 엘리먼트 크기

반대로, 오프셋 = 인덱스 x 엘리먼트 크기

🤔 만약 자바에서 배열의 오프셋 개념이 없다면? 🤔

만약 오프셋이 없다면, 배열은 더 이상 "빠른 자료구조"가 아니게 되고,
자바 내부에서도 성능과 메모리 효율이 급격히 나빠지는 결과를 초래하게 된다.

 

1️⃣ 배열 접근이 O(1) → O(n) 으로 느려짐

// 오프셋이 있다면✔️ :
엘리먼트 주소 = base + index * sizw (빠름) ✔️

// 오프셋이 없다면❌ :
배열[5]에 접근하기 위해 배열[0]부터 차례로 탐색해야 함
  • 배열의 가장 큰 장접인 빠른 인덱스 접근이 사라짐
  • 리스트와 같은 선형 탐색 구조가 된다

2️⃣ 각 요소의 위치를 별도로 저장해야 함

오프셋이 없다는 건, 각 엘리먼트가 메모리상 어디에 있는지 계산할 수 없다는 뜻이다.

→ 각 엘리먼트의 메모리 주소를 배열처럼 저장해둬야 함/
→ 즉, "포인트 배열" 형태가 되어야 함.
→ 메모리 낭비 + 구조 복잡도 증가

 

3️⃣ CPU 캐시 효율성(CPU cache locality) ↓ ↓ ↓

오프셋이 있어 배열 엘리먼트가 연속된 메모리 공간에 있을 때,
CPU는 한번에 여러 요소를 캐시라인에 미리 불러와 빠르기 처리한다.
하지만,
  ❌ 오프셋이 없다면 엘리먼트 간 메모리 위치를 예측할 수 없고
  ❌ CPU는 캐시 미스를 자주 겪데 된다.

→ 고성능 연산에서 병목 발생

 

4️⃣ JVM 내부 구현도 훨씬 비효율적으로 변경

지금의 JVM은 배열에 접근할 때 :

배열[5] → base 주소 + (5 x 타입 크기)   직접 계산 후 접근

 

오프셋이 없어지면 JVM은 :

for(int i = 0; i < index; i++) {
    현재 위치 = 다음 요소의 위치를 포인터로 따라가기
}

→ JVM 자체가 훨씬 무겁고 느린 구조가 되버린다

 

😄 오프셋은 배열이 배열일 수 있게 해주는 이유

오프셋 덕분에 자바의 배열은

  • 빠르고 단순한 인덱스 기반 접근이 가능하고
  • 메모리를 연속적으로 쓸 수 있어서 공간 효율이 높고
  • CPU 캐시 효율도 극대화할 수 있다.

이 구조는 단순히 "기술적으로 괜찮다"가 아니라,
시스템 프로그래밍 기본 원칙이자 고성능 컴퓨터의 핵심 기반이다

 

⌨️ 배열의 반복 구문(Looping over Arrays)

1️⃣ 전통적인 for 반복문

public class UseArrays {
	public static void useArray() {
		int[] studentsId = new int[20];	
		for (int i=0; i<studentsId.length; i++) { 
			studentsId[i] = i + 1;
		}		
		// for 반목구문
		for (int i=0; i<studentsId.length; i++) {
			studentsId[i] = studentsId[i] + 1000;
		}
	}
}
장점 설명
인덱스 사용 가능  i  를 통해 위치 기반 연산 가능 (ex : 짝수 인덱스만 접근)
수정 가능 students[i] = newValue 처럼 요소 수정 가능

2️⃣ 향상된 for-each 반복문

public static void main(String[] args){
    Object obj;
    int[] numbers = {1,2,3,4,5,6,7,8,9,10};
         
    // 자바 컴파일러가 배열에 대한 for-each 문을 내부적으로 
    // for (int i = 0; i < arr.length; i++) 형태로 변환
    for (int item : numbers) {
        System.out.println("Count is: " + item);
    }
}
장점 설명
코드가 간결함 인덱스, 조건 없이 깔끔하게 반복
일기 전용 요소 값만 읽고, 수정은 불가

3️⃣  while 반복문

public class WhileDemo {

	public static void main(String[] args){
        int loopingCount = 1; // for 구문에서 int i = 1;
       
        // while(expression)의 조건식이 true 이면
        // while 블럭의 Statement(s)들을 수행한다.
        // 언제까지? while(expression)의 조건식이 true이면 계속해서
        // while 블럭의 statement(s)들을 수행한다.
        while (loopingCount < 11)/* for 구문에서 i<11;*/ {
        	// true or false	
        	
        	System.out.println("Count is: " + loopingCount);
            
        	loopingCount++; // for 구문에서 i++
        }
        // while 조건식의 값이 false가 되면,
        // while 블럭의 next statement로 실행 순서가 이루어진다.
    }
}
장점 설명
반복 조건을 더 유연하게 제어 가능 반복 중 break, continue 사용 유용

4️⃣ do-while 반복문

public class DoWhileDemo {
    public static void main(String[] args){
        int count = 11;
        int a = 0;
        
        do {
            int doVariable = 0;
            System.out.println("Count is: " + count);
            count++;
        } while (count < 11); // 첫번째 반복시에 while 조건식 값이 
        					  // false 이더라도
        					  // 한번은,,, do..while 블럭의 스테이트먼트가 수행됨
        System.out.println();
    }
}
특징 설명
무조건 한 번은 실행됨 조건이 나중에 검사되기 때문

🤔 만약 배열을 쓰지 않는다면? 🤔

public class WhyUseArray {
// ✅ 배열을 사용하면	
    public static void useArrayForId() {
        int[] studensId = new int[20];	
        for (int i=0; i<studensId.length; i++) { 
        studensId[i] = i + 1;
        }		
        for (int i=0; i<studensId.length; i++) {
        studensId[i] = studensId[i] + 1000;
        }
    }

// ❌ 배열이 없다면
    public static void main(String ...args) {
        int st1Id = 0;
        int st2Id = 0;
        int st3Id = 0;
        int st4Id = 0;
        int st5Id = 0;
              .
              .
              .
        int st1000Id = 0;     

        st1Id = 1;
        st2Id = 2;
        st3Id = 3;
        st4Id = 4;
        st5Id = 5;
              .
              .
              .
        st1000Id = 1000;

        st1Id = st1Id + 1000;
        st2Id = st2Id + 1000;
        st3Id = st3Id + 1000;
        st4Id = st4Id + 1000;
        st5Id = st5Id + 1000;
              .
              .
              .
        st1000Id = st1000Id + 1000;
    }
}

배열 없이 데이터를 여러 개 저장하고 반복적으로 처리 하기란 사실상 불가능에 가까움

→ 유지 보수도, 반복문도, 연산도 불가능 ❌

 

Java 배열의 단점

1️⃣ 크기 고정 (Fixed Size)

배열은 한 번 생성하면 크기를 변경할 수 없다.

public class ArrayDemo {
    public static void main(String[] args) {
        int[] anArray = new int[5]; // 나중에 엘리먼트를 10개로 늘리고 싶을 때?
                                    // 새 배열을 만들어 복사해야 함
         anArray[0] = 100; 
         anArray[1] = 200;
         anArray[2] = 300;
         anArray[3] = 400;
         anArray[4] = 500;
         
         int[] anArray6 = new int[6]; 
         System.arraycopy(anArray, 0, anArray6, 0, 10);
         anArray[5] = 600; // 인덱스가 6인 엘리먼트틑 없는데,
                           // 인덱스가 6인 엘리먼트에 특정 값을 할당하려고 하니,
                           // Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 
                           // Index 5 out of bounds for length 5
                           // 에러 발생
   }

💬  자바 배열은 연속된 메모리 공간을 쓰기 때문에 중간에 늘리거나 줄일 수 없음
      실무에서 불편함 : 동적으로 데이터를 추가/삭제하기 어려움

2️⃣ 삽입(insert)/삭제(delete)/꼬리 추가(attach)  불가능 or 비효율적

중간에 데이터를 삽입하거나 삭제할 때 모든 엘리먼트를 직접 이동해야 함
ArrayList는 이런 삽입/삭제/꼬리 추가 작업을 내부적으로 처리해주지만,
배열은 전부 수동으로 조작해야 한다.

 

▶ 삽입( 예: 인덱스 2번에 값 넣기)

for (int i = arr.length - 1; i > index; i--) {
    arr[i] = arr[i - 1]; // 뒤로 한 칸씩 밀기
}
arr[index] = newValue;

→ 수작업 + 공간 부족하면 new 배열을 만들어야 함

 

▶ 삭제

for (int i = index; i < arr.length - 1; i++) {
    arr[i] = arr[i + 1]; // 앞으로 당기기
}

→  실제로는 삭제된 게 아니라 "마지막 엘리먼트가 중복됨"

 

▶ 꼬리 추가

배열에 여유 공간이 있을 때만 수동으로 가능
여유 공간이 없으면? → 새 배열을 만들어서 복사해야 함

int[] newArr = new int[arr.length + 1];
System.arraycopy(arr, 0, newArr, 0, arr.length);
newArr[arr.length] = newValue;

3️⃣ 데이터 타입 고정(Single Type Only)

배열은 하나의 타입만 저장 가능

Object[] arr = {1, "Hello", true}; // Object 배열로는 가능하긴 함

 

하지만 기본적으로 int[], String[] 등 동일 타입만 저장 가능

4️⃣ 타입 제한으로 인한 박식/언박싱 성능 저하

List<Integer> list = new ArrayList<>();
// int[] → Integer[] 변환 시 오토박싱 발생 → 성능 저하 가능

배열은 기본형을 담을 수 있지만, ArrayList는 불가

→ 배열 간 변환 시 박싱 비용 발생

5️⃣ 기능 부족

배열 자체는 메서드를 거의 제공하지 않음

arr.length // O
arr.add(3) // X ❌
arr.remove(2) // X ❌

이런 기능은 전부 java.util.Array 또는 ArrayList 에서 제공
배열은 기본 저장소에 가까움, 로직은 다 수작업

6️⃣ 메모리 낭비 가능성

너무 크게 잡으면 낭비, 작게 잡으면 오버플로우 가능성

int[] arr = new int[1000]; // 100개만 써도 900칸은 낭비

특히 사용량이 예측되지 않는 상황에서 치명적

크기를 유연하게 조절할 수 없는 것이 문제의 핵심

 

🎯 정리 🎯

 배열은 Java의 기초이자, 모든 자료구조의 출발점

✅ 단순하지만 자주 쓰이고, 성능 최적화에도 큰 영향을 줌

'JAVA > JAVA 기초 문법' 카테고리의 다른 글

Control Flow Statements  (1) 2025.05.14
Operators (연산자)  (0) 2025.04.30
자바에서 This  (0) 2025.04.15
Constructor (생성자)  (0) 2025.04.14
Access Modifier(접근 제어자)  (0) 2025.04.04