Updated:

데이터 타입 분류

 자바의 데이터 타입은 크게 기본 타입(Primitive type, 2장)과 참조 타입(reference type)으로 분류된다.

  • 참조 타입: 객체(Object)의 번지를 참조하는 타입으로 배열, 열거, 클래스, 인터페이스 타입이 있다.

 기본 타입으로 선언된 변수와 참조 타입으로 선언된 변수의 차이점은 저장되는 값이다. 기본 타입으로 선언된 변수는 값 자체를 저장하고 있지만, 참조 타입으로 선언된 변수는 객체가 생성된 메모리 번지를 저장한다.
 다음은 기본 타입은 int와 double로 선언된 변수들과 참조 타입인 String 클래스로 선언된 변수들이다.

// 기본 타입 변수
int age = 25;
double price = 100.5;

// 참조 타입 면수
String name = "신용권";
String hobby = "독서";

 메모리상에서 이 변수들이 갖는 값을 그림으로 표현하면 다음과 같다.

 변수들은 모두 스택(stack)이라는 메모리 영역에 생성된다. 기본 타입 변수인 age와 price는 직접 값을 저장하고 있지만, 참조 타입 변수인 name과 hobby는 힙 메모리 영역의 String 객체 번지를 저장하고 이 번지를 통해 String 객체를 참조한다.

메모리 사용 영역

 java 명령어로 JVM이 구동되면 JVM은 운영체제에서 할당받은 메모리 영역(Runtime Data Area)을 다음과 같이 구분해서 사용한다.

  • 메소드(Method) 영역
    • 바이트코드 파일을 읽은 내용이 저장되는 영역으로 클래스별 상수, 정적 필드, 메소드 코드, 생성자 코드 등이 저장된다.
  • 힙(heap) 영역
    • 객체가 생성되는 영역이다.
    • 객체의 번지는 메소드 영역과 스택 영역의 상수와 변수에서 참조할 수 있다.
  • 스택(stack) 영역
    • 메소드를 호출할 때마다 생성되는 프레임(Frame)이 저장되는 영역이다.
    • 메소드 호출이 끝나면 프레임은 자동 제거된다.
    • 프레임 내부에는 로컬 변수 스택이 있고, 여기에서 기본 타입과 참조 타입 변수가 생성되고 제거된다.

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

 ==, != 연산자는 변수의 값이 같은지, 아닌지를 조사한다. 참조 타입 변수의 값은 객체의 번지이므로 참조 타입의 변수의 ==, != 연산자는 번지를 비교하는 것이 된다.

 위 그림에서 refVar1과 refVar2는 서로 다른 객체를 참조하고 있으므로 == 및 != 연산의 결과는 다음과 같다.

refVar1 == refVar2  // 결과: false
refVar1 != refVar2  // 결과: true

 refVar2와 refVar3은 동일한 객체2를 참조하고 있으므로 ==과 != 연산의 결과는 다음과 같다.

refVar2 == refVar3  // 결과: true
refVar2 != refVar3  // 결과: false

null과 NullPointException

 참조 타입 변수는 아직 번지를 저장하고 있지 않다는 뜻으로 null 값을 가질 수 있다. null도 초기값으로 사용할 수 있기 때문에 null로 초기화된 참조 변수는 스택 영역에 생성된다.

String refVar1 = "자바";
String refVar2 = null;

 자바는 프로그램 실행 도중에 발생하는 오류를 예외(Exception)라고 부른다. 참조 변수를 사용하면서 가장 많이 발생하는 예외 중 하나는 NullPointException이다. 변수가 null인 상태에서 객체의 데이터나 메소드를 사용하려 할 때 이 예외가 발생한다.

int[] intArray = null;
intArray[0] = 10;
  • 배열 변수 intArray에 null을 대입한 상태에서 배열 객체의 0 인덱스 항목에 10을 대입하는 코드를 실행하면 NullPointException이 발생
  • intArray가 참조하는 배열 객체가 없으므로 10을 저장할 수 없음
