이것이 자바다 - Chapter 5

윤여준·2022년 5월 8일
0
post-thumbnail

데이터 타입 분류

자바의 데이터 타입은 크게 기본 타입참조 타입으로 분류된다.

기본 타입이란 정수, 실수, 문자, 논리 리터럴을 저장하는 타입을 말한다. 참조 타입이란 객체의 번지를 참조하는 타입으로 배열, 열거, 클래스, 인터페이스 타입을 말한다.

기본 타입으로 선언된 변수는 실제 값을 변수 안에 저장하지만, 참조 타입으로 선언된 변수는 메모리의 번지를 값으로 갖는다. 번지를 통해 객체를 참조한다는 뜻에서 참조 타입이라고 부른다.

메모리 사용 영역

메소드(Method) 영역

메소드 영역에는 코드에서 사용되는 클래스들을 클래스 로더로 읽어 클래스별로 런타임 상수풀(runtime constant pool), 필드(field) 데이터, 메소드(method) 데이터, 메소드 코드, 생성자(constructor) 코드 등을 분류해서 저장한다. 메소드 영역은 JVM이 시작할 때 생성되고 모든 스레드가 공유하는 영역이다.

힙(Heap) 영역

힙 영역객체와 배열이 생성되는 영역이다. 힙 영역에 생성된 객체와 배열은 JVM 스택 영역의 변수나 다른 객체의 필드에서 참조한다. 참조하는 변수나 필드가 없다면 의미 없는 객체가 되기 대문에 이것을 쓰레기로 취급하고 JVM은 쓰레기 수집기(Garbage Collector)를 실행시켜 쓰레기 객체를 힙 영역에서 자동으로 제거한다.

JVM 스택(Stack) 영역

JVM 스택 영역은 각 스레드마다 하나씩 존재하며 스레드가 시작될 때 할당된다. JVM 스택은 메소드를 호출할 때마다 프레임(Frame)을 추가(push)하고 메소드가 종료되면 해당 프레임을 제거(pop)하는 동작을 수행한다.

프레임 내부에는 로컬 변수 스택이 있는데, 기본 타입 변수와 참조 타입 변수가 추가(push)되거나 제거(pop)된다. 변수가 이 영역에 생성되는 시점은 초기화가 될 때, 즉 최초로 변수에 값이 저장될 때이다. 변수는 선언된 블록 안에서만 스택에 존재하고 블록을 벗어나면 스택에서 제거된다.

참조 변수의 ==, != 연산

기본 타입 변수의 ==, != 연산은 변수의 값이 같은지, 아닌지를 조사하지만 참조 타입 변수들 간의 ==, != 연산은 동일한 객체를 참조하는지, 다른 객체를 참조하는지 알아볼 때 사용된다. 참조 타입 변수의 값은 힙 영역의 객체 주소이므로 결국 주소 값을 비교하는 것이 된다. 동일한 객체를 참조하고 있을 경우 == 연산의 결과는 true이고 != 연산의 결과는 false이다.

null과 NullPointerException

참조 타입 변수는 힙 영역의 객체를 참조하지 않는다는 뜻으로 null 값을 가질 수 있다. null 값도 초기값으로 사용할 수 있기 때문에 null로 초기화된 참조 변수는 스택 영역에 생성된다.

자바는 프로그램 실행 도중에 발생하는 오류를 예외(Exception)라고 부른다. 참조 변수를 사용하면서 가장 많이 발생하는 예외 중 하나로 NullPointerException이 있다. 이 예외는 참조 타입 변수를 잘못 사용하면 발생한다. 참조 타입 변수가 null을 가지고 있을 경우, 참조 타입 변수는 사용할 수 없다. 참조 타입 변수를 사용하는 것은 곧 객체를 사용하는 것을 의미하는데, 참조할 객체가 없으므로 사용할 수가 없는 것이다. 그러나 프로그래머의 실수로 null 값을 가지고 있는 참조 타입 변수를 사용하면 NullPointerException이 발생한다.

String 타입

문자열은 String 객체로 생성되고 변수는 String 객체를 참조한다.

스택 영역에서 선언된 변수가 힙 영역의 문자열 객체를 참조한다.

자바는 문자열 리터럴이 동일하다면 String 객체를 공유하도록 되어 있다. 하지만 new 연산자를 사용해서 직접 String 객체를 생성할 수도 있다. 여기서 new 연산자는 힙 영역에 새로운 객체를 만들 때 사용하는 연산자로 객체 생성 연산자라고 한다.

