멀티캠퍼스

[2026.04.21] TIL - 17일차 Java (배열 복사, 2차원 배열, 객체)

buckwheat 2026. 4. 22. 00:16

 

1. 배열 복사

 

배열은 참조형 자료형이기 때문에 복사할 때 얕은 복사와 깊은 복사를 구분해서 이해해야 한다. 겉보기에는 둘 다 “복사”처럼 보이지만, 실제 동작 방식은 다르다.

1  ) 얕은 복사

얕은 복사는 배열의 값 자체를 새로 복사하는 것이 아니라, 배열의 주소만 복사하는 방식이다.
즉, 새로운 배열이 만들어지는 것이 아니라 기존 배열을 다른 변수도 함께 참조하게 된다.

 

int[] cArr = oArr;

 

위와 같이 대입하면 oArrcArr는 서로 다른 배열이 아니라 같은 배열 하나를 함께 바라보게 된다. 그래서 원본 배열의 값을 바꾸면 복사한 배열의 값도 같이 바뀐다. 

해시코드도 같게 나오므로 같은 배열 객체를 참조하고 있다는 것을 확인할 수 있다.

얕은 복사는 복사처럼 보이지만 실제로는 주소만 공유하는 방식이다.

 

2 ) 깊은 복사

깊은 복사는 새로운 배열을 따로 만들고, 원본 배열의 값을 하나씩 복사하는 방식이다.
이 경우 원본 배열과 복사한 배열은 서로 다른 배열 객체가 되므로, 한쪽의 값을 바꿔도 다른 쪽에는 영향을 주지 않는다. 해시코드도 서로 다르게 나타난다.

즉, 깊은 복사는 값 자체를 새 배열에 따로 복사하는 방식이라고 이해하면 된다.

 

  • for문을 이용한 깊은 복사

가장 기본적인 깊은 복사 방법은 for문을 이용해 배열의 각 요소를 하나씩 복사하는 것이다.
원본 배열의 길이만큼 반복하면서 복사 배열의 같은 인덱스 위치에 값을 넣어주면 된다. 이 방법은 동작 원리를 이해하기 쉽다는 장점이 있다.

 

  • System.arraycopy()를 이용한 깊은 복사

System.arraycopy()는 배열의 값을 한 번에 복사할 수 있는 메소드이다.
원본 배열, 복사를 시작할 위치, 복사될 배열, 붙여넣기를 시작할 위치, 복사할 길이를 지정해 사용할 수 있다. 따라서 원하는 위치에 원하는 개수만큼 복사할 수 있어 활용도가 높다.

이번 코드에서는 원본 배열의 값을 복사 배열의 세 번째 인덱스부터 복사해 넣는 방식으로 사용했다. 이를 통해 배열 복사는 단순히 처음부터 끝까지 복사하는 것만이 아니라, 원하는 위치를 지정해서도 가능하다는 점을 확인할 수 있었다.

 

  • clone()을 이용한 깊은 복사

clone() 메소드는 배열 전체를 복사할 때 사용할 수 있는 방법이다.
원본 배열과 같은 내용을 가진 새로운 배열 객체를 만들어 주기 때문에, 간단하게 깊은 복사를 할 수 있다.

코드에서 cArr = oArr.clone();을 실행한 뒤 해시코드를 확인해보면, 복사 후의 배열은 원본과 다른 해시코드를 가진다. 즉, 값은 같지만 서로 다른 배열 객체가 생성되었다는 뜻이다.

 

 

배열 복사에서 가장 중요한 점은 값이 복사된 것인지, 주소가 복사된 것인지를 구분하는 것이다.

  • 얕은 복사: 주소만 복사, 같은 배열을 함께 참조
  • 깊은 복사: 새로운 배열을 만들고 값 자체를 복사

또한 깊은 복사는 for문, System.arraycopy(), clone() 등 여러 방법으로 구현할 수 있다.
배열은 참조형 자료형이기 때문에 복사 방식을 정확히 이해하는 것이 중요하다.

 