String str = null;
System.out.println("총 문자 수: " + str.length());  // NullPointException
  • str 변수에 null을 대입한 상태에서 문자열의 길이를 얻기 위해 length() 메소드를 호출하면 NullPointException이 발생
  • str 변수가 참조하는 String 객체가 없으므로 문자열의 길이를 구할 수 없음

 경우에 따라서는 참조 타입 변수에 일부러 null을 대입하기도 한다. 변수에 null을 대입하면 번지를 잃게 되므로 더 이상 객체를 사용할 수 없게 된다.
 어떤 변수에도 객체를 참조하지 않으면 힙 메모리에는 있지만, 위치 정보를 모르기 때문에 해당 객체는 프로그램에서 사용할 수 없는 객체가 된다. 자바는 이러한 객체를 쓰레기로 취금하고, Garbage Collector를 실행시켜 자동으로 제거한다.

package ch05;

public class GarbageObjectExample {

    public static void main(String[] args) {
        String hobby = "여행";
        hobby = null;   // "여행"에 해당하는 String 객체를 쓰레기로 만듬

        String kind1 = "자동차";
        String kind2 = kind1;   // kind1 변수에 저장되어 있는 번지를 kind2 변수에 대입
        kind1 = null;   // "자동차"에 해당하는 String 객체는 쓰레기가 아님
        System.out.println("kind2: " + kind2);
    }
}

 kind2 변수가 여전히 “자동차”를 참조하고 있기 때문에 kind1 변수에 null을 대입한다고 해서 “자동차”애 해당하는 String 객체가 쓰레기가 되지는 않는다.

문자열(String) 타입

 자바의 문자열은 String 객체로 생성된다.

문자열 비교

 자바는 문자열 리터럴이 동일하다면 String 객체를 공유하도록 설계되어 있다. 다음과 같이 name1과 name2 변수에는 동일한 String 객체의 번지가 저장된다.

String name1 = "홍길동";
String name2 = "홍길동";

 String 변수에 문자열 리터럴을 대입하는 것이 일반적이지만, new 연산자로 직접 String 객체를 생성하고 대입할 수도 있다. new 연산자는 새로운 객체를 만드는 연산자로 객체 생성 연산자라고도 한다.

String name1 = new String("홍길동");
String name2 = new String("홍길동");
  • 이 경우 name1과 name2 변수는 서로 다른 String 객체의 번지를 가지게 된다.

 문자열 리터럴로 생성하느냐 new 연산자로 생성하느냐에 따라 비교 연산자의 결과가 달라질 수 있다.

String name1 = "홍길동";
String name2 = "홍길동";
String name3 = new String("홍길동");
  • name1과 name2는 동일한 문자열 리터럴로 생성된 객체를 참조하기 때문에 name1==name2의 결과는 true이다.
  • name3는 new 연산자로 String 객체를 별도로 생성했기 때문에 name1==name3의 결과는 false이다.

 동일한 String 객체든 다른 String 객체든 상관없이 내부 문자열만을 비교할 경우에는 String 객체의 equals() 메소드를 사용한다.

boolean result = str1.equals(str2); // 문자열이 같은지 검사(대소문자 구분)
boolean result = !str1.equals(str2);  // 문자열이 다른지 검사

 String 변수에 빈 문자열(““)을 대입할 수도 있다. 빈 문자열도 String 객체로 생성되기 때문에 변수가 빈 문자열을 참조하는지 조사하려면 equals(““)를 사용하면 된다.

문자 추출

 문자열에서 특정 위치의 문자를 얻고 싶다면 charAt() 메소드를 시용할 수 있다.

  • charAt(): 매개값으로 주어진 인덱스의 문자를 리턴

문자열 길이

 문자열에서 문자의 개수를 얻고 싶다면 length() 메소드를 사용한다.

  • length(): 문자열의 길이 리턴

문자열 대체

 문자열에서 특정 문자열을 다른 문자열로 대체하고 싶다면 replace() 메소드를 사용한다.

  • replace(): 기존 문자열은 그대로 두고, 대체한 새로운 문자열을 리턴
