1996년 3월 19일 첫 출판, 1998년 6월 13일 최종 갱신


앞 페이지 뒷 페이지 색인


자바의 주요 문법

자바는 C/C++에서 문법을 빌어온 언어이다. C 프로그래머는 자바 코드가 하는 일을 어렵잖게 이해할 수 있을 것이며, 특히 C++ 프로그래머는 몇 가지 개념 차이만 이해하면 쉽게 자바 프로그래밍을 할 수 있다. 썬에서 제공하는 자바 언어 명세(Java Language Specification) 문서를 한 번 읽어보는 것이 큰 도움이 될 것이다. http://java.sun.com/docs/books/jls/html/index.html에서 구할 수 있을 것이다.

기본 유형

자바의 세계는 객체로 시작해서 객체로 끝난다.

실제 자바에 사용되는 모든 유형은 객체이며 클래스이다. 자바의 모든 클래스는 클래스 상속 계층 구조에서 최상위에 위치하는 (즉, 족보에서 시조 할아버지 위치에 존재하는) java.lang.Object 클래스로부터 파생된다. (java.lang.Object에서 클래스 이름은 Object이며 클래스가 소속되어 있는 패키지 이름이 java.lang이다. 뒤에서 다시 언급하겠지만 java.lang 패키지는 자바 언어의 핵심적인 클래스들이 정의되어 있다.)

그런데, 예외 없는 법칙이 없듯이 몇 가지 예외가 있다. 즉, 다음에 언급되는 유형은 아주 자주 사용되는 유형들로 퍼포먼스 등의 이유로 객체로 처리되지 않도록 설계되었다. 하지만, 이들 각 기본 유형도 대응되는 클래스가 있어 (즉, 예를 들어 정수를 나타내는 int 기본 유형에는 대응되는 Integer 클래스가 있어 상호 변환할 수 있게 되어 있다.) 객체 세계를 완전히 떠나지는 않는다. 이들 기본 유형은 C나 C++와 달리 시스템에 관계없이 유형 정의가 엄격하게 유지되도록 하여 기계 중립적인 속성을 유지한다.

논리값 유형

boolean : 논리값으로 다른 기본 유형에서 형 변환할 수 없다. true 혹은 false 값을 가진다. C/C++ 프로그래머는 boolean 유형의 값인 true나 false가 정수값인 1과 0으로 변환될 수 없음에 주의하라.

문자 유형

char : 16비트 유니코드 문자이다. C/C++ 프로그래머는 자바에서 char 유형과 byte 유형이 서로 다름에 유의해야 한다. 자바는 유니코드를 지원하기 위해 문자 하나가 16비트로 표현되며 내부적으로 16비트 부호없는 정수로 처리된다. char와 정수 유형은 상호 유형 변환(type casting)이 가능하다.

정수 유형

byte : 부호 있는 8비트 정수 값이다.
short : 부호 있는 16비트 정수 값이다.
int : 부호 있는 32비트 정수 값이다. int가 기계에 관계없이 32비트로 고정되었다는 것을 C/C++ 프로그래머는 주목할 필요가 있다.
long : 부호 있는 64비트 정수 값이다. long이 기계에 관계없이 64비트로 된 것은 현대 운영 체제들의 변화에 따른 것이다. 64비트 정수 연산의 필요성이 종종 대두되고 있고 32비트 기계에서 long과 int가 모두 32비트로만 되던 C/C++ 언어의 문제점을 현대에 등장한 자바는 수정한 셈이다.

부동 소수점 유형

float : 32비트 IEEE754 표준 부동 소수점 값이다.
double : 64비트 IEEE754 표준 부동 소수점 값이다.

클래스 선언문

앞에서 잠깐 클래스에 대해서 언급했지만 C나 파스칼 같은 절차적 혹은 구조적 프로그래밍에 익숙해 있던 프로그래머에게 가장 생소한 개념은 역시 객체와 클래스일 것이다. 자바에서는 앞의 예외를 제외하면 모든 것이 객체로 존재한다. 좁은 의미의 객체는 클래스 인스턴스를 뜻하는데 인스턴스란 해당 클래스 유형의 변수를 뜻한다고 생각하면 무방하다. 자바의 실행 파일은 파일 하나당 하나의 클래스가 선언되어 있는 클래스 파일임을 생각해보면 자바가 모두 클래스 위주로 구성되어 있다는 것을 쉽게 느낄 수 있다.

