Development/JavaEssential

클래스와 객체

linuxism 2012. 3. 18. 12:13
1. 객체지향언어
   

1.1 객체지향언어의 역사

요즘은 컴퓨터의 눈부신 발전으로 활용 폭이 넓고 다양해져서 컴퓨터가 사용되지 않는 분야가 없을 정도지만, 초창기에는 주로 과학실험이나 미사일 발사실험과 같은 모의실험(Simulation)을 목적으로 사용되었다.
이 시절의 과학자들은 모의실험을 위해 실제 세계와 유사한 가상 세계를 컴퓨터 속에 구현하고자 노력하였으며, 이러한 노력은 객체지향이론을 탄생시켰다.
객체지향이론의 기본 개념은 '실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물간의 상호작용이다.'라는 것이다.

실제 사물의 속성과 기능을 분석한 다음, 데이터(변수)와 함수로 정의함으로써 실제 세계를 컴퓨터 속에 옮겨 놓은 듯한 가상세계를 구현하고 이 가상세계에서 모의실험을 함으로써 많은 시간과 비용을 절약할 수 있었다.
객체지향이론은 상속, 캡슐화, 추상화 개념을 중심으로 점차 구체적으로 발전되었으며, 1960년대 중반에 객체지향이론을 프로그래밍언어에 적용한 Simula라는 최초의 객체지향언어가 탄생하였다.
그 당시 FORTRAN이나 COBOL과 같은 절차적 언어들이 주류를 이루었으며, 객체지향언어는 널리 사용되지 못하고 있었다.
1980년대 중반에 C++을 비롯하여 많은 수의 객체지향언어가 발표되면서, 객체지향언어가 본격적으로 개발자들의 관심을 끌기 시작하였지만 여전히 사용자 층이 넓지 못했다.
그러나 프로그램의 규모가 점점 커지고 사용자들의 요구가 빠르게 변화해가는 상황을 절차적언어로는 극복하기 어렵다는 한계를 느끼고 객체지향언어를 이용한 개발방법론이 대안으로 떠오르게 되면서 조금씩 입지를 넓혀가고 있었다.
자바가 1995년에 발표되고 1990년대 말에 인터넷의 발전과 함께 크게 유행하면서 객체지향언어는 이제 프로그래밍의 주류로 자리잡았다.


1.2 객체지향언어

객체지향언어는 기존의 프로그래밍언어와 다른 전혀 새로운 것이 아니라, 기존의 프로그래밍 언어에 몇가지 새로운 규칙을 추가한 보다 발전된 형태의 것이다. 이러한 규칙들을 이용해서 코드간에 서로 관계를 맺어 줌으로써 보다 유기적으로 프로그램을 구성하는 것이 가능해졌다. 기존의 프로그래밍 언어에 익숙한 사람이라면 자바의 객체지향적인 부분만 새로 배우면 될 것이다. 다만 절차적언어에 익숙한 프로그래밍 습관을 객체지향적으로 바꾸도록 노력해야할 것이다.

객체지향언어의 주요특징은 다음과 같다.


1. 코드의 재사용성이 높다.
     - 새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.
2. 코드의 관리가 용이하다.
     - 코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다.
3. 신뢰성이 높은 프로그래밍을 가능하게 한다.
     - 제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 하며,
       코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다.



객체지향언어의 가장 큰 장점은 '코드의 재사용성이 높고 유지보수가 용이하다.'는 것이다. 이러한 객체지향언어의 장점은 프로그램의 개발과 유지보수에 드는 시간과 비용을 획기적으로 개선하였다.
앞으로 상속, 다형성과 같은 객체지향개념을 학습할 때 재사용성과 유지보수 그리고 코드의 중복 제거, 이 세가지 관점에서 보면 보다 쉽게 이해할 수 있을 것이다.