String oldStr = "자바 프로그래밍";
String newStr = oldStr.replace("자바", "JAVA");
  • newStr 변수는 “JAVA 프로그래밍” 문자열을 참조

문자열 잘라내기

 문자열에서 특정 위치의 문자열을 잘라내어 가져오고 싶다면 substring() 메소드를 사용한다.

  • substring(int beginIndex): beginIndexd에서 끝까지 잘라내기
  • substring(int beginIndex, int endIndex): beginIndexd에서 endIndex 앞까지 잘라내기
package ch05;

public class SubStringExample {
    public static void main(String[] args) {
        String ssn = "880815-1234567";

        String firstNum = ssn.substring(0, 6);
        System.out.println(firstNum); // "880815" 출력

        String secondNum = ssn.substring(7);
        System.out.println(secondNum);  // "1234567" 출력
    }
}

문자열 찾기

 문자열에서 특정 문자열의 위치를 찾고자 할 때에는 indexOf() 메소드를 사용한다.

  • indexOf(): 주어진 문자열이 시작되는 인덱스를 리턴
String subject = "자바 프로그래밍";
int index = subject.indexOf("프로그래밍");  // index에 3이 저장

 만역 주어진 문자열이 포함되어 있지 않으면 indexOf() 메소드는 -1을 리턴한다.
 주어진 문자열이 단순히 포함되어 있는지만 조사하고 싶다면 contains() 메소드를 사용하면 편리하다. 원하는 문자열이 포함되어 있으면 contains() 메소드는 true를 리턴하고, 그렇지 않으면 false를 리턴한다.

boolean result = subject.contains("프로그래밍");

문자열 분리

 문자열이 구분자를 사용하여 여러 개의 문자열로 구성되어 있을 경우, 이를 따로 분리해서 얻고 싶다면 split() 메소드를 사용한다.

String board = "번호, 제목, 내용, 성명";
String[] arr = board.split(",");
// arr[0]: "번호", arr[1]: "제목", arr[2]: "내용", arr[3]: "성명"

배열(Array) 타입

배열은 연속된 공간에 값을 나열시키고, 각 값에 인덱스(index)를 부여해 놓은 자료구조이다. 배열은 다음과 같은 특징을 가지고 있다.

  • 배열은 같은 타입의 값만 관리한다.
  • 배열의 길이는 늘리거나 줄일 수 있다.

  • 배열 변수 선언
    • 타입[ ] 변수;, 관례적으로 이 방법을 주로 사용
    • 타입 변수[ ];
    • 배열 변수는 참조 변수이므로, 객체인 배열은 힙 영역에 생성되고 배열 변수는 힙 영역의 배열 주소를 저장한다.
    • 참조할 배열이 없다면 배열 변수도 null로 초기화할 수 있다.(타입[ ] 변수 = null;)
      • 만약 배열 변수가 null 값을 가진 상태에서 변수[인덱스]로 값을 읽거나 저장하게 되면 NullPointException이 발생한다.
  • 값 목록으로 배열 생성
    • 타입[ ] 변수 = {값0, 값1, 값2, … };
      • 중괄호는 나열된 값들을 항목으로 가지는 배열을 힙에 생성하고, 번지를 리턴한다.
      • 배열 변수는 리턴된 번지를 저장함으로써 참조가 이루어진다.
    • 중괄호로 감싼 값의 목록을 배열 변수에 대입할 때는 배열 변수를 미리 선언한 후에는 값 목록을 변수에 대입할 수 없다.
    • 배열 변수를 선언한 시점과 값 목록이 대입되는 시점이 다르다면 new 타입[ ]을 중괄호 앞에 붙여 주면 된다.
    타입[] 변수;
    변수 = {값0, 값1, 값2, ... }; // 컴파일 에러
    변수 = new 타입[] {값0, 값1, 값2, ... };
    
    • 메소드의 매개변수가 배열 타입일 경우에도 매개값으로 중괄호 감싼 값 목록을 주면 컴파일 에러가 발생하므로 new 타입[]을 붙여서 호출해야 한다.
    // 메소드 선언
    void printItem(int[] scores) {...}
    
    // 잘못된 메소드 호출
    printItem({95, 85, 90});
    
    // 올바른 메소드 호출
    printItem(new int[] {95, 85, 90});
    
  • new 연산자로 배열 생성
    • 값의 목록은 없지만 향후 값들을 저장할 목적으로 타입[] 변수 = new 타입[길이] 를 사용해 배열을 미리 선언할 수도 있다.
    • new 연산자는 해당 길이의 배열을 생성하고 배열의 번지를 리턴하기 때문에 배열 변수에 대입할 수 있다.
      타입[] 변수 = null;
      변수 = new 타입[길이];
      
    • new 연산자로 배열을 처음 생성하면 배열 항목은 기본값으로 초기화된다.
      • 정수 배열은 0, 실수 배열은 0.0, 논리 배열은 false, 탐조 배열은 null로 초기화 된다.

