백기선님 자바 기초 스터디 5주차 과제 질문을 참조하여 포스트를 작성하였습니다.
이번 포스트에서는 자바의 클래스에 대해 알아보겠습니다.
클래스에 대해 이야기 하기 전에, 객체지향이 무엇인지에 대해 간략히만 알아보고자 합니다.
"실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물 간의 상호작용이다."
객체지향의 기본 개념은 실제 사물의 속성과 기능을 분석하고 데이터(필드)와 함수로 정의함으로써 실제 세계를 컴퓨터 속으로 옮겨 놓는다는 것입니다.
예를 들어, 아이언맨의 슈트가 있다고 해봅시다. 아이언맨 슈트의 속성과 기능은 다음과 같이 분석될 수 있습니다.
<객체 모델링>
객체 | 속성 | 기능 |
---|---|---|
아이언맨 슈트 | 색상 | 이륙하기 |
길이 | 착륙하기 | |
무게 | 레이저 쏘기 |
아이언맨은 슈트를 여러 벌 가지고 있고, 슈트마다 가지는 속성과 기능이 다릅니다.
토니 스타크는 이 중에서 원하는 기능의 슈트로 언제든지 교체하여 장착할 수 있습니다. 슈트의 일부분이 망가지면 고칠 수도 있고, 다른 장치로 교환할 수도 있습니다. 여러 가지 기능을 가지는 슈트를 만들어 놓고, 필요할 때 선택해서 사용할 수도 있습니다.
이처럼 언제든지 사용할 수 있도록 준비된 아이언맨 슈트가 객체라고 생각하면 됩니다.
여기서 클래스란 무엇인지 짚고 넘어가보겠습니다.
클래스란 객체를 만드는 설계도 또는 틀이라고 정의할 수 있습니다. 컴퓨터 프로그래밍으로 객체를 만들기 위해서는 클래스가 필요합니다.
앞서 아이언맨 슈트가 가지고 있는 속성과 기능으로 구조화하는 객체 모델링을 간략히 진행해보았습니다. 이제 이를 바탕으로 클래스를 만들어보겠습니다.
컴퓨터 세상에서 아이언맨 슈트(객체)의 속성은 객체가 가진 고유한 특성인 필드(멤버변수)로, 동작은 객체가 할 수 있는 동작인 메서드로 표현될 수 있습니다.
<클래스 다이어그램>
클래스 | 필드 (멤버변수) | 메서드 |
---|---|---|
IronManSuit | color | takeOff() |
height | land() | |
weight | shootLaser() |
이 클래스 다이어그램을 기반으로, 클래스를 작성하면 아래와 같습니다.
<클래스 다이어그램을 기초로 작성한 클래스>
public class IronManSuit{
private String color;
private int height;
private int weight;
public void takeOff() {}
public void land() {}
public void shootLaser() {}
}
이제부터 클래스를 작성하는 방법에 대해 알아보겠습니다.
클래스는 접근 제한자를 두어 접근 권한을 조절할 수 있습니다. 접근 제한자는 클래스와 필드, 메서드를 선언할 때 지정할 수 있습니다.
접근 제한자 | 클래스 내부 | 동일 패키지 | 하위 클래스 | 그 외 영역 |
---|---|---|---|---|
public | o | o | o | o |
protected | o | o | o | x |
default | o | o | x | x |
private | o | x | x | x |
<클래스 선언 방법>
제어자 class 클래스명 {
필드 선언;
메서드 선언
}
클래스명을 지정할 때는 다음의 규칙을 지켜 작성해야 합니다.
<필드 선언 방법>
제어자 데이터타입 변수이름;
필드명은 다음과 같은 규칙을 지켜야 합니다.
<메서드 선언 방법>
제어자 반환타입 메서드이름(매개변수) {
실행문;
}
메서드명을 지정할 때는 다음과 같은 규칙을 따릅니다.
앞 부분에서는 객체를 생성하기 위한 틀인 클래스를 작성하는 방법을 배워보았습니다.
클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화라고 하며, 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스라고 합니다. 인스턴스와 객체는 같은 의미이지만, 문맥에 따라 구별하여 사용하는 것이 좋습니다.
ex) 아이언맨 슈트는 인스턴스다 → 아이언맨 슈트는 객체다
ex) 아이언맨 슈트는 아이언맨 슈트 클래스의 객체다 → 아이언맨 슈트는 아이언맨 슈트 클래스의 인스턴스다
인스턴스의 생성은 다음과 같이 수행할 수 있습니다.
클래스명 변수명;
변수명 = new 클래스명();
IronManSuit suit; // (1) 클래스의 객체를 참조하기 위한 참조변수 선언
suit = new IronManSuit(); // (2) 클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장
(1)과 (2) 과정을 자세히 살펴보겠습니다.
IronManSuit
클래스 타입의 참조변수 suit
를 선언하였습니다. 메모리 스택 영역에 참조변수를 위한 공간이 마련됩니다. 하지만 아직 인스턴스가 생성되지 않았으므로 참조변수로 아무것도 할 수 없는 상태입니다.
지난번 자바의 자료형 포스트에서 작성했던 것처럼, new
키워드를 사용하면 메모리의 힙 영역에 참조변수 suit
를 위한 영역을 할당받은 후, 해당 영역의 주소를 반환하게 됩니다. 힙 영역에 생성된 인스턴스의 멤버 변수는 생성자에 의해 초기화되며, 대입연산자(=)에 의해 생성된 객체의 주소 값이 참조변수 suit
에 저장됩니다. 이제 참조변수 suit
를 통해 인스턴스에 접근할 수 있게 되었습니다.
인스턴스는 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입은 인스턴스의 타입과 일치해야합니다.
(2)번에 보면, 인스턴스의 멤버 변수는 생성자에 의해 초기화될 수 있다고 하였습니다. 인스턴스 변수 초기화 작업에 사용되는 생성자에 대해 알아보겠습니다.
생성자는 인스턴스가 생성될 때 자동으로 호출되는 메서드입니다. 따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해 사용되곤 합니다.
생성자 역시 메서드처럼 클래스 내에 선언되며, 다음과 같은 조건을 만족해야 합니다.
리턴 값이 없을 경우 메서드에서는 void
를 붙여야 하지만, 생성자의 경우 모든 생성자가 리턴 값이 없으므로 void
를 생략할 수 있습니다.
<생성자 선언 방법>
클래스명(타입 변수명, 타입 변수명, ...) {
//인스턴스 생성시 수행될 코드
//주로 인스턴스 변수의 초기화 코드를 적는다.
}
public class IronManSuit{
private String color;
private int height;
private int weight;
IronManSuit() { //매개변수가 없는 생성자
...
}
IronManSuit(String color, int height, int weight) { //매개변수가 있는 생성자
...
}
public void takeOff() {}
public void land() {}
public void shootLaser() {}
지금까지 인스턴스 생성에 사용해왔던 IronManSuit suit = new IronManSuit();
의 IronManSuit()
가 바로 생성자입니다.
모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 하지만, 지금까지는 클래스에 생성자를 정의하지 않고도 인스턴스를 생성할 수 있었습니다. 이유는 컴파일러가 기본 생성자를 제공하기 때문입니다.
컴파일 시, 소스코드의 클래스에 생성자가 하나도 정의되지 않은 경우 컴파일러는 자동으로 매개변수도 없고, 아무런 내용도 없는 기본 생성자를 추가하여 컴파일합니다. 주의해야할 점은, 생성자를 하나라도 추가했다면 기본 생성자를 생성해주지 않는다는 것입니다.
예를 들어 매개변수가 있는 생성자를 하나 생성한 경우, 이미 생성자가 정의되어 있으므로 기본 생성자를 추가해주지 않습니다. 특별히 인스턴스 초기화 작업이 필요하지 않다면, 기본 생성자를 사용하는 것도 좋습니다.
생성자를 선언할 때, 매개변수가 없는 생성자와 있는 생성자를 선언했었습니다. 매개변수만 다르고 메서드명은 같은 것이죠!
같은 클래스 내에서 서로 구별될 수 있어야 하기 때문에, 메서드는 각기 다른 이름을 가져야 합니다. 그러나 자바에서는 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도 매개변수의 개수나 타입이 다르면 같은 이름을 사용해서 메서드를 정의할 수 있습니다.
이처럼, 같은 기능을 수행하는 메서드들을 이름은 같게, 매개변수 구성은 다르게 선언하는 것을 메서드 오버로딩이라 부릅니다.
자바에서 메서드 오버로딩이 가능한 이유는 컴파일 시 메서드 이름이 다르게 지정되기 때문입니다. 컴파일 시점에 오버로딩된 메서드 명은 메서드명_매개변수타입
으로 지정됩니다.
ex) sppedUp_int, sppedUp_String, sppedUp_int_double ...
따라서 메서드 이름과 매개변수 개수, 타입이 같다면 오류가 발생합니다.
public void sppedUp(int value) { // speedUp_int
int speed = value;
}
public void speedUp(int data) { // speedUp_int
int speed = data;
}
위 코드는 오버로딩된 메서드의 매개변수 이름은 다르지만 타입과 개수가 같습니다. 컴파일 시점에 매개변수 이름은 영향을 미치지 않기 때문에, 위 메서드들은 모두 sppedUp_int()
로 이름이 변경되어 메서드 이름 중복으로 오류가 발생합니다.
this
키워드는 자바에서 사용하는 예약어로 현재 실행 중인 인스턴스의 주솟값을 나타냅니다. this
는 쉽게 생각해서 this
가 사용된 곳, 즉 자신의 주솟값이라고 생각해도 됩니다. 따라서 this
는 사용한 곳에 따라 값이 달라집니다.
일반적으로 this
는 다음의 용도로 사용합니다.
그렇다면 this
변수는 언제 생성될까요?
자바는 새로운 메서드가 실행될 때마다 스택에 새로운 메서드 영역이 생성됩니다. 자바 프로그램이 실행되면 항상 main()
메서드가 처음 실행되므로 스택에는 main()
메서드 영역부터 생성됩니다. 그리고 메서드가 실행될 때 스택에 새로운 메서드 영역이 생성되는데, 이 때 자동으로 실행 중인 메서드가 속한 인스턴스의 주솟값이 저장된 this
변수가 선언됩니다.
예를 들어 보겠습니다.
public class ThisTest {
int i = 1;
public void first() {
int i = 2;
int j = 3;
this.i = i+j; // (3)
second(4); // (4)
}
public void second(int i) {
int j = 5;
this.i = i+j; // (5)
}
public static void main(String[] args){
ThisTest exam = new ThisTest(); // (1)
exam.first(); // (2)
}
}
(1) ThisTest exam = new ThisTest();
스택에 exam
이라는 지역변수가 생성되고, 힙에는 new
연산자에 의해 클래스 ThisTest
의 인스턴스가 생성됩니다.
(2) exam.first();
지역변수 exam
이 참조하는 인스턴스의 first()
에서드를 호출합니다. 메서드가 실행될 때 스택에 새로운 메서드 영역이 생성되므로, first
메서드 영역과 함께 this
변수가 선언됩니다. 이 때 this
변수는 first()
메서드가 속한 인스턴스 주소 값을 갖는 exam
과 동일한 값을 갖게 됩니다.
(3) this.i = i+j;
이 때 this
는 힙에 생성된 ThisTest
인스턴스의 주소 값이며, this.i
는 해당 인스턴스의 i
필드의 값을 의미합니다. first()
메서드의 스택 영역에 선언된 i(2)
와 j(3)
의 값을 더한 5의 값이 인스턴스 변수 i
에 저장됩니다.
(4) second(4);
새로운 메서드를 호출하였으므로 스택에는 새로운 영역이 생성되고, this
변수가 만들어집니다. this
변수는 first()
메서드의 this
변수와 동일하게 ThisTest
인스턴스의 주소 값을 가집니다.
(5) this.i = i+j;
ThisTest
인스턴스의 멤버변수 i
에 매개변수 i
의 값과 지역변수 j
의 값이 더해진 9의 값이 저장됩니다.
정답은 아니오 입니다. main
함수를 잘 보면, public static void main(String[] args)
로 선언되어 있습니다. 여기서 중요한 점은 static
함수라는 점인데요, 자바에서 static
은 객체 생성 이전에 메모리에 관련된 메서드를 로딩합니다. 따라서 자기 자신의 객체를 이용해야하는 this
키워드를 사용할 수 없습니다.
비단 main
메서드 뿐만 아니라 static
으로 정의된 메서드에서는 this
키워드를 사용할 수 없습니다!
this
키워드는 생성자 호출에도 사용할 수 있습니다. 생성자를 공부할 때, 생성자는 일반 메서드처럼 호출할 수 없고 new
명령문을 이용해 객체를 생성할 때 자동으로 호출된다고 했습니다. 그런데 생성자 내에서 다른 생성자를 호출할 수도 있습니다. 즉, 생성자를 명시적으로 호출할 수 있는데, 이는 생성자 내에서만 가능하다는 이야기 입니다. 이 때 사용하는 예약어가 this()
입니다.
생성자를 호출할 때 주의사항은 생성자 호출문 this()
는 생성자에서만 사용할 수 있고, 생성자 내에서 가장 첫번째 줄에 위치해야 한다는 것입니다.