새로운 클래스 유형을 선언하려면 반드시 존재하는 어떤 다른 부모 클래스(super-class)로부터 상속받아야 한다. 부모 클래스가 없는 클래스 유형은 단 하나, 자바에서 시조 할아버지 클래스에 해당하는 java.lang 패키지의 Object 클래스이다. 다음은 클래스 선언문의 일반 형식이다. 대괄호 안의 내용은 생략 가능하다.

['클래스 제한자'] class '클래스 이름' [extends '부모 클래스 이름'] [implements '인터페이스 이름들'] {
        ... 클래스 내용 ...
}

예) JDK의 jdk/demo/Blink 디렉토리의 Blink.java 파일에서

public class Blink extends java.applet.Applet implements Runnable {
// Blink 클래스는 애플릿이므로 Applet 클래스로부터 상속을 받는다. 
// 뒷부분의 implements Runnable 구문은 아래에서 다룰 인터페이스 구현 선언문이다.
        ...
}

위의 예에서 부모 클래스 이름은 java.applet.Applet이다. 자바에서 클래스 이름에 '.'이 들어간 것은 디렉토리 구분자(유닉스에서 '/'를, 윈도우에서는 '\'을 사용한다.)와 유사한 의미를 가진다. 디렉토리 개념을 패키지라는 개념에 차용하고 있는 셈이다. 패키지는 아래에서 다시 설명한다. JDK 1.0, 1.1에는 자바의 표준 클래스들이 java/lib/classes.zip 파일에 압축되어 있다. 이 압축을 풀어 보면 하위 디렉토리에 java/applet/Applet.class가 있는 것을 볼 수 있다.
클래스 제한자(class modifier)는 해당 클래스에 대해 접근 권한 수준을 지정하는 것으로 객체 지향 프로그래밍에서 불필요한 내부 사정은 외부에 보이지 않도록 하는 캡슐화(Encapsulation)에 해당하는 것이다. C++의 접근 제한자와 거의 유사하다.
자바의 클래스 제한자에는 public, abstract, final만이 올 수 있다.

public : 클래스에 대한 접근 제한자로 클래스 패키지 외부의 코드가 사용할 수 있는 클래스이다. 하나의 소스 파일에는 최대 하나의 public 클래스만 허용되며 소스 파일의 이름은 반드시 이 클래스의 이름과 같아야 한다. 즉, 위 예제에서 public 클래스가 Blink이므로 소스 파일 이름도 Blink.java이다. (윈도우 환경에 익숙한 프로그래머는 자바가 대소문자 구별을 엄격하게 함에 주의하라. 대소문자가 잘못되면 컴파일할 수가 없다.)
public 제한자가 없는 경우(기본 접근 권한 혹은 패키지 권한): 현재 클래스와 동일한 패키지 내부에서 사용 가능한 클래스이다.
abstract : 추상 메소드(abstract method-실행문이 없는 메소드)를 포함하거나 직접 실행되지 않는 클래스이다. C++와 추상 클래스와 거의 같다.
final : C++에 없는 개념으로 자식 클래스를 만들 수 없는 클래스이다.

C++와 마찬가지로 클래스 자신을 가리킬 때에는 this라는 예약어를 사용할 수 있다. 게다가 자바에서는 부모 클래스를 가리킬 때 super라는 예약어를 사용할 수 있어 편리하다. 주의할 점은 C++에서의 this는 포인터 개념이지만 자바에서는 인스턴스 개념이라는 점이다. 그리고 생성자(constructor)를 클래스 내의 다른 메소드에서 호출할 때에도 this 예약어를 사용한다.

클래스 메소드와 필드

C++에서는 클래스가 상태를 나타내는 멤버와 행위를 나타내는 멤버 함수로 구성된다. 자바의 클래스에서는 멤버를 필드(field), 멤버 함수를 메소드(method)로 부른다. C++의 멤버 접근 제한과 마찬가지로 자바의 필드와 메소드 차원에서도 public, protected, private 제한자(modifier)를 지정할 수 있다.
자바에서 메소드를 선언하려면 클래스 내부에 다음과 같은 형식으로 한다.

'메소드 제한자' '반환 유형' '메소드 이름'('인자 유형 1' '인자 1', ...) {
        ... 메소드 내용 ...
}

예) Blink.java 파일에서

public void paint(Graphics g) {
        ...
}

필드 및 메소드 제한자의 종류와 의미는 다음과 같다.
필드 제한자

    public protected private (접근 제한자)
    final static transient volatile
메소드 제한자
    public protected private (접근 제한자)
    abstract static final synchronized native