문자열 리터럴로 생성하느냐 new 연산자로 생성하느냐에 따라 비교 연산자의 결과가 달라질 수 있다. 동일한 문자열 리터럴로 String 객체를 생성했을 경우 == 연산의 결과는 true가 나오지만, new 연산자로 String 객체를 생성했을 경우 == 연산의 결과는 false가 나온다. == 연산자는 변수에 저장된 객체 번지가 동일한지를 검사하기 때문이다. 동일한 String 객체이건 다른 String 객체이건 상관없이 문자열만을 비교할 때에는 String 객체의 equals() 메소드를 사용해야한다. equals() 메소드는 원본 문자여로가 매개값으로 주어진 비교 문자열이 동일한지 비교한 후 true 또는 false를 리턴한다.

배열 타입

배열이란?

배열같은 타입의 데이터를 연속된 공간에 나열시키고, 각 데이터에 인덱스(index)를 부여해 놓은 자료구조이다.

배열은 같은 타입의 데이터만 저장할 수 있다. 배열은 선언과 동시에 저장할 수 있는 데이터 타입이 결정된다. 만약 다른 타입의 값을 저장하려고 하면 타입 불일치(Type mismatch) 컴파일 오류가 발생한다.

배열의 또 다른 특징은 한 번 생성된 배열은 길이를 늘리거나 줄일 수 없다. 만약 배열의 길이를 바꿔야 하는 경우엔, 새로운 배열을 만들고 기존 배열의 항목들을 복사해야 한다.

배열 선언

배열을 사용하기 위해서는 우선 배열 변수를 선언해야 한다. 배열 변수 선언은 다음과 같이 두 가지 형태로 작성할 수 있다. 타입[] 변수; , 타입 변수[];

배열 변수는 참조 변수에 속한다. 배열도 객체이므로 힙 영역에 생성되고 배열 변수는 힙 영역의 배열 객체를 참조하게 된다. 참조할 배열 객체가 없다면 배열 변수는 null 값으로 초기화될 수 있다.

값 목록으로 배열 생성

배열 항목에 저장될 값의 목록이 있다면, 다음과 같이 간단하게 배열 객체를 만들 수 있다.
데이터타입[] 변수 = {값0, 값1, 값2, 값3, ... };

중괄호는 주어진 값들을 항목으로 가지는 배열 객체를 힙에 생성하고, 배열 객체의 번지를 리턴한다. 배열 변수는 리턴된 번지를 저장함으로써 참조가 이루어진다.

값의 목록으로 배열 객체를 생성할 때 주의할 점이 있는데, 배열 변수를 이미 선언한 후에 다른 실행문에서 중괄호를 사용한 배열 생성은 허용되지 않는다.

타입[] 변수;
변수 = { 값0, 값1, 값2, ...}; // 컴파일 에러

배열 변수를 미리 선얺나 후, 값 목록들이 나중에 결정되는 상황이라면 다음과 같이 new 연산자를 사용해서 값 목록을 지정해주면 된다. new 연산자 바로 뒤에는 배열 변수 선언에서 사용한 타입[]를 붙여주고 중괄호에는 값들을 나열해주면 된다.
변수 = new 타입[] { 값0, 값1, 값2, ... };

메소드의 매개값이 배열일 경우에도 마찬가지다. 아래와 같이 매개 변수로 int[] 배열이 선언된 add() 메소드가 있을 경우, 값 목록으로 배열을 생성함과 동시에 add() 메소드의 매개값으로 사용하고자 할 때는 반드시 new 연산자를 사용해야 한다.

int add(int[] scores) { ... }
------------------------------
int result = add( {95, 85, 90} ); // 컴파일 에러
int result = add( new int[] {95, 85, 90} );

new 연산자로 배열 생성

값의 목록을 가지고 있지 않지만, 향후 값들을 저장할 배열을 미리 만들고 싶다면 new 연산자로 다음과 같이 배열 객체를 생성시킬 수 있다.
타입[] 변수 = new 타입[길이];

길이는 배열이 저장할 수 있는 값의 수를 말한다.

new 연산자로 배열을 생성할 경우에는 이미 배열 변수가 선언된 후에도 가능하다.

new 연산자로 배열을 처음 생성할 경우, 배열은 자동적으로 기본값으로 초기화된다. 다음은 배열의 초기값을 보여준다.

데이터 타입초기값
byte[]0
char[]'\u0000'
short[]0
int[]0
long[]0L
float[]0.0F
double[]0.0
boolean[]false
클래스[]null
인터페이스[]null

배열이 생성되고 나서 새로운 값을 저장하려면 대입 연산자를 사용하면 된다.
변수[인덱스] = 값;

배열의 길이