2. 2차원 배열

 

1 ) 개념

2차원 배열은 같은 자료형의 1차원 배열을 여러 개 묶어서 다루는 구조이다. 쉽게 말하면 배열 안에 또 다른 배열이 들어 있는 형태라고 볼 수 있다. 1차원 배열이 한 줄로 값을 저장한다면, 2차원 배열은 행과 열의 형태로 값을 저장한다. 그래서 인덱스도 두 개를 사용하며, 앞의 인덱스는 행, 뒤의 인덱스는 열을 의미한다.

 

2 ) 2차원 배열 선언과 할당

배열 선언 배열 할당
자료형[][] 배열명;
자료형 배열명[][];
자료형[] 배열명[];
자료형[][] 배열명 = new 자료형[행크기][열크기];
자료형 배열명[][] = new 자료형[행크기][열크기];
자료형[] 배열명[] = new 자료형[행크기][열크기];

 

예를 들면 다음과 같다.
int[][] arr = new int[3][4];
int arr[][] = new int[3][4];

3행 4열의 정수형 2차원 배열을 만드는 코드이다.

보통은 배열이라는 의미가 더 잘 보이기 때문에 자료형[][] 배열명 형태를 더 많이 사용한다.

 

3 ) 2차원 배열 구조

2차원 배열은 메모리에서 보면 “배열 안에 배열이 들어 있는 구조”로 이해할 수 있다. 사진의 int[][] arr = new int[2][4];를 보면, 먼저 arr이라는 변수는 Stack 영역에 생성되고, 그 안에는 실제 값이 아니라 Heap에 생성된 2차원 배열의 주소값이 저장된다. 사진에서는 그 주소가 0x123으로 표시되어 있다.

이후 Heap 영역에는 먼저 1차원 배열 두 개를 가리키는 공간이 만들어진다. 즉, arr[0]arr[1]이 각각 하나의 배열 주소를 저장하고 있는 형태이다. 사진에서는 arr[0]0x678, arr[1]0x098을 가리키고 있다.

그리고 다시 arr[0]은 4칸짜리 정수 배열을 참조하고, arr[1]도 또 다른 4칸짜리 정수 배열을 참조한다. 그래서 최종적으로는 arr[0][0]부터 arr[0][3], arr[1][0]부터 arr[1][3]까지의 값들이 저장되는 구조가 된다.

 

즉, 2차원 배열은 값이 한 번에 쭉 붙어 있는 구조라기보다, 먼저 큰 배열이 각 행에 해당하는 배열들의 주소를 가지고 있고, 각 행 배열이 다시 실제 값을 저장하는 형태라고 볼 수 있다. 그래서 2차원 배열은 “행을 기준으로 여러 개의 1차원 배열을 묶은 구조”라고 이해하면 된다.

 

4 ) 2차원 배열 초기화

 

  • 인덱스를 이용한 초기화
arr[0][0] = 1;
arr[0][1] = 2;

행과 열의 인덱스를 직접 지정해서 값을 하나씩 넣는 방법이다. 어느 위치에 어떤 값을 넣는지 명확하게 지정할 수 있다는 특징이 있다.

 

  • for문을 이용한 초기화
for(int i = 0; i < arr.length; i++){
    for(int j = 0; j < arr[i].length; j++){
        arr[i][j] = j;
    }
}

중첩 for문을 사용해 행과 열을 반복하면서 값을 넣는 방법이다. 값이 많거나 일정한 규칙이 있을 때 특히 유용하다. 2차원 배열은 보통 행과 열을 함께 다뤄야 하기 때문에 중첩 반복문과 같이 사용하는 경우가 많다.

 

  • 선언과 동시에 초기화
public void testDeArray04(){ 
    int[][] iarr = {{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}};
    for(int i=0; i<iarr.length; i++){
        for(int j=0; j<iarr[i].length; j++){
            System.out.print(iarr[i][j] + " ");
        }
    System.out.println();
    }
}

