2021.08.13 - [Java] - [Java 기본 문법] 1. 변수와 타입
2021.08.13 - [Java] - [Java 기본문법] 2. 연산자
2021.08.13 - [Java] - [Java 기본 문법] 3. 제어문
1번 포스팅에서 자바의 타입에는 기본타입과 참조타입이 있다고 했다. 이제 참조타입을 설명할 때다.
참조타입이란?
기본타입의 변수는 그 공간에 기본타입의 값을 직접 저장한다. 그러나, 참조타입의 변수는 값이 저장된 공간의 주소를 값으로 가지고있다. 그리고 그 공간에는 객체가 저장되어있다. C언어의 포인터와 비슷한 개념이다. 다만, 포인터와 다르게 프로그래머입장에서 값을 읽거나 쓸 때 변수가 참조타입인지 기본타입인지에 따라 방식이 같다. 구체적으로, 변수는 스택영역에 할당된다. 기본타입의 경우 바로 스택영역에 값이 들어가지만, 참조타입의 경우 힙(heap)이라 부르는 영역에 메모리가 할당되고 값이 거기에 적힌다. 그 후 스택에 할당된 참조변수의 공간에 힙의 메모리 주소가 값으로 적힌다.
참조타입의 종류
참조타입에는 클래스, 인터페이스, 배열, 열거타입이 있다. 클래스와 인터페이스는 자바에서 아주 중요한 부분이므로 따로 다룬다. 이 포스팅에서는 참조타입에대한 공통적인 이야기와 배열에 대해서만 다룬다.
참조타입에서의 비교연산
기본타입에서 ==, != 연산은 값이 같은지 여부를 판단한다. 참조타입도 마찬가지다. 그러나, 참조타입에서의 값은 메모리 주소고 따라서 같은 메모리주소를 값으로 가지고있는지를 비교하게 된다. 즉, 두 참조타입 변수가 가리키고있는 메모리에 적힌 값이 동일해도, 다른 위치를 가리키고있다면 같다고 판단되지 않는다.
null 과 NullPointerException
참조타입의 변수가 아무 객체도 참조하지 않는다는 뜻으로 null 값을 가질 수 있다. 초기값이 null이어도 상관없다. 단, 실제 그 변수를 사용하려고 하면 예외(자바에서 오류를 예외라 한다)가 발생한다. 아무것도 참조하지 않기 때문에 값을 읽거나 사용한다는것 자체가 정의될 수 없기 때문이다. 자바에서 예외는 그 종류마다 이름이 있는데, null인 참조변수를 사용할때 발생하는 예외가 NullPointerException이다. 자바 프로그래머가 가장 많이 만나는 예외이다(null의 존재가 자바의 큰 약점이다).
배열 타입
배열이란?
같은 타입의 의미적으로 묶어서 관리할 수 있는 데이터를 묶기위한 타입이 배열이다. 이 떄 클래스나 인터페이스등의 참조타입도 배열타입으로 묶을 수 있다. 만약 한 학급의 학생수가 30명이고 이들의 각 과목별 시험점수를 저장하기 위해선 수백개의 변수가 필요할 것이다. 만일 이를 int같은 기본타입 변수로 선언한다면 아주 골치아플것이다. 이때 필요한 것이 배열이다. 배열은, 같은 타입의 데이터를 연속된 공간에 나열하고 각 데이터에 인덱스(index)를 부여한 자료구조다. 이때 배열을 구성하는 각각의 값을 원소라고 부른다. 배열의 각 인덱스에 접근하기 위해서는 score[0], score[20] 처럼 배열이름[인덱스번호] 로 사용하면된다. 배열의 인덱스는 0부터 시작한다.
int score[] = new int[30];
score[0] = 10;
score[29] = 100;
score[30] = 233; // 배열의 인덱스 범위를 초과했으므로 컴파일이되지 않는다.
배열에 값을 할당하는 방법은 3가지가 있다.
- null
- {값1, 값2, ...};
- new 타입[크기];
배열변수에 null을 대입한 경우 값을 읽거나 쓰려하면 NullPointerException이 발생한다. 배열의 각 원소에 접근하기 위해서는 배열을 생성해 배열변수에 대입해야 한다.
값의 목록으로 배열 생성
이미 값의 목록을 가지고있는 경우 배열객체를 이렇게 생성할 수있다.
int[] score = {10, 20, 40, 50, 80, 100};
이 경우에 배열의 길이는 목록에 적힌 값의 갯수로 결정된다. 즉, 위 코드에서 score 배열의 길이는 6이다. 주의할 점이, 이 방법의 경우 배열변수를 선언할 때에만 사용할 수 있다. 즉, 아래 코드는 컴파일되지 않는다.
int[] score;
score = {10, 20, 40, 50, 80, 100};
만일 이미 배열 변수를 선언한 상태에서 값의 목록으로 배열을 생성하고 싶으면 new 연산자를 사용해 이렇게 할 수는 있다.
int[] score;
score = new int[] {10, 20, 40, 50, 80, 100};
매소드의 매개값으로 배열을 넘길 때에도 위와같은 방법으로 배열을 생성해야 한다. 매소드에 대한 설명은 클래스를 설명할 때 하니 일단 이렇다고 알고 넘어가자. 사실 굳이 메소드로 이런식으로 배열을넘길일은 거의 없다.
int sum(int[] array){
...
}
int result = sum({10, 20, 40, 50, 80, 100}); // 컴파일 에러
int result = sum(new int[]{10, 20, 40, 50, 80, 100});
new 연산자로 배열 생성
만일, 값의 목록은 가지고있지 않으나, 필요한 배열의 길이는 알고있다면, 배열을 미리 만들 수 있다.
타입[] 변수 = new 타입[길이];
배열을 위와같이 선언한다면, 배열의 각 원소는 기본값으로 초기화되어있다. 각 타입별 초기값은 다음과같다.
| 정수 타입 | 0 (0L) |
| 실수 타입 | 0.0 (0.0F) |
| 논리 타입 | false |
| 참조 타입 ( 클래스, 인터페이스) | null |
배열의 길이
배열의 길이란, 배열에 저장할 수 있는 원소의 갯수를 말한다. 배열의 길이를 알고싶으면 배열이름.length를 이용하면된다.
int[] array = {10, 20, 40, 50, 80, 100};
int length = array.length; // 6이 저장된다.
배열의 길이는 한번 정해지면 변하지 않는다. 만일 배열의 길이를 바꾸고싶다면, 바꾸고싶은 길이로 새 배열을 생성한 다음 기존 배열의 값들을 새 배열로 옮겨줘야 한다.
다차원 배열
이전까지 본 배열은 인덱스 - 값으로 구성된 1차원의 배열이다. 이와는 달리, 값이 마치 표의 행과 열로서 구성된 배열을 2차원 배열이라고 한다. 같은 방식으로 3차원 이상의 배열도 가능하다. 하지만 활용이 드물기 때문에 3차원 이상의 배열은 생략한다.
2차원 배열은 이렇게 선언한다
int[][] array = new int[행길이][열길이];
이렇게 선언하면 행렬의 첫번째 원소가 길이가 "열길이"인 행렬이 된다. 행렬의 원소에 접근하는 방법은 1차원배열과 같다. 배열이름[행인덱스][열인덱스] 와같이 접근하면 된다.
int[][] array = new int[2][3];
array[0][0] = 1; // array 의 첫번째 원소인 행렬의 첫번째 원소에 1을 대입
array[1][1] = 3; // array 의 두번째 원소인 행렬의 두번째 원소에 1을 대입
이런 방식은 수학의 행렬이나 좌표 등을 표현하기에 적합하다. 자바에서 2차원 행렬의 각 원소의 길이는 다를 수도 있다.
int[][] array = new int[2][];
array[0] = new int[10];
array[1] = {1, 2, 3, 4};
이런식으로 2차원 배열의 원소인 1차원 배열들을 다 따로 선언할 수도 있다. 만일, 2차원 배열에 들어갈 값의 목록을 모두 알고있다면 아래처럼 값의 목록 형태로 선언할 수도 있다.
int[][] array = {{1, 2, 3}, {4, 6, 4}, {543, 54, 66}};
참조타입의 복사
프로그램을 작성하다보면, 기존의 데이터는 따로 보존하고, 복사본을 이용해 데이터를 가공하는 일이 생긴다. 이 때 참조타입의 경우 원본의 데이터가 변할 수 있다. 아래 코드를 보자.
int original = 10;
int copy = original;
copy = copy +1;
System.out.println(original);
System.out.println(copy);
이 경우 original은 변경되지 않고 copy만 변경된다. 하지만 참조타입의 경우 다르다.
int[] array = {1,2,3};
int[] array2 = array;
array2[0] = 3;
for(int i=0;i<array2.length;i++)
System.out.println(array[i]);
두번째 줄에서 array2 배열에 array 배열을 대입했다. 그 후 array2 배열의 원소를 변경하고 array 배열을 출력했다. 만일 원본이 보존되었다면 1 2 3 이 출력되어야 하지만 실제 출력결과는 3 2 3 이다. 즉, 원본이 변경된다.
이유를 생각해보면 당연하다. 참조타입 변수의 경우 힙영역에 저장된 데이터의 주소를 값으로 가진다. 따라서, 참조변수의 값을 다른 참조변수에 대입한다면 두 참조변수는 같은 힙영역의 메모리주소를 값으로 가진다. 따라서, 둘 중 한 변수로 힙영역의 데이터를 수정하면 당연히 다른 변수로 접근해도 값이 변경되어있다.
이를 극복하기 위해서는 다음과같이 코드를 작성해야 한다.
int[] array = {1,2,3};
int[]array2 = new int[array.length];
for(int i=0;i<array2.length;i++) {
array2[i] = array[i];
}
array2[0] = 3;
for(int i=0;i<array2.length;i++) {
System.out.println(array[i]);
}
이렇게 복사할 참조변수에 새 객체(여기선 배열)를 할당하고, 객체의 구성요소(여기선 원소)를 하나씩 복사해야 한다.
for-each 문
자바에선 배열이나 컬렉션(나중에 정리함)객체를 좀 더 쉽게 처리하기 위해 특수한 for문을 제공한다. 이런 for문을 for-each 문 혹은 향상된 for문 으로 부른다. for-each 문에서는 카운터변수와 증감식을 사용하지 않는다. 배열이나 컬렉션의 항목의 개수만큼 반복하고 자동으로 for문을 빠져나온다. 사용방법은 아래와 같다.
for ( 타입 변수: 배열 ) {
실행문;
}
말로 설명하는 것보다 실제 코드를 보는게 편할것이다.
int[] scores = {10, 30, 50, 70};
int sum =0;
for ( int score: scores ) {
sum = sum + score;
}
for-each 문의 첫 루프에서 score 변수는 scores 배열의 인덱스 0 원소를 값으로 가진다. 그 다음은 인덱스 1, 2, .. 인 방식이다. scores 배열의 마지막 인덱스까지 반복문이 돌고 자동으로 반복문을 빠져나온다. 주의할 점이, for-each 문은 배열의 원소를 읽는것만 가능하고 쓰는것은 불가능하다. 이유는 동작원리를 생각하면 당연하다.
다음글
'Java > 기초문법' 카테고리의 다른 글
| [Java 기본 문법] 6. 접근제한자와 캡슐화 (0) | 2021.08.16 |
|---|---|
| [Java 기본 문법] 5. 객체와 클래스 (0) | 2021.08.14 |
| [Java 기본 문법] 3. 제어문 (0) | 2021.08.13 |
| [Java 기본문법] 2. 연산자 (0) | 2021.08.13 |
| [Java 기본 문법] 1. 변수와 타입 (0) | 2021.08.13 |