본문 바로가기

카테고리 없음

자바를 배우고 싶은가?(3)

2장  소스 코드 구성
2.1  소개
자바의 소스 코드는 패키지(package), 임포트(import), 클래스(class)로 구성된다. 먼저 패키지는 공통된 기능을 가진 클래스들을 묶어서 쉽게 관리하기 위한 것으로 윈도우에서 사용되는 폴더와 비슷한 개념으로 볼 수 있다. 그리고 임포트는 이미 작성되어 있는 클래스를 현재 작성하는 클래스에서 불러들여 사용하기 위한 명령문이다. 마지막으로 클래스는 실제 로직을 작성하기 위한 자바 소스 코드의 몸체로서 소스 코드에서 가장 중요한 부분이다. 

그림 2-1-0 소스 코드 구성 
자바 소스 코드는 이 그림처럼 일정한 형식이 있기 때문에 형식을 지키지 않고 작성하면 컴파일시 에러가 발생한다. 그러므로 소스 코드를 작성할 때는 가장 먼저 패키지를 선언하고 그 다음에 임포트를 선언해야 한다. 그리고 이어서 클래스를 정의하면 자바 소스 코드가 완성된다. 이러한 작성 순서는 자바 코드 작성시 반드시 지켜야 하지만 패키지와 임포트는 필요하지 않은 경우에는 생략할 수 있다.

다음 소스 코드는 소스 코드 구성을 지켜 작성한 소스 코드를 보여준다.

코드) 소스 코드 구성
package com.dev.java;

import
 java.util.*; 

public classMyTest {
    publicMyTest() {
    }

    
public static voidmain(String[] args) {
    }
}


2.2  패키지(package)
프로그램을 작성하기 위해서는 많은 클래스가 필요하다. 작은 프로그램인 경우에는 몇 개의 클래스로 완성될 수 있지만 대부분의 프로그램들은 많은 클래스들을 필요로 한다. 이러한 많은 클래스들이 하나의 폴더에 저장되어 있을 경우 원하는 클래스를 찾기 위해서 많은 시간을 소비해야 하는 일이 발생할 수 있다. 그렇기 때문에 이러한 문제를 없애고 좀 더 효율적으로 클래스들을 관리하기 위해서는 종류별로 클래스를 구분하는 것이 좋다. 자바에서 이러한 기능을 제공하기 위한 것이 패키지라는 개념이다. 이 개념은 윈도우 환경에서 수많은 파일들을 관리하기 위해 사용하는 폴더와 같은 개념이라고 볼 수 있다.


그림 2-2-1 자바 표준 패키지
위 그림은 자바 표준 패키지의 구조를 보여준다. java 폴더 아래 io, lang, math 등의 폴더가 있고 각각의 폴더 아래 실제 클래스들이 위치하고 있다. 이러한 구조로 패키지를 작성하기 위해서는 해당 소스 파일에 패키지를 선언해야 한다. 패키지 선언은 위 그림처럼 폴더 순으로 선언하면 된다. 
여러분은 패키지 구조를 직접 확인할 수도 있다. 자바가 설치된 폴더에 보면 자바 API 소스를 모아 놓은 src.zip 파일이 있다. 이 파일을 열면 위와 같이 되어 있는 것을 볼 수 있다.

그리고 이 파일의 java.io 패키지에 있는 Bits.java 파일을 열어보면 다음과 같이 패키지가 선언되어 있는 것을 확인할 수 있다. 또한 이 파일 아래로 보이는 많은 자바 파일들이 모두 java.io 패키지에 속해 있다는 것도 알 수 있다. 

코드) java.io 패키지의 Bits.java 소스 코드

/* @(#)Bits.java    1.4 03/12/19
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */ 

package java.io; 

class Bits {
}
//  이하 생략


작성한 패키지는 내부에서 사용하거나 외부에 배포할 수 있다. 이때 배포된 패키지명이 이미 다른 조직에서 배포한 패키지명과 동일하다면 이 두 개의 패키지를 동시에 사용할 수 있을까? 자바 프로그램에서는 같은 패키지명을 가진 두 개의 패키지를 구분할 수 없기 때문에 한 개의 패키지는 사용할 수 없다. 그래서 자바에서는 패키지명이 중복되는 일이 발생하지 않도록 도메인 이름을 역순으로 하여 패키지명을 작성하는 것을 권장하고 있다. 예를 들어 자신의 회사의 도메인 이름이 www.kbc.com 이고 작성된 클래스들이 io 를 위한 것이라면 패키지명을 com.kbc.io 로 한다. 물론 이러한 패키지명 작성 규칙을 지키지 않고 패키지를 작성할 수도 있지만 되도록 작성 규칙을 지키는 것이 차후 발생할 수 있는 문제를 미연에 방지하는 길이라는 것을 명심해야 한다.

코드) 패키지(package)
package com.kbc.io;


2.3  임포트(import)
프로그램을 작성하는 경우 자신이 모든 클래스를 만들어 사용하는 것은 매우 불필요한 일이며 현명하지 않은 선택이다. 예를 들어 자바 프로그램을 작성하면서 화면에 출력하기 위해 자주 사용하는 System.out.println() 메소드를 직접 만들어 사용한다고 했을 때 원하는 프로그램을 작성하기 위해서는 너무 많은 시간이 걸리게 된다는 것을 누구나 알 것이다. 그렇기 때문에 우리는 프로그램을 빠르게 작성하기 위해서는 JDK 에서 지원하는 표준 패키지를 사용해야 한다. 어떻게 사용할 수 있을까? 바로 이때 사용하는 것이 임포트이다. 임포트를 사용하면 이미 작성되어 있는 클래스를 이용하여 프로그램을 쉽고 빠르게 작성할 수 있다.

임포트를 사용하는 방법은 패키지를 선언하는 방법과 동일하며 패키지 전체의 클래스를 사용할 필요가 없을 경우에는 특정 클래스만을 지정할 수도 있다. 예를 들어 프로그램에서 파일 작업을 위해 자바 표준 패키지의 FileReader 클래스가 필요하다면 import java.io.FileReader; 라고 선언하고 FileReader 클래스 뿐만 아니라 io 관련 클래스를 모두 사용하고 싶다면 import java.io.*; 라고 선언하면 된다. 

코드) 임포트(import)
import java.io.*;


2.4  클래스(class)
클래스는 객체를 표현하기 위한 설계도로서 자바 프로그램의 가장 핵심적인 부분이다. 우리는 이 클래스에 변수, 생성자, 메소드를 작성하여 프로그램을 원하는 방향으로 동작하도록 할 수 있다. 클래스에 대한 자세한 설명은 차후에 다시 살펴보도록 하고 여기서는 자바 프로그램의 핵심이 클래스라는 것만 알아두도록 하자.

코드) 클래스(class)
public classMyTest {
    publicMyTest() {
    }

    
public static voidmain(String[] args) {
    }
}


2.5  주석(Comments)
주석은 프로그램의 흐름에는 영향을 주지 않으면서 소스 코드의 분석을 용이하게 해주는 문장으로서 자바에서 사용할 수 있는 주석에는 라인(Line) 주석, 블록(Block) 주석, 문서화(Document) 주석이 있다. 이들 주석은 프로그램의 흐름에는 영향을 주지 않는 문장들이기 때문에 필요하지 않다면 반드시 작성할 필요는 없다. 


