자바 객체지향 프로그래밍

1. 객체지향 개념과자바

가. 객체지향개념

객체지향 개념을이해하기 위해 기본적으로 객체에대해 알아야합니다. 지금 여러분 주위를 한 번 둘러보세요. 거기에는 실세계라는 것이 있고, 그 실세계에 존재하는 여러 객체들이 보일것입니다. 화장실 다녀오는 김대리, 여러분이 지금 사용하고 있는 컴퓨터, 또는 밖에서 경적을 울리며 지나가는 자동차 등을 모두 객체라고 할 수 있습니다. 이러한 실세계에 존재하는 객체는 다음과 같은 두 가지 구성요소를 갖습니다.

  • 상태(state): 객체가 가지고 있는 속성 또는 특성

  • 행동(behavior): 객체가 가지고 있는 기능 또는 할 수 있는 행동

예를 들어, 실생활에 존재하는 자동차 객체는 색, 네 개의 바퀴, 핸들, 배기량, 현재 속도, 현재 기어 위치 등등의 상태를 갖고, 달린다, 멈춘다, 기어를 바꾼다, 속도를 높이거나 낮춘다, 경적을 울린다 등과 같은 행동을 할 수 있습니다.

이러한 실세계의 객체를 소프트웨어적으로 표현하기 위한 방법 중의 하나가 객체지향 방법입니다. 따라서, 소프트웨어 객체는 실세계의 객체가 갖는 구성요소를 모두 표현할 수 있어야 합니다. 이를 위해 객체지향 방법을 이용하여 실세계의 객체가 갖는 상태(state)와 행동(behavior)을 소프트웨어 객체의 변수(variable)와 메소드(method 또는 function)로 모델링하게 됩니다. 자세히 말하자면, 소프트웨어 객체는 실세계의 객체가 갖는 특성이나 상태를 나타내기 위해 변수를 이용하고, 이러한 특성이나 상태를 변경시키는 행동을 표현하기 위해 변수의 값을 변경하거나 다른 객체로부터 온 요청에 대한 서비스를 수행하는 메소드 즉 함수를 구현하는 것입니다. 그리고, 이 상태를 나타내는 변수들과 상태를 변경해 주는 행동을 구현한 메소드(또는 함수)를 하나로 묶어줌으로써 실세계의 객체를 소프트웨어 객체로 모델링하고 구현할 수 있습니다. 이러한 객체지향 방법에서 나타나는 몇 가지 특징들을 살펴보면 다음과 같습니다.

  • 캡슐화(Encapsulation): 상태 정보를 저장하고 있는 변수와 상태를 변경하거나 서비스를 수행하는 메소드를 하나의 소프트웨어 묶음으로 캡슐화합니다. 캡슐화는 소프트웨어의 개발자에게 높은 모듈성(modularity)과 정보은닉(information hiding) 등 두 가지 이득을 제공해 줍니다. 모듈성은 하나의 객체를 위한 소스 코드가 다른 객체를 위한 소스 코드와 무관하게 유지될 수 있다는 것입니다. 객체가 자신이 갖는 정보에 대해 public 접근 권한을 줌으로써 다른 객체들이 접근할 수 있도록 하거나, 또는 다른 객체가 접근할 수 없도록 private 접근 권한을 줄 수 있습니다. 이러한 접근 권한을 이용하여 중요한 정보 또는 불필요한 접근이 필요하지 않은 정보에 대해서는 다른 객체에게 공개하지 않아도 된다는 것입니다. 또한, 객체 자신이 가지고 있는 정보에 대해 접근 권한을 달리함으로써, 또는 자신이 제공한 메소드를 이용하여 접근하도록 함으로써 정보에 대한 보호를 할 수 있습니다.

  • 메시지(Message): 객체는 메시지를 다른 객체에게 보냄으로써 다른 객체와 통신을 합니다. 다시 말해서 객체 A는 객체 B에게 메시지를 보냄으로써 객체 B의 메소드를 수행할 수 있다는 것입니다. 이러한 메시지는 메시지를 받을 객체, 수행을 요청한 메소드의 이름, 그리고 메소드에 의해 필요한 매개변수 등 세 가지 구성요소를 갖습니다.

  • 클래스(Class): 클래스란 어떤 특정 종류의 모든 객체들에 대해 일반적으로 적용할 수 있는 변수와 메소드를 정의하고 있는 소프트웨어적인 설계도(blueprint) 또는 프로토타입(prototype)이라 할 수 있습니다. 예를 들어, 실세계에 존재하는 자동차들인 아반떼, 누비라, 그랜저, 티코, 아토스, 봉고, 또는 버스 등의 자동차들이 가질 수 있는 상태 정보와 행동에 대하여 일반화시켜 정의해 놓은 것이 자동차 클래스가 될 수 있습니다. 다시 말해서, 실세계에 존재하는 객체들이 가질 수 있는 상태와 행동들에 대해 소프트웨어적으로 추상화(abstraction) 해 놓은 것이 클래스라는 것입니다. 이러한 클래스를 청사진이라 표현하기도 하고, 벽돌을 찍기 위한 하나의 틀로써 비유되기도 합니다.

  • 인스턴스(Instance): 자동차라는 클래스를 만들었다고 해서, 우리가 실제로 사용할 수 있는 것은 아닙니다. 자동차 클래스라는 것은 단지 자동차 객체가 가질 수 있는 상태 정보와 행동들에 대한 정의이기 때문입니다. 실제로, 자동차 클래스는 김대리가 가지고 있는 아반떼, 박실장이 가지고 있는 티코 등의 식으로 나타나야 합니다. 실제화되어야 합니다. 이렇게 클래스를 실제로 사용할 수 있도록 선언하는 것, 다시 말해서 클래스에 대한 변수를 선언하는 것을 “인스턴스를 생성한다(instantiate)”라 하고, 이렇게 생성된 변수를 인스턴스라 하며, 이 인스턴스는 메모리 공간을 차지하게 됩니다. 그리고, 인스턴스의 메소드를 이용하여 변수들의 값을 설정 및 변경할 수 있습니다. 인스턴스를 생성한다는 것은 벽돌 틀을 이용해서 벽돌을 찍는 것과 같다고 볼 수 있습니다.

  • 객체(Object): 객체지향 개념에 대하여 처음 접하게 될 때, 혼동되는 것 중 하나가 클래스, 객체, 인스턴스 등의 차이를 구분하는 것입니다. 물론, 그 구분이 명확하다면 다른 혼동에 빠져들어야 겠지요. 먼저, 클래스와 객체를 구분하는 것은 실세계를 살펴볼 때 좀 더 명확해 질 수 있습니다. 예를 들어, 자동차 클래스가 있으면, 아반떼, 누비라, 그랜저, 티코, 아토스, 봉고, 또는 버스 등을 실세계에 존재하는 객체라고 할 수 있습니다. 그리고 이를 소프트웨어적으로 추상화시켜 놓은 것을 클래스라 하고, 클래스를 실제 사용할 수 있도록 실제화시켜 놓은 것을 또는 클래스가 실제 값들을 가질 수 있도록 메모리 공간을 할당해 놓은 것을 인스턴스라 합니다. 이를 소프트웨어적으로 정리하자면, 클래스는 객체의 상태(또는 변수)와 행동(또는 메소드)을 정의하고 있는 설계도(blueprint) 또는 프로토타입(prototype)이고, 클래스를 실제 사용할 수 있도록 변수 선언한 것을 인스턴스이며, 이 인스턴스를 객체라 할 수 있습니다. 객체는 높은 모듈성(modularity)과 정보은닉(information hiding)의 장점을 제공해 줍니다. 클래스는 재사용성(reusability)의 장점을 제공해 줍니다. 자동차 공장에서는 하나의 자동차 설계도(클래스)를 이용하여 여러 대의 자동차(객체 또는 인스턴스)를 생산해 냅니다. 이렇듯 하나의 클래스를 선언해 놓으면 그 클래스를 여러 번 재사용할 수 있지요. 소프트웨어 개발자 역시 하나나 둘 이상의 또는 수 없는 인스턴스 또는 객체를 생성하기 위해 하나의 클래스를 수 없이 우려먹겠지요. 이러한 것이 가능한 것은 클래스가 제공해 주는 재사용성의 장점때문입니다.

  • 상속(Inheritance): 객체지향 개념은 클래스를 이용하여 다른 클래스를 생성 또는 정의할 수 있도록 하고 있습니다. 예를 들어, 자동차가 갖는 일반적인 상태와 행동들을 자동차 클래스로 정의해 놓고, 이 자동차 클래스를 확장하여 버스만이 갖는 상태와 행동을 추가하여 버스 클래스를 정의하고, 트럭이 갖는 상태와 행동을 추가하여 트럭 클래스를 정의하고, 그리고 자가용이 갖는 상태와 행동들을 추가하여 자가용 클래스를 정의 할 수 있겠지요. 이 때, 자동차 클래스를 상위클래스(superclass)라 하고 버스 클래스, 트럭 클래스, 자가용 클래스 등을 하위클래스(subclass)라 하며, 이들 간의 관계에 대해 얘기할 때 “하위클래스는 상위클래스를 상속한다(inherit)”라고 합니다. 다시 말해서, 하위클래스는 상위클래스가 갖고 있는 모든 특성들을 상속하여 사용할 수 있다는 것입니다. 이러한 상속 관계를 트리로 나타낼 수 있고, 이 상속관계 트리를 클래스 계층도(class hierarchy)라 합니다. 상속의 장점을 살펴보면, 상위클래스는 하위클래스들이 가질 수 있는 일반적인 상태와 행동을 정의하고 있고, 하위클래스는 하위클래스 만이 갖는 특별한 상태와 행동을 정의하도록 함으로써 상위클래스를 여러 하위클래스들이 재사용할 수 있고 소프트웨어 개발에 드는 비용을 감소할 수 있습니다. 프로그램 개발자는 상위클래스를 일반적인 행동(genericbehavior)을 정의하도록 할 수 있고, 이러한 클래스를 추상 클래스(abstract class)라 합니다. 이러한 추상 클래스는 부분적으로 구현되거나 구현이나 정의가 이루어지지 않을 수 있는데, 이는 하위클래스를 정의하면서 완성할 수 있도록 가이드라인을 제공해 주는 역할을 합니다.

    <그림1. 클래스 계층도>



  • 다형성(Polymorphism): 다형성의 기본 개념은 여러 개의 클래스가 같은 메시지에 대해서 각자의 방법으로 작용할 수 있는 능력이라고 볼 수 있습니다. 다시 말해서, 다형성은 같은 이름을 갖는 여러 가지 형태가 존재한다는 것입니다. 다형성을 제공해 주기 위해 C++에서는 연산자 다중 정의(overloading), 함수 다중 정의, 그리고 함수 재정의(overriding) 등을 제공해 주고 있지만, 자바에서는 메소드(함수) 다중 정의와 메소드 재정의를 제공해 주고 있습니다.

    <그림 2. 메소드 다중 정의>



나. 클래스정의 및 인스턴스(객체) 생성

자바 언어는 객체지향 프로그래밍을 할 수 있도록 문법을 제공해 주고 있습니다. 객체지향 프로그래밍이란 기본적으로 클래스를 정의할 수 있고, 객체 또는 인스턴스를 생성할 수 있어야 합니다. 자바 언어를 이용하여 클래스를 정의할 때 다음과 같이 할 수 있습니다.

class 클래스이름 {

   // 변수 선언부

      …

   // 메소드 선언부

      …

}    

객체가 갖는 상태를 변수로써 정의하고, 행동을 메소드로써 정의할 수 있고, 이를 하나의 묶음으로 캡슐화 하기 위해 “class { … }”와 같이 하면 됩니다. 따라서, 자바에서는 위와 같이 클래스를 정의할 수 있습니다.

이렇게 정의된 클래스를 실제로 인스턴스로 생성할 수 있고, 이렇게 생성된 인스턴스를 객체라고 할 수 있다고 했습니다. 클래스에 대한 객체 또는 인스턴스를 생성하기 위해서는 먼저 클래스에 대한 객체 또는 인스턴스를 선언해야 하며, 객체 또는 인스턴스를 선언하는 방법은 의외로 간단합니다. 기본 자료형에 대한 변수를 선언하는 것과 같습니다. 다시 말해서 클래스형에 대한 변수를 선언한다고 생각할 수 있습니다. C++에서와 마찬가지로 자바에서 클래스에 대한 인스턴스 또는 객체를 선언하고 생성하는 방법은 다음과 같습니다.

클래스이름 클래스인스턴스이름 = new 클래스이름();

또는

클래스이름 클래스인스턴스이름;

클래스인스턴스이름 = new 클래스이름();

자바에서 클래스에 대한 객체(또는 인스턴스)를 선언하고 생성하기 위해 클래스에 대한 변수를 선언함과 동시에 생성하는 방법과, 클래스에 대한 변수를 선언하고 나중에 필요할 때 객체 (또는 인스턴스)를 생성하는 방법 등 두 가지 방법을 모두 제공해 주고 있습니다. 자바에서는 이러한 클래스에 대한 변수를 클래스 참조형 변수라 하고, 클래스 참조형 변수는 참조형이므로 반드시 new 연산자를 이용하여 메모리 공간을 할당해 주어야 합니다.

다음은 자바 언어를 이용하여 클래스를 정의하고, 이 클래스에 대한 객체를 선언하고 생성하는 예를 보여주는 자바 프로그램입니다.