public : C++의 멤버 접근 제한과 유사하다. 즉, public은 다른 클래스에서 자유롭게 접근할 수 있다.
protected : C++의 멤버 접근 제한과 유사하지만, 자바에서는 패키지와 관련된 새로운 의미를 가진다. protected 멤버 필드 혹은 메소드는 같은 패키지의 클래스들에서 접근할 수 있고, 또 다른 패키지일지라도 자식 클래스에서 접근할 수 있다.
private : C++의 멤버 접근 제한과 유사하다. 즉, private인 경우 자기 클래스 내부에서만 접근할 수 있다.
위의 제한자가 없는 경우(기본 제한 혹은 패키지 제한): 같은 패키지 안의 클래스에서만 접근 가능하다.
따라서, 접근 제한이 엄격한 정도를 표시하면 다음과 같다.

    public < protected < 기본(패키지) < private 

final(method와 field의 경우가 뜻이 다름) : 메소드의 경우에는 클래스를 상속할 때 오버라이드할 수 없는 메소드이다. 필드인 경우에는 수정이 불가능한 즉, 상수를 뜻한다. C++의 const와 유사하다.
static : C++의 static과 같다. 즉, static은 클래스 자체 수준의 필드 혹은 메소드임을 나타낸다. 이 경우, 해당 클래스의 모든 인스턴스에서 이를 공유하며 접근 시엔 '클래스 이름'.'메소드 혹은 필드 이름'을 사용한다.
synchronized(method) : 메소드 시작 시에 객체를 잠그고 끝나면 풀어준다. 객체가 이미 잠겨져 있으면 이 메소드는 객체가 풀릴 때까지 기 다린 후 실행한다. 자바는 다중 쓰레딩 환경에서 실행되므로 쓰레드 동기화를 위해 필요한 제한자이다. 동일한 객체의 두 synchronized 메소드는 절대 동시에 수행될 수 없다.
native(method) : C로 쓰여진 스텁(stub) 파일에서 호출되는 메소드

transient(field) : 일시적임을 뜻하는 필드 제한자로 객체 직렬화(object serialization)시에 직렬화되지 않는 필드를 표시한다.

volatile(field) : 쓰레드의 동작과 관련된 필드 제한자로 한 쓰레드에 대한 volatile 변수들에 대한 연산은 쓰레드가 요청한 순서대로 진행되도록 보장한다.

필드의 선언 방법은 다음과 같다.

'필드 제한자' '유형' '필드 이름';

예) Blink.java 파일에서 
Thread blinker;
String lbl;
Font font;
int speed;

필드 제한자는 접근 제한자(public, protected, final)의 경우에는 메소드 제한자와 거의 의미가 같지만 abstract, synchronized, native가 없고 transient, volatile이 더 있으며 final의 경우에는 의미가 다름에 주의하자. final이 메소드에 쓰이면 오버라이드할 수 없다는 의미이지만 필드에 사용되면 상수로 사용하겠다는 의미이다. 예를 들어 java.awt.Color 클래스의 white는 final static으로 선언되어 있어 Color.white와 같이 흰색을 나타내는 상수로 사용된다.

상속, 오버라이드, 오버로드

객체 개념에서 가장 중요한 것 중 하나는 상속이다. 부모의 속성과 기능을 자식이 상속받아 사용할 수 있다는 것이다.

클래스 선언문에서 extends는 상속 관계를 나타낸다. 즉, extends 다음에는 클래스의 바로 윗 부모가 지정된다. extends 문이 생략된 클래스 선언문에서는 바로 윗 부모가 java.lang 패키지의 Object 클래스이다. (자바에서 Object 클래스는 모든 클래스의 부모임을 다시 한번 강조한다. 부모가 없는 클래스는 단 하나, Object 클래스뿐이다.)

메소드 오버라이드(overriding method)는 부모로부터 물려받은 메소드를 자식이 별도로 정의하여 다르게 구현하는 것이다. 예를 들어 Oval이라는 타원을 의미하는 클래스가 정의되어 있고 이 클래스는 Circle이라는 원을 의미하는 클래스를 자식으로 가진다고 하자. Oval과 Circle 클래스에 동일한 draw() 메소드가 구현되어 있다 하더라도 Oval 클래스는 타원을 그릴 것이고 Circle 클래스는 원을 그릴 것이다.

class Oval { // extends가 없으므로 바로 윗 부모는 java.lang.Object이다.
    public void draw() {
        // 타원을 그린다.
    }
}
class Circle extends Oval {
    public void draw() {
        // 원을 그린다.
    }
}