2.5.1  라인 주석(Line Comment)
라인 주석은 단일 문장을 주석 처리하기 위해 사용하며 보통 변수나 프로그램 로직의 간단한 설명을 위해 사용한다. 라인 주석을 선언하기 위해서는 주석 처리를 위한 문장 앞에 // 를 추가하면 된다. 이렇게 라인 주석을 선언하면 해당 라인의 끝까지 모두 주석처리 되어 프로그램의 흐름에 영향을 줄 수 없게 된다. 다음은 라인 주석의 사용 예를 보여준다.

코드) 라인 주석(Line Comment)
int no = 0; // 임시 변수
//String name = null;


2.5.2  블록 주석(Block Comment)
블록 주석은 여러 라인의 문장을 주석 처리하기 위해 사용하는 주석문이다. 보통 라인 주석으로 처리하기에는 많은 라인을 한번에 주석 처리하기 위해 사용한다. 블록 주석을 선언하기 위해서는 주석 처리하고자 하는 문장들을 로 감싸기만 하면 된다. 블록 주석은 다음과 같이 선언한다.

코드) 블록 주석(Block Comment)
/*
    이 메소드는 테스트를 위해 잠깐 주석 처리함.
    public String getName() {
        return name;
    }
*/


2.5.3  문서화 주석(Documentation Comment)
문서화 주석은 변수, 메소드, 클래스 등에 선언되어 클래스의 역할과 메소드의 기능을 설명하기 위해 사용한다. 문서화 주석은 차후에 도큐먼트 도구(javadoc)를 이용하여 자동으로 자바 API 와 같은 HTML 페이지로 생성할 수 있기 때문에 클래스의 구조를 살펴보는데 매우 유용하게 사용될 수 있다. 문서화 주석은 주석 처리하고자 하는 문장을 로 감싸면 된다. 문서화 주석은 다음과 같이 선언한다.

코드) 클래스 문서화 주석(Class Documentation Comment)

public class UserInfo {
    //...
}


코드) 변수 문서화 주석(Field Documentation Comment)
/ **
 * 사용자 이름
 */   
String name;


코드) 메소드 문서화 주석(Method Documentation Comment) 
/ **
 * 이름과 주민번호를 연결해서 반환한다.
 *
 * @param name 이름
 * @param jumin_id 주민번호
 * @return 이름과 주민번호가 연결된 문자열
 * @see java.lang.String
 */
public String getStr(String name, String jumin_id) {
    returnname + jumin_id;
}

문서화 주석에는 헤더 테그를 제외한 모든 HTML 태그를 사용할 수 있다. 또한 문서화 주석에서 사용할 수 있는 특별한 태그를 제공하고 있는데 이 태그는 @로 시작한다. 이 문서화 주석 태그에는 많은 종류가 있지만 여기서는 가장 자주 사용하는 태그에 대해서만 살펴보도록 하겠다.

 2-4-3-1 문서화 주석 태그

3장  클래스 구성
3.1  소개
클래스는 자바 프로그래밍에서 가장 핵심적인 요소로서 프로그램을 실행하기 위한 최소 단위이다. 그렇기 때문에 클래스를 얼마나 잘 설계하느냐가 전체 프로그램의 완성도를 결정하는 관건이 된다. 자바 프로그래밍에서는 클래스를 작성하는 기본적인 규칙이 있다. 이 규칙을 이 장에서 살펴보도록 하겠다.

그림 3-1-1 클래스 구성도
 자바 클래스는 일반적으로 멤버 변수(Member Variable), 생성자(Constructor), 메소드(Method)로 구성되며 상황에 따라 이 구성 요소들 중 필요 없는 부분은 생략할 수 있다. 그렇기 때문에 멤버 변수만 선언하거나 모든 구성 요소를 선언하지 않는 것이 가능하다. 클래스의 구성 요소 중 멤버 변수에는 static 제한자 선언 여부에 따라 구분되는 클래스 변수(Class Variable)와 인스턴스 변수(Instance Variable)를 선언할 수 있으며 메소드에는 매개 변수(Parameter)와 자동 변수(Automatic Variable)를 선언할 수 있다. 이들 구성 요소에 대한 자세한 설명은 차후에 다시 살펴보기로 하고 여기서는 클래스의 구성 요소를 보여주는 전체 소스 코드를 살펴보는 것으로 클래스에 대한 기본적인 설명을 마칠까 한다. 

코드)
public
 class Account {
    // 클래스 변수
    static int account_no;

    //인스턴스 변수
    private String name;
    private String jumin_id;

    
    public Account() {
        account_no++;
    }

    
    public Account(String name, String jumin_id) {
        account_no++;
        this.name = name;
        thisjumin_id = jumin_id;
    }   

    

    public voidsetJumin_id(String jumin_id) {
        thisjumin_id = jumin_id;
    }
}


3.2  키워드(Keyword)
키워드는 자바에서 특정 용도로 사용하기 위해 미리 예약되어 있는 단어이기 때문에 변수명, 메소드명, 클래스명으로 선언하여 사용할 수 없다. 다음 표와 같이 키워드를 알파벳 순으로 나열해 보면 JAVA 5.0 에 추가된 키워드를 포함해서 총 50가지의 키워드가 예약되어 있는 것을 알 수 있다. 추가적으로 다음 표에 등록되어 있지 않은 true, false, null 은 자바의 키워드는 아니지만 예약어로 등록되어 있기 때문에 프로그램에서 변수명이나 메소드명, 클래스명으로 사용할 수 없다는 것에 주의해야 한다.

표 
3-1-1 키워드 종류
※ const, goto 는 사용되지 않는 키워드이다.
※ strictfp 는 JAVA 1.2 에서 추가된 키워드이다.
※ assert 는 JAVA 1.4 에서 추가된 키워드이다.
※ enum 은 JAVA 5.0(1.5)에서 추가된 키워드이다.

참고로 Byte, Short, Character, Integer, Long, Float, Double은 기본 데이터형에 사용하는 키워드와 이름이 유사하기는 하지만 키워드는 아니다. 이것은 래퍼 클래스(Wrapper Class)로서 기본 데이터형과 관련된 여러 기능들을 묶어 놓은 클래스일 뿐이다.



3.3  식별자(Identifier)
식별자(Identifier)는 클래스, 인터페이스, 메소드, 변수, 문자열, 배열 등을 구분하는 이름을 말한다. 식별자는 개발자 임의로 정의하여 사용할 수 있지만 적어도 아래의 규칙은 지켜서 이름을 작성해야 한다.

정리 식별자 선언 규칙
1. 식별자의 첫 문자에는 숫자가 올 수 없다.
2. 식별자는 알파벳 대문자, 소문자, 숫자, _(밑줄 문자), $(달러 문자)로만 구성해야 한다.
3. 키워드를 식별자로 사용할 수 없다.
4. 공백은 사용할 수 없다.

식별자는 선언 규칙에 따른 특징 이외의 자바 언어의 고유 특징으로 인해 다음과 같은 특징을 추가적으로 가진다.

정리 식별자의 추가 특징
1. 길이 제한이 없다.
2. 대소문자를 구분한다.
3. 유니코드 문자로 선언할 수 있다.


이제 유효한 식별자와 그렇지 못한 식별자를 살펴보도록 하자. 

유효한 식별자(Valid Identifier)
MyProg, Integer, my_prog, $prog, Int,
string, my__prog, _1234

잘못된 식별자(Invalid Identifier)
2test, interface, class, int, char, public, null

