-
소설같은자바 1부(1장~5장)정리필요2 2008. 9. 1. 23:40
1장 클래스의 기본 배경
객체 지향 언어인 자바를 시작하면서 제일 먼저 접하게 되는 것은 바로 객체와 클래스라는 단어입니다. 이 두 단어는 객체지향언어의 기본이며 가장 중요한 부분이기 때문에 올바른 클래스의 배경 지식 없이 접하게 된다면 자바는 점점 어려워 질 것입니다.
클래스의 개념을 파악하기 위해서는 데이터타입, 변수, 상수의 의미를 제대로 아는 것이 중요합니다. 그리고 변수와 상수의 관계에서 나타나는 할당의 법칙을 이해 함으로서 각각의 관계에 대하여 안다면 우리는 언어적인 기초를 가지고 있다고 이야기 할 수 있습니다. 이러한 면에서 이책의 1장에서 소개되는 부분은 아주 기초적인 사항들을 다루고 있습니다. 그 기초적인 개념들은 아래와 같습니다.
n 데이터 타입
n 변수
n 상수
n 할당의 법칙
각각의 내용들은 클래스의 개념적 이해를 돕기 위한 언어적인 기초를 설명하고 있으며 이들을 이용해서 클래스와 객체라는 단어에 점차적으로 접근하고 있습니다. 클래스라는 것은 새롭게 탄생된 것이 아니라 기존 언어를 바탕으로 하여 보다 효율적인 방법론을 제시하고 있기 때문에 컴퓨터 언어의 배경적인 지식을 습득한다면 클래스의 개념을 보다 효율적으로 이해할 수 있으리라 생각합니다.
어떻게 보면, 1장은 모든 컴퓨터 언어 설명서에서 가장 정확하게 설명하려고 하는 부분입니다. 하지만 정확한 뜻을 파악하기 힘든 것은 언어적인 배경에 대하여 약간은 등한시하는 분위기가 있고, 이미 알고 있다는 전제 하에 설명이 이루어지고 있기 때문일 것입니다. 점점 개념들이 모호해지는 것이죠. 기본이 되는 부분을 설명하지 않고 클래스 자체만을 설명하기 때문에 많은 혼선을 유발하고 있습니다. 이러한 혼선을 없애기 위해서 클래스라는 단어가 컴퓨터 언어에서 어떻게 만들어졌는지 밑바닥부터 따져 보도록 하겠습니다.
1.1 데이터 타입이란?
1.1.1 데이터 타입의 의미?
데이터 타입이란 자료에 대한 형태를 의미합니다. 이것을 기록하고 보니 영어를 풀어서 쓴 것과 같군요. 현실세계에 존재하는 모든 데이터들은 그 나름대로 의미가 있고 어느 정도 사람이 알 수 있는 구분이 있습니다. 이러한 실세계의 데이터들이 컴퓨터에 사용될 때에는 더 명확한 구분을 하고 있습니다. 데이터의 형태들이 모호하면 컴퓨터가 헷갈려 하겠죠. 데이터를 분류하는 방법이란 것은 컴퓨터 언어에 정의 되어 있으며 여러분은 그 분류 원칙에 따라 데이터를 사용하게 됩니다. 컴퓨터 내에 데이터를 구분하는 방법론을 제시해 주고 있는 것이 바로 데이터 타입입니다. 데이터 타입을 자료에 대한 형태를 지정해주는 것이라고 부를 수 있으며, 우리는 영어 철자 그대로 Data Type이라고 부릅니다.
기본적인 데이터 타입들은 언어의 컴파일러를 만든 사람들이 구분 짓고 있습니다. 기본적인 구분은 수와 문자의 분류이며 수는 정수와 실수로 구분하고 있으며, 정수는 작은 수와 큰 수 그리고 실수는 정밀한 실수와 덜 정밀한 실수로 나누고 있습니다. 이러한 구분은 일반적으로 컴퓨터 내에서 하나의 데이터를 표현하기 위해서 어느 정도의 메모리를 사용하고 있느냐에 따라 분류되어집니다. 그리고, 각각의 구분법은 이미 언어 내에 정의 되어 있으며 우리는 그 규칙을 지키면서 사용하여야 합니다. 그렇다면 데이터 타입이 어떠한 역할을 하는지 정확하게 따져 보도록 하겠습니다.
1.1.2 데이터 타입의 역할
데이터라는 관점에서 두 가지 분류를 할 수 있습니다. 데이터를 구분 짓는 데이터 타입과 데이터 자체로 나눌 수 있습니다.
n 데이터 타입
n 데이터
자바에서 사용되는 데이터 타입은, 수를 표현하는 int, long, float, double등이 있으며, 문자를 표현하기 위한 char 그리고 참과 거짓을 표현하기 위한 boolean이 있습니다. 이러한 데이터 타입을 사용하는 것은 그렇게 어렵지 않습니다. 단지 어떤 식으로 사용하느냐가 문제죠. 간단하게 정의를 내린다면, 데이터타입은 데이터의 형태가 이렇다고만 지정할 뿐 그 자체를 사용하는 것은 아닙니다. int 자체를 사용할 수는 없습니다. 데이터 타입은 저 혼자서 아무 일도 할 수 없습니다.
데이터는 보통 우리가 말하는 정보 그 자체입니다. 예를 들어, 3은 데이터입니다. 그리고 ‘a’라는 문자 또한 데이터 입니다. 이러한 데이터를 구분 짓는 것이 바로 데이터 타입입니다.
데이터타입의 쓰임새와 데이터의 관계는 다음과 같습니다.
n 변수에 데이터의 형태를 지정한다.
n 데이터 타입은 변수를 만드는 역할을 한다.
n 데이터 타입이 지정되어진 변수에는 데이터를 넣을 수 있다.
데이터 타입은 모양만을 지정하기 때문에 데이터 타입 스스로 아무 일도 할 수 없습니다. 말 그대로 형태만 있을 뿐 보이지도 않고 스스로 존재할 수도 없는 아주 불쌍한 놈입니다. 일단, 자바에서 사용되는 데이터 타입이 어떤 것이 있는가를 보고 넘어가도록 하죠. 자바 언어에서 사용하는 대표적인 데이터 타입은 다음과 같습니다.
정 수 형
실 수 형
문자형
불형
데이터 타입
int
long
float
double
char
boolean
데이터
0
0L
0.0f
0.0d
‘a’
false
byte 수
4
8
4
8
2
1
이 이외에도 약간씩 변형된 기본 자료형이 몇 가지 더 존재합니다. 위의 표에서 알 수 있듯이 데이터 타입은 데이터의 분류법입니다. 그런데, 이상하게도 각각의 데이터 타입에 byte수라는 것을 표시하고 있습니다. 이 의미는 “데이터 타입은 변수에 데이터의 형태를 지정한다, 데이터 타입은 변수를 만드는 역할을 한다”라고 말한 부분과 관련이 있습니다. 하나의 변수를 만드는데 그냥 만들 수는 없죠. 컴퓨터 내에 사용할 변수를 만드는데 그 변수에 어떠한 데이터가 들어 갈 수 있는지는 명시해야 하지 않을까요? 그래서 변수를 만들 때 어떠한 데이터가 들어가며 메모리는 몇 바이트나 차지하는지를 명시해 주는 것입니다. 메모리를 차지하는 바이트수는 데이터 타입에 따라 다르게 지정됩니다.
데이터 타입은 형태이기때문에 모양만을 가지고 있습니다. 실제 사용하기 위해서는 다른 방법을 사용합니다. 이미 설명했듯이 변수라는 것을 사용합니다. 컴퓨터의 메모리 속에서 형태 그 자체 만으로는 아무런 의미가 없습니다. 형태에 이름을 부여한다면 컴퓨터의 메모리 속에서 특정한 형태의 메모리가 생성되며 이 메모리를 가르키는 이름이 바로 변수가 됩니다. 그래서, 데이터 타입은 변수를 만드는 일을 한다고 한 것입니다.
정리를 해보죠. 데이터 타입은 형태만을 나타낼 뿐 메모리 속에서는 존재할 수 없습니다. 존재할 수 없다는 것은 답답한 일인데 이것을 존재하게 만들 수 있도록 만드는 방법은 바로 변수라는 것을 만들어 존재하게 합니다. 이름을 주는 것이죠. 데이터 타입이 하는 일은 바로 변수를 생성하는 일입니다. 어떠한 모양의 변수를 만들지를 결정하는 것이 바로 데이터 타입을 결정하는 일입니다. 아래의 예제에서 int a라고 한 부분에서, int는 데이터 타입이겠죠. 그리고 a는 변수입니다. 이렇게 하면, int모양의 메모리를 가진 a라는 이름의 변수가 생성되어지고 이제부터 a에는 int모양의 값을 넣을 수 있습니다. 데이터 타입에 관련된 간단한 예제를 하나 살펴 보도록 하겠습니다.
Test.java(변수의 개념을 설명하기 위한 예제)
public class Test1{
public static void main(String[] args){
int a = 1; //int type의 a변수선언
int b = 2; //int type의 b변수선언
int c = a + b; //int type의 c변수선언
System.out.println("a b 의 합은 : " + c);//c의 결과 출력
}
}C:\examples\1.클래스의 개념>javac Test1.java
C:\examples\1.클래스의 개념>java Test1
a b 의 합은 : 3이 예제는 데이터 타입이 존재하는 방식을 증명해 주고 있습니다. a, b, c는 int형태의 데이터 타입이며 a, b, c라는 이름으로 메모리에 존재하게 되는 것입니다. 이것은 아주 간단해 보이지만, 데이터 타입을 사용하는 방법을 잘 보여 주고 있습니다. 먼저 변수 이름을 정하기 전에 타입을 명시해 준 후 개별적인 이름을 부여하고 있습니다. 이름은 어떻게 부여하느냐고요? 사소한 규칙 몇 가지만 지킨다면 이것을 만드는 사람 임의로 만들 수 있습니다. 그리고 여러분의 손끝에서 int a라고 한다면, int 타입의 4바이트의 메모리가 a라는 이름으로 생성이 되었다는 것을 의미합니다. 그리고 다시 b, c라고 했기 때문에 전체 12바이트의 메모리를 여러분의 손으로 할당한 것입니다.
1.1.3 결론
결론적으로, 데이터 타입, 변수, 데이터의 관계는 필연적인 관계입니다. 데이터 타입은 변수를 생성하는 일입니다. 변수를 생성할 때 데이터 타입은 어떠한 형태의 변수를 지정할 지, 몇 바이트나 되는 메모리를 할당할 지를 결정하는 역할을 합니다. 그리고 만들어진 변수에는 데이터를 넣습니다.
데이터 타입에 대해서 자세하게 설명을 붙이는 이유는 다른데 있습니다. 지금까지는 만들어져 있는 데이터 타입만을 사용했습니다. 그런데, C++이후의 언어에서는 우리가 직접 만든 데이터 타입을 사용하게 됩니다. 이것을 사용자 정의 데이터 타입이라 부르며 자바에서는 클래스라고 부릅니다.
자바 프로그래머를 시작하는 대부분의 사람들이 데이터 타입의 역할을 이해 못하고 있고, 데이터 타입을 제대로 이해한다고 해도 많은 혼선을 경험하고 있기 때문에 책을 시작하는 초반부에 데이터 타입에 대하여 정확한 설명을 붙이고 있습니다. 참고적으로, 이 데이터 타입의 의미는 모든 언어에 공통적으로 사용되는 아주 기본적인 사항입니다. 이것만을 알고 들어가도 자바는 쉬워집니다. 느낌 그대로죠.
1.2 변수
1.2.1 변수란?
변수란 데이터를 담을 수 있는 그릇입니다. 데이터 타입이 변수를 만든다면 변수는 그 속에 데이터 타입에 명시된 형태의 데이터를 담을 수 있습니다. 데이터 타입으로 변수를 만든다는 것은 메모리 속에 데이터 타입에서 명시한 모양의 메모리를 생성할 수 있습니다. 그리고는 그 메모리에 어떠한 데이터를 넣는데, 그 데이터는 데이터 타입에 명시된 데이터라야 된다는 것입니다. 이것을 정리하면
n 데이터 타입이 변수를 만들 수 있다.(변수 생성)
n 변수가 만들어진다는 것은 메모리생성의 의미이다. (변수생성 = 메모리생성)
n 생성된 변수에는 해당 데이터 타입의 데이터를 넣을 수 있다.(할당)
이러한 방식으로 데이터, 데이터 타입의 중간자 역할을 하는 변수 그리고 데이터 타입으로 세분화 시켜 볼 수 있습니다.
1.2.2 변수의 존재
변수는 데이터타입 없이는 홀로 존재할 수 없습니다. 즉, 데이터타입이 자신의 형태를 지정해 주기 때문에 존재하는 것입니다. 이것은 반드시 지켜져야 되는 규칙이며, 이 규칙을 깨는 법은 절대 용납되어서는 안 된다고 생각하시면 됩니다. 데이터타입의 허락을 얻어야만 하나의 변수로서 역할을 할 수 있으며, 변수는 메모리에 존재의 의미를 그대로 담고 있습니다. 데이터타입은 홀로 메모리를 생성 할 수 없는 불쌍한 놈입니다. 자신이 존재하기 위해서는 변수라는 것을 통해서 자신의 형태를 존재하게 하는 것입니다.
철학적으로 정리를 해 보죠. 데이터타입은 모양만을 가지고 있는 무존재이며 변수는 데이터타입을 정해 주면 메모리에 존재할 수 있는 존재의 의미를 담고 있습니다. 이것을 잊어버리면 거의 프로그램을 만든다는 의미는 없다고 봐도 과언이 아니죠. 보통의 변수가 존재하는 법은 다음과 같습니다.
변수의 생성
int x;
int a, b;
long z;
앞에서 언급한 것과 같이 “데이터타입 변수명” 의 형식을 따르게 됩니다. 이러한 방식으로 데이터 타입과 변수를 사용하는 것입니다. 한번 더 밝혀 두지만, 데이터타입 혼자서만은 아무것도 할 수 없으며 변수가 존재하기 위해서는 반드시 데이터 타입을 지정해야만 합니다. 이것은 불변의 규칙입니다.
1.2.3 결론
변수를 만드는 이유는 데이터를 보관하기 위해서 입니다. 데이터 타입으로 변수를 만들며, 변수는 데이터를 담을 수 있는 그릇의 역할을 합니다. 변수에 아무것도 넣지 않으면 필요가 없죠. 그렇다면 뭔가를 변수에 넣어야 변수의 역할을 하게 됩니다. 데이터 타입은 단순히 해당되는 형태의 메모리를 생성할 뿐 더 이상의 일은 하지 않고, 변수는 데이터를 보관할 수 있는 일을 합니다. 여기에 들어가는 데이터는 반드시 변수를 만들 때 명시한 데이터 타입에 맞는 데이터의 형을 넣어야 한다는 것이 핵심입니다.
변수는 데이터타입의 주어진 형태대로 메모리를 생성한 후 컴퓨터 내에 존재하며, 변수가 생성 된 후에는 주어진 형태에 맞는 데이터를 넣을 수 있습니다. 그리고, 변수를 아무리 많이 만들어도 어떠한 데이터를 넣지 않으면 필요가 없습니다. 이 데이터는 변수를 만들 때 사용한 데이터타입에 맞는 형의 데이터를 넣어야만 한다는 것은 모든 언어의 아주 기본적인 특징입니다. 계속해서, 우리는 변수에 들어가는 데이터이라는 측면과 특정 데이터를 변수에 넣는 방법이라는 측면에서 상수와 할당이라는 것에 대해 자세히 알아 보도록 하겠습니다.
1.3 상수
1.3.1 상수란?
상수는 그 자체가 데이터라는 것을 의미합니다. 다른 말로 하면 상수는 항상 수, 항상 데이터라고 말 할 수 있습니다. 변수의 의미는 데이터를 보관하는 일이지만 데이터 그 자체를 말할 때 우리는 상수라는 단어를 사용합니다. 상수가 데이터라고 한다면 당연히 자료의 구분 즉, 데이터 타입의 분류가 있습니다. 모든 자료는 데이터 타입의 관점에서 분류할 수 있으며 분류에 따른 표현법도 있습니다. 이러한 분류법과 표현법에 따라서 해당 데이터 타입에 관련된 각각의 상수들이 존재합니다. 물론 이렇게 세분화 시켜서 분류하는 이유는 바로 보다 인간보다 컴퓨터가 더 까다롭기 때문입니다. 정확하게 가르쳐주지 않으면 모르는 바보 컴퓨터죠.
1.3.2 상수의 분류와 표현
자료의 데이터 타입에 따른 분류와 표현은 아주 간단합니다. 앞에서 배운 데이터 타입, 그 자체가 분류가 되며 그 분류에 따라서 표현되어지는 방법이 다릅니다. 이것을 구분 지어보면 다음과 같습니다.
데이터의 분류와 표현법
정 수 형
실 수 형
문자형
불형
데이터 타입
int
long
float
double
char
boolean
byte 수
4
8
4
8
2
1
표현법
0, 100
100L
0.0f
0.0d
‘a’, ‘c’
false,true
* float d = 0.0f; f를 붙이지 않으면 드폴트로 double이 된다.
변수에 할당하는 상수의 표현
n int : 10진수로 표현
n char : 작은 따옴표로 묶는다.
n long : 숫자 뒤에 l(영문자L)을 붙인다.
n float : 숫자 뒤에 f를 붙인다.
n double : 숫자 뒤에 d를 붙인다.
* int형과 long형 모두 1이라는 숫자를 포함하고 있습니다. 이러한 구분을 하기 위해서 상수 뒤에 형에 맞는 문자를 붙이게 됩니다. float형과 double형도 같은 원리에 의해서 구분됩니다.
위의 표에서와 같은 표현법을 사용해서 상수를 표시하고 그 상수를 적절한 데이터형에 맞는 변수에 넣어야만 프로그램언어에서 컴파일할 때 에러가 나지 않습니다. 이 형에 대한 컴파일러의 점검은 아주 엄격하게 검사되며, 이 규칙을 무시하고 사용했을 때는 컴파일러는 언제나 그런 것처럼 무차별적으로 에러를 발생시킵니다.
'a', 'b', 'A', 'B' 등의 아스키코드와 1, 100, 200등의 정수 그리고 0.01f와 같은 실수는 프로그램상에서 인식할 수 있는 상수입니다. 이러한 상수의 개념은 이미 만들어져 있는, 우리가 바꿀 수도 없다는 의미에서 상수라고 부르기도 합니다.
상수의 진정한 의미에 대해 한번 생각 해 보고 넘어가기로 하겠습니다. 상수는 그 자체가 수이며 내가 만든 것이 아니라 이미 만들어져 있는 것을 말합니다. 이미 만들어져 있는 것이라는 의미는 내가 만든 것이 아니며 내가 바꿀 수도 없는 것이라고 생각하면 됩니다. “100은 내가 만든 것이 아니며, 이미 존재하는 것이며, 내가 바꿀 수 없다”라는 말은 참입니다. 일반적으로 우리가 알고 있으며 바꿀 수 없는 그러한 데이터를 상수라고 합니다.
컴퓨터의 컴파일러는 이미 이러한 상수의 개념을 가지고 있습니다. 당연히 1, 2, 3, 100, 1000, 같은 수를 이미 알고 있으며 이것을 사용하여 계산을 합니다. 즉 상수는 그 자체가 데이터의 의미를 담고 있으며 이 데이터는 바꿀 수 없는 진리의 데이터를 말합니다.
상수의 느낌은 다음과 같습니다. “나무” 라는 데이터가 있습니다. 물론, 이것은 스트링형의 데이터입니다. 이제부터 “나무”를 제가 “바보나무”라고 하겠다고 한다면, 어떻게 될까요? 아마 현실세계에 이렇게 주장하고 다닌다면, 정말 바보 취급을 받겠죠. 100이라는 숫자형태의 데이터가 있습니다. 지금부터 100을 1000이라고 하겠다고 주장한다면 이 또한 같은 일이 발생합니다. 결론적으로, 상수는 절대 바꿀 수 없으며, 그 자체로 데이터의 의미를 담고 있으며, 미리 우리가 알고 있는 진리라고 할 수 있습니다. 바로 이러한 면이 상수의 느낌입니다.
앞에서 이미 언급했지만 데이터 타입은 무 존재로서 결코 혼자서는 존재할 수 없으며 이것이 존재하는 방식은 변수의 방식으로 존재하게 됩니다. 이 존재의 공간에 담을 수 있는 데이터를 우리는 상수라고 부르며 상수 자체는 이미 존재하고 있는 실존의 의미를 담고 있습니다. 이러한 원리를 아래의 표와 같습니다.
종류
존재여부
데이터 타입
변수
상수
무 존재
존재
실존
이러한 아주 단순한 원리를 컴퓨터 언어를 배우면서 거의 무시하기 때문에 처음 접하는 이들이 당황하고 결코 쉽게 느낄 수 없을지도 모릅니다.
1.3.3 결론
데이터 타입은 변수를 생성하여 존재하게 하고, 존재하는 변수의 빈 그릇에는 상수를 넣는 것, 이것을 다시 컴퓨터의 언어와 연결하여 나타내면 다음과 같습니다.
int a = 3; 에 대한 분류
종류
존재여부
예
데이터 타입
변수
상수
무 존재
존재
실존
int
a
3
위의 표에 표시되어 있는 “int a = 3;” 에서 우리는 데이터 타입과 변수, 상수의 사용을 정확하게 볼 수 있습니다. 데이터 타입 int라는 것으로 변수 x를 선언하였으며 x에는 3을 할당하고 있습니다.
이쯤에서, 우리는 변수와 상수사이의 또 다른 관계를 찾아 볼 수 있습니다. 변수에 데이터를 넣은 방법이라는 관점에서 본다면, 우리는 이것을 할당이라는 말을 사용합니다. 다음 절에서는 할당이란 무엇인지에 대하여 알아 보도록 하겠습니다.
1.4 할당의 법칙
1.4.1 할당이란
변수에 데이터를 넣는 것을 우리는 할당(assignment)이라고 합니다. 할당의 법칙은 변수를 선언하고 난 후, 변수에 값을 넣는다라고 말할 수 있습니다. 이때 이용되는 연산자가 바로 “=” 연산자입니다. “=”연산자는 직접 할당의 의미를 가지고 있으며 이것은 변수에 값을 넣는다는 의미를 가지고 있지만 “=” 자체에서 풍기는 맛은 바로 데이터 타입에 맞는 타입의 값을 할당해 달라는 의미를 가지고 있습니다.
1.4.2 할당의 기본 법칙
변수에 상수를 할당하는 방법론도 여러 가지 방식으로 가능합니다. 이러한 방법을 알아 보도록 하겠습니다. 가장 기본적인 방법론은 다음과 같습니다.
n 오른쪽에서 왼쪽으로만 할당 가능하다.(절대적)
n 변수끼리도 할당가능하다.(오른쪽에서 왼쪽으로 할당)
n 변수에 이미 상수가 존재하더라도 다른 것을 할당하면 마지막에 할당한 것이 할당됩니다.
우리는 쉽게 할당을 하지만 그 기본원리는 항상 있기 마련입니다. 위의 규칙은 너무 단순하면서도 꼭 지켜지고 있는 규칙입니다. 위의 규칙을 이용한 몇 가지 예를 살펴보도록 하겠습니다.
할당의 예
n 선언과 할당을 동시에 : int a = 100
n 선언과 할당을 분리 : int a; a=100
n 여러 개를 동시에 선언과 할당 : int a=100, b=200;
n 분리하여 여러 개를 동시에 선언과 할당 : int a, b; a=100, b=200;
n 변수끼리의 할당 : int a=100, b; b=a;
위의 수식에서 우리는 지금까지 설명한 모든 것을 다 밝히고 있습니다. 선언하는 방법과 선언과 동시에 할당하는 방법 등…. 할당과 선언의 방법이 몇 가지 존재하지만 모두 같은 방식이며 일정한 규칙을 따르고 있습니다. 만약 다음과 같은 예를 본다면 할당의 완전한 법칙을 이해 할 수 있을 것입니다.
할당의 기본 법칙의 예
int sum=100;
sum = sum + 200 ;
위의 예에서 sum은 왼쪽과 오른쪽 모두 존재합니다. 하지만 오른쪽에서 왼쪽으로 할당 되기 때문에 “sum + 200”이 먼저 작업이 이루어진 뒤, 최종적으로 sum에 처리된 결과가 할당되는 것입니다. 이러한 원리에 따라 처리하면 결과 값은 당연히 300으로 나오는 것을 짐작할 수 있을 것입니다. 꼭 초등학교 산수를 배우는 기분이 드는군요.
1.4.3 할당에 관련된 implementation
다음의 예제는 0부터 99까지의 합을 출력하는 프로그램입니다. sum 변수와 i변수의 역할을 지금까지 배운 것을 이용해서 관찰해 보시기 바랍니다.
Test2.java(할당을 이해하기 위한 예제)
public class Test2{
public static void main(String[] args) {
int sum = 0;
for (int i=0;i<100 ;i++ ) {
sum = sum + i;
}
System.out.println("sum은 :" + sum);
}
}C:\examples\1.클래스의 개념>javac Test2.java
C:\examples\1.클래스의 개념>java Test2
sum은 :4950
1.5 마무리
데이터 타입(Datatype)이란 데이터에 대한 형태를 의미합니다. 수많은 글들이 같은 말을 반복하고 있지만 여전히 그냥 넘어가는 부분일지도 모릅니다. 데이터 타입, 변수, 상수의 관계를 안다면 자바 언어 자체를 편하게 이해 할 수 있을 것입니다. 그 다음으로 변수와 상수에 대해서 알아 보았으며 마지막으로 변수와 상수의 관계에서 나타나는 할당이라는 것을 알아 보았습니다.
이로써 우리는 클래스의 배경이 되는 몇 가지 기초 상식에 대해서 알아 보았습니다. 기초라고 해서 얕보기 쉬운데 약간의 자바 경험이 있다면 이 부분이 얼마나 무시하기 힘든 부분인지 정확하게 알게 될 것입니다. 이 장에서는 클래스의 배경에 대해서 설명하고 있지만 다음 장에서는 이 장에서 나온 데이터타입과 변수, 상수, 할당의 개념을 이용하여 클래스 그 자체를 설명하고 있습니다. 자바를 시작하는 이에게 꼭 알려 주고 싶은 정보가 있다면 이장과 다음 장에 있는 개념들입니다. 간단하지만 결코 간단하지 않은 개념들을 배워 보도록 하겠습니다.
2장 기본 클래스의 제작
클래스의 배경이 되는 여러 가지 요소들을 1장의 클래스의 기본 배경에서 알아 보았습니다. 데이터타입, 변수, 상수, 할당을 클래스라는 관점에서 어떠한 관련이 있는지를 알아 보겠습니다. 그리고 클래스의 전신이라 할 수 있는 구조체가 어떻게 관련이 있는지에 대해서 알아보고 실제 클래스가 어떻게 만들어지고 어떻게 사용되어지는지 살펴보도록 하겠습니다.
이 장에서 소개하는 내용은 다음과 같습니다. '
n 클래스란?
n 구조체로 데이터 타입을 생성하는 방법
n 클래스로 데이터 타입을 생성하는 방법
n 클래스만의 특징
n 클래스 변수를 생성하는 법의 차이
클래스와 객체의 의미는 혼동되는 요소들입니다. 처음으로 객체와 클래스를 접했을 때, 이 개념을 이해하는데 거의 1년 이상 걸린다고 합니다. 프로그래머로서 가장 잘 다루어야 하는 부분이지만 너무나도 간단히 책장을 넘겨버리기 때문에 대충 넘어가기 쉬운 부분이기도 합니다. 프로그램을 한참 배우고 난 후, 진정한 클래스의 의미에 대해 다시 생각해 볼 때쯤에 이해 할 법한 부분입니다. 보통의 자바 Beginner의 경우, 이 개념을 안다면 자바의 절반이상을 깨닫고 있다고 해도 과언이 아닙니다. 이 장에서 설명되는 대부분의 것들은 앞장의 데이터 타입을 습득하지 않으면 거의 이해 할 수 없게 설명되어 있습니다. 앞장의 데이터 타입을 한번 읽어보는 것은 건강에 좋습니다.
2.1 클래스란?
2.1.1 클래스란?
클래스란 새로운 데이터 타입을 만드는 데이터 타입 생성기입니다. 기존의 데이터 타입을 이용하여 새로운 데이터 타입을 만들어내는 것이 바로 클래스의 역할입니다. 클래스가 데이터 타입 생성기라는 말만 이해하고 있어도 많은 부분이 해결되는 느낌을 가지시는 분이 이미 있을 것입니다.
여러분은 지금까지 클래스로 새로운 데이터타입을 만들고 있었던 것입니다. 보통 뭔가를 탐구하면서 뭘 하는지 모르고 일을 하고 있을 때만큼 답답한 순간은 없습니다. 자바언어를 조금이라도 공부하신 분은 수없이 클래스를 만들고, 그 클래스로 작업을 합니다. 그런데 정작 클래스가 뭘 하는 건지 모르고 작업하는 경우가 많습니다. 여기서 정확하게 정의를 내려 두겠습니다. .
n 클래스는 새로운 데이터 타입을 만드는 데이터 타입 생성기이다.
이 명제는 자바 뿐만 아니라 C++관련언어를 한다면 누구나 다 기억해야 하는 객체지향언어의 제1조의 원칙에 해당 됩니다. 자! 이제부터 클래스의 좀더 자세한 내용을 알아 보도록 하겠습니다.
2.1.2. 클래스의 지위와 구조
앞장에서 설명한 데이터 타입과 변수와 상수의 구조는 계층구조로 되어 있습니다. 이 구조를 알고 있다면 쉽게 클래스의 개념에 접근 할 수 있습니다. 그 구조를 다시 살펴보면 다음과 같습니다.
데이터 타입과 변수와 상수의 구조
데이터 타입
변수
상수
무 존재
존재
실존
데이터 타입의 하위에는 변수가 존재하며 변수의 하위에는 상수가 존재합니다. 각각의 역할이 없으면 프로그램은 수행 될 수 없습니다. 그렇다면 클래스는 어느 부분에 속하는 것일까요? 위의 구조에서 제일 상위에 존재하는 것이 바로 클래스입니다. 클래스는 데이터 타입의 윗부분에 자리를 차지하고 있으며 데이터 타입과 관련이 있습니다. 그 구조를 다시 만든다면 아래와 같이 나타낼 수 있습니다.
데이터 타입과 변수와 상수의 구조
클래스
데이터 타입
변수
상수
무
무 존재
존재
실존
무엇에 사용하는 것이길래 제일 위에 있을까요? 답은 데이터 타입을 새로 하나 만드는 역할을 담당하고 있습니다. 데이터 타입은 형태는 있지만 존재하지 않기 때문에 무존재라고 칭했습니다. 그렇지만 클래스는 그 상위레벨에 있기 때문에 아예 무에서 무존재, 즉 무형을 만드는 역할을 합니다. 그래서 위의 표에서 무(無)라고 표시했습니다. 클래스는 신(神)이라고 생각되는군요. 신은 인간이라는 데이터 타입을 만들었습니다. 그리고 인간이라는 데이터 타입에 각각의 이름을 부여하여 하나의 객체(변수)로 만들었습니다. 인간이라는 데이터 타입을 만든 이는 바로 클래스입니다. 그래서 클래스는 신이죠. 개인적인 생각입니다.!! 현재에서 가장 핵심적인 사항은 “클래스는 데이터 타입 생성기의 역할을 한다”라는 것입니다. 주제는 잊지 말아야죠.
자바에서 제공하는 기본적인 데이터 타입은 int, long, float, double, char…. 등이 있는데 이러한 데이터 타입을 우리는 기본데이터타입이라고 부릅니다. 기본데이터 타입의 한계는 하나의 데이터만을 넣을 수 밖에 없다 단점이 있습니다. 각각의 형태에 해당되는 데이터를 하나만을 담을 수 있는 것이죠. 현실세계의 데이터들은 제일 밑바닥에는 이러한 기본 데이터들로 이루어져 있겠지만 알고 보면 기본적인 데이터를 묶어서 또 다른 형태의 틀을 만듭니다. 예를 들어 인간이라는 데이터 타입이 있습니다. 이 데이터 타입의 형태는 여러분이 어렴풋이 생각하는 인간의 형태 그 자체입니다. 여기에 변수를 붙이면, 즉 이름을 홍길동이라고 준다면 홍길동은 현실 세계에 공간을 차지하는 하나의 변수가 됩니다.
n 인간 홍길동;
자세히 들여다보면 인간이라는 데이터 타입의 밑바닥은 변수로 이루어져 있으며, 많은 변수가 뭉쳐서 하나의 데이터 타입을 이루고 있습니다. 이와 같이 여러 개의 변수를 묶어서 하나의 데이터 타입으로 생성하는 역할을 하는 놈이 바로 class입니다. 인간이 만들어지기 위해서는 클래스에서 아주 다양한 형태의 작업을 해주어야 합니다. 클래스로 간단히 인간이라는 데이터 타입을 만드는 것을 보여주고 있습니다.
인간 클래스의 디자인
class 인간{
String name;
int age;
float height;
float weight;
등등….
}
이쯤에서, 다시 한번 class에 대해서 정의를 내리면, 많은 변수들이 뭉쳐서 새로운 데이터 타입을 만들어 내는 역할을 하는 것이 클래스라고 정의를 내립니다. 그런데 왜 데이터가 아니고 변수일까요? 그것은 인간이 태어나면 각각의 이름과 나이, 키등을 부여 받습니다. 각각이 생성되어지기 전 단계이기 때문에 생성되는 순간 어떠한 값이 할당 될지 알 수 없으니 즉, 데이터 타입은 형태에서 만들어지는 순간 각각 다른 값을 가져야 하기 때문에 변수의 형태로 데이터 타입 내에 들어가는 것입니다.
자바에서는 클래스를 사용하지만, 자바 이전의 C언어에서는 구조체라는 것을 사용합니다.구조체는 자바의 전단계에 데이터 타입을 만드는 놈이었습니다. 클래스의 할아버지정도 되는 구조체의 의미를 이해한다면 클래스의 정확한 특징을 이해 할 수 있을 것입니다. 여기서 간단히 구조체라는 것을 소개지만 더욱 자세하게 다음 절에서 설명을 하겠습니다.
2.1.3 결론
클래스는 데이터 타입을 만들며 그리고 데이터 타입은 변수를 생성하며 변수에는 상수를 할당할 수 있습니다. 클래스는 새로운 데이터 타입을 만드는 데이터타입 생성기입니다. 그리고 다음 장에서 구조체와 클래스 그리고 데이터 타입 생성기라는 측면에서 클래스를 살펴보도록 하겠습니다.
2.2 구조체로 데이터타입을 생성하는 방법
2.2.1 구조체란?
구조체는 표준 C에서 클래스가 없던 시절에 새로운 데이터 타입을 만드는 역할을 담당하고 있었습니다. 현재 우리가 언급한 데이터 타입은 분명히 하나만의 데이터를 담을 수 있는 데이터 타입입니다. “int a=3” 이라고 했을 때 우리는 a라는 변수에 다른 수를 담고자 한다면 3은 사라지고 맙니다. a=5라고 했을 때 a에 들어 있던 3은 없어지고 다시 5라는 수가 a의 메모리에 자리를 잡게 됩니다. 이러한 측면에서 기본 데이터 타입의 문제점은 단 하나의 데이터를 넣을 수 있다는 것입니다.
데이터 타입이 2개의 수를 담을 수 있다고 한다면 어떨까요? 표준 C(ANSI C)언어에서는 이를 구조체라는 것을 사용하여 해결하고 있습니다. 물론, 자바언어에서는 사용하지 않지만 구조체의 의미는 객체언어의 역사를 말해 주고 있기 때문에 구조체에 대해 약간의 지식을 가지고 있으면 자바언어가 어떠한 구조로 되어 있는지 쉽게 이해 할 수 있습니다. 구조체의 정확한 문법구조는 알 필요가 없습니다. 단순히 아 이렇게 만드는구나 정도를 이해 하시면 됩니다. 일단 구조체의 예를 살펴 보도록 하겠습니다.
2.2.2 구조체를 만드는 방법
클래스로 데이터 타입을 만든다고 했습니다. 구조체는 데이터타입을 생성하는 데이터 타입 생성기 역할을 하고 있기 때문에 클래스와 같은 레벨에 존재합니다. 그래서 구조체는 클래스와 동급입니다. 구조체는 완벽하게 클래스와 그 의미는 같습니다. 그리고 초기 구조 또한 클래스와 아주 흡사합니다. 구조체의 위치는 아래에서와 같이 제일 위에 존재합니다.
구조체, 데이터 타입, 변수, 상수의 구조
구조체
데이터 타입
변수
상수
무
무 존재
존재
실존
구조체로 데이터 타입을 생성하는 예를 살펴 보겠습니다.
구조체 사용의 예
struct PERSON
{
int age;
long height;
float weight;
} family_member;
struct PERSON sister; // C style structure declaration
PERSON brother; // C++ style structure declarationsister.age = 13; // assign values to members
brother.age = 7;자바언어는 C++의 문법 구조를 따르고 있으며 C++는 C언어를 기반으로 하고 있습니다. C언어의 구조체는 C++에 와서 완전하게 다른 모습인 class라는 이름으로 발전하여 원래의 구조체와 공존하게 됩니다. 현재 소개되는 C, C++문법을 잘 살펴 보면 특정한 Rule을 찾을 수 있을 겁니다. 그리고 이러한 Rule은 자바언어에서도 그대로 사용하게 됩니다.
위의 구조체에 대한 예를 하나씩 따져 보도록 하죠. 구조체라는 것은 앞에 struct이라는 키워드를 사용합니다. 그리고 이름을 지정합니다. PERSON이라는 이름으로 구조체를 만든다는 의미겠죠. 그리고 그 내부에는 3개의 변수를 포함하고 있습니다. struct키워드를 이용하여 3개의 변수를 묶어서 PERSON이라고 부르겠다는 것입니다. 하나씩 분해해서 보면 다음과 같습니다.
데이터 타입과 변수와 상수의 구조
n struct : 구조체를 만드는 키워드
n PERSON : 구조체로 만들어진 새로운 데이터 타입
n int age : PERSON 내부의 멤버
n int height : PERSON 내부의 멤버
n int weight : PERSON 내부의 멤버
이 부분에서 구조체에 대해 헷갈리지 않게 이해를 잘 해야 겠죠. 구조체는 새로운 형태의 데이터 타입을 만드는 역할을 합니다. 이러한 점에서 구조체는 클래스와 같은 역할을 합니다. 여기서 PERSON은 새로운 데이터 타입이며 struct은 표준 C언어에서 제공하는 데이터 타입 생성기입니다.
n 누가 만들었을까요?
그것은 누구나 만들 수 있으며 여기서는 제가 임의의 이름으로 하나 만들었습니다.
n PERSON이라는 이름은 무엇일까요?
새로운 데이터 타입의 이름입니다.
n struct는 무엇을 하는 것일까요?
새로운 데이터 타입을 만들 수 있게 해주는 키워드입니다.
n age, height, weight는 새로운 데이터 타입 내부에 존재하는 멤버입니다.
정리를 해보죠. 기존에는 주어진 데이터타입으로 데이터를 하나만 넣을 수 있는 변수를 선언하여 하나의 데이터만을 넣어 사용했습니다. 이제는 달라졌습니다. 구조체를 이용하여 PERSON이라는 새로운 데이터 타입을 만들었으며, 그 속에는 3개의 변수를 포함하고 있으며, 3개의 변수들을 대표하는 이름을 바로 PERSON으로 하겠다는 것입니다.
앞 장에서 언급한 데이터 타입을 상기시켜보십시오. 데이터 타입은 어디에 사용한다고 했습니까? 바로 변수를 선언할 수 있다는 것입니다. 위의 만들어진 데이터 타입 PERSON을 이용하여 아래와 같이 변수를 선언 했습니다.
PERSON brother;
이와 같이 변수를 하나 만들었을 때 메모리가 하나 생성되어집니다. 여기서, 변수이름은 누가 뭐래도 brother입니다. 하지만 아주 곤란한 문제가 발생합니다. 기존의 방식대로라면 이 속에 있는 3개의 변수에 값을 넣을 수가 없습니다. 그래서 나온 것이 점의(.) 원리입니다. 그냥 단순히 이해하자면 소속이라는 의미겠죠.
PERSON brother;
brother.age =100
brother.height=170L
brother.weight=67.5F
이와 같이 사용자가 임의로 새로운 데이터타입을 만들며 그리고 그 타입을 사용할 수 있게 하는 것이 ANSI C의 구조체입니다. 구조체의 의미는 여기까지만 알아 두도록 하죠. 더 이상 구조체는 자바에서 사용되지 않습니다. 하지만 이 구조체야 말로 자바언어에 대한 핵심의 의미를 그대로 담고 있으니 그 느낌만큼은 정확히 알아 두어야 합니다.
2.2.3 결론
구조체가 클래스와 사용방법이 동일하다면 왜 굳이 구조체를 이용하지 않고 새로운 형태의 클래스를 만들었을까요? 바로 이 차이점이 클래스만의 특징이며 객체 지향언어의 장점입니다. 앞으로 구조체보다 왜 클래스가 나은가를 알게 된다면 우리는 클래스의 개념을 어느 정도 파악하고 있는 것입니다.
뭐가 다른지 답부터 보고 다음으로 넘어가도록 하죠. 구조체와 클래스는 데이터 타입의 생성이라는 측면에서는 같은 역할을 합니다. 클래스가 구조체보다 강력한 점은 접근제어, 상속의 개념 그리고 메서드의 활용 부분이 클래스에 추가 되었다는 점입니다. 자바를 배운다는 것은 바로 클래스의 장점을 배우는 것입니다. 다음 절에서 클래스의 만드는 방법을 살펴 보도록 하겠습니다.
2.2 구조체로 데이터타입을 생성하는 방법
2.2.1 구조체란?
구조체는 표준 C에서 클래스가 없던 시절에 새로운 데이터 타입을 만드는 역할을 담당하고 있었습니다. 현재 우리가 언급한 데이터 타입은 분명히 하나만의 데이터를 담을 수 있는 데이터 타입입니다. “int a=3” 이라고 했을 때 우리는 a라는 변수에 다른 수를 담고자 한다면 3은 사라지고 맙니다. a=5라고 했을 때 a에 들어 있던 3은 없어지고 다시 5라는 수가 a의 메모리에 자리를 잡게 됩니다. 이러한 측면에서 기본 데이터 타입의 문제점은 단 하나의 데이터를 넣을 수 있다는 것입니다.
데이터 타입이 2개의 수를 담을 수 있다고 한다면 어떨까요? 표준 C(ANSI C)언어에서는 이를 구조체라는 것을 사용하여 해결하고 있습니다. 물론, 자바언어에서는 사용하지 않지만 구조체의 의미는 객체언어의 역사를 말해 주고 있기 때문에 구조체에 대해 약간의 지식을 가지고 있으면 자바언어가 어떠한 구조로 되어 있는지 쉽게 이해 할 수 있습니다. 구조체의 정확한 문법구조는 알 필요가 없습니다. 단순히 아 이렇게 만드는구나 정도를 이해 하시면 됩니다. 일단 구조체의 예를 살펴 보도록 하겠습니다.
2.2.2 구조체를 만드는 방법
클래스로 데이터 타입을 만든다고 했습니다. 구조체는 데이터타입을 생성하는 데이터 타입 생성기 역할을 하고 있기 때문에 클래스와 같은 레벨에 존재합니다. 그래서 구조체는 클래스와 동급입니다. 구조체는 완벽하게 클래스와 그 의미는 같습니다. 그리고 초기 구조 또한 클래스와 아주 흡사합니다. 구조체의 위치는 아래에서와 같이 제일 위에 존재합니다.
구조체, 데이터 타입, 변수, 상수의 구조
구조체
데이터 타입
변수
상수
무
무 존재
존재
실존
구조체로 데이터 타입을 생성하는 예를 살펴 보겠습니다.
구조체 사용의 예
struct PERSON
{
int age;
long height;
float weight;
} family_member;
struct PERSON sister; // C style structure declaration
PERSON brother; // C++ style structure declarationsister.age = 13; // assign values to members
brother.age = 7;자바언어는 C++의 문법 구조를 따르고 있으며 C++는 C언어를 기반으로 하고 있습니다. C언어의 구조체는 C++에 와서 완전하게 다른 모습인 class라는 이름으로 발전하여 원래의 구조체와 공존하게 됩니다. 현재 소개되는 C, C++문법을 잘 살펴 보면 특정한 Rule을 찾을 수 있을 겁니다. 그리고 이러한 Rule은 자바언어에서도 그대로 사용하게 됩니다.
위의 구조체에 대한 예를 하나씩 따져 보도록 하죠. 구조체라는 것은 앞에 struct이라는 키워드를 사용합니다. 그리고 이름을 지정합니다. PERSON이라는 이름으로 구조체를 만든다는 의미겠죠. 그리고 그 내부에는 3개의 변수를 포함하고 있습니다. struct키워드를 이용하여 3개의 변수를 묶어서 PERSON이라고 부르겠다는 것입니다. 하나씩 분해해서 보면 다음과 같습니다.
데이터 타입과 변수와 상수의 구조
n struct : 구조체를 만드는 키워드
n PERSON : 구조체로 만들어진 새로운 데이터 타입
n int age : PERSON 내부의 멤버
n int height : PERSON 내부의 멤버
n int weight : PERSON 내부의 멤버
이 부분에서 구조체에 대해 헷갈리지 않게 이해를 잘 해야 겠죠. 구조체는 새로운 형태의 데이터 타입을 만드는 역할을 합니다. 이러한 점에서 구조체는 클래스와 같은 역할을 합니다. 여기서 PERSON은 새로운 데이터 타입이며 struct은 표준 C언어에서 제공하는 데이터 타입 생성기입니다.
n 누가 만들었을까요?
그것은 누구나 만들 수 있으며 여기서는 제가 임의의 이름으로 하나 만들었습니다.
n PERSON이라는 이름은 무엇일까요?
새로운 데이터 타입의 이름입니다.
n struct는 무엇을 하는 것일까요?
새로운 데이터 타입을 만들 수 있게 해주는 키워드입니다.
n age, height, weight는 새로운 데이터 타입 내부에 존재하는 멤버입니다.
정리를 해보죠. 기존에는 주어진 데이터타입으로 데이터를 하나만 넣을 수 있는 변수를 선언하여 하나의 데이터만을 넣어 사용했습니다. 이제는 달라졌습니다. 구조체를 이용하여 PERSON이라는 새로운 데이터 타입을 만들었으며, 그 속에는 3개의 변수를 포함하고 있으며, 3개의 변수들을 대표하는 이름을 바로 PERSON으로 하겠다는 것입니다.
앞 장에서 언급한 데이터 타입을 상기시켜보십시오. 데이터 타입은 어디에 사용한다고 했습니까? 바로 변수를 선언할 수 있다는 것입니다. 위의 만들어진 데이터 타입 PERSON을 이용하여 아래와 같이 변수를 선언 했습니다.
PERSON brother;
이와 같이 변수를 하나 만들었을 때 메모리가 하나 생성되어집니다. 여기서, 변수이름은 누가 뭐래도 brother입니다. 하지만 아주 곤란한 문제가 발생합니다. 기존의 방식대로라면 이 속에 있는 3개의 변수에 값을 넣을 수가 없습니다. 그래서 나온 것이 점의(.) 원리입니다. 그냥 단순히 이해하자면 소속이라는 의미겠죠.
PERSON brother;
brother.age =100
brother.height=170L
brother.weight=67.5F
이와 같이 사용자가 임의로 새로운 데이터타입을 만들며 그리고 그 타입을 사용할 수 있게 하는 것이 ANSI C의 구조체입니다. 구조체의 의미는 여기까지만 알아 두도록 하죠. 더 이상 구조체는 자바에서 사용되지 않습니다. 하지만 이 구조체야 말로 자바언어에 대한 핵심의 의미를 그대로 담고 있으니 그 느낌만큼은 정확히 알아 두어야 합니다.
2.2.3 결론
구조체가 클래스와 사용방법이 동일하다면 왜 굳이 구조체를 이용하지 않고 새로운 형태의 클래스를 만들었을까요? 바로 이 차이점이 클래스만의 특징이며 객체 지향언어의 장점입니다. 앞으로 구조체보다 왜 클래스가 나은가를 알게 된다면 우리는 클래스의 개념을 어느 정도 파악하고 있는 것입니다.
뭐가 다른지 답부터 보고 다음으로 넘어가도록 하죠. 구조체와 클래스는 데이터 타입의 생성이라는 측면에서는 같은 역할을 합니다. 클래스가 구조체보다 강력한 점은 접근제어, 상속의 개념 그리고 메서드의 활용 부분이 클래스에 추가 되었다는 점입니다. 자바를 배운다는 것은 바로 클래스의 장점을 배우는 것입니다. 다음 절에서 클래스의 만드는 방법을 살펴 보도록 하겠습니다.
2.3 클래스로 데이터 타입을 만드는 방법
2.3.1 배경
위에서는 구조체로 데이터 타입을 만드는 방법에 대하여 알아 보았습니다. 이 데이터 타입을 만드는 방법을 자바의 클래스라는 측면에서 살펴보도록 하겠습니다. C언어의 구조체와 자바의 클래스가 같은 역할을 한다면 확장된 클래스가 아닌 기본적인 자바 클래스는 구조체와 그 모습이 동일하다는 것을 밝히면서 클래스의 의미를 다시 한번 상기 시켜 보도록 하겠습니다.
2.3.2 클래스 만들기
먼저, 앞에서 만들어 본 구조체와 똑같은 데이터 타입을 자바 클래스로 만들어 보도록 하겠습니다. 아래의 클래스는 앞 절에서 나온 구조체와 철자도 틀린 것이 별로 없습니다.
PERSON.java
public class PERSON {
public int age;
public long height;
public float weight;
}C:\examples\1.클래스의 개념>javac PERSON.java
C:\examples\1.클래스의 개념>dir
C:\examples\1.클래스의 개념 디렉터리
2001-06-03 09:29p <DIR> .
2001-06-03 09:29p <DIR> ..
2001-06-03 09:29p 246 PERSON.class
2001-06-03 09:29p 90 PERSON.java이 파일을 컴파일 했을 때 우리는 .class확장자를 가진 파일을 하나 얻을 수 있습니다. 약간 다르긴 하지만 public을 모두 제거하고 struct키워드를 class로 바꾼다면 모든 면에서 구조체와 똑같습니다. 3개의 변수를 담고 있는 새로운 데이터 타입이 만들어진 것입니다.
위의 class키워드로 만든 데이터 타입을 실제 자바로 실행해 볼 수 있는 main메서드를 포함한 코드를 제작해 보도록 하겠습니다. 자바에서는 main메서드를 포함하고 있는 클래스가 실행 클래스가 되며 실행은 “java 클래스의 이름”으로 하고 있습니다. main 메서드를 포함한 코드는 다음과 같습니다.
Test3.java
public class Test3 {
public static void main(String[] args) {
PERSON brother = new PERSON();
brother.age =100;
brother.height = 170L;
brother.weight = 67.0F;
System.out.println("age:" + brother.age);
System.out.println("height:" + brother.height);
System.out.println("weight:" + brother.weight);
}
}C:\examples\1.클래스의 개념>javac Test3.java
C:\examples\1.클래스의 개념>java Test3
age:100
height:170
weight:67.0구조체와 클래스의 차이점은 현재의 자바 소스에서는 전혀 찾아 볼 수 없습니다. 아직 클래스의 장점들이 나타나고 있지 않기 때문에 별다른 차이점을 느끼지 못할 것입니다. 하지만 그 차이점들이 객체 지향언어의 매력이라는 것을 미리 짐작 할 수 있을 것입니다.
이제, 클래스와 구조체를 비교해 보겠습니다. 아래의 표는 클래스와 구조체의 비교를 잘 보여 주고 있습니다.
클래스와 구조체의 비교
클래스
구조체
종류
클래스
클래스로 만든 데이터 타입
클래스 변수
상수
구조체
구조체로 만든 데이터 타입
구조체 변수
상수
무
무 존재
존재
실존
이 표에서 나타내고자 하는 것은 클래스와 구조체는 차이가 없다는 것입니다. 클래스는 구조체의 복사판일 정도로 그 개념은 비슷합니다.
2.3.3 결론
결론적으로, 클래스는 구조체를 발전시켜 만든 것이라고 봐도 될 것입니다. 여기서 클래스는 당연히 최고 상위에 존재하는 신적인 존재이며 객체 지향적인 개념에서 새로운 데이터 타입을 만든다는 것은 당연한 일이지만 고전 프로그래머에게는 약간 생소한 접근 방식일 수도 있습니다. 그렇다면 과연 차이점은 무엇일까요? 그 차이점이 자바의 큰 힘이며 매력인 것입니다. 다음 장에서는 클래스와 구조체의 차이점인 클래스만의 특징을 알아보도록 하겠습니다.
2.4 클래스만의 특징
2.4.1 클래스만의 특징 소개
구조체에는 없지만, 클래스에만 있는 특징이 바로 클래스와 구조체의 차이점입니다. 우리는 앞으로 나올 부분들에서 클래스에 적용되는 특징들을 하나 하나씩 따져 볼 것입니다. 클래스만의 특징은 아주 다양하게 열거할 수 있지만 구조체와의 차이점에서 찾아 볼 수 있는 단순하면서 중요한 개념들만 열거한다면 다음과 같습니다.
구조체와 다른 클래스만의 특징들
n 선언과 메모리 할당
n 데이터의 접근 방법(private과 public)
n 멤버로 메서드 포함
n 상속의 개념 적용
간단해 보이지만 자바언어의 모든 특징을 전부 내포하고 있습니다. 메모리의 할당의 문제는 어차피 구조체이든 클래스이든 만들어지면 변수를 선언하고 메모리를 할당해야 하는 것은 마찬가지 입니다. 사용방법만 약간 다를 뿐입니다. 데이터의 접근과 메서드의 포함, 상속개념의 적용은 기존의 절차적 프로그래밍 기법에 반기를 드는 혁명적인 일입니다. 클래스의 특징들을 알아 보도록 하겠습니다.
2.4.2 클래스의 특징들
기본적으로 구조체에서는 메서드를 포함하지 않고 일반적인 데이터타입의 변수를 포함 할 수 있습니다. 하지만 클래스는 한 단계 더 나아가 메서드까지 포함하고 있습니다. 그리고 구조체로 새로운 데이터 타입을 만들었다면 새로운 데이터 타입 내에 들어 있는 모든 요소들은 기본적으로 public입니다. 하지만 클래스에서는 private과 public의 차원에서 접근제어를 구분하고 있습니다. 점찍고 값을 할당할 수 있다면 public이고 없다면 private입니다. 마지막으로 상속의 개념은 클래스만의 막강한 힘을 주고 있습니다. 만들어져 있는 데이터 타입에 대한 재 사용이란 측면에서 상속은 꿈의 기술입니다.
C++언어의 창시자인 Bjarne Stroustrup은 여러 개의 변수를 조합하여 많은 양의 데이터를 담을 수 있는 새로운 데이터 타입을 만드는 방법을 제안하였으며, 그것을 확장하여 변수들의 관계를 정의하는 메서드를 포함시킴으로써 새로운 모델을 만들었는데 이것이 바로 클래스입니다. 그리고 각각의 데이터 타입들간의 상속구조를 포함시키면서 Object Oriented Programming 기법의 발전된 모델을 제시하고 있습니다.
2.4.3 결론
클래스 내부에 접근제어, 메서드, 상속의 개념이 들어가면 그 규칙을 유지하고 지키기 위해서 보다 복잡한 객체개념이 들어가게 됩니다. 클래스 구조를 유지하기 위한 규칙을 배우는 것이 바로 자바언어를 배우는 것으로 보아도 될 것입니다. 앞으로 여러분들은 접근제어, 메서드 그리고 상속을 활용하여 새로운 클래스를 디자인하고 그 클래스를 잘 사용하는 방법, 즉 다른 사람들이 만들어 둔 클래스를 활용하여 자신의 클래스로 확장하는 방법을 배우게 되는 것이죠. 그렇다면 차이점에 관련된 사항들을 다음 절에서 하나씩 알아 보도록 하겠습니다.
2.5 클래스 변수를 생성하는 방법의 차이
2.5.1 서론
구조체와 클래스의 차이점 중 여기서는 변수를 생성하는 방법의 차이에 대하여 알아 보도록 하겠습니다. 구조체와 클래스는 생성하는 방법에 있어서 차이점을 보이고 있습니다. 아주 기본적인 사항이지만 클래스를 알겠다고 한다면 이것을 무시할 수는 없을 것입니다.
2.5.2 클래스 변수를 생성하는 법
일단 구조체와 클래스의 생성의 방법의 차이점에 대해서 알아 보도록 하죠. 구조체와 클래스의 생성방법은 아래와 같습니다.
사용자 정의 데이터 타입의 생성하는 방법
구조체 변수의 생성
클래스 변수의 생성
PERSON brother;
PERSON brother = new PERSON()
이 두 가지에 분명 다른 방법을 사용하고 있습니다. 구조체에서는 선언만 해 주었습니다. 하지만, 클래스에서는 new연산자라는 것을 사용하고 생성자메서드를 명시해 주어야 합니다. 좀 더 깊이 들어가 보도록 하겠습니다.
자바의 클래스 개념에서는 반드시 new연산자로서 메모리를 생성하는 부분이 나와야 합니다. 만약 “PERSON brother” 처럼 단순히 선언만 해 준다면 절대 메모리는 생성되지 않습니다. 이것은 기존의 C와 C++의 차이점입니다.
참고로 C++언어에서는 new를 했다면 반드시 delete를 해서 메모리를 해제해 주어야 하지만 자바언어에서는 이것을 쓰레기 수집기를 사용하여 메모리를 자동으로 해제 해 주기 때문에 delete연산자는 사용하지 않습니다.
생성자메서드라는 것을 호출해 주어야 하는데 이 생성자메서드는 무조건적으로, 반드시 호출해 주는 것을 원칙으로 합니다. “PERSON()” 이란 놈이 바로 생성자메서드인데 이 절에서는 생성자메서드라고만 언급하고 3장의 The Class에서 아주 심도 깊게 다루게 될 것입니다. new와 생성자메서드의 기능은 new연산자를 이용하여 메모리를 생성하고 생성자메서드를 이용하여 멤버필드의 초기화 작업을 해 주는 것입니다. 자바에서 사용하는 클래스 에 대한 변수선언을 하는 부분인 “PERSON brother = new PERSON()”을 분해 해 보면 다음과 같습니다.
자바 클래스 데이터 타입의 변수 선언과 메모리 할당
n PERSON : 클래스로 생성한 데이터 타입
n brother : PERSON 데이터타입으로 선언한 변수
n new : 메모리를 생성하는 연산자
n PERSON(): 메모리 생성 후 초기화 작업을 담당하는 생성자
아주 간단하죠. 하지만 보통의 책에서는 그냥 new라고 설명하고 있으며 생성자메서드와 같이 사용한다고 되어 있지만 그 내부의 의미는 아주 조직적입니다. 이 부분은 뒤에 new연산자와 생성자메서드를 다룰 때 자세하게 언급하도록 하겠습니다.
2.5.3 결론
이 절에서는 클래스 변수를 만드는 방법에 대하여 알아 보았습니다. 클래스는 변수이름만 주는 것이 아니라 new를 이용하여 메모리까지 생성 시켜주어야 한다는 것을 배워 보았습니다. 이점에서 클래스는 기존의 언어에서보다 메모리의 핸들을 보다 더 정확하게 하겠다는 의미를 내포하고 있습니다. 그리고 메모리가 있느냐 없느냐는 자바를 공부할 때 아주 중요한 부분을 차지합니다. 여러분이 프로그램할 때 가장 많이 접하게 되는 에러가 바로 메모리가 없다는 Null Point Exception입니다. 이 에러는 객체에 메모리가 없는데 사용하였다는 에러입니다. 이것은 프로그램을 보다 명확하게 해주는 역할을 합니다.
2.6 마무리
자바의 가장 기본적인 원리인 데이터 타입의 이용, 변수의 선언, 메모리의 할당 그리고 초기화 작업을 한꺼번에 전부 보여주는 부분이 바로 “PERSON brother = new PERSON()” 이라는 한 줄의 소스입니다. 이 장에서 설명할 수 있는 클래스의 기초개념 또한 포함하고 있기때문에 여러분은 이 한 줄의 의미를 제대로 아는 것이 무엇보다 우선합니다. 클래스의 특징인 선언과 메모리의 할당이라는 관점에서 클래스의 특징을 먼저 알아보았으며 다음 장으로 넘어가면서 데이터의 접근제어(private과 public), 멤버로 메서드 포함에 대해서 자세하게 알아 보게 될 것입니다.
구조체와 구별되는 클래스의 접근제어와 클래스내의 메서드 포함은 3장의 The Class에서 상세하게 다루게 될 것이며, 4장에서는 상속과 함께 클래스의 재사용이라는 측면에서 클래스의 개념을 배우게 될 것입니다. 3, 4장에서 진정한 클래스의 느낌을 얻으시기 바랍니다.
3장 The Class
클래스의 개념을 파악하기 위한 기초적인 작업을 1장과 2장에서 다루어 보았습니다. 이 장에서는 클래스 그 자체에 초점을 맞추어 알아 보도록 하겠습니다. 일반적인 클래스의 설명은 은폐성, 상속성, 다형성의 관점에서 클래스를 설명하고 있지만 이 책에서는 다른 시각으로 클래스에 접근하고 있습니다.
클래스는 근원적으로 객체지향개념을 그대로 내포하고 있기 때문에 오히려 철학적인 면이 상당 부분 포함 되어 있습니다. 그렇기 때문에 개념적인 접근 없이 프로그램만 한다면 오히려 클래스의 디자인 차원에서 많은 어려움을 경험하게 됩니다. 이미 이것을 경험하신 분들이 이 책을 본다면 만족스러운 결과를 얻을 수 있을 것이며 처음으로 접하시는 분들은 어떠한 혼선 없이 자바를 배울 수 있으실 것입니다.
클래스는 데이터 타입 생성기입니다. 보통 여러분이 클래스를 생성한다면 그 클래스를 우리는 사용자 정의 데이터 타입이라고 합니다. 이것을 자바에서는 그저 클래스라 칭하지만 정확한 표현은 사용자 정의 데이터 타입입니다. 이 사용자 정의 데이터 타입은 C++, Visual C++, J++, C#에 이르기까지 다양 분야에 적용 가능합니다. 제대로 알아 둔다면 여타 언어를 배우는데 상당한 도움을 얻을 수 있을 것입니다.
이 장에서 소개하는 내용들은 다음과 같습니다.
n 프로그램의 기초
n 접근제어
n 메서드란?
n 메서드의 매개변수
n 메서드의 클래스 삽입
n 메서드와 접근제어
n private의 진정한 의미
n 메모리 관점에서 객체의 생성
위와 같은 관점에서 객체를 바라보는 것은 많은 설명을 필요로 하지만, 몇 줄의 소스를 배우는 것 보다도 상당한 도움이 되리라 생각됩니다. 자! 그렇다면 이제부터 클래스를 탐험 해 보도록 하죠.
3장 The Class
클래스의 개념을 파악하기 위한 기초적인 작업을 1장과 2장에서 다루어 보았습니다. 이 장에서는 클래스 그 자체에 초점을 맞추어 알아 보도록 하겠습니다. 일반적인 클래스의 설명은 은폐성, 상속성, 다형성의 관점에서 클래스를 설명하고 있지만 이 책에서는 다른 시각으로 클래스에 접근하고 있습니다.
클래스는 근원적으로 객체지향개념을 그대로 내포하고 있기 때문에 오히려 철학적인 면이 상당 부분 포함 되어 있습니다. 그렇기 때문에 개념적인 접근 없이 프로그램만 한다면 오히려 클래스의 디자인 차원에서 많은 어려움을 경험하게 됩니다. 이미 이것을 경험하신 분들이 이 책을 본다면 만족스러운 결과를 얻을 수 있을 것이며 처음으로 접하시는 분들은 어떠한 혼선 없이 자바를 배울 수 있으실 것입니다.
클래스는 데이터 타입 생성기입니다. 보통 여러분이 클래스를 생성한다면 그 클래스를 우리는 사용자 정의 데이터 타입이라고 합니다. 이것을 자바에서는 그저 클래스라 칭하지만 정확한 표현은 사용자 정의 데이터 타입입니다. 이 사용자 정의 데이터 타입은 C++, Visual C++, J++, C#에 이르기까지 다양 분야에 적용 가능합니다. 제대로 알아 둔다면 여타 언어를 배우는데 상당한 도움을 얻을 수 있을 것입니다.
이 장에서 소개하는 내용들은 다음과 같습니다.
n 프로그램의 기초
n 접근제어
n 메서드란?
n 메서드의 매개변수
n 메서드의 클래스 삽입
n 메서드와 접근제어
n private의 진정한 의미
n 메모리 관점에서 객체의 생성
위와 같은 관점에서 객체를 바라보는 것은 많은 설명을 필요로 하지만, 몇 줄의 소스를 배우는 것 보다도 상당한 도움이 되리라 생각됩니다. 자! 그렇다면 이제부터 클래스를 탐험 해 보도록 하죠.
3.1 프로그램의 기초
3.1.1 객체의 용어
용어적인 차원에서 변수, 객체, 인스턴스(instance)라는 말들을 여러 책에서 이용하고 있습니다. 이러한 용어는 그 나름대로의 뜻을 내포하고 있기 마련입니다. 하지만 이것 때문에 혼동되는 요소들이 있다면 당연히 제거 해 버려야 겠죠. 이 절에서는 변수, 객체 그리고 인스턴스라는 용어에 대해서 알아보고 그에 관련된 지식들을 습득해 보도록 하겠습니다.
3.1.2 변수, 객체, instance의 구분
일반적으로 클래스로 만든 데이터 타입이든지 기본 데이터 타입이든지 간에, 여하튼 데이터 타입으로 만든 이름은 모두 변수라고 부를 수 있습니다. 기본 데이터 타입은 변수의 이름을 선언함과 동시에 메모리가 할당되지만 클래스는 다릅니다. 2장에서 이미 언급한 것과 같이 클래스는 변수의 이름과 변수에 대한 메모리의 할당이 분리 되어 있습니다. 2장의 5절을 한 번 복습해 볼까요
클래스를 선언하고 메모리를 할당하는 부분인 “PERSON brother = new PERSON()”을 아래와 같이 분해 할 수 있다고 배웠습니다.
자바 클래스 데이터 타입의 변수 선언과 메모리 할당
n PERSON : 클래스로 생성한 데이터 타입
n brother : PERSON 데이터타입으로 선언한 변수
n new : 메모리를 생성하는 연산자
n PERSON(): 메모리 생성 후 초기화 작업을 담당하는 생성자
“PERSON brother = new PERSON()”은 한 줄이지만 이것을 두 줄로 만들어 보겠습니다.
n PERSON brother;
n brother = new PERSON();
이렇게 두 줄의 코드로 나누어 사용할 수 있습니다. 한 줄로 사용하나 두 줄을 사용하나 결과는 같습니다. 하지만 “PERSON brother;”은 변수의 이름을 선언한다는 의미를 지니고 있습니다. 그리고 “brother = new PERSON();”은 만들어져 있는 이름에 메모리를 할당한다는 의미가 부여 되어 있습니다. 이렇게 본다면 변수의 선언과 메모리의 할당은 완전히 분리 되어 있습니다. 하지만 메모리가 있거나 없거나 우리는 이것을 변수라 부릅니다. 정확하게 표현한다면 메모리가 없는 변수와 메모리가 있는 변수라고 말할 수 있습니다.
클래스의 변수의 종류
n 메모리가 있는 변수
n 메모리가 없는 변수
예를 들어 보죠. 아기를 낳기 전에 보통의 경우, 아기의 이름을 지어 둡니다. 아기가 태어나기 전에 이름을 미리 만들어 두면 아기가 태어나기 전에 미리 만들어둔 이름 입니다. 만약 아기가 태어 났다면 당연히, 만들어 둔 이름을 부여하게 될 것입니다. 이렇게 된다면 아기가 태어난 후의 이름이 되는 것입니다. 이름은 있지만 아기가 태어나고 태어나지 않고는 엄연하게 다른 것입니다. 이러한 면에서 따지고 보면 변수도 메모리가 생성되기 전의 변수와 메모리가 생성된 후의 변수로 구분 지을 수 있습니다.
기본 데이터 타입에 변수를 만들면 이를 우리는 당연히 변수라고 합니다. 클래스로 만든 새로운 데이터 타입으로 변수를 만들어도 우리는 이를 변수라고 합니다. 물론 메모리의 생성의 문제는 남아있지만 일단 변수는 변수입니다. 그리고 새로운 데이터타입으로 생성되어진 변수를 객체라고 부릅니다. 객체도 변수의 한 형태이며 클래스에서 만들어졌다하여 객체라고 부릅니다.
객체도 당연히 변수처럼 객체의 선언과 객체의 메모리 할당의 문제는 남아 있습니다. 그래서 객체의 이름만 선언한다면 객체변수의 선언이라고 말하며 new연산자를 이용하여 메모리까지 생성하였다면 객체의 생성이라고 하겠죠. 그리고 객체가 메모리를 생성하였다면 instance가 하나 만들어졌다고 말할 수 있습니다.
객체변수, 객체, instance
n 객체변수 선언: PERSON brother;
n 메모리 생성, instance 생성 : brother = new PERSON();
n 동시에 객체변수 선언과 생성 : PERSON brother = new PERSON();
* new연산자를 이용하여 메모리를 생성하였다면 우리는 instance를 생성하였다고 합니다. PERSON brother; 이렇게만 한다면 brother은 null값을 갖게 됩니다. 메모리의 할당이 되지 않았다는 것입니다. 형과 이름만 있지 아무것도 아니라는 이야기죠.
3.1.3 결론
객체는 메모리를 가지고 있느냐 없느냐가 아주 중요한 문제입니다. 메모리와 변수의 이름을 함께 생성하였다면 우리는 instance를 만들었다고 말할 수 있습니다. 거의 대부분의 번역 서적들은 이것을 거의 동일시 하고 사용하지만 약간의 의미상의 차이는 있습니다. 어감적인 차이는 메모리가 존재하느냐 하지 않느냐의 차이에서 아주 두드러지게 나타납니다. 어느 정도 변수와 객체 그리고 instance의 어감적인 차이를 이해 하셨으리라 생각됩니다.
다음으로, 자바를 배울 때 중요하면서도 약간은 무시당하는 접근제어에 대하여 알아 보도록 하겠습니다.
3.2 접근제어
3.2.1 접근제어란?
접근제어는 새로운 데이터 타입을 만들고 그 데이터 타입으로 객체를 선언한 후, 객체 내의 변수에 값을 할당 할 때 값을 직접 할당 할 수 있는가 없는가를 결정합니다. 구조체의 경우 모두 직접할당을 원칙으로 합니다. 하지만 클래스를 이용하여 만든 새로운 데이터 타입일 경우에는 이것을 private과 public으로 구분하며 private인 경우에는 값을 직접할당하지 못하도록 하고 있습니다. 당연히 public인 경우에는 직접할당을 할 수 있습니다.
보통 이러한 접근제어를 자료의 은폐화(Encapsulation)이라고 표현합니다. 이 은폐화는 아래와 같이 두 가지 측면에서 생각해 볼 수 있습니다.
n 객체가 메모리가 생성 된 후 객체 내의 변수에 대한 접근제어
n 상속 관계에서 아버지 클래스와 아들클래스 간의 접근제어
이 두 접근제어에 대한 분류는 미세한 차이를 보이고 있습니다. 첫번째 접근제어를 이 절에서 자세하게 다루게 되며 두번째 접근제어는 상속을 배우면서 접하게 될 것입니다. 여기서 보여 주는 미세한 차이란 클래스의 디자인타임이냐 실행 타임이냐를 따지고 있습니다.
클래스의 실행 타임은 객체를 생성하고 메모리를 할당한 후이며 클래스의 디자인타임은 현재 클래스 내부를 만들고 디자인하는 시점을 말합니다. 이 절에서는 실행타임의 접근제어에 대하여 알아 보겠습니다.
3.2.2 접근제어의 구현
접근제어의 관점에서 우리는 클래스를 살펴보도록 하겠습니다. 보통 접근제어를 자료의 은폐화(Encapsulation)의 대표적인 부분이라고 알고 있습니다. 접근제어란 무엇일까요? 여기서 말하는 접근제어란 점(.)찍고 접근 할 수 있느냐 없느냐의 문제입니다. 접근제어 때문에 에러가 발생하는 예제를 살펴보면서 알아 보도록 하겠습니다.
PERSON.java (private float weight 부분을 public에서 private으로 수정)
public class PERSON {
public int age;
public long height;
private float weight;
}Test3.java
public class Test3 {
public static void main(String[] args) {
PERSON brother = new PERSON();
brother.age =100;
brother.height = 170L;
brother.weight = 67.0F;
System.out.println("age:" + brother.age);
System.out.println("height:" + brother.height);
System.out.println("weight:" + brother.weight);
}
}C:\examples\1.클래스의 개념>javac PERSON.java
C:\examples\1.클래스의 개념>javac Test3.java
Test3.java:8: weight has private access in PERSON
ps.weight = 67.0F;
^
Test3.java:12: weight has private access in PERSON
System.out.println("weight:" + ps.weight);
^
2 errors이 예제에서 위와 같은 에러를 만날 수 있습니다. 위의 에러가 나타나는 것은 바로 아래의 그림처럼 private에 직접 접근하려 했기 때문입니다.
“Test3.java:8: weight has private access in PERSON” 는 분명히 컴파일타임에 에러를 발생시키고 있습니다. 이것은 접근제어를 위반 하였기때문에 컴파일이 되지 않는 것입니다. 여기서 주의 깊게 보아야 할 것은 점(.)으로 접근하는 순간입니다. 언제 접근하고 있습니까? 위에서 보는 바와 같이 객체의 메모리가 생성된 후에 접근하고 있습니다. 항상 자바를 설명할 때 이것에 대해 아주 까다로운 설명을 붙이곤 하지만 잘 되지 않는 부분입니다. 접근제어의 기본법칙은 메모리가 생성된 후에 접근을 말합니다. 하나의 데이터 타입을 만들기 위해서 우리는 class를 디자인 합니다. 그리고 만들어진 클래스로 객체변수를 선언하고 그 후에, 메모리를 생성합니다. 그 다음 순간에야 비로소 접근을 하고 있다는 것을 잊어서는 안됩니다.
다시 말하면, 접근제어란 두 가지 측면에서 생각 해 볼 수 있습니다. 상속관계에서 접근제어와 지금과 같이 new연산자를 이용하여 메모리를 생성 시킨 후의 접근제어를 생각 해 볼 수 있습니다. 아직 상속관계에서 나타나는 접근제어는 설명하고 있지는 않지만 현재의 접근 자체는 클래스로 디자인이 끝난 후, 메모리가 생성된 뒤의 접근입니다. 이 때 접근제어를 위한 접근 지정자가 private으로 되어 있다면 접근이 되지 않는다는 것입니다.
클래스의 디자인타임과 실행타임을 반드시 구분해야 합니다. 이것은 아주 중요한 부분입니다. 디자인타임은 클래스를 만들고 있는 시간이며 실행타임은 만든 클래스의 메모리를 생성하는 시간입니다. 클래스를 사용한다는 측면에서 본다면 접근제어는 만들어진 데이터 타입을 사용할 때의 문제라는 것입니다.
실행시의 접근제어, 즉 메모리가 생성된 후에 점(.) 찍고 접근을 하느냐 못하느냐의 문제는 아무리 강조해도 지나치지 않습니다. 자바를 두 달 정도 배운 이들도 클래스가 데이터 타입 생성하는 부분이라는 것과 실행타임과 디자인타임을 혼동을 하더군요. 지금 알아 둔다면 두 달을 절약하는 것이 아닐까요?
자바의 클래스를 만드는 시간과 자바 클래스를 사용하는 순간의 차이는 정확하게 구분하고 넘어가야 합니다. 하나의 데이터 타입을 만든다는 것은 자신이 사용 할 데이터의 정확한 모양을 만드는 것입니다. 이러한 모양이 만들어지면 사용할 수 있겠죠. 인간이라는 데이터 타입은 이름과 인간의 육체가 주어지는 순간, 즉 하나의 개체로써 활동할 수 있는 것입니다. 이와 마찬가지로 객체도 클래스의 모양을 다 만들고 나면 new연산자를 이용하여 메모리를 생성한 후 사용하는 것이 맞겠죠. 그리고 내부의 내용은 점(.)을 찍어서 접근하고 있지만 접근 할 수 있는 부분과 없는 부분이 있습니다. 이를 접근제어라 합니다. 이것을 구분하는 기준은 데이터 타입 앞에 private과 public을 붙이는 방법을 사용합니다.
3.2.3 결론
클래스 내부의 변수에 접근 지정자를 private으로 사용했을 때 접근되지 않고 컴파일할 때 에러가 발생 한다고만 했을 뿐 사실상의 접근제어의 설명은 하지 않았습니다. 이러한 질문을 던질 수 있습니다. 이 데이터에는 어떻게 접근 할 수 있을까요? private을 public으로 한다면 당연히 접근 할 수 있습니다. private을 사용했을 때 어떠한 장점 때문에 이것을 사용하고 있을까요? 어떻게 private 데이터에 접근을 할 수 있을까요? 이 문제는 다음 절에서 메서드의 개념을 파악한 후에 private에 접근하는 방법에 대해 알아 보겠습니다.
3.3 메서드란?
3.3.1 메서드
사전적인 의미의 함수는 다음과 같습니다.
함ː수 (函數) [-쑤] (수학) 2개의 변수 x, y 사이에 어떤 대응 관계가 있고, x의 값이 정해지면 그것에 대응하여 y의 값이 종속적으로 정해질 때의 대응 관계. 또는, y의 x에 대한 일컬음. 따름수.
사전적인 의미의 함수는 보통의 우리가 수학에서 배운 그러한 의미를 담고있습니다. 하지만 사전적인 의미이외에 컴퓨터에서 적용되는 함수를 설명한다면 약간은 다른 의미를 지니고 있습니다. “함수는 변수이다.”라는 명제로 시작해 보죠. 함수를 왜 변수라고 할까요?
자바에서는 함수를 메서드라는 용어로 대체하고 있습니다. 그래서 앞으로는 함수를 메서드라는 용어로 대체해서 사용하겠습니다. 마이크로 sun사에서 그렇게 하겠다니 어쩌겠습니까? 함수를 메서드로 부르겠다는데!
함수를 변수라고 부르는 그 이유에 대해서 한번 알아보죠.
3.3.2 메서드의 특징
일단 “메서드는 변수다”라는 것을 전제로 하겠습니다. 하나의 변수를 선언할 때 우리는 데이터 타입을 지정합니다. 그리고 우리는 메서드를 만들 때에도 데이터 타입을 지정합니다. 변수를 선언할 때나 메서드를 선언할 때 데이터 타입을 선언하지 않으면 변수를 생성하거나 메서드를 생성하는 것은 불가능하다고 보면 될 것입니다. (물론 함수에는 예외라는 것도 있지만) 변수의 선언과 메서드의 선언부를 살펴보면 아래와 같습니다.
변수와 메서드의 선언
변수의 선언
메서드의 선언과 정의
int a
int sum(int x, int y){
return x+y;
}
일단 변수를 한번 살펴 볼까요. int a 말 그대로 int라는 모양의 메모리를 생성하는데 a라는 이름을 붙여 놓은 것이죠. 이것은 데이터 타입을 설명할 때 명확하게 구분했습니다. 문제는 메서드라는 놈인데 이 메서드는 약간 모양이 특이하죠. int sum까지는 int a라는 것과 비슷합니다. 하지만 내부에 뭔가가 있습니다. 그리고 sum 옆 부분에 뭔가가 또 있습니다. 이 부분은 정말 처음 시작하는 이들에게는 난해하기 이를 데 없는 부분이죠.
하나 하나씩 따져 보도록 하죠. 이 책의 전반에 걸쳐서 따지고 묻고 그리고 설명하는 것으로 끝나는 면이 많죠. 먼저 메서드의 개념에 대해서 알아보죠. sum 이라는 메서드는 그냥 값을 만들지 못합니다. 이것이 메서드의 특징이죠. 그럼 어떤 방식으로 값을 만들까요? 그것은 내부의 작업 후 리턴(return)이라는 방법으로 값을 만들어 냅니다. 예를 들면, 어린애에게 심부름을 시켜보죠. 휴지를 가져오라고 시킵니다. 애를 부르고 명령을 내리겠죠. “휴지가져오렴” 이라고! 애는 휴지를 가지고 올 겁니다. 이것을 메서드로 만들어 보면 이렇습니다.
휴지 휴지가져오렴(){
휴지 있는 곳으로 간다;
휴지를 찾아서 잡는다.;
return 두루마리휴지;
}
“휴지”는 데이터 타입이며 “휴지가져오렴” 이라는 메서드 이름을 사용하고 내부에 일을 합니다. 일을 마치고 나면 “휴지가져오렴()”이라는 메서드의 이름은 변수의 역할을 수행하게 됩니다. 이 때 최종적으로 메서드의 이름이 가지는 값은 return에 의해서 결정되며 그리고 그 데이터의 타입은 메서드 앞에 명시하는 방법을 사용합니다. 이것을 실제 프로그램에 적용해 보죠.
int sum(int x, int y){
return x+y;
}
우선 int sum(int x, int y)라는 부분부터 한번 보죠. int sum은 메서드의 이름입니다. 아니 변수라고 하죠. 변수와 모양이 똑같으니. 그리고 괄호 안에 뭔가가 들어 있는 것이 틀리죠. 괄호 안에 들어 있는 부분을 매개변수라고 부릅니다. 그리고 내부에서는 x+y를 더하는 일을 하며 더해서 바로 리턴을 합니다.
위의 예와 다른 것은 매개변수가 있는 것이 다르죠. 방법론에 있어서 약간 다릅니다. 매개변수는 이렇게 생각하면 편할 겁니다. 휴지를 사오라고 한다면 돈을 주지 않으면 사오지 않을 것입니다. 그럼 돈을 어떻게 건네 받죠. 돈을 건네 받을 중간 역할을 하는 것이 바로 매개변수의 기본 원리입니다. 내부에 필요한 요소들을 외부로부터 받기 위한 역할을 하는 것입니다. 여기서 한가지 주의 할 것은 매개변수는 메서드 내에 존재하는 변수라는 것입니다. 그래서 이 변수들은 메서드 내에서 마음대로 사용할 수 있습니다.
변수와 비슷하다는 느낌이 들지 않습니까? 메서드가 어떠한 행위를 하든 간에 결과적인 메서드의 값은 하나의 데이터 값을 갖게 되기 때문에 메서드를 변수라고 하는 것이다. 만약, int sum(int x, int y)가 어떠한 값을 더해서 리턴 한다고 한다면, sum(3,5)를 넣어준다면, sum(3,5)은 8의 값이 될 겁니다.
그렇다면 sum(3,5)은 8입니다. 그리고 만약 a에 8을 할당한다면 a=8이 될 겁니다. a는 분명 8입니다. 그리고 sum(3,5) 또한 8입니다.
변수와 메서드의 비교
int a;
int sum(int x, int y){
return x+y;
}
a = 8;
sum(3,5)
System.out.println(a);
System.out.pritnln(sum(3,5));
위의 코드는 조각 코드이지만 출력 구문을 출력하면 둘 다 8을 출력 할 것입니다. 분명 둘 다 변수의 역할을 충분히 수행하고 있습니다. 그리고 메서드 앞에 명시되어 있는 데이터 타입은 메서드가 특정 값을 넣어주고 호출이 되었을 때 가지는 값의 데이터 타입을 명시해 주는 것입니다. 이 또한 변수와 똑같습니다. 자신이 가질 값을 명시한다는 점에서는 같은 점을 발견 할 수 있습니다.
다른점은 할당의 법칙이 다르다는 것입니다. 기본 데이터 타입의 변수는 직접 할당을 원칙으로 합니다. 그리고 메서드는 간접 할당을 원칙으로 합니다. 위의 예에서 보듯이 변수는 그저 '='을 사용해서 변수 a에 값을 할당하고 있지만 sum(x,y)에는 두개의 어떠한 값을 주어서 내부에 값을 할당하고 있습니다. sum함수는 두개의 매개변수 3, 5를 합하여 또 다른 값을 만들고 있습니다. 즉 변수 a에는 8이 할당되는 것이고 sum(3,5)라는 메서드는 내부에서 작업이 이루어진 후에 리턴을 통하여 자신이 스스로 값을 할당한다는 것입니다. 이를 정리해 보면 다음과 같은 결과를 얻을 수 있습니다.
메서드의 특징
n 일반 변수와 같이 최종적으로 특정한 데이터 값을 가진다.
n 데이터 값은 리턴 값에 의해서 결정된다.
n 리턴 값의 형은 메서드 이름 앞 부분에 명시한다.
n 필요할 때 매개 변수라는 것을 이용할 수 있다.
3.3.3 결론
변수와 메서드의 가장 큰 차이는 바로 할당에서의 차이이며 별다른 차이는 없습니다. 하지만 자신이 가질 값의 데이터 타입은 반드시 지정해야 한다는 것은 불변입니다. 우선 메서드라는 것을 만들 때 가장 고려해야 하는 것은
n 메서드의 결과 값은 어떠한 데이터 형이며 어떠한 값인지
n 메서드의 이름은 무엇으로 할 것인지
n 메서드에 어떠한 값을 주어야 작업을 하는지, 아니며 아무런 값도 주지 않아도 되는지
n 메서드에 어떠한 값을 주면 몇 개나 주어야 하는지
n 메서드에 어떠한 값을 넘겨 받을 경우 매개 변수의 이름은 무엇으로 할지
n 메서드는 리턴 값을 가지는지 가지지 않는지
등을 한번쯤 생각해 보아야 합니다. 이런 사소한 것들을 메서드를 만들기 전에 한번쯤은 반드시 생각해 보아야 하는 것들입니다. 일단 이 절에서는 메서드는 변수의 역할을 하며 일반 변수는 값을 직접 할당하며 메서드는 값을 메서드 내에 명시되어 있는 작업을 처리한 후 리턴 값에 의해서 스스로 할당한다는 것을 기억하시면 됩니다. 그리고 메서드가 어떠한 값을 주어야 작업을 한다면 매개변수라는 것이 필요하다고 했습니다. 다음절에서는 매개 변수에 대해서 알아 보도록 하겠습니다..
3.4 메서드의 매개변수
3.4.1 매개변수란?
메서드가 호출 될 때 값을 주어야만 호출 되어지는 것을 쉽게 볼 수 있습니다. 위의 예제에서 int sum(int x, int y)이라는 메서드에서 x, y값을 두개를 넣어 주면 sum이라는 메서드는 내부적인 작업을 끝마친 뒤 x와 y를 더해서 리턴을 하게 됩니다. 이 때 우리는 x, y를 매개 변수라고 합니다. x, y는 sum 메서드의 호출과 동시에 x, y를 메서드의 내부에서 생성하게 되며 외부에서 넘어오는 값을 중간에서 할당 받게 됩니다. sum(3, 5)라고 한다면 x에는 3이 직접 할당의 방법으로 할당되고 y에는 5가 할당 되어집니다. 이 매개변수의 원리에 대하여 자세하게 알아 보도록 하겠습니다.
3.4.2 매개변수의 사용
메서드와 변수의 할당은 완전히 다릅니다. 메서드와 변수는 거의 다른점이 없지만 값의 할당에서 차이점을 보이고 있는 것을 위에서 이미 살펴 보았습니다. 매개변수라는 관점에서 다시 한번 살펴보죠
int a;
a=8;
int sum(int x, int y){
return x+y;
}
sum(3,5);
위의 두 할당에서 a도 8이며 sum(3,5) 또한 8입니다. 이것은 메서드도 변수의 역할을 수행할 수 있다는 증거입니다. 하지만 여기서 아주 중요한 문제로 대두되는 것이 바로 매개변수입니다.
n 언제 매개변수를 사용할까요?
이에 대한 답은 필요하다고 생각 될 때 메서드를 만드는 이가 만들면 됩니다.
n 몇 개나 사용할 수 있을까요?
이에 대한 답은 필요하다고 생각 될 때 필요한 만큼을 메서드를 만드는 이가 만들면 됩니다.
사용자가 메서드를 임의로 만들기 때문에 필요한 만큼 매개변수를 생성해서 만들면 됩니다. 그리고 매개변수로 넣어야 하는 데이터의 타입 또한 정확하게 명시해야 합니다. 자세히 살펴보면 메서드의 매개변수에 값을 넣는 방법은 일반적인 변수의 할당법과 비슷합니다. 아래의 그림을 한번 보죠
sum(3,5)에서 x에는 3의 값이 할당되면 y에는 5의 값이 할당 됩니다. 아주 간단한 원리죠. 그리고 x, y는 sum의 내부에서 생성되어 3, 5의 값을 각각 넘겨 받게 됩니다. 매개변수는 기본적인 할당의 원리를 그대로 따르고 있습니다.
메서드에서 기억 해야 할 사항
n 메서드는 자신의 데이터 타입을 가진다.
n 메서드의 이름은 매개변수와 결합하여 변수의 역할을 수행한다.
n 리턴으로 자신의 값을 결정하며 리턴값의 데이터타입은 반드시 자신의 이름 앞에 선언한 데이터타입과 일치하여야 한다.
n 매개변수의 개수를 맞추지 않으면 메서드는 사용(호출)할 수 없다.
n 매개변수의 데이터 타입은 반드시 지켜야 한다.
n 매개변수는 메서드 내에 만들어지지만 외부로부터 값을 받을 수 있는 유일한 통로이다.
n 리턴값은 단 하나만 존재한다.
그냥 들어도 대충 알만한 사항들입니다. Return의 의미는 간단하게 메서드의 생명과 같은 역할을 합니다. 그리고 리턴값이 메서드가 가지는 유일한 데이터 값이죠. 변수가 가질 수 있는 값은 할당한 값이지만 메서드가 가지는 값은 메서드 내부의 일정한 작업 후에 만들어진 메서드의 생명과 같은 존재입니다. 하지만 매개변수도 없을 수도 있으며 리턴도 존재하지 않을 수 있습니다. 값을 리턴하는 것이 아니라 작업만을 묶어서 사용하는 경우가 있는데 그렇다면 이 때 리턴이 있을 필요가 있을까요? 없습니다. 일만 한다면 딱히 값을 리턴할 필요는 없습니다. 하지만 메서드의 데이터 타입은 반드시 필요합니다. 이때 메서드는 어떠한 데이터 타입을 가질까요? 이러한 상황에 대비하기 위해서 메서드의 특별한 타입인 void타입을 만들어 두었습니다. void는 리턴을 할 필요가 없으며 오히려 앞에 명시된 메서드의 타입이 void인데 쓸데없는 값을 리턴한다면 컴파일러는 에러를 발생하게 됩니다.
3.4.3 결론
자! 이제 왜 메서드를 사용할까요? 그 이유에 대해서 알아보죠. 프로그래머들은 아주 긴 작업을 또는 반복되는 작업을 묶어서 처리하고자 하며 다시 반복해서 그 작업을 하고자 합니다. 이때 특정한 작업을 메서드로 묶어서 처리하면 한번 이용하고 난 후에 다시 그 작업을 할 수 있으며 그리고 언제나 같은 루틴으로 되어 있기 때문에 모듈식의 구조적 작업을 할 수 있게 됩니다. 이러한 장점은 아주 많습니다. 코드를 상당 부분 줄일 수 있으며 반복되면서 다시 사용할 수 있는 일에는 아주 적격입니다. 하지만 메서드를 잘 만들어야 겠죠
매개변수는 아주 중요한 메서드의 구성 요소입니다. 그리고 리턴 또한 메서드의 생명과 같은 역할을 하며 리턴의 데이터 타입도 반드시 명시해 주도록 하고 있습니다. 하지만 무엇보다도 이 책의 전반부에서 가장 중요한 문제로 제기되는 것은 바로 메서드가 변수의 역할을 그대로 한다는 것입니다. 완벽하게 같지는 않지만 메서드도 변수와 비슷하다고는 말할 수 있을 겁니다. 그렇다면 클래스의 내부에 메서드를 넣을 수 있지 않을까요?
3.5 메서드의 클래스 내의 삽입
3.5.1 클래스 내의 메서드
메서드가 클래스의 내부로 들어가면서 C++는 아주 엄청난 일을 해 내게 됩니다. 메서드는 변수의 특징을 그대로 가지고 있기 때문에 기존의 구조체에서 메서드를 포함시켜 새로운 C++문법을 만들어 내고 있습니다. 왜 메서드가 변수라고 주장했는지 이제는 아마 아실 겁니다. 바로 클래스 내부에 메서드를 포함 시킬 수 있다라고 주장하기 위해서 입니다. 왜냐하면 메서드도 변수의 역할을 완벽하게 해 내기 때문이라고 설명했죠. 좀 억지이긴 하지만 이해하기엔 이 방법이 제일 편한 것 같습니다.
3.5.2 클래스 내에 메서드 삽입의 예
설명한 것처럼 클래스에 메서드가 들어 갈 수 있다면 한번 넣어보죠. 어떻게 사용할 수 있는지 실질적인 예를 들어서 사용해 보도록 하죠.
Test4.java(클래스에 메서드를 포함한 예)
public class Test4{
public int a;
public int b;
public int sum(int x, int y){
return x + y;
}
}Test4Main.java(Test4 데이터타입의 사용의 예)
public class Test4Main{
public static void main(String[] args){
int s;
Test4 t4 = new Test4();
t4.a = 100;
t4.b = 200;
s = t4.sum(3, 5);
System.out.println("a는:" + t4.a);
System.out.println("b는:" + t4.b);
System.out.println("결과는:" + t4.sum(3,5));
System.out.println("결과는:" + s);
}
}C:\examples\1.클래스의 개념>javac Test4.java
C:\examples\1.클래스의 개념>javac Test4Main.java
C:\examples\1.클래스의 개념>java Test4Main
a는:100
b는:200
결과는:8
결과는:8용어
보통 클래스 내에 존재하는 변수나 메서드를 현재 클래스의 멤버(member)라고 부릅니다. 앞으로는 이 책에서도 멤버로 명명하며 클래스내의 변수를 멤버 필드라고 부르며 클래스내의 메서드를 멤버메서드로 부르겠습니다.Test4.java는 Test4라는 새로운 데이터 타입을 생성하고 있습니다. 내부에 존재하는 3개의 멤버를 가지고 있는 것을 볼 수 있죠. 2개의 멤버 필드와 1개의 멤버메서드를 지니고 있습니다.
왜 멤버라고 부르는지 아십니까? 넌센스 같은 말이지만 같은 반이니 멤버죠. 일단은 Test4데이터 타입을 만들고 있습니다. 그리고 Test4Main.java클래스에 메인 메서드를 만들어 그 안에서 Test4타입을 t4라는 이름으로 new연산자를 사용하여 메모리를 생성하고 있습니다. 그리고 모두 public으로 되어 있기 때문에 점(.)찍고 접근할 수 있습니다. 3개의 멤버 모두 접근할 수 있습니다.
메서드도 변수(멤버필드)와 똑같은 방법으로 접근할 수 있을까라는 의문이 생기겠죠. 똑같다는 말 밖에는 더 이상의 답은 없습니다. 3개의 멤버에 접근하여 각각 변수와 메서드의 할당의 법칙을 사용하여 할당하고 있습니다. 여기서 멤버에 대한 접근은 이미 배웠습니다. 그리고 메서드에 대한 접근을 한번 살펴 보시기 바랍니다. 이 부분은 예사롭지 않죠. 메서드도 변수처럼 접근하고 있으며 메서드의 방식대로 할당하며 꼭 변수처럼 사용됩니다. t4.sum(3,5)는 그 자체가 변수의 역할을 할 수 있으며 s라는 변수에 그 값을 다시 할당하기도 하죠. 이제 여러분은 컴퓨터 언어세계에서 너무나도 무시당하고 있는 변수와 메서드의 의미를 깨달은 것입니다.
3.5.3 결론
메서드가 클래스에 포함 될 수 있다는 사실은 컴퓨터 언어에서는 혁명적인 사건입니다. 왜 메서드가 클래스 내에 들어가는지는 여러분도 잘 아시다시피 메서드도 하나의 변수 역할을 하기 때문이라고 했습니다. 이것이 사실과 다르다 할지라도 여러분이 이해하는데는 최고의 방법인 것 같습니다.
메서드가 무엇인지, 그리고 메서드의 매개변수와 메서드의 클래스 삽입에 대해서 배워 보았습니다. 메서드를 논하기 전에 미루어 두었던 클래스의 접근에 관한 문제도 해결해 보도록 하죠. 이것을 메서드의 설명 후에 다시 토론하기로 했죠. 클래스 내에 private 이라고 되어 있던 변수들은 접근이 불가능했기 때문에 값을 할당 하지 못했습니다. 그렇다면 어떻게 할당 할 수 있을까라는 문제에서 메서드를 사용한다고 말하려고 합니다. 외부로부터 들어오는 데이터를 매개변수를 통해서 받아서 내부에 전달해 주는 방법을 사용합니다. 밖으로 내보낼 때는 어떻게 하냐고요. 물론, 메서드의 리턴을 통해서 해결하고 있습니다. 이것을 다음 절에서 따져 보도록 하죠
3.6 메서드와 접근제어의 활용
3.6.1 메서드를 이용한 접근제어
접근제어 문제에 있어서 우리는 아직까지 private에 어떻게 접근하는지 언급하지 않았습니다. 단순히 메서드를 이용 한다고만 했을 뿐 더 이상의 설명도 없었습니다. 이 장에서는 어떻게 메서드를 이용해서 private 멤버 필드에 접근하며 어떻게 private멤버 필드의 값을 외부로 전달 하는지에 대해서 배워 보도록 하죠. 기본원리는 외부로부터 들어오는 데이터를 매개변수를 통해서 받아서 내부의 멤버 변수에 전달해 주는 방법을 사용합니다. 밖으로 내보낼 때는 메서드의 리턴을 통해서 외부로 전달하고 있습니다.
3.6.2 private의 접근
일단, 접근제어가 무엇인지 어떠한 문제를 제기했는지 한번 상기해 보시기 바랍니다. 다시 한번 더 정리하자면, 클래스 키워드로 새로운 데이터 타입을 하나 만드는 과정에서 멤버에 접근 지정자를 명시할 수 있습니다. 이 때 우리는 모두 public으로 접근 지정자를 부여한 다음, 그 새로운 데이터 타입의 이름을 부여한 후 new연산자를 이용하여 메모리를 생성하였습니다. 메모리를 생성한 후 점(.)으로 모두 접근 가능했습니다. 하지만 private으로 되어 있는 필드에 점(.)찍고 접근할 때 컴파일도 되지 않는 황당한 에러를 경험했습니다. 이때 “어떻게 접근 할까요”가 이장의 주제입니다.
메서드로 접근한다고 했으니 일단은 메서드는 public으로 되어 있어야 겠죠. 그 메서드를 통해서 외부에서 받은 데이터를 클래스 내부로 전달하는 방법을 사용합니다. 그리고 클래스 내부에 있는 멤버 필드의 값을 외부로 전달하기 위해서는 다시 다른 메서드를 만들어서 사용 해야겠죠. 이때는 리턴의 방식을 사용합니다. 예제를 한번 살펴보죠.
Test5.java(클래스의 private 멤버 필드에 접근하기 위한 방법을 제시하는 예제)
public class Test5{
private int top_secret;
public void setMyTop(int x){
top_secret = x;
}
public int getTop(){
return top_secret;
}
}
Test5Main.java(클래스의 private 멤버 필드에 접근하기 위한 방법을 제시하는 예제)
public class Test5Main {
public static void main(String[] args) {
int s;
Test5 t5 = new Test5();
t5.setMyTop(1000);
s = t5.getTop();
System.out.println(s);
System.out.println(t5.getTop());
}
}
결과
C:\examples\1.클래스의 개념>javac Test5.java
C:\examples\1.클래스의 개념>javac Test5Main.java
C:\examples\1.클래스의 개념>java Test5Main
private멤버의 값은:1000
private멤버의 값은:1000
s, t5.getTop() 두가지를 출력한 이유는 메서드가 변수의 역할을 한다는 것을 한번더 명시하기 위해서입니다.
이 예제를 한번 보죠. 새로운 데이터 타입 Test5가 만들어졌습니다. Test5라는 새로운 데이터 타입은 3개의 멤버를 가지고 있습니다. 1개의 private 멤버필드와 2개의 public 메서드를 지니고 있습니다. private 멤버필드이기 때문에 Test5Main에서 Test5데이터타입이 t5라는 이름으로 new연산자를 사용하여 메모리를 생성한 후 Test5 private 멤버필드에 데이터값을 넣기 위해서 메서드를 이용하고 있습니다. 이 때 private멤버필드에 점(.)찍고 접근한다면 컴파일도 되지 않겠죠. 당연한 에러랍니다. 그래서 public 메서드를 통해서 접근하고 있습니다.
이제 public 메서드를 살펴보죠. 메서드의 이름은 제 마음대로 주었는데 처음 배울 때는 주고 싶은 이름을 주십시오. 나중에는 좋은 이름을 주시고요. 그리고 좀 배우고 나면 어떤 규칙에 의해서 이름을 준답니다. 자! 이제 이름부터 살펴보죠. 이름은 이렇게 됩니다.
n public void setMyTop(int x)
n public int getTop()
이 두 메서드의 역할에 대해서 알아보죠. setMyTop이라는 멤버메서드는 데이터 타입이 void입니다. 이것은 메서드가 일만하고 리턴을 하지 않을 때 void형을 사용합니다. 그리고 하나의 매개변수를 외부로부터 받을 것입니다. 그것은 x에 들어가 있겠죠. 다음 것을 보죠. getTop은 매개변수가 없습니다. 매개변수를 주지 않아도 일을 하지만 이 메서드는 내부에서 뭔가를 리턴하고 있습니다.
setMyTop메서드는 매개변수 x를 이용하여 외부에서 들어오는 데이터를 넘겨받아서 클래스 내부의 멤버 필드에 넣어주고 있습니다. 그리고 getTop은 return이라는 특수한 메서드의 기법을 이용하여 외부로 던져주고 있습니다. 그리고 Test5데이터타입은 메모리가 생성된 후 public메서드 2개를 사용하여 내부의 private멤버필드에 값을 넣고 그리고 값을 받아내는 역할을 수행하고 있습니다.
3.6.3 결론
private멤버필드에 직접 접근을 할 수 없기 때문에 외부로부터 들어오는 데이터를 매개변수를 통해서 내부의 멤버 변수에 전달해 주었습니다. 외부로 내보낼 때는 메서드의 리턴을 통해서 전달하였습니다. 이렇게 한다면 private멤버 변수의 접근은 아주 우아하게 해결 되었습니다. 그런데 이 부분에서 이런 질문이 나올 수 있을 겁니다. 어떻게 public 메서드는 private멤버필드를 바로 사용할 수 있습니까라는 질문이 아닐까요. 그것은 아주 단순한 대답이 있습니다. 일단 클래스내의 멤버는 private pubic따지지 않습니다. 그리고 멤버끼리는 공유가 가능합니다. 그래서 이런 말을 한적이 있습니다. private과 public의 접근제어는 점(.)찍고 접근의 문제이다라는 말 기억 하시는지요.
디자인타임과 실행타임은 반드시 구분지어야 한다라는 말을 언급한적이 있습니다. 새로운 데이터타입을 만드는 순간은 클래스 디자인타임입니다. 그리고 만들어진 데이터타입에 변수를 지정하고 메모리를 생성하여 사용하는 것은 실행타임입니다. 아주 중요한 문제라고 이미 지적한 바가 있습니다. 이것은 접근제어를 할 수 있는 시점이 언제인지를 판단 할 수 있습니다.
3.7 private의 진정한 의미
3.7.1 private의 사용 이유
private을 사용하는 이유에 대해 3가지 질문을 던지고 질문을 해결하면서 private의 또 다른 의미에 대해서 알아보죠
n private 멤버메서드도 있을 수 있을까요?
n private 멤버 필드에 접근하는 방법이 메서드 밖에는 없을까요?
n private 멤버 필드를 왜 사용할까요?
일단 private 멤버메서드부터 해결을 하죠. public 멤버메서드도 있는데 private멤버메서드가 없겠습니까? 당연히 있습니다. 그렇다면 점(.)찍고 private메서드에 접근할 수 없으니 필요가 없지 않습니까라고 하겠죠. 맞습니다. 외부에서 사용할 때는 정말 필요 없습니다. 하지만 클래스 내부에서만 사용하기 위해서 private메서드를 사용합니다. 물론 멤버끼리는 공유가 가능하다고 했죠. 멤버끼리는 private public따지지 않습니다. 대답은 “멤버니까요!”.
두 번째 질문을 해결하죠. private멤버필드에 접근하고자 한다면 public메서드 밖에는 없을까요. 거의 그렇다고 보면 될 겁니다. private멤버필드를 어떻게 하고 싶으면 public 메서드를 통하지 않고서는 방법이 없습니다.
세 번째 질문으로 넘어가죠. 이것이 가장 큰 질문입니다. 그리고 Object Oriented Programming기법에서 처음부터 나오지만 잘 설명이 되지 않고 아주 어렵게 설명되는 부분이죠. 이것을 보통 은폐의 기술, Encapsulation, 자료 보호라고 하죠. 자료를 보호하기 위해서 public 메서드를 통해서만 접근 가능합니다. 이것은 객체지향의 느낌을 아는 사람들에게나 통하는 소리죠. 그렇다면 private 멤버필드를 왜 사용하는지에 대해 자세한 사항을 알아 보겠습니다.
3.7.2 private 멤버 필드를 사용하는 이유
private 멤버 필드를 사용하는 이유에 대하여 보다 쉬운 말로 설명해 보겠습니다. 사실, 이 부분은 제가 처음 객체지향언어를 공부할 때 잘 설명되어 있는 책을 봐도 느낌이 안 오더군요. 거의 대부분의 책에서도 설명이 모호하죠. 저도 이 부분에서는 여러분의 느낌만을 바랄 뿐입니다. 느낌을 얻어보시죠.
제일 먼저, 외부의 데이터, 사과가 있습니다. 사과를 private멤버필드인 우리의 위(胃)속으로 넣으려고 합니다. 그냥은 안됩니다. 사과를 위속으로 그냥 집어 넣다간 난리 나죠. 입을 크게 벌리고 그리고 목구멍을 최대로 하고 그리고 밀어 넣으면. 이런 일은 있으면 안됩니다. private의 공간에는 메서드를 통해서 데이터를 걸러서 집어넣어야 합니다. 메서드를 통해서 넣어 보겠습니다. 사과라는 데이터를 입안이라는 public메서드를 통해서 잘게 부수고, 침을 바르고, 갈아서 목구멍을 통해서 사뿐히 삼켜야 private 위라는 공간으로 들어 갈 겁니다. 그런데, 생각하기는 힘들지만 사과를 다시 받아 내려고 합니다. 사과를 다시 받아 낼 수 있을까요. 당연히 이것도 소화라는 public메서드를 통해서 찌꺼기만을 밑으로 내보내야겠죠.
정리를 해 보죠. public메서드의 역할은 외부에서 어떠한 데이터가 들어와도 public메서드를 통해야 하기 때문에 public메서드의 매개변수에 맞추어 데이터를 넣어야 하고 public메서드의 작업을 통해서 최종적으로 private멤버필드에 들어간다면 private은 아주 정제된 데이터가 될 것입니다. 그리고 private멤버필드의 데이터를 내 보낼 때도 private멤버필드를 그대로 내 보내는 것이 아니라 다시 가공을 해서 내 보낸다면 데이터를 넣고 빼는 사람은 내부에 무슨 일이 일어나는지 몰라도 됩니다. public 메서드에 합당한 데이터만 넣어주면 되니까요? 그리고 데이터를 받아 낼 때도 안이 어떻게 되어 있는 것은 상관 없습니다. 오직 자신이 원하는 값만을 얻으면 되니까요
이제까지 설명한 것을 예제로 한번 풀어보죠. 예제의 스토리는 이렇습니다. 외부에서 int 데이터 2개가 들어오고 이 2가지는 가공을 해서 private멤버필드로 나눠 갖게 됩니다. 그래서 private 멤버필드는 더한 값인 sum과 곱한 값인 mul이 됩니다. 반대로, 값을 외부로 내 보낼 때는 더한 값, 곱한 값을 더해서 내 보내도록 하겠습니다.
Test6.java(클래스의 private을 사용하는 이유의 예제)
public class Test6{
private int sum;
private int mul;
public void setValue(int x, int y){
sum = x + y;
mul = x * y;
}
public int getValue(){
int s = sum + mul;
return s;
}
}
Test6Main.java(Test6를 테스트하기 위한 메인메서드를 가진 클래스)
public class Test6Main{
public static void main(String[] args){
Test6 t6 = new Test6();
t6.setValue(3, 5);
int s = t6.getValue();
System.out.println("두수의 합과 곱을 다시 더한값은:" + s);
}
}
C:\examples\1.클래스의 개념>javac Test6.java
C:\examples\1.클래스의 개념>javac Test6Main.java
C:\examples\1.클래스의 개념>java Test6Main
두수의 합과 곱을 다시 더한값은:23
계산: (3+5) + (3*5) = 23 이 되는 것 아시죠
3.7.3 결론
여러분이 은폐화라고 말하는 이유를 알았으면 좋겠군요. 이런 면에서 private은 아주 획기적인 것입니다. 기존에는 데이터를 정제하는 과정을 사용자가 직접 프로그램으로 제어를 해서 사용했는데 앞으로는 하나의 클래스가 새로운 데이터타입을 만들어 두면 언제라도 그것을 사용할 수 있는 것이죠. 이러한 측면에서 자바를 바라본다면 또 다른 면이 보이리라 생각됩니다.
3.8 객체의 메모리 생성과 할당
3.8.1 객체의 메모리 생성
class라는 키워드로 우리는 새로운 데이터 타입의 클래스를 하나 생성합니다. 이 새로운 클래스, 즉 데이터 타입으로 변수, 즉 객체의 이름을 만들었다 하더라도 메모리의 생성이 필수적입니다. 메모리의 생성이란 특정 데이터 타입으로 컴퓨터 내의 메모리 속에 데이터 타입에 해당하는 만큼의 메모리를 확보하는 일입니다. 메모리를 확보하기 위해서 우리는 new연산자를 사용하고 무조건적으로 생성자메서드를 호출한다고 배웠습니다. 이것을 좀 더 자세하게 알아 보도록 하죠. 또한, 객체도 하나의 변수이기 때문에 할당의 문제가 야기 됩니다. 객체의 할당방법에 대해서 알아 보도록 하겠습니다.
데이터타입 즉, 클래스를 하나 만들었을 때 우리는 새로운 데이터의 형을 만든 것입니다. 새로운 데이터의 형, 즉 만들어진 클래스를 이용하여 변수를 선언 했을 때 우리는 객체변수를 선언하였다고 합니다. 하지만 변수의 선언은 기존의 C에서와 다른 의미를 담고 있습니다. 즉, 객체의 이름을 하나 만든 것이지 아직 완전한 객체로서의 역할을 수행할 수 없습니다. 그럼 언제 이용할 수 있는가라는 의문을 제기할 것입니다. 클래스는 변수를 선언하고 new연산자를 이용하여 생성자메서드를 호출 하였을 때 객체로서의 역할을 수행 할 수 있습니다. 여기서 우리는 new연산자가 하는 일을 생각 해 보아야 합니다. new연산자는 객체의 메모리를 생성시켜주는 역할을 합니다. 다른 말로 바꾸면 객체변수가 제대로 된 역할을 할 수 있는 순간은 바로 객체의 메모리가 생성되었을 때입니다.
3.8.2 new의 메모리 생성 후 메모리의 주소를 누가 챙기는가?
간단한 예제를 사용하여 메모리의 주소를 누가 챙기는가에 대해서 알아 보도록 하겠습니다.
Father.java(new를 테스트 하기 위한 예제)
public class Father {
public Father(){
System.out.println(" I am Father ");
}
public static void main(String[] args){
Father f;
f = new Father();
}
}
Father 클래스는 새롭게 만들어진 데이터 타입이며 Father f 라고 선언하였습니다. 즉 “Father f;”이렇게 했을 경우에는 아무런 소용이 없습니다. 단지 변수를 하나 선언한 것 밖에는 아무 일도 하지 않았습니다. 그리고 new연산자를 사용하여 메모리를 할당하여 f에게 뭔가를 넘겨 주고 있습니다. 그 예는 다음과 같습니다.
n Father f ;
n f = new Father();
즉, new연산자와 생성자메서드 Father()를 함께 사용했을 때에만 객체 f는 완전한 객체로서 의미가 있습니다. 즉 new Father()이 하는 일은 f라는 객체 변수에 메모리의 참조값을 넘기고 난 후 생성된 메모리의 초기화 작업을 생성자메서드가 담당하게 됩니다. 즉, new연산자는 메모리를 생성한 다음 메모리의 주소의 참조값은 객체변수 f가 가지며 모든 객체변수는 참조값이라는 명제가 성립합니다.
3.8.4 객체의 메모리 생성과 할당
객체생성이라는 측면에서 본다면 우리는 당연히 new연산자를 사용하여 메모리 속에 해당 객체의 데이터 타입에서 요구하는 메모리 사이즈 만큼의 메모리를 할당합니다. 그리고 생성자메서드라는 것을 호출합니다. 이렇게 하면 완전한 하나의 객체의 이름과 객체의 메모리가 생성됩니다.
객체자체를 보면 여러 개의 변수를 묶어서 하나의 이름으로 대표하는 변수를 생성한 것입니다. 그렇기때문에 객체는 변수의 묶음인 셈이죠. 이러한 변수의 묶음은 변수가 아주 많을 수도 있기 때문에 반드시 new연산자로 정확한 메모리를 할당 해 주는 순서를 밟게 되는 것입니다. 메모리에 초점을 맞추면서 예제를 보도록 하죠.
Test7.java(객체의 메모리 생성을 테스트 하기 위한 예제)
public class Test7{
private String name;
private String telephone;
private int age;
public void setName(String str){
name = str;
}
public void setTelephone(String tel){
telephone = tel;
}
public void setAge(int old){
age = old;
}
public String getName(){ return name;}
public String getTelephone(){ return telephone;}
public int getAge(){ return age;}
}
Test7.java로 Test7.class를 생성하여 만들어진 새로운 데이터 타입
Test7의 멤버들
private String name
private String telephone
private int age
public void setName(String str)
public void setTelephone(String tel)
public void setAge(int old)
public String getName()
public String getTelephone()
public int getAge()
위의 예제에서 Test7이라는 클래스는 3개의 private멤버필드와 각각의 멤버필드에 접근할 수 있는 public method들을 가지고 있습니다. 전체 9개의 멤버로 구성되어진 아주 기본적인 모델입니다. 이 데이터 타입을 테스트 하기 위한 main메서드를 포함한 예제를 만들어 보도록 하죠.
Test7Main.java(Test7을 테스트 하기 위한 예제)
public class Test7Main{
public static void main(String[] args){
Test7 hong = new Test7();
hong.setName("홍길동");
hong.setTelephone("450-5555");
hong.setAge(25);
System.out.println(hong.getName() + hong.getTelephone() + hong.getAge());
Test7 kim = new Test7();
kim.setName("김삿갓");
kim.setTelephone("888-9999");
kim.setAge(52);
System.out.println(kim.getName() + kim.getTelephone() + kim.getAge());
Test7 babo = null;//홍길동의 닉네임
babo = hong;
System.out.println(babo.getName() + babo.getTelephone() + babo.getAge());
}
}
C:\examples\3. The Class>javac Test7.java
C:\examples\3. The Class>javac Test7Main.java
C:\examples\3. The Class>dir
C 드라이브의 볼륨에는 이름이 없습니다.
볼륨 일련 번호: CC86-A7E6
C:\examples\3. The Class 디렉터리
2001-06-09 06:13p 689 Test7.class
2001-06-09 05:40p 390 Test7.java
2001-06-09 06:13p 1,011 Test7Main.class
2001-06-09 06:12p 581 Test7Main.java
4개 파일 2,671 바이트
2 디렉터리 12,682,702,848 바이트 남음
C:\examples\3. The Class>java Test7Main
홍길동450-555525
김삿갓888-999952
홍길동450-555525
Test7.java에서 우리는 새로운 데이터 타입을 만들고 Test7Main.java의 main 메서드에서 Test7의 변수를 전체 3개 생성하였습니다. hong과 kim이라는 객체는 분명 메모리를 할당 받았으며 Test7타입의 두개의 객체가 생성되었습니다. hong이라는 객체에는 “홍길동”의 정보를 넣었으며 kim이라는 객체에는 “김삿갓”의 정보를 넣었습니다. 하지만, babo라는 객체는 이름만 생성 하였을 뿐 메모리 할당은 하지 않았습니다. babo는 hong이라는 객체를 할당 받습니다. 이렇게 되면 babo의 정보나 hong의 정보는 같을 것입니다.
객체의 할당이라는 것을 “babo = hong”으로 하고 있습니다. 그런데 여기서 주목해야 하는 것은 bobo라는 객체는 hong객체의 모든 메모리를 복사하는가의 문제입니다. 기본 데이터 타입의 변수들은 변수 안의 데이터를 복사를 합니다. 하지만 객체는 메모리를 복사하지 않습니다. 그것은 객체가 참조값으로 이루어져 있기 때문입니다. 객체의 할당은 다른 의미를 담고 있습니다. 이것에 대해서 좀더 자세하게 알아 보도록 하죠.
3.8.3 new연산자와 객체의 할당
객체를 생성하는 부분을 다시 한 번 보겠습니다.
Test7 hong = new Test7();
자바 클래스 데이터 타입의 변수 선언과 메모리 할당
n Test7 : 클래스로 생성한 데이터 타입
n hong : PERSON 데이터타입으로 선언한 변수
n new : 메모리를 생성하는 연산자
n Test7(): 메모리 생성 후 초기화 작업을 담당하는 생성자
new연산자에 대해서 알아 보죠. Test7 hong = new Test7()이라고 했을 때 hong에는 어떠한 값이 들어 있는가라는 질문에서 시작하죠. 여러 개의 데이터를 묶어서 하나의 데이터 타입으로 만든 후 메모리를 생성했다면 큰 덩어리의 메모리일 것입니다. 이 큰 덩어리를 어떻게 관리하는지에 대해서 알아 보도록 하죠. 관리 방법은 다음과 같습니다.
Test7 hong = new Test7();
hong에 어떠한 값이 들어 있을까요. 그 메모리를 어떠한 형식으로 관리할까요?
n hong객체에는 new가 메모리를 생성한 뒤 메모리의 주소를 넘겨 줍니다.
n hong객체에는 정확하게 주소에 대한 참조값이 들어 있습니다.
n new연산자가 생성한 참조값을 hong이라는 객체가 가지는 것입니다.
n new연산자는 당연히 주소의 참조값을 던져줍니다.
n 그래서 객체변수를 참조값이라고 말합니다. handle이라고 표현하기도 합니다.
객체변수를 참조값이라고 한다면 그 의미는 주소와 관련이 있습니다. 그 구조를 그림으로 나타낸다면 정확하게 그 느낌을 얻을 수 있을 것입니다.
hong객체가 만들어지고 hong객체에 주소 값 1000번지의 참조값이 들어 있다면 babo는 hong에서 값을 받아도 1000번지의 참조값입니다. 1000번지에 대한 참조값만 가지고 있다면 hong객체를 handle할 수 있습니다. 이렇게 된다면 메모리는 하나지만 이름은 둘이 되는 현상이 나타납니다. 즉, hong은 babo라는 별명을 하나 더 가지고 있는 것이죠. hong이나 babo나 같은 놈입니다. 이쯤에서 결론을 내려 보도록 하겠습니다.
n 객체의 이름은 레퍼런스이기 때문에 레퍼런스를 할당한다 해도 메모리는 복사가 되지 않는다.
n 객체는 레퍼런스만 가지고 있다면 그 객체를 핸들 할 수 있다.
n 객체 복사를 위해서 clone이라는 기법을 사용합니다.(객체복사의 기법)
clone은 새로 소개되어지는 기법입니다. 객체의 이름은 참조값이기 때문에 복사할 수 없습니다. 기본적인 방법으로 복사할 수 없기 때문에 자바에서는 clone이라는 방법을 제공해 주고 있습니다.
3.8.7 결론
객체 복사를 위한 clone의 기법은 앞으로 나올 장들에서 배우게 될 것입니다. 이 절에서 나오는 객체의 할당에서 가장 중요한 부분은 바로 객체는 참조 값이라는 것입니다. 주소의 참조 값, 즉 레퍼런스라는 것은 할당을 하더라도 단순한 레퍼런스만 복사할 뿐 객체 자체의 메모리는 복사할 수 없습니다. 객체는 복사할 수 없기 때문에 자바에서 이 객체에 대한 메모리 차원의 복사를 위해서 Clonable이라는 인터페이스를 제공해 주고 있습니다. 객체 복사는 차후에 상세한 설명을 덧붙이도록 하겠습니다. 여기서는 객체는 일반적인 복사로 복사가 되는 것이 아니라는 것과 객체를 다른 객체에 할당 하려고 한다면 당연히 데이터 타입이 같아야 하며 할당하더라도 참조 값만이 전달 되어진다는 것을 기억하시기 바랍니다.
Test7.java에서 우리는 새로운 데이터 타입을 만들고 Test7Main.java의 main 메서드에서 Test7의 변수를 전체 3개 생성하였습니다. hong과 kim이라는 객체는 분명 메모리를 할당 받았으며 Test7타입의 두개의 객체가 생성되었습니다. hong이라는 객체에는 “홍길동”의 정보를 넣었으며 kim이라는 객체에는 “김삿갓”의 정보를 넣었습니다. 하지만, babo라는 객체는 이름만 생성 하였을 뿐 메모리 할당은 하지 않았습니다. babo는 hong이라는 객체를 할당 받습니다. 이렇게 되면 babo의 정보나 hong의 정보는 같을 것입니다.
객체의 할당이라는 것을 “babo = hong”으로 하고 있습니다. 그런데 여기서 주목해야 하는 것은 bobo라는 객체는 hong객체의 모든 메모리를 복사하는가의 문제입니다. 기본 데이터 타입의 변수들은 변수 안의 데이터를 복사를 합니다. 하지만 객체는 메모리를 복사하지 않습니다. 그것은 객체가 참조값으로 이루어져 있기 때문입니다. 객체의 할당은 다른 의미를 담고 있습니다. 이것에 대해서 좀더 자세하게 알아 보도록 하죠.
3.8.3 new연산자와 객체의 할당
객체를 생성하는 부분을 다시 한 번 보겠습니다.
Test7 hong = new Test7();
자바 클래스 데이터 타입의 변수 선언과 메모리 할당
n Test7 : 클래스로 생성한 데이터 타입
n hong : PERSON 데이터타입으로 선언한 변수
n new : 메모리를 생성하는 연산자
n Test7(): 메모리 생성 후 초기화 작업을 담당하는 생성자
new연산자에 대해서 알아 보죠. Test7 hong = new Test7()이라고 했을 때 hong에는 어떠한 값이 들어 있는가라는 질문에서 시작하죠. 여러 개의 데이터를 묶어서 하나의 데이터 타입으로 만든 후 메모리를 생성했다면 큰 덩어리의 메모리일 것입니다. 이 큰 덩어리를 어떻게 관리하는지에 대해서 알아 보도록 하죠. 관리 방법은 다음과 같습니다.
Test7 hong = new Test7();
hong에 어떠한 값이 들어 있을까요. 그 메모리를 어떠한 형식으로 관리할까요?
n hong객체에는 new가 메모리를 생성한 뒤 메모리의 주소를 넘겨 줍니다.
n hong객체에는 정확하게 주소에 대한 참조값이 들어 있습니다.
n new연산자가 생성한 참조값을 hong이라는 객체가 가지는 것입니다.
n new연산자는 당연히 주소의 참조값을 던져줍니다.
n 그래서 객체변수를 참조값이라고 말합니다. handle이라고 표현하기도 합니다.
객체변수를 참조값이라고 한다면 그 의미는 주소와 관련이 있습니다. 그 구조를 그림으로 나타낸다면 정확하게 그 느낌을 얻을 수 있을 것입니다.
hong객체가 만들어지고 hong객체에 주소 값 1000번지의 참조값이 들어 있다면 babo는 hong에서 값을 받아도 1000번지의 참조값입니다. 1000번지에 대한 참조값만 가지고 있다면 hong객체를 handle할 수 있습니다. 이렇게 된다면 메모리는 하나지만 이름은 둘이 되는 현상이 나타납니다. 즉, hong은 babo라는 별명을 하나 더 가지고 있는 것이죠. hong이나 babo나 같은 놈입니다. 이쯤에서 결론을 내려 보도록 하겠습니다.
n 객체의 이름은 레퍼런스이기 때문에 레퍼런스를 할당한다 해도 메모리는 복사가 되지 않는다.
n 객체는 레퍼런스만 가지고 있다면 그 객체를 핸들 할 수 있다.
n 객체 복사를 위해서 clone이라는 기법을 사용합니다.(객체복사의 기법)
clone은 새로 소개되어지는 기법입니다. 객체의 이름은 참조값이기 때문에 복사할 수 없습니다. 기본적인 방법으로 복사할 수 없기 때문에 자바에서는 clone이라는 방법을 제공해 주고 있습니다.
3.8.7 결론
객체 복사를 위한 clone의 기법은 앞으로 나올 장들에서 배우게 될 것입니다. 이 절에서 나오는 객체의 할당에서 가장 중요한 부분은 바로 객체는 참조 값이라는 것입니다. 주소의 참조 값, 즉 레퍼런스라는 것은 할당을 하더라도 단순한 레퍼런스만 복사할 뿐 객체 자체의 메모리는 복사할 수 없습니다. 객체는 복사할 수 없기 때문에 자바에서 이 객체에 대한 메모리 차원의 복사를 위해서 Clonable이라는 인터페이스를 제공해 주고 있습니다. 객체 복사는 차후에 상세한 설명을 덧붙이도록 하겠습니다. 여기서는 객체는 일반적인 복사로 복사가 되는 것이 아니라는 것과 객체를 다른 객체에 할당 하려고 한다면 당연히 데이터 타입이 같아야 하며 할당하더라도 참조 값만이 전달 되어진다는 것을 기억하시기 바랍니다.
3.9 마무리
3장의 The Class에서는 구조체에서는 없는 클래스에서만 존재하는 특징들을 중심으로 클래스 자체에 대하여 알아 보았습니다. 물론 이것이 클래스의 전부는 아니지만 기존의 언어와 구별되기 때문에 그리고 아주 기초적인 클래스의 개념들이기 때문에 자바를 새로 시작하는 이들에게는 아주 쉽게 접근할 수 있으리라 생각 됩니다.
이장이 마무리 된다면 여러분들은 자바의 Hello World를 출력할 자격이 주어졌다고 생각합니다. 대부분 무조건 Hello World부터 출력하지만 기초적인 배경 없이 자바를 접하게 된다면 오히려 혼동만 더하게 됩니다. 다음 장부터 클래스의 객체 지향 개념을 배워 보도록 하겠습니다.
4장 Class for Basic Java
이 장에서는 지금까지 배운 기본적인 클래스 개념을 이용하여 클래스를 만들기 위한 기법을 배워 보도록 하겠습니다. 3장의 클래스개념을 제대로 이해 했다면 쉽게 접근할 수 있는 개념들입니다. 앞에서 배운 개념들과 이장에서 배우는 객체 지향 개념들을 합치면 여러분들은 어느 정도 자바 프로그램을 할 수 있는 기반을 가졌다고 말할 수 있을 것입니다. 이장에서 나타나는 주제들은 다음과 같습니다.
n Java Hello World
n 생성자메서드
n Overloading Method
n Inheritance
n Inheritance와 Overriding
n this
n super
프로그램을 배우면서 제일 먼저 접하게 된다는 Hello World프로그램을 시작하여 생성자메서드, Overloading개념, 상속, 상속에서 나타나는 Overriding의 개념을 살펴보고 이러한 개념들이 왜 사용되는지를 따져 보도록 하겠습니다. 메모리 차원에서 this와 super의 개념을 익히면서 우리는 더욱 자바에 익숙해 지게 될 것입니다.
자바를 배우고자 한다면 우선적으로 프로그램을 잘 짜는 것보다는 그 개념을 중시하는 것이 우선됩니다. 프로그램을 작성하는 것은 아주 쉽습니다. 하지만 조직적이며 객체지향개념에 적합한 프로그램을 작성하기는 정말 어렵습니다. 여러분이 객체지향개념을 정확히 알고 프로그램을 작성하면서 그 개념들의 원칙을 고수한다면 프로그램은 확장성100%, 재사용성 100%, 중복성 0%에 가까워질 것입니다.
자바는 처음에 개념을 적용하기가 어렵습니다. 알고는 있지만 객체지향개념들의 정확한 느낌이 없기 때문에 응용과 활용이 되지 않는 것이라고 생각됩니다. 아무리 고차원적인 프로그램을 한다 하더라도 클래스의 느낌 없이 프로그램을 한다면 당연히 배우는 속도는 떨어지게 마련입니다. 이제부터 그 느낌을 가져 보시기 바랍니다.
4.1 Java Hello World
4.1.1 HelloWorld.java
자바의 Hello World를 출력할 시간입니다. 여러분들이 Hello World를 출력하는 순간 느낄 수 있는 것은 아 되는구나라는 느낌이 있을 것입니다. 하지만 모든 것이 모호합니다. 차라리 그 모든 것을 자세히 알아 보도록 하겠습니다.
HelloWorld.java 소스부터 한번 보도록 하죠.
HelloWorld.java(자바의 기본 프로그램 테스트를 위한 예제)
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello World!");
}
}
Hello World프로그램을 작성하면서 나타나는 자바의 개념들은 아래와 같습니다.
n main 메서드
n String배열 String[]
n main 메서드의 매개변수 args
n static키워드
n System.out.println
n “Hello World!” String
이 책은 전체적으로 간단하게 설명되는 것들을 길게 설명하고 있습니다. 이것은 단점 아닌 장점이 되길 바라면서! 알고 계시다면 이 절을 건너 띄어도 좋습니다.
4.1.2 main메서드
모든 프로그램에서 main메서드는 있습니다. 자바의 main메서드는 아래와 같은 형식으로 되어 있습니다.
자바의 main메서드
public static void main(String[] args)
{
}
MainTest.java(자바의 main메서드)
public class MainTest{
public void sayHello(){
System.out.println("Hello World!");
}
public static void main(String[] args){
MainTest m = new MainTest();
m.sayHello();
}
}
C:\examples\4. Class for Basic Java>javac MainTest.java
C:\examples\4. Class for Basic Java>java MainTest
Hello World!
4.1.3 String[] args
MainParam.java(자바의 main메서드의 매개변수를 테스트 하기 위한 예제)
public class MainParam {
public static void main(String[] args){
System.out.println(args[0]);
System.out.println(args[1]);
}
}
C:\examples\4. Class for Basic Java>javac MainParam.java
C:\examples\4. Class for Basic Java>java MainParam 안녕! 자바
안녕!
자바
4.1.4 static
예를 든다면 여러분의 학교에 컴퓨터실에 간다면 모든 컴퓨터는 하나의 패키지처럼 모든 요소를 하나씩 전부 가지고 있습니다. 그리고 컴퓨터 내에 프린트까지 달려 있습니다. 하지만 프린트는 하나죠. 모든 컴퓨터에서 프린트는 사용할 수 있지만 프린트 하나는 다른 곳에서 공유를 하는 형식을 사용합니다. 이 때 프린트는 static으로 잡혀 있는 것입니다. 객체가 아무리 많이 생성되더라도 스태틱 멤버필드로 선언되어 있으면 프로그램 내에서 유일 무일한 메모리를 차지하게 됩니다. 모든 객체에서 공통으로 사용하는 전역변수의 개념으로 사용할 때 이 static을 사용합니다. static에 관한 간단한 예를 하나 살펴 보도록 하겠습니다.
StaticTest.java(스태틱 멤버필드를 테스트하기 위한 예제)
public class StaticTest{
private static int sint=0;
private int nint = 0;
public StaticTest(){
sint = sint +1;
nint = nint +1;
}
public void sayMember(){
System.out.println("sint:" + sint + " nint:" + nint);
}
public static void main(String[] args){
for(int i=0; i<10; i++){
StaticTest s = new StaticTest();
s.sayMember();
}
}
}
C:\examples\4. Class for Basic Java>javac StaticTest.java
C:\examples\4. Class for Basic Java>java StaticTest
sint:1 nint:1
sint:2 nint:1
sint:3 nint:1
sint:4 nint:1
sint:5 nint:1
sint:6 nint:1
sint:7 nint:1
sint:8 nint:1
sint:9 nint:1
sint:10 nint:1
StaticTime.java(스태틱 멤버필드의 초기화를 테스트하기 위한 예제)
public class StaticTime{
private static int sint=0;
static{
sint = 100;
System.out.println("sint:" + sint);
}
private int nint = 0;
public static void main(String[] args){
StaticTime s = null;
}
}
C:\examples\4. Class for Basic Java>javac StaticTime.java
C:\examples\4. Class for Basic Java>java StaticTime
sint:100
StaticAccess.java(스태틱 멤버필드에 대한 접근을 테스트하기 위한 예제)
public class StaticAccess{
public static int sint=0;
public int nint =0;
public static void main(String[] args){
StaticAccess.sint = 3333;
System.out.println("스태틱직접접근:" + StaticAccess.sint);
}
}
C:\examples\4. Class for Basic Java>javac StaticAccess.java
C:\examples\4. Class for Basic Java>java StaticAccess
스태틱직접접근:3333
StaticAccessMethod.java(스태틱 멤버메서드에 대한 접근을 테스트하기 위한 예제)
public class StaticMethodAccess{
private static int sint=100;
public int nint =0;
public static void setStaticInt(int x){
sint = x;
}
public static int getStaticInt(){
return sint;
}
public static void main(String[] args){
StaticMethodAccess.setStaticInt(33333);
int s = StaticMethodAccess.getStaticInt();
System.out.println("스태틱값은:" + s);
}
}
C:\examples\4. Class for Basic Java>javac StaticMethodAccess.java
C:\examples\4. Class for Basic Java>java StaticMethodAccess
스태틱값은:33333
4.1.5 System.out.println
4.1.6 결론
여러분은 이제 비로소 Hello World라는 자바 콘솔 출력 프로그램을 시작하신 겁니다. static때문에 약간 까다롭기는 하지만 대충이라도 Hello World의 심오한 뜻을 파악한다면 다음 개념으로 접근하기 쉬워질 것입니다.
4.2 생성자메서드
4.2.1 생성자메서드
생성자메서드란 클래스를 이용하여 메모리를 가진 instance 즉, 객체를 생성할 때 제일 먼저 호출되는 메서드입니다.
생성자메서드의 정의
n 객체를 생성과 동시에 기본적으로 호출 되어지는 메서드
여러분은 이미 생성자메서드를 사용하고 있었습니다. 생성자메서드를 여러분이 따로 만들지 않는다면 여러분이 만든 클래스에서 자동으로 디폴트생성자가 만들어지게 됩니다. 앞 절의 예제에서 우리는 다음과 같은 구문을 사용했습니다.
MainTest.java(디폴트 생성자메서드를 위한 예제)
public class MainTest{
public void sayHello(){
System.out.println("Hello World!");
}
public static void main(String[] args){
MainTest m = new Ma inTest();
m.sayHello();
}
}
MainTest()라는 메서드는 없습니다. 만들지도 않았죠. 이 때 여러분이 생성자메서드라하여 클래스의 이름과 같은 메서드를 만들지 않는다면 자바에서 기본적으로 디폴트 생성자라는 것이 만들어지는데 이 디폴트 생성자가 바로 MainTest()입니다. 메모리가 생성된 직후 가장 먼저 호출되는 메서드입니다. 물론 현재의 디폴트 생성자는 하는 일이 없습니다. 단지 생성자라고 표시만 할 뿐입니다.
즉, 클래스를 만들 때 사용자가 따로 생성자를 만들지 않는다면 매개변수가 없는 디폴트 생성자메서드 “MainTest()”가 만들어져서 사용되며, 디폴트 생성자메서드를 여러분이 약간 변형하여 만들거나 아니면 매개변수가 있는 생성자메서드를 다시 만든다면 기존의 디폴트 생성자메서드는 없어지고 새로운 생성자가 만들어지는 것입니다.
4.2.2 생성자메서드의 특징
생성자메서드는 유일하게 리턴타입이 없는 메서드입니다. 그리고 생성자메서드의 이름은 클래스의 이름과 동일하며 일반적으로 초기화나 멤버의 기본값 할당을 위해 사용됩니다. 보통의 경우 클래스 작업을 할 때 제일 먼저 프로그램해야 하는 것들을 생성자메서드 내에 프로그램하게 됩니다.
생성자메서드의 특징
n 메서드의 리턴타입이 없다.
n 메서드의 이름은 클래스의 이름과 동일하다.
n new생성자가 메모리를 생성한 직후 호출 되어진다.
생성자메서드는 new연산자와 함께 사용되는데 new연산자가 메모리를 생성하고 나면 멤버 변수들이 메모리를 할당 받게 됩니다. 멤버 변수들이 메모리가 존재하기 때문에 멤버필드에 값을 할당하거나 초기화를 할 수 있습니다. 이때 생성자 메서드가 활동을 하는 것입니다. 보통은 멤버필드의 초기화를 주로하지만 필요하다면 객체의 생성과 동시에 해 주어야 하는 작업들을 이 생성자메서드에서 하게 됩니다. 생성자가 없는 클래스에 대하여 컴파일러는 인수 리스트를 가지지 않는 생성자메서드를 자동으로 제공합니다. 이 생성자를 우리는 디폴트 생성자라고 부릅니다.
생성자의 역할은 초기화 작업이라고 했습니다. 생성자에 관련된 예제를 하나 만들어 보도록 하죠.
ConstructTest.java(생성자메서드를 위한 예제)
public class ConstructTest {
private String name = null;
private String address = null;
private int age = 0;
public ConstructTest(){
name = "이름없음";
address="주소없음";
age=0;
}
public void setList(String sname, String saddress, int sage){
name = sname;
address = saddress;
age = sage;
}
public String getList(){
String str = name + " " + address + " " + age;
return str;
}
public static void main(String[] args) {
ConstructTest ct = new ConstructTest();
String s;
s = ct.getList();
System.out.println(s);
ct.setList("홍길동", "서울시 중구", 25);
s = ct.getList();
System.out.println(s);
}
}
C:\examples\4. Class for Basic Java>java ConstructTest
이름없음 주소없음 0
홍길동 서울시 중구 25
생성자메서드에서 값을 name, address, age 멤버필드에 할당하고 있습니다. 그렇기 때문에 객체를 생성한 직후 바로 getList멤버메서드를 호출 했을 때 생성자메서드에서 초기화 한 값을 얻을 수 있습니다. 다시 setList메서드를 통해서 값을 할당한 이후에는 바뀌겠죠. 이렇게 생성자에서 우선적으로 값을 할당해야 할 경우가 있다면 생성자메서드를 이용하면 아주 편하게 해결할 수 있습니다.
생성자메서드도 매개변수를 이용하여 외부로부터 직접 생성자메서드에 값을 할당하는 방법으로 멤버필드를 초기화 할 수 도 있습니다. 그 예를 살펴 보기로 하죠.
ConstructDirect.java(생성자메서드를 위한 예제)
public class ConstructDirect{
private String name = null;
private String address = null;
private int age = 0;
public ConstructDirect(String sname, String saddress, int sage){
name = sname;
address = saddress;
age = sage;
}
public void setList(String sname, String saddress, int sage){
name = sname;
address = saddress;
age = sage;
}
public String getList(){
String str = name + " " + address + " " + age;
return str;
}
public static void main(String[] args) {
ConstructDirect ct = new ConstructDirect("홍길동", "서울시 중구", 25);
String s;
s = ct.getList();
System.out.println(s);
}
}
C:\examples\4. Class for Basic Java>javac ConstructDirect.java
C:\examples\4. Class for Basic Java>java ConstructDirect
홍길동 서울시 중구 25
우리는 이 예제에서 생성자에서 매개변수를 사용하는 방법을 쉽게 볼 수 있습니다. 만약 여러분이 이 예제처럼 매개변수가 있는 생성자메서드를 만들었다면 디폴트 생성자는 더 이상 사용할 수 없습니다. 그리고 반드시 생성자메서드를 사용될 때 매개변수의 개수와 형을 맞추어 주어야 합니다. 메서드는 매개변수의 개수와 매개변수의 형에 목숨을 걸고 있습니다. 그리고 생성자메서드에는 리턴타입이 없기 때문에 당연히 리턴이라는 것이 없습니다. 다른 일반 메서드 또한 리턴타입과 리턴값은 반드시 지켜야 합니다.
4.2.3 결론
생성자메서드는 말 그대로 생성될 때 호출 되는 메서드입니다. 그 유용함은 객체 생성순간에 같이 호출 될 수 있다는 장점이 있습니다. 이 생성자를 아주 유용하게 사용하면 편리한점 뿐만 아니라 반드시 필요한 메서드입니다. 물론 그것은 아주 미세한 것이지만 이렇게 생각 해 볼 수 있습니다. 만약 인간이라는 데이터 타입으로 아기를 만든다고 생각 해 보십시오. 아기는 어느 정도 초기화는 되어 있어야 합니다. 눈, 코, 입, 팔, 다리등 필수적인 요소들은 전부 초기화 되어 있어야 합니다. 이러한 면에서 본다면 생성순간에 해 주어야 하는 일은 아주 중요한 일입니다. 만약 이것을 잘못하게 되면 아마 여러분들이 만든 객체는 이상한 괴짜 객체가 되고 말 것입니다.
4.3 Overloading Method
4.3.1 Overloading
Overloading은 같은 이름을 가진 여러 개의 메서드를 말합니다. 보통 다중정의, 중복 메서드라고 부르며 일반적으로는 Overloading이라고 합니다. 이것은 하나의 이름으로 여러 개의 메서드를 가지고 있다는 뜻을 내포하고 있는데 Overloading은 클래스의 사용자를 편하게 하기 위해서 사용하는 객체지향의 기법입니다.
Overloading의 정의
n 하나의 이름으로 여러 개의 메서드를 가지고 있다.
4.3.2 Overloading의 예
중복메서드의 경우 필요 없다면 프로그램을 할 때 사용하지 않아도 됩니다. 하지만 아주 유용한 면도 있습니다. 일단 기초적인 중복메서드의 개념을 파악해보고 중복메서드를 사용했을 때의 장점에 대해서 알아 보도록 하겠습니다.
중복 메서드를 이용하여 두 수를 합하는 예를 들어보죠.
OverloadCalc(중복 메서드를 테스트하기 위한 예제)
public class OverloadCalc{
public int plus(int a, int b){
return(a+b);
}
public float plus(float a, float b){
return(a+b);
}
public double plus(double a, double b){
return(a+b);
}
public static void main(String[] args){
OverloadCalc oc=new OverloadCalc();
int i=oc.plus(3,5);
float j=oc.plus(0.1f,0.2f);
double k=oc.plus(0.5,0.7);
System.out.println("int합:"+i);
System.out.println("float합:"+j);
System.out.println("double합:"+k);
}
}
C:\examples\4. Class for Basic Java>javac OverloadCalc.java
C:\examples\4. Class for Basic Java>java OverloadCalc
int합:8
float합:0.3
double합:1.2
위의 예제 우리는 같은 이름의 메서드를 3개 가지고 있습니다. 중복 정의한 메서드는 다음과 같습니다.
중복 정의한 메서드
n public int plus(int a, int b)
n public float plus(float a, float b)
n public double plus(double a, double b)
두수를 더할 때 우리는 쉽게 int형만을 생각하게 됩니다. 즉, int형 수가 더해져서 합을 리턴 하리라 생각합니다. 이것은 지극히 당연한 일입니다. 하지만 어디 합하는 것이 int형만 있겠습니까? 다른 형들의 합도 존재합니다. 이것은 컴퓨터언어에서 데이터 타입을 아주 정밀하게 구분하고 있고 메서드의 매개변수의 형은 반드시 지켜져야 된다는 원칙 때문에 하나의 메서드로는 다양한 계산을 해 낼 수 없습니다. 일반적으로 여러분이 메서드를 만들 때 메서드의 매개변수형, 매개변수의 개수 그리고 리턴형이 다르다면 메서드를 각자 따로 만들어 주어야 합니다. 물론 이름도 다르겠죠. 하지만 이러한 약점을 보완하기 위해서 객체지향에서는 하나의 메서드의 이름으로 다양한 매개변수를 받아 들일 수 있는 메서드를 지원합니다. 이것을 우리는 Overloading이라고 합니다.
그렇다면 아주 많은 메서드가 존재할 수 있습니다. 위의 예제에서 우리는 3개의 메서드를 찾아 볼 수 있습니다. 이 클래스를 이용하여 객체를 만든 사람은 그냥 plus하고 사용하게 된다면 3가지 종류의 메서드를 전부 사용할 수 있습니다. 반드시 매개변수의 형과 개수를 맞추어야 하는 것은 철칙입니다.
하지만 이것이 호출 되었을 때 컴파일러는 내부적으로는 3개중에 어느 메서드가 호출되었는지 구분해 주어야 합니다. 이름은 같고 매개변수의 형, 매개변수의 개수 그리고 리턴값이 틀릴 수 있기 때문에 구분할 수 있는 방법은 이 세가지 중의 있겠죠. 리턴타입은 각각의 형에 따라 틀려질 수 도 있으며 같아 질 수 도 있기 때문에 리턴타입으로는 구분하지 않습니다. 그렇다면 남아 있는 것은 매개변수의 형과 매개변수의 개수입니다. 정확하게 내부적으로 사용자가 plus라는 중복메서드를 호출 했을 때 매개변수의 형과 매개변수의 개수로 구분합니다.
중복메서드를 구분할 때의 규칙
n 매개변수의 개수가 달라야 한다.
n 매개변수의 타입이 달라야 한다.
n 위의 개수와 타입 중 하나만 달라도 중복메서드의 조건이 성립 됩니다.
n 메서드의 리턴타입은 중복메서드 구분할 때 사용하지 않습니다.
중복 메서드를 자바 내부적으로 위의 방식대로 구분한다면 여러분이 중복 메서드를 만들고자 한다면 위의 규칙을 반드시 지켜야 합니다.
4.3.3 중복메서드의 역할
중복 메서드는 누구를 위한 것일까요. 클래스를 만드는 사람 입장일까요 아니면 클래스를 사용하는 입장일까요?
쉽게 생각 되는 것은 만들어진 클래스를 사용하는 사람 입장에서 아주 편리합니다. 이것을 만드는 사람은 아주 고생 꽤나 하겠죠. 그리고 어떤 일반적인 규칙을 지켜가면서 만들어야 하며 여러 가지 경우에 대해 대처해야 합니다. 이 역할의 예는 System.out.println에서 찾아 볼 수 있습니다. println이라는 메서드는 기본적으로 문자열을 출력합니다. 하지만 int, float, char형을 입력해도 콘솔창에 값을 출력합니다.
println의 중복 메서드는 아래 와 같이 상당히 종류가 많습니다. 그래서 웬만한 것은 전부 출력할 수 있는 메서드를 갖추고 있습니다. 하지만 이것을 만드는 사람입장에서는 거의 대부분의 경우를 전부 다 생각했을 것입니다.
n println()
n println(boolean)
n println(char)
n println(char[])
n println(double)
n println(float)
n println(int)
n println(long)
n println(java.lang.Object)
n println(java.lang.String)
언어의 기본적인 특성상 매개변수의 데이터형이나 개수가 다르면 메서드 또한 다시 만들어야 하지만 Overloading의 개념을 지원해서 이를 보다 편리하게 처리하고 있습니다. 그리고 사용자 측면의 인터페이스는 훨씬 간편해 지는 것입니다.
4.3.4 결론
C++와 같은 객체지향 언어에서 한가지의 이름으로 여러가지 기능을 제공해 주는 것이 중복메서드입니다. 중복 메서드는 생성자메서드에서도 지원되며 자바 라이브러리에서 아주 많은 부분이 Overloading개념을 포함하고 있기 때문에 Overloading개념을 알고 있어야 만 자바 라이브러리를 잘 이용할 수 있습니다. 기존의 C언어 계열에서는 모든 것을 사용자가 전부 만들어 사용하였지만 언어의 발전에 따라 고급언어로 발전하면서 객체지향의 여러 기법들을 이용하고 있습니다. 이러한 객체지향 기법 중에서 가장 일반적으로 많이 사용되는 Overloading을 잘 알고 있다면 보다 효율적으로 프로그램 개발을 할 수 있을 것입니다.
Overloading의 개념은 일반 메서드, 생성자메서드, Static메서드에 이르기까지 메서드가 활용되는 곳이면 어디서든지 등장하고 있습니다. 현재 적용한 중복 메서드의 개념은 부분적인 것입니다. 앞으로 다루어질 대부분의 장에서 중복 메서드의 개념을 별다른 설명없이 다루게 될 것입니다. 이 절에서 언급한 개념만 가지고 있다면 문안하게 Overloading을 해결 하리라 생각됩니다.
4.4 Inheritance
4.4.1 상속이란?
상속이란 기존의 만들어 둔 클래스를 다시 이용할 방법을 제공하고 있습니다. 상속은 기존 구조체에서는 없던 개념으로 클래스에서만 통용되는 개념입니다. C와 가장 잘 구별 되는 부분이기도 하지만 상속의 개념은 객체지향의 많은 부분에서 상당히 난해한 개념들을 내포하고 있습니다.
자바에서는 만들어진 클래스를 이용하여 새로운 클래스로 확장할 때 extends라는 키워드를 사용합니다. 사실 자바에서 extends가 전부라 해도 과언이 아닐 것입니다. 자바에서 상속을 빼면 남는 것이 없죠. 자바의 다양한 라이브러리를 상속 받을 수 있다는 장점때문에 우리는 자바를 선호하는지도 모릅니다. 그렇다면 상속은 무엇일까요? 하위클래스가 상위 클래스를 상속 받았을 때 일단 하위클래스는 상위클래스의 모든 권한을 갖게 됩니다.
즉, 상위클래스에서 많은 프로그램을 해 두었고 많은 능력을 가지고 있다면 이것을 모두 이용할 수 있다는 장점이 있습니다. 객체지향기법에서 클래스내에 메서드의 삽입보다도 더 엄청난 기능이 바로 상속의 개념입니다. 이미 만들어진 클래스를 이용하여 새로운 클래스를 생성한다는 말 보다는 만들어져 있는 클래스에서 상속을 받는 순간 현재의 클래스가 곧 상위클래스에서 출발한다고 생각하는 것이 더 이해가 쉽겠군요. 예를 들면 java.awt.Frame을 상속받아 새로운 클래스인 HelloFrame클래스를 만들었다면 아래와 같이 만들어 집니다.
public class HelloFrame extends Frame{
// 클래스 내에 작업
}
4.4.2 상속의 기본 예
상속에 관련된 클래스를 직접 만들어 보겠습니다. 일단 할아버지 클래스를 만들죠. 할아버지 클래스를 상속 받아 아버지 클래스를 만들고 아버지클래스를 이용하여 객체를 만들고 아버지 객체에서 할아버지 소속의 메서드들 호출해 보죠. 이렇게 된다면 아버지가 할아버지 것을 이용하는 것이 되겠죠. 지금 설명한 예제는 아래와 같습니다.
GrandFather.java
public class GrandFather{
public GrandFather(){
System.out.println(" I am GrandFather ");
}
public void sayGrandNumber(){
for(int i =0; i<10; i++){
System.out.print( i + "\t");
}
}
}
Father.java
public class Father extends GrandFather {
public Father(){
System.out.println(" I am Father ");
}
public void sayFatherNumber(){
for(int i =0; i<20; i++){
System.out.print( i + "\t");
}
}
}
InheritanceTest.java
public class InheritanceTest {
public static void main(String[] args) {
Father f = new Father();
System.out.println("GrandFather의 sayGrandNumber");
f.sayGrandNumber(); // 할아버지의 메서드 호출
System.out.println("Father의 sayFatherNumber");
f.sayFatherNumber(); // 아버지 자신의 메서드 호출
}
}
C:\examples\4. Class for Basic Java>javac GrandFather.java
C:\examples\4. Class for Basic Java>javac Father.java
C:\examples\4. Class for Basic Java>javac InheritanceTest.java
C:\examples\4. Class for Basic Java>java InheritanceTest
I am GrandFather
I am Father
GrandFather의 sayGrandNumber
0 1 2 3 4 5 6 7 8 9
Father의 sayFatherNumber
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
위의 예제에서 Father클래스는 GrandFather클래스를 상속하고 있습니다. GrandFather를 상속 받는 순간 Father클래스는 GrandFather클래스의 모든 것을 가지고 시작한다고 보면 옳을 것입니다. Father클래스를 테스트하기 위해서 InheritanceTest클래스의 main메서드에서 Father 객체 f를 생성하고 Father객체는 GrandFather클래스의 것을 자신의 것처럼 호출 하고 있습니다. 이것이 바로 상속의 장점입니다. 생성자메서드를 제외한다면 Father클래스는 아버지의 클래스의 메서드와 자신의 메서드 2개를 가지게 됩니다. 다음은 위의 프로그램의 구조를 보여 주고 있습니다.
여기서 잠깐 좀 거슬리는게 있죠. 바로 생성자메서드입니다. 상속은 상위 클래스의 모든 것을 다 받는다면 생성자메서드도 상속 받을까요? 여기서는 답은 No입니다. 생성자메서드는 상속 하지 않습니다.
아버지가 할아버지를 상속 받았기 때문에 할아버지의 메서드를 아버지가 사용할 수 있습니다. 하지만 결과에서 이상한 부분을 발견할 수 있습니다. 바로 생성자메서드의 호출입니다. 아버지 객체를 만드는데 할아버지의 생성자메서드도 호출 되고 있습니다. 이상한 일이죠. 이것은 이렇게 설명할 수 있습니다. 상속과정에서 상위 레벨의 클래스의 메모리가 생성되지 않는다면 자식 레벨의 메모리는 생성할 수 없다. 그렇기 때문에 Father클래스로 객체를 만들었다면 Father이 상속 받은 모든 상위 레벨의 생성자메서드가 차례대로 호출 되어지고 제일 마지막에 자신의 것이 호출 되어집니다. 할아버지가 없다면 아버지도 없겠죠. 그래서 할아버지 생성자가 호출 된 후. 아버지의 생성자가 호출 됩니다.
여기서 할아버지, 아버지, 아들까지 3단계로 나누어서 상속해도 이 규칙을 어기지는 않습니다. 아래는 3단계의 구조와 프로그램의 예입니다.
Child.java(3단계상속을 위한 아들 클래스)
public class Child extends Father {
public Child(){
System.out.println(" I am Child");
}
public void sayChildNumber(){
for(int i =0; i<30; i++){
System.out.print( i + "\t");
}
}
}
InheritanceTest2.java(3단계 상속을 위한 예제)
public class InheritanceTest2 {
public static void main(String[] args) {
Child c = new Child();
System.out.println("GrandFather의 sayGrandNumber");
c.sayGrandNumber();
System.out.println("Father의 sayFatherNumber");
c.sayFatherNumber();
System.out.println("Child sayChildNumber");
c.sayChildNumber();
}
}
C:\examples\4. Class for Basic Java>javac Child.java
C:\examples\4. Class for Basic Java>javac InheritanceTest2.java
C:\examples\4. Class for Basic Java>java InheritanceTest2
I am GrandFather
I am Father
I am Child
GrandFather의 sayGrandNumber
0 1 2 3 4 5 6 7 8 9
Father의 sayFatherNumber
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
Child sayChildNumber
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
상속단계가 늘어나더라도 기본적인 법칙은 똑같습니다. 메모리는 최상위 클래스부터 차례대로 생성되어지며 Child클래스는 최하위 클래스인데도 그 상위의 클래스에 속해있는 메서드를 모두 사용할 수 있습니다.
4.4.3 생성자 호출 순서
어떤 클래스가 상위클래스로부터 상속을 받았다면 당연히 상위 클래스의 메모리를 가지고 있어야 합니다. 상위클래스의 메모리를 가지고 있기 때문에 우리는 상위클래스를 상속 받았다고 합니다. 이 증거는 하위 클래스를 생성했을 경우 상위클래스의 생성자메서드부터 차례대로 호출하는 것을 볼 때 정확하게 알 수 있습니다. 즉 상위 클래스의 메모리를 하위 클래스가 포함하는 방법은 하위클래스를 생성하였을 때 상위클래스의 메모리를 자신이 갖기 위해서 자체적으로 상위클래스의 메모리를 생성시키기는 방법을 이용하는 것입니다. 이러한 방법으로 하위 클래스는 상위클래스의 모든 메모리를 차례대로 확보할 수 있습니다. 위의 예에서 생성자의 호출 순서를 나타내면 아래의 그램과 같습니다.
4.4.4 중복상속
자바에서 중복상속은 허용하지 않습니다. 2개의 클래스로부터 동시에 상속할 수 없다는 뜻입니다. 표준 C++에서는 중복상속을 허용하지만 동시에 여러 개를 상속할 때의 문제점때문에 자바에서는 이것을 배제시켰다고 합니다. 자바에서는 중복상속을 허용하고 있지는 않지만 편법으로 이를 사용하고 있다. 기존의 중복상속의 문제점을 없애고 그리고 획일적으로 사용하는 방법을 사용하고 있습니다. 이것이 바로 interface라고하는데 interface는 5장에서 다루도록 하겠습니다.
4.4.5 private과 public의 상속관계
private과 public은 상속관계를 논할 때 우리는 실행 타임에 한하여 접근제어를 설명하였습니다. 접근제어는 실행타임과 디자인타임 두 분류로 나뉘며 접근제어 부분에서는 객체의 메모리를 생성한 후의 접근제어인 실행타임 접근제어만 배웠습니다. 지금이 바로 클래스를 디자인하는 타임의 접근제어를 논할 때인 것 같습니다.
즉, 상속관계에서 디자인타임의 접근제어가 이용된다는 것입니다. 자 그렇다면 정확하게 상속에서의 접근제어 문제는 어떤 것인지 알아보도록 하겠습니다. 먼저 이런 질문부터 던져 보죠. (쉬운 설명을 위하여 상위클래스를 아버지라 부르고 하위클래스를 아들이라 부르겠습니다. 아들은 당연히 아버지를 상속하고 있는 상태입니다.)
n 상속을 한다면 아버지의 private 멤버필드와 멤버메서드를 아들이 물려 받습니다. 이 때 아들은 아버지의 private멤버를 자신의 멤버처럼 마음대로 접근 할 수 있을까요?( 힌트는 public이라면 완전히 자신의 것과 똑같이 다룰 수 있습니다.)
이 질문에 대한 해답은 한번 private은 영원한 private이라는 것입니다. 아들이 아버지로부터 상속을 받았다면 아버지의 것은 내것, 내것도 내것이라는 명제가 통용됩니다. 하지만 아버지의 마지막 남은 자존심인 private만큼은 직접접근을 불허합니다.
MyFather.java
public class MyFather {
public String name;
private String nickname;
public MyFather(){
name ="위대한 아버지";
nickname="뺑코";
}
public String getNickName(){
return nickname;
}
}
Son.java
public class Son extends MyFather{
public void sayFatherNames(){
System.out.println(name);
System.out.println(nickname);
}
public static void main(String[] args){
Son s = new Son();
s.sayFatherNames();
}
}
C:\examples\4. Class for Basic Java>javac MyFather.java
C:\examples\4. Class for Basic Java>javac Son.java
Son.java:5: nickname has private access in MyFather
System.out.println(nickname);
^
1 error
이 예제는 에러가 발생합니다. 아버지의 private을 마음대로 사용하려다 컴파일도 하지 못하고 있습니다. 아버지의 nickname멤버필드는 private인데 이를 아들이 마음대로 접근하는 것은 오류입니다. 컴파일 에러입니다. 하지만 name이라는 pubic멤버필드는 마음대로 접근하고 있습니다. 아들의 클래스를 디자인 할 때 마치 자신의 멤버 변수처럼 이용하고 있습니다. 현재 설명하고 있는 것은 상속의 관계에서 나타나는 디자인타임의 접근제어입니다. 한번 private은 영원한 private입니다. private의 접근제어는 static접근, 상속관계, 실행타임의 접근제어 모든 곳에 해당 그 규칙을 지킵니다. 즉, private은 예외가 존재하지 않습니다.
그렇다면 아버지의 private에 어떻게 접근 할 수 있을까요? 너무나도 맹한 질문이죠. private은 public메서드를 통해서만 접근 할 수 있습니다. 더 이상 이것은 논하지 않아도 뻔한 이야기 입니다. 자 그렇다면 이런 질문을 다시 던져보죠.
n 아버지의 멤버는 실행 타임에는 private이면서 아들에게는 완전한 public이고자합니다. 이것이 부모의 마음 아니겠습니까? 이러한 방법이 자바에 존재할까요?
답은 예입니다. 위의 질문은 객체의 메모리를 생성한 후 점(.)찍고 접근 할 때는 완벽한 prviate이면서 상속관계의 클래스 디자인타임에는 완벽한 public이고자 한다는 것입니다. 이를 방법론을 제공하기 위해서 아직 설명하지 않은 protected접근지정자가 존재합니다.
n 위의 예제에서 private을 protected로 고친다면 이 예제는 아주 멋지게 동작 할 것입니다.
간단하게 설명을 했지만 다시 설명을 한다면 protected 는 상속의 관계에서 나온다. protected는 private과 똑같다. 외부에서는 접근 불가능하다. 그리고 private은 상속을 했을 때도 하위 클래스에서도 직접접근 불가능해진다. 혹자는 이런 의문을 가질 것이다. 상속을 받았다면 상위클래스의 모든 멤버는 하위클래스의 멤버가 되는 것이 아닌가라는 의문을 제기할 것이다.
모든 면에서 private을 수행하면서 상속의 관계에서만 public의 행위를 하는 것이 바로 protected입니다. 이것을 다르게 표현한다면 아버지의 모든 것은 상속을 받아 직접 사용할 수 있지만 아버지의 private만은 직접접근 못합니다. 하지만 아버지의 protected는 아들의 멤버처럼 사용할 수 있습니다. 이렇게 하면 아버지의 모든 것이 아들의 것이 된다고 해도 과언이 아니다. 즉 아버지것도 내것 내것도 내것이라는 말을 사용할 수 있는것입니다. 이러한 모든 관계를 위의 예제를 이용하여 나타내면 아래의 그림과 같습니다.
멤버필드만을 이야기하는데 멤버메서드는 어떻게 되느냐고 하신다면 똑같다고 이야기 할 수 밖에 없습니다. 메서드나 변수나 똑 같은 놈들이니까요. 객체지향에서도 부모 자식관계의 인륜의 법칙은 완전하게 지키고 있는 것이 신기하지 않습니까? 알고 보면 자바는 객체 지향기술의 진수만 모아 둔 언어입니다. 골치 아픈 OOP개념은 내부로 숨기면서 명확하고 간단한 방법론을 제시하는 것이 자바라는 언어입니다.
4.4.6 라이브러리를 이용한 상속
자바 라이브러리를 사용하여 상속을 배워 보도록 하죠. 현재는 간단히 윈도우 창을 만들어 띄워 보도록 하겠습니다. 상속을 하고자 한다면 상속할 클래스를 라이브러리를 import시키거나 아니면 같은 디렉토리에 두어야 한다.
import java.awt.*;
현재 java.awt내에 Frame이라는 클래스가 존재합니다. 이 클래스를 사용하겠다는 것입니다. 물론, 이 클래스는 JDK에서 기본으로 제공해 주는 라이브러리입니다. 우리가 상속하거나 아니면 사용하려고 하는 라이브러리를 import하는 역할을 합니다. 같은 디렉토리에 있다면 import할 필요가 없습니다. 자바를 설치할 때 “classpath =.;” 이라고 잡아 준 것은 같은 디렉토리의 클래스를 import하지 않아도 되는 역할을 합니다. 우리가 만든 라이브러리가 있다면 당연히 classpath에 추가시켜 주어야 합니다. 이렇게 하지 않는다면 해당 클래스를 프로그램에서 사용할 수 없습니다. 간단한 예를 만들어 보도록 하죠.
MyFrame .java(라이브러리 상속을 테스트 하기 위한 예제)
import java.awt.*;
public class MyFrame extends Frame{
public static void main(String[] args){
MyFrame m = new MyFrame();
m.setSize(200,200);
m.show();
}
}
C:\examples\4. Class for Basic Java>javac MyFrame.java
C:\examples\4. Class for Basic Java>java MyFrame
윈도우를 닫고자 한다면 Ctrl-C를 콘솔창에서 누르시면 됩니다.
4.4.7 결론
이 절에서 우리는 어느 정도 상속이 무엇인지에 대해서 알았습니다. 물론 상속의 개념은 잘 만들어 둔 클래스를 재 사용할 수 있을 때는 아주 효과 적입니다. 그리고 간단한 접근제어의 원칙만 지킨다면 아주 유용하게 사용할 수 있습니다. 하지만 상속구조를 잘못 잡으면 프로그램은 엉망이 되고 맙니다. 상속은 두 가지 시각에서 바라 볼 수 있습니다. 자신이 전체적인 프로그램의 구조를 잡기 위해서 상속구조를 만드는 입장과 만들어져 있는 클래스나 라이브러리를 이용하는 입장으로 나눌 수 있습니다. 어떠한 경우든 상속의 개념을 잘 파악하지 못하면 상속에서 나타나는 장점들을 이용하기에는 어렵습니다.
일반적인 상속개념을 알고 있다면 라이브러리를 이용하는 것은 그렇게 어렵지 않습니다. 하지만 자신의 프로그램 구조를 상속 구조에 맞게 잘 만들기 위해서는 작업 설계와 분석이 반드시 포함 되어야 합니다. 작업 분석과 설계가 없는 상속 구조는 결국에 쓰레기통으로 보내야겠죠. 우리가 마지막으로 배운 접근제어는 protected입니다. 이 protected의 진수(珍秀)는 바로 Clonable이라는 인터페이스를 이용할 때 사용됩니다. 객체는 참조를 기본으로 하고 있기때문에 객체의 메모리를 일반적인 복사기법으로 복사할 수는 없습니다. 이러한 문제를 해결하기 사용되는 Clonable기법에서 protected메서드를 사용하게 됩니다. 이 Clonable은 다른 장에서 다시 설명하도록 하겠습니다.
4.5 Inheritance와 Overriding
4.5.1 메서드 재정의란?
객체지향 개념에서 Overloading만큼이나 중요하며 상속 개념에서 빼놓을 수 없는 것이 바로 Overriding입니다. Overriding은 즉, 메서드 재정의는 말 그대로 메서드를 다시 재정의하는 것을 말합니다. 아버지를 상속 받은 아들이 아버지의 메서드와 똑같은 메서드를 다시 만들 때 이를 어떻게 해결하느냐의 문제를 담고 있습니다. Overriding을 개인적으로 아버지 무시하기로 부르고 있는데 그 이유는 아들이 아버지의 메서드를 재정의 했을 때 아버지의 메서드를 완전히 무시하는 경향이 있기 때문입니다. 메서드 재정의에 대해서 자세하게 알아 보도록 하겠습니다.
4.5.2 상속개념에서의 Overriding
아버지클래스를 상속 받아 아들클래스를 만들었을 때, 아버지가 가지고 있던 메서드를 아들클래스가 다시 만들었다고 가정해 보십시오. 아버지 것은 내 것이고 내 것도 내 것이니 완벽하게 똑같은 이름의 메서드가 2개 존재합니다. 아들클래스가 객체변수를 만들고 이 메서드를 호출한다면 아들클래스는 순간 당황하게 될 것입니다. 아버지 것을 사용할까? 내 것을 사용할까? 하지만 아들클래스의 객체는 유유히 자신의 것을 사용합니다. 왜냐하면, 자신의 것이 더 소중하니까요? 아버지의 메서드는 완전히 무시당하는 것이죠. 이것을 우리는 메서드 재정의라고 부르고 있습니다. 같은 이름의 메서드를 다시 만들 때 상위클래스의 메서드는 무시 당한다는 것입니다. 개념은 아주 쉽습니다. 하지만 여기서 발생하는 약간 난해한 문제점들이 있습니다. 좀 이상하기도한 그런 개념이니 주의해서 관찰해 보도록 하시기 바랍니다. 우선, 메서드 재정의의 가장 표준이 되는 예를 만들어서 메서드 재정의의 개념부터 파악해 보도록 하겠습니다.
OverrideTest.java(상속에서 Override Method를 테스트하기 위한 예제)
class FaFa {
public void print() {
System.out.println("난 할아버지다");
}
}
class Baby extends FaFa{
public void print() {
System.out.println("난 아버지다");
}
}
public class OverrideTest{
public static void main(String[] args){
FaFa fafa = new FaFa();
fafa.print();
Baby baby = new Baby();
baby.print();
FaFa faba = new Baby();
faba.print();
}
}
C:\examples\4. Class for Basic Java>javac OverrideTest.java
C:\examples\4. Class for Basic Java>java OverrideTest
난 할아버지다
난 아버지다
위의 예제에서 객체 3개를 생성하고 있습니다. 객체 생성 부분부터 살펴 보기로 하겠습니다.
n FaFa fafa = new FaFa();
n Baby baby = new Baby();
n FaFa faba = new Baby();
첫번째와 아주 쉽습니다. FaFa클래스가 자신의 메모리 만큼을 생성하고 있습니다. fafa객체의 print메서드의 호출은 당연히 FaFa클래스의 메서드가 될 것입니다. 두 번째의 경우, Baby클래스는 Baby클래스만큼을 생성하고 있습니다. 그리고 Baby클래스는 baby객체를 만들었으며 Overriding개념을 적용 시킨다면 아버지의 print는 무시되어지고 자신의 print가 호출 되어집니다. 이것은 이 절의 중심 주제입니다. 여기서 잠깐! 참고적으로 하나의 파일 내에 두개의 클래스가 존재할 때 메인 메서드를 가지고 있는 클래스가 파일이름의 주인이 됩니다. 그리고 public클래스는 하나만 존재할 수 있습니다.
Overriding개념이 너무 단순하다는 것이 약간 의심스럽지 않습니까? Baby클래스가 baby객체를 만들었을 때 자신의 것이 우선하느냐 아버지 것이 우선하느냐의 문제에서 자신의 것이 우선한다는 것은 기정 사실처럼 이치에 딱 들어 맞습니다. 그런데 세 번째 객체 생성을 한번 보시기 바랍니다. 아주 황당무계(荒唐無稽)한 일이 벌어지고 있습니다. 데이터 타입은 아버지의 것을 사용하며 메모리는 아들 것을 사용하고 있습니다. 정리해 보면, 아버지의 데이터 타입 객체변수에 아들의 메모리를 넣는 형식이 됩니다. 우리는 이것을 업캐스팅이라고 부릅니다. 이렇게 만들어진 아들의 메모리를 가진 아버지의 데이터타입 객체변수는 아버지의 특성이 강할까요? 아들의 특성이 강할까요? 메서드가 재정의 되었다면 아버지이면서 아들의 메서드를 호출할 수 있습니다. 그렇기 때문에 아버지의 객체변수로 print를 호출 했을 때 아들의 메서드가 호출 되어집니다. 그 이외에는 완벽하게 아버지의 역할을 수행합니다. 그래서 위 예제의 결과는 아버지 객체변수가 아들의 메서드를 호출한 것입니다. 전체적인 구조는 아래의 그림과 같습니다.
문제는 “FaFa faba = new Baby()”이 어떠한 장점이 있는가에 있습니다. 많은 장점이 있다고 합니다. 사실, 엄청난 장점이 있습니다. 이 업캐스팅의 느낌을 가지고 있다면 여러분은 더 이상 배울 것이 없습니다. 하산(下山)해도 된다는 소리입니다. 업캐스팅은 너무 덩치가 크기 때문에 앞으로 나올 장 중에서 다시 설명을 하도록 하겠습니다. 여기서는 이렇게 정의를 내리고 가겠습니다.
n 아버지의 이름으로 아들의 메모리를 참조한다.
n 아버지라 하더라도 메서드 재정의가 사용되었다면 아들의 메서드를 호출한다.
n 메서드 재정의 이외에는 완벽한 아버지의 역할을 한다.
이 개념은 분명한 C++의 개념입니다. Visual C++에서도 이 개념을 빼버리면 VC++가 아닙니다. 보통 ANSI C++에서는 가상 메서드라고 부릅니다. 물론 Visual C++와 C#에서도 이 virtual 메서드는 사용됩니다. 하지만 자바에서는 특별한 설명을 붙이고 있지 않기 때문에 더 혼동 되는 요소들이 있습니다.
4.5.3 메서드 재정의에 대한 느낌
아버지의 메서드를 아들이 다시 만들었다면 이것은 아들이 아버지의 특징을 더욱 발전시키거나 아니면 아들만의 특성을 가진 것으로 다시 만든 것입니다. 당연히 아들은 아버지의 것보다는 자신의 것을 사용할 것이다. 이것은 아버지의 모든 것을 상속한다는 특수한 방법을 통해서 아들은 아버지 것을 모두 받아 들이고 필요한 부분만 개선하겠다는 의미를 담고 있습니다. 아주 유용하게 사용할 수 있으며 자바에서 메서드 재정의를 빼면 시체가 되어버릴 정도로 아주 일반적인 프로그래밍 기법입니다.
4.5.4 아버지 무시의 느낌
할아버지, 아버지 그리고 아들 모두 print메서드를 가지고 있다고 생각해 보죠. 아버지는 할아버지의 print와 아버지의 print두개의 메서드를 가지고 있습니다. 아들은 3개를 가지고 있죠. 이때 아들의 입장에서 어느것을 사용할 것인가? 아버지는 할아버지의 print를 무시합니다. 그리고 아들은 다시 아버지의 print를 무시하게 됩니다. 아들은 전체 3개의 print를 가지고 있지만 아버지 이전의 것들은 통으로 하나의 조상으로 취급하기 때문에 print는 2개가 있는 것입니다.
n 할아버지의 print
n 아버지의 print
n 아들의 print
아버지가 모든 그 상위 레벨의 정보를 다 가지고 있기 때문에 아들은 아버지 자체를 하나의 조상으로 취급을 하게 됩니다. 그 이전의 모든 정보나 특징들은 아버지가 알아서 다 가지고 있는 것입니다. 처음 이러한 상속개념을 접하게 되면, 할아버지의 할아버지는 어떻게 처리할 것인지 당연히 의심이 가게 마련입니다. 하지만 아들의 입장에서 바라보면 아버지가 모든 정보를 다 처리해서 아버지 상위에 있는 조상들의 정보를 정리해 주기 때문에 아들은 아버지만을 생각하면 됩니다. 즉 아들은 아버지의 print만 생각하면 되는 것입니다. 실제로는 3개의 print가 존재하지만 아들입장에서는 print가 2개 존재하는 것이 되어 버리죠. 아버지의 print와 자신의 print 즉, 아버지의 print를 무시하는 것입니다.
만약 아래와 같이 된다면, 아버지의 print가 없다면 어떻게 될까요. 잘 생각 해 보시기 바랍니다.
n 할아버지의 print
n 아버지의 print가 없음
n 아들의 print
이러한 아래와 같은 구조로 비교하여 나타낼 수 있습니다.
아버지는 할아버지를 상속 받아 할아버지의 print가 아버지의 print가 됩니다. 그렇기 때문에 할아버지의 print메서드가 아버지의 print메서드가 되는 것입니다. 그래서 아버지의 print를 무시하게 되지만 실제적으로는 할아버지의 print를 무시하는 것이 됩니다. 그렇게 어려운 말은 아닙니다.
4.5.5 결론
상속의 개념에서 적용되는 Overriding은 클래스의 재사용이라는 측면에서 아주 유용하게 사용됩니다. 아버지의 클래스의 원하는 부분만을 개선하여 다시 사용하겠다는 의미입니다. Overriding 자체의 개념은 아주 쉽게 넘어 갈 수 있습니다. 아직 완벽하게 설명하지 않은 “아버지의 이름으로 아들을 가르킨다”라는 Upcasting의 개념은 Overriding개념의 최고봉이라 할 수 있습니다. 이 절에서는 간단하게 “이렇게 한다.” 정도만 알아두고 다시 나오면 그 때 철저하게 분해를 해 보겠습니다.
그리고, 아버지 무시하기 즉, 메서드 재정의를 했을 때 아버지를 완벽하게 무시한다면 아버지의 메서드를 사용하고 싶을 때는 문제가 발생합니다. 무시해버렸기 때문에 아버지의 메서드를 호출하고 싶을 때 방법이 없습니다. 이것의 방법을 자바에서는 super라는 아버지의 참조값을 두어 해결하고 있습니다. 아들은 super라는 아버지의 참조값으로 아버지의 메서드를 호출할 수 있습니다. 이제 this의 개념과 super의 개념에 대해서 알아 보도록 하겠습니다.
4.6 this의 개념
4.6.1 this란?
this란 클래스 내에서 클래스가 가지고 있는 멤버필드 또는 멤버 메소드를 직접 참조할 수 있는 자신의 참조 변수입니다. 이게 무슨 말이냐고요. 이게 무슨 말인지 알아 보도록 하겠습니다. this를 사용할 때는 3가지 방법을 이용하고 있습니다. 그 예는 아래와 같습니다.
n this.멤버필드, this.멤버메서드
n this
n this(매개변수);
이 예를 알아보면서 this의 개념에 접근 해 보도록 하겠습니다.
4.6.2 this의 의미
먼저 this에 대한 정의부터 해결 해 보도록 하죠. this를 다음과 같이 풀어서 이야기 할 수 있습니다.
n 클래스 내에서 클래스가 가지고 있는 변수 또는 메소드를 직접 참조할 수 있다.
n 클래스 내에서 자신의 멤버들을 이용할 수 있는 것은 당연한 일입니다.
n 자신을 참조하는 객체 변수 this를 이용하여 멤버를 이용할 수 있습니다.
n 디자인타임에 자기 자신을 직접 참조할 수 있는 객체 변수입니다.
n 디자인타임에 자기 자신을 참조할 수 있는 유일한 키워드입니다.
자기가 자기 자신을 참조한다는 의미가 무엇일까요. 분명히 여러분은 this를 클래스의 디자인타임에 사용 할 수 있습니다. this는 디자인타임에 사용하는 것이지 실행타임에 직접 여러분이 사용하는 것은 아닙니다. 자바 가상 머신이 사용하는 것이라고 말 할 수 있습니다.
클래스를 디자인 할 때는 아직 메모리가 할당되지 않은 순간입니다. 언젠가는 할당 되겠죠. 하지만 언젠가 할당될 주소의 참조 값은 아직 미정입니다. 객체를 생성하기 전 단계에 그 주소를 this라고 칭하고 할당되는 순간 this는 할당 되는 메모리의 참조값을 갖게 된다면 this는 디자인타임에 실행타임의 주소를 아는 것처럼 행동할 수 있습니다. 제가 설명해 놓고도 모호하네요. 그렇다면 이렇게 해 보십시오. this를 “언젠가 생성되어질 내 주소this의”라는 간단한 단어를 바꾸어서 해석해 보십시오.
위의 그림은 this가 초기화 되는 순간을 보여 주고 있습니다. 디자인타임에서 클래스는 아직 데이터 타입이기 때문에 메모리를 할당 하지 않은 상태입니다. 클래스가 객체를 생성하여 메모리를 갖는 순간 생성된 메모리의 참조값을 객체변수가 가지게 됩니다.
n Top a = new Top();
n Top b = new Top();
n Top c = new Top();
n Top d = new Top();
위의 그림에서 4개의 참조 값 a, b, c, d를 생성하고 있습니다. 문제는 이 참조 값들은 메모리가 생성되는 순간의 참조 값이라는 것입니다. 이것은 일반적인 방법입니다. 하지만, this는 디자인타임에 자신을 참조할 수 있는 참조 값입니다. 디자인타임에 클래스의 메모리가 생성되나요. 아닙니다. 그저 하나의 형태일 뿐입니다. 그렇다면 어떻게 this가 생성되지도 않은 자신을 참조 할 수 있을까요?
잘 생각 해 보십시오. 디자인타임의 참조값은 아직은 비어있는 있다고 가정하고 실행할 때 this에다 각각의 객체가 할당 받은 값을 넣어 준다면 하나의 this가 메모리가 생성되는 순간 각각의 객체에 대한 this가 되지 않겠습니까!
this라는 객체변수는 아직 만들어지지도 않은 자신의 참조값이라는 가정하에서 클래스 내에 사용되어집니다. 그리고 메모리가 생성되면 그 때 객체변수가 가지는 값과 같은 참조값을 가지게 되는 것입니다. 이렇게 함으로써 디자인타임에 하나의 이름으로 자신을 참조하고 실행 타임에서는 각각에 적용 되어질 수 있는 것입니다.
4.6.3 this.멤버필드, ths.멤버메서드
this의 사용법 중에서 this를 이용하여 자신을 참조하는 방법을 알아 보도록 하겠습니다. 클래스 내에서 사용되어지는 멤버필드와 멤버메서드를 사용할 때 this를 사용하는 것과 하지 않는 것은 동일합니다. 귀찮기 때문에 this를 사용하지 않지만 this를 표시한다면 코드는 더욱 명확해집니다. 코드가 길어지면 사용하는 변수가 클래스의 멤버필드인지 아니면 지역변수인지 혼동 될 수가 있습니다. 만약 this를 이용한다면 아주 간단히 멤버인지 아닌지를 구분할 수 있습니다.
디자인타임의 this는 공통된 this이지만 실행 타임의 this는 생성되어지는 객체 변수의 참조값과 같은 값을 가지기 때문에 각각의 this가 되는 것입니다. 아직 생성도 되지 않은 메모리에 대한 참조값을 this라는 객체변수가 나중에 메모리가 생성되어지면 가질 것이라는 가정하에 사용하는 것이니 this를 사용하여 멤버에 접근하든지 그냥 멤버에 접근하든지 결국에는 같은 것입니다. 일단 예를 들어 보도록 하겠습니다.
ThisTest.java(this 테스트하기 위한 예제)
public class ThisTest {
private int number;
private String name;
public ThisTest(String name, int number){
//this를 이용한 매개변수와의 구분
this.name = name;
this.number = number;
}
public void print(){
System.out.println("-- this를 이용한 멤버 테스트---");
System.out.println("name:" + this.name + " number:" + this.number);
}
public void myprint(){
System.out.println("-- this를 이용한 메서드 테스트---");
this.print();
}
public static void main(String[] args){
ThisTest th = new ThisTest("홍길동", 20);
th.print();
th.myprint();
}
}
C:\examples\4. Class for Basic Java>javac ThisTest.java
C:\examples\4. Class for Basic Java>java ThisTest
name:홍길동 number:20
-- this를 이용한 메서드 테스트---
이 ThisTest클래스에서는 두 개의 멤버필드를 가지고 있습니다. 이 멤버필드에 접근하는 방법은 클래스 내에서는 name과 number를 사용하고 싶은 곳에 마음대로 사용할 수 있습니다. 하지만 생성자메서드를 본다면 name이 중복되어 있습니다. 이것은 메서드의 매개변수와 클래스의 멤버필드가 동일하기 때문에 구분을 해 주어야 합니다. 이것을 구분하기 위한 방법으로 this를 사용한다면 멤버라는 의미입니다. 위 예제의 생성자 부분에서 이러한 변수의 애매성을 명확하게 해결하고 있습니다.
public ThisTest(String name, int number){
//this를 이용한 매개변수와의 구분
this.name = name;
this.number = number;
}
매개변수의 이름도 name이고 멤버필드의 이름도 name입니다. 언어적인 원리에 의하면 가까운 변수가 더 큰 영향력이 있습니다. this를 붙이지 않으면 생성자메서드내에 존재하는 name은 모두 매개변수의 name입니다. 이렇게 된다면 외부에서 들어오는 데이터를 멤버필드에 넣을 수가 없습니다.
만약에 this를 붙인다면 this는 언젠가 생성되어질 내 주소this의 name이 되어 버립니다. 이렇게 되면 당연히 this.name은 멤버 변수의 name이 되기 때문에 별다른 노력 없이 변수의 이름이 같아도 니꺼 내꺼 확실하게 구별 지을 수 있습니다. name과 number는 생성자메서드의 매개변수를 말하는 것이며 this.name과 this.number은 멤버필드를 가르키는 것입니다.
this라는 키워드는 멤버라는 것을 알리는 하나의 표시방법이지만 만약 위의 상황처럼 특수한 경우가 아니면 this키워드를 사용하지 않아도 무방하다. 하지만 this키워드를 사용하여 어느 소속의 메서드인지 변수인지를 분명히 한다면 코드는 더욱 명확해 집니다. 클래스 내에 모든 멤버는 this를 사용할 수 있으며 사용하지 않아도 상관은 없습니다.
4.6.4 this
this가 혼자서 이용되면 자신의 참조값을 의미합니다. 하지만 이 참조는 디자인타임에서 적용되어지지만 결국의 this는 언젠가 만들어질 메모리의 참조값 this입니다. 즉, 이 말은 실행 타임에 this 자체는 객체를 생성할 때 객체 변수가 가지는 값과 같은 값을 가지게 됩니다. 생성될 객체의 참조값를 의미 하는 것입니다. this자체를 사용하는 예를 보도록 하겠습니다.
SpecialThis.java(this 테스트하기 위한 예제)
public class SpecialThis{
private int i = 0;
public void plusCount(){
i++;
}
public SpecialThis getMySelf(){
return this;
}
public void print(){
System.out.println("member i = " + this.i);
}
public static void main(String args[]){
SpecialThis st = new SpecialThis();
SpecialThis st2 = st.getMySelf();
st.print();
st.plusCount();
st.print();
st2.plusCount();
st2.print();
st.plusCount();
st.print();
}
}
C:\examples\4. Class for Basic Java>javac SpecialThis.java
C:\examples\4. Class for Basic Java>java SpecialThis
member i = 0
member i = 1
member i = 2
this라는 키워드가 없다면 클래스는 현재 디자인타임이기 때문에 자신을 참조할 방법은 없습니다. 잘 생각해 보시기 바랍니다. 아직 만들어지지도 않은 자신을 어떻게 참조할 수 있 있겠습니까? this키워드는 그저 this.멤버필드, this.멤버메서드 이렇게 사용하는 것이 아니라 객체가 메모리를 할당 받는 순간에 객체변수가 가지는 값과 똑 같은 값을 가지기 때문에 this 자체는, 디자인타임에 자신을 가르키는, 자기자신의 객체변수라고 보면 됩니다.
public SpecialThis getMySelf(){
return this;
}
위의 이 메서드는 객체가 생성되어진 후 호출 할 수 있습니다. 그리고 this는 객체가 생성되어지면 객체변수의 값과 같은 참조 값을 가지게 됩니다. 그러니 this는 자신의 형은 현재 클래스의 데이터타입과 같습니다. 메서드의 리턴타입과 리턴 되어지는 것은 같아야 합니다. 여기서 this의 타입은 자신의 참조변수이기 때문에 SpecialThis 데이터 타입입니다. 그리고, 자신을 리턴하고자 한다면 “return this;” 라고 하면 되는 것입니다.
4.6.5 this()
this()는 클래스 자신의 생성자메서드를 호출할 때도 사용합니다. 클래스 내에서 유일하게 호출할 수 없는 메서드가 있습니다. 이 메서드가 바로 생성자메서드입니다. 이것을 호출하는 방법을 제공하는 것이 바로 this입니다. 아래는 this를 이용하여 생성자메서드를 호출하고 있는 예입니다.
ThisSelf.java(this 테스트하기 위한 예제)
public class ThisSelf{
private String name;
private int age;
public ThisSelf(){
this("이름없음");
}
public ThisSelf(String name){
this(name, -1);
}
public ThisSelf(String name, int age){
this.name = name;
this.age = age;
System.out.println("name:" + name + " number:" + age);
}
public static void main(String[] args){
ThisSelf ts1 = new ThisSelf();
ThisSelf ts2 = new ThisSelf("홍길동");
ThisSelf ts3 = new ThisSelf("김삿갓", 50);
}
}
C:\examples\4. Class for Basic Java>javac ThisSelf.java
C:\examples\4. Class for Basic Java>java ThisSelf
name:이름없음 number:-1
name:홍길동 number:-1
ThisSelf클래스의 디폴트 생성자를 호출 했을 때
n this(“이름없음”)은 ThisSelf(String name)메서드를 호출 합니다.
n 그리고 ThisSelf(String name)생성자는 다시 this(name, -1)이라는 곳에서 ThisSelf(String name, int age)를 호출 합니다.
생성자메서드는 아무나 호출할 수 있는 것이 아닙니다. 객체의 생성당시 여러분이 new와 같이 명시해 주는 것이 생성자메서드입니다. 그 외의 경우 생성자메서드를 호출할 수 있는 방법은 이 방법 밖에는 없습니다. 디폴트 생성자를 호출하였지만 디폴트 생성자는 자신의 클래스 내에 다른 생성자를 this를 사용해서 호출하고 있습니다.
그런데 왜 이렇게 복잡한 방법을 이용하고 있을까요? 왜 자신의 생성자메서드를 이렇게 복잡하게 호출 하고 있을까요? 그 답은 알고 나면 별로 복잡하지 않다는 것입니다. 농담이구요. 자신의 생성자를 재 이용하는 것입니다. 클래스를 만들다 보면, 상당히 많은 작업을 하나의 생성자에서 처리했다면 다른 생성자에서도 다시 많은 작업을 해 주게 됩니다. 여러분이 클래스를 디자인하다 보면 이러한 현상은 자주 일어납니다. 물론 이것은 생성자 중복을 할 때 발생하는 문제점입니다. 그런데 자신의 생성자메서드를 호출할 수 있는 방법은 없고, 작업은 중복 된다면 객체지향의 기본 법칙을 위배하는 것입니다. 그래서 this를 이용해서 생성자메서드를 호출 하는 방법을 제공하고 있는 것입니다. 여기서 가장 조심해야 하는 것은 생성자메서드에서 다른 생성자메서드를 호출 할 때 생성자메서드 호출은 제일 윗부분에 사용해야 한다는 것입니다.
4.6.6 결론
this의 특징
n 객체 자신에 대한 참조값을 갖습니다.
n 매개변수와 객체 자신이 가지고 있는 변수의 이름이 같을 경우 이를 구분하기 위해 자신이 가지고 있는 변수 앞에 this를 사용합니다.
n 객체 생성자 내에서 다른 생성자를 호출하기 위해 사용합니다.
n 객체 자신에 대한 참조값을 메소드에 전달하거나 리턴해 주기 위해 사용하기도 합니다.
n 디자인타임의 this는 객체가 생성되어지는 순간에 객체변수가 가지는 값을 갖게 됩니다.
this를 사용함으로써, 모호하지 않고 좀더 명확한 프로그램을 작성할 수 있습니다.
4.7 super
4.7.1 super참조변수
super 참조변수란 상속구조에서 디자인타임에 사용할 수 있는 상위 클래스의 참조 변수입니다. 상위 클래스를 참조할 수 있는 유일한 방법입니다.
4.7.2 super를 이용하여 상위 클래스를 참조
아버지의 클래스로부터 상속을 받았다면 아버지클래스의 모든 것은 아들 클래스 것이 됩니다. 아들 클래스에서는 아버지 것도 내 것이요, 내 것도 내 것이니 this라는 키워들 사용 해서 아버지의 멤버에 접근 할 수 있습니다. 물론 아버지의 public이나 protected접근 지정자를 사용하는 멤버에만 접근할 수 있습니다.
이것은 너무나 당연한 상속 개념입니다. 아버지 클래스와 아들 클래스의 상속관계에서 재정의 메서드가 있고 아들 클래스 내부에서 재정의 된 메서드를 호출한다면, 무조건 아들의 재정의 메서드가 호출 됩니다. 재정의 자체는 아버지의 메서드를 무시하고 있기 때문입니다. 그런데 실제로는 재정의 된 메서드와 아버지의 무시되어진 메서드 2개가 존재합니다. 아들 입장에서 아버지의 메서드를 호출 하고자 한다면 어떻게 할까요?
나쁜 아들이죠. 자신이 재정의한 아버지의 메서드를 호출 할 수 있게 할 만큼 아버지 클래스는 너그러울까요? 무시되어진 아버지클래스의 메서드를 호출하는 방법이 바로 super참조변수를 이용하는 것입니다. super는 this와 같은 원리로 동작 되어집니다. 그래서 super자체는 디자인타임에 아버지를 참조하는 유일한 참조변수입니다.
NewSon .java(super를 테스트하기 위한 예제)
class NewFather{
public void overrideFunc(){
System.out.println("아버지의 함수");
}
}
public class NewSon extends NewFather{
public void overrideFunc(){
System.out.println("아들의 재정의 함수");
}
public void testFunc(){
super.overrideFunc();
}
public static void main(String[] args){
NewSon s = new NewSon();
s.overrideFunc();//재정의 된 함수호출
s.testFunc();//super를 이용한 아버지 호출
}
}
C:\examples\4. Class for Basic Java>javac NewSon.java
C:\examples\4. Class for Basic Java>java NewSon
아들의 재정의 함수
상속을 아들 클래스가 받았다고 한다면 아버지 무시하기 즉, 특정 메서드를 Overriding하고 있는 것입니다. 당연히 아버지 클래스의 메서드는 아들클래스와 같은 이름의 메서드가 존재하기 때문에 아들이 이 메서드를 사용하면 무시되어 집니다. 그렇다면 무시되어진 아버지클래스의 메서드를 사용하고 싶다면 어떻게 할까요? 방법이 없습니다. 물론 ANSI C++에서도 이것에 대한 해결책을 제시해 주고 있지만 자바에서는 super라는 키워들 제공해 주는 것입니다.
public void testFunc(){
super.overrideFunc();
}
testFunc()메서드에서 super를 이용하여 아버지의 메서드를 호출하고 있습니다. 자신의 메서드와 아버지의 메서드를 구분하기 위해서 이것을 사용하는 것이지 별다른 것은 없습니다
4.7.3 super를 이용한 생성자메서드 호출
상속을 받았을 경우 아버지의 클래스의 생성자메서드가 매개변수를 가지고 있다면 어떻게 될까요? 잘 생각해 보면 어쨌든 아버지의 클래스의 생성자메서드가 호출 된 후에 아들의 생성자메서드가 호출되는 것은 당연한 일입니다. 그렇다면 아들이 상속을 받았을 경우 아버지의 생성자의 매개변수는 누가 넣어 줄 수 있을까요? 아버지의 생성자의 매개변수를 넣어주지 않아도 아버지의 생성자가 호출 가능할까요?
아버지클래스의 생성자메서드가 매개변수를 가지고 있다면 아들 입장에서는 아버지의 생성자메서드의 매개변수를 넣어주어야 할 의무가 있다. 이것은 아들이 아버지를 상속 받았기 때문이며 자동으로 아들이 생성되어질 때 아버지의 생성자메서드를 호출하기 때문이다.
이것은 불가능합니다. 무조건적으로, 절대적으로, 생성자에 매개변수가 존재한다면 생성자의 매개변수의 형과 개수를 맞추어 주어야만 호출 가능합니다. 상속을 아들 클래스가 받아 버린 상태이고 그리고 아들은 아버지의 생성자에게 매개변수를 넣어줄 의무가 있을 것입니다. 이것을 무시한다면 아들은 만들어질 수 없습니다.
SuperSon.java(this 테스트하기 위한 예제)
class SuperFather{
private String name;
public SuperFather(String name){
this.name = name;
System.out.println("name:" + name);
}
}
public class SuperSon extends SuperFather{
public SuperSon(String str){
super(str);
}
public static void main(String[] args){
SuperSon s = new SuperSon("상속의 super Test");
}
}
C:\examples\4. Class for Basic Java>javac SuperSon.java
C:\examples\4. Class for Basic Java>java SuperSon
4.7.4 결론
4.8 마무리
이 장에서는 자바 프로그램을 하기 위한 기본적인 객체지향의 기법들을 배워 보았습니다. 상속, Overloading, Overriding과 같은 개념들은 자바에서 아주 큰 부분을 차지하고 있고 이러한 개념없이 자바를 배운다는 것은 불가능합니다. 기본적인 개념들은 아주 쉽지만 상속 관계와 연결되면서 나타나는 여러 개념들은 아직도 설명이 부족하다는 말 밖에는 할 말이 없습니다.
다음 장에서는 이러한 개념들이 총동원되면서 어떻게 다형성과 연결되는지에 대해서 알아 보도록 하겠습니다.
5장. Class for Polymorphism Java
다형성이라고는 하지만 자바에서 다형성을 대표하는 곳은 아주 많은 곳에 존재합니다. 다형성의 개념이 적용되는 곳을 따져 볼까요.
다형성이 적용되어지고 있는 곳
n Overloading의 사용은 하나의 메서드가 여러 가지의 기능을 가지고 있습니다.
n 상속은 한 번 만든 클래스를 다시 사용할 수 있습니다.
n Overriding은 상속의 관계에서 재사용한 것을 확장 할 수 있습니다.
n Upcasting은 아버지의 이름으로 아들을 참조할 수 있습니다.]
n abstract와 interface의 상속과 구현
다형성이란 글 뜻 그대로 다양한 형태의 성질입니다. 이러한 다양한 형태의 성질이 자바에서 한 두 가지입니까? 보이는 것이 전부 다형성을 지원하고 있는데. 물론 상속의 개념 속에서 나타나는 다형성이 제일 큰 부분을 차지합니다. 그 중에서도 Upcasting이라는 개념이 다형성을 대표한다고 이야기 합니다. 왜냐하면 Upcasting에서 다양한 프로그램을 할 수 있게 도와 주는 부분이 많기 때문일 것입니다.
Upcasting을 알고자 한다면 상속은 무조건적으로 알아야 되고 Overriding과 Upcasting은 직접적으로 관련 있으니 반드시 알아야 하며 abstract와 interface에서 나타나는 상속과 구현의 문제도 이 Upcasting과 직접 관련이 있습니다. 그리고 객체 할당이라는 측면도 고려해야 합니다. 알고 보면 Upcasting은 대부분의 객체 개념들을 모아둔 것 같은 느낌이 강합니다. 그래서 Upcasting이 자바의 다형성이라고 이야기들 하는 것 같습니다. Upcasting 자체는 거의 종합 예술입니다. 자바 프로그램을 하면서 종합예술은 채팅이겠지만 자바 개념에서 종합예술은 바로 Upcasting입니다.
우리는 상속 부분을 공부하면서 Upcasting에 대해서 “Upcasting은 이런거야!” 정도밖에는 언급하지 않았습니다. 물론 제대로 설명하려고 하는 사람도 없습니다. Upcasting이 도대체 어디에 좋은지도 모르고 그냥 그렇게 프로그램하면 된다고 생각해서 대충 프로그램을 하는 경우가 많습니다. 알고 있다면 다행이겠지만 모른다면 불행하겠죠. 그래서 아예 Upcasting자체를 분해 해 버릴까 합니다. 이 장에서 핵심적으로 다루고 있는 abstract, interface, Upcasting, Downcasting은 모두 다형성을 지원하기 위한 강력한 도구입니다. 이 들을 분석해 나가면서 자바에서 사용되는 다형성에 대한 느낌을 찾아 보겠습니다.
5.1 Abstract클래스
5.1.1 추상클래스의 개념
Abstract클래스는 빈 깡통 클래스입니다. abstract클래스는 구현이 덜 되었거나 또는 아직은 미완성 클래스이기 때문에 우리는 이 클래스를 추상 클래스라 부릅니다. 어디가 미완성일까요? 미완성 메서드를 포함하고 있기 때문에 클래스자체가 미완성이 되는 것입니다. 미완성 메서드는 어디가 미완성일까요? 메서드의 몸체가 없습니다. 몸체 없는 메서드, 이를 우리는 추상 메서드라 부릅니다. 그리고 이 추상 메서드를 단 하나라도 포함하고 있는 클래스를 추상 클래스라고 합니다.
5.1.2 추상클래스와 추상메서드
추상클래스는 미완성 클래스이기 때문에 약점이 있습니다. 완전한 클래스가 아니기 때문에 절대 객체를 생성하지 못합니다. 당연한 것 아니겠습니까? 완성되지 않은 클래스이니 객체를 만들 수 없다는 것은 이치에 합당합니다. 이 미완성 클래스를 어디에 사용할까요? 사용방법은 다음과 같습니다.(이해하기 편하게 추상클래스를 추상아버지클래스, 이를 상속 받는 클래스를 아들클래스라고 하겠습니다. )
n 추상아버지 클래스는 추상 메서드를 가지고 있습니다.
n 아들 클래스는 추상 아버지클래스로부터 상속 받습니다.
n 아들은 추상 아버지 클래스의 몸체 없는 메서드를 모두 메서드 재정의와 같이 다시 만듭니다. 이것을 우리는 구현한다고 말합니다. 몸체를 달아 주는 것입니다.
n 아들 클래스는 완성된 클래스이기 때문에 객체를 생성할 수 있습니다.
다음은 추상 메서드를 만드는 방법을 보여 주고 있는 예입니다.
추상 메서드를 만드는 방법
abstract class 클래스이름 {
abstract 메소드선언; // 메소드의 몸체는 없음
abstract 메소드선언; // 메소드의 몸체는 없음
}
예)
public abstract class AbstractTest {
public abstract void abstractMethod();
}
메서드에 몸체가 붙지 않는다면 여러분은 반드시 abstract키워드를 붙여야 합니다. 그리고 이 abstract메서드를 하나라도 포함하고 있다면 클래스명 앞에 abstract를 붙여야 합니다. 안 붙이면 언제나 그런 것처럼 친절하게 에러를 발생 시킵니다. 아래의 에러는 추상클래스내에 추상 메서드에 abstract를 붙이지 않았을 때 발생하는 에러입니다.
C:\examples\5. Class for Polymorphism Java>javac AbstractTest.java
AbstractTest.java:2: missing method body, or declare abstract
public void abstractMethod();
^
1 error
아주 간단하죠. 추상 클래스는 그 자체로는 객체를 생성할 수 없다는 것을 한번 더 밝혀 두며 실제 이용하는 가장 간단한 예를 보도록 하겠습니다.
NewCan.java(추상 메서드를 만드는 방법)
public class NewCan extends EmptyCan{
public void sound(){
System.out.println("EmptyCan:빈깡통은 소리가 요란하다");
}
public void who(){
System.out.println("EmptyCan:나는 빈깡통입니다.");
}
public void sayHello(){
System.out.println("NewCan:추상클래스 테스트입니다.");
}
public static void main(String args[]) {
NewCan ecm = new NewCan();
ecm.who();
ecm.sound();
ecm.sayHello();
}
}
Empty.java(추상클래스)
public abstract class EmptyCan{
public abstract void sound();//몸체없음
public abstract void who();//몸체 없음
}
C:\examples\5. Class for Polymorphism Java>javac NewCan.java
C:\examples\5. Class for Polymorphism Java>java NewCan
EmptyCan:나는 빈깡통입니다.
EmptyCan:빈깡통은 소리가 요란하다
NewCan:추상클래스 테스트입니다.
추상클래스는 객체가 가지는 특성들을 추상화시켜 놓았을 뿐, 아직 구체화 시키지 못한 클래스이므로, 이 추상클래스를 상속하는 하위클래스에서 좀 더 구체화 시키도록 하는 방법을 사용합니다. 주의 할 것은 추상 메서드를 하나도 빼지 말고 전부 구현 해야 객체를 생성할 수 있습니다. 아들이 상속을 받아서 추상 메서드를 하나라도 남겨 두었다면 아들도 추상클래스가 됩니다. 부전자전이죠. 다음은 상속을 했지만 추상 메서드를 전부 구현 하지 않았다면 abstract클래스가 되는 것을 증명하는 예제입니다.
CompleteCan.java(추상 메서드를 만드는 방법)
public class CompleteCan extends IncompleteCan{
public void who(){
System.out.println("EmptyCan:나는 빈깡통입니다.");
}
public void sayHello(){
System.out.println("NewCan:추상클래스 테스트입니다.");
}
public static void main(String args[]) {
CompleteCan cc = new CompleteCan();
cc.who();
cc.sound();
cc.sayHello();
}
}
Incomplete.java(추상클래스)
public abstract class IncompleteCan extends EmptyCan{
public void sound(){
System.out.println("EmptyCan:빈깡통은 소리가 요란하다");
}
}
Empty.java(추상클래스)
public abstract class EmptyCan{
public abstract void sound();//몸체없음
public abstract void who();//몸체 없음
}
C:\examples\5. Class for Polymorphism Java>javac EmptyCan.java
C:\examples\5. Class for Polymorphism Java>javac IncompleteCan.java
C:\examples\5. Class for Polymorphism Java>javac CompleteCan.java
C:\examples\5. Class for Polymorphism Java>java CompleteCan
EmptyCan:나는 빈깡통입니다.
EmptyCan:빈깡통은 소리가 요란하다
NewCan:추상클래스 테스트입니다.
추상메소드는 추상클래스와 마찬가지로 아직 구현이 이루어지지 않고 단지 그 메서드의 이름만 가지고 있다는 뜻입니다. 그래서 몸체가 없다고 말하고 있는 것입니다. 이것을 역으로 말한다면 추상메서드가 되려면 몸체가 없어야 한다라는 명제를 얻을 수 있습니다. 보통 우리가 전문적인 용어로 이야기 할 때 추상메서드는 메서드의 프로토타입만 가지고 있다라고 이야기 합니다.
5.1.3 클래스의 구조를 디자인하기 위한 추상 클래스
추상클래스는 클래스의 구조를 잡기 위한 방법으로 사용됩니다. 하나의 클래스를 만든다고 가정한다면 하나의 클래스에 모든 것을 전부 넣을 수는 없습니다. 이러한 방법은 별로 좋지 않은 방법이죠. 클래스가 크다고 가정한다면 작업별로 클래스를 쪼개게 됩니다. 작업을 분할 하는 것은 수평적인 개념에서 비슷한 작업끼리 묶는 것입니다. 유유상종(類類相從)이라는 말이 적당할 것 같군요. 비슷한 작업끼리 묶어서 작업별 클래스를 만드는 것입니다.
저 또한 초보시절의 자바 프로그램은 비슷한 작업을 묶는 것으로 시작했습니다. 하지만 수평적으로 묶는 것 조차도 작업의 설계와 분석을 해야만 이루어지는 아주 어려운 일입니다. 이 단계를 거치고 나면 작업을 레벨단위로 묶는 것을 생각하게 됩니다. 거의 1년 이상 걸리더군요. 제가 머리가 나빠서 그런지도 모르겠지만 개인적으로는 상당히 어려운 일이었습니다. 레벨 단위의 작업으로 발전하기까지는 상당히 많은 시간과 생각이 필요한 것 같습니다. 작업을 단계별로 만들어 주는 역할을 하는 것이 바로 추상 클래스의 역할입니다.
레벨 단위의 구조를 살펴 보도록 하죠.
위의 그림은 도형을 그리기 위한 클래스를 디자인 하고 있습니다. 모든 클래스에서 공통되는 작업을 상위레벨에 놓고 그 클래스를 상속 받아 각각의 다른 클래스로 분류되어지고 있습니다. 상위레벨에 존재하는 작업은 구현할 필요성이 없다고 생각하여 abstract으로 만듭니다. 즉, 하위레벨에서 구현하겠다는 것입니다. 이렇게 하였을 때의 코드를 직접 만들어 보겠습니다. 물론 Shape클래스는 abstract으로 만들어질 것이며 그 하위의 Circle, Triangle, Rectangle은 모두 Shape의 abstract메서드를 구현 할 것입니다. 구현은 대충 아래와 같이 되어 질 것입니다.
FigureTest.java(abstract의 레벨기법을 테스트하기 위한 예제)
abstract class Shape {
public abstract void draw();
public abstract void delete();
}
class Circle extends Shape {
public void draw(){
System.out.println("원을 그립니다");
}
public void delete(){
System.out.println("원모양을 지웁니다");
}
}
class Triangle extends Shape {
public void draw(){
System.out.println("삼각형을 하나, 둘, 셋, 그립니다.");
}
public void delete(){
System.out.println("삼각형을 지웁니다");
}
}
class Rectangle extends Shape {
public void draw(){
System.out.println("사각형을 원, 투, 쓰리, 포 그립니다.");
}
public void delete(){
System.out.println("사각형을 지웁니다");
}
}
//abstract을 테스트하기 위한 클래스
public class FigureTest{
public static void main(String[] args){
Circle c = new Circle();
Triangle t = new Triangle();
Rectangle r = new Rectangle();
c.draw();
t.draw();
r.draw();
c.delete();
t.delete();
r.delete();
}
}
C:\examples\5. Class for Polymorphism Java>javac FigureTest.java
C:\examples\5. Class for Polymorphism Java>java FigureTest
원을 그립니다
삼각형을 하나, 둘, 셋, 그립니다.
사각형을 원, 투, 쓰리, 포 그립니다.
원모양을 지웁니다
삼각형을 지웁니다
상속과 abstract을 설명하기 위해서 어디서나 흔히 볼 수 있는 예제입니다. 이 간단한 예제가 무엇을 의미하는지 제대로 안다면 자바의 강력한 힘을 발휘할 수 있습니다. 클래스의 수평적인 작업 분할과 수직적인 작업 분할을 동시에 이용한다면 여러분은 아주 효율적인 코드를 작성할 수 있습니다. 재사용성 100%와 유지, 보수, 관리 능력까지 갖춘 잘 설계된 프로그램을 하실 수 있을 것입니다.
5.1.4 결론
추상클래스에 대하여 정리 해 보죠.
n 추상 메서드는 몸체 없는 프로토타입만을 가진 메서드입니다.
n 추상 메서드는 반드시 메서드이름 앞에 abstract 키워드를 명시해야 합니다.
n 추상 메서드를 단 하나라도 포함하고 있으면 이를 추상 클래스라고 합니다.
n 추상 클래스는 클래스이름 앞에 abstract를 명시해야 합니다.
n 상속을 이용하여 추상 메서드를 모두 구현한 뒤, 객체를 생성할 수 있습니다.
일반적인 특징은 이러하지만 추상클래스의 뒷면에 존재하는 느낌은 수직적인 작업의 분할이라는 아주 무서운 개념이 숨어 있습니다. 프로그램을 하면서 어떻게 계층적으로 프로그램을 할지를 결정하지말고 펜을 들고 작업을 분석하는 것이 옳을 것입니다. 그리고 작업 분석이 끝났다면 수직과 수평의 개념을 적용 시켜서 어느 정도 설계를 하는 것이 옳을 것입니다. 하지만, 기본적인 배경 없이는 아무것도 할 수 없으니 지금은 느낌을 얻으시기 바랍니다.
여러분이 클래스를 만들고 시간이 지나서 다시 여러분이 만든 클래스를 볼 기회 있을 것입니다. 절대 같은 디자인의 클래스는 만들 수 없습니다. 물론, 클래스를 만드는 기법은 비슷하지만 같은 디자인으로는 프로그램을 하려고 해도 잘 되지 않습니다. 약간의 시간이 지난 후, 과거와 현재를 비교해 보면 여러분 스스로 진화했다는 말을 사용해도 될 만큼 아주 많은 발전을 이룬 것을 볼 날이 있을 것입니다.
5.2 Interface
5.2.1 인터페이스란?
인터페이스란 영어입니다. 따로 번역할 말이 없습니다. 말 그대로 인터페이스입니다. 이 말을 이해하시는 분은 인터페이스의 허탈함을 느끼신 분이라 생각됩니다. 자바관련책에서는 인터페이스를 어떻게 만들며 어떻게 사용하는지 구구절절 설명하고 있습니다. 보통 다음과 같이 설명하고 있습니다.
인터페이스의 정의
n 인터페이스란 추상 클래스의 일종이다.
n 인터페이스 내의 메서드는 모두 추상 메서드다.
n 인터페이스 내의 멤버는 모두 static final이다.
n implements를 사용해서 모든 추상 메서드를 구현해야 한다.
n 단일 상속의 한계를 극복하고 있다.
아시는 분은 아시겠지만 이러한 정의가 바로 인터페이스입니다. 인터페이스를 설명한 곳은 수 없이 많습니다. 하지만 인터페이스에 대해서 제대로 이해 시켜준 책은 없었습니다. 조금만 더 친절하게 설명해 주었더라면 그렇게 오랜 시간을 이 인터페이스 때문에 헤매지 않았을 것을!
여하튼 인터페이스는 아주 골치 아프지만 인터페이스 없이는 고차원적인 자바의 개념은 없다고 보아도 과언이 아닙니다. 인터페이스의 본질적인 의미가 변질되어 인터페이스의 다른 면만을 보는 경우가 많습니다. 일반적으로 여러분이 직접 인터페이스를 만들어 사용하는 곳은 다음과 같습니다.(직접 인터페이스를 새로 만드는 것이 아니라 만들어진 규칙에 의해서만 인터페이스를 사용합니다.)
여러분이 직접 인터페이스를 직접 사용하는 곳
n 이벤트의 처리
n Runnable인터페이스를 이용한 스레드 생성
아마도 여러분이 자바를 배우면서 인터페이스를 가장 많이 접한 곳일 것입니다. 보통 이것을 설명할 때 이렇게 하면 된다라고 배웁니다. 그리고는 인터페이스는 implements해서 사용하면 된다라고 배울 뿐 더 이상은 언급하지 않는 것이 사실입니다. 하지만 자바에서 인터페이스는 엄청나게 많습니다. 특히 데이터베이스 쪽으로 넘어간다면 인터페이스가 더 많습니다. 물론 그 만한 이유가 있습니다만 우리는 그것이 인터페이스인줄도 모르고 사용하고 있습니다. 아래는 데이터베이스의 인터페이스의 트리 구조를 보여주고 있는 구조도 입니다. 한번이라도 데이터베이스를 해 보신 분은 아! 이럴수가라는 감탄사를 표시할 만큼 많습니다.
데이터베이스에서 사용되는 인터페이스
interface java.sql.Array
interface java.sql.Blob
interface java.sql.Clob
interface java.sql.Connection
interface java.sql.DatabaseMetaData
interface java.sql.Driver
interface java.sql.Ref
interface java.sql.ResultSet
interface java.sql.ResultSetMetaData
interface java.sql.SQLData
interface java.sql.SQLInput
interface java.sql.SQLOutput
interface java.sql.Statement
interface java.sql.PreparedStatement
interface java.sql.CallableStatement
interface java.sql.Struct
자바에서 사용자 측면에서 사용하는 클래스의 수는 전체 1732개 그리고 인터페이스의 수는 422개입니다. 이 정도 된다면 반드시 인터페이스에 대해서 알아야 하지 않을까요? 직접 세어 보니 422개더군요. 어떻게 세어 보았는지는 비밀입니다. 물론 신뢰성은 있습니다.
인터페이스는 인터페이스입니다. 데이터베이스에 인터페이스가 많은 이유 또한 인터페이스의 근본적인 개념 때문입니다. 일단 이 절에서는 일반적인 인터페이스를 설명하는 관점에서 인터페이스를 설명한 후 원래의 인터페이스 사용에 관해 알아 보겠습니다. 그리고 더 자세한 사항은 앞으로 나올 절에서 상세하게 설명하도록 하겠습니다.
5.2.2 일반적인 인터페이스의 구조
인터페이스는 public abstract멤버메서드와 static final 멤버 변수로 되어 있습니다. 물론, 멤버라는 측면에서는 클래스와 같습니다. 하지만, 앞에 다른 키워드가 붙는다는 것은 많은 뜻을 내포하고 있습니다. 자바에서 말하는 interface의 구조를 다음과 같이 정리 할 수 있습니다.
n 모든 메서드는 묵시적으로 public abstract이다.
n 모든 데이터는 묵시적으로 static final이다.
n abstract메서드를 포함하고 있기 때문에 객체를 생성할 수 없다.
n extends를 이용하여 상속을 하는 것이 아니라 implements를 이용하여 구현한다.
묵시적이라는 말은 앞에 public abstract를 붙이지 않아도 무조건 public abstract이며 public abstract이어야 한다는 것입니다. public abstract가 붙는다는 것은 모든 멤버메서드는 반드시 추상 메서드라는 것입니다. 즉, 메서드의 몸체가 없다는 말입니다. abstract의 의미는 쉽습니다. 하지만 멤버필드가 static final이라는 것은 좀 생각을 해 보아야 합니다. 그 느낌은 interface는 메서드로만 이루어져 있다고 말해도 상관없다는 말로 표현할 수 있습니다. 분명 2개의 구성요소가 있는데 하나가 없는 것이라고 말해도 된다니 이상하지 않습니까? 차근 차근 따지고 본다면 static이란 객체와 메모리 생성과 전혀 상관 없는 전역적인 static메모리공간에 자리를 잡게 됩니다. 그리고 final이라는 것은 그 메모리에 들어 있는 데이터는 final이기 때문에 한번 정해지면 결코 바꿀 수 없다는 의미를 내포하고 있습니다. 그렇기 때문에 static final은 프로그램의 구조와 별 상관 없는 것처럼 생각되어 질 수 있습니다.
대표적인 static final의 예를 들어 보겠습니다. 한번 생각해 보십시오. 어떤 예가 있을까요? 보통 우리들이 많이 사용하는 static final멤버필드 중에 이런 것이 있습니다.
static final의 예
Math클래스의 멤버필드 - public static final double PI
Color클래스의 멤버필드 - public static final Color black
사용할 때
Math.PI
Color.black
위의 표에서 나타나는 static final멤버필드는 누구나 다 알 수 있는 값들이며 바뀔 수 없는 값들입니다. public static이니 클래스의 이름으로 접근 가능할 것이며 final이니까 완벽한 상수입니다. 잘 생각해 보시기 바랍니다. PI라는 static final은 상수입니다. Math클래스에 있는 이유는 딱히 static final은 Math와 전혀 별개의 것처럼 작동합니다. 이것은 static의 원리 때문입니다. 그렇다면 PI가 다른 클래스 내에 있어도 별반 차이가 없다는 뜻이 아닐까요? 그렇다면 왜 Math클래스 내에 만들었을까요? 답은 우습게도 Math라는 것이 수에 관련된 클래스이고 PI라는 static final변수를 딱히 놓아 둘 곳도 없으니 Math에 놓아 둔 곳이죠.
앞에서 언급한 내용 중에서 interface는 “public abstract이다. 그리고 메서드로만 이루어져 있다”라고 정의를 내린 이유에 대해서 명확한 증거를 제시해 보았습니다. 그렇다면 interface를 한번 만들어 보겠습니다. 아직, implements에 대해서 배우지 않았지만 직접 눈으로 보시면 implements가 어떻게 사용되는지 알 수 있을 것입니다. extends와 별 차이가 없지만 인터페이스의 모든 메서드를 구현하겠다는 것이 implements의 의지이니 이것만 기억하시면 됩니다. 아래의 예는 인터페이스를 이용하는 방법을 보여주고 있습니다.
BodySign.java(interface를 테스트하기 위한 예제)
public interface BodySign{
public static final int STRAIGHT = 1;
public static final int CURVE = 2;
public static final int DOWN = 1;
public static final int UP = 2;
public static final int LEFT = 3;
public static final int RIGHT = 4;
public void countFinger(int how_ball);
public void directionBall(int how_direct);
}
Catcher.java(interface를 테스트하기 위한 예제)
public class Catcher implements BodySign{
public void countFinger(int how_ball){
if(how_ball == BodySign.STRAIGHT){
System.out.println("직구를 던집니다");
}
else if(how_ball == BodySign.CURVE){
System.out.println("변화구를 던집니다");
}
}
public void directionBall(int how_direct){
if(how_direct == BodySign.UP){
System.out.println("상향구를 던집니다.");
}
else if(how_direct == BodySign.DOWN){
System.out.println("하향구를 던집니다.");
}
else if(how_direct == BodySign.LEFT){
System.out.println("좌향구를 던집니다.");
}
else if(how_direct == BodySign.RIGHT){
System.out.println("우향구를 던집니다.");
}
}
public static void main(String[] args){
Catcher c = new Catcher();
c.directionBall(3);//제1구. 좌향구를 던집니다.
c.directionBall(BodySign.LEFT);//제2구. 다시 좌향구
c.countFinger(1);//제3구. 직구를 던집니다.
c.countFinger(BodySign.CURVE);//제4구. 변화구를 던집니다.
}
}
위의 예는 아주 단순한 interface의 사용법을 보여주고 있습니다. interface 내부에 public static final 변수를 6개 만들고 public abstract메서드 2개를 만들어 주었습니다. abstract키워드를 붙이지 않아도 자동으로 abstract메서드가 됩니다. 왜 안 붙였을까요? 넌센스 같은 문제인데요. 모든 메서드가 abstract이면 귀찮아서 붙이지 않은 것은 아닐까요!! 뭐 믿거나 말거나 입니다.
final의 사용법은 익히 알고 있지만 static final인 것에 주의하시기 바랍니다. 그리고 interface는 implements를 이용해서 모든 메서드를 구현하지 않으면 에러가 발생한다는 것도 잊으시면 안됩니다. 이로써 우리는 인터페이스에서 제일 간단한 예를 만들어 보았습니다. 하지만 인터페이스는 이것이 전부는 아닙니다. 일단 인터페이스의 메모리 구조를 살펴보고 그리고 근본적인 의미의 인터페이스에 대해서 알아보도록 하겠습니다.
5.2.3 인터페이스의 메모리와 다중상속
인터페이스를 보통 다중상속을 변칙적으로 지원한다라고 되어 있습니다. 보통 다중상속이라고 그냥 밀어 붙여 버리면 힘들지 않겠습니까? 다중상속이란 2개의 클래스를 동시에 상속 받는 것을 말합니다. 하지만 자바에서 다중상속을 지원하지 않는 이유는 무엇일까요? 그 이유를 그림으로 한번 그려 보겠습니다.
다중상속을 사용했을 때 문제점을 정확하게 보여주고 있는 그림입니다. 아들A와 아들 B 둘 다 아버지 F로부터 상속 받고 난 후 다시 아들A와 아들B를 중복상속 했을 때 발생하는 문제점은 아버지F가 둘 생긴다는 것이다. 아버지F를 둘 다 상속 받기 때문에 손자입장에서는 아버지F가 둘을 가지고 있는 것이다. 이렇게 되면 손자가 아버지F를 사용할 때 아들A의 아버지F인지 아들B에 아버지F인지 모호함이 발생합니다. C++에서는 영역지정자를 이용해서 이것을 해결 하고 있지만 상속관계가 더 복잡해지고 상속을 거듭하게 되면서 오히려 더 큰 문제점을 발생시키게 되자 자바에서는 아예 중복 상속을 금지하고 있습니다. 자바는 기본적으로 단일 상속만 가능합니다.
하지만 아버지가 다를 때 사용한다면 어느 정도 효율성이 있습니다. 한꺼번에 두 가지 기능을 모두 상속하는 것은 아주 막강한 힘입니다. 아버지가 다르다면 말이죠. 이것을 어느 정도 활용하고 있는 것이 바로 interface입니다. 일단 모든 클래스는 Object클래스로부터 상속을 받습니다. 디폴트로 Object클래스라는 것으로부터 우리가 모르는 사이에 자동으로 상속받습니다. 여러분이 extends를 하지 않았다고 하더라도 extends Object라는 것이 붙어 있는 것과 마찬가지 입니다. 그리고 interface로부터 상속 받았다면 어떻게 변할까요?
BodySign.java(interface를 테스트하기 위한 예제)
public interface BodySign{
public static final int STRAIGHT = 1;
public static final int CURVE = 2;
public static final int DOWN = 1;
public static final int UP = 2;
public static final int LEFT = 3;
public static final int RIGHT = 4;
public void countFinger(int how_ball);
public void directionBall(int how_direct);
}
Catcher.java(interface를 테스트하기 위한 예제)
public class Catcher extends Object implements BodySign{
public void countFinger(int how_ball){
//….중략
}
public void directionBall(int how_direct){
//….중략
}
public static void main(String[] args){
Catcher c = new Catcher();
c.directionBall(3);//제1구. 좌향구를 던집니다.
c.directionBall(BodySign.LEFT);//제2구. 다시 좌향구
c.countFinger(1);//제3구. 직구를 던집니다.
c.countFinger(BodySign.CURVE);//제4구. 변화구를 던집니다.
}
}
앞에서 나온 예제에 extends Object만을 붙이면 같은 결과가 나옵니다. 이것은 모든 클래스가 Object클래스로부터 상속 받는다는 증거입니다. 그리고 우리는앞에서 interface와 abstract class, class 모두 사촌간이라는 말을 했습니다. abstract class는 당연히 class라고 했으니 클래스구요. interface는 abstract클래스이지만 모든 메서드가 abstract으로 되어있는 축약된 형태의 클래스입니다. 그렇다면 Cather같은 경우에 BodySingn 인터페이스와 Object클래스 둘을 상속 받고 있다고 생각해도 됩니다.
위의 그림에서와 같이 Catcher는 중복상속의 효과를 보고 있습니다. 인터페이스도 클래스의 한 종류이기 때문에 Object클래스와 인터페이스의 중복상속 개념이 적용되어 지고 있으며 interface자체는 아버지의 중복상속의 모호성을 어느 정도 해결하고 있기도 합니다. 그리고 extends는 단 하나 밖에 할 수 없지만 interface는 여러 개를 implements해서 사용할 수 있기 때문에 다중 상속의 개념이 필요할 경우 interface를 많이 사용하고 있습니다. 여기서 아주 단순히 인터페이스도 클래스의 한 종류라고 했지만 이 의미는 인터페이스로 Upcasting을 할 수 있다는 의미가 됩니다. 앞에서 배운 abstract와 interface는 둘 다 클래스의 개념을 가지고 있기 때문에 Upcasting이 된다는 것은 아주 많은 개념을 포함하고 있습니다. interface가 진정한 interface가 되는 것은 바로 interface가 클래스의 종류이며 Upcasting이 가능하다는 것입니다. 이 부분은 Upcasting 부분에서 자세하게 살펴 보도록 하겠습니다. 지금까지 배운 것은 인터페이스를 사용하는 가장 쉬운 방법입니다. 다음으로 interface 그 본질적인 의미에서의 사용법을 알아 보도록 하겠습니다.
5.2.4 근본적인 인터페이스
클래스를 이용하여 객체를 생성했을 때 외부와 상호작용을 하는 것은 public 메서드입니다. 이 상호작용이라는 단어를 영어로 바꾼다면 인터페이스입니다. “클래스에서 인터페이스의 역할을 하는 것은 public으로 정의된 메서드들이다.”라고 바꾸어 말할 수 있습니다. 즉 클래스에서 public메서드는 인터페이스라는 말이 통합니다. 여러분이 다른 사람에게 신호를 보냈는데 잘 못 알아들으면 인터페이스가 통하지 않는다고 이야기하지 않습니까! 그때의 인터페이스가 이러한 인터페이스가 자바에서 추구하는 interface입니다.
보통 자바의 인터페이스에 대하여 설명하고 있지만 구체적으로 어떻게 이용하고 있는지는 설명하고 있지 않습니다. 인터페이스의 대표적인 예는 데이터베이스 드라이브에서 볼 수 있습니다. 데이터베이스는 여러 회사에서 개발하고 있지만 이를 통합하기는 힘듭니다. 각 회사의 제품과 내부동작은 다르게 되고 있지만 여러분이 데이터 베이스에 접근하는 것은 드라이브만 있다면 어떠한 데이터베이스에도 똑 같은 인터페이스로 접근할 수 있습니다. 이것은 sun사에서는 이러한 방법론을 인터페이스라는 것을 이용해서 해결하고 있기 때문입니다. Micro Sun사에서는 데이터베이스의 표준 인터페이스를 정의해 두고 이 인터페이스에 따라서 각 회사에서 자신에 맞는 데이터베이스 드라이브를 개발 하도록 하는 것입니다. 이렇게 된다면 모든 DB회사들이 자바 JDBC의 인터페이스에 맞추어 드라이브를 개발하게 되고 여러분은 똑 같은 인터페이스를 사용하게 되는 것입니다.
이 인터페이스를 직접 디자인 하여 만들어 보도록 하겠습니다. 일단 스토리는 다음과 같습니다. TV를 만드는 두 회사가 있습니다. 이 회사들이 TV를 만들지만 반드시 TV를 켜거나 채널을 바꾸거나 볼륨을 조절하는 인터페이스는 같아야 한다는 법을 제정하겠습니다. 그렇다면 먼저 TV의 인터페이스를 법으로 제정해 두어야겠죠. TV의 인터페이스는 다음과 같습니다.
TVBoard.java(interface를 테스트하기 위한 예제)
public interface TVBoard{
public void channelDown();
public void channelUp();
public void volumeDown();
public void volumeUp();
public void powerOnOff(boolean power);
}
이러한 인터페이스를 기본적으로 제공하지 않으면 TV를 판매 못하게 하죠. SSg와 LGg회사에서 TV를 만든다고 가정하죠. 먼저 SSg회사에서 TV를 자체적으로 만들 것입니다. 일단은 법은 지켜야 겠죠. 사용자들의 편리성을 위해서 인터페이스를 통일시키겠다는 법이니까요. 그래서 SSg는 TVBoard 인터페이스를 구현해야 합니다.
SSgTV.java(interface를 테스트하기 위한 예제)
public class SSgTV extends Object implements TVBoard{
private String name="SSg TV";
private int volume = 5;
private int channel = 7;
private boolean power = false;
public SSgTV(){
System.out.println("SSg TV가 만들어 졌습니다");
}
public void channelDown(){
this.channel -=1;
System.out.println(this.name + "-채널Down");
}
public void channelUp(){
this.channel +=1;
System.out.println(this.name + "-채널Up");
}
public void volumeDown(){
this.volume -=1;
System.out.println(this.name + "-볼륨Down");
}
public void volumeUp(){
this.volume +=1;
System.out.println(this.name + "-볼륨Up");
}
public void powerOnOff(boolean power){
this.power = power;
if(this.power==false){
System.out.println(this.name + "-전원Off");
}else{
System.out.println(this.name + "-전원On");
}
}
public void SleepTimer(int time){
System.out.println(time + "알람설정후에 자동으로 종료 됩니다.");
//기다리는 작업
this.powerOnOff(false);
}
}
LGg라는 회사에서도 기본적으로 TVBoard인터페이스는 구현한 후 자신들의 기능을 넣을 수 있습니다. 법에 의하면! LGg회사의 제품을 한 번 보도록 하겠습니다.
LGgTV.java(interface를 테스트하기 위한 예제)
public class LGgTV extends Object implements TVBoard{
private String name="LGg TV";
private int volume = 5;
private int channel = 3;
private boolean power = false;
public LGgTV(){
System.out.println("LGg TV가 만들어 졌습니다");
}
public void channelDown(){
this.channel -=1;
System.out.println(this.name + "-채널Down");
}
public void channelUp(){
this.channel +=1;
System.out.println(this.name + "-채널Up");
}
public void volumeDown(){
this.volume -=1;
System.out.println(this.name + "-볼륨Down");
}
public void volumeUp(){
this.volume +=1;
System.out.println(this.name + "-볼륨Up");
}
public void powerOnOff(boolean power){
this.power = power;
if(this.power==false){
System.out.println(this.name + "-전원Off");
}else{
System.out.println(this.name + "-전원On");
}
}
public void AlarmTimer(int time){
System.out.println(time + "알람설정후에 자동으로 켜집니다.");
//기다리는 작업
this.powerOnOff(true);
}
}
두 회사 제품을 비교해 보십시오. 모두다 TV Board를 구현하고 있습니다. 하지만 각 회사의 자체적인 기능도 가지고 있습니다. 자 일단 TV를 만들고 테스트를 해 보아야 겠죠. 잘 작동하는지 확인 해 보도록 하겠습니다.
TVTestMain.java(interface를 테스트하기 위한 예제)
public class TVTestMain{
public static void main(String[] args){
TVBoard s = new SSgTV();
s.powerOnOff(true);//TV를 켭니다.
s.channelUp();//채널을 올립니다.
s.volumeUp();
s.volumeUp();
s.volumeUp();
s.volumeUp();//볼륨을 올립니다.
s.powerOnOff(false);
System.out.println();
TVBoard g = new LGgTV();
g.powerOnOff(true);//TV를 켭니다.
g.channelUp();//채널을 올립니다.
g.channelUp();
g.channelUp();
g.channelUp();
g.volumeDown();
g.volumeDown();
g.volumeDown();
g.powerOnOff(false);
}
}
TV들이 아주 작 작동하는 군요. 하지만 사용자는 인터페이스를 통해서 조작하고 있습니다. 물론 TV를 만들어지만 TV내부는 각 회사에서 알아서 하겠죠. 하지만 TV의 인터페이스가 같기 때문에 어느 회사의 제품이나 조작하기는 편할 것입니다. 데이터베이스의 드라이브도 이러한 방식으로 Sun사에서 Database의 interface를 정의해두고 그 interface에 의해서 만들도록 하는 것입니다.
TVBoard s = new SSgTV();
TVBoard g = new LGgTV();
이 두 줄을 유심히 봐 주시기 바랍니다. 일반적인 Upcasting의 개념이 그대로 interface에서도 적용되고 있습니다. 이것은 인터페이스 자체도 클래스이기 때문에 Upcasting을 적용할 수 있는 것입니다. 사용자 측면에서 인터페이스가 고정되어 있으면 많은 시간과 비용을 절약할 수 있을 것입니다. 모든 TV가 같은 인터페이스로 되어 있다면 하나의 조작법만 안다면 모든 TV를 조작 할 수 있을 것입니다. 이것이 바로 인터페이스의 본질적인 역할입니다. 말 그대로 인터페이스는 인터페이스입니다.
5.2.5 결론
지금까지 우리는 인터페이스의 일반적인 의미와 근본적인 의미를 살펴보았습니다. 알고 보면 인터페이스의 일반적의 의미와 근본적인 의미가 비슷하지만 약간 느낌상의 차이는 상당히 큽니다. 여러분이 인터페이스를 다룰 때 두 개념을 접목 시켜서 인터페이스를 공부하신다면 훨씬 빠르게 고급 자바로 접근 하실 수 있을 것입니다.
여기서 인터페이스를 끝내기에는 인터페이스라는 놈은 너무나도 덩치가 큰 놈입니다. 인터페이스를 이용한 Upcasting에 관한 간략한 설명만 나왔을 뿐 반 밖에 오지 못한 것 같군요. 인터페이스는 Upcasting, Reflection, RMI, Thread등에서 상당히 많은 부분에서 많은 활약을 하고 있습니다. 현재의 절에서는 인터페이스의 의미상의 차이만 논했을 뿐 인터페이스의 제대로 된 활용은 소개도 하지 않았습니다. abstract와 interface의 간단한 비교 후, Upcasting에서 소개되는 인터페이스의 활용법에 대해서 좀더 자세하게 알아 보도록 하겠습니다. 물론 계속되는 장들에서 많은 부분 interface에 관련 된 부분들이 나올 것입니다. 그 때 그때마다 interface의 느낌을 찾으시기 바랍니다.
5.3 Abstract와 Interface의 비교
abstract와 interface는 비슷한 점이 많습니다. interface 자체가 abstract클래스의 의미를 담고 있기 때문에 interface는 abstract메서드의 특징을 그대로 가지고 있습니다. 하지만 interface는 작업의 레벨 분할을 위해서 사용되기 보다는 오히려 공동작업을 위한 인터페이스를 위해서 사용됩니다. 알고 보면 interface가 더 큰 범위를 가지고 있다는 느낌이 들 것입니다. 물론 둘 다 클래스의 설계를 할 때 사용되어지지만 설계의 쓰임새가 다릅니다.
단순히 abstract는 추상메서드를 단 하나라도 포함한다면 추상 클래스가 되고 interface는 모든 메서드가 추상 메서드로 이루어져 있다라고 생각한다면 엄청난 오류를 범하게 됩니다. 다음을 차근 차근 따져 보시기 바랍니다.
abstract
n abstract클래스는 클래스이다.
n 추상메서드와 일반 메서드 일반 멤버메서드를 가질 수 있다.
n 상속을 위해서 extends를 사용한다.
n 오직 단일적으로 extends를 사용한다.
n 모든 추상 메서드는 구현하여야 사용할 수 있다.
n 작업의 레벨 분할을 위해서 사용된다.
n upcasting이 가능하다.
interface
n 인터페이스도 클래스의 일종이다. 특히 추상클래스의 일종이다.
n 추상메서드와 static final멤버필드만 가질 수 있다.
n 구현을 위해서 implements를 사용한다.
n 여러 개를 중복하여 implements할 수 있다.
n 모든 추상 메서드를 구현하여야 사용할 수 있다.
n 공동작업을 위한 상호간의 인터페이스를 위해 사용된다.
n upcasting이 가능하다.
abstract와 interface에 대해서 알아보면서 대부분의 사항은 알아 보았습니다. 하지만 아직 abstract와 interface의 upcasting의 활용이라는 측면은 알아 보지 않았습니다. 이 절에서는 단순히 abstract와 interface를 한번 비교 해 보는 것을 끝내겠습니다. 그리고 다음절에서 진정한 의미의 upcasting에 관해서 알아 보도록 하겠습니다.
5.4 Upcasting
5.4.1 upcasting이란?
upcasting이라는 상위클래스로 casting하는 것을 의미합니다. 상위클래스로 casting할 때 우리는 단순히 상위 클래스로 casting되는구나! 이렇게만 생각하고 그 깊은 의미는 따지지 않습니다. 이 절에서는 일반적인 upcasting의 의미와 실제 upcasting이 적용되는 부분을 조사해 보도록 하겠습니다.
5.4.2 upcasting의 일반적인 구현
우선, upcasting의 단순한 개념부터 파악해보도록 하겠습니다. 아래의 그림은 upcasting의 개념을 그대로 그림으로 나타낸 것입니다.
클래스에서 상속 받아 새로운 클래스를 만들고 있습니다. Circle과 Triangle 그리고 Rectangle은 모두 Shape로 Upcasting이 가능합니다. 위의 그림을 있는 그대로 프로그램으로 옮겨 보겠습니다. 아래의 프로그램은 Upcasting을 테스트하기 위한 예제입니다.
UpcastingClassTest.java(interface를 테스트하기 위한 예제)
class Shape {
public void draw(){
System.out.println("모양을 그립니다");
}
public void delete(){
System.out.println("모양을 지웁니다");
}
}
class Circle extends Shape {
public void draw(){
System.out.println("원을 그립니다");
}
public void delete(){
System.out.println("원모양을 지웁니다");
}
public void sayCircle(){
System.out.println("안녕하세요 원입니다");
}
}
class Triangle extends Shape {
public void draw(){
System.out.println("삼각형을 하나, 둘, 셋, 그립니다.");
}
public void delete(){
System.out.println("삼각형을 지웁니다");
}
public void sayTriangle(){
System.out.println("안녕하세요 삼각형입니다");
}
}
class Rectangle extends Shape {
public void draw(){
System.out.println("사각형을 원, 투, 쓰리, 포 그립니다.");
}
public void delete(){
System.out.println("사각형을 지웁니다");
}
public void sayRect(){
System.out.println("안녕하세요 사각형입니다");
}
}
//abstract을 테스트하기 위한 클래스
public class UpcastingClassTest{
public static void main(String[] args){
Shape s = new Shape();
s.draw();
s.delete();
System.out.println();
//클래스의 기본적인 사용법
Circle c = new Circle();
c.draw();
c.delete();
c.sayCircle();
System.out.println();
Triangle t = new Triangle();
t.draw();
t.delete();
t.sayTriangle();
System.out.println();
Rectangle r = new Rectangle();
r.draw();
r.delete();
r.sayRect();
System.out.println();
//Upcasting의 사용
Shape c1 = new Circle();
c1.draw();
c1.delete();
//c1.sayCircle(); 에러가 발생합니다.
System.out.println();
Shape t1 = new Triangle();
t1.draw();
t1.delete();
//t1.sayTriangle(); 에러가 발생합니다.
System.out.println();
Shape r1 = new Rectangle();
r1.draw();
r1.delete();
//r1.sayRect(); 에러가 발생합니다.
}
}
여러분은 당연히 다음과 같은 객체생성이 가능하다고 생각합니다.
n Circle c = new Circle();
n Triangle t = new Triangle();
n Rectangle r = new Rectangle();
upcasting을 이용하면 다음과 같은 객체 생성도 가능합니다.
n Shape c1 = new Circle();
n Shape t1 = new Triangle();
n Shape r1 = new Rectangle();
그런데 위의 코드에서 upcasting을 이용하는 부분에서 이상한 점을 발견할 수 있습니다.
Shape c1, t1, r1은 모두 Shape클래스의 객체변수입니다. 하지만 아버지의 메모리를 생성하여 할당하였습니다. 이 때 c1, t1, r1이 재정의된 메서드를 호출했을 때 누구의 메서드를 호출 하느냐는 것입니다. 분명 c1, t1, r1은 모두 Shape형입니다. 그렇다면 재정의 되었다 하더라도 c1, t1, r1은 모두 Shape 내의 멤버메서드를 호출 하는 것이 맞습니다. 이것의 구조를 아래의 그림과 같습니다.
일반적인 관점에서 본다면, c1 자체의 크기는 Shape형이기 때문에 실제로 생성된 메모리에서 핸들할 수 있는 부분은 Shape의 draw와 delete입니다. 만일 재정의 된 메서드가 Circle에 존재한다면 Circle의 draw와 delete를 사용합니다. 없다면 Shape의 것을 이용하지만 있다면 아들의 것을 이용합니다. 이러한 원리를 다시 그림으로 나타내 보도록 하겠습니다.
위의 그림은 Shape c1 = new Circle()이라고 객체를 생성한 후 c1을 이용하여 c1.draw()를 호출 했을 때의 수행 순서입니다. 먼저 c1은 자신의 draw()쪽으로 넘어갑니다. 그리고 재정의 된 메서드가 있는지 없는지 확인 합니다. 그리고 아들이 재정의 된 메서드를 가지고 있다면 아들의 draw를 호출 하게 되는 것입니다. 이것을 우리는 upcasting이라 부르는 것입니다. 좁은 방향으로 casting된다고 하여 narrow casting이라고 부릅니다. 이것은 C++에서는 Virtual Function으로 소개 되고 있는 부분이지만 자바에서는 디폴트로 이 개념을 이용하고 있습니다. 원래 C++에서는 일반 메서드와 가상메서드를 구분하기 위해서 virtual이라는 키워드를 재공하지만 자바에서는 이것을 재정의냐 아니냐로 구분 할 수 있기 때문에 그냥 이용하는 것 같습니다. 개인적으로 생각하기에는, C++뒤에 자바가 나왔기 때문에 이러한 불편을 내부로 숨겨 버리지 않았나 생각합니다.
이 upcasting의 구조에서 유의해야 할 것이 하나 있습니다. 소스에서 본다면 에러가 난 부분이 있습니다. 에러가 나는 이유는 재정의 된 메서드가 아니라는 것입니다. 재정의 되었다면 찾아 갈 수 있지만 재정의 되지 않았다면 찾아 갈 수 없습니다. 그 이유를 그림으로 본다면 다음과 같을 것입니다.
아들이 메서드를 재정의 했을 때만 Shape c1은 아들의 것을 찾아 갈 수 있습니다. 이 말은 Shape에 있어야 만이 그 메서드를 사용할 수 있다는 말입니다. 즉, c1은 Shape형이라는 것입니다. 아주 단순한 말이지만 upcasting때문에 형 그 차제를 망각하면 안됩니다.
5.4.3 Abstract와 Interface에서의 Upcasting
우리는 upcasting을 일반적인 클래스 구조에서만 살펴 보았습니다. 하지만 이것은 abstract와 interface에서도 적용 되어 질 수 있습니다. 그 구조는 다음과 같을 것입니다.
그런데 다음과 같은 질문을 할 수 있을 것입니다.
n abstract는 객체를 생성할 수 없다.
n interface는 객체를 생성할 수 없다.
이러한 말에 부딪히게 됩니다. abstract와 interface는 객체를 생성하는 것이 아니라 만들어진 객체를 할당 받는 것입니다. 형은 같기 때문에 이러한 것이 가능합니다. abstract나 interface로는 new연산자를 사용하지 않습니다. 단지 만들어진 객체를 upcasting을 이용해서 넘겨 받을 뿐입니다. 이러한 것은 많은 장점을 내포하고 있습니다. 알고 보면 interface는 빈 껍데기에 불과 합니다.
사용자는 용량이 적은 빈 껍데기만 가지고 있어도 모든 작업을 전부 수행 할 수 있습니다. 우의 그림에서는 설명을 위해서 임의의 interface만들었습니다. 이 인터페이스를 이용해서 우리는 다음과 같은 코드를 작성 할 수 있을 것입니다.
n Shape s = new Circle();
n Action a = new Circle();
n Attribute ab = new Circle();
n Circle c = new Circle();
이 코드의 장점은 사용자는 내부를 몰라도 된다는 것입니다. interface만 있다면 어떠한 작업이라도 전부 할 수 있습니다. 이러한 장점은 우리가 직접 만들어서 작업하기 보다는 내부적으로 이루어지는 경우가 아주 많습니다. 하지만 이것을 잘 이용하면 보다 정교한 프로그램작업을 할 수 있으리라 생각됩니다.
5.4.4 결론
여러분들이 잘 모르고 사용하는 Upcasting은 아주 많은 장점을 제공하고 있습니다. 자바도 여러 종류이지만 고급 자바로 갈수록 이 Upcasting개념은 더욱 철저하게 적용됩니다. RMI, Enterprise Java Beans, Mobile Java, Personal Java, Java Dynamic Management Kit등 사용 안 되는 곳이 없습니다. 어차피 자바를 배우면 실무에서 다루지 않겠습니까?
다형성을 주장하는 Upcasting의 장점은 abstract, interface, encapsulation, overriding 등 아주 많은 부분과 관련이 있습니다. 이 upcasting과 interface에 대해서 제대로 이해했다면 자바는 바로 여러분들의 손에 있는 것입니다.
그런데, 지금까지는 상위로 casting하는 것을 배웠습니다. 상위캐스팅이 있는데 하위 캐스팅은 없겠습니까? 다음으로 하위캐스팅을 하는 다운 캐스팅에 대해서 배워 보겠습니다.
5.5 Upcasting과 Downcasting
5.5.1 다운 캐스팅
다운 캐스팅이란 upcasting한 것을 다시 복구 시켜주는 작업을 합니다. 의외로 Downcasting은 쉽게 설명되어 질 수 있습니다. 하지만 downcasting을 할 때 정확한 형으로 다운 캐스팅을 하지 않는다면 무조건 적으로 에러를 발생시킵니다. 자바 가상 머신은 캐스팅의 문제를 가상머신의 차원에서 점검을 하고 있기 때문에 아주 신중하게 캐스팅을 하여야 합니다.
5.5.2 Upcasting과 Downcasting의 실제 사용
업캐스팅과 다운 캐스팅을 가장 잘 설명 해 줄 수 있는 예제를 한번 골라 보았습니다. 이 예제만큼이나 캐스팅을 잘 설명해 줄 수 있는 것은 없는 것 같습니다. 아주 주의 깊게 살펴 보시기 바랍니다.
UpDownCasting.java
import java.util.Vector;
public class UpDownCasting{
public static void main(String[] args){
String name = new String("홍길동");
Integer id = new Integer(1000);
Vector v = new Vector();
v.addElement(name);
v.addElement(id);
Object obj1 = v.elementAt(0);
Object obj2 = v.elementAt(1);
String str = (String)obj1;
Integer num = (Integer)obj2;
System.out.println("이름:" + str+ " 번호:" + num);
String str2 = (String)v.elementAt(0);
Integer num2 = (Integer)v.elementAt(1);
System.out.println("이름:" + str2+ " 번호:" + num2);
}
}
C:\examples\5. Class for Polymorphism Java>javac UpDownCasting.java
C:\examples\5. Class for Polymorphism Java>java UpDownCasting
이름:홍길동 번호:1000
이 예제에서 우리는 Vector를 사용하기 위해서 먼저 java.util.Vector클래스를 import하고 있습니다. 그리고 Vector를 만들었습니다. Vector는 객체를 잠깐 담을 수 있는 저장소 역할을 하는 아주 단순한 놈입니다. 이 Vector에 객체를 넣고 빼는 것이 이 프로그램에서 설명하려고 하는 부분입니다. 우선 Vector에서 사용된 멤버메서드를 살펴보기로 하겠습니다.
n public Object elementAt(int index)
n Returns the component at the specified index.
n public void addElement(Object obj)
n Adds the specified component to the end of this vector, increasing its size by one.
이 메서드들을 잘 관찰해 보면 addElement는 Object형을 매개변수로 넣어 준비 주게 됩니다. 그리고 elementAt메서드는 Object형을 리턴하게 됩니다. 하지만 우리는 실제 프로그램에서 String객체와 Integer객체를 넣었습니다. 어떻게 된 것일까요? 모든 클래스는 Object클래스를 무조건 적으로 상속 받기 때문에 Object형으로 업캐스팅이 가능합니다. 그래서 실제 Vector가 가지고 있는 것은 Object형입니다. 그리고 밖으로 내 보낼때도 Object형으로 리턴하고 있는 것입니다.
위의 그림은 프로그램에서 벡터에 데이터가 삽입되고 그리고 데이터를 가져오는 구조를 보여 주고 있습니다. addElement에 객체를 입력하는 순간 업캐스팅이 다음과 같이 일어 나고 있습니다.
n Object obj = name; //스트링형의 객체를 Object형의 객체로 업캐스팅
n Object obj = id; //Integer형의 객체를 Object형의 객체로 업캐스팅
이렇게 매개변수에 들어가는 순간에 업캐스팅을 하는 것을 자주 볼 수 있을 것입니다. 그리고 입력한 객체들을 얻어 내면 입력되는 순간 모두 Object형으로 업캐스팅 되었기 때문에 모두 Object형입니다. 그래서 다음과 같은 구문이 가능한 것입니다.
n Object obj1 = v.elementAt(0);
n Object obj2 = v.elementAt(1);
그리고 이 obj1과 obj2를 강제적으로 다운 캐스팅을 합니다. 위의 프로그램에서 다운 캐스팅하는 구문은 다음과 같습니다.
n String str = (String)obj1;
n Integer num = (Integer)obj2;
벡터에서 객체를 얻어 내고 그리고 다운 캐스팅을 동시에 한다면 다음과 같은 구문으로 바꾸어 사용할 수 도 있습니다.
n String str2 = (String)v.elementAt(0);
n Integer num2 = (Integer)v.elementAt(1);
보통 이러한 방법은 가장 일반적인 구문으로 알고 있지만 우리는 업캐스팅과 다운 캐스팅을 생각하지 않고 그냥 사용하는 경우가 많습니다. 캐스팅의 개념을 이해하시고 이것을 사용하신다면 더욱 효과적으로 객체를 다룰 수 있을 것입니다.
5.5.3 결론
우리는 이 절에서 단순히 업캐스팅과 다운 캐스팅이라는 관점에서 객체를 할당하고 객체를 얻어내는 것을 알아 보았습니다. 업캐스팅과 다운 캐스팅을 이해하기 위해서는 상속과 인터페이스, 추상클래스, 메서드 재정의 등 많은 개념들이 필요합니다. 너무 많은 부분들이 겹쳐 있기 때문에 이것을 설명하기는 아주 까다롭습니다만 앞장들의 개념들을 가지고 있다면 여러분들은 쉽게 이해 하실 수 있을 것입니다.
5.6 마무리
이제 여러분은 다형성이 자바의 전부구나 하는 말을 이해 하리라 생각됩니다. 다형성은 구석구석 연결 안된 부분이 없습니다. 그 중에서 가장 관련이 많다고 생각되는 것을 모아서 어느 정도 설명을 해 보았지만 그래도 다형성의 느낌을 설명하지 않은 부분이 많습니다. 이것은 자바를 직접 프로그래밍하면서 여러분 스스로 느낄 수 있는 부분이지 말로는 설명이 불가능한 것 같습니다. 물론, 개인적으로 더 많은 느낌을 전달하고자 하겠지만 여러분도 다형성의 느낌을 찾아 보시기 바랍니다.