메소드 오버로드(overloaded methods)는 같은 객체(클래스) 안에 서로 다른 인자를 갖는 동일한 이름의 메소드들을 말한다. 자바에서는 오버로드된 메소드들을 인자의 유형, 순서, 개수를 가지고식별할 수 있다. (인자의 이름은 중요하지 않다.) 다음은 오버로드의 간단한 예이다.

class Circle extends Oval {
    public void draw() {
        // 원을 그린다.
    }
    public void draw(int n) {
        // n 개의 동심원을 그린다.
    }
}

오버라이드(override)와 감춤(hide)

부모 클래스를 상속하면 부모 클래스의 필드와 메소드들도 상속받게 된다.
이 때 자식 클래스는 상속받은 필드와 메소드를 재정의해서 사용할 수 있는데 필드와 메소드는 서로 다른 방식으로 부모의 것을 재정의하게 된다.
자식 클래스가 부모로부터 상속받은 메소드를 재정의하는 것을 메소드 오버라이드라고 한다.
메소드가 오버라이드되면 부모 클래스의 동일한 메소드는 무시되고 새로 정의된 메소드만 실행되게 된다.
또, 자식 클래스에서 필드가 재정의되면 부모 클래스의 동일한 이름을 가진 필드를 숨기게 되는데 이것을 감춤(hiding)이라고 한다.
재정의된 메소드와 필드의 동작은 서로 다른데 특히 부모 클래스로의 형 변환에서 서로 다른 결과를 보여준다.
다음 예제는 Parent라는 클래스의 number라는 정수 필드와 doWork()이란 메소드를 자식 클래스인 Child에서 재정의하는 예제이다.
class Parent {
	final int number = 0;
	void doWork() {
		System.out.println("Parent is working...");
	}
}
	
class Child extends Parent {
	final int number = 999;
	void doWork() {
		System.out.println("Child is working...");
	}
}
	
public class OverrideAndHide {
	public static void main(String args[]) {
		Parent p = new Parent();
		Child c = new Child();
	
		// 결과는 Parent의 number
		System.out.println("p.number="+p.number);
		// 결과는 Child의 number
		System.out.println("c.number="+c.number);
		// 결과는 Parent의 number
		System.out.println("((Parent)c).number="+((Parent)c).number);
	
		// 결과는 Parent의 doWork()
		p.doWork();
		// 결과는 Child의 doWork()
		c.doWork();
		// 결과는 Child의 doWork()
		((Parent)c).doWork();
	}
}
<예제:오버라이드와 감춤 테스트>
실행하면 결과는 어떻게 될까?
OverrideAndHide.java가 있는 디렉토리> javac OverrideAndHide.java  

OverrideAndHide.class가 있는 디렉토리> java OverrideAndHide  
p.number=0
c.number=999
((Parent)c).number=0
Parent is working...
Child is working...
Child is working...
필드의 경우 Child 객체인 c를 Parent 클래스로 형변환한 다음 number 필드를 참조하면 부모 클래스의 number 필드가 참조된다.
하지만 메소드의 경우 마찬가지로 Child 객체인 c를 Parent 클래스로 형변환한 다음 doWork() 메소드를 호출했지만 원래 c 객체의 유형인 Child 클래스의 doWork() 메소드가 호출되었다.
이것은 자바 언어가 원래 객체의 유형을 기억하고 있어서 형 변환되더라도 원래 객체 유형의 해당 메소드를 호출하는 가상 함수(virtual function) 방식을 사용하기 때문이다.
언제 어떤 메소드가 호출되는지 혹은 어떤 필드가 참조되는지 반드시 기억해두자.
필드의 경우 자식 클래스에서 재정의할 경우 항상 필드 감춤이 되지만, 메소드의 경우에는 다음 세 가지 가능성이 있다.
위의 경우처럼 보통의 인스턴스 메소드를 재정의하면 메소드 오버라이드가 되고, static 메소드(static 메소드는 클래스 메소드라고도 한다)인 경우에는 메소드 감춤이 된다.
또, 만약 부모 클래스의 메소드가 abstract였다면 자식 클래스에서 동일한 메소드를 재정의한 것은 인터페이스 메소드를 정의하는 경우처럼 구현(implement)이라고 부른다.
자식 클래스에서 부모 클래스의 감추어진 필드나 오버라이드된 메소드에 접근하려면 super 변수를 사용하면 된다.
즉, 위의 경우 Child 클래스의 한 메소드에서 super.number를 택하면 부모 클래스의 number 값인 0를 나타낼 것이다.
마찬가지로 super.doWork() 메소드를 호출하면 부모 클래스의 doWork() 메소드가 호출된다.

부전자전(父傳子傳)-클래스 자료형 변환(type cast between classes)