Integer는 래퍼 클래스(Wrapper Class)일 뿐 키워드는 아니기 때문에 프로그램에서 사용할 수 있으며 Int도 기본 데이터형인 int와는 다른 단어이기 때문에 식별자로 사용할 수 있다. 그리고 _1234는 식별자 선언 규칙을 지켜 작성된 단어이기 때문에 이상 없이 사용할 수 있다. 하지만 interface나 class와 같은 단어는 키워드이기 때문에 식별자로 사용할 수 없다. 식별자의 첫 문자는 숫자가 올 수 없기 때문에 2test는 식별자가 될 수 없다.

식별자 선언 규칙은 변수나 메소드명을 선언하는 데 있어서 최소한의 규칙이기 때문에 개발자가 원하는 방식으로 변수와 메소드명을 선언하면 가독성이 떨어질 수 있다. 그래서 대부분의 자바 개발자들은 가독성을 높이기 위해 아래와 같은 명명 규칙을 지켜 코드를 작성한다.

정리 식별자 명명 규칙
1. 변수나 메소드의 첫 문자는 소문자로 한다.
2. 클래스나 인터페이스의 첫 문자는 대문자로 한다.
3. 변수와 클래스 이름은 명사로 작성하며 메소드의 이름은 동사로 작성한다.
4. 단어 결합시에는 결합되는 단어를 대문자로 시작하거나 _(밑줄 문자)를 이용하여 단어를 결합한다.
5. 식별자 선언시 되도록 대문자, 소문자, _(밑줄 문자)만을 이용한다.


3.4  데이터형(Data Types)
자바에서 사용할 수 있는 데이터형에는 기본 데이터형(Primitive Data Type)과 참조형(Reference Type)이 있다. 먼저 기본 데이터형은 선언된 변수에 값을 바로 할당하는 int, long, double 형 등을 말하며 참조형은 생성된 객체의 주소만을 변수에 할당하는 데이터형을 말한다. 참조형에는 배열, 클래스, 인터페이스 등이 있다. 이러한 데이터형을 정리하면 다음과 같다.

그림 3-4-1 데이터형 분류
3.4.1  기본 데이터형(Primitive Data Type)
기본 데이터형은 크게 정수형, 실수형, 문자형, 논리형으로 구분할 수 있다. 정수형에는 byte, short, int, long 형이 있으며 실수형에는 float, double 형이 있다. 그리고 문자형에는 char 형이 있으며 논리형에는 boolean 형이 있다. 이러한 자바의 기본 데이터형을 표로 정리하면 다음과 같다.

표 3-2-1-1 기본 데이터형
기본 데이터형은 저장할 수 있는 데이터 범위가 정해져 있기 때문에 데이터 범위를 초과하는 데이터를 저장할 수 없다. 예를 들어서 4Byte 크기를 가지는 int 형에 8Byte 크기를 가지는 데이터를 저장할 수 없다. 그래도 어떻게든 저장하고 싶다면 4Byte 를 버리고 남는 4Byte 만을 저장해야 한다. 이렇게 하기 위해서는 기본 데이터형을 형변환해야 한다. 형변환은 특정 데이터형을 다른 데이터형으로 변환하기 위한 것으로 캐스팅(Casting)과 컨버젼(Conversion)으로 구분할 수 있다. 먼저 캐스팅은 참조형을 변환하는 것을 말하며 컨버젼은 기본 데이터형을 변환하는 것을 말한다. 이에 대해서는 뒤에서 더 자세히 다루도록 하겠다.


이제 이러한 기본 데이터형으로 선언한 변수에 어떻게 값을 저장할 수 있는지를 살펴보도록 하자.

표 3-2-1-2 기본 데이터형 사용 예제
이 표를 통해서 알 수 있는 사실은 숫자 뒤에 숫자의 데이터형을 지정하기 위한 특정 문자를 붙일 수도 있다는 것이다. 예를 들어 long 형에는 대문자(L), 소문자(l)를 붙일 수 있으며 float 형에는 대문자(F), 소문자(f) 를 붙일 수 있다. 마찬가지로 double 형에는 대문자(D), 소문자(d) 를 붙일 수 있다. 만약 이러한 특정 문자를 붙이지 않았을 경우에는 해당 숫자는 자동적으로 데이터형 구분에 맞는 대표 데이터형으로 인식된다. 정수형의 대표 데이터형은 int 형이며 실수형의 대표 데이터형은 double 형이다.


3.4.2  참조형(Reference Type)
참조형은 자바에서 생성된 객체의 주소를 가지고 있는 데이터형을 말한다. 기본 데이터형은 숫자나 문자, 논리값을 직접적으로 가지고 있는 반면에 참조형은 생성된 객체를 직접적으로 가지고 있는 것이 아니라 생성된 객체의 주소만을 가지고 있다. 이와 같은 참조형에는 배열, 클래스, 인터페이스가 있다. 이들을 사용하기 위해서는 먼저 객체를 생성하고 생성한 객체의 주소를 가지고 있는 변수를 이용해서 객체에 접근해야 한다.

참조형의 사용 예제를 보면 다음과 같다.

표 3-2-2-1 참조형 사용 예제
※ 기본 데이터형 이외에는 모두 참조형이다.


3.4.3  배열(Array)
배열은 동일한 형의 데이터를 여러 개 담을 수 있는 구조로서 자바에서는 참조형으로 처리된다. 배열에 대한 이해를 돕기 위해 배열 구조를 설명하는 간단한 그림을 먼저 살펴보도록 하겠다.

다음은 int 형 데이터를 저장하기 위한 1차원 배열 구조를 보여준다. 먼저 int 형 배열을 생성하기 위해 int[] arr = new int[5] 를 선언하다. 여기서 int[5] 는 int 형 데이터를 5개 담을 수 있는 공간을 만들어 놓으라는 얘기이다. 이렇게 선언된 이후에는 배열의 길이는 변경될 수 없다. 그 다음 arr[0] = 5 와 같이 선언하여 생성된 배열 공간에 값을 할당한다. 여기서 배열의 첫 번째 공간은 0번 인덱스로 참조되며 마지막 공간의 인덱스는 (선언된 크기 ? 1)이 된다. 그래서 이 배열의 마지막 공간은 (5 ? 1)이 되어 인덱스 4가 된다. 

그림 3-4-3-1 배열 구조
배열은 선언에 따라 1차원 배열이 될 수도 있고 2차원 배열이 될 수도 있다. 물론 3차원 배열이 될 수도 있다. 하지만 3차원 배열은 사용하기가 복잡하여 자주 사용하지 않는다. 

배열은 다양한 방법으로 선언해서 사용할 수 있다. 다음 그림들은 1차원 배열과, 2차원 배열을 선언하는 방법을 보여준다. 그리고 선언 방법 옆의 표는 해당 코드에 의해 생성된 배열의 인덱스와 데이터를 나타낸다.


그림 3-4-3-2 1차원 배열
지금까지 배열의 구조와 배열을 선언하는 방법에 대해서 살펴보았다. 배열을 선언하는 방법은 변수를 선언하는 방법과는 다르지만 그 방법이 어렵지는 않다는 것을 알 수 있을 것이다. 이제 배열이 가진 특징에 대해서 정리하도록 하자.

배열(Array)의 특징
1. 객체와 배열을 포함한 어떤 데이터형도 저장할 수 있다.
2. 고정 길이를 가지기 때문에 선언된 길이를 수정할 수 없다.
3. 배열의 길이는 배열 속성 변수 length로 알 수 있다.
4. 배열의 데이터는 인덱스로 접근할 수 있다.
5. 배열의 시작 인덱스는 0이며 마지막 인덱스는 배열 길이 - 1이다.
6. 배열은 서로 다른 데이터형을 저장할 수 없다.