객체지향 프로그래밍은 프로그래머에게 거시적 관점에서 설계할 수 있는 능력을 요구하기 때문에 객체지향개념을 이해했다 하더라도 자바의 객체지향적 장점들을 충분히 활용한 프로그램을 작성하기란 쉽지 않을 것이다.
너무 객체지향개념에 얽매여서 고민하기 보다는 일단 프로그램을 기능적으로 완성한 다음 어떻게 하면 보다 객체지향적으로 코드를 개선할 수 있는가에 대해서 고민하여 점차 개선해 나가는 것이 좋다.
이러한 경험들이 축적되어야 프로그램을 객체지향적으로 설계할 수 있는 능력이 길러지는 것이지 처음부터 이론을 많이 안다고 해서 좋은 설계를 할 수 있는 것은 아니다.
특히 프로그래밍에 익숙하지 않다면 객체지향개념 보다는 연산자와 조건문과 같은 프로그래밍언어의 기본적인 요소들에 먼저 익숙해질 수 있도록 간단한 예제들을 많이 작성해 보는 것이 좋다.




   2. 클래스와 객체
   

2.1 클래스와 객체의 정의와 용도

클래스란 '객체를 정의해놓은 것.' 또는 클래스는 '객체의 설계도 또는 틀'이라고 정의할 수 있다. 클래스는 객체를 생성하는데 사용되며, 객체는 클래스에 정의된 대로 생성된다. 


클래스의 정의 - 클래스란 객체를 정의해 놓은것이다.
클래스의 용도 - 클래스는 객체를 생성하는데 사용된다.


객체의 사전적인 정의는, '실제로 존재하는 것'이다. 우리가 주변에서 볼 수 있는 책상, 의자, 자동차와 같은 사물들이 곧 객체이다. 객체지향이론에서는 사물과 같은 유형적인 것뿐만 아니라, 개념이나 논리와 같은 무형적인 것들도 객체로 간주한다. 
프로그래밍에서의 객체는 클래스에 정의된 내용대로 메모리에 생성된 것을 뜻한다. 


객체의 정의 - 실제로 존재하는것. 사물 또는 개념
객체의 용도 - 객체가 가지고 있는 기능과 속성에 따라 다름

유형의 객체 - 책상, 의자, 자동차, TV와 같은 사물
무형의 객체 - 수학공식, 프로그램 에러와 같은 논리나 개념


클래스와 객체의 관계를 우리가 살고 있는 실생활에서 예를 들면, 제품 설계도와 제품과의 관계라고 할 수 있다. 예를 들면, TV설계도(클래스)는 TV라는 제품(객체)을 정의한 것이며, TV(객체)를 만드는데 사용된다. 
또한 클래스는 단지 객체를 생성하는데 사용될 뿐이지 객체 그 자체는 아니다. 우리가 원하는 기능의 객체를 사용하기 위해서는 먼저 클래스로부터 객체를 생성하는 과정이 선행되어야 한다. 
우리가 TV를 보기 위해서는, TV(객체)가 필요한 것이지 TV설계도(클래스)가 필요한 것은 아니며, TV설계도(클래스)는 단지 TV라는 제품(객체)을 만드는데만 사용될 뿐이다. 그리고 TV설계도를 통해 TV가 만들어진 후에야 사용할 수 있는 것이다. 
프로그래밍에서는 먼저 클래스를 작성한 다음, 클래스로부터 객체를 생성하여 사용한다.

[참고]객체를 사용한다는 것은 객체가 가지고 있는 속성과 기능을 사용한다는 뜻이다. 

 

클래스를 정의하고 클래스를 통해 객체를 생성하는 이유는 설계도를 통해서 제품을 만드는 이유와 같다. 하나의 설계도만 잘 만들어 놓으면 제품을 만드는 일이 쉬워진다. 제품을 만들 때마다 매번 고민할 필요없이 설계도 대로만 만들면 되기 때문이다. 
설계도 없이 제품을 만든다고 생각해보라. 복잡한 제품일 수록 설계도 없이 제품을 만든다는 것은 상상할 수도 없을 것이다. 
이와 마찬가지로 클래스를 한번만 잘 만들어 놓기만 하면, 매번 객체를 생성할 때마다 어떻게 객체를 만들어야 할지를 고민하지 않아도 된다. 그냥 클래스로부터 객체를 생성해서 사용하기만 하면 되는 것이다. 
J2SDK(Java2 Standard Development Kit)에서는 프로그래밍을 위해 많은 수의 유용한 클래스(Java API)를 기본적으로 제공하고 있으며, 우리는 이 클래스들을 이용해서 원하는 기능의 프로그램을 보다 쉽게 작성할 수 있다. 