아버지가 자전거를 타면 아들도 자전거를 탄다?

자식 클래스 유형의 객체는 부모 클래스 유형으로 형변환(type cast)이 가능하다. 아래 예제는 상속과 클래스 형변환에 관한 간단한 문제이다. 아버지의 차는 르망이고 맏이와 둘째의 차는 레간자...

자, Father 클래스의 main() 메소드에서 실행될 결과를 예측해보라.

// Father.java
 
public class Father {
    String car="LeMans";
 
    void drive() {
        System.out.println(car);
    }
 
    public static void main (String args[]) {
        Father father=new Father();
        father.drive(); // LeMans
        System.out.println(father.car); // LeMans
 
        Child1 child1=new Child1();
        child1.drive(); // LeMans
        System.out.println(child1.car); // Leganza
        ((Father) child1).drive(); // LeMans
        System.out.println(((Father)child1).car); // LeMans
 
        Child2 child2=new Child2();
        child2.drive(); // Leganza
        System.out.println(child2.car); // Leganza
        ((Father) child2).drive(); // Leganza
        System.out.println(((Father)child2).car); // LeMans
 
        Child3 child3=new Child3();
        child3.drive(); // LeMans
        System.out.println(child3.car); // LeMans
        ((Father) child3).drive(); // LeMans
        System.out.println(((Father)child3).car); // LeMans
    }
}
 
class Child1 extends Father {
    String car="Leganza";
}
 
class Child2 extends Father {
    String car="Leganza";
 
    void drive() {
        System.out.println(car);
    }
}
 
class Child3 extends Father {
    void drive() {
        System.out.println(car);
    }
}
답 설명

 

 

인터페이스

자바의 클래스는 단일 상속(single inheritance)만을 지원한다. 따라서 모든 클래스의 부모는 궁극적으로 java.lang 패키지의 Object 클래스이다. 이것은 객체의 다중 상속이 가지는 복잡성 및 불합리성을 우려하여 보다 언어를 간단하게 하려는 의도에서이다. 하지만, 메소드 단위에서는 여러 클래스로부터 메소드를 상속할 필요성이 생기는데 자바에서는 이를 충족시키기 위해 인터페이스(Interface) 개념을 도입하였다.
인터페이스는 실행 코드가 없는 메소드들의 집합에 대한 선언문이다. 즉, 클래스에서 구현해야 할 메소드를 선언하기만 하는 것이다. C++에는 인터페이스 개념이 없지만, 대부분의 객체 지향 언어들이 그렇듯 자바의 인터페이스는 메소드(method)가 선언될 것을 전제하고 메소드의 집합을 정의한다. C++의 순수 가상 함수(pure virtual fuction, C++에서 널 코드로 된, 즉 실행 코드가 없는 함수를 지칭한다.)만으로 구성된 추상 클래스(C++에서 하나 이상 의 순수 가상 함수를 멤버로 가진 클래스를 추상 클래스라고 하는데 이들은 인스턴스가 될 수 없고 다른 클래스의 기본 클래스로만 사용된다.)라고 볼 수 있다.

인터페이스는 메소드 선언부와 상수만으로 구성된다. 즉, 메소드 몸체와 변수를 가질 수 없다.
실행문의 세부적인 코딩은 응용 프로그램의 실행 주기 동안에 완성되어도 좋으므로, 이러한 인터페이스와 세부적인 실행문의 분리는 객체 지향 프로그램의 디자인을 빠르게 해준다.
하나의 클래스는 여러 개의 인터페이스를 구현할 수 있다. 또 인터페이스는 클래스와 유사하게 부모 인터페이스로부터 상속받을 수 있다.

예를 들어 위의 Blink.java에서 Blink 클래스는 Runnable 인터페이스를 구현하고 있다.
Runnable 인터페이스는 java.lang 패키지에 포함되어 있는 인터페이스로 public abstract void run() 이라는 하나의 메소드 선언문만으로 구성되어 있다.

예) 인터페이스 예제

// Test.java
 
class Test implements MyInterface { // Test 클래스는 MyInterface 인터페이스를 구현한다고 선언
    public int getValue() { // 인터페이스 MyInterface에 선언된 메소드를 반드시 구현해야 한다.
        return value;       // 실제 리턴되는 value는 인터페이스에 선언된 상수값이다.
    }
 
    public static void main (String args[]) {
        // main() 메소드는 static이므로 객체의 인스턴스에서 호출되는 것이 아님을 명심하자.
        Test test=new Test(); // Test 클래스의 인스턴스를 생성한다.
        System.out.println("value="+test.getValue());
    }
}
 