배열의 길이란 배열에 저장할 수 있는 전체 항목 수를 말한다. 코드에서 배열의 길이를 얻으려면 다음과 같이 배열 객체의 length 필드를 읽으면 된다. 참고로 필드객체 내부의 데이터를 말한다.
배열변수.length;

커맨드 라인 입력

main() 메소드의 매개값인 String[] args가 무엇인지 알아보자.

"java 클래스"로 프로그램을 실행하면 JVM은 길이가 0인 String 배열을 먼저 생성하고 main() 메소드를 호출할 때 매개값으로 전달한다.

만약 다음과 같이 "java 클래스" 뒤에 공백으로 구분된 문자열 목록을 주고 실행하면, 문자열 목록으로 구성된 String[] 배열이 생성되고 main() 메소드를 호출할 때 매개값으로 전달된다.
java 클래스 문자열0 문자열1 문자열2 ... 문자열n

main() 메소드는 String[] args 매개 변수를 통해서 커맨드 라인에서 입력된 데이터의 수와 입력된 데이터를 알 수 있게 된다.

다차원 배열

2차원 배열은 수학의 행렬을 떠올리면 되는데, 가로 인덱스와 세로 인덱스를 사용한다. 자바는 2차원 배열을 중첩 배열 방식으로 구현한다. 예를 들어 2 x 3 행렬을 만들기 위해 다음과 같은 코드를 사용한다.
int[][] scores = new int[2][3];

자바는 일차원 배열이 서로 연결된 구조로 다차원 배열을 구현하기 때문에 수학 행렬 구조가 아닌 계단식 구조를 가질 수 있다.

int[][] scores = new int[2][];
scores[0] = new int[2];
scores[1] = new int[3];

만약 그룹화된 값 목록을 가지고 있다면 다음과 같이 중괄호 안에 다시 중괄호를 사용해서 값 목록을 나열하면 된다.
타입[][] 변수 = { {값1, 값2, ...}, {값1, 값2,...} };

배열 복사

배열은 한 번 생성하면 크기를 변경할 수 없기 때문에 더 많은 저장 공간이 필요하다면 보다 큰 배열을 새로 만들고 이전 배열로부터 항목 값들을 복사해야 한다.

배열 간의 항목 값들을 복사하려면 for문을 사용하거나 System.arraycopy() 메소드를 사용하면 된다. for문으로 배열을 복사하는 코드는 다음과 같다.

public class ArrayCopyByForExample {
	public static void main(String[] args) {
    	int[] oldIntArray = {1,2,3};
        int[] newIntArray = new int[5];
        
        for(int i = 0; i < oldIntArray.length; i++){
        	newIntArray[i] = oldIntArray[i];
        }
	}
}

System.arraycopy() 메소드를 호출하는 방법은 다음과 같다.
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
src 매개값은 원본 배열이고, srcPos원본 배열에서 복사할 항목의 시작 인덱스이다. dest 매개값은 새 배열이고, destPos새 배열에서 붙여넣을 시작 인덱스이다. 마지막으로 length복사할 개수이다.

참조 타입 배열일 경우, 배열 복사가 되면 복사되는 값이 객체의 번지이므로 새 배열의 항목은 이전 배열의 항목이 참조하는 객체와 동일하다. 이것을 얕은 복사(shallow copy)라고 한다. 반대로 깊은 복사(deep copy)는 참조하는 객체도 별도로 생성하는 것을 말한다.

향상된 for문

자바 5부터 배열 및 컬렉션 객체를 좀 더 쉽게 처리할 목적으로 향상된 for문을 제공한다. 향상된 for문은 반복 실행을 하기 위해 카운터 변수와 증감식을 사용하지 않는다. 배열 및 컬렉션 항목의 개수만큼 반복하고, 자동적으로 for문을 빠져나간다.

for( 타입 변수이름 : 배열 ){
	실행문;
}

for문의 괄호에는 배열에서 꺼낸 항목을 저장할 변수 선언과 콜론 그리고 배열을 나란히 작성한다. for문이 처음 실행될 때 배열해서 가져올 첫 번째 값이 존재하는지 평가한다. 가져올 값이 존재하면 해당 값을 변수에 저장하고 실행문을 실행한다. 블록 내부의 실행문이 모두 실행되면 다시 배열에서 가져올 다음 값이 존재하는지 평가한다. 만약 다음 항목이 존재하면 앞의 과정을 다시 진행하고, 가져올 다음 항목이 없으면 for문이 종료된다. 따라서 for문의 반복 횟수는 배열의 항목 수가 된다.

열거 타입

