Chapter | 5 |
class와 object |
이 장을 시작하기 전에 2장을 한번 더 읽어보는 것도 좋다. 자바는 기본 자료형을 제외한 거의 모든 것이 class와 객체로 구성되어 있다. 객체 지향 프로그래밍이란 무엇일까? 객체 지향 개념이 나오기 전에는 절차형 프로그래밍 방법이 사용되었다. 절차형 프로그래밍이란 시작부터 어떤 순서, 또는 단계를 거쳐 원하는 결과를 얻는 프로그래밍 방법이다. 다음 그림은 절차형 프로그래밍을 표현한 것이다.
[그림 5-1] 절차형 프로그래밍
절차형 프로그래밍은 코드의 수정과 재사용이 어렵고, 현실 세계의 문제를 프로그래밍으로 표현하는 데에는 적합하지 않다는 문제를 갖고 있는데, 객체 지향 프로그래밍은 절차형 프로그래밍의 그러한 단점을 보완하기 위해 도입되었다.
객체 지향 프로그래밍은 객체들간의 메시지 교환에 의해 프로그램이 실행되도록 하는 프로그래밍 기법이다. 객체는 때가 되면 탄생하여 자기 본분을 다하고 죽는다(소멸한다). 보다 완전한 객체 지향이 되기 위해서는 이 객체들의 관계가 분명해야 하고 객체간의 메시지 처리가 효율적이고 안정적이어야 한다. 초보자는 무슨 말인지 잘 모를 것이다. 하지만 걱정할 필요 없다. 프로그램을 자꾸 짜다보면 자연스레 이해될 것이기 때문이다. 다음 그림은 객체 지향 프로그래밍을 표현한 것이다.
[그림 5-2] 객체 지향 프로그래밍
클래스 정의와 객체 만들기
객체는 속성과 동작을 포함한다. 속성을 멤버 변수, 동작을 멤버 메소드라고 부른다. 변수와 메소드를 가지고 있는 객체를 만들기 위해서는 틀이 필요한데, 그 틀이 클래스라는 것도 알고 있다. 여기서는 클래스와 객체를 코드로 구현해 보자.
객체는 클래스로부터 만들어지기 때문에 객체를 만들기 전에 클래스부터 정의해야한다. 예를 들어 사람(Man)클래스를 만든다고 생각하자. Man 클래스는 어떤 종류의 변수와 메소드를 가지고 있을까? 변수만 보더라도 무한히 많다. 그런 이유로 실제 사물을 완전하게 묘사하는 객체를 만드는 것은 어쩌면 불가능한 것이다. 따라서 프로그램이 요구하는 목적을 충족시키는 것들을 선택하는 단순화 작업이 필요하다. 필자는 사람의 변수와 메소드를 아래와 같이 정했다.
멤버 변수(속성) : 키(height), 나이(age) 멤버 메소드(동작) : 먹다(eat), 자다(sleep) |
필자가 정한 Man클래스는 너무나도 단순해서 사람이라고 보기 힘들 정도이다. 아무튼 Man클래스의 변수와 메소드를 정했으니 이제 자바 코드로 옮겨 보자.
클래스의 정의의 기본은 다음과 같다.
class Man{ 변수와 메소드 정의 } |
클래스의 이름은 Man이고 실제 정의 부분은 블록({}) 안에 한다. 블록 안에 변수와 메소드들을 적어주면 된다.
멤버 메소드를 만드는 작업은 약간 복잡하므로 멤버 변수만 선언하고, 나중에 멤버 메소드를 정의하도록 하자.
class Man{ // Man클래스 정의 시작 int height; // 키 int age; // 나이 } // Man클래스 정의 끝 |
키와 나이는 정수이므로 int로 했다.
약간 부실하지만 Man 클래스를 정의했다. 이제 Man클래스로부터 객체를 만들어 보자. 객체를 만들기 위해서는 일련의 절차를 그쳐야 한다. 일단은 객체를 대표하는 객체의 이름을 먼저 만들어야 한다. 아래는 객체의 이름을 man1이라고 정했다.
Man man1; |
여기서 man1은 객체가 아니다. 객체의 이름일 뿐이다. 무슨 소리인가? 여성이 아기를 낳기 전에 아기의 이름을 짓는 것과 비슷하다. 이름은 있지만 아직 아기는 태어나지 않은 상태이다. 아기를 객체로 생각하면 객체는 없지만 객체의 이름을 만든 것이다.
객체의 이름을 만들었으니 실제로 객체를 탄생시켜야 한다. 앞으로 '탄생'이라는 용어 대신에 '생성'이라는 용어를 쓰자. 객체를 생성하는 방법은 아래와 같다.
man1 = new Man(); |
여기서 new는 '새로운' 이라는 뜻인데 '새로운 Man의 객체를 만들어라'라고 해석하면 된다. Man()은 '생성자 메소드' 또는 간단히 '생성자(Constructor)'라고 한다. 아기가 탄생하기 위해선 남녀간의 어떤 동작이 필요하듯이 객체가 생성되기 위해서도 동작이 필요한데, 이 필요한 동작이 생성자이다. 나중에 생성자를 자세히 다룰 것이다.
객체를 만드는 또 다른 방법은 객체의 이름을 만듦과 동시에 객체를 생성하는 것이다. 아기가 태어날 때 이름을 짓는 것과 같다.
Man man1 = new Man(); |
위의 방법은 기본자료형 변수를 만들고 초기화하는 방법과 비슷해 보인다.
int a = 10; |
객체가 생성되었으니 객체를 활용해보자. man1이 가지고 있는 것은 키와 나이이다.
man1의 키에 180을 대입하고 나이에는 20을 대입해보자.
man1.height=180; man1.age=20; |
멤버 변수를 나타낼 때는 도트(.)를 사용한다. 이제 아래의 예제를 해보자.
Class1.java |
|
classMan{ // Man클래스 정의
int height;
int age;
}
public classClass1{ // Class1클래스 정의
public static void main(String[] args){ // 맨 처음 실행
Man man1; // 객체의 이름 man1 만듦
man1=new Man(); // 실제로 객체를 생성
man1.height=180; // man1의 키를 180으로 변경
man1.age=20; // man1의 나이를 20으로 변경
System.out.println(man1.height); // man1의 키를 출력
System.out.println(man1.age); // man1의 나이를 출력
}
}
출력 결과 |
|
180
20
하나의 파일 안에서 public 클래스는 하나만 존재할 수 있으며 public 클래스의 이름은 파일의 이름과 동일해야 한다. 그리고 main 메소드를 포함한 클래스만이 public클래스가 될 수 있다. 위 예제에서 public 클래스는 Class1이고 파일이름과 같다. 그리고 Class1은 main을 포함하고 있다.
man1과 같은 객체의 이름을 다른 말로 '레퍼런스(Reference)' 또는 '참조변수'라고 한다. 왜 객체의 이름을 레퍼런스라고 할까? 아래 그림은 객체와 객체이름의 관계를 나타낸 것이다. 객체와 객체의 이름은 각기 다른 공간(메모리)에 존재한다. 객체의 이름을 이용하여 객체에 접근할 수 있다. 즉, man1을 통해서 객체에 접근할 수 있다. 'man1.height=180'은 man1을 통하여 객체의 멤버변수 height에 180을 대입하라는 의미이다. 이 때 "man1은 객체를 참조한다"라고 표현한다. 화살표는 man1이 객체를 참조하고 있음을 뜻한다.
[그림 5-3] 레퍼런스와 객체
레퍼런스 대입
앞으로 객체이름을 레퍼런스라고 부르자. 레퍼런스는 객체를 대표하기 때문에 '객체'라고 부르기도 한다. 아래 코드를 보면 x3행에서 man2에 man1을 대입하고 있다. man2는 어떤 객체를 참조할까?
Man man1=new Man(); // x1 Man man2; // x2 man2=man1; // x3, 레퍼런스 대입 |
x3행처럼 레퍼런스(man2)에 레퍼런스(man1)를 대입하면 man2는 man1이 참조하고 있는 객체를 참조하게 된다. 즉, man1과 man2는 같은 객체를 참조한다. 아래 그림처럼 말이다.
[그림 5-4] 레퍼런스 대입
하나의 객체에 이름이 둘이다. 따라서 man1을 이용하여 객체에 접근할 수 있고, man2를 이용하여 객체에 접근할 수도 있다. 예제를 해보자.
Class2.java |
|
classMan{
int height;
int age;
}
public classClass2{
public static void main(String[] args){
Man man1=new Man();
Man man2;
man2=man1; // man2는 man1의 객체를 참조한다.
man1.height=180;
man1.age=20;
System.out.println(man2.height); // man2의 키를 출력한다.
System.out.println(man2.age); // man2의 나이를 출력한다.
}
}
출력 결과 |
|
180
20
멤버 메소드
메소드(Method)는 다른 말로 함수(Function)라고도 하는데 학교에서 배운 함수와 비슷하다. 이해를 돕기 위해 학교에서 배운 함수를 살펴보자.
f(x) = 2.5+x : 함수의 정의 |
x를 매개 변수라고 한다. 함수 f를 사용해보자.
y=f(3) : 함수의 실행 |
'f(x)=2.5+x'와 같이 정의된 함수를 사용하는 것을 '함수의 실행' 또는 '함수의 호출(Call)'이라고 한다. 매개변수 x에 3이 대입되고 2.5+3(=5.5)이 y에 대입된다. 함수를 실행하면 생기는 값을 '함수값', '리턴값(Rutrun Value)', 또는 '반환값'이라고 한다. f(3)의 리턴값은 5.5이고 f(2)의 리턴값은 4.5이다.
자바에서 메소드 만드는 규칙은 다음과 같다.
리턴형 메소드명(매개변수){ 메소드 정의; } |
리턴형(Return Type)이란 리턴값의 자료형을 말한다. 'f(x)=2.5+x'에서 리턴형, 메소드명, 매개변수, 메소드 정의를 구분해보자. 매개변수(x)의 자료형은 정수(int)로 정하고 리턴형은 리턴값이 실수(double)이므로 double로 정하자.
리턴형: double 메소드명: f 매개변수: int x 메소드 정의: return 2.5+x; |
메소드를 실행했을 때 생기는 값이 a라면, 즉 리턴값이 a라면 메소드 내에서 'return a;'라는 명령을 적어준다. 'f(x) = 2.5+x'의 리턴값은 '2.5+x'이므로 return 2.5+x; 라고 해야 한다. 이제 'f(x)=2.5+x'를 자바 메소드로 정의해 보자.
double f(int x){ // 메소드 f의 정의 시작 return 2.5+x; } // 메소드 f의 정의 끝 |
메소드는 헤더(header)와 바디(body)로 구성된다.
헤더: double f(int x) 바디: { return 2.5+x; } |
아래 예제는 멤버 메소드 정의 및 호출 방법을 보여준다.
Class3.java |
|
public classClass3{
double f(int x){ // x1
return 2.5+x; // x2
}
public static void main(String[] args){
Class3 aaa=new Class3(); // x3
double y=aaa.f(3); // x4
System.out.println(y); // x5
System.out.println(aaa.f(2)); // x6
}
}
출력 결과 |
|
5.5
4.5
Class3 클래스의 멤버는 메소드 f와 main뿐이다. x3행을 자세히 보면 자신의 클래스 안에서 자신의 객체를 만들고 있다. 이것은 main 메소드가 static이기 때문에 가능하다. 나중에 static을 배울 때 자세히 하도록 하고 static메소드에서는 자신의 객체를 만들 수 있다는 것만 알아두자. x4행에서 aaa의 멤버 메소드 f(3)를 호출하고 있다. 멤버 메소드를 호출할 때도 도트(.)를 사용한다. 이제 실행 순서가 x1행으로 가서 매개변수 x에 3이 대입되고 5.5를 가지고 메소드를 호출한 곳으로 돌아(return)온다. 즉, x4행으로 5.5가 반환된다. 따라서 x4행의 y에 5.5가 대입된다. x6행에서는 'aaa.f(2)'를 호출하는데, x1행의 x에 2가 대입되고, 4.5를 가지고 x6행으로 리턴한다.
알아두기 |
|
Return return은 '되돌아가다'의 뜻이다. return a; -> a를 가지고 되돌아가라. return b; -> b를 가지고 되돌아가라. 되돌아가는 곳은 자기를 호출한 곳이다. |
다음 그림은 메소드가 호출되고 반환하는 과정을 나타낸 것이다.
[그림 5-5] 메소드의 호출(Call)과 반환(Return) 과정
void Return
main 메소드의 리턴형은 무엇인가?. 바로 앞에 있는 void가 main의 리턴형이다. 그렇다면 void는 도대체 무엇일까? void는 '빈', '아무 것도 없는'을 뜻하므로 '리턴값이 비어있다', 또는 '리턴값이 없다'라고 해석하면 된다.
void printInt(int x){ // x1 System.out.println("정수= "+x); // x2 } // x3 |
메소드 printInt는 리턴형이 void이다. 따라서 리턴값이 없다. 리턴값이 없으므로 아래와 같이 호출할 수 없다.
y=객체.printInt(10); // 틀렸음 |
printInt 메소드를 실행한 후, 값을 안 가지고 돌아오기 때문에 y에 대입될 수 없는 것이다. 따라서 다음과 같이 호출해야 한다.
객체.printInt(10); |
위와 같이 호출하면 x1행의 매개변수 x에 10이 대입되고 x2행이 실행된다. 그 다음 x3을 만나서 메소드가 끝난다. 메소드가 끝나면 자동적으로 return되는데 리턴값 없이 되돌아간다. 되돌아갈 곳은 메소드를 호출한 곳이다. main 메소드도 리턴형이 void인데 리턴값이 없는 method이다.
Class4.java |
|
public classClass4{
void printInt(int x){
System.out.println("정수= "+x);
}
public static void main(String[] args){
Class4 aaa=new Class4();
aaa.printInt(10);
System.out.println("안녕~~");
aaa.printInt(20);
}
}
출력 결과 |
|
정수= 10
안녕~~
정수= 20
리턴형이 void라도 'return'을 사용할 수 있다. 중간에 함수의 실행을 마치고 리턴할 필요가 있을 때 사용된다.
return; |
Class5.java |
|
public classClass5{
void print자연수(int x){
if(x<=0) return; // x가 음수이면 리턴
System.out.println("자연수= "+x);
}
public static void main(String[] args){
Class5 aaa=new Class5();
aaa.print자연수(10);
aaa.print자연수(-10);
}
}
출력 결과 |
|
자연수= 10
void 매개변수
void 매개변수란 매개변수가 비어있는, 즉, 매개변수가 없는 메소드를 말한다.
void hello(){ System.out.println("안녕하세요?"); } |
hello 메소드는 매개변수가 없으므로, 호출할 때 ()안을 비워두어야 한다. 그리고 리턴형도 void이므로 아래와 같이 호출한다.
객체.hello(); |
Class6.java |
|
public classClass6{
void hello(){
System.out.println("안녕하세요?");
}
public static void main(String[] args){
Class6 aaa=new Class6();
aaa.hello();
aaa.hello();
}
}
출력 결과 |
|
안녕하세요?
안녕하세요?
복수 매개변수
매개변수는 여러 개 존재할 수도 있다. 매개변수와 매개변수는 콤마(,)를 찍어 구분한다.
int sum(int a, int b){ return a+b; } |
위와 같이 메소드를 만들고 아래와 같이 호출하면 'a=10', 'b=20'이 되고 30을 리턴한다. 따라서 x에 30이 대입된다.
x=객체.sum(10, 20); |
다음 average메소드는 정수 3개를 받아서 평균을 리턴하는 메소드이다.
int average(int a, int b, int c){ return (a+b+c)/3; } |
아래와 같이 호출하면 'a=10', 'b=20', 'c=30'이 되고 20을 리턴한다.
따라서 x에 20이 대입된다.
x=객체.average(10, 20, 30); |
Class7.java |
|
public classClass7{
int sum(int a, int b){
return a+b;
}
int average(int a, int b, int c){
return (a+b+c)/3;
}
public static void main(String[] args){
Class7 aaa=new Class7();
int 합계=aaa.sum(10,20);
int 평균=aaa.average(10,20,30);
System.out.println("합계= "+합계);
System.out.println("평균= "+평균);
}
}
출력 결과 |
|
합계= 30
평균= 20
혼자 해보기 | Alone5_1.java |
두 정수를 받아서 가장 큰 수를 반환하는 메소드를 만들고 호출해보자.
메소드의 헤더: int max(int x, int y)
출력 결과 예시 |
|
max(10, 20) => 20
혼자 해보기 | Alone5_2.java |
세 실수를 받아서 가장 큰 수를 반환하는 메소드를 만들고 호출해보자.
메소드의 헤더: double max(double x, double y, double z)
출력 결과 예시 |
|
max(10.2, 50.5, 30.2) => 50.5
혼자 해보기 | Alone5_3.java |
정수 n를 받아서 '★'을 n크기의 삼각형 모양으로 출력하는 메소드를 만들고 호출해보자.
메소드의 헤더: void star(int n)
출력 결과 예시 |
|
n=7
★
★★
★★★
★★★★
★★★★★
★★★★★★
★★★★★★★
지역 변수(Local Variable)와 멤버 변수
지역 변수란 메소드 안에서 만들어진 변수를 말하며 메소드 안에서만 사용된다. 지역 변수는 메소드를 호출하면 생겨서 메소드가 끝나면 소멸한다. 매개 변수도 지역 변수이다.
int ten(inta){ intx=10; returna*x; } |
a와 x가 지역 변수이다. a와 x는 ten메소드 안에서만 사용할 수 있다. ten메소드를 호출하지 않으면 a와 x는 존재하지 않으며 ten메소드를 호출하면 a와 x가 생긴다. 그리고 ten메소드가 끝나면 즉 return하면 a와 x는 소멸한다. 그리고 지역변수는 값을 얻어오기 전에 꼭 초기화되어 있어야 한다. 초기화란 변수를 만들고 값을 처음으로 대입하는 것을 말한다.
int a=10; // 초기화되었다. int b; // 초기화되지 않았다. String s1=new String("abc"); // 초기화되었다. String s2; // 초기화되지 않았다. |
다음 예제의 출력 결과는 무엇일까?
Class8.java |
|
public classClass8{
public static void main(String[] args){
int a;
System.out.println(a);
}
}
출력 결과 |
|
C:\...\Class8.java:4:variable a might not have been initialized
System.out.println(a);
^
1 error
변수 a는 main메소드 안에서 만들어졌으므로 지역 변수이다. 그런데 a를 초기화하지 않고 값을 출력하고 있다. 이 예제를 컴파일 하면 출력 결과와 같이 변수가 초기화되지 않았다는 에러 메시지가 출력된다.
멤버 변수를 초기화하지 않으면 기본 자료형 변수에는 0이 레퍼런스 변수에는 null이 대입된다.
Class9.java |
|
public classClass9{
int a;
int b=100;
String c;
String d="하이~~~~";
public static void main(String[] args){
Class9 ob= new Class9();
System.out.println(ob.a);
System.out.println(ob.b);
System.out.println(ob.c);
System.out.println(ob.d);
}
}
출력 결과 |
|
0
100
null
하이~~~~
null의 의미는 사전적으로 '없는, 존재하지 않는'의 뜻으로 레퍼런스에 객체를 대입하지 않으면 레퍼런스에 null이 대입된다. null은 참조하는 객체가 없음을 표시하는 하나의 상수이다. 어떤 레퍼런스가 참조하는 객체를 가지고 있는가? 알아보려면 다음과 같이 하면 된다.
if( 레퍼런스==null){ 객체가 없다. } else 객체가 있다. |
this 레퍼런스
this 레퍼런스란 객체 자신을 참조하는 레퍼런스이다. 무슨 소리?
아래 코드에서 x1행의 a와 b는 어떤 변수를 말하는 것일까?
class Hi{ int a; void setA(int b){ a=b; //x1 } int getA(){ return a; } } |
x1행에서 a는 멤버 변수이고 b는 매개 변수인데 컴파일러는 어떤 식으로 구분할까? 사실 컴파일러는 위의 Hi클래스를 아래와 같이 수정한다.
class Hi{ int a; void setA(int b){ this.a=b; } int getA(){ returnthis.a; } } |
this.a는 객체 자신의 멤버 변수 a를 말하고 this가 붙지 않은 b는 지역 변수를 말한다. 따라서 setA 메소드는 매개 변수 b에 값을 받아서 멤버 변수 a에 대입하는 역할을 한다.
setA 메소드를 아래와 같이 수정해 보자.
void setA(int a){ this.a=a; } |
this.a는 멤버 변수 a를 나타내고 this가 붙지 않은 a는 지역 변수를 가리키게 된다. 지역 변수의 이름이 멤버 변수의 이름과 같을 경우, 변수 앞에 this가 없으면 지역 변수이고 변수 앞에 this가 있으면 멤버 변수이다.
Class10.java |
|
public classClass10{
int a;
void setA(int a){
this.a=a;
}
int getA(){
returna; // 여기서 a는 this.a를 말한다.
}
public static void main(String[] args){
Class10 ob= new Class10();
ob.setA(1000);
System.out.println(ob.a);
System.out.println(ob.getA());
}
}
출력 결과 |
|
1000
1000
멤버 메소드에서 멤버 메소드를 호출할 수 있다.
Class11.java |
|
public classClass11{
void hi(){
System.out.println("Hi~~~~");
hello(); // x1
}
void hello(){
System.out.println("Hello~~");
}
public static void main(String[] args){
Class11 ob= new Class11();
ob.hi();
}
}
출력 결과 |
|
Hi~~~~
Hello~~
x1행을 보면 hi에서 hello를 호출하고 있다. 멤버 메소드를 호출할 때는 '객체.hello()'라고 해야 한다고 앞에서 배웠다. 그러나 컴파일러는 x1행을 아래와 같이 번역한다.
this.hello(); // 객체 자신의 hello를 호출하라. |
혼자 해보기 | Alone5_4.java |
세 정수를 받아서 합을 구하는 메소드 sum을 만들고, 세 정수를 받아서 평균을 구하는 메소드 average를 만들어라. 단, average 메소드는 sum 메소드를 호출하여야 한다.
sum 메소드의 헤더: int sum(int x, int y, int z)
average 메소드의 헤더: double average(int x, int y, int z)
출력 결과 예시 |
|
average(60, 85, 90) => 78.3
static 변수와 static 메소드
static 멤버 변수는 객체를 만들지 않고도 사용할 수 있는 변수이자 모든 객체가 공유하는 변수이다. 일반 멤버 변수(non-static)는 객체를 만들어야만 사용할 수 있는데 반해 static 변수는 객체를 만들지 않고도 사용할 수 있다.
static 변수는 아래와 같이 선언한다.
static int a; |
static 변수를 사용하려면 아래와 같이 한다.
클래스.a=10; 객체.a=20; |
다음 예제를 해보자. static 변수는 모든 객체가 공유하는 변수이고 객체를 만들지 않고도 사용할 수 있는 변수임을 확인하자.
Class12.java |
|
public classClass12{
static int a; // static 변수
int b; // 멤버 변수
public static void main(String[] args){
Class12.a=10; // x1
Class12 ob1= new Class12();
Class12 ob2= new Class12();
System.out.println(ob1.a);
ob1.a=20;
System.out.println(ob2.a);
ob2.a=30;
System.out.println(Class12.a);
}
}
출력 결과 |
|
10
20
30
Class12.a, ob1.a, ob2.a는 모두 같은 변수이다. static 변수는 x1행과 같이 사용할 수 있으므로 '클래스 변수'라고도 한다.
static 메소드는 객체를 만들지 않고도 호출할 수 있다. main도 static 메소드이다. main은 프로그램이 실행되면 JVM이 호출하는 메소드이다. 그런데 객체를 만들지 않고 호출하기 때문에 main 메소드도 static 메소드이다. main 메소드의 베일이 조금씩 벗겨지고 있다.
static 메소드를 정의하려면 아래와 같이 메소드의 리턴형 앞에 static을 삽입한다.
staticint return123(){ return 123; } |
static 메소드를 호출하려면 아래와 같이 한다. x와 y는 리턴값을 받기 위한 변수이지 절대적으로 필요한 변수는 아니다.
int x=클래스.return123(); int y=객체.return123(); |
Class13.java |
|
public classClass13{
static int return123(){
return 123;
}
public static void main(String[] args){
System.out.println(Class13.return123());
Class13 ob= new Class13();
System.out.println(ob.return123());
}
}
출력 결과 |
|
123
123
멤버와 static 메소드의 관계
멤버 변수나 멤버 메소드는 객체가 있어야 호출이 가능하고 static 메소드는 객체 없이도 호출할 수 있다. 그렇다면 다음 예제의 결과는 무엇일까?
Class14.java |
|
public classClass14{
int a;
static void setA(int b){
a=b; // x1
}
public static void main(String[] args){
Class14.setA(10);
}
}
출력 결과 |
|
C:\...\Class14.java:4:
non-static variable a cannot be referenced from a static context
a=b;
^
1 error
에러 메시지를 해석해보면 static이 아닌 변수는 static 메소드에서 참조될 수 없다는 것이다. a는 멤버(non-static)변수이므로 객체를 만들어야만 참조될 수 있다. 따라서 객체가 없어도 호출되는 static 메소드에서는 참조될 수 없다. 따라서 x1행에서 a의 사용은 잘못된 것이다.
그러나 static변수나 static메소드는 어떤 메소드에서도 사용되어질 수 있다.
Class15.java |
|
public classClass15{
static int a; // static 변수
static void hello(){ // static 메소드
System.out.println("안뇽~~");
}
static void setA(int b){ // static 메소드에서 static 메소드 hello 호출
a=b; // static 변수 a
hello(); // static 메소드
}
void printA(){ // 일반 메소드(non-static)에서 static 메소드 hello 호출
System.out.println(a); // static 변수 a
hello(); // static 메소드
}
public static void main(String[] args){
Class15.setA(100); // x1
Class15 ob=new Class15();
ob.printA();
}
}
출력 결과 |
|
안뇽~~
100
안뇽~~
main메소드도 static이면서 class15의 메소드이므로 x1행에서 클래스 이름을 생략할 수 있다.
Class15.setA(100); --> setA(100); |
package와 import
package의 사전적 의미는 '다발', '꾸러미', '묶음'의 뜻이다. 자바에서 패키지의 의미는 '클래스 꾸러미', '클래스 묶음' 이라는 뜻이다. 말 그대로 클래스들을 묶어 놓았다. 자바에서 지원하는 클래스는 그 수가 상당히 많아서 기능별로 클래스들을 구분하여 묶어 놓았는데 그것이 바로 패키지이다. 패키지의 개념은 디렉토리(directory)의 개념과 비슷하다.
SRC문서가 있는 디렉토리를 열어보자. 여러 디렉토리가 보일 것이다. 그 중에서 java 디렉토리를 열어보자. java 디렉토리 안에 있는 lang 디렉토리를 열어보자. 두 개의 디렉토리와 수많은 파일들이 보일 것이다. 이 파일들은 확장자가 '*.java'인데, 자바에서 지원하는 클래스들의 소스파일이다. 독자가 이미 알고 있는 자바 클래스는 String클래스와 System클래스인데, 이 두 클래스의 소스파일이 이 디렉토리에 존재한다. 이들 파일은 자바 소스 파일이므로 메모장이나 다른 텍스트 에디터로 불러올 수 있다.
'System.java' 파일의 경로는 SRC문서 디렉토리로부터 'java\lang\System.java'이다. 이를 패키지 표기로 바꾸면 'java.lang.System'이 된다. '\'대신에 '.'을 사용하는 것으로 이를 도메인(Domain)식 표기라고 한다. 여기서 java와 lang이 패키지이다.
나중에 배울 애플릿(Applet) 클래스의 주소는 아래와 같다. 지금 SRC문서 디렉토리를 열어 확인하기 바란다.
java.applet.Applet |
아래의 예제는 사용자가 패키지를 만들고 만들어진 패키지 안에 클래스를 생성하는 예제이다.
Class16.java |
|
package Mypack.pack1; // x1
public classClass16{
public void hi(){ // x2
System.out.println("하이여~~~~");
}
}
x1행에서 보자.
package Mypack.pack1; |
위와 같이 하고 컴파일 하면 Mypack 디렉토리가 만들어지고 그 안에 pack1 디렉토리가 만들어진다. 컴파일 할 때는 -d옵션을 사용해야한다.
javac -d.Class16.java |
-d옵션은 package의 위치를 지정하는 것으로 -d에 도트(.)를 삽입하면 현재 디렉토리에 패키지를 만든다. 현재 디렉토리란 소스파일(Class16.java)이 있는 위치를 말한다.
컴파일이 완료되면 현재 디렉토리 안에 'Mypack.pack1.Class16'이 생겨있을 것이다. Class16 클래스는 main 메소드가 없으므로 혼자서 실행될 수 없는 클래스이다. 그러나 다른 클래스에서 Class16 클래스의 객체를 만들어 사용할 수 있다.
x2행에서 'void hi()'앞에 있는 public과 같은 것을접근 지정자(Access Modifier)라고 하는데 public은 다른 패키지에 있는 클래스에서 hi메소드를 사용할 수 있게 하는 것이다. 접근 지정자는 조금 후에 자세히 알아보자.
다른 패키지에 있는 클래스를 사용하기 위하여 import 키워드를 이용한다. import의 사전적 의미는 '수입하다'이다.
importMypack.pack1.Class16; |
위의 코드는 Mypack 안의 pack1 안에 있는 Class16 클래스를 수입하라는 명령이다. 다른 패키지에 있는 클래스를 사용하려면 위와 같은 방법으로 해당 클래스를 import해와야 한다. 아래 예제는 다른 패키지에 있는 클래스를 import하여 사용하는 방법을 보여준다.
Class17.java |
|
import Mypack.pack1.Class16; // Class16 클래스를 import한다.
public classClass17{
public static void main(String[] args){
Class16 ob=new Class16();
ob.hi();
}
}
출력 결과 |
|
하이여~~~~
패키지를 만드는 이유는 자기만의 Library를 만들어 클래스들을 효율적으로 관리하기 위해서이다. 또는 대형 프로젝트를 작성할 때 클래스가 아주 많이 생기므로 클래스들을 묶어서 효율적으로 관리할 수 있다. 초보자는 많아야 대여섯 개 정도의 클래스를 만들 것이고, 대부분은 한 두 개의 클래스로 연습하기 때문에 패키지의 필요성을 못 느낀다. 따라서 어느 정도의 실력이 쌓이면 그 때 패키지 작성 방법을 공부해도 괜찮을 듯 싶다.
Date 클래스는 'java.util' 패키지에 있는 날짜와 관련된 클래스이다. 이 클래스를 사용하려면 'java.util.Date'를 import한다.
Class18.java |
|
import java.util.Date;
public classClass18{
public static void main(String[] args){
Dated=new Date();
System.out.println(d);
}
}
출력 결과 |
|
Thu Feb 21 13:42:26 JST 2002
String 클래스와 System 클래스는 'java.lang' 패키지에 있는 클래스이다. 자바는 기본적으로 'java.lang'에 있는 모든 클래스를 import한다.
import java.lang.*; |
'*'는 모든 클래스를 의미한다. 우리가 'java.lang.*'을 import하지 않더라도 자동적으로 'java.lang.*'을 import하기 때문에 System클래스나 String클래스를 사용할 수 있는 것이다. lang은 language의 약자로 자바 언어를 하기 위한 기본적인 클래스들이 있는 패키지이다.
Class18.java |
|
import java.util.Date;
import java.lang.*; // 이 행은 삭제해도 된다. 자동으로 import한다.
public class Class18{
public static void main(String[] args){
Dated=new Date();
System.out.println(d);
}
}
자바에서 제공하는 클래스는 그 양이 상당하므로 클래스를 모두 설명하고자 한다면 책으로 수십 권내지 수백 권될 것이다. 따라서 이 책에 없는 클래스나 메소드를 공부하려면 SRC문서나 API문서를 잘 활용해야 한다. 아쉬운 것은 이 문서들이 영어로 되어있다는 것이다. 자바도 공부하고 영어도 공부할 수 있는 절호의 기회라고 생각하고 열심히 번역하길 바란다. 일거양득(一擧兩得)이라면 좀 위안이 될라나?. 인터넷에 한글로 번역된 API문서가 유통되고 있으므로 영어가 딸린다면 한글 API문서를 보는 것도 좋다.
접근 지정자(Access Modifier)
접근 지정자의 종류는 아래와 같다.
멤버 변수나 멤버 메소드 앞에 올 수 있는 접근 지정자 private public default(friendly) protected |
클래스 앞에 올 수 있는 접근 지정자 public default(friendly) |
그렇다면 이런 접근 지정자의 역할은 무엇일까?
private 접근을 가진 멤버는 클래스 내부에서만 사용(접근)될 수 있다. 즉 다른 클래스에서는 이 멤버를 사용할 수 없다. 말 그대로 사적(私的)인 용도로 사용한다.
Class19.java |
|
classClass19_A{
private int num;
private void hi(){
System.out.println("안녕~~~");
}
}
public classClass19{
public static void main(String[] args){
Class19_A ob=new Class19_A();
ob.num=10; // x1
ob.hi(); // x2
}
}
출력 결과 |
|
C:\...\Class19.java:11:num has private access in Class19_A
ob.num=10;
^
C:\...\Class19.java:12:hi() has private access in Class19_A
ob.hi();
^
2 errors
위 에러는 "num과 hi()는 Class19_A에서 private 접근을 가지고 있어서 다른 클래스에서는 접근할 수 없다"는 내용이다. x1행과 x2행을 보면 num과 hi()를 Class19에서 사용하고 있으므로 에러이다. num과 hi()는 private이므로 Class19_A 클래스의 내부에서만 사용 가능하다.
일반적으로 멤버 변수는 private 접근을 가지게 하여 다른 클래스(객체)에서 멤버 변수에 직접적으로 접근하는 것을 막는다. 이것을 은닉화, 또는캡슐화(Encapsulation)라고 한다.
[그림 5-6] Encapsulation
[그림 5-6]은 캡슐화를 표현한 것이다. 객체는 자신의 데이터와 내부에서 일어나는 일은 외부로부터 숨기고, 단지 외부에는 객체 내부와 통신할 수 있는 인터페이스들을 제공한다. 따라서 외부에서는 객체의 인터페이스만을 통해 그 객체를 사용할 수 있게 하는 것이다. 예를 들면, 자동차와 자동차를 운전하는 기사가 있다. 가고 있는 자동차를 세우려면 기사는 어떤 행동을 해야할까? 자동차의 브레이크를 밟을 것이다. 그 다음은 자동차 내부에서 척척 알아서 처리한다(자동차를 세운다). 기사는 자동차 내부에서 어떤 일이 일어나고 있는 지 알 필요가 없고, 브레이크만 밟으면 된다. 자동차 객체는 내부에서 일어나는 일은 숨기고, 단지 외부에 여러 가지 인터페이스를 제공한다. 따라서 사용자는 그 인터페이스를 통해서 자동차 객체를 사용할 수 있다. 자동차의 동작 원리를 모르는 많은 사람들이 자동차를 자유자재로 운전할 수 있는 이유는 자동차를 완벽하게 캡슐화 했기 때문이라고 말할 수 있을 것이다. 믿거나 말거나...,
캡슐화 되지 않은 프로그램은 객체 지향 프로그램이라고 말할 수 없다.
아래 예제는 객체의 데이터를 숨기고, 외부에 인터페이스를 제공하는 예이다.
Class20.java |
|
class Class20_A{
private int num;
void setNum(int a){ // x1, 인터페이스
num=a; // 클래스(객체) 내부에서만 사용 가능하다
}
int getNum(){ // x2, 인터페이스
return num;
}
}
public classClass20{
public static void main(String[] args){
Class20_A ob=new Class20_A();
ob.setNum(10); // num의 값을 바꾸지만 num은 보이지 않는다.
System.out.println(ob.getNum());
}
}
출력 결과 |
|
10
위 예제를 해보면 의문점이 생길 것이다. x1행과 x2행의 setNum()과 getNum()의 접근 지정자가 생략되어있다. 접근 지정자가 생략된 멤버를 default 또는 friendly 멤버라고 한다. default 멤버는 같은 패키지 안의 클래스에서 접근할 수 있다. 같은 말로 다른 패키지의 클래스는 default 멤버에 접근할 수 없다. Class20_A와 Class20은 같은 패키지(디렉토리)에 있는 클래스이므로 Class20_A의 default 멤버를 Class20에서 사용할 수 있다. Class20_A와 Class20이 같은 패키지에 있다는 것을 어떻게 알 수 있을까? 탐색기를 열어서 확인해보면 Class20_A.class와 Class20.class이 같은 디렉토리에 있음을 확인할 수 있을 것이다. 즉 같은 패키지이다.
public 멤버는 모든 클래스가 접근할 수 있다. 말 그대로 공적(公的)인 멤버이다.
Class21.java |
|
class Class21_A{
public int num; // public 멤버 변수
public void hi(){ // public 멤버 함수
System.out.println("안녕~~");
}
}
public classClass21{
public static void main(String[] args){
Class21_A ob=new Class21_A();
ob.num=10; // 모든 클래스가 num에 접근할 수 있다.
ob.hi(); // 모든 클래스가 hi에 접근할 수 있다.
System.out.println(ob.num);
}
}
출력 결과 |
|
안녕~~
10
protected 멤버는 같은 패키지에 있는 클래스에서 접근 가능할 뿐만 아니라 다른 패키지에 있는 자식(child) 클래스에서도 접근 가능하다. 상속(Inheritance)을 알아야 이해되므로 나중에 상속 부분에서 살펴보자.
여기까지 배운 접근 지정자를 요약하면 아래와 같다.
private:클래스 내부에서만 사용할 수 있다. public:모든 클래스가 접근할 수 있다. default(friendly): 같은 패키지 안의 클래스가 접근할 수 있다. protected:같은 패키지 안의 클래스가 접근할 수 있고, 다른 패키지에 있는 자식 클래스도 접근할 수 있다. |
클래스 앞에 올 수 있는 접근 지정자는 public과 default인데 public은 모든 클래스가 해당 클래스를 사용할 수 있고 default는 같은 패키지 안의 클래스에서 해당 클래스를 사용할 수 있다.
오버로드(Overload)
오버로드란 같은 이름을 가지는 멤버 메소드를 정의하는 것을 말한다.
voidhi(){ System.out.println("안녕~~"); } |
voidhi(String name){ System.out.println(name+"씨 안녕~~"); } |
voidhi(String name1, String name2){ System.out.println(name1+"씨, "+name2+"씨 안녕~~"); } |
위 세 메소드는 이름이 모두 hi이다. 그러나 매개 변수의 개수가 다르거나 자료형이 다르다. 이것을 메소드 오버로드라고 한다. 리턴형은 같아도 되고 달라도 된다. 호출할 때는 아래와 같이 한다.
ob.hi(); // 첫 번째 hi호출 ob.hi("철수"); // 두 번째 hi호출 ob.hi("철수","영희"); // 세 번째 hi호출 |
Class22.java |
|
public classClass22{
voidhi(){
System.out.println("안녕~~");
}
voidhi(String name){
System.out.println(name+"씨 안녕~~");
}
voidhi(String name1, String name2){
System.out.println(name1+"씨, "+name2+"씨 안녕~~");
}
public static void main(String[] args){
Class22 ob=new Class22();
ob.hi();
ob.hi("철수");
ob.hi("철수","영희");
}
}
출력 결과 |
|
안녕~~
철수씨 안녕~~
철수씨, 영희씨 안녕~~
다음 예제를 검토하자.
Class23.java |
|
public classClass23{
void hi(){
System.out.println("안녕~~");
}
int hi(){
System.out.println("씨 안녕~~");
return 1;
}
public static void main(String[] args){
Class23 ob=new Class23();
ob.hi();
}
}
출력 결과 |
|
C:\...\Class23.java:5:hi() is already defined in Class23
int hi(){
^
1 error
오버로드할 때는 반드시 매개 변수의 개수가 다르거나 매개변수의 자료형이 달라야 한다.
Class24.java |
|
public classClass24{
static boolean isSame(int a, int b){
return a==b;
}
static boolean isSame(double a, double b){
return a==b;
}
public static void main(String[] args){
System.out.println(isSame(1, 2)); // 첫 번째
System.out.println(isSame(1.2, 1.2)); // 두 번째
}
}
출력 결과 |
|
false
true
짚어두기 |
|
Static Static 변수나 메소드는 객체 없이도 호출될 수 있다. |
혼자 해보기 | Alone5_5.java |
평균을 구하는 메소드를 오버로드하고 호출하여 보자.
메소드 헤더: double average(int a, int b)
메소드 헤더: double average(int a, int b, int c)
메소드 헤더: double average(int a, int b, int c, int d)
출력 결과 예시 |
|
average(10, 20) => 15.0
average(10, 20, 30) => 20.0
average(10, 20, 30, 40) => 25.0
생성자(Constructor)
멤버 변수를 초기화하지 않으면 기본 자료형은 0을, 레퍼런스는 null을 기억하게 된다고 앞에서 배웠다.
Class25.java |
|
public classClass25{
int a;
String b;
public static void main(String[] args){
Class25 ob1=new Class25();
Class25 ob2=new Class25();
Class25 ob3=new Class25();
System.out.println(ob1.a+" "+ob1.b);
System.out.println(ob2.a+" "+ob2.b);
System.out.println(ob3.a+" "+ob3.b);
}
}
출력 결과 |
|
0 null
0 null
0 null
ob1과 ob2, 그리고 ob3은 모두 같은 값을 가지고 생성된다. 그러나 ob1, ob2 그리고 ob3는 엄연히 서로 다른 객체가 분명하다. 그런데 같은 값을 가지고 있으므로 쌍둥이라고 불러야할 것이다. 여자가 아기를 낳을 때마다 같은 모습을 가진 아기를 낳을 수 없듯이 객체도 서로 다르게 태어나도록 해야함이 옳다. 객체 지향 프로그래밍은 실세계를 반영하는 프로그래밍이다. 아기를 낳기 위해서는 난자와 정자가 만나서 수정하고, 여자가 힘을 주는 등의 일련의 동작이 필요하다. 즉 객체를 생성하려면 동작이 필요하다. 이 필요한 동작을생성자라고 한다.
Class25의 객체를 만드는 방법은 아래와 같다.
Class25 ob1=new Class25(); |
여기서 Class25()는 메소드를 호출하는 것처럼 보인다. 사실은 이것이 바로 생성자이다. 생성자는 객체를 만드는 순간에 호출되어 실행되는 특수한 메소드이다. 생성자를 만들어 주지도 않았는데 어떻게 생성자가 호출된다는 것일까? 사용자가 생성자를 만들지 않으면 컴파일러가 알아서 생성자를 만들어 주기 때문이다. 컴파일러가 만들어 주는 생성자는 어떤 모습일까? 다음은 컴파일러가 만들어 주는 Class25의 생성자이다.
Class25(){} |
생성자의 이름은 클래스의 이름과 동일하고 리턴형이 없다. 위와 같이 컴파일러가 만들어 주는 생성자를 default 생성자라고 한다. 디폴트 생성자는 매개변수가 없고 몸체가 비어있다. 즉 아무 것도 실행하지 않는 생성자이다. 하는 일이 있다면 객체를 만들어 주는 것이다.
이제 직접 생성자를 만들어보자.
Class26.java |
|
public classClass26{
int a;
public Class26(){ //x1
a=10;
}
public static void main(String[] args){
Class26 ob=new Class26(); // x2
System.out.println(ob.a);
}
}
출력 결과 |
|
10
x1행에 있는 메소드가 생성자이다. x2행에서 ob객체가 만들어 질 때 x1행의 생성자가 실행된다. 따라서 'ob.a=10'된다. 일반적으로 생성자는 다른 클래스에서 호출되므로 접근 지정자를 보통 public으로 지정한다. 그러나 목적에 따라 다른 접근 지정자가 사용되기도 한다. 생성자는 리턴형이 없고 클래스의 이름과 동일하다는 것을 확인하자.
위 예제에서 객체가 생성되면 멤버 변수 a에 10이 대입된다. 다른 객체를 만들더라도 a에 10이 대입될 것이다. 한마디로 쌍둥이이다. 생성자를 오버로드해서 이 문제를 해결할 수 있다.
Class27.java |
|
public classClass27{
int a;
public Class27(){ // x1
a=10;
}
public Class27(int a){ // x2
this.a=a;
}
public static void main(String[] args){
Class27 ob1=newClass27(); // x3
Class27 ob2=newClass27(20); // x4
Class27 ob3=newClass27(30); // x5
System.out.println(ob1.a);
System.out.println(ob2.a);
System.out.println(ob3.a);
}
}
출력 결과 |
|
10
20
30
x3행은 x1행의 생성자를 호출하고, x4행과 x5행은 x2행의 생성자를 호출한다.
아래 예제를 유심히 보자.
Class28.java |
|
public classClass28{
int a;
publicvoidClass28(){ // x1, 생성자??????
a=10;
}
public static void main(String[] args){
Class28 ob=new Class28();
System.out.println(ob.a);
}
}
출력 결과 |
|
0
|
x1행의 void Class28()은 생성자가 아니라 멤버 메소드이다. 왜냐하면 생성자는 리턴형이 없기 때문이다. 따라서 ob.a=0이 된다. Class28에는 디폴트 생성자만 존재한다. 왜냐하면 사용자가 생성자를 만들어주지 않았기 때문이다.
아래 예제도 유심히 보자.
Class29.java |
|
public classClass29{
int a;
public Class29(int a){
this.a=a;
}
public static void main(String[] args){
Class29 ob=newClass29(); // x1
System.out.println(ob.a);
}
}
출력 결과 |
|
C:\...\Class29.java:7:cannot resolve symbol
symbol :constructor Class29 ()
location: class Class29
Class29 ob=new Class29();
^
1 error
x1행에서 매개 변수가 없는 생성자를 호출하고 있다. 디폴트 생성자가 존재하면 문제가 없지만 이미 생성자를 만들었으므로 컴파일러는 디폴트 생성자를 만들어 주지 않는다. 따라서 매개 변수가 없는 생성자가 없으므로 에러가 뜬다.
this(...)
생성자에서 오버라이드된 다른 생성자를 호출할 수도 있다. 다른 생성자를 호출하고자 할 때는 this(...)를 사용한다.
this(), this(a), this(a,b), ... ; -> 생성자 호출 |
Class30.java |
|
public classClass30{
publicClass30(){ // x1
System.out.println("안녕~~"); // x2
}
publicClass30(String s){ // x3
this(); // x4, Class30()호출, x1행으로 이동
System.out.println(s); // x5
}
publicClass30(int a){ // x6
this("반가워~~"); // x7, Class30(String s) 호출, x2행으로 이동
System.out.println(a); // x8
}
public static void main(String[] args){
Class30 ob=new Class30(20); // x9
} // x10
}
출력 결과 |
|
안녕~~
반가워~~
20
실행 순서는 다음과 같다.
x9 → x6 -> x7 -> x3 → x4 → x1 → x2 → x5 → x8 → x10 |
메소드의 실행이 끝나면 그 메소드를 호출한 부분으로 돌아간다는 것을 상기하면 실행 순서가 이해될 것이다.
알아두기 |
|
this(...) 호출의 제한 ◈ 생성자의 실행은 다른 처리보다 우선적이므로 this(...)는 블록({})안에서 맨 위에 있어야한다.
public Class30(String s){ System.out.println(s); this(...); // 에러, 생성자의 실행은 다른 처리보다 우선적이다. }
◈ 일반 메소드는 객체를 생성한 후에 사용하는 메소드이므로 this(...)를 일반 메소드에서 호출할 수 없다. 생성자는 객체를 생성하기 위해서만 사용되기 때문이다.
void method(){ this(...); // 에러 }
|
혼자 해보기 | Alone5_6.java |
Alone5_6 클래스는 다음과 같은 멤버 변수를 가지고 있다. 멤버 변수의 값을 초기화시키는 생성자를 만들어보자. 그리고 변수의 값을 얻는 메소드를 작성하고 테스트해보자.
단, 생성자를 오버로드할 때 'this(...)'를 이용해야 한다.
public classAlone5_6{
int a;
double b;
String c;
public Alone5_6(){
// x1행의 생성자를 호출한다.
}
public Alone5_6(int a){...} // x1행의 생성자를 호출한다.
public Alone5_6(int a, double b){...} // x1행의 생성자를 호출한다.
public Alone5_6(int a, double b, String c){...} // x1
int getA(){...}
double getB(){...}
String getC(){...}
}
클래스 초기화
생성자와 같이 멤버 변수를 초기화할 수 있는 다른 방법이 있다. 클래스를 정의할 때 멤버 변수에 값을 대입하면 객체가 생성될 때 멤버 변수에 그 값이 대입된다.
Class31.java |
|
public class Class31{
int a=10; // x1
static int b=20; // x2
public static void main(String[] args){
Class31 ob=new Class31(); // x3
System.out.println(ob.a);
System.out.println(Class31.b);
}
}
출력 결과 |
|
10
20
x1행이 이상하게 보인다. 일반 멤버 변수는 객체를 만들어야만 메모리를 할당한다고 알고 있다. 그런데 x1행의 멤버 변수 a는 메모리에 존재하지 않으므로 a에 값을 대입할 수 없지 않은가? 물론 대입할 수 없다. 그렇다면 어떻게 해석해야 옳을까?
int a=10; ->나중에객체를 만들면 a에 10을 대입하라. |
알아두기 |
|
해석의 차이 C++에서는 클래스의 멤버 변수를 초기화할 수 없다.
C++: "멤버 변수는 객체를 만들어야만 존재한다. 따라서 없는 변수에 값을 대입할 수 없다"
자바: "나중에 객체가 생길 때 대입하면 되지~~잉." |
static block
다음 예제에서 x1행과 같은 'static{...}'은 클래스가 메모리에 loading될 때 실행된다.
Class32.java |
|
public classClass32{
static int b;
static{ // x1
System.out.println("하이~~"); // x2
b=30; // x3, static 멤버만 올 수 있다.
}
public static void main(String[] args){
System.out.println("헐~~"); // x4
System.out.println(b); // x5
}
}
출력 결과 |
|
하이~~
헐~~
30
main은 클래스가 메모리에 올라오고 나서 실행되므로 static보다 늦게 실행된다. static이 아닌 멤버 변수는 static 메소드에서 사용될 수 없는 것처럼 'static{...}'에서도 사용될 수 없다. 'static{...}'은 static 변수의 초기화나 static 메소드를 호출할 때 주로 사용한다.
Instance block
인스턴스는 클래스로부터 생성된 객체를 의미하고, 인스턴스화 한다는 것은 클래스의 객체를 만드는 것을 말한다. 넓은 의미로 객체를 클래스의 인스턴스라고 부른다.
인스턴스 블록은 클래스의 인스턴스화, 즉 객체를 만들 때 실행된다. 생성자도 객체를 만들 때 실행되는데 인스턴스 블록은 생성자 보다 먼저 실행된다.
다음 예제에서 x1행의 '{'에서 x2행의 '}'까지가 인스턴스 블록이다.
Class33.java |
|
public classClass33{
int a;
{ // x1, 인스턴스 블록 시작
System.out.println("인스턴스 블록");
a=10;
} // x2, 인스턴스 블록 끝
Class33(int a){
System.out.println("생성자");
this.a=a;
}
public static void main(String[] args){
Class33 ob=new Class33(30);
System.out.println(ob.a);
}
}
출력 결과 |
|
인스턴스 블록
생성자
30
인스턴스 블록과 생성자를 같이 사용하면 코드를 복잡하게 만들기 때문에 생성자보다 인스턴스 블록이 덜 사용된다.
| 연습 문제 |
|
1. 다음을 만족하는 Pen 클래스를 정의하고 객체를 만들어서 테스트해보자.
Pen은 볼의 두께, 잉크 량, 잉크의 색, 뚜껑의 유무, 길이와 반지름을 가지고 있다. Pen으로 그림을 그리거나, 글자를 쓸 수 있다. 또 잉크를 교체할 수도 있다. |
2. 다음을 만족하는 Point(위치) 클래스를 정의하고 객체를 만들어서 테스트해보자.
Point 객체는 x좌표, y좌표를 가지며 좌표를 얻거나 변경할 수 있는 메소드를 제공한다. |
3. 다음을 만족하는 Dimension(크기) 클래스를 정의하고 객체를 만들어서 테스트해보자.
Dimension 객체는 너비(width)와 높이(height)를 가지며 크기를 얻거나 변경할 수 있는 메소드를 제공한다. |
4. 다음을 만족하는 Rectangle(직사각형) 클래스를 정의하고 객체를 만들어서 테스트해보자.
직사각형은 좌표(Point)와 크기(Dimension)를 가진다. 넓이를 반환하는 메소드와 좌표와 크기를 변경하는 메소드로 제공한다. |
5. Shape(도형) 클래스를 정의해보자. 멤버로 어떤 것이 있는가? 도형 클래스를 구체적으로 정의하기 어려운 이유는 무엇인가?
6. Mathematics(수학) 클래스를 정의해 보자. 이 클래스는 다음과 같은 멤버를 가진다.
<메소드> doubleceil(double a) : a보가 크거나 같은 정수를 구해준다. doublefloor(double a) : a보다 작거나 같은 정수를 구해준다. doublerint(double a) : a와 가장 가까운 정수를 구해준다. doublepow(double a, double b) : a의 b승을 구해준다. intabs(int a) : a의 절대값을 구해준다. intmax(int a, int b) : a와 b중, 큰 수를 반환한다. intmin(int a, int b) : a와 b중, 작은 수를 반환한다. |