// 인터페이스 선언부
interface MyInterface {
    final int value=5;  // 인터페이스는 변수는 가질 수 없지만 상수를 가질 수는 있다.
 
    public int getValue(); // 몸체가 없는 메소드 선언문이다. 인터페이스를 구현하는 클래스에서 몸체를 구현(실행 코드 작성)해야 한다.
}
 
실행 결과

패키지

인터페이스와 함께 C++ 프로그래머에게 생소한 개념은 패키지이다. 패키지는 관련된 클래스들의 그룹이다. 패키지를 만들려면 해당 소스 파일의 맨 윗부분에 package 문을 사용하면 된다.
자바의 핵심 시스템 클래스들 역시 여러 가지의 패키지로 구성되어 있다. 예를 들어, 애플릿으로 사용할 수 있게 해주는 Applet 클래스는 java.applet 패키지에 포함되어 있다. 실제 Applet 클래스의 소스인 Applet.java는 다음과 같은 선언문으로 시작된다.

package java.applet; 

자바에서 패키지 개념은 디렉토리 (혹은 폴더) 개념을 내포하고 있는데, Applet 클래스를 포함하여java.applet 패키지 안의 모든 클래스들은 클래스 경로 환경변수가 지정되어 있는 디렉토리 아래의 java/applet 디렉토리 아래에 존재해야 한다는 것이다. JDK에 포함되어 있는 rt.jar(JDK 설치 디렉토리 아래의 jre/lib 디렉토리에 들어 있다. JDK 1.2 이전 버전에서는 lib 디렉토리에 있는 classes.zip)의 압축을 풀어보면 이와 같은 사실을 확인할 수 있다.

클래스에서 자신이 속하지 않은 외부의 패키지를 불러 사용하려면 클래스 선언부 위에 import 문을 선언해줘야 한다. 위에서 예제로 든 Blink.java는 애플릿으로 사용하기 위해 java.applet 패키지의 Applet 클래스를 상속하여 그 메소드들을 사용하므로 소스 윗 부분에

import java.applet.*;

과 같은 패키지 사용 선언을 해줘야 한다. 실제 Blink.java 클래스 소스를 들여다보면 위의 구문이 생략되어 있는데 이것은 extends Applet을 쓰지 않고 extends java.applet.Applet과 같이 패키지 경로를 지정해주는 방법을 사용하였기 때문이다. 이와 같이 패키지 경로를 지정하는 방법으로 사용할 클래스를 지정할 수도 있겠지만 많은 경우 자주 사용되는 패키지를 일일이 클래스 이름 앞에 패키지 경로를 지정하기보다는 import 문을 사용하여 간단히 클래스 이름만 사용하는 것이 코딩에 효율적이다.

C나 C++에서 선행 처리기인 #include 문에 해당하는 역할을 import가 수행한다고 생각할 수 있다. 자바는 선행처리기 개념을 지원하지 않는다.
예를 들어 Blink.java는 다음 두 개의 import 문으로 java.awt 패키지의 모든 클래스와 java.util.StringTokenizer 클래스를 사용할 수 있게 한다. import 문을 사용하는 두 가지 방식이므로 눈여겨 보아두자.

import java.awt.*; // java.awt 패키지 안의 모든 클래스를 사용할 수 있게 한다.
import java.util.StringTokenizer; // java.util 패키지 안에서 StringTokenizer 클래스만 사용할 수 있게 한다.

자바에 필수적으로 제공되는 코어 API를 구성하는 주요 패키지들은 다음과 같다.(자바 API 문서를 참고하라.)

java.lang 패키지 : 모든 클래스의 부모 클래스인 Object 클래스를 포함하여 필수적인 자바 클래스들을 포함하고 있어서 모든 자바 파일에 자동으로 포함된다. 즉, import 문을 별도로 쓰지 않아도 자동으로 포함되는 기본 패키지로 자바 언어의 핵심 부분을 구성하는 패키지이다.
java.applet 패키지 : 애플릿을 만들 수 있게 해주는 패키지이다. Blink 클래스에서 확인할 수 있듯이 모든 애플릿 클래스는 이 패키지의 Applet 클래스에서 파생된다.
java.awt 패키지 : 추상 윈도우 툴킷 클래스들을 포함하고 있다. 추상 윈도우 툴킷(Abstract Window Toolkit)은 기반 플랫폼에 따라 X-윈도우 시스템의 모티프(motif) 또는 Win32 시스템의 윈도우 API 등을 이용하여 플랫폼에 독립적인 GUI 응용 프로그램을 만들 수 있게 해준다 .
java.awt.image 패키지 : 이미지 처리 관련 클래스들을 포함하고 있다.
java.io 패키지 : 입출력에 관계된 클래스들을 포함하고 있다.
java.net 패키지 : 네트워크 연결에 관련된 클래스들을 포함하고 있다.
java.util 패키지 : Date, Dictionary, Hashtable 등 여러 가지 유용한 클래스들을 포함하고 있는 패키지이다.