배열은 항상 선언된 길이로만 사용할 수 있기 때문에 데이터를 얼마나 저장해야 할 지 모르는 경우에는 배열은 적당하지 않은 방법이다. 이러한 경우에는 이후에 살펴볼 컬렉션 클래스를 사용하는 것이 좋다. 컬렉션 클래스는 개발자가 길이를 지정하지 않아도 자동적으로 길이를 증가시켜 데이터를 계속해서 저장할 수 있도록 설계되어 있기 때문에 배열보다 사용하기가 편한다. 

[TIP&TECH] 
컬렉션 클래스는 java.util 패키지에 있는 Collection 인터페이스를 상속하는 Set, List 인터페이스의 구현 클래스와 Map 인터페이스를 구현하는 클래스들을 말한다. 이에 대한 자세한 내용은 이 책의 컬렉션 부분을 살펴보기를 바란다.

마지막으로 배열을 사용하는 완전한 코드를 보도록 하자.

public class Test {
    public static void main(String[] args) {
        int[] arr = new int[5];
       
        arr[0] = 5;
        arr[1] = 4;
        arr[2] = 3;
        arr[3] = 2;
        arr[4] = 1;

        System.
out.println("array size : " + arr.length);
        System.out.println("arr[3] value : " + arr[3]);
    }
}



3.5  변수(Variable)
변수는 데이터를 저장하기 위한 공간이다. 이 공간에는 기본 데이터형과 참조형 모두 저장될 수 있다. 기본 데이터형이 저장되면 해당 데이터가 그대로 저장되며 참조형이 저장될 경우에는 객체의 주소가 저장된다. 

변수는 접근 범위에 따라 멤버 변수와 자동 변수로 분류할 수 있으며 멤버 변수는 다시 클래스 변수와 인스턴스 변수로 분류할 수 있다.

그림 3-3-1 변수의 종류
3.5.1  멤버 변수와 자동 변수
변수는 선언되는 위치에 따라 멤버 변수와 자동 변수로 분류된다. 멤버 변수(Member Variable)는 메소드와 동등한 레벨에 선언된 변수를 말하며 자동 변수(Automatic Variable)는 메소드와 같은 블록 내에 선언된 변수를 말한다. 자동 변수는 지역 변수(Local Variable) 라 하기도 한다.

멤버 변수와 자동 변수를 소스 코드로 살펴보면 다음과 같다.

1) 멤버 변수
멤버 변수는 메소드와 같이 메소드 레벨에 선언되기 때문에 메소드에서 해당 변수를 참조할 수 있다. 그래서 m1() 메소드에서는 변수 a 와 b 에 접근할 수 있다.

class
 MemberTest {
    int a;
    staticString b;
   
    public voidm1() {
        //....
    }
}
 

2) 자동 변수
자동 변수는 멤버 변수처럼 메소드 레벨이 아닌 메소드와 같은 블록 내에 선언되는 변수를 말한다. 다음의 예제 코드에서 m1() 메소드의 a 와 b 그리고 m2() 메소드의 c 와 d 가 자동 변수가 된다. 자동 변수는 해당 블록 내에서만 접근이 가능하기 때문에 m1() 메소드에서 m2 메소드의 변수에 접근할 수 없으며 반대의 경우도 마찬가지이다. 또한 m2() 메소드의 매개 변수(Parameter)인 int 형 변수 e 는 메소드 내에 선언되는 자동 변수와 비슷한 접근 범위를 가지기 때문에 이 메소드의 외부에서는 이 변수에 접근할 수 없다.

class AutoTest {
    public voidm1() {
        int a;
        String b;
    }

    
public voidm2(int e) {
        int c;
        String d;
    }
}


3.5.2  클래스 변수와 인스턴스 변수
메소드 레벨에 선언되는 멤버 변수는 static 제한자의 선언 여부에 따라서 클래스 변수와 인스턴스 변수로 분류된다. 클래스 변수(Class Variable)는 static 제한자가 선언된 변수를 말하며 인스턴스 변수(Instance Variable)는 static 제한자가 선언되지 않은 변수를 말한다.

클래스 변수는 클래스가 로딩되면서 메모리에 설정되는 변수이기 때문에 객체의 참조 없이도 바로 접근하여 사용할 수 있다. 하지만 인스턴스 변수는 생성된 객체마다 하나씩 존재하는 변수이기 때문에 다른 객체에서는 해당 객체에 대한 참조 없이는 접근할 수 없다.
 
클래스 변수와 인스턴스 변수를 소스 코드로 살펴보면 다음과 같다.

1) 클래스 변수
ClassTest 클래스에 정의되어 있는 클래스 변수인 a 와 b 를 외부에서 접근할 경우에는 ClassTest 클래스에 대한 객체를 생성할 필요 없이 바로 ClassTest.a 와 ClassTest.b 와 같이 접근하면 된다.

class
 ClassTest {
    static int a;
    staticString b;
}


2) 인스턴스 변수
InstanceTest 클래스에 선언되어 있는 인스턴스 변수인 a 와 b 를 외부에서 접근할 경우에는 반드시 InstanceTest 클래스에 대한 객체를 생성한 후 접근해야 한다. 그렇기 때문에 a 와 b 에 접근하는 코드는 (new InstanceTest()).a 와 (new InstanceTest()).b 가 된다.
 
class InstanceTest {
    int a;
    String b;
}


3.5.3  디폴트 값
값이 설정되지 않은 멤버 변수는 자동적으로 디폴트 값으로 초기화가 이루어진다. 디폴트 값을 살펴보면 다음과 같다.

표 3-3-3-1 디폴트 값
※ reference type 은 String, Vector 등 자바의 참조형을 의미한다.

멤버 변수가 아닌 자동 변수는 디폴트로 초기화가 되지 않기 때문에 초기화를 하지 않고 사용하면 에러가 발생함으로 주의해야 한다.

class
 Test {
    int memberA//멤버 변수
   
    // 정상
    public voidtestMember() {   
        int Sum = memberA + 1;
       
        System.out.println(Sum);
    }
 
    //에러 발생
    public voidtestAutomatic() {
        int automaticA; //자동 변수
       
        int Sum = automaticA + 1; //에러 발생
       
        System.out.println(Sum);
    }
}


3.5.4  접근 범위
마지막으로 변수의 접근 범위에 대해서 알아보도록 하자. 이전에 살펴보았듯이 클래스 변수는 모든 인스턴스들이 객체 생성없이 접근할 수 있기 때문에 가장 큰 접근 범위를 가진다. 그리고 인스턴스 변수는 클래스에 선언되어 인스턴스마다 따로 소유되기 때문에 클래스 변수보다는 작은 접근 범위를 가진다. 자동 변수의 일종인 매개 변수는 메소드 시작부터 메소드 종료까지 유효하기 때문에 선언된 시점부터 유효한 자동 변수보다 더 넓은 접근 범위를 가지고 있다. 이러한 변수별 접근 범위를 그림으로 표현하면 다음과 같다.

그림 3-5-4-1 변수 접근 범위
3.6  생성자(Constructor)
생성자(Constructor)란 클래스 초기화를 위한 특별한 메소드이다. 일반적으로 메소드는 객체 생성시 자동으로 호출되지 않지만 생성자는 객체 생성시 자동으로 호출되어 객체가 프로그램에서 사용될 수 있도록 메모리 할당, 변수 초기화 등의 여러 작업을 한다. 생성자의 특징을 정리하면 다음과 같다.