배열 길이

 배열의 길이를 얻으려면 도트(.) 연산자를 사용해서 참조하면 배열의 length 필드를 읽으면 된다.

  • 배열변수.length;
  • 배열의 length 필드는 읽기만 가능하므로 “intArray.length = 10;” 처럼 값을 변경할 수는 없다.
  • 배열의 인덱스를 초기화 해서 사용하면 ArrayIndexOutOfBoundsException이 발생한다.

다차원 배열

 배열 항목에는 또 다른 배열이 대입될 수 있는데, 이러한 배열을 다차원 배열이라고 한다. 다차원 배열에서 각 차원의 항목에 접근하는 방법은 변수[1차원인덱스][2차원인덱스]…[N차원인덱스] 이다.

  • 값 목록으로 다차원 배열 생성
    • 값 목록으로 다차원 배열을 생성하려면 배열 변수 선언 시 타입 뒤에 대괄호 []를 차원의 수만큼 붙이고, 값 목록도 마찬가지로 차원의 수만큼 중첩시킨다.
    타입[][] 변수 = {
      {값1, 값2, ...},  // 1차원 배열의 0 인덱스
      {값3, 값4, ...},  // 1차원 배열의 1 인덱스
      ...
    };
    
    • 배열의 length 필드로 길이를 알 수 있다.
      • 변수.length: 1차원 배열의 길이와 동일
      • 변수[인덱스].length: 2차원 배열의 길이와 동일
  • new 연산자로 다차원 배열 생성
    • new 연산자로 다차원 배열을 생성하러면 배열 변수 선언 시 타입 뒤에 대괄호를 차원 수만큼 붙이고, new 타입 뒤에도 차원의 수만큼 대괄호를 작성하면 된다.
      • 타입[ ][ ] 변수 = new 타입[1차원수][2차원수];
    • 다차원 배열의 초기값도 정수 배열은 0, 실수 배열은 0.0, 논리 배열은 false, 탐조 배열은 null로 초기화 된다.
    • 2차원 배열의 길이를 다르게 줄 수 있다.
    int[][] scores = new int[2][];
    scores[0] = new int[3];
    scores[1] = new int[2];
    

객체를 참조하는 배열

 기본 타입 배열은 각 항목에 값을 직접 저장하지만, 참조 타입(클래스, 인터페이스) 배열은 각 항목에 객체의 번지를 저장한다. 다음 예를 보자.

String[] strArray = new String[3];
strArray[0] = "Java";
strArray[1] = "C++";
strArray[2] = "C#";

 ==, != 연산자를 사용하면 배열 항목이 참조하는 객체가 같은 객체인지 다른 객체인지를 확인할 수 있고, 문자열만 비교할 때는 equals() 매소드를 사용한다.