이외에도 java.rmi(원격 메소드 호출 관련 패키지), java.beans(자바 컴퍼넌트 소프트웨어 모델인 빈즈 관련 패키지), java.sql(데이터베이스 조작 관련 패키지), java.text(다국어 지원 관련 패키지) 등의 패키지들이 있다.

 

예외 처리(Exception Handling)

예외 처리는 C++와 문법이 거의 같다. 즉, try, catch, throw 구문을 사용한다.
다음은 전형적인 예외 처리 구문이다.

try {
    Thread.currentThread().sleep(1000);
}
catch ( InterruptedException e ) {
    System.out.println("Sleep interrupted!!!");
}

try 블록은 예외가 발생할 수 있는 실행문들을 포괄하는 블록이며 catch는 try 블록에서 예외가 발생한 경우 이를 처리하는 블록이다. 또, throw는 예외를 발생시키는 예약어이다.

이 구문에서 예외를 발생시킬 가능성이 있는 메소드는 java.lang.Thread 클래스의 sleep()으로 다음과 같이 선언되어 있다.

public static void sleep(long millis) throws InterruptedException 

메소드 안에서 throw가 쓰일 경우 C++에서와 마찬가지로 throw의 대상은 클래스 인스턴스이다. 예를 들면 다음과 같다.

class RangeException extends Exception {
    // RangeException 예외 클래스 선언문 몸체
}
 
class Sample {
    try {
        getRange(i);
    } catch(RangeException e) {
        System.out.println("범위 밖의 값입니다.");
    }
 
    getRange (int i) {
        if (i>10) {
            throw new RangeException();
        }
        ...
    }
}

C++와 달리, 자바에서는 예외 처리와 관련한 또하나의 예약어 finally가 있다.
catch(Exception e){} 블록과 달리 finally {} 블록의 내용은 try {} 블록의 실행 결과에 무관하게 항상 실행된다. 예를 들면 다음과 같다.

try {
        if (music != null) {
                music.loop();
        }
        ... 생략(실행할 내용들)...
} finally {
        if (music != null) {
                music.stop();
        }
}

위 예에서는 music이라는 이름의 오디오 파일을 반복해서 연주하면서 다른 실행문들을 수행하다가, 그 실행문들의 수행이 끝나거나 혹은 수행 중에 어떤 예외나 break, continue 문 등을 만나서 try 블록을 탈출하더라도 finally 블록을 무조건 수행하게 되어 오디오 파일 연주를 반드시 중단시키게 된다. 이 finally 블록은 주로 파일이나 네트웍 소켓 등과 같이 일단 연 다음, 어떠한 예외 상황이 발생하더라도 반드시 닫아야만 하는 리소스를 관리하는 데 흔하게 사용된다.

throws 선언문

위의 sleep 선언문을 주의깊게 본 사람은 throws라는 새로운 단어에 어리둥절했을 것이다. throw는 예외를 발생시키는 예약어였는데 그것도 sleep 메소드 선언부에서 사용된 것은 throw가 아니라 throws이다. throws는 메소드 내부에서 예외가 발생할 수 있음을 선언하는 구문이다. 이것은 메소드 내부에서 try-catch 블록으로 예외를 처리하지 않고 이 메소드를 사용하는 곳에서 이 예외를 반드시 처리하도록 지시하는 것이다. 메소드 내부에서 발생 가능한 예외를 처리하지도 않고 throws 구문도 선언하지 않으면 컴파일 에러가 발생하여 소스가 컴파일되지 않는다.

그 외 기본 문법 사항들

클래스, 인터페이스, 패키지 등을 제외한 나머지 문법 사항은 거의 C/C++의 문법을 준용해서 사용한다고 볼 수 있다. 간단하게 자바의 나머지 문법을 짚어보자.

1) 주석문

C++의 주석문인 '// 주석 내용'과 '/* 주석 내용 */'은 그대로 자바에 사용된다. 자바에는 또하나의 주석문('/** 문서 주석 */' )이 있는데 이것은 선언문 앞에만 사용할 수 있고 javadoc란 유틸리티를 사용하여 소스 설명 문서를 자동 생성할 때 사용된다.