생성자(Constructor) 특징
1. 클래스의 이름과 동일하며 리턴형을 가지지 않는다.
2. 객체 생성시 한 번만 수행된다.
3. final, abstract, native, synchronized 제한자와 함께 선언될 수 없다.
4. 메소드와 유사하지만 메소드처럼 호출 할 수 없다.
5. 매개 변수를 다르게 설정하면 생성자를 여러 개 선언할 수 있다.
6. 생성자를 정의하지 않으면 디폴트 생성자가 자동으로 추가된다.

생성자의 특징을 살펴봤으니 이제 소스 코드를 통해 생성자를 선언하는 방법을 살펴보도록 하겠다.

생성자(Constructor) 예제
public
 classConTest {
    privateString name;
 
    //생성자 1
    publicConTest() {
    }
   
    //생성자 2
    publicConTest(String name) {
        this.name = name;
    }
}

위의 소스 코드에는 두 개의 생성자가 선언되어 있다. 첫 번째 생성자는 어떤 기능도 수행하지 않는 생성자이고 두 번째 생성자는 생성자의 매개 변수의 값을 ConTest 클래스의 String 변수 name 에 할당하는 생성자이다. 이 코드를 봤을 때 생성자의 형식은 리턴형이 선언되어 있지 않다는 것을 제외하고는 메소드와 매우 유사하다는 것을 알 수 있다. 이와 같이 선언된 생성자를 호출하기 위해서는 new 연산자를 사용해야 한다. 생성자와 유사한 메소드는 new 연산자가 아닌 객체를 생성한 후 객체의 참조를 통해 해당 메소드를 호출하면 되지만 생성자는 이와는 다르게 new 연산자에 의해 자동적으로 호출된다.

생성자(Constructor) 호출
public classTest {
    public static voidmain(String[] args) {
        ConTest ct1 = new ConTest(); //생성자 1 호출
        ConTest ct2 = new ConTest("con"); //생성자 2 호출
    }
}
 

3.7  메소드(Method)
객체는 상태와 행동을 표현하기 위한 변수와 메소드로 구성되며 변수는 객체의 상태 정보를 저장하기 위해 사용되고 메소드는 상태를 얻거나 변경하기 위해 사용된다. 

메소드(Method) 특징
1. 코드의 중복을 피하며 재사용성을 높인다.
2. 메소드는 시그니처(Signature)로 구분된다. 
3. 메소드에는 제한자(public, private, protected, static, final, synchronized 등)가 선언될 수 있다.
4. 메소드를 통해 다형성(Polymorphism)을 구현할 수 있다.
5. public static void main(String[] args)로 선언된 메소드는 자바가상머신이 어플리케이션 시작을 위해 가장 먼저 호출하는 메소드이다.

 
[TIP&TECH]
시그니처(Signature)는 메소드의 리턴형(Return Type), 메소드명(Method Name), 매개 변수(Parameter)를 말한다.


3.7.1  메소드의 종류
변수에 인스턴스 변수와 클래스가 변수가 있듯이 메소드에도 인스턴스 메소드(Instance Method)와 클래스 메소드(Class Method)가 있다.

그림 메소드 종류
인스턴스 메소드는 static 제한자가 선언되지 않은 메소드로서 보통 메소드라고 하면 인스턴스 메소드를 가리키며 클래스 메소드는 static 제한자가 선언된 메소드로서 정적 메소드(Static Method)라고도 한다. 결국 인스턴스 메소드와 클래스 메소드의 차이는 static 제한자가 있느냐 없느냐이기 때문에 메소드의 차이는 static 제한자의 특성에 따른다고 볼 수 있다. 일단 이 메소드들이 어떻게 생겼는지 소스 코드를 보도록 하자.

인스턴스 메소드(Instance Method)
 public class Test {
    public int add(int a, int b) {
        return a + b;
    }
} 

클래스 메소드(Class Method)
public class Test {
    public static int add(int a, int b) {
        return a + b;
    }
}

static 제한자에 대해서는 [4.5 static 제한자(static Modifier)]에 자세히 나와 있으니 이를 참고하기로 하고 여기서는 인스턴스 메소드와 클래스 메소드의 특징에 대해서 간단히 정리해보도록 하자. 

인스턴스 메소드(Instance Method)
1. static 제한자를 선언하지 않은 메소드를 말한다.
2. 인스턴스 메소드는 인스턴스 변수와 인스턴스 메소드에 바로 접근할 수 있다.
3. 인스턴스 메소드는 클래스 변수와 클래스 메소드에 바로 접근할 수 있다.


클래스 메소드(Class Method)
1. static 제한자를 선언한 메소드를 말한다.
2. 클래스 메소드는 클래스 변수와 클래스 메소드에 바로 접근할 수 있다.
3. 클래스 메소드에서 인스턴스 변수와 인스턴스 메소드에 접근하기 위해서는 해당 객체의 레퍼런스가 필요하다.
4. 클래스 메소드에서는 this 키워드를 사용할 수 없다.
※ 우리가 이전에 작성했던 main 메소드가 클래스 메소드의 대표적인 예이다.


3.7.2  main 메소드
자바에는 프로그램 시작을 위해 반드시 작성해야 하는 메소드가 있다. 이 메소드는 main 메소드로서 항상 정해진 형식으로 선언해야 한다. 그렇지 않을 경우 자바가상머신은 main 메소드를 인식하지 못해 프로그램을 시작하지 못한다. 다음은 main 메소드의 형식을 보여준다.

코드) main 메소드
public classTest {
    public static voidmain(String[] args) {
           //...
    }
}
 

main 메소드의 접근 제한자는 어떤 외부 클래스에서도 호출할 수 있도록 public 제한자로, 객체 생성 없이 main 메소드를 바로 호출할 수 있도록 static 제한자로 선언해야 한다. 그리고 이 메소드는 어떤 값도 반환하지 않기 때문에 void 로 선언해야 한다. 마지막으로 사용자 입력값을 처리하기 위해 String[] args 가 선언되어야 한다. 이때 args 는 단순한 변수명이기 때문에 어떤 이름으로 선언해도 무방하다.

여기서 여러분들은 public 과 static 제한자가 어떤 것인지 궁금해 할 것이다. 이에 대해서는 차후에 다시 설명하도록 하겠으니 여기서는 main 메소드는 반드시 public static void 로 선언되어야 한다는 것만 기억하도록 하자.

마지막으로 main 메소드를 사용하는 소스 코드를 살펴보도록 하겠다. 이 소스 코드는 사용자로부터 두 개의 문자열을 입력받아 화면에 출력한다.

코드) main 메소드 예제
public classTest {
    public static voidmain(String[] args) {
        if (args.length != 2) {
            System.out.println("두 개의 문자열을 입력해주세요!!");
            return;
        }
 
        System.out.println("1: " + args[0]);
        System.out.println("2: " + args[1]);
    }
}

코드) main 메소드 예제 실행 결과
c:\work>javac Test.java
c:\work>java Test easy java
1: easy
2: java

 

3.7.3  오버로딩(Overloading)
오버로딩(Overloading)이란 한 클래스 내에서 동일한 이름의 메소드를 여러 개 정의하는 것을 말한다. 오버로딩을 통해 메소드의 이름은 동일하면서 매개 변수의 개수나 데이터형이 서로 다른 메소드들을 여러 개 정의할 수 있다. 이렇게 정의된 대표적인 메소드가 우리가 자주 사용하는 println() 메소드이다. 이 메소드는 매개 변수의 데이터형에 상관없이 화면에 해당 값을 출력하는 기능을 가진다. 자바 API 에 정의된 println() 메소드를 살펴보면 다음과 같다. 