2.2 객체와 인스턴스

클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instantiate)라고 하며, 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance)라고 한다. 

예를 들면, Tv클래스로부터 만들어진 객체를 Tv클래스의 인스턴스라고 한다. 결국 인스턴스는 객체와 같은 의미이지만, 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 갖고 있으며, 인스턴스는 어떤 클래스로부터 만들어진 것인지를 강조하는 보다 구체적인 의미를 갖고 있다. 
예를 들면, '책상은 인스턴스다.'라고 하기 보다는 '책상은 객체다.'라는 쪽이, '책상은 책상 클래스의 객체이다.'라고 하기 보다는 '책상은 책상 클래스의 인스턴스다.'라고 하는 것이 더 자연스럽다. 

인스턴스와 객체는 같은 의미이므로 두 용어의 사용을 엄격히 구분지을 필요는 없지만, 위의 예에서 본 것과 같이 문맥에 따라 구별하여 사용하는 것이 좋다. 

 


2.3 객체의 구성요소 - 속성과 기능


객체는 속성과 기능, 두 종류의 구성요소로 이루어져 있으며, 일반적으로 객체는 다수의 속성과 다수의 기능을 갖는다. 즉, 객체는 속성과 기능의 집합이라고 할 수 있다. 그리고, 객체가 가지고 있는 속성과 기능을 그 객체의 멤버(구성원, member)라 한다. 

 

클래스란 객체를 정의한 것이므로 클래스에는 객체의 모든 속성과 기능이 정의되어있다. 클래스로부터 객체를 생성하면, 클래스에 정의된 속성과 기능을 가진 객체가 만들어지는 것이다. 

보다 쉽게 이해할 수 있도록 TV를 예로 들어보자. TV의 속성으로는 전원상태, 크기, 길이, 높이, 색상, 볼륨, 채널과 같은 것들이 있으며, 기능으로는 켜기, 끄기, 볼륨 높이기, 채널 변경하기 등이 있다. 

 

객체지향 프로그래밍에서는 속성과 기능을 각각 변수와 함수로 표현한다. 



속성(property) → 멤버변수(variable)
기능(function) → 메서드(method)

채널 → int channel
채널 높이기 → channelUp() { ... }



위에서 분석한 내용을 토대로 Tv클래스를 만들어 보면 다음과 같다. 


class Tv { 
     // Tv의 속성(멤버변수) 
     String color;             // 색상 
     boolean power;       // 전원상태(on/off) 
     int channel;             // 채널 

     // Tv의 기능(메서드) 
     void power() {      power = !power; }      /* TV를 켜거나 끄는 기능을 하는 메서드 */ 
     void channelUp() {     ++channel; }       /* TV의 채널을 높이는 기능을 하는 메서드 */
     void channelDown() {    --channel; }     /* TV의 채널을 낮추는 기능을 하는 메서드 */ 



[참고] 멤버변수와 메서드를 선언하는데 있어서 순서는 관계없지만, 일반적으로 메서드보다는 멤버변수를 먼저 선언하고 멤버변수는 멤버변수끼리 메서드는 메서드끼리 모아 놓는 것이 일반적이다. 

실제 TV가 갖는 기능과 속성은 이 외에도 더 있지만, 프로그래밍에 필요한 속성과 기능만을 선택하여 클래스를 작성하면 된다.

각 변수의 자료형은, 속성의 값에 알맞은 것을 선택해야한다. 전원상태(power)의 경우, on과 off 두 가지 값을 가질 수 있으므로 boolean형으로 선언했다. 

[알아두면 좋아요]
power()의 power = !power;문은 power의 값이 true면 false로, false면 true로 변경하는 일을 한다. power의 값에 관계없이 항상 반대의 값으로 변경해주면 되므로 굳이 if문을 사용할 필요가 없다. 참고로 if문을 사용하여 코드를 작성하면 다음과 같다. 