배열 복사

 배열은 한 번 생성하면 길이를 변경할 수 없다. 더 많은 저장 공간이 필요하다면 더 큰 길이의 배열을 새로 만들고 이전 배열들로 부터 항목들을 복사해야 한다. 가장 기본적인 복사 방법은 for 문을 이용해서 항목들을 하나씩 읽고 새로운 배열에 저장하는 것이다.
 배열 복사를 위한 좀 더 간단한 방법이 있다. System의 arraycopt() 메소드를 이용하면 한 줄만으로도 배열 복사를 할 수 있다.

  • System.arraycopy(원본 배열, 원본 배열 복사 시작 인덱스, 새 배열, 새 배열 붙여넣기 시작 인덱스, 복사 항목 수)

 예를 들어서 길이 3인 배열을 길이 5인 배열로 복사를 하면 3번 인덱스와 4번 인덱스 항목은 null이 된다.

배열 항목 반복을 위한 향상된 for 문

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

for(타입 변수: 배열) {
  실행문;
}
  • for 문이 실행되면 배열에서 가져올 항목이 있을 경우 변수에 항목을 저장하고, 실행문을 실행한다. 다시 반복해서 배열에서 가져올 다음 항목이 존재하면 위 과정을 진행하고, 가져올 다음 항목이 없으면 for 문을 종료한다.
package ch05;

public class AdvancedForExample {
    public static void main(String[] args) {
        int[] scores = {95, 71, 84, 93, 87};
        int sum = 0;
        for (int score : scores) {
            sum = sum + score;
        }

        System.out.println("점수 총합 = " + sum);
        double avg = (double) sum / scores.length;
        System.out.println("점수 평균 = " + avg);
    }
}

main() 메소드의 String[] 매개변수 용도

 자바 프로그램을 실행하기 위해 지금까지 main() 메소드를 작성했는데, 여기에서 문자열 배열 형태인 String[] args 매개변수가 사용된다. 위도우의 명령 프롬프트나 맥OS의 터미넣에서 프로그램을 실행할 때는 요구하는 값이 있을 수도 있다. 예를 들어 두 수를 입력받고 덧셈을 수행하는 Sum 프로그램은 “java Sum 10 20” 처럼 두 수를 요구할 수 있다. 공백으로 구분된 10과 20은 문자열로 취급되며 String[] 배열의 각 항목으로 구성된다. 그리고 main() 메소드를 호출 시 매개값으로 전달된다.

열거(Enum) 타입

 데이터 중에는 몇 가지로 한정된 값을 갖는 경우가 있다. 예를 들어 요일은 월, 화, 수, 목, 금, 토, 일 이라는 7개의 값을 갖는다. 이와 같이 한정된 값을 갖는 타입을 열거 타입(enumeration type)이라고 한다.
 열거 타입을 사용하기 위해서는 먼저 열거 타입 이름으로 소스 파일을 생성하고 한정된 값을 코드로 정의해야 한다. 열거 타입 이름은 첫 문자를 대문자로 하고 캐멀(camel)스타일로 지어 주는 것이 관례이다.

package ch05;

public enum Week {
    MONDAY, // 열거 상수수
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}
  • 열거 상수는 열거 타입으로 사용할 수 잇는 한정된 값을 말한다. 관례적으로 알파벳으로 정의하며, 모두 대분자로 작성한다. 만약 여러 단어로 구성될 경우에는 단어와 단어 사이를 언더바로 연결하는 것이 관례이다.
  • 열거 타입도 하나의 데이터 타입이므로 변수를 선언하고 사용해야 한다.
    • 열거 변수의 값이 특정 열거 상수인지 비교할 때는 ==와 != 연산자를 사용한다.
      Week today = Week.SUNDAY;
      Week reservationDay;
      Week birthday = null; // null 대입도 가능
      

 컴퓨터의 날짜 및 요일, 시간을 얻을 때는 Calendar를 이용한다.

Calendar now = Calendar.getInstance();  // Calendar 객체 얻기기
int year = now.get(Calendar.YEAR);
int month = now.get(Calendar.MONTH) + 1;  // 월(1~12)
int day = now.get(Calendar.DAT_OF_MONTH);
int week = now.get(Calendar.DAY_OF_WEEK); // 요일(1~7)
int hour = now.get(Calendar.HOUR);
int minute = now.get(Calendar.MINUTE);
int second = now.get(Calendar.SECOND);

댓글남기기