void println()
void println(boolean x)
void println(char x)
void println(char[] x)
void println(int x)
void println(long x)
void println(float x)
void println(double x)
void println(String x)
void println(Object x)

※ 직접 이 메소드들을 확인하고 싶다면 자바 API 에서 java.lang.System 클래스의 out 변수를 클릭하면 된다.

println() 메소드처럼 오버로딩을 적용하여 메소드를 작성하면 동일한 메소드명으로 다양한 데이터를 받아서 처리할 수 있다. 이 메소드를 사용하는 간단한 예제 코드를 보도록 하자.

class
 Test {    
    public static void main(String[] args) {
        System.out.println(10);       

        System.out.println("test");
    }
}


오버로딩을 하기 위해서는 다음과 같은 규칙을 반드시 지켜야 한다. 만약 다음과 같은 규칙을 지키지 않고 메소드를 선언하면 메소드가 오버로딩된 것이 아니라 단순히 메소드가 선언된 것에 불과하다.
 
오버로딩(Overloading) 규칙
1. 메소드는 동일한 이름으로 선언되어야 한다.
2. 매개 변수의 개수나 데이터형이 서로 달라야 한다.


오버로딩 규칙을 적용해 메소드를 선언하면 다음과 같다.

코드) 오버로딩(Overloading) 예제
public classOverloadingTest {
    public intadd(int a, int b) {
        return a + b;
    }

    
public float add(float a, float b) {
        return a + b;
    }

    
public doubleadd(double a, double b, double c) {
        return a + b + c;
    }
}

OverloadingTest 클래스에는 오버로딩 규칙을 적용해 작성된 3개의 메소드가 선언되어 있다. 모두 같은 이름을 가졌기 때문에 어떤 매개 변수가 들어오는지에 따라 호출되는 메소드가 달라진다. int 형 매개 변수가 지정되면 첫 번째 메소드가 호출되며 float 형 매개 변수가 지정되면 두 번째 메소드가 호출된다. 그런데 만약 a, b 변수 모두 long 형으로 지정되면 어떤 메소드가 호출될까? 이때는 long 형을 표현할 수 있는 float 형 매개 변수를 가진 두 번째 메소드가 호출된다. 


3.7.4  오버라이딩(Overriding)
오버라이딩(Overriding)은 부모 클래스에 이미 선언되어 있는 메소드를 하위 클래스에서 재정의하는 것을 말한다. 오버라이딩은 부모 클래스의 형질을 물려받았지만 부모 클래스의 메소드를 그대로 사용하지 않고 특정 기능을 추가하여 사용거나 새로 정의하여 사용하고자 할 때 사용하는 방법이다. 오버로딩과 마찬가지로 오버라이딩을 하기 위해서는 다음과 같은 규칙을 지켜야 한다.

오버라이딩(Overriding) 규칙
1. 상위 클래스의 메소드 이름, 리턴형, 매개 변수의 개수, 데이터형이 일치해야 한다.
2. 접근 제한자는 범위가 같거나 더 넓은 접근 제한자로 선언해야 한다.
3. 상위 클래스에서 private, final 접근 제한자로 선언된 메소드는 오버라이딩할 수 없다.
4. 상위 클래스에서 static 제한자로 선언된 메소드를 오버라이딩할 때는 반드시 static 제한자를 선언해야 한다.

오버라이딩 규칙을 적용해 메소드를 선언하면 다음과 같다.

코드) 오버라이딩(Overriding) 예제
public classSuperClass {
    public intadd(int a, int b) {
        return a + b;
    }
 
    public static longadd2(long a, long b) {
        return a + b;
    }
}

public
 classSubClass extends SuperClass {
    public intadd(int a, int b) {
        return a - b;
    }

    
public static longadd2(long a, long b) {
        return a - b;
    }
}
 
이 소스 코드에서 SubClass 는 SuperClass 를 상속하고 있다. 그렇기 때문에 SubClass 에서 add() 메소드를 선언하지 않아도 SuperClass 의 add() 메소드를 사용할 수 있다. 하지만 이 코드에서는 상위 클래스의 add() 메소드를 하위 클래스인 SubClass 에서 오버라이딩하고 있다. 그렇기 때문에 하위 클래스의 add() 메소드를 호출하면 더하기 연산이 수행되는 것이 아니라 빼기 연산이 수행된다. 마찬가지로 add2() 메소드도 오버라이딩 되어 있다. 다만 이 메소드는 상위 클래스에 static 으로 선언되어 있기 때문에 하위 클래스에서도 static 제한자로 선언되어 있다.



3.8  가변 매개 변수(Varags)
JDK 5.0 에 추가된 가변 매개 변수(Varargs, 가변 인수라고도 함)는 변하기 쉬운(Variable)과 매개 변수(Arguments)를 합쳐서 만든 용어로서 매개 변수가 변할 수 있다라는 것을 함축하고 있다. 용어 이름에서 알 수 있듯이 매개 변수를 가변적으로 처리할 수 있다는 것은 배열이나 컬렉션을 사용하지 않고도 여러 인수를 메소드에 넘겨줄 수 있다는 것을 의미한다. 

[TIP&TECH]
메소드에 선언되어 있는 변수를 매개 변수(Parameter), 이 변수를 통해 실제 넘어오는 값을 인수(Arguments, 인자라고도 함)라고 이 둘을 서로 구분한다. 하지만 이 둘을 구분하지 않고 동일하게 매개 변수라고 해도 된다. 왜냐하면 Parameter 와 Arguments 는 형식 매개 변수냐 실 매개 변수냐의 차이일 뿐, 모두 매개 변수이기 때문이다.

다음 코드를 보자. 다음은 args 매개 변수로 넘어온 값에 따라 화면에 출력하는 변수의 개수를 다르게 하여 화면에 출력하기 위한 코드이다. 이 코드에서 print() 메소드를 여러분이 작성해야 한다면 어떻게 작성하겠는가?

public class Test {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int c = 3;
       
        if (args[0].equals("1")) {
            print(a);
        } else if (args[0].equals("2")) {
            print(a, b);
        } else if (args[0].equals("3")) {
            print(a, b, c);
        }
    }
}


대부분이 print() 메소드를 다음처럼 작성했을 것이다. 이 방법이 잘못된 것은 아니지만 좀 불편해 보이지 않는가?

public static void print(int a) {
    System.out.println(a);
}

public static void print(int a, int b) {
    System.out.println(a);
    System.out.println(b); 
}

public static void print(int a, int b, int c) {
    System.out.println(a);
    System.out.println(b);
    System.out.println(c);
}

이렇게 메소드를 오버로딩(overriding)해서 작성하면 원하는 결과를 얻을 수 있지만 매개 변수의 수가 4개, 5개 이상을 넘어가게 되면 해당 매개 변수의 개수를 처리할 수 있는 메소드도 더 추가되어야 한다. 이러한 불편함을 없앨 수 있는 방법이 바로 가변 매개 변수이다. 가변 매개 변수는 "..."를 사용하여 선언할 수 있으며 이를 사용하면 매개 변수의 수가 얼마나 증가하느냐에 상관 없이 하나의 메소드에서 모두 처리할 수 있다. 위 코드를 가변 매개 변수를 사용해서 변경하면 다음과 같다.

public static void print(int ... vars) {
    for (int var : vars) {
        System.out.println(var);
    }
}
※ 이 코드에서 사용한 for문은 향상된 for문이다. 이에 대해서는 "6장 제어문"에서 살펴보기 바란다.