배열을 만들면서 값을 한 번에 넣는 방법이다. 처음부터 저장할 값이 정해져 있다면 가장 간단하게 사용할 수 있는 방식이다.

 

2차원 배열은 같은 자료형의 1차원 배열 여러 개를 묶은 구조이며, 행과 열의 형태로 데이터를 저장한다. 선언과 할당도 1차원 배열과 비슷하지만 인덱스를 두 개 사용한다는 점이 다르다.
또한 초기화할 때는 인덱스를 직접 사용할 수도 있고, 중첩  for문이나 선언과 동시에 초기화하는 방법도 사용할 수 있다.

 


 

3. 객체(Object)

 

1) 객체 지향 언어

자바는 객체 지향 언어이다. 객체 지향 언어는 현실 세계를 이루는 사물이나 개념을 각각의 객체로 보고, 이 객체들 사이의 상호작용을 프로그램으로 표현하는 방식의 언어이다. 즉, 현실에서 사람이 물건을 사고, 자동차가 움직이고, 학생이 수업을 듣는 것처럼 프로그램도 여러 객체가 서로 영향을 주고받으며 동작한다고 이해하면 된다.

 

2) 객체

자바에서 객체란 클래스에 정의된 내용대로 new 연산자를 통해 메모리에 생성된 것이다.

쉽게 말하면 클래스는 설계도이고, 객체는 그 설계도를 바탕으로 실제 만들어진 결과물이다. 붕어빵 틀이 클래스라면, 틀로 만들어진 붕어빵 하나하나가 객체라고 볼 수 있다.

 

  • 클래스와 인스턴스화

클래스는 객체를 만들기 위한 틀이다. 예를 들어 Student 클래스는 “학생이 공통적으로 가질 수 있는 속성과 기능”을 정리한 설계도라고 볼 수 있다. 하지만 클래스만 만들었다고 해서 실제 학생 한 명이 바로 생기는 것은 아니다. 클래스는 어디까지나 설계도일 뿐이고, 실제로 메모리에 만들어진 결과물이 객체이다.

 

예를 들어 Student라는 클래스가 있다면, 이를 바탕으로 김철수 객체, 김영희 객체처럼 여러 개의 실제 객체를 만들 수 있다. 이처럼 클래스를 바탕으로 실제 객체를 생성하는 과정을 인스턴스화라고 한다. 즉, 인스턴스화란 설계도인 클래스를 이용해 메모리에 실제 사용할 수 있는 객체를 만드는 과정이다.

객체는 현실에 존재하는 독립적이면서 하나로 취급되는 사물이나 개념이다. 객체 지향 언어에서 객체의 개념은 클래스에 정의된 내용대로 메모리에 할당된 결과물(Object)이다.
  • 객체(인스턴스)의 생성과 메모리 구조

객체는 new 연산자를 사용해 생성한다. Student s = new Student();라는 코드는 Student 타입의 객체를 하나 만들고, 그 객체를 참조할 변수 s를 선언한 것이다.

 

이때 메모리 구조를 보면, 변수 sStack 영역에 생성된다. 하지만 실제 Student 객체는 Heap 영역에 만들어진다. 즉, s 안에는 객체 자체가 들어 있는 것이 아니라, Heap에 생성된 객체의 주소값이 저장된다. 그림에서는 그 주소가 0x0123처럼 표시되어 있다.

Stack에는 객체를 가리키는 참조 변수 s가 있고, Heap에는 실제 Student 객체가 저장된다. 그래서 객체 변수는 값을 직접 저장하는 것이 아니라, 객체가 저장된 위치를 참조하는 역할을 한다.

2) 클래스

데이터를 다루는 방식은 변수 →   배열 →  구조체처럼 점점 발전해 왔다. 변수는 하나의 자료형에 하나의 데이터만 저장할 수 있고, 배열은 같은 자료형의 여러 값을 저장할 수 있다. 구조체는 서로 다른 자료형의 여러 값을 하나로 묶을 수 있다.