2) 배열과 연산자

자바의 배열 선언은 C++와 거의 동일하다. 차이점이 있다면 자바는 배열을 스택에 만드는 것을 허용하지 않는다는 점이다. (아래 예 참조.) 다시 말하면 직역 변수로 배열의 메모리 공간(크기)을 할당하는 것이 허용되지 않기 때문에 반드시 동적으로 배열을 할당해야 한다는 것이다. 동적으로 배열을 할당하는 방법은 C++에서처럼 new 연산자를 사용하거나 인자를 바로 지정하는 동적인 방법이 있다. 다음 예를 참고하자.

int matrix[5]={1,2,3,4,5}; // 잘못됨. 스택에 배열을 만들지 않으므로 자료형을 선언할 때 배열 크기를 지정할 수 없다.
int matrix[5]; // 마찬가지로 잘못됨.

int matrix[][]=new int[3][3];
int[][] matrix=new int[3][3]; // 자바에서는 변수 다음에도 배열 표시를 사용할 수 있지만 유형 지시자 다음에도 배열 표시를 할 수 있다.
int matrix[][]={ {1}, {0, 1, 0}, {0, 0, 1} };
int[][] makeMatrix() { return new int[3][3]; }
int makeMatrix() [][] { return new int[3][3]; }

자바의 배열에서 특이한 점 하나는 배열은 항상 객체로 취급되며 length라는 속성을 가져 언제든지 배열 크기를 알 수 있다는 것이다. 배열을 사용하는 아주 간단한 예제를 보자.
// Array.java
class Array {
	public static void main(String args[]) {
		int array[]={ 1, 2, 3, 4, 5 };
		for (int i=0; i < array.length; i++)
			System.out.println("value[" + i + "]=" + array[i]);
	}
}

연산자의 경우도 C 혹은 C++와 동일하다고 생각하면 된다.

3) 제어문

자바는 여러 가지 면에서 C나 C++를 닮았다. 특히, 제어문(if, for, while, do {} while, switch, break, continue 등)은 C와 거의 동일하다. 따라서, 자바의 제어문을 다룰 때 C와 다른 점만 간략히 설명한다.
먼저 가장 주의할 것으로 if나 for, while 문의 분기 근거가 되는 값이 자바에서는 boolean 유형이라는 점이다.
예를 들면 무한 루프를 지시하는 while문은 다음과 같다.

while(true) {
        // 무한 루프의 내용;
}

C나 C++와 달리 boolean 유형이 자바의 기본 유형인 것을 생각하면 당연하다. C 사용자는 while(1)과 같이 구문을 사용하는 데 익숙하겠지만 자바에서는 true라는 불린 유형 값이 정수로 표현될 수 없으므로 이러한 구문은 컴파일 시 문법 에러를 발생시킨다.
주의해야 할 부분은 for 제어문이다. C++에서처럼 제어문 안에서 변수를 선언할 수 있는데 이 경우, 이 변수의 통용 범위는 제어문 내부이라는 점이다.
예를 들면 다음과 같다.

for (int i=0; i<10; i++) {
        // for 제어문의 내용
}
i++;  // 잘못된 사용. for 제어문 내부에서만 i가 통용된다.

또하나 주의할 부분은 break와 continue 문이다.
자바에서는 이들 문장이 레이블을 가질 수 있다. 레이블이 없는 경우는 C 혹은 C++와 동일한 역할을 하지만 레이블을 인자로 가지는 경우에는 해당 레이블로 분기하게 된다. 다음 예를 보자.

begin: // 레이블, 실제로 여기 사용된 continue 문은 여기로 분기한다.
for (int i=0; i<10; i++) {
        // continue 문 다음에 레이블이 없으면 j++를 바로 수행
        for (int j=0; j<10; j++) {
                if (isAgain==true) {
                        continue begin; // begin이라는 레이블로 간다.
                }
        }
}

break의 경우에도 마찬가지이다.

begin: // 레이블, break begin 문은 여기로 분기
for (int i=0; i<10; i++) {
        // 레이블이 없으면 break는 switch문을 벗어나 이 곳으로 분기
        switch(value) {
                case 1: value++; break;
                case 5: break begin;  // begin이라는 레이블로 분기한다.
                default : value--; break;
        }
}

이와 같은 레이블 사용법을 보면 C 프로그래머는 goto 예약어를 떠올릴 것이다. 하지만 자바에서는 goto 문이 비록 예약어이긴 하지만 사용되지 않는다.


앞 페이지 뒷 페이지 색인