한정된 값만을 갖는 데이터 타입을 열거 타입(enumeration type)이라고 한다. 열거 타입은 몇 개의 열거 상수(enumeration constant) 중에서 하나의 상수를 저장하는 데이터 타입이다.

열거 타입 선언

열거 타입을 선언하기 위해서는 먼저 열거 타입의 이름을 정하고 열거 타입 이름으로 소스 파일(.java)을 생성해야 한다. 열거 타입 이름은 관례적으로 첫 문자를 대문자로 하고 나머지는 소문자로 구성한다. 만약 여러 단어로 구성된 이름이라면 각 단어 첫 문자는 대문자로 하는 것이 관례이다.

소스 파일의 내용으로는 다음과 같이 열거 타입 선언이 온다. public enum 키워드는 열거 타입을 선언하기 위한 키워드이다. 반드시 소문자로 작성해야 한다. 열거 타입 이름은 소스 파일명과 대소문자가 모두 일치해야한다.
public enum 열거타입이름 { ... }

열거 타입을 선언했다면 이제는 열거 상수를 선언하면 된다. 열거 상수는 열거 타입의 값으로 사용되는데, 관례적으로 열거 상수는 모두 대문자로 작성한다. 만약 열거 상수가 여러 단어로 구성될 경우에는 단어 사이를 밑줄(_)로 연결하는 것이 관례이다.

열거 타입 변수

열거 타입을 선언했다면 이제 열거 타입을 사용할 수 있다. 열거 타입도 하나의 데이터 타입이므로 변수를 선언하고 사용해야 한다. 다음은 열거 타입 변수를 선언하는 방법이다.
열거타입 변수;

열거 타입 변수를 선언했다면 다음과 같이 열거 상수를 저장할 수 있다. 열거 상수는 단독으로 사용할 수 없고 반드시 열거타입.열거상수로 사용된다.
열거타입 변수 = 열거타입.열거상수;

열거 타입 변수는 null 값을 저장할 수 있는데 열가 타입도 참조 타입이기 때문이다.

열거 상수는 열거 객체로 생성되는 객체이다.

열거 객체의 메소드

열거 객체는 열거 상수의 문자열을 내부 데이터로 가지고 있다.

아래 표는 열거 객체가 가지는 메소드들이다. 열거 객체의 메소드는 java.lang.Enum 클래스에 선언된 메소드인데, 열거 객체에서 사용할 수 있는 이유는 모든 열거 타입은 컴파일 시에 Enum 클래스를 상속하게 되어 있기 때문이다.

리턴 타입메소드(매개 변수)설명
Stringname()열거 객체의 문자열을 리턴
intordinal()열거 객체의 순번(0부터 시작)을 리턴
intcompareTo()열거 객체를 비교해서 순번 차이를 리턴
열거 타입valueOf(String name)주어진 문자열의 열거 객체를 리턴
열거 배열values()모든 열거 객체들을 배열로 리턴

name() 메소드

name() 메소드는 열거 객체가 가지고 있는 문자열을 리턴한다. 이때 리턴되는 문자열은 열거 타입을 정의할 때 사용한 상수 이름과 동일하다.

// name() 메소드 사용 예시
Week today = Week.SUNDAY;
String name = today.name();

ordinal() 메소드

ordinal() 메소드는 전체 열거 객체 중 몇 번째 열거 객체인지 알려준다. 열거 객체의 순번은 열거 타입을 정의할 때 주어진 순번을 말하는데, 0번부터 시작한다.

// ordinal() 메소드 사용 예시
Week today = Week.SUNDAY;
int ordinal = today.ordinal();

compareTo() 메소드

compareTo() 메소드는 매개값으로 주어진 열거 객체를 기준으로 전후로 몇 번째 위치하는지를 비교한다. 만약 열거 객체가 매개값의 열거 객체보다 순번이 빠르다면 음수가, 순번이 늦다면 양수가 리턴된다.

// compareTo() 메소드 사용 예시
Week day1 = Week.MONDAY;
Week day2 = Week.WEDNESDAY;
int result1 = day1.compareTo(day2); //-2
int result2 = day2.compareTo(day1); //2

valueOf() 메소드

valueOf() 메소드는 매개값으로 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴한다.

// valueOf() 메소드 사용 예시
Week weekDay = Week.valueOf("SATURDAY"); // weekDay 변수는 Week.SATURDAY 열거 객체를 참조하게 된다

values() 메소드

values() 메소드는 열거 타입의 모든 열거 객체들을 배열로 만들어 리턴한다.

// values() 메소드 사용 예시
Week[] days = Week.values();
profile
Junior Backend Engineer

0개의 댓글