클래스는 여기서 더 나아가 데이터뿐 아니라 그 데이터를 다루는 기능까지 함께 묶을 수 있다.

그래서 클래스는 단순한 데이터 묶음이 아니라, 속성과 기능을 함께 가진 객체의 설계도라고 할 수 있다.

 

클래스는 객체를 만들기 위한 틀이다. 예를 들어 Student 클래스는 “학생이 공통적으로 가질 수 있는 속성과 기능”을 정리한 설계도라고 볼 수 있다. 하지만 클래스만 만들었다고 해서 실제 학생 한 명이 바로 생기는 것은 아니다. 클래스는 어디까지나 설계도일 뿐이고, 실제로 메모리에 만들어진 결과물이 객체이다.

 

예를 들어 Student라는 클래스가 있다면, 이를 바탕으로 김철수 객체, 김영희 객체처럼 여러 개의 실제 객체를 만들 수 있다. 이처럼 클래스를 바탕으로 실제 객체를 생성하는 과정을 인스턴스화라고 한다. 즉, 인스턴스화란 설계도인 클래스를 이용해 메모리에 실제 사용할 수 있는 객체를 만드는 과정이다.

 

3) 추상화

클래스는 객체의 공통 요소를 뽑아내어 정의한 것이다. 이 과정을 추상화라고 한다. 추상화는 프로그램에 필요한 공통점은 남기고, 불필요한 세부 내용은 제거하는 과정이다.

사람 클래스를 만든다면 이름, 나이, 주소 같은 공통 정보는 넣을 수 있지만, 지금 무슨 생각을 하는지 같은 정보까지 모두 담을 필요는 없다. 추상화는 필요한 것만 남겨 클래스를 더 유연하게 만드는 과정이다.

 

추상화 예시

항목 변수명 자료형(type)
주민등록번호 pNo String
이름 name String
성별 gender char
주소 address String
전화번호 phone String
나이 age int

변수명과 자료형을 다이어그램으로 나타내면 아래와 같다.

Person
- pNo : String
- name : String
- gender : char
- address : String
- phone : String
- age : int
접근 제한자
+ : public
# : protected
~ : default
- : private

변수 앞에 붙은 - 기호는 접근 제한자 private를 의미한다. TIL 12일차에 정리했는데 자바의 접근 제한자는 public, protected, default, private 네 가지가 있으며, 이 중 private는 클래스 외부에서 직접 접근하지 못하게 막는 역할을 한다. 따라서 중요한 데이터는 private로 감추고, 필요한 경우 메소드를 통해 접근하도록 만드는 것이 일반적이다. 이런 구조는 캡슐화와도 연결된다.

 

4) 캡슐화

캡슐화는 데이터와 기능을 하나로 묶고, 외부에서 함부로 접근하지 못하게 보호하는 것이다. 중요한 데이터를 private으로 선언하면 클래스 외부에서는 직접 접근할 수 없고, 클래스 내부에서만 사용할 수 있다. 이렇게 하면 객체의 데이터를 안전하게 보호할 수 있고, 필요한 기능을 통해서만 값을 변경할 수 있다.

 

5) 객체 지향의 특징

객체 지향 프로그래밍의 대표적인 특징은 캡슐화, 상속, 다형성이다.

캡슐화는 데이터와 기능을 하나로 묶고 외부 접근을 제한하는 것이고,
상속은 기존 클래스의 특징을 새로운 클래스가 물려받는 것이며,
다형성은 같은 이름의 기능이 상황에 따라 다르게 동작할 수 있게 하는 것이다.

 

클래스는 객체를 만들기 위한 설계도이고, 객체는 그 설계도를 바탕으로 실제 메모리에 만들어진 결과물이다. 자바는 이러한 객체를 중심으로 프로그램을 구성하는 객체 지향 언어이며, 객체의 공통점을 뽑아내는 추상화, 데이터를 보호하는 캡슐화 같은 개념이 함께 사용된다.