      if (power) { 
          power = false; 
     } else { 
          power = true; 
     } 




2.4 인스턴스의 생성과 사용

Tv클래스를 완성했으니, 이제는 Tv클래스의 인스턴스를 만들어서 사용해 보도록 하자. 클래스로부터 인스턴스를 생성하는 방법은 여러 가지가 있지만 일반적으로는 다음과 같이 한다. 


클래스명 변수명;      // 클래스의 객체를 참조하기 위한 참조변수를 선언한다. 
변수명 = new 클래스명(); // 클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장한다. 

Tv t;                   // Tv클래스 타입의 참조변수 t를 선언 
t = new Tv();       // Tv인스턴스를 생성한 후, 생성된 Tv인스턴스의 주소를 t에 저장한다. 


[예제6-1] TvTest.java


class Tv { 
     
// Tv의 속성(멤버변수) 
     
String color;             // 색상 
     
boolean power;       // 전원상태(on/off) 
     
int channel;             // 채널 

     
// Tv의 기능(메서드) 
     
void power() {      power = !power; }      /* TV를 켜거나 끄는 기능을 하는 메서드 */ 
     
void channelUp() {     ++channel; }       /* TV의 채널을 높이는 기능을 하는 메서드 */
     void channelDown() {    --channel; }     /* TV의 채널을 낮추는 기능을 하는 메서드 */ 
}

 

class TvTest { 
      public static void main(String args[]) { 
            Tv t;                         // Tv인스턴스를 참조하기 위한 변수 t를 선언       
            t = new Tv();             // Tv인스턴스를 생성한다. 
            t.channel = 7;             // Tv인스턴스의 멤버변수 channel의 값을 7로 한다. 
            t.channelDown();             // Tv인스턴스의 메서드 channelDown()을 호출한다. 
            System.out.println("현재 채널은 " + t.channel + " 입니다."); 
      } 

[실행결과]
현재 채널은 6 입니다. 

위의 예제는 Tv클래스로부터 인스턴스를 생성하고, 인스턴스의 속성(channel)과 함수(channelDown())를 사용하는 방법을 보여 주는 것이다. 예제를 그림과 함께 한 줄 씩 살펴보도록 하자. 

1. Tv t; 
Tv클래스 타입의 참조변수 t를 선언한다. 메모리에 참조변수 t를 위한 공간이 마련된다. 



2. t = new Tv() 
연산자 new에 의해 Tv클래스의 인스턴스가 메모리의 빈 공간에 생성된다. 주소가 0x100인 곳에 생성되었다고 가정하자. 이 때, 멤버변수는 각 자료형에 해당하는 기본값으로 초기화 된다. 
color는 참조형이므로 null로, power는 boolean이므로 false로, 그리고 channel은 int이므로 0으로 초기화 된다. 

 


그 다음에는 대입연산자(=)에 의해서 생성된 객체의 주소값이 참조변수 t에 저장된다. 이제는 참조변수 t를 통해 Tv인스턴스에 접근할 수 있다. 인스턴스를 다루기 위해서는 참조변수가 반드시 필요하다. 

 

[참고]위 그림에서의 화살표는 참조변수 t가 Tv인스턴스를 참조하고 있다는 것을 알기 쉽게 하기 위해 추가한 상징적인 것이다. 이 때, 참조변수 t가 Tv인스턴스를 '가리키고 있다' 또는 '참조하고 있다'라고 한다. 

3. t.channel = 7 ; 
참조변수 t에 저장된 주소에 있는 인스턴스의 멤버변수 channel에 7을 저장한다. 여기서 알 수 있는 것처럼, 인스턴스의 멤버변수(속성)를 사용하려면 '참조변수.멤버변수'와 같이 하면 된다. 

 

4. t.channelDown(); 
참조변수 t가 참조하고 있는 Tv인스턴스의 channelDown메서드를 호출한다. channelDown메서드는 멤버변수 channel에 저장되어 있는 값을 1감소시킨다. 

void channelDown() {       --channel;             } 

channelDown()에 의해서 channel의 값은 7에서 6이 된다. 

 

5. System.out.println("현재 채널은 " + t.channel + " 입니다."); 
참조변수 t가 참조하고 있는 Tv인스턴스의 멤버변수 channel에 저장되어 있는 값을 출력한다. 현재 channel의 값은 6이므로 '현재 채널은 6 입니다.'가 화면에 출력된다. 

인스턴스와 참조변수의 관계는 마치 우리가 일상생활에서 사용하는 TV와 TV리모콘의 관계와 같다. TV리모콘(참조변수)을 사용하여 TV(인스턴스)를 다루기 때문이다. 다른 점이라면, 인스턴스는 오직 참조변수를 통해서만 다룰 수 있다는 것이다. 
그리고, TV를 사용하려면 TV 리모콘을 사용해야하고, 에어콘을 사용하려면, 에어콘 리모콘을 사용해야하는 것처럼, Tv인스턴스를 사용하려면, Tv클래스 타입의 참조변수가 필요한 것이다. 

[예제6-2] TvTest2.java

class TvTest2 { 
      public static void main(String args[]) { 
            Tv t1 = new Tv(); 
            Tv t2 = new Tv(); 
            System.out.println("t1의 channel값은 " + t1.channel + "입니다."); 
            System.out.println("t2의 channel값은 " + t2.channel + "입니다."); 

            t1.channel = 7;       // channel 값을 7으로 한다. 
            System.out.println("t1의 channel값을 7로 변경하였습니다."); 

            System.out.println("t1의 channel값은 " + t1.channel + "입니다."); 
            System.out.println("t2의 channel값은 " + t2.channel + "입니다."); 
      } 

[실행결과]
t1의 channel값은 0입니다. 
t2의 channel값은 0입니다. 
t1의 channel값을 7로 변경하였습니다. 
t1의 channel값은 7입니다. 
t2의 channel값은 0입니다. 

위의 예제는 Tv클래스의 인스턴스 t1과 t2를 생성한 후에, 인스턴스 t1의 멤버변수인 channel의 값을 변경하였다. 
[참고] 참조변수 t1이 가리키고(참조하고) 있는 인스턴스를 간단히 인스턴스 t1이라고 했다. 

1. Tv t1 = new Tv(); 
    Tv t2 = new Tv();
 

 


2. t1.channel = 7; // 참조변수 t1이 가리키고 있는 인스턴스의 멤버변수 channel의 값을 7로 한다. 

 

같은 클래스로부터 생성되었을지라도 각 인스턴스들의 속성(멤버변수)은 서로 다른 값을 유지할 수 있으며, 메서드의 내용은 모든 인스턴스에 대해 동일하다. 

[예제6-3] TvTest3.java

class TvTest3 { 
      public static void main(String args[]) { 
            Tv t1 = new Tv(); 
            Tv t2 = new Tv(); 
            System.out.println("t1의 channel값은 " + t1.channel + "입니다."); 
            System.out.println("t2의 channel값은 " + t2.channel + "입니다."); 

            t2 = t1;             // t1이 저장하고 있는 값(주소)을 t2에 저장한다. 
            t1.channel = 7;       // channel 값을 7로 한다. 
            System.out.println("t1의 channel값을 7로 변경하였습니다."); 

            System.out.println("t1의 channel값은 " + t1.channel + "입니다."); 
            System.out.println("t2의 channel값은 " + t2.channel + "입니다."); 
      } 

[실행결과]
t1의 channel값은 0입니다. 
t2의 channel값은 0입니다. 
t1의 channel값을 7로 변경하였습니다. 
t1의 channel값은 7입니다. 
t2의 channel값은 7입니다. 

1. Tv t1 = new Tv(); 
    Tv t2 = new Tv();
 

 


2. t2 = t1;             // t1이 저장하고 있는 값(주소)을 t2에 저장한다. 

t1은 참조변수이므로, 인스턴스의 주소를 저장하고 있다. 이 문장이 수행되면, t2가 가지고 있던 값은 잃어버리게 되고, t1에 저장되어 있던 값이 t2에 저장되게 된다. 그렇게 되면, t2 역시 t1이 참조하고 있던 인스턴스를 같이 참조하게 되고, t2가 원래 참조하고 있던 인스턴스는 더 이상 사용할 수 없게 된다. 

[참고]자신을 참조하고 있는 참조변수가 하나도 없는 인스턴스는 더 이상 사용되어질 수 없으므로 가비지 컬렉터(Garbage Collector)에 의해서 자동적으로 메모리에서 제거된다. 




3. t1.channel = 7;       // channel 값을 7로 한다. 

 

4. System.out.println("t1의 channel값은 " + t1.channel + "입니다."); 
    System.out.println("t2의 channel값은 " + t2.channel + "입니다."); 

이제 t1, t2 모두 같은 Tv클래스의 인스턴스를 가리키고 있기 때문에, t1.channel의 값과 t2.channel의 값은 7이며 다음과 같은 결과가 화면에 출력된다. 


t1의 channel값은 7입니다. 
t2의 channel값은 7입니다. 


이 예제에서 알 수 있듯이, 참조변수에는 하나의 값(주소)만이 저장될 수 있으므로 둘 이상의 참조변수가 하나의 인스턴스를 가리키는(참조하는) 것은 가능하지만 하나의 참조변수로 여러 개의 인스턴스를 가리키는 것은 가능하지 않다. 





2.5 클래스의 또 다른 정의

클래스는 '객체를 생성하기 위한 틀'이며 '클래스는 속성과 기능으로 정의되어있다.'고 했다. 이것은 객체지향이론의 관점에서 내린 정의이고, 이번엔 프로그래밍적인 관점에서 클래스의 정의와 의미를 살펴보도록 하자.

1. 클래스 - 데이터와 함수의 결합

프로그래밍언어에서 데이터 처리를 위한 데이터 저장형태의 발전과정은 다음과 같다.





1. 변수 - 하나의 데이터를 저장할 수 있는 공간
2. 배열 - 같은 종류의 여러 데이터를 하나의 집합으로 저장할 수 있는 공간
3. 구조체 - 서로 관련된 여러 데이터를 종류에 관계없이 하나의 집합으로 저장할 수 있는 공간
4. 클래스 - 데이터와 함수의 결합(구조체 + 함수)


하나의 데이터를 저장하기 위해 변수를, 그리고 같은 종류의 데이터를 보다 효율적으로 다루기 위해서 배열이라는 개념을 도입했으며, 후에는 구조체(structure)가 등장하여 자료형의 종류에 상관없이 서로 관계가 깊은 변수들을 하나로 묶어서 다룰 수 있도록 했다.
그동안 데이터와 함수가 서로 관계가 없는 것처럼 데이터는 데이터끼리, 함수는 함수끼리 따로 다루어져 왔지만, 사실 함수는 주로 데이터를 가지고 작업을 하기 때문에 많은 경우에 있어서 데이터와 함수는 관계가 깊다.

그래서 자바와 같은 객체지향언어에서는 변수(데이터)와 함수를 하나의 클래스에 정의하여 서로 관계가 깊은 변수와 함수들을 함께 다룰 수 있게 했다.
서로 관련된 변수들을 정의하고 이들에 대한 작업을 수행하는 함수들을 함께 정의한 것이 바로 클래스이다.

C언어에서는 문자열을 문자의 배열로 다루지만, 자바에서는 String이라는 클래스로 문자열을 다룬다. 문자열을 단순히 문자의 배열로 정의하지 않고 클래스로 정의한 이유는 문자열과 문자열을 다루는데 필요한 함수들을 함께 묶기 위해서이다.


public final class String implements java.io.SerializableComparable { 
      private char[] value;       // 문자열을 저장하기 위한 공간 
      public String replace(char oldChar, char newChar) { 
            ... 
            char[] val = value;       // 같은 클래스 내의 변수를 사용해서 작업을 한다. 
            ... 
      } 
            ... 



이 코드는 String클래스의 실제 소스의 일부이다. 클래스 내부에 value라는 문자형 배열이 선언되어 있고, 문자열을 다루는 데 필요한 함수들을 함께 정의해 놓았다. 문자열의 일부를 뽑아낸다던가 문자열의 길이를 알아내는 함수들은 항상 문자열을 작업대상으로 필요로 하기 때문에 문자열과 깊은 관계에 있으므로 함께 정의하였다.
이렇게 함으로써 변수와 함수가 서로 유기적으로 연결되어 작업이 간단하고 명료해 진다.



2. 클래스 - 사용자정의 타입(User-defined Type)

프로그래밍언어에서 제공하는 자료형(Primitive type)외에 프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것을 사용자정의 타입(User-defined type)이라고 한다.
많은 프로그래밍언어에서 사용자정의 타입을 정의할 수 있는 방법을 제공하고 있으며, 자바와 같은 객체지향언어에서는 클래스가 곧 사용자 정의 타입이다. 기본형의 개수는 8개로 정해져 있지만 참조형의 개수가 정해져 있지 않은 이유는 이처럼 프로그래머가 새로운 타입을 추가할 수 있기 때문이다. 
시간을 표현하기 위해서 다음과 같이 3개의 변수를 선언할 수 있다.


int hour;             // 시간을 표현하기 위한 변수 
int minute;             // 분을 표현하기 위한 변수 
float second;    // 초를 표현하기 위한 변수, 백분의 일초까지 표현하기 위해 float로 했다. 


만일 3개의 시간을 다뤄야 한다면 다음과 같이 해야 할 것이다. 


int hour1, hour2, hour3; 
int minute1, minute2, minute3; 
float second1, second2, second3; 


이처럼, 다뤄야 하는 시간의 개수가 늘어날 때마다 시, 분, 초를 위한 변수를 추가해줘야 하는데 다뤄야 하는 데이터의 개수가 많으면 이런 식으로는 곤란하다. 


int[] hour = new int[3]; 
int[] minute; = new int[3]; 
float[] second; = new float[3]; 


배열로 처리하면 다뤄야 하는 시간 데이터의 개수가 늘어나더라도 배열의 크기만 변경해주면 되므로, 변수를 매번 새로 선언해줘야 하는 불편함과 복잡함은 없어졌다. 그러나 하나의 시간을 구성하는 시, 분, 초가 서로 분리되어 있기 때문에 프로그램 수행과정에서 시, 분, 초가 따로 뒤섞여서 올바르지 않은 데이터가 될 가능성이 있다. 이런 경우 시, 분, 초를 하나로 묶는 사용자정의 타입, 즉 클래스를 정의하여 사용해야한다. 


class Time { 
      int hour; 
      int minute; 
      float second; 



시, 분, 초를 저장히기 위한 세 변수를 멤버변수로 갖는 Time클래스를 정의하였다. 새로 작성된 사용자정의 타입인 Time클래스를 사용해서 코드를 변경하면 다음과 같다. 

 

이제 시, 분, 초가 하나의 단위로 묶어서 다루어지기 때문에 다른 시간 데이터와 섞이는 일은 없겠지만, 시간 데이터에는 다음과 같은 추가적인 제약조건이 있다.


1. 시, 분, 초는 모두 0보다 커야한다. 
2. 시의 범위는 0~23, 분과 초의 범위는 0~59이다.


이러한 조건들이 모두 프로그램 코드에 반영될 때, 보다 정확한 데이터를 유지할 수 있을 것이다. 객체지향언어가 아닌 언어에서는 이러한 추가적인 조건들을 반영하기가 어렵다. 그러나 객체지향언어에서는 제어자와 메서드를 이용해서 이러한 조건들을 코드에 쉽게 반영할 수 있다. 
아직 제어자에 대해서 배우지는 않았지만 위의 조건들을 반영하여 Time클래스를 작성해 보았다. 가볍게 참고만 하기 바란다. 


public class Time { 
      private int hour; 
      private int minute; 
      private int second; 

      public int getHour() { 
            return hour; 
      } 

      public void setHour(int h) { 
            if (h < 0 || h > 23) 
                  return; 
            hour=h; 
      } 

      public int getMinute() { 
            return minute; 
      } 

      public void setMinute(int m) { 
            if (m < 0 || m > 59) 
                  return; 
            minute=m; 
      } 

      public int getSecond() { 
            return second; 
      } 

      public void setSecond(int s) { 
            if (s < 0 || s > 59) 
                  return; 
            second=s; 
      } 



제어자를 이용해서 변수의 값을 직접 변경하지 못하도록 하고 대신 메서드를 통해서 값변경하도록 작성하였다. 값을 변경할 때 지정된 값의 유효성을 조건문으로 점검한 다음에 유효한 값일 경우에만 변경한다.
이 외에도 시간의 차를 구하는 함수와 같이 시간과 관련된 함수를 추가로 정의하여 Time클래스를 향상시켜 보는 것도 좋은 프로그래밍 공부거리가 될 것이다.

출처 -  http://cafe.naver.com/javachobostudy