class Point {
   int x, y;
   void setX(int xValue) { x = xValue; }
   void setY(int yValue) { y = yValue; }
   int getX() { return(x); }
   int getY() { return(y); }
   void move(int xValue, int yValue) {
      x += xValue;
      y += yValue;
   }
}
class Circle {
   Point center = new Point();
   int radius;
   void setRadius(int r) { radius = r; }
   void setCenter(int x, int y) {
      center.setX(x);
      center.setY(y);
   }
   int getRadius() { return(radius); }
   int getCenterX() { return(center.getX()); }
   int getCenterY() { return(center.getY()); }
   double getArea() { return(Math.PI * radius * radius); }
}
class ClassTest {
   public static void main(String args[]) {
      Circle c1 = new Circle(), c2;
      c2 = new Circle();
      c1.setRadius(5);
      c1.setCenter(10, 10);
      c2.setRadius(10);
      c2.setCenter(20, 20);
      System.out.println("c1(x,y,r): (" + c1.getRadius() + ","
                                        + c1.getCenterX() + ","
                                        + c1.getCenterY() + ")");
      System.out.println("c2(x,y,r): (" + c2.getRadius() + ","
                                        + c2.getCenterX() + ","
                                        + c2.getCenterY() + ")");
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\07>java ClassTest
 c1(x,y,r): (5,10,10)
 c2(x,y,r): (10,20,20)
 D:\AIIT\JAVA\Working\07>
 */

<프로그램1.ClassTest.java>

다. 변수 및 메소드의 접근제어

지금까지 클래스를 정의하고 객체를 선언 및 생성하는 방법에 대하여 살펴보았습니다. 객체지향 개념에서는 객체가 갖는 상태 정보에 대해 접근 및 변경할 수 있는 메소드를 제공해 줌으로써 객체가 갖는 상태 정보에 대한 잘못된 접근 또는 변경을 막을 수 있었지요. 또한, 상태 정보를 나타내는 변수 또는 행동이나 접근 및 변경을 위한 메소드에 대한 접근 권한을 지정해 줌으로써 객체가 갖는 데이터에 대한 정보은닉을 가능하도록 했습니다. 자바에서는 객체가 갖는 변수 또는 메소드에 대한 접근 권한을 지정하도록 하기 위해 다음과 같은 네 가지 종류의 접근지정자를 제공해 주고 있습니다.

  • public: 같은 클래스, 하위클래스, 또는 같은 패키지 내에 있는 어떤 클래스에서도 접근할 수 있습니다. 이 접근 권한은 클래스 또는 객체의 외부 인터페이스를 제공해 줄 때 주로 사용합니다.

  • private: 같은 클래스 내에서만 접근가능 합니다. 다시 말해서, 이 접근 권한으로 지정된 변수 또는 메소드를 다른 객체에서 참조하거나 사용하는 것이 불가능하고, 자신의 클래스 내에 있는 메소드에서만 참조하거나 사용할 수 있습니다. 클래스가 제공하는 기능을 내부적으로 구현할 때 주로 사용하고, 이렇게 함으로써 완벽한 정보은닉이 가능합니다.

  • protected: 자바에서 정의된 클래스들은 기본적으로 하나의 그룹 단위 또는 묶음단위로써 유지되는데, 이를 패키지라 합니다. 다시 말해서 패키지는 클래스들의 그룹이라 볼 수 있습니다. protected 접근지정자를 이용하면 같은 클래스, 하위클래스, 또는 같은 패키지 내의 모든 클래스에서 접근가능 합니다.

  • 생략(friendly): 같은 클래스 또는 같은 패키지 내에 있는 모든 클래스 내에서 접근가능 합니다. protected 접근지정자와는 달리 하위클래스에서는 접근할 수 없습니다.

접근지정자 변수 선언;

접근지정자 메소드 선언;

private int x, y;

public void setX() { … }

public int getX() { … }

다음에 나오는 예제는 접근지정자를 사용하고 있는 자바 프로그램입니다. 접근지정자 private로 선언된 변수 또는 메소드는 같은 클래스 내에서만 접근할 수 있습니다. 따라서, 다음에 나오는 자바 프로그램에서와 같이 Point 클래스가 아닌 곳에서는 사용할 수 없고, 단지 Point 클래스에서 제공해 주는 접근가능한 메소드를 사용하여 값을 참조해야 합니다.

class Point {
   private int x, y;
   public void setX(int xValue) { x = xValue; }
   public void setY(int yValue) { y = yValue; }
   public int getX() { return(x); }
   public int getY() { return(y); }
   public void move(int xValue, int yValue) {
      x += xValue;
      y += yValue;
   }
}
class AccessModifierTest {
   public static void main(String args[]) {
      Point p1 = new Point(), p2 = new Point();
      p1.setX(5); p1.setY(10);
      p1.move(10, 10);
      p2.setX(35); p2.setY(30);
      System.out.println("p1(" + p1.x + "," + p1.y + ")");        // 에러 발생
      System.out.println("p2("+p2.getX()+","+p2.getY()+")");
   }
}
/*
 * Results:
D:\AIIT\JAVA\Working\07>javac AccessModifierTest.java
 AccessModifierTest.java:22: Variable x in class Point not accessible from class
 AccessModifierTest.System.out.println("p1(" + p1.x + "," + p1.y + ")");
                                   ^
 AccessModifierTest.java:22: Variable y in class Point not accessible from class
 AccessModifierTest.System.out.println("p1(" + p1.x + "," + p1.y + ")");
                                                ^
 2 errors
 D:\AIIT\JAVA\Working\07>
 */

<프로그램2.ClassTest.java>

라. 메소드의 다중 정의(Overloading)

C++와 같은 객체지향 언어에서 다형성을 제공해 주기 위한 방법 중의 하나가 다중 정의입니다. 다중 정의란 같은 이름으로 여러 가지 일을 하는 것은 말합니다. C++에서는 함수 다중 정의와 연산자 다중 정의 등 두 가지 다중 정의를 제공해 줍니다. 함수 다중 정의는 같은 이름의 함수가 여러 개 존재하는 것이고, 연산자 다중 정의란 같은 이름의 연사자가 클래스에 따라 다른 연산을 수행할 수 있도록 하는 것입니다. 이러한 다중 정의에 대해 자바에서는 연산자 다중 정의는 제공해주지 않고, 단지 함수 다중 정의 즉 메소드 다중 정의만을 제공해 주고 있습니다. 그렇다면 메소드의 이름이 같다면 어떤 것을 이용하여 같은 이름의 여러 개의 메소드를 구별할 수 있을까요? C++에서와 마찬가지로 자바에서도 같은 이름의 여러 개의 메소드를 구별하기 위하여 메소드가 갖는 매개변수의 개수와 자료형을 이용하여 구분합니다. 예를 들면, 다음과 같은 메소드는 메소드 다중 정의에 의해 서로 구별가능 합니다.

void f(char n) { … }

void f(int n) { … }

void f(int i, int j) { … }

void f(long n) { … }

void f(float n) { … }

이렇게 메소드 다중 정의에 의해 선언된 메소드는 그 메소드를 호출할 때의 매개변수의 개수 또는 매개변수의 자료형에 따라 동적으로 호출이 이루어집니다. 따라서, 메소드를 호출할 때 호출되는 메소드는 실매개변수의 자료형과 가장 가까운 형식매개변수를 갖는 메소드가 호출됩니다. 다음은 메소드 다중 정의에 대한 예를 보여주기 위해 작성한 자바 프로그램입니다.

class MethodOverloadingTest {
   public static void main(String args[]) {
      short i=1;
      long l=2;
      f('A');
      f(1);
      f(i);
      f(l);
      f(3.141592);
   }
//   static void f(char n) { System.out.println("  char: " + n); }
    static void f(byte n) { System.out.println("  byte: " + n); }
    static void f(short n) { System.out.println(" short: " + n); }
    static void f(int n) { System.out.println("   int: " + n); }
    static void f(long n) { System.out.println("  long: " + n); }
    static void f(float n) { System.out.println(" float: " + n); }
    static void f(double n) { System.out.println(" double: " + n); }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\07>java MethodOverloadingTest
    int: 65
    int: 1
  short: 1
   long: 2
  double: 3.141592
 D:\AIIT\JAVA\Working\07>
 */

<프로그램 3.MethodOverloadingTest.java>

위의 자바 프로그램에서 char 자료형 매개변수를 갖는 메소드는 주석 처리되었지만, 위에서는 메소드 호출이 성공적으로 이루어진 것을 볼 수 있습니다. 이런 경우가 가능한 것은, 앞에서도 살펴보았듯이 암시적 형변환이 이루어지기 때문에 가능합니다. 다시 말해서, char 자료형 매개변수를 갖는 메소드를 찾고, 이러한 메소드가 없을 경우 char 자료형에 대해 내부적으로 형변환이 가능한 int 자료형 매개변수를 갖는 메소드를 찾게 되고, 이 int 자료형 매개변수를 갖는 메소드가 존재하므로, 이 메소드를 호출하게 되는 것입니다.

2. 객체 생성자와 객체 생성 과정

가. 객체 생성자

자바 언어를 이용하여 객체를 생성하고 필요에 따라 값을 초기화 해 주어야 하는 경우가 있습니다.

   Point p1 = new Point();

   p1.setX(5); p1.setY(10);

위의 예에서는 Point 클래스 객체 p1을 선언하고, “new Point()”를 통해서 객체를 생성하였습니다. 그리고, 마지막으로 p1 객체가 같은 상태를 저장하기 위한 x와 y 변수에 각각 “p1.setX(5)”와 “p1.setY(10)” 메소드를 이용하여 초기화하고 있습니다. 이러한 작업을 좀 더 간단하게 할 수는 없을까요? 물론, C++를 접해 본 사용자라면 생성자(constructor)라는 것이 있잖아요”라고 생각하겠지요.

자바 프로그램을 처음 시작하거나 자바를 이용하여 많은 프로그램을 작성해 본 프로그래머들이라도 쉽게 지나칠 수 있는 것이 객체 생성자입니다. 이러한 객체 생성자는 new 연산자를 이용하여 객체를 생성할 때 호출됩니다. 자바에서도 C++와 마찬가지로 객체 생성자를 제공해 주고 있고, 이러한 객체 생성자의 특징을 살펴보면 다음과 같습니다.

  • 객체가 생성될 때 자동으로 호출되며, 주로 객체가 가지는 변수에 대한 초기화 작업 또는 메모리 할당 등의 작업을 주로 합니다.

  • new 연산자를 이용하여 객체를 생성할 때 호출되며, 이 때 객체를 위한 메모리를 할당하고, 다음으로 객체 생성자를 호출합니다.

  • 메소드와 비슷한 구조를 갖지만, 다른 점은 객체가 생성될 때 자동으로 호출되며, 반환값을 갖지 않으며, 메소드와 같이 직접 호출할 수 없습니다.

  • 메소드 다중정의에 의해 같은 이름을 갖는 메소드가 여러 개 존재할 수 있듯이, 객체 생성자도 여러 개 존재가능하며 객체 생성자가 갖는 매개변수의 개수와 자료형을 이용하여 서로 구별하여 줍니다.

  • 객체 생성자가 여러 개 정의되어 있을 때, 필요에 따라 하나의 객체 생성자에서 다른 객체 생성자를 호출 할 수 있는데, 이 때 this라는 키워드를 사용하고, 상속 관계에 있는 하위클래스에서 상위클래스의 객체 생성자를 호출할 때는 super라는 키워드를 사용하며, 이러한 다른 객체 생성자의 호출은 반드시 첫 번째 줄에 나타나야 합니다.

  • 객체 생성자를 정의하지 않으면, 자바 컴파일러는 아무런 매개변수도 취하지 않고 아무런 작업도 하지 않는 디폴트 객체 생성자를 자동으로 추가하여 줍니다.

클래스이름(형식매개변수 리스트) { … }

클래스이름(형식매개변수 리스트) {

다른 객체 생성자 호출;  ß 반드시 첫번째 줄에서 

이루어져야 함.

 …

}

클래스이름 변수이름 = new 클래스이름(실매개변수 리스트);

new 연산자를 이용하여 객체를 생성할 때, 먼저 객체를 위한 메모리 공간을 할당하여 이를 디폴트 값으로 초기화 하며, 객체 생성자의 첫 번째 문장이 다른 생성자를 호출할 경우 이를 먼저 처리하고, 다음으로 객체가 갖는 인스턴스 변수의 초기화 수식, 초기화 블록, 생성자의 몸체를 순서대로 수행합니다. 다음에 나오는 예제 프로그램은 이러한 과정을 보여주기 위한 자바 프로그램입니다.

class Point {
   private int x, y;
   Point() {
   }
   Point(int xValue, int yValue) {
      x = xValue;
      y = yValue;
   }
   public void setX(int xValue) { x = xValue; }
   public void setY(int yValue) { y = yValue; }
   public int getX() { return(x); }
   public int getY() { return(y); }
   public void move(int xValue, int yValue) {
      x += xValue;
      y += yValue;
   }
   public String toString() {
      return("(" + x + "," + y + ")");
   }
}
class ConstructorTest {
   public static void main(String args[]) {
      Point p1 = new Point();
      Point p2 = new Point(10, 20);
      System.out.println("p1: " + p1);
      System.out.println("p2: " + p2);
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\07>java ConstructorTest
 p1: (0,0)
 p2: (10,20)
 D:\AIIT\JAVA\Working\07>
 */

<프로그램 4. ConstructorTest.java>

이 때, 자바 프로그램에서 Circle 클래스를 정의하고 Circle 클래스를 위한 아무런 객체 생성자도 정의해 주지 않았다면, 자바 컴파일러에 의해 자동으로 삽입되는 Circle 클래스의 객체 생성자는 다음과 같습니다.

public Circle() {

}

<그림 3. 객체 생성자가 없을 경우 자동으로 삽입되는 객체 생성자>

나. this

자바에서는 일반적으로 객체의 메소드 내에서 객체가 가지고 있는 변수 또는 메소드를 직접 참조할 수 있습니다. 그런데, 자바에서 클래스를 정의하고 객체를 생성할 때, this라는 키워드를 자주 사용하게 됩니다. 그렇다면 this는 어떤 역할을 하도록 자바에서 제공해 주는 걸까요? this는 다음과 같은 몇 가지 중요한 특징을 갖습니다.

  • 객체 자신에 대한 참조값을 갖습니다.

  • 메소드 내에서만 사용됩니다.

  • 매개변수와 객체 자신이 가지고 있는 변수의 이름이 같을 경우 이를 구분하기 위해 자신이 가지고 있는 변수 앞에 this를 사용합니다.

  • 객체 생성자 내에서 다른 생성자를 호출하기 위해 사용합니다.

  • 객체 자신에 대한 참조값을 메소드에 전달하거나 리턴해 주기 위해 사용하기도 합니다.

  • this를 사용함으로써, 모호하지 않고 좀더 명확한 프로그램을 작성할 수 있습니다.

this

this.멤버변수

this(매개변수);

앞에서 살펴보았던 Point 클래스에 비해 훨씬 간결하고 보기 좋은 프로그램이라는 느낌이 들 것입니다. 다음은 this를 사용하여 자바 프로그램을 좀 더 간결하게 하는 예제 프로그램입니다.

class Point {
>   private int x, y;
   Point() {
      this(0, 0);
   }
   Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
   public void setX(int x) { this.x = x; }
   public void setY(int y) { this.y = y; }
   public int getX() { return(this.x); }
   public int getY() { return(this.y); }
   public void move(int x, int y) {
      this.x += x;
      this.y += y;
   }
   public String toString() {
      return("(" + this.x + "," + this.y + ")");
   }
}
class ThisTest {
   public static void main(String args[]) {
      Point p1 = new Point();
      Point p2 = new Point(10, 20);
      System.out.println("p1: " + p1);
      System.out.println("p2: " + p2);
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\07>java ThisTest
 p1: (0,0)
 p2: (10,20)
 D:\AIIT\JAVA\Working\07>
 */

<프로그램 5.ThisTest.java>

다음은 this를 사용하여, 자신의 다른 객체 생성자를 호출하였을 경우, 그 생성 과정은 어떻게 되는 지를 보여주기 위한 예입니다.

class Point {
   private int x, y;
   Point() {
      this(0, 0);
      System.out.println("Point(): ("+x+","+y+")");
   }
   Point(int x, int y) {
      this.x = x;
      this.y = y;
      System.out.println("Point(x,y): ("+this.x+","+this.y+")");
   }
   public void setX(int x) { this.x = x; }
   public void setY(int y) { this.y = y; }
   public int getX() { return(this.x); }
   public int getY() { return(this.y); }
   public void move(int x, int y) {
      this.x += x;
      this.y += y;
   }
   public String toString() {
      return("(" + this.x + "," + this.y + ")");
   }
}
class ThisTest2 {
   public static void main(String args[]) {
      Point p1 = new Point();
      Point p2 = new Point(10, 20);
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\07>java ThisTest2
 Point(x,y): (0,0)
 Point(): (0,0)
 Point(x,y): (10,20)
 D:\AIIT\JAVA\Working\07>
 */

<프로그램 6.ThisTest2.java>

다. 클래스멤버와 인스턴스 멤버

클래스 내에 정의되어 있는 멤버는 변수와 메소드가 있고, 이는 다시 인스턴스 변수와 인스턴스 메소드 및 클래스 변수와 클래스 메소드로 나뉩니다. 일반적으로 클래스 내에 선언된 변수와 메소드는 대부분이 인스턴스 변수와 인스턴스 메소드입니다. 이는 클래스에 대해 인스턴스를 생성할 때, 각 인스턴스가 독립적으로 이를 위한 메모리 공간을 확보하기 때문입니다. 이와는 달리, 클래스 변수와 클래스 메소드는 클래스의 모든 객체(또는 인스턴스)가 공유하는 전역 변수와 전역 메소드를 말합니다. 클래스 변수 또는 메소드는 기존의 인스턴스 변수와 인스턴스 메소드의 앞에 ‘static’이라는 키워드를 명시해 주면 됩니다. 이러한 이유 때문에 클래스 변수와 클래스 메소드를 각각 static 변수와 static 메소드라고도 합니다.

인스턴스 변수는 각 인스턴스(또는 객체)가 따로 갖는 변수이고 클래스 변수는 메소드 영역에 따로 잡혀 해당 클래스에 대한 모든 인스턴스들이 공유하는 변수입니다. 따라서, 클래스 변수는 객체의 생성과 관련이 없으며, 클래스가 메모리에 로드 될 때 단 한번 클래스 변수를 위한 메모리가 할당되고 초기화됩니다. 따라서, 객체를 생성하지 않더라도 클래스 변수에 대한 접근이 가능합니다.

클래스 변수와 클래스 메소드의 선언)

[접근권한] static 변수 선언;

[접근권한] static 메소드 선언;

클래스 변수와 클래스 메소드의 접근)

클래스이름.클래스메소드()

클래스메소드()

객체참조값.클래스메소드()

인스턴스 메소드는 각 객체에 속하는 메소드이므로 인스턴스 메소드를 호출할 때는 해당 객체에 대한 참조값이 필요하지만, 클래스 메소드는 객체에 속한 것이 아니므로 객체에 대한 참조값이 필요가 없습니다. 또한, 인스턴스 변수 내에서는 자신에 대한 참조값 또는 상위 클래스에 대한 참조값을 각각 나타내는 this와 super와 같은 객체 참조값을 사용할 수 있고, 따라서 변수와 메소드의 사용에 제약이 없으나, 클래스 메소드 내에서는 해당 객체에 대한 참조값을 갖지 않으므로 인스턴스 변수 또는 인스턴스 메소드를 사용할 때는 해당 객체 참조값을 명시해 주어야 하며, 그렇지 않을 경우 클래스 메소드 내에서는 인스턴스 변수 또는 인스턴스 메소드를 참조할 수 없습니다. 다음은 클래스 변수와 클래스 메소드를 사용하는 자바 예제 프로그램입니다.

class Point {
   private static int countPoint=0;
   private int pointID;
   private int x, y;
   Point() {
      this(0, 0);
   }
   Point(int x, int y) {
      pointID = ++countPoint;
      this.x = x;
      this.y = y;
   }
   public static int getCount() { return(countPoint); }
   public int getID() { return(pointID); }
   public void setX(int x) { this.x = x; }
   public void setY(int y) { this.y = y; }
   public int getX() { return(this.x); }
   public int getY() { return(this.y); }
   public void move(int x, int y) {
      this.x += x;
      this.y += y;
   }
   public String toString() {
      return("(" + this.getID() + "/"
                 + this.getCount() + ":"
                 + this.x + "," + this.y + ")");
   }
}
class ClassVarMethodTest {
   public static void main(String args[]) {
      Point p1 = new Point();
      Point p2 = new Point(10, 20);
     
      System.out.println("p1: " + p1);
      System.out.println("p2: " + p2);
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\07>java ClassVarMethodTest
 p1: (1/2:0,0)
 p2: (2/2:10,20)
 D:\AIIT\JAVA\Working\07>
 */

<프로그램 7.ClassVarMethodTest.java>

다음에 나오는 예제는 클래스 메소드에서 인스턴스 변수에 접근하는 잘못된 경우를 보여 주고 있는 예입니다.

class Point {
   private static int countPoint=0;
   private int pointID;
   private int x, y;
   Point() {
      this(0, 0);
   }
   Point(int x, int y) {
      pointID = ++countPoint;
      this.x = x;
      this.y = y;
   }
   public static int getCount() { return(countPoint); }
   public int getID() { return(pointID); }
class Point {
   private static int countPoint=0;
   private int pointID;
   private int x, y;
   Point() {
      this(0, 0);
   }
   Point(int x, int y) {
      pointID = ++countPoint;
      this.x = x;
      this.y = y;
   }
   public static int getCount() { return(countPoint); }
   public static void printPointID() {
      System.out.println("pointID: " + pointID);  // 에러 발생
   }
   public static void printPointID(Point p) {
      System.out.println("pointID: " + p.pointID);
   }
   public int getID() { return(pointID); }
   public String toString() {
      return("(" + this.getID() + "/"
                 + this.getCount() + ":"
                 + this.x + "," + this.y + ")");
   }
}
class ClassVarMethodTest2 {
   public static void main(String args[]) {
      Point p1 = new Point();
      Point p2 = new Point(10, 20);
      System.out.print("p1" + p1 + " ");
      System.out.println("p2" + p2);
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\07>javac ClassVarMethodTest2.java
 ClassVarMethodTest2.java:16: Can't make a static reference to nonstatic variable
  pointID in class Point.
       System.out.println("pointID: " + pointID);  // 에러 발생
                                      ^
 1 error
 D:\AIIT\JAVA\Working\07>
 */

<프로그램 8.ClassVarMethodTest2.java>

라. 클래스 초기화 및 객체의 생성 과정

자바에서 클래스를 처음으로 메모리에 적재(loading)하고, 클래스에 대한 객체(또는 인스턴스)를 생성할 때 자바에서는 내부적으로 초기화 작업을 수행합니다. 클래스는 클래스 변수 및 클래스 메소드 등과 같은 클래스 멤버와 인스턴스 변수 및 인스턴스 메소드 등과 같은 인스턴스 멤버로 구성되어 있습니다. 이 때, 클래스 메소드와 인스턴스 메소드를 메모리에 적재하고, 클래스 변수와 인스턴스 변수에 대한 메모리를 할당하고 초기화 해야 하는데, 자바에서 언제 어떻게 이러한 작업을 수행하는 지에 대하여 살펴보도록 하겠습니다.

자바에서는 자바 가상머신에 의해 해당 클래스가 메모리에 적재될 때 클래스 초기화를 무엇보다도 먼저 수행합니다. 이 때 수행되는 클래스 초기화는 다음과 같이 이루어집니다.

  • 먼저, 모든 클래스 변수는 자바에서 제공해 주는 세 가지 메모리 영역 중 메소드 영역에 위치합니다.

  • 다음으로, 각 클래스 변수는 0, ‘\u0000’, false, 또는 null 등과 같은 디폴트 초기값으로 초기화 됩니다.

  • 마지막으로, 클래스 변수 초기화 수식과 클래스 초기화 블록을 정의된 순서대로 실행하게 됩니다.

다음과 같이 클래스 변수의 초기화 수식만으로 클래스 변수 초기화할 수 없을 경우 클래스 초기화 블록을 사용할 수 있습니다.

  • 배열형의 클래스 변수를 초기화 할 때

  • 여러 개의 클래스 변수를 한꺼번에 초기화 할 때

  • 예외(exception)를 발생시키는 메소드를 호출할 때

클래스 초기화는 객체의 생성과는 무관하게 클래스가 메모리에 적재될 때 이루어지는 것과는 달리, new 연산자에 의해 객체가 생성될 때마다 수행되는 작업이 있는데 다음과 같습니다.

  • new 연산자는 객체를 위한 메모리 공간을 할당합니다.

  • 객체가 가지고 있는 모든 인스턴스 변수들을 디폴트 초기값(0, ‘\u0000’, false, null)으로 초기화 됩니다.

  • 객체가 갖고 있는 인스턴스 변수의 초기화 수식 및 초기화 블록을 실행합니다.

  • 객체 생성자의 첫번째 문장에서 다른 생성자를 호출할 경우 이를 먼저 처리합니다.

  • 마지막으로 객체 생성자의 몸체를 실행합니다.

클래스 변수의 초기화 및 초기화 블록

static 변수선언=초기값;

또는

static 배열형변수선언=new 배열형;

static {

여러 개의 변수들의 초기화

또는

배열의 초기화;

}

클래스 변수의 초기화 및 초기화 블록의 예

static int countPoint=0;

또는

static int monthDays[] = new int[10];

static {

  monthDays[ 0] = 31; monthDays[ 1] = 28;

        …

monthDays[10] = 30; monthDays[11] = 31;

}

단, 인스턴스 변수의 초기화 및 초기화 블록은 위에서 static 를 제거하면 됩니다.

new 연산자를 이용하여 객체를 생성할 때, 먼저 객체를 위한 메모리 공간을 할당하여 이를 디폴트 값으로 초기화 하며, 객체 생성자의 첫 번째 문장이 다른 생성자를 호출할 경우 이를 먼저 처리하고, 다음으로 객체가 갖는 인스턴스 변수의 초기화 수식, 초기화 블록, 생성자의 몸체를 순서대로 수행합니다. 다음에 나오는 예제 프로그램은 그러한 과정을 잘 보여주는 자바 프로그램입니다.

class Point {
   private int x, y;
   Point() {
      System.out.println("Point()");
   }
   Point(int xValue, int yValue) {
      x = xValue;
      y = yValue;
      System.out.println("Point("+x+","+y+")");
   }
   public void setX(int xValue) { x = xValue; }
   public void setY(int yValue) { y = yValue; }
   public int getX() { return(x); }
   public int getY() { return(y); }
   public void move(int xValue, int yValue) {
      x += xValue;
      y += yValue;
   }
   public String toString() {
      return("(" + x + "," + y + ")");
   }
}
class ConstructionTest2 {
   Point p1 = new Point();
   {
      System.out.println("Point p1 = new Point();");
   }
   Point p2;
   {
      p2 = new Point(10, 20);
      System.out.println("p2 = new Point(10, 20);");
   }
   ConstructionTest2() {
      System.out.println("ConstructionProcessTest()");
   }
   public static void main(String args[]) {
      new ConstructionTest2();
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\07>java ConstructionTest2
 Point()
 Point p1 = new Point();
 Point(10,20)
 p2 = new Point(10, 20);
 ConstructionProcessTest()
 D:\AIIT\JAVA\Working\07>
 */

<프로그램 9.ConstructionTest2.java>

또한, 객체의 생성과는 무관하게 클래스를 메모리에 적재하면서 클래스 변수의 초기화 클래스 초기화 블록의 실행 등의 초기화를 수행하고, 객체를 생성할 때 인스턴스 변수의 초기화 및 초기화 블록의 실행 등을 하고 객체 생성자를 실행합니다. 다음에 나오는 예제 프로그램은 그러한 과정을 잘 보여주는 자바 프로그램입니다.

class A {
   A() {
      System.out.println("A()");
   }
}
class ClassInitTest {
   static int monthNum=12;
   static int monthDays[];
   static
   {
      monthDays = new int[monthNum];
      monthDays[ 0] = 31; monthDays[ 1] = 28;
      monthDays[ 2] = 31; monthDays[ 3] = 30;
      monthDays[ 4] = 31; monthDays[ 5] = 30;
      monthDays[ 6] = 31; monthDays[ 7] = 31;
      monthDays[ 8] = 30; monthDays[ 9] = 31;
      monthDays[10] = 30; monthDays[11] = 31;
     
      System.out.println("static int monthDays[];...");
   }
   A a;
   {
      a = new A();
      System.out.println("A a;");
   }
   ClassInitTest() {
      this(0);
      System.out.println("ClassInitTest()");
   }
   ClassInitTest(int x) {
      System.out.println("ClassInitTest(int x)");
   }
   public static void main(String args[]) {
      new ClassInitTest();
      System.out.println("new ClassInitTest();");
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\07>java ClassInitTest
 static int monthDays[];...
 A()
 A a;
 ClassInitTest(int x)
 ClassInitTest()
 new ClassInitTest();
 D:\AIIT\JAVA\Working\07>
 */

<프로그램 10.ClassInitTest.java>

3. 상속

가. 상속

객체지향 개념은 클래스를 이용하여 새로운 클래스를 생성 또는 정의할 수 있도록 하고 있습니다. 예를 들어, 자동차가 갖는 일반적인 상태와 행동들을 자동차 클래스로 정의해 놓고, 이 자동차 클래스를 확장하여 버스만이 갖는 상태와 행동을 추가하여 버스 클래스를 정의하고, 트럭이 갖는 상태와 행동을 추가하여 트럭 클래스를 정의하고, 그리고 자가용이 갖는 상태와 행동들을 추가하여 자가용 클래스를 정의 할 수 있겠지요. 이 때, 자동차 클래스를 상위클래스(superclass)라 하고 버스 클래스, 트럭 클래스, 자가용 클래스 등을 하위클래스(subclass)라 하며, 이들 간의 관계에 대해 얘기할 때 “하위클래스는 상위클래스를 상속한다(inherit)”라고 합니다. 다시 말해서, 하위클래스는 상위클래스가 갖고 있는 모든 특성들을 상속하여 사용할 수 있다는 것입니다. 이러한 상속 관계를 트리로 나타낼 수 있고, 이 상속관계 트리를 클래스 계층도(class hierarchy)라 합니다.

자바에서의 모든 클래스들은 반드시 어떤 클래스로부터 파생되어야 하는데, 클래스 계층 구조의 최상위 클래스는 바로 java.lang이라는 패키지에 있는 Object라는 클래스입니다. 따라서, 자바에서 정의된 모든 클래스는 기본적으로 Object클래스로부터 파생된 클래스가 되며, 자바 개발자가 만든 클래스가 그 어떤 클래스도 상속하지 않도록 정의하였다면, 자바는 내부적으로 이 사용자 클래스가 Object 클래스를 상속하도록 코드를 추가하여 줍니다. 그러므로, 자바에서 생성된 모든 클래스는 Object 클래스가 가지고 있는 변수와 메소드를 상속하여 그대로 사용할 수 있습니다.

class A {

  …

}

class B extends A {

  …

}

위와 같이 함으로써, 클래스 B는 클래스 A를 상속하면서 확장하게 되고, 이 때 클래스 A에 있는 변수와 메소드에 데해 접근 권한에 따라 상속하여 사용할 수 있습니다. 다음은 클래스 B가 클래스 A를 상속하도록 하고 있는 자바 프로그램입니다.

class A {
   int x, y;
   A() {
      x = 0; y = 0;
   }
   public void setXY(int x, int y) {
      this.x = x;
      this.y = y;
   }
   public int getX() { return(this.x); }
   public int getY() { return(this.y); }
}
class B extends A {
   int z;
   public void setXYZ(int x, int y, int z) {
      setXY(x, y);
      this.z = z;
   }
   public int getZ() { return(this.z); }
}
class InheritanceTest {
   public static void main(String args[]) {
      A a = new A();
      B b = new B();
      a.setXY(1, 2);
      b.setXYZ(3, 4, 5);
      System.out.println("YZ(" + a.getX() + ","
                             + a.getY() + ") ");
      System.out.println("XYZ(" + b.getX() + ","
                              + b.getY() + ","
                              + b.getZ() + ")");
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\09>java InheritanceTest
 YZ(1,2)
 XYZ(3,4,5)
 D:\AIIT\JAVA\Working\09>
 */

<프로그램 11.InheritanceTest.java>

나. 상위클래스와 하위클래스

자바에서 “B 클래스가 A 클래스를 상속한다”고 할 때, 상속관계에 있는 두 클래스의 관계를 정의해 보면, A 클래스를 상위클래스(superclass)라 하고, B 클래스를 하위클래스(subclass)라 합니다. 이 때, 하위클래스는 다른 클래스로부터 파생된 클래스를 나타내며, 상위클래스의 모든 상태(변수)와 행동(메소드)을 상속하게 됩니다. 상위클래스란 클래스 계층구조에서 바로 한 단계 위 클래스를 나타냅니다. 하위클래스는 상위클래스의 외부 인터페이스 및 그 구현에 대해 재사용하므로, 상위클래스의 모든 변수와 메소드에 대해 하위클래스에서 접근 가능한 변수와 메소드는 하위클래스의 것으로 생각할 수 있는데, 하위클래스가 상속할 수 있거나 그렇지 못한 상위클래스의 멤버는 다음과 같습니다.

  • public 또는 protected 접근 지정자로 선언된 변수와 메소드는 상속할 수 있습니다.

  • 같은 패키지 내의 상위클래스에 있는 접근 지정자가 생략되어 있는 변수와 메소드는 상속할 수 있습니다.

  • 상위클래스와 하위클래스에 같은 이름의 변수나 메소드가 있는 경우, 하위클래스에서 상속할 수 없습니다. 메소드의 이름이 같은 경우 재정의(override)한다고 말합니다.

  • private 접근 지정자로 선언된 변수와 메소드는 상속할 수 없습니다.

또한, 자바에서는 C++에서와는 달리 클래스는 단 하나의 클래스만을 상속하도록 하고 있습니다. 다시 말해서, 상위클래스와 하위클래스 간의 관계는 항상 1:1이라는 것이지요. 또한, 클래스는 클래스 계층 구조를 최상위 클래스까지 거슬러 올라가면서 만날 수 있는 상위클래스, 상위클래스의 상위클래스 등등 모든 상위클래스의 변수 및 메소드를 상속하는 것입니다. 마지막으로 상위클래스는 상위자료형이라고 말할 수 있고, 하위클래스는 하위자료형이라 말할 수 있습니다. 이 때, 하위자료형은 수식 내에서 상위자료형이 나타날 수 있는 어떤 위치에도 나타날 수 있습니다. 다시 말해서, 하위자료형은 상위자료형을 확장한 자료형이므로, 상위자료형 변수는 하위자료형 값을 가리킬 수 있지만, 하위자료형 변수는 상위자료형 값을 가리킬 수 없습니다.

// 상위클래스 정의

class A {

  …

}

// 하위클래스 정의 – A 클래스를 확장

class B extends A {

  …

}

다음은 상위클래스와 하위클래스의 관계에 따른 상위자료형과 하위자료형의 관계를 명확하게 보여주기 위한 자바 예제 프로그램입니다.

class A {
   int x, y;
   A() {
      x = 0; y = 0;
   }
   public void set(int x, int y) {
      this.x = x; this.y = y;
   }
   public int getX() { return(this.x); }
   public int getY() { return(this.y); }
}
class B extends A {
   int z;
   B() {
      x = 0; y = 0; z = 0;
   }
   public void set(int x, int y, int z) {
      set(x, y);
      this.z = z;
   }
   public int getZ() { return(this.z); }
}
class SuperSubClassTest {
   public static void main(String args[]) {
      A a1, a = new A();
      B b1, b = new B();
      a.set(10, 10);
      b.set(5, 5, 5);
      a1 = b;
      b1 = a;                   // 에러 발생
      System.out.print("YZ(" + a.getX() + "," + a.getY() + ") ");
      System.out.print("XYZ(" + b.getX() + "," + b.getY() + "," + b.getZ() + ")");
   }
}
/*
 * Results:
D:\AIIT\JAVA\Working\09>javac SuperSubClassTest.java
SuperSubClassTest.java:38: Incompatible type for =. Explicit cast needed to
convert A to B.
      b1 = a;                  // 에러 발생
         ^
1 error
D:\AIIT\JAVA\Working\09>
 */

<프로그램12.SuperSubClassTest.java>

다음은 상위클래스와 하위클래스의 관계를 좀 더 명확하게 보여주기 위한 자바 예제 프로그램입니다.

class A {
   int x, y;
   public void setXY(int x, int y) {
      this.x = x; this.y = y;
   }
   public int getX() { return(this.x); }
   public int getY() { return(this.y); }
}
class B extends A {
   int z;
   public void setXYZ(int x, int y, int z) {
      setXY(x, y);
      this.z = z;
   }
   public int getZ() { return(this.z); }
}
class SuperSubClassTest2 {
   public static void main(String args[]) {
      A a;
      B b = new B();
      a = b;
      b.setXYZ(1, 2, 3);
      System.out.println("XYZ(" + b.getX() + "," + b.getY() + "," + b.getZ() + ") ");
      a.setXY(4, 5);
      System.out.println("XYZ(" + b.getX() + "," + b.getY() + "," + b.getZ() + ") ");
      b.setXY(6, 7);
      System.out.println("XYZ(" + b.getX() + "," + b.getY() + "," + b.getZ() + ")");
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\09>java SuperSubClassTest2
 XYZ(1,2,3)
 XYZ(4,5,3)
 XYZ(6,7,3)
 D:\AIIT\JAVA\Working\09>
 */

<프로그램 13.SuperSubClassTest2.java>

다. 상속과 생성자 및 생성 과정

자바에서는 상위클래스에 있는 변수와 메소드에 대해 하위클래스에서 접근 가능한 모든 변수와 클래스를 하위클래스가 상위클래스로부터 상속하여 자신의 것으로 사용합니다. 그러나 상위클래스가 가지고 있는 객체 생성자는 하위클래스가 상속할 수 없고, 대신 하위클래스의 객체 생성자에서 상위클래스의 객체 생성자를 호출할 수 있도록 합니다. 클래스의 객체 생성자에서 자신의 다른 객체 생성자를 호출하기 위해, 키워드 this를 사용하여 객체 생성자의 첫 줄에 나타내 주었습니다. 그렇다면, 하위클래스의 객체 생성자에서 상위클래스의 객체 생성자를 호출하기 위해서는 어떻게 해야 할까요? 하위클래스의 객체 생성자에서 상위클래스의 객체 생성자를 명시적으로 호출하기 위해서는 super라는 키워드를 사용합니다. 그리고 하위클래스의 객체 생성자에서 상위클래스의 객체 생성자를 호출하는 문장을 명시적으로 나타내 주지 않으면, 자바 컴파일러는 자동으로 "super();" 문장을 삽입해 줍니다. 여기서, 객체 생성자에서 다른 객체 생성자를 호출하기 위해 다음과 같은 키워드를 사용할 수 있고, 이러한 문장은 반드시 객체 생성자의 첫번째 문장으로 나타나야 합니다.

  • this(…): 클래스 내의 객체 생성자에서 다른 객체 생성자를 호출합니다.

  • super(…): 하위클래스의 객체 생성자에서 상위클래스의 객체 생성자를 호출합니다.

이렇게, 자바 프로그램이 상속과 다른 객체 생성자를 호출하게 되는 복잡한 구조를 갖게 될 때, 객체의 생성 순서를 살펴보면 다음과 같습니다.

  • new 생성자를 이용하여 객체를 위한 메모리 공간을 할당합니다.

  • 모든 인스턴스 변수를 0, '\u0000', false, 그리고 null 등과 같은 디폴트 초기치로 초기화합니다.

  • 상위클래스의 생성자를 호출합니다.

  • 하위클래스에 선언된 인스턴스 변수 초기자 수식 및 인스턴스 초기화 블록을 실행합니다.

  • 하위클래스의 생성자 몸체를 실행합니다.

class A {

   A() {

      this(…);

      …

   }

   …

}

class B extends A {

   B() {

      super(…);

      …

   }

   …

}

다음은 상속에 따른 생성자들의 관계를 보여주기 위한 자바 예제 프로그램입니다.

class A {
   static int check(String msg) {
      System.out.println(msg);
      return(0);
   }
   int a = check("int a");
   public A() { check("public A()"); }
}
class SuperThisTest extends A {
   public int b = check("public int b");
   public SuperThisTest() {
      check("public SuperThisTest()");
   }
   public static void main(String args[]) {
      SuperThisTest b = new SuperThisTest();
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\09>java SuperThisTest
 int a
 public A()
 public int b
 public SuperThisTest()
 D:\AIIT\JAVA\Working\09>
 */

<프로그램 14.SuperSubClassTest2.java>

다음은 상속에 따른 생성자들의 관계를 보여주기 위한 자바 예제 프로그램입니다.

class A {
   static int check(String msg) {
      System.out.println(msg);
      return(0);
   }
   int a = check("int a");
   public A() {
      check("public A()");
   }
   public A(int i) {
      this();
      check("public A(" + i + ") after this()");
   }
}
class SuperThisTest2 extends A {
   public int b = check("public int b");
   public SuperThisTest2() {
      super(1);
      check("public SuperThisTest2()");
   }
   public static void main(String args[]) {
      SuperThisTest2 b = new SuperThisTest2();
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\09>java SuperThisTest2
 int a
 public A()
 public A(1) after this()
 public int b
 public SuperThisTest2()
 D:\AIIT\JAVA\Working\09>
 */

<프로그램 15.SuperThisTest2.java>

라. 상속과 인스턴스 메소드의 재정의(Overriding)

자바에서는 객체지향 개념에서 정의하고 있는 특성 중 하나인 다형성(Polymorphism)을 제공해 주기 위해, 메소드의 재정의(method overriding) 및 메소드의 다중 정의(method overloading)를 할 수 있도록 하고 있습니다. 상속관계에서 나타날 수 있는 다형성의 특징이 메소드 재정의입니다. 이러한 다형성은 보다 더 강력한 소스 코드 및 외부 인터페이스 재사용할 수 있도록 해 주고, 상위클래스의 일부 메소드가 하위클래스에 적합하지 않을 경우에도, 적합하지 않은 메소드만 재정의(overriding) 함으로써 상속이 가능하며 나머지 부분은 재사용 될 수 있습니다. 또한, 프로그램의 다른 부분이 수정 없이 재사용되고, 응용 프로그램의 개발을 돕기 위한 뼈대 구조를 제공하는 강력한 클래스 라이브러리(패키지)를 제공할 수 있습니다.

하위클래스는 상위클래스로부터 상속되는 상태와 행동들을 가질 수 있습니다. 또한, 하위클래스는 자신에게 필요한 변수들과 메소드를 추가적으로 정의할 수 있습니다. 그리고, 하위클래스는 상위클래스에서 정의된 메소드와 같은 이름, 같은 인자들을 갖는 새로운 메소드를 정의하여 상위클래스에서 상속되는 메소드를 재정의(overriding)할 수 있습니다. 이렇게 하위클래스가 상위클래스의 인스턴스 메소드를 새로 구현함으로써 상위클래스에서 제공해주고 있는 메소드를 하위클래스에 맞게 새롭게 구현할 수 있는 것입니다. 하위클래스에서 상위클래스의 메소드를 재정의하기 위해서는 다음과 같은 규약을 지켜주어야 합니다.

  • 인스턴스 메소드이어야 한다.
  • 메소드의 이름이 같아야 한다.
  • 매개변수의 개수가 같아야 한다.
  • 매개변수 각각의 자료형이 일치해야 한다.
  • 메소드의 리턴형이 일치해야 한다.

매개 변수의 개수나 자료형이 일치하지 않을 경우, 이는 메소드 다중 정의(method overloading)가 됩니다.

class A {

   …

   int m1(int i) { … }

   int m2(float f) { … }

   …

}

class B extends A {

   …

   int m1(int i) { … }                // 메소드 재정의

   int m2(float f1, float f2) { … }<       // 메소드 다중 정의

   int m3() { … }                    // 메소드 추가

   …

}

상위자료형을 갖는 변수를 통하여 재정의된 메소드를 호출하면, 참조되고 있는 객체의 실제 클래스에서 마지막으로 재정의된 메소드가 호출됩니다. 그리고, 인스턴스 변수, 클래스 변수, 그리고 클래스 메소드는 은닉될 수는 있어도 재정의 될 수는 없습니다. 마지막으로, 하위클래스에서 재정의되었지만 상위클래스에 있는 메소드를 호출하기 위해서는 super라는 키워드를 사용합니다. 기존의 상위클래스의 메소드 및 그 상위클래스에 접근하는 기존 클래스의 재사용 인스턴스 메소드 내에서의 super 변수는 this 변수와 마찬가지로 현재 객체의 참조값을 갖지만, 그 자료형은 직계 상위클래스의 자료형이 됩니다. 따라서, super 키워드를 통하여 재정의된 메소드를 호출할 경우에는, 현재의 클래스에 재정의된 메소드를 호출하지 않고 상위클래스의 메소드를 호출하는 특별한 의미를 갖습니다.

다음은 메소드 재정의 및 메소드 다중 정의를 보여주기 위한 자바 프로그램입니다.

class SuperClass {
   void m1(int i) {
      System.out.println("SuperClass:m1:" + i + " ");
   }
   void m2(float f) {
      System.out.println("SuperClass:m2:" + f + " ");
   }
}
class SubClass extends SuperClass {
   void m1(int i) {  // method overriding
      super.m1(i);
      System.out.println("  SubClass:m1:" + i + " ");
   }
   void m2(float f1, float f2) {  // method overloading
      System.out.println("  SubClass:m2:" + f1 + "," + f2 + " ");
   }
   void m3() {
      System.out.println("  SubClass:m3");
   }
}
class OverridingTest {
   public static void main(String args[]) {
      SubClass sub = new SubClass();
      sub.m1(10);
      sub.m2(3.141592f);
      sub.m2(30, 40);
      sub.m3();
   }
}
/*
 * Results:
D:\AIIT\JAVA\Working\09>java OverridingTest
 SuperClass:m1:10
   SubClass:m1:10
 SuperClass:m2:3.141592
   SubClass:m2:30.0,40.0
   SubClass:m3
 D:\AIIT\JAVA\Working\09>
 */

<프로그램 16. OverridingTest.java>

마. 상속과 변수 및 메소드의 접근제어

인스턴스 변수, 클래스 변수, 그리고 클래스 메소드는 은닉될 수는 있어도 재정의 될 수는 없습니다. 또한, 상위클래스에 있는 변수를 참조하거나 또는 메소드를 호출하기 위해서는 super라는 키워드를 사용합니다. 기존의 상위클래스의 변수, 메소드 및 그 상위클래스에 접근하는 기존 클래스의 재사용 인스턴스 메소드 내에서의 super 변수는 this 변수와 마찬가지로 현재 객체의 참조값을 갖지만, 그 자료형은 직계 상위클래스의 자료형이 됩니다. super 키워드를 통하여 재정의된 메소드를 호출할 경우에는, 현재의 클래스에 재정의된 메소드를 호출하지 않고 상위클래스의 메소드를 호출하는 특별한 의미를 갖습니다.

하위클래스는 상위클래스의 외부 인터페이스 및 그 구현에 대해 재사용하므로, 상위클래스의 모든 변수와 메소드에 대해 하위클래스에서 접근 가능한 변수와 메소드는 하위클래스의 것으로 생각할 수 있는데, 하위클래스가 상속할 수 있거나 그렇지 못한 상위클래스의 멤버는 다음과 같습니다.

  • private 접근 지정자로 선언된 변수는 상속할 수 없고, 메소드는 상속 및 재정의 할 수 없습니다.
  • public 또는 protected 접근 지정자로 선언된 변수와 메소드는 상속할 수 있고, 메소드에 대해 재정의 할 수 있습니다.

같은 패키지 내에 있는 상위클래스에 있는 접근 지정자가 생략되어 있는 변수와 메소드는 상속할 수 있고, 메소드에 대해 재정의 할 수 있습니다.

class A {

   int x, y, z;

   …

   …

}

class B extends A {

   int x;                       // x는 새롭게 정의

   float y;                     // y는 새롭게 정의

                              // z는 상속

   …

   int m3() {

      this.x = super.x;

      this.y = super.m1(this.x);

   …

}

다음에 나오는 자바 프로그램에서 B 클래스가 A 클래스를 상속하도록 하였는데, 다음에 나오는 프로그램에서 상속 가능한 변수 또는 메소드에 대하여 생각해보고, 결과를 살펴보고 확인해 보세요.

class A {
   public int x;
   protected int y;
   private int z;
   int getX() { return(this.x); }
   private int getY() { return(this.y); }
   protected int getZ() { return(this.z); }
}
class B extends A {
   public int x;
   public void setXYZ(int x, int y, int z) {
      this.x = x;
      this.y = y;
      this.z = z;                      // 에러 발생
   }
}
class AccessRightTest {
   public static void main(String args[]) {
      B b = new B();
      b.getX();
      b.getY();                        // 에러 발생
      b.getZ();
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\09>javac AccessRightTest.java
 AccessRightTest.java:16: No variable z defined in class B.
       this.z = z;                         // 에러 발생
           ^
 AccessRightTest.java:25: No method matching getY() found in class B.
       b.getY();                         // 에러 발생
             ^
 2 errors
 D:\AIIT\JAVA\Working\09>
 */

<프로그램 17.AccessRightTest.java>

다음은 접근 권한에 따른 상속을 보여주기 위한 자바 프로그램입니다. B 클래스가 A 클래스를 상속하도록 하였는데, 다음 중 상속과 관련하여 에러가 발생하는 부분에 대해서 다시 한 번 살펴보시기 바랍니다.

class A {
   int i;
   public int j;
   protected int k;
   private int l;
}
class B extends A {
   public void print() {
      System.out.println("i:" + i);
      System.out.println("j:" + j);
      System.out.println("k:" + k);
      System.out.println("l:" + l);               // 에러 발생
   }
}
class AccessRightTest2 {
   public static void main(String args[]) {
      A a = new A();
      new B().print();
      System.out.println("ii:" + a.i);
      System.out.println("jj:" + a.j);
      System.out.println("kk:" + a.k);
      System.out.println("ll:" + a.l);               // 에러 발생
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\09>javac AccessRightTest2.java
 AccessRightTest2.java:13: Undefined variable: l
       System.out.println("l:" + l);                 // 에러 발생
                                 ^
 AccessRightTest2.java:25: Variable l in class A not accessible from class
 Access RightTest2.
       System.out.println("ll:" + a.l);                // 에러 발생
                                   ^
 2 errors
 D:\AIIT\JAVA\Working\09>
 */

<프로그램 18.AccessRightTest2.java>

4. abstract/final/interface

가. 추상클래스와 추상메소드

추상클래스(abstract class)는 추상적인 클래스로써 그 구현이 덜 되었거나 또는 아직은 미완성 클래스이므로 실제 인스턴스(또는 객체)를 생성할 수 없도록 한 클래스입니다. 다시 말해서, 추상클래스는 객체가 가지는 특성들을 추상화시켜 놓았을 뿐 아직 구체화 시키지 못한 클래스이므로, 이 추상클래스를 상속하는 하위클래스에서 좀 더 구체화 시키도록 하는 것입니다. 따라서, 추상클래스를 상위클래스로 하여 상속하는 하위클래스는 추상클래스인 상위클래스에서 완전히 구현하지 못한 부분들을 완전하게 구현해 주어야만 하위클래스에 대한 객체 생성이 가능하고, 그렇지 못할 경우 하위클래스는 상위클래스인 추상클래스와 같이 미완성이므로 자체적으로 객체를 생성할 수 없고, 이 하위클래스는 다시 추상클래스가 됩니다.

추상메소드는 추상클래스와 마찬가지로 아직 구현이 이루어지지 않고 단지 그 프로토타입만을 가지고 있는 메소드를 말합니다. 추상메소드의 특징은 다음과 같습니다.

  • 추상메소드는 미완성 메소드이어야 하므로, 메소드의 몸체(body)를 가질 수 없습니다.
  • 추상메소드는 클래스가 가져야 할 인터페이스에 대한 프로토타입(메소드의 형태)을 정의하고 있습니다.
  • 추상메소드는 하위클래스가 가져야 할 인터페이스를 정의합니다.
  • 추상메소드를 포함하는 클래스는 반드시 추상클래스로 선언되어야 합니다.

추상클래스는 추상메소드를 포함할 수 있고, 추상메소드를 포함하는 클래스는 반드시 추상클래스로 선언되어야 합니다. 추상메소드를 포함하고 있는 추상클래스를 상속하는 하위클래스는 추상클래스가 갖고 있는 모든 추상메소드를 구현하여 주어야 합니다. 그럴 경우, 하위클래스는 하나의 클래스처럼 사용할 수 있고, 인스턴스의 생성도 가능하지만, 추상메소드를 모두 구현해 주지 못한 경우에는 하위클래스도 구현이 완전히 이루어지지 않은 추상메소드를 포함하게 되므로 추상클래스가 되며, 이 때 반드시 추상클래스로 선언되어야 합니다. 그렇지 않을 경우 다음과 같은 에러가 발생합니다. 이러한 추상 클래스(abstract class)와 추상 메소드는 해당 클래스와 메소드의 앞에 abstract 키워드를 사용하여 다음과 같이 선언할 수 있습니다.

abstract class 클래스이름 {

   abstract 메소드선언;        // 메소드의 몸체는 없음

}

예)

abstract class AbstractClass {

   abstract void abstractMethod();

}

다음에 나오는 예제는 abstract를 잘못 사용하고 있는 예를 보여주는 자바 프로그램입니다.

class A {
   abstract void m1();
   abstract void m2();
   abstract void m3() {
      /* ... */
   }
}
class AbstractTest extends A {
   void m1() {
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\10>javac AbstractTest.java
 AbstractTest.java:4: Abstract and native methods can't have a body: void m3()
    abstract void m3() {
                  ^
 AbstractTest.java:8: class AbstractTest must be declared abstract. It does not
 define void m3() from class A.
 class AbstractTest extends A {
       ^
 AbstractTest.java:8: class AbstractTest must be declared abstract. It does not
 define void m2() from class A.
 class AbstractTest extends A {
       ^
 3 errors
 D:\AIIT\JAVA\Working\10>
 */

<프로그램 19.AbstractTest.java>

위의 자바 프로그램을 살펴보면, 먼저, (a) 부분은 abstract 메소드를 포함하고 있으므로 abstract로 선언되어야 합니다. 그리고, (b) 부분에서 abstract 메소드는 메소드 몸체를 갖지 못합니다. 마지막으로, (c) 부분에서는 abstract 클래스를 상속하고 있고 abstract 메소드 중 m2가 아직 구현되지 않았으므로 abstract로 선언되어야 합니다.

class A {
   abstract void m1();
   abstract void m2();
   abstract void m3() {
      /* ... */
   }
}
class AbstractTest extends A {
   void m1() {
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\10>javac AbstractTest.java
 AbstractTest.java:4: Abstract and native methods can't have a body: void m3()
    abstract void m3() {
                  ^
 AbstractTest.java:8: class AbstractTest must be declared abstract. It does not
 define void m3() from class A.
 class AbstractTest extends A {
       ^
 AbstractTest.java:8: class AbstractTest must be declared abstract. It does not
 define void m2() from class A.
 class AbstractTest extends A {
       ^
 3 errors
 D:\AIIT\JAVA\Working\10>
 */

<프로그램 20.AbstractTest.java>

아래의 자바 프로그램을 컴파일 해 보면 에러가 발생하는데, 이는 추상클래스 참조형 변수를 선언할 수는 있지만, 인스턴스(또는 객체)를 생성할 수는 없기 때문입니다.

abstract class A {
   abstract void m1();
   abstract void m2();
   void m3() { /* ... */ }
}
abstract class B extends A {
   void m1() { /* ... */ }
}
class C extends B {
   void m2() { /* ... */ }
}
class AbstractTest2 {
   public static void main(String args[]) {
      A a;
      B b;
      C c;
      a = new A();                  // 에러 발생
      c = new C();
          }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\10>javac AbstractTest2.java
 AbstractTest2.java:21: class A is an abstract class. It can't be instantiated.
       a = new A();                  // 에러 발생
           ^
 1 error
 D:\AIIT\JAVA\Working\10>
 */

<프로그램 21. AbstractTest2.java>

나. final 변수(상수)와 상수 객체

매개변수, 지역변수, 인스턴스 변수, 클래스 변수 등과 같은 모든 종류의 변수를 final로 선언하면 그 변수의 내용을 변경할 수 없습니다. 상수는 보통 클래스 상수를 많이 사용합니다. 클래스 상수는 기본 자료형일 경우, 이름을 모두 대문자로 만들고, 이름이 여러 단어로 이루어져 있을 경우, 단어와 단어 사이를 ’_’로 구분하여 줍니다. 리터럴 상수에 대한 파이널 변수의 이점은 문서화의 한 형태로서 가독성을 높이고, 상수가 정의한 곳에서만 값을 바꾸어 주면 되므로 수정이 용이하고, 관련된 상수를 하나의 클래스에 모아 놓을 수 있으므로 C에서의 enum 자료형을 대신할 수 있습니다. final 변수에 아무런 초기값도 대입하지 않을 경우가 있는데, 이러한 상수를 공백 상수(blank constant)라 하고 프로그램 내에서 단 한번 값을 대입할 수 있는데, 이러한 공백 상수는 초기화 수식만으로 초기값을 계산할 수 없거나 초기값을 계산할 때 예외가 발생할 수 있는 경우 주로 사용합니다. 이렇게 한 번 설정된 final 변수의 값은 변경할 수 없고, 변경하려 할 경우 에러가 발생합니다.

상수 객체란 final 변수(상수)와 같이 그 내용을 변경할 수 없는 것을 말합니다. 다시 말해서, 객체의 내용을 외부에서 전혀 바꿀 수 없는 객체를 상수 객체라 합니다. 이렇게 객체의 내용을 외부에서 전혀 변경할 수 없게 하기 위해서는, 클래스를 정의할 때 모든 인스턴스 변수를 final로 선언하거나, private 접근권한을 줌으로써 외부에서 아예 접근하지 못하도록 하는 방법이 있습니다. 또한, 변수의 값을 변경할 수 있도록 제공해주는 메소드에 대해서도 private 접근권한을 줌으로써 외부에서 변수에 대해 변경할 수 있는 방법을 아예 없애버리는 것입니다. 실제로 자바에서 기본적으로 제공되고 있는 클래스 중 이러한 방법을 사용하여 내용을 전혀 변경할 수 없도록 정의된 클래스에 대한 예를 들면, String 클래스와 Integer 클래스 등이 있습니다. 그런데 한가지 재미있는 것은, final 변수가 참조형일 경우, final 변수가 직접 저장하고 있는 참조값은 바꿀 수 없지만, 참조하고 있는 객체 또는 배열의 내용은 바꿀 수 있다는 것입니다.

final 변수선언=초기값;

또는

final 변수선언;

변수=초기값;

다음은 final 변수(상수)에 대한 사용 예를 보여주기 위한 자바 프로그램입니다.

class ConstantClass {
   final static int MAXIMUM_NUM=5;
   static int countNum=0;
   final int num;
   ConstantClass() {
      this.num = ++countNum;
   }
   static boolean avaiableNum() {
      return(countNum < MAXIMUM_NUM);
   }
   public String toString() {
      return("(" + num + "/" + countNum + ")");
   }
}
class ConstantTest {
   public static void main(String args[]) {
      final int finalLocalVar;
      finalLocalVar = 10;
      inc(20);
      System.out.print(" MAXIMUM_NUM: " + ConstantClass.MAXIMUM_NUM);
      System.out.print(" " + new ConstantClass().num);
      System.out.print(" " + new ConstantClass().num);
      System.out.print(" " + new ConstantClass());
   }
   static int inc(final int a) {
      System.out.print("final int a: " + a++);
      return(a);
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\10>javac ConstantTest.java
 ConstantTest.java:31: Can't assign a second value to a blank final variable: a
       System.out.print("final int a: " + a++);
                                         ^
 1 error
 D:\AIIT\JAVA\Working\10>
 */

<프로그램 22.ConstantTest.java>

위의 자바 프로그램처럼 final 변수는 지역변수, 매개변수 등 모두 가능합니다. 대신, 한 번 값을 지정하였으면, 더 이상 변경할 수 없고, 변경하려고 하면 에러가 발생하게 됩니다. 다음은 final 변수(상수)에 대한 사용 예를 보여주기 위한 자바 프로그램입니다.

class MenuClass {
   final static int MENU_MIN   =0;
   final static int MENU_EXIT  =0;
   final static int MENU_INSERT=1;
   final static int MENU_DELETE=2;
   final static int MENU_SEARCH=3;
   final static int MENU_UPDATE=4;
   final static int MENU_MAX   =4;
   final static String[] menuStr = { "Exit",
                                     "Insert",
                                     "Delete",
                                     "Search",
                                     "Update" };
   final static boolean validMenu(int i) {
      return((i >= MENU_MIN)&&(i <= MENU_MAX));
   }
   final static void printMenu() {
      System.out.println("[1] Insert\n[2] Delete\n[3] Search\n[4] Update\n\n[0] Exit");
   }
}
class EnumVarTest {
   public static void main(String args[])
      throws java.io.IOException
   {
      int menu;
      for(;;) {
         do {
            MenuClass.printMenu();
            menu = System.in.read() - '0';
            System.in.read(); System.in.read();          // To skip CR + LF
         } while(!MenuClass.validMenu(menu));
         System.out.println("Selected menu: " + MenuClass.menuStr[menu]);
         if(menu == MenuClass.MENU_EXIT) break;
      }
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\10>java EnumVarTest
 [1] Insert
 [2] Delete
 [3] Search
 [4] Update
 [0] Exit
 1
 Selected menu: Insert
 [1] Insert
 [2] Delete
 [3] Search
 [4] Update
 [0] Exit
 0
 Selected menu: Exit
 D:\AIIT\JAVA\Working\10>
 */

<프로그램 23.EnumVarTest.java>

자바 언어에서는 C/C++에서 제공해 주고 있는 enum 형에 대해서 제공해 주고 있지 않습니다. 그러나, 이러한 enum 형에 대해서 자바에서 사용하고자 한다면, 위에서와 같이 final로 선언된 클래스 변수를 이용할 수 있습니다.

다. 파이널(final) 클래스와 파이널(final) 메소드

파이널 클래스와 파이널 메소드에 대한 필요성은 자바에서 객체지향 개념 중 상속을 제공해 주기 때문에 발생합니다. 간단하게 말해서, 파이널 클래스는 더 이상 상속될 수 없고, 파이널 메소드는 더 이상 재정의 될 수 없다는 것을 의미합니다.

파이널 클래스는 더 이상 상속될 수 없는 클래스를 나타냅니다. 다시 말해서, 어떤 클래스가 더 이상 상속되지 않도록 하기 위해서는 final 키워드를 이용하여 클래스를 선언하여 주면 됩니다. 현재 작성되는 클래스가 더 이상 기능이 추가될 필요없이 완벽한 클래스로 만들어졌다고 생각한다면 파이널 클래스로 선언하여 주면 됩니다. 이렇게 final 키워드를 이용하여 선언된 파이널 클래스를 상속하려고 한다면 에러가 발생합니다. 파이널 클래스를 선언하는 방법은 다음과 같습니다.

final class 클래스이름 {

}

파이널 메소드는 상속관계에 있는 상위클래스와 하위클래스에 대해, 상위클래스에 있는 메소드가 더 이상 하위클래스에 의해 재정의(overriding)되지 않도록 하기 위해 사용됩니다. 다시 말해서, 상위클래스가 가지고 있는 메소드에 대해 더 이상 구현될 필요가 없거나, 객체에 있어 매우 중요한 메소드이므로 하위클래스에 의해 재정의되면 안 되는 메소드이거나, 또는 객체의 일관성 있는 상태를 유지하기 위해 중요한 메소드일 경우 파이널 메소드로 선언하게 됩니다. 이러한 파이널 메소드를 선언하기 위한 방법은 다음과 같습니다.

class 클래스이름 {

   final 메소드 선언;

}

다음은 파이널 클래스와 파이널 메소드를 사용하는 자바 프로그램입니다.

final class A {
   public int a, b;
}
class B {
   final void m1()      {  }
   final void m2(int a) {  }
}
class C extends A {
   public int c;
}
class D extends B {
   void m1()             {  }
   void m2(int a, int b) {  }
}
class FinalTest {
   public static void main(String args[]) {
      A a = new A();
      B b = new B();
      C c = new C();
      D d = new D();
   }
}
/*
 * Results:
 D:\AIIT\JAVA\Working\10>javac FinalTest.java
 FinalTest.java:10: Can't subclass final classes: class A
 class C extends A {
                 ^
 FinalTest.java:15: The method void m1() declared in class D cannot override the
 final method of the same signature declared in class B.  Final methods cannot be
  overridden.
    void m1()             {  }
         ^
 2 errors
 D:\AIIT\JAVA\Working\10>
 */

<프로그램 24.FinalTest.java>

위의 자바프로그램에서는 파이널 클래스는 더 이상 상속될 수 없지만 상속하려 하고 있고, 파이널 메소드는 더 이상 재정의 될 수 없지만 재정의 하려 하고 있습니다. 따라서, 위와 같은 에러가 발생합니다.

라. 인터페이스

인터페이스란 구현되지 않고 – 메소드 몸체를 가지지 않고 – 메소드 정의(프로토타입)만 가지고 있고, 거기에 이와 관련된 상수값 만을 모아 놓은 클래스라 할 수 있습니다. 여기서 메소드 정의 즉 프로토타입이란 추상메소드라 할 수 있고, 상수란 파이널 변수라 할 수 있겠죠. 다시 말해서, 인터페이스란 추상메소드와 파이널 변수로만 이루어진 클래스라 할 수 있습니다. 따라서, 인터페이스 내에 정의된 메소드는 자바에 의해 자동으로 ‘public abstract’로 선언되게 됩니다. 마찬가지로 인터페이스 내에 선언된 변수는 자동으로 ‘public static final’로 선언되도록 되어 있습니다. 추상메소드를 포함하고 있는 추상클래스는 객체 생성을 할 수 없듯이, 구현이 이루어지지 않은 추상메소드를 포함하고 있는 인터페이스 역시 객체 생성을 할 수 없습니다. 따라서, 어떤 클래스가 이 인터페이스를 상속하여 인터페이스에 정의된 모든 메소드를 구현해 주어야 객체를 생성할 수 있겠지요. 이러한 관계를 ‘클래스가 인터페이스를 구현한다’라고합니다. 클래스와 클래스 사이에는 상속이 이루어지고, 클래스와 인터페이스 간에는 구현이 이루어지는 것입니다. 이 때, 클래스는 인터페이스의 모든 메소드 및 변수들을 상속하게 되며, 인터페이스에 정의되어 있는 모든 메소드를 구현해 주지 않을 경우 이 클래스는 인터페이스 메소드 즉 추상메소드를 포함하고 있으므로 추상클래스로 선언되어야 합니다. 이러한 인터페이스는 다음과 같은 경우에 유용하게 사용할 수 있습니다.

  • 강제적인 클래스 관계를 만들지 않으면서 서로 관련없는 클래스들 상이의 유사성을 나타내야 할 경우
  • 하나 이상의 클래스들이 구현하기를 원하는 메소드를 선언할 경우
  • 클래스를 보여주지 않고서 객체의 프로그래밍 인터페이스를 보여주어야 할 경우

자바에서 인터페이스가 다중 상속을 할 수 있도록 해준다고 얘기하는데, 인터페이스는 다중 상속을 할 수 있도록 해 주는 것이 아니라 다중 상속을 하는 것처럼 해 주는 것입니다. 인터페이스와 클래스의 다중 상속 사이에는 다음과 같은 약간의 차이가 있기 때문입니다.

  • 클래스는 인터페이스로부터 단지 상수만을 상속합니다.
  • 클래스는 인터페이스로부터 메소드의 구현(몸체)에 대해서는 상속할 수 없습니다. 왜냐하면, 인터페이스에는 단지 메소드를 선언만 했을 뿐 메소드를 구현해 놓은 것이 아니기 때문입니다.

    인터페이스의 계층 구조는 클래스의 계층 구조와 서로 무관합니다. 같은 인터페이스를 구현하는 클래스들은 클래스 계층 구조 상에서 서로 관련이 있을 수도 없을 수도 있습니다. 이러한 특성 때문에 진정한 다중 상속이라 할 수 없습니다. 그러나 자바에서는 하나의 인터페이스가 여러 개의 상위인터페이스를 가질 수 있도록 함으로써, 다중 인터페이스 상속을 할 수 있도록 해 줍니다.

    [public] interface 인터페이스이름 [extends 상위인터페이스] {

       상수선언;

       메소드선언;

    }

    인터페이스 이름은 대문자로 시작하고, “able” 또는 ”ible”와 같은 형태로 끝나게 됩니다.

  • 마. 인터페이스의 구현 및 다중 상속

    인터페이스는 클래스에 의해 구현되어야 합니다. 다시 말해서, 인터페이스는 자체적으로 객체를 생성할 수 없으므로, 클래스가 이 인터페이스를 상속(implements)하여 인터페이스에 정의된 모든 메소드를 구현해 주어야 합니다. 그렇게 할 경우에만 객체를 생성하여 사용할 수 있습니다. 그리고, 클래스의 상속에서 하나의 클래스는 하나의 클래스만을 직속으로 상속할 수 있는 것과는 달리, 하나의 클래스는 여러 개의 인터페이스를 구현해 줄 수 있습니다. 이렇게 클래스가 인터페이스를 구현한다는 것을 표현하기 위하여 다음과 같이 할 수 있습니다. 인터페이스 정의는 다음과 같이 인터페이스 선언부와 인터페이스 몸체부(body) 등 크게 두 부분으로 구성되어 있습니다.

    • 인터페이스 선언부: 인터페이스 선언부는 접근 권한, 인터페이스의 이름, 상속하고 있는 인터페이스 리스트 등으로 구성되어 있습니다.
    • 인터페이스 몸체부: 인터페이스 몸체부는 인터페이스를 위한 상수와 메소드 선언(프로토타입)을 포함하고 있습니다.

    인터페이스선언부 {

       인터페이스몸체부

    }

    또는

    [public] interface 인터페이스이름 [extends 상위인터페이스] {

       상수선언;

       메소드선언;

    }

    • 인터페이스 선언부의 접근 지정자:
    • public: 인터페이스는 어떤 패키지 내에 있는 클래스에 의해서도 접근 가능하다.
    • 생략(friendly): 인터페이스는 인터페이스와 같은 패키지 내에 있는 클래스에 의해서만 접근 가능하다.

    인터페이스도 클래스와 마찬가지로 인터페이스를 상속할 수 있습니다. 그러나, 클래스 상속과 한 가지 차이점은 클래스는 오직 하나의 클래스만 상속 가능한테 비해, 인터페이스는 여러 개의 인터페이스를 상속할 수 있다는 것입니다. 인터페이스가 상속하는 상위인터페이스들은 ‘,’로 구분하여 주고, 상위인터페이스가 가지는 모든 상수와 메소드들을 상속하게 됩니다.

    • 인터페이스 몸체부: 인터페이스 몸체부는 인터페이스 내에 있는 모든 상수와 메소드를 위한 선언을 포함하게 됩니다. 이 때, 인터페이스 내에 메소드를 선언할 때, 다음과 같은 선언과 관련된 지정자들은 사용할 수 없습니다.
    • 선언 지정자: transient, volatile, synchronized
    • 접근 지정자: private, protected

    자바에서 인터페이스를 선언할 때 또는 인터페이스 내에 선언된 메소드를 선언할 때, abstract 지정자를 사용하는 것은 불필요합니다. 왜냐하면 자바에서는 인터페이스 및 인터페이스 내의 메소드는 그 구현이 이루어지지 않았다는 특성 때문에 인터페이스 및 인터페이스 내의 메소드에 대해 내부적으로 abstract로 취급하기 때문입니다.

    [public] interface 인터페이스이름 [extends 상위인터페이스] {

       상수선언;

       메소드선언;

    }

    인터페이스는 추상클래스와 같이 아직 구현이 이루어지지 않았으므로, 클래스에 의해 상속되어 그 구현이 완성되어야 객체(또는 인스턴스)를 생성하여 사용할 수 있습니다. 클래스가 인터페이스를 상속하여 구현하기 위해서는 다음과 같이 해 주어야 합니다.

    접근지정자 class [extends 클래스이름]

                    implements 상위인터페이스리스트 {

       // 인터페이스에 선언된 메소드 구현

    }

    인터페이스는 참조 자료형으로서, 기본 자료형이나 다른 참조 자료형과 같이 어느 곳에서나 사용할 수 있습니다. 다음은 인터페이스를 잘못 사용하는 자바 프로그램에 대한 예를 보여 주고 있습니다.

    private interface InterfaceTest {
       int MAX_NUM = 10;
       boolean SUCCESS=true;
       boolean FAIL=false;
       public abstract int m1(Object obj);
       private int m2(Object obj);
       protected int m3(Object obj);
       abstract int m4();
       transient int m5(Object obj);
       volatile int m6(Object obj);
       synchronized int m7();
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\10>javac InterfaceTest.java
     InterfaceTest.java:1: The type type InterfaceTest can't be private. Package members are always accessible within the current package.
     private interface InterfaceTest {
                       ^
     InterfaceTest.java:7: Interface methods can't be native, static, synchronized, final, private, or protected: int m2(Object)
        private int m2(Object obj);
                    ^
     InterfaceTest.java:8: Interface methods can't be native, static, synchronized, final, private, or protected: int m3(Object)
        protected int m3(Object obj);
                      ^
     InterfaceTest.java:10: Method int m5(Object) can't be transient. Only variables
    can be transient.
        transient int m5(Object obj);
                      ^
     InterfaceTest.java:11: Method int m6(Object) can't be volatile. Only variables
    can be volatile.
        volatile int m6(Object obj);
                     ^
     InterfaceTest.java:12: Interface methods can't be native, static, synchronized, final, private, or protected: int m7()
        synchronized int m7();
                         ^
     6 errors
     D:\AIIT\JAVA\Working\10>
     */

    <프로그램 25.InterfaceTest.java>

    다음은 인터페이스를 선언하고 클래스를 이용하여 인터페이스를 상속 및 구현하는 자바 프로그램입니다.

    interface DataStructure {
       int MAX_NUM = 100;
       boolean SUCCESS=true;
       boolean FAIL=false;
       boolean insert(Object obj);
       boolean delete(Object obj);
       Object search(Object obj);
       int numberOf();
    }
    class StackA implements DataStructure {             //  (a)
       /* ... */
       public boolean insert(Object obj) { return(SUCCESS); }
       public boolean delete(Object obj) { return(SUCCESS); }
    }
    class StackB implements DataStructure {
       /* ... */
       public boolean insert(Object obj) { return(SUCCESS); }
       public boolean delete(Object obj) { return(SUCCESS); }
       public Object search(Object obj) { return((Object)null); }
       public int numberOf() { return(0); }
    }
    class StackC extends StackA {
       /* ... */
       public Object search(Object obj) { return((Object)null); }
       public int numberOf() { return(0); }
    }
    class InterfaceTest {
       public static void main(String args[]) {
          DataStructure ds = new DataStructure();             //  (b)
          StackA stackA = new StackA();                    //  (c)
          StackB stackB = new StackB();
          StackC stackC = new StackC();
          System.out.println("MAX_NUM: " + StackB.MAX_NUM);
       }
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\10>javac InterfaceTest2.java
     InterfaceTest2.java:12: class StackA must be declared abstract. It does not
     define int numberOf() from interface DataStructure.
     class StackA implements DataStructure {
           ^
     InterfaceTest2.java:12: class StackA must be declared abstract. It does not
     define java.lang.Object search(java.lang.Object) from interface DataStructure.
     class StackA implements DataStructure {
           ^
     InterfaceTest2.java:35: interface DataStructure is an interface. It can't be
     instantiated.
           DataStructure ds = new DataStructure();
                              ^
     InterfaceTest2.java:36: class StackA is an abstract class. It can't be
     instantiated.
           StackA stackA = new StackA();
                           ^
     4 errors
     D:\AIIT\JAVA\Working\10>
     */

    <프로그램 26.InterfaceTest2.java>

    자바에서 클래스가 인터페이스를 상속하여 구현하려 할 때, (a)와 같이 인터페이스 내에 있는 모든 메소드를 구현해 주지 않으면, 그 클래스는 추상클래스(abstract class)로 선언되어야 합니다. 또한, (b), (c)와 같이 인터페이스 또는 그 구현이 끝나지 않은(인터페이스를 제대로 구현하지 못한) 클래스에 대해서는 객체를 생성할 수 없습니다.

    5. 선언 지역에 따른 클래스의 구분

    가. 중첩클래스(nested class)

    클래스 내부에 선언되는 클래스는 변수 또는 메소드와 마찬가지로 두 가지 종류가 있습니다. 클래스를 따로 선언하고 정의하기에는 간단하고 특정 클래스와 밀접한 관계를 갖는 경우 또는 클래스로 정의해야 되지만 다른 클래스에서는 사용하지 않고 특정 클래스 하나에서만 사용하는 경우, 특정 클래스 내부에 중첩된 클래스로 만듭니다. 이렇게 함으로써 클래스의 이름 짓는 것을 간단하게 하고 프로그램을 보다 알아보기 쉽게 할 수 있습니다. 또한, 이러한 중첩클래스를 사용하여 프로그램의 구조를 보다 더 간단하고 알아보기 쉽게 할 수 있습니다. 이러한 두 개의 클래스 중 클래스 내부에 static으로 선언된 클래스가 있는데, 이 클래스를 중첩클래스라 합니다. 중첩 클래스의 객체는 중첩클래스를 포함하고 있는 클래스의 이름을 패키지 이름처럼 사용할 수 있는 것일 뿐 서로 동등합니다. 그리고, 중첩 클래스가 포함하고 있는 메소드에서는 클래스 메소드에서처럼 인스턴스 변수 또는 인스턴스 메소드에 접근할 수 없지만, 클래스 변수 또는 클래스 메소드에는 접근할 수 있습니다. 마지막으로, 중첩클래스와 이 중첩클래스를 포함하고 있는 클래스는 컴파일 했을 때, 다음과 같이 각각에 대한 클래스 파일이 생성됩니다.

    class A {

       static class B {

          …

       }

       …

    }

    컴파일 했을 때 생성되는 파일

    A.class      ß 중첩클래스를 포함하는 클래스

    A$B.class    ß 중첩클래스

    다음은 중첩클래스를 사용할 때 주의해야 할 것에 대해 간단히 보여주는 자바 프로그램입니다.

    class NestingClass {
       int i=11;
       static int si=12;
       static class NestedClass {
          int j=21;
          static int sj=22;
          void print() {
             System.out.println(" i = " + i);             // 에러 발생
             System.out.println("si = " + si);
             System.out.println(" j = " + j);
          }
       }
       NestingClass() {
          NestedClass nested = new NestedClass();
          System.out.println(nested.j);
       }
    }
    class NestedClassTest {
       public static void main(String args[]) {
          NestingClass nesting = new NestingClass();
          System.out.println(nesting.i);
          NestingClass.NestedClass nested
             = new NestingClass.NestedClass();
          System.out.println(nested.j);
       }
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\12>javac NestedClassTest.java
     NestedClassTest.java:10: Can't make a static reference to nonstatic variable
     int i in void print().
              System.out.println(" i = " + i);            // 에러 발생
                                           ^
     1 error
     D:\AIIT\JAVA\Working\12>
     */

    <프로그램 27.NestedClassTest.java>

    다음은 정상적으로 중첩클래스를 정의하여 컴파일했을 경우, 다음과 같습니다.

    class NestingClass2 {
       int i=11;
       static int si=12;
       static class NestedClass2 {
          int j=21;
          static int sj=22;
          void print() {
             System.out.println("si = " + si);
             System.out.println(" j = " + j);
          }
       }
       NestingClass2() {
          NestedClass2 nested = new NestedClass2();
          System.out.println(nested.j);
       }
    }
    class NestedClassTest2 {
       public static void main(String args[]) {
          NestingClass2 nesting = new NestingClass2();
          System.out.println(nesting.i);
          NestingClass2.NestedClass2 nested = new NestingClass2.NestedClass2();
          System.out.println(nested.j);
       }
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\12>java NestedClassTest2
     21
     11
     21
     D:\AIIT\JAVA\Working\12>dir *.class
      드라이브 D   이름 R&D
      볼륨 일련 번호 1606-1F74
      디렉터리 D:\AIIT\JAVA\Working\12
     NESTED~1 CLA       535  99-09-21  1:00  NestedClassTest.class
     NESTIN~1 CLA        584  99-09-21  1:06  NestingClass2.class
     NESTIN~2 CLA        806  99-09-21  1:06 NestingClass2$NestedClass2.class
     NESTED~2 CLA       536  99-09-21  1:06  NestedClassTest2.class
              4개 파일                2,461 바이트
              0개 디렉터리      253,198,336 바이트 사용 가능
     D:\AIIT\JAVA\Working\12>
     */

    <프로그램 28.NestedClassTest2.java>

    위와 같이, 정상적으로 컴파일 했을 경우 다음과 같은 세 개의 '.class' 파일이 생성됩니다.

    • NestingClass2.class
    • NestingClass2$NestedClass2.class
    • NestedClassTest2.class
    나. 내부클래스(inner class)

    중첩클래스와 같이 클래스 내부에 선언되는 클래스로서 중첩클래스와는 달리 static 없이 선언된 클래스를 말합니다. 중첩클래스를 사용하는 것과 같이 내부클래스를 사용하는 것 역시 프로그램의 구조를 더 좋게 할 수 있고, 특정 클래스와 관련 깊은 클래스를 그 클래스 외부에 따로 정의하지 않고 클래스의 내부에 정의함으로써, 프로그램을 보다 읽기 쉽고 알아보기 쉽게 해 줍니다.

    이러한 내부클래스는 이벤트 처리, 데이터 구조 클래스 등을 위해 자주 사용되며, 내부클래스 객체는 생성 당시에 외부 클래스 객체에 대한 보이지 않는 참조값을 갖도록 초기화됩니다. 이를 통하여, 외부클래스 객체의 메소드와 필드가 내부클래스 객체의 메소드와 필드인 것처럼 접근할 수 있게 해 줍니다.

    내부클래스의 메소드에서 외부클래스 객체의 참조값을 얻기 위해서는 “외부클래스이름.this”를 사용합니다. 이렇게 함으로써, 내부클래스에서 외부클래스의 변수 및 메소드를 자신의 변수 및 메소드인 것처럼 접근 가능합니다. 따라서, 중첩클래스에서는 자신을 포함하고 있는 클래스의 인스턴스 변수 및 인스턴스 메소드에는 접근할 수 없고, 단지 클래스 변수 및 클래스 메소드에만 접근가능 했지만, 내부클래스에서는 자신을 포함하고 있는 클래스의 클래스 변수 및 클래스 메소드에 접근할 수 있을 뿐만아니라, 인스턴스 변수 및 인스턴스 메소드에도 접근가능 합니다. 그러나, 내부클래스는 클래스 변수나 클래스 메소드를 가질 수 없고, 내부클래스를 선언하고, 이를 컴파일 했을 때 생성되는 바이트 코드의 클래스 파일 이름은 다음과 같습니다. 마지막으로, 내부클래스는 클래스 변수 또는 클래스 메소드를 가질 수 없습니다. 다시 말해서, static으로 선언된 변수 또는 메소드를 가질 수 없다는 것입니다.

    class A {

       class B {

          …

       }

       …

    }

    외부클래스의 변수 또는 메소드 참조

    A.this.변수또는 A.this.메소드

    컴파일 했을 때 생성되는 파일

    A.class      ß 외부클래스

    A$B.class    ß 내부클래스

    다음에 나오는 예제는 외부클래스와 내부클래스의 관계를 보여주기 위한 자바 프로그램입니다.

    class OuterClass {
       int i=11;
       static int si=12;
       class InnerClass {
          int j=21;
          static int sj=22;
          static void staticMethod() {
          }
          void print() {
             System.out.println(" i = " + i);
             System.out.println(" i = " + OuterClass.this.i);
             System.out.println("si = " + si);
             System.out.println(" j = " + j);
          }
       }
       OuterClass() {
          InnerClass nested = new InnerClass();
          System.out.println(nested.j);
       }
    }
    class OuterClassTest {
       public static void main(String args[]) {
          OuterClass outer = new OuterClass();
          System.out.println(outer.i);
          OuterClass.InnerClass inner = new OuterClass.InnerClass();
          System.out.println(inner.j);
       }
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\12>javac OuterClassTest.java
     OuterClassTest.java:9: Method void staticMethod() can't be static in inner class
     OuterClass. InnerClass.  Only members of interfaces and top-level classes can
     be static.
           static void staticMethod() {
                       ^
     OuterClassTest.java:30: No enclosing instance of class OuterClass is in scope;
     an explicit one must be provided when creating inner class OuterClass.
     InnerClass, as in "outer. new Inner()" or "outer. super()".
              = new OuterClass.InnerClass();
                ^
     3 errors
     D:\AIIT\JAVA\Working\12>
     */

    <프로그램 29.OuterClassTest.java>

    첫 번째와 두 번째 에러의 경우, 내부클래스에서는 static 변수 또는 static 메소드는 불가능하기 때문에 발생하는 것이고, 세 번째 에러의 경우에는 "OuterClass.InnerClass inner = outer. new InnerClass();"와 같이 바꾸어 주어야 합니다. 다음에 나오는 예제는 정상적으로 컴파일 가능한 자바 프로그램입니다.

    class OuterClass2 {
       int i=11;
       static int si=12;
       class InnerClass2 {
          int j=21;
          void print() {
             System.out.println(" i = " + i);
             System.out.println(" i = " + OuterClass2.this.i);
             System.out.println("si = " + si);
             System.out.println(" j = " + j);
          }
       }
       OuterClass2() {
          InnerClass2 nested = new InnerClass2();
          System.out.println(nested.j);
       }
    }
    class OuterClassTest2 {
       public static void main(String args[]) {
          OuterClass2 outer = new OuterClass2();
          System.out.println(outer.i);
          OuterClass2.InnerClass2 inner = outer. new InnerClass2();
          System.out.println(inner.j);
       }
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\12>java OuterClassTest2
     21
     11
     21
     D:\AIIT\JAVA\Working\12>dir OuterClass*
      드라이브 D   이름 R&D
      볼륨 일련 번호 1606-1F74
      디렉터리 D:\AIIT\JAVA\Working\12
     OUTERC~1 JAV      1,358  99-09-21   1:12  OuterClassTest.java
     OUTERC~2 JAV      1,286  99-09-21   1:22  OuterClassTest2.java
     OUTERC~1 CLA       601  99-09-21   1:24  OuterClass2.class
     OUTERC~2 CLA       903  99-09-21   1:24 OuterClass2$InnerClass2.class
     OUTERC~3 CLA       602  99-09-21   1:24 OuterClassTest2.class
              5개 파일                4,750 바이트
              0개 디렉터리      252,870,656 바이트 사용 가능
     D:\AIIT\JAVA\Working\12>
     */

    <프로그램 30.OuterClassTest2.java>

    위와 같이, 정상적으로 컴파일 했을 경우 다음과 같은 세 개의 '.class' 파일이 생성됩니다.

    • OuterClass2.class
    • OuterClass2$InnerClass2.class
    • OuterClassTest2.class
    다. 지역클래스(local class)

    지역클래스는 지역변수와 같이 메소드 내에 정의된 클래스를 가리킵니다. 지역클래스는 지역변수와 같이 메소드 내에서만 사용하고자 할 경우에 주로 사용합니다. 이러한 지역클래스는 메소드에서 자신을 포함하고 있는 클래스가 갖는 변수 및 메소드를 사용할 수 있듯이, 외부클래스가 포함하고 있는 변수 및 메소드를 사용할 수 있습니다. 그런데, 지역클래스를 포함하고 있는 메소드가 리턴되고 난 후에도 지역클래스 객체는 계속 존재하고, 계속 자신을 포함하고 있는 메소드의 지역변수에 접근하게 됩니다. 따라서, 지역클래스를 포함하고 있는 메소드의 지역변수 중 지역클래스에서 참조하는 지역변수는 항상 final로 선언함으로써, 지역클래스 객체가 언제라도 사용할 수 있도록 해 주어야 합니다. 다시 말해서 지역변수는 메소드가 끝난 후에는 사라지게 되므로, 지역변수를 final로 선언함으로써 항상 메모리에 존재하도록 해 주어야 합니다.

    class 클래스이름 {

       메소드선언 {

          지역변수선언 // 항상 final로 선언

          class 지역클래스이름 {

          }

          …

       }

       …

    }

     

    다음에 나와 있는 자바 프로그램에서는 지역클래스를 다룰 때 발생하기 쉬운 잘못을 보여주고 있습니다.

    class LocalClassTest {
       int i=11;
       static int si=12;
       final int fi=13;
       Object getLocalClass(int pi) {
          int mi=21;
    //      static int msi=22;  // (a) 메소드에서는 static 변수를
    선언하지 못함
          final int mfi=23;
          class LocalClass {
             public String toString() {
                return("LocalClass: " +   i + ","
                                  +  si + ","
                                  +  fi + ","
                                  +  pi + ","      // (b) 에러발생
                                  +  mi + ","     // (c) 에러 발생
    //                             + msi + ","
                                  + mfi);
             }
          }
          return(new LocalClass());
       }
       public static void main(String args[]) {
          Object localClass = new LocalClassTest().getLocalClass(1);
          System.out.println("Test - " + localClass);
       }
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\12>javac LocalClassTest.java
     LocalClassTest.java:16: Attempt to use a non-final variable pi from a different
     method. From enclosing blocks, only final local variables are available.
                                       +  pi + ","      // 에러발생
                                          ^
     LocalClassTest.java:17: Attempt to use a non-final variable mi from a different
     method. From enclosing blocks, only final local variables are available.
                                       +  mi + ","      // 에러 발생
                                          ^
     2 errors
     D:\AIIT\JAVA\Working\12>
     에러가 난 부분을 주석처리한 후 수행)
     D:\AIIT\JAVA\Working\12>java LocalClassTest
     Test - LocalClass: 11,12,13,23
     D:\AIIT\JAVA\Working\12>
     */

    <프로그램 31.LocalClassTest.java>

    먼저, (a) 부분에서는 메소드 내에서 static 지역 변수를 선언하지 못하게 되어 있기 때문에 에러가 발생하고, (b)와 (c) 부분에서는 지역클래스에서는 자신을 포함하고 있는 메소드의 final 변수에만 접근 가능하기 때문입니다.

    다음에 나오는 예제는 프라임 숫자를 구하기 위한 스레드 클래스를 메소드 내에 내부클래스로 선언한 자바 프로그램입니다.

    class LocalClassTest2 {
       Thread getPrimeChecker(final int number) {
          class PrimeChecker extends Thread {
             public void run() {
                int n = 3;
                while(n < number) {
                   if(isPrimeNumber(n)) {
                      System.out.println(n + " is a prime number");
                   }
                   n++;
                   try {
                      sleep(100);
                   } catch(InterruptedException e) {
                   }
                }
             }
             public boolean isPrimeNumber(int n) {
                for(int i=2;i<=(n/2);i++) {
                   if((n % i) == 0) {
                      return(false);
                   }
                }
                return(true);
             }
          }
          return(new PrimeChecker());
       }
       public static void main(String args[]) {
          LocalClassTest2 localClass = new LocalClassTest2();
          Thread primeChecker = localClass.getPrimeChecker(10);
          primeChecker.start();
       }
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\12>java LocalClassTest2
     3 is a prime number
     5 is a prime number
     7 is a prime number
     D:\AIIT\JAVA\Working\12>
     */

    <프로그램 32.LocalClassTest2.java>

    라. 익명클래스(anonymous class)

    단순한 클래스 또는 인터페이스를 정의하여 사용할 때, 여러 곳에서 사용하는 것이 아니고 단 한번만 정의하여 사용한다거나 할 경우 주로 익명클래스를 사용합니다. 익명클래스는 클래스 또는 인터페이스에 대한 객체를 생성하면서 바로 클래스 또는 인터페이스를 정의하도록 되어 있습니다. 다시 말해서, new 수식이 있는 곳에서 바로 클래스 또는 인터페이스를 정의합니다. 이러한 익명클래스는 주로 클래스 또는 인터페이스를 구현하여 바로 사용하고자 할 때 이용됩니다. 그리고, 익명클래스를 사용할 때 한 가지 주의할 점은 익명클래스는 new 수식의 연장이므로 끝에 꼭 세미콜론(;)을 붙여주어야 한다는 것입니다. 다음과 같이 익명클래스를 정의하여 사용할 수 있습니다.

    다음 문장들과 마찬가지로 new 수식 역시 메소드 내에서만 사용되므로 이름이 없다는 것만 제외하고는 익명클래스와 지역클래스는 모든 점에서 같습니다. 따라서, 익명클래스를 포함하고 있는 메소드의 지역변수 중 익명클래스에서 참조할 수 있는 지역변수는 final로 선언된 지역변수만 가능합니다.

    new 클래스명() {

       클래스 정의

    };

    또는

    new 인터페이스명() {

       인터페이스 정의 및 구현

    };

    위와 같이 제공된 클래스 또는 인터페이스를 이름없는 하위클래스를 정의한 후, 그 하위클래스의 객체를 만듭니다. 다음에 나와 있는 자바 프로그램은 위에서 지역클래스를 이용하여 했던 작업을 그대로 익명클래스를 이용하여 하고 있는 프로그램입니다.

    class AnonymousClassTest {
       Thread getPrimeChecker(final int number) {
          return(new Thread() {
             public void run() {
                int n = 3;
                while(n < number) {
                   if(isPrimeNumber(n)) {
                      System.out.println(n + " is a prime number");
                   }
                   n++;
                   try {
                      sleep(100);
                   } catch(InterruptedException e) {
                   }
                }
             }
             public boolean isPrimeNumber(int n) {
                for(int i=2;i<=(n/2);i++) {
                   if((n % i) == 0) {
                      return(false);
                   }
                }
                return(true);
             }
          });
       }
       public static void main(String args[]) {
          ThreadprimeChecker=newAnonymousClassTest().getPrimeChecker(10);
          primeChecker.start();
       }
    }
    /*
     * Results:
     D:\AIIT\JAVA\Working\12>java AnonymousClassTest
     3 is a prime number
     5 is a prime number
     7 is a prime number
     D:\AIIT\JAVA\Working\12>
     */

    <프로그램 33.AnonymousClassTest.java>


    Copyright All right reservered
    by Yongwoo's Park 1999.
    2007/11/15 15:47 2007/11/15 15:47

    트랙백 주소 :: http://thinkit.or.kr/programming/trackback/3

    댓글을 달아 주세요