메소드를 오버로딩하는 것보다 가변 매개 변수를 사용하는 것이 코드를 더 짧고 이해하기 쉽게 한다. 이러한 가변 매개 변수의 구조를 정리하면 다음과 같다.
사용 예)
public static void print(int ... args) {}
private void print(String ... vars) {}
String process(String ... names) {}


3.9  리터럴(Literal)
리터럴(Literal)은 대입문의 오른쪽에 위치하는 값으로서 개발자가 변수에 값을 대입하기 위해 선언하는 값을 말한다. 다음 코드에서 10, 'a', "java"를 리터럴이라고 한다.

코드) 리터럴
int i = 10;
char c = 'a';
String s = "java";

이러한 리터럴은 크게 기본 데이터형과 문자열로 분류할 수 있다.

그림 리터럴(Literal) 종류
3.10  정적 임포트(Static Import)
우리가 일반적으로 클래스를 사용하기 위해 선언하는 임포트(import)와 비슷한 기능을 하는 정적 임포트가 JDK 5.0 에 새로 추가되었다. 이름에서 알 수 있듯이 정적 임포트도 무언가를 프로그램에서 사용하기 위해 선언한다. 먼저 일반적인 임포트를 사용하는 코드를 보도록 하자.

코드)
import java.util.ArrayList;
 
public class Test {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
       
        java.util.Vector vec = new java.util.Vector();
    }
}

소스 코드는 java.util 패키지의 ArrayList 클래스와 Vector 클래스를 사용하고 있다. 여기서 ArrayList 클래스는 임포트를 사용하였기 때문에 main() 메소드 내에서는 전체 패키지명을 선언하지 않았다. 하지만 Vector 클래스는 임포트문을 사용하지 않았기 때문에 main() 메소드 내에서 Vector 의 전체 패키지명을 선언하고 있다. 이 코드를 통해서 우리는 임포트는 패키지 명을 사용하지 않고 해당 클래스를 사용할 수 있게 해준다는 것을 알 수 있다. 그러면 정적 임포트는? 아직 정적 임포트가 어떤 기능을 하는지는 살펴보지 않았지만 다음 코드를 보면 정적 임포트가 무엇인지 감이 잡힐 것이다.

코드)
import java.lang.Integer;
import static java.lang.Integer.MAX_VALUE;
 
public class Test {
    public static void main(String[] args) {
        int max = MAX_VALUE;
        int min = Integer.MIN_VALUE;
    }
}

이 코드를 보면 정적 임포트로 Integer 클래스의 정적 변수인 MAX_VALUE 를 임포트하고 있다. 그래서 main() 메소드에서는 이 변수의 클래스명을 지정하지 않고도 사용할 수 있다. 하지만 정적 임포트로 임포트하지 않은 MIN_VALUE 는 해당 클래스명을 선언해야 한다. 이 코드를 통해 여러분은 정적 임포트는 정적 변수를 임포트해서 해당 클래스명을 지정하지 않고도 정적 변수를 사용할 수 있게 한다는 것을 알았을 것이다. 추가적으로 정적 임포트는 정적 변수에만 사용할 수 있는 것은 아니다. static 으로 선언된 정적 메소드에도 사용할 수 있다. 정적 메소드를 임포트하는 것은 정적 변수를 임포트하는 것이랑 동일하다.

코드)
import
 static java.lang.Math.abs;
 
public class Test {
    public static void main(String[] args) {
        int a = abs(-10);
    }
}

정리하면, 일반 임포트(import)는 패키지명을 선언하지 않고도 클래스를 사용할 수 있게 하는 것이고 정적 임포트(static import)는 클래스명을 선언하지 않고도 정적 변수와 정적 메소드 같은 정적 멤버(static member)를 사용할 수 있게 하는 것이다.

정적 임포트의 구조를 정리하면 다음과 같다.
정적 임포트를 사용한 간단한 예제 코드를 작성하면 다음과 같다.
import static java.lang.Math.*;      //Math 클래스의 모든 메소드 임포트
import static java.lang.Math.max;   //Math 클래스의 max 메소드 임포트
import static java.lang.Math.pow;   //Math 클래스의 pow 메소드 임포트


3.11  타입 안전 열거형(Typesafe enums)
타입 안전 열거형(Typesafe enums)은 기존의 열거형이 타입에 안전하지 못했던 것을 개선하여 타입에 안전하도록 JDK 5.0 부터 추가한 기능이다. 먼저 상수를 이용한 기존의 열거형을 보도록 하자.

코드) 
public class Test {
    public static void main(String[] args) {
        int selMovie = 1; //고객이 선택한 영화 종류 번호
        int loanDay = 0; //대여일
       
        //고객이 선택한 영화가 액션이라면
        if (selMovie == Comics.ACTION) {
            //대여일은 1
            loanDay = 1;
        } else {
            //대여일은 3
            loanDay = 3;
        }       
    }
}
 
//만화 종류 열거형
interface Comics {
    public static final int FANTASY = 0;
    public static final int ACTION = 1;
    public static final int DRAMA = 2;
    public static final int SPORTS = 3;
}
 
//영화 종류 열거형
interface Movie {
    public static final int COMEDY = 0;
    public static final int DRAMA = 1;
    public static final int HORROR = 2;
    public static final int ACTION = 3;
}
※ 
상수를 이용한 열거형이기 때문에 고객이 선택한 영화 종류 번호를 만화 종류 열거형과 비교해도 에러가 발생하지 않음.

기존의 열거형은 열거형이라고 하기 보다는 단순히 인터페이스에 상수를 선언한 것에 불과하다. 그래서 인터페이스에 선언된 상수들의 의미에 상관 없이 해당 상수들이 갖고 있는 숫자만 일치하면 해당 상수의 의미와 동일한 것으로 프로그램이 판단하는 문제가 있었다. 이를 타입에 안전하지 않다고 하며 이를 개선한 것이 타입 안전 열거형이다. 타입 안전 열거형을 사용하여 위의 코드를 변경해 보도록 하자. 

public
 class Test {
    public static void main(String[] args) {       
        Movie selMovie = Movie.DRAMA//고객이 선택한 영화 종류 번호
        int loanDay = 0; //대여일       

        
//고객이 선택한 영화가 액션이라면
        if (selMovie == Movie.ACTION) {
            //대여일은 1
            loanDay = 1;
        } else {
            //대여일은 3
            loanDay = 3;
        }       
    }
}
 
//만화 종류 타입 안전 열거형
enum Comics {FANTASYACTIONDRAMASPORTS};
 
//영화 종류 타입 안전 열거형
enum Movie {COMEDYDRAMAHORRORACTION};

※ Movie selMovie = Movie.DRAMA : 고객이 선택한 영화 종류도 타입 안전 열거형으로 설정
※ selMovie == Movie.ACTION : 영화 타입 안전 열거형과 비교하지 않을 경우 컴파일 에러 발생


기존의 열거형을 사용하면 열거형의 타입이 다르더라도 정상적으로 컴파일되고 실행되지만 타입 안전 열거형을 사용하면 열거형의 타입을 다르게 사용할 경우 바로 컴파일 에러가 발생한다. 이렇게 컴파일 시간에 오류를 찾아낼 수 있다는 것은 실제 실행 중에는 더 견고하게 동작할 수 있다는 것을 의미한다. 반면에 기존 열거형을 사용할 경우에는 프로그램 실행 중에도 에러가 발생하지 않으므로 오랜 시간이 지난 후에야 프로그램이 이상하게 동작하는 것을 발견할 위험성도 있다. 



지금까지 살펴본 타입 안전 열거형의 구조를 정리하면 다음과 같다.
타입 안전 열거형을 사용한 간단한 예제 코드를 작성하면 다음과 같다.
enum Season {SPRINGSUMMERAUTUMNWINTER}
enum Car {SONATAAVANTEACCENTWINSTORM}


3.12  어노테이션(Annotations)
어노테이션은 자바에서 메타데이터를 사용하기 위한 기능이다. 어노테이션은 패키지, 생성자, 메소드, 변수 등에 선언할 수 있는 변경자(modifier)로서 내장(Builtin) 어노테이션을 사용할 수도 있으며 직접 커스텀(Custom) 어노테이션을 작성하여 사용할 수도 있다. 이 장에서는 쉽게 사용해 볼 수 있는 내장 어노테이션에 대해서 살펴보도록 하겠다.

[TIP&TECH]
메타데이터(Metadata)는 데이터에 대한 데이터라는 의미로서 문서화, 컴파일러 체크, 코드 분석 등에 유용하게 사용된다.

어노테이션의 표기법은 문서화 주석을 선언하듯이 어노테이션 이름 앞에 @(at)를 붙이면 된다.
이러한 어노테이션의 용도를 간단히 정리하면 다음과 같다.

1) 컴파일러에게 정보 제공
    컴파일 시간에 에러를 검사하거나 경고를 감추게 하기 위해 사용할 수 있다.

2) 컴파일 시간과 디플로이 시간 처리
    어노테이션 정보를 이용해 코드, XML 파일 등을 생성할 수 있다.
   
3) 실행 시간 처리
    몇몇 어노테이션은 실행 시간에 검사 목적으로 사용될 수 있다.



3.12.1  내장 어노테이션(Builtin Annotations)의 종류
내장 어노테이션에는 Override 어노테이션, Deprecated 어노테이션, SuppressWarning 어노테이션이 있다. 이 어노테이션들에 대해서 하나씩 살펴보도록 하겠다.


1) Override 어노테이션(@Override)
Override 어노테이션은 메소드에 선언되어 해당 메소드가 상위 클래스의 메소드를 오버라이드 한다는 것을 명시한다. 그렇기 때문에 이 어노테이션이 선언된 메소드가 상위 클래스의 메소드를 오버라이드하지 않는 경우에는 컴파일시 에러가 발생한다. 다음 코드는 @Override 어노테이션을 사용하여 java.lang.Object 객체에 있는 toString() 메소드를 오버라이드하는 코드이다. 하지만 이 코드에서는 toString() 메소드를 tostring() 메소드로 잘못 선언하였기 때문에 컴파일 시간에 오버라이드가 되지 않았다는 에러가 발생한다.

[TIP&TECH]
java.lang.Object 는 모든 클래스의 최상위 클래스로서 명시적으로 상속하지 않아도 자동적으로 상속되는 클래스이다.

코드)
class
 AnoTest {
    @Override
    public String tostring() {
        return super.toString() + " annotation";
    }
}

C:\Test>javac AnoTest.java
Test.java:2: method does not override a method from its superclass
    @Override
     ^
1 error

Override 어노테이션을 선언하지 않았을 경우에는 위와 같이 컴파일 시간에 에러가 발생하지 않는다. 그렇기 때문에 오버라이드해서 java.lang.Object 클래스에 있는 toString() 메소드의 기능이 아닌 AnoTest 클래스만의 toString() 메소드로 처리하고자 했던 기능을 사용할 수 없게 된다. 이는 차후 프로그램 실행 시간에 프로그램이 원하는 데로 동작하지 않는 원인을 찾기 위한 디버깅 시간을 소비해야 한다는 것을 의미한다. 그렇기 때문에 명시적으로 이 메소드는 오버라이드 한다는 것을 어노테이션을 통해 자바 컴파일러에게 알려준다면 메소드 이름을 잘못 선언하여 발생하는 문제를 미연에 방지할 수 있다. 


2) Deprecated 어노테이션(@Deprecated)
Deprecated 어노테이션은 더 이상 사용되지 말아야 하는 메소드에 선언한다. 그렇기 때문에 이 어노테이션이 선언된 메소드를 호출할 경우에는 컴파일 시간에 경고가 발생한다. 

다음 코드를 보자. 이 코드에서 AnoTest 클래스의 tostring() 메소드에 @Deprecated 어노테이션이 선언됐는데도 불구하고 Test 클래스에서 해당 메소드를 호출하고 있다. 그렇기 때문에 이 코드를 컴파일하면 경고 메시지가 발생한다.

코드)
public class Test {
    public static void main(String[] args) {
            AnoTest ano = new AnoTest();
            System.out.println(ano.tostring());
    }
}
 
class AnoTest {
    @Deprecated
    public String tostring() {
        return super.toString() + " annotation";
    }
}

C:\Test>javac -Xlint Test.java
Test.java:6: warning: [deprecation] tostring() in AnoTest has been deprecated
            System.out.println(ano.tostring());
                                  ^
1 warning

[TIP&TECH]
javac 의 ?Xlint 옵션은 컴파일 시간에 발생한 경고 메시지에 대한 상세 정보를 얻고자 할 때 사용한다. 이 옵션은 ?Xlint 와 같이 단독으로 사용할 수도 있고 ?Xlint:deprecation 나 -Xlint:unchecked 와 같이 얻고자 하는 특정 유형을 지정하여 사용할 수도 있다.


3) SuppressWarnings 어노테이션(@SuppressWarnings)
SuppressWarning 어노테이션은 이름에서 알 수 있듯이 경고를 감추기 위한 어노테이션이다. 이 어노테이션은 클래스나 메소드에 선언할 수 있다.
SuppressWarning 어노테이션은 자바 언어 스펙에 지정된 unchecked 와 deprecation 와 같은 경고문을 감출 수 있다. 다음은 SuppressWarning 어노테이션을 사용하는 다양한 방법을 보여준다.

@SuppressWarnings("unchecked")
@SuppressWarnings("deprecation")
@SuppressWarnings(value={"unchecked"})
@SuppressWarnings(value={"deprecation"})
@SuppressWarnings(value={"unchecked", "deprecation"})


이제 SuppressWarning 어노테이션을 사용하는 다음 예제 코드를 살펴보도록 하자. 다음 코드를 저장하고 컴파일하면 어떻게 될까? unchecked 경고 메시지가 출력된다. 이 경고 메시지가 출력되는 이유는 List 컬렉션에 저장될 타입을 지정하지 않았기 때문이다.

코드)
public
 class Test {
    public static void main(String[] args) {
            java.util.List list = new java.util.ArrayList();
            list.add("1");
            list.add("2");
    }
}

C:\Test>javac -Xlint Test.java
Test.java:4: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.List
            list.add("1");
                    ^
Test.java:5: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.List
            list.add("2");
                    ^
2 warnings

이러한 경고 메시지를 보기 싫은 경우에는 SuppressWarning 어노테이션을 클래스나 메소드에 선언하면 된다. 만약 다음과 같이 선언했는데도 불구하고 해당 경고가 사라지지 않는다면 설치된 JDK 의 버전을 확인해야 한다. 왜냐하면 JDK 1.5.0_06 이전 버전에서는 SuppressWarning 어노테이션을 지원하지 않았기 때문이다.

@SuppressWarnings
(value={"unchecked"})
//@SuppressWarnings("unchecked") 와 같이 사용할 수도 있음.
 
public class Test {
    public static void main(String[] args) {
            java.util.List list = new java.util.ArrayList();
            list.add("1");
            list.add("2");
    }
}

C:\Test>javac -Xlint Test.java
※ ?Xlint 는 unchecked 에 대한 상세 정보를 보기 위한 옵션이다.