글을 작성하기 앞서 자바의 정석 앞부분(자바 설치, 툴, 기본 문법 등)에 대해서는 다루지 않는다. 해당 부분은 다른 글을 참고하거나 스스로 공부하는 것을 권장한다.
객체지향 언어
예를 들자면 공장에서 과자를 만들어내는 것을 생각해보자. 하나의 기계에서 반죽을 만들고 모양 틀을 짜서 구워가지고 과자가 나오는 것은 없다. 어떤 기계는 반죽을 만들 것이고 어떤 기계는 모양 틀만 만들어줄 것이고 어떤 틀은 구워주기만 할 것이다. 이 3가지 행위 모두 하나의 객체라고 생각하면 된다. 그 객체들이 모여서 과자를 만들듯 자바 또한 그런 식으로 결과물을 만들어 준다.
객체지향의 특징
1. 코드의 재사용성이 높다.
2. 코드의 관리가 용이하다.
3. 신뢰성이 높은 프로그래밍을 가능하게 한다.
사실 이론적으로 보면 객체지향이 뭐야? 라고 되뇌일 수 있는데 너무 얽매이지 말고 천천히 학습하면서 이를 알아나가본다.
클래스와 객체
클래스는 객체를 정의해놓은 것, 객체는 실제로 존재하는 것이라고 생각하면 된다. 더 쉽게 생각해서 클래스는 TV 설계도, 객체는 TV라고 생각하면 된다. 이걸 코드적으로 적어보기 전에 TV에 대해 생각해보자. TV는 외형적으로 크기, 높이, 색상, 볼륨, 채널 등이 있고 기능적으로는 켜기, 끄기, 볼륨 높이기 등등이 존재한다.
class TV {
Integer width; // 너비
Integer height; // 높이
String color; // 색
boolean power; // 전원
void power() {
power = !power;
}
}
프로그래밍 언어로 구현해보면 대충 이런 느낌일 것이다. 클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화라고 한다. 어떤 클래스를 통해 만들어진 객체를 인스턴스라고 보면 된다. 인스턴스가 객체에 포함되긴 하지만 좀 더 명확하게 이야기하자면, 책상은 인스턴스다라고 하는 것보다 책상은 객체다라고 하는 것이 맞고 책상은 교실 클래스의 객체이다보다 책상은 교실 클래스의 인스턴스다라고 하는 것이 자연스럽다.
public class Main {
public static void main(String[] args) {
TV tv;
tv = new TV();
tv.channel = 7;
tv.power();
System.out.println("현재 채널은 " + tv.channel + "번 입니다.");
System.out.println("전원 상태가 변경되었습니다. " + tv.power);
}
}
class TV {
// 멤버변수
String color;
boolean power = true;
int channel;
void power() {
power = !power;
}
}
조금 더 쉽게 이해하기 위해 멤버변수를 변경했다.
TV tv;
tv = new TV();
이 형태에 대해 알아보자. Tv tv;의 형태는 TV 클래스의 객체를 참조하기 위해 참조변수인 tv를 선언했다. new TV()를 통해 참조변수 tv에 생성된 TV 클래스의 인스턴스의 주소값을 저장한다라는 의미이다.
TV tv = new TV(); 를 통해서 참조변수 tv가 선언되고 클래스의 인스턴스가 메모리의 빈 공간에 할당되었다. 이 할당된 주소 값을 Ox100이라고 가정하겠다.
참조변수 tv에 TV 클래스의 인스턴스 주소값이 등록되었으니 참조변수 tv를 통해서 TV클래스의 인스턴스에 접근할 수 있게 되었다.
tv.channel = 7; 을 통해서 참조변수에 저장된 인스턴스의 멤버변수 channel에 7을 저장하면 다음과 같이 된다.
public class Main {
public static void main(String[] args) {
TV tv1 = new TV();
TV tv2 = new TV();
System.out.println("tv1의 채널은 " + tv1.channel + "입니다.");
System.out.println("tv2의 채널은 " + tv2.channel + "입니다.");
tv1.channel = 7;
System.out.println("tv1의 채널은 " + tv1.channel + "입니다.");
System.out.println("tv2의 채널은 " + tv2.channel + "입니다.");
}
}
class TV {
// 멤버변수
String color;
boolean power = true;
int channel;
void power() {
power = !power;
}
}
위의 출력문은 어떻게 나올지 생각해본다. 크게 어렵지 않을 것이다. TV클래스의 인스턴스 주소값을 tv1에 저장하고 또 다른 인스턴스 주소값을 tv2에 저장한다. 각자 다른 주소값이기에 tv1.channel을 할 경우 tv1의 인스턴스 멤버변수의 값만 저장하기 때문에 아래와 같은 값을 얻게 될 것이다.
선언위치에 따른 변수의 종류
변수의 선언부 위치에 따라 의미가 조금 달라진다.
class Variables {
int lv = 0; // 인스턴스 변수
static int sv = 0; // 클래스 변수
void method(int x) {
int lv = 0; // 지역변수
System.out.println("함수에 들어온 숫자는 " + x);
System.out.println("함수 내의 숫자는 " + lv);
}
}
주석을 달아둔 것처럼 변수의 위치에 따라 그 역할을 달라지는 것을 알 수 있다. 구구절절한 이론 설명보다 코드로 직접 써보고 그게 왜 그런지 알아보겠다.
public class Main {
public static void main(String[] args) {
Variables v1 = new Variables();
Variables v2 = new Variables();
v1.lv = 10;
System.out.println("v1의 인스턴스 변수 lv는 " + v1.lv + "입니다.");
System.out.println("v2의 인스턴스 변수 lv는 " + v2.lv + "입니다.");
v1.sv = 15;
System.out.println("v1의 클래스 변수 sv는 " + v1.sv + "입니다.");
System.out.println("v2의 클래스 변수 sv는 " + v2.sv + "입니다.");
v2.sv = 20;
System.out.println("v1의 클래스 변수 sv는 " + v1.sv + "입니다.");
System.out.println("v2의 클래스 변수 sv는 " + v2.sv + "입니다.");
v1.method(3);
}
}
class Variables {
int lv = 0;
static int sv = 0;
void method(int x) {
int lv = 0;
System.out.println("함수에 들어온 숫자는 " + x);
System.out.println("함수 내의 숫자는 " + lv);
}
}
자, 이렇게 실행시켜보고 본인이 생각한대로 나오는지 확인해본다.
만약 나오지 않았다면 클래스 변수의 부분이 틀렸을 것이라 생각한다. 인스턴스 변수의 경우는 인스턴스를 생성할 때 만들어지는 변수를 말한다. 그렇기에 각각 다르게 메모리에 할당되어 인스턴스가 각자만의 주소값을 가지기에 값 또한 다르게 가질 수 있다. 하지만 클래스 변수의 경우는 참조변수가 각각 다른 주소값을 가지더라도 공통된 저장 공간으로 인해 공유하게 되는 것이다. 그 외에 메소드 내부의 지역변수는 메소드가 실행되었을 때, 잠시 생성되다가 함수가 종료되면 바로 소멸한다.
메소드
중학교 시절을 돌이켜보면 f(x) = x + 1 이런 형태를 공부했던 적이 있을 것이다. 우리는 이를 함수라고도 불렀는데 메소드 또한 동일한 의미이다. f(x) = x + 1의 예제를 생각해보자. f(3)이라고 했을 때 값이 얼마가 나올까? 4라고 생각한 사람은 벌써 메소드를 이해하고 있는 것이다. 프로그래밍적으로 만들어보면 다음과 같다.
public int f(int x) {
return x + 1;
}
우리가 어릴 때 배웠던 함수가 바로 이 형태가 되는데 이걸 메소드라고 한다.
호출스택
호출스택은 메모리 공간에 메소드의 작업을 할당해준다 생각하면 된다. 말이 어려운 거 같으니 그림으로 설명하겠다.
public class Main {
public static void main(String[] args) {
System.out.println("퇴근하고싶다.");
}
}
우리는 간단히 "퇴근하고싶다" 라는 문자열을 출력해주는 코드를 구성했다. 근데 이게 메모리가 공간을 어떻게 제공해주고 있는걸까?
허접하지만 이걸 메모리라고 생각하겠다. System.out.println() 함수가 실행되기 전에 main() 함수가 먼저 실행되는 것을 알 수 있다.
그리고 나서 System.out.println() 함수가 실행되어 메모리에 할당된다.
main() 함수는 할당되었지만 호출스택은 대기상태로 남아있고 System.out.println() 함수는 할당되어 "퇴근하고싶다"를 화면에 출력해줬다. 이제 완료되었기에 호출스택에서 사라지게 되고 대기 중인 main() 함수는 수행을 재개한다.
재개한 main() 함수도 더 이상 재개할 코드가 없기 때문에 호출스택에서 제거되고 프로그램은 종료가 된다.
내일 할 부분
호출스택을 조금 더 심도있게 볼 예정이다. 기본형 매개변수와 참조형 매개변수라고 하는데 이를 그림을 통해서 이해를 진행해볼 예정이다.
'java > 자바의 정석' 카테고리의 다른 글
자바의 정석 - 객체지향 프로그래밍 6편 (0) | 2024.02.21 |
---|---|
자바의 정석 - 객체지향 프로그래밍 5편 (0) | 2024.02.20 |
자바의 정석 - 객체지향 프로그래밍 4편 (0) | 2024.02.19 |
자바의 정석 - 객체지향 프로그래밍 3편 (0) | 2024.02.17 |
자바의 정석 - 객체지향 프로그래밍 2편 (0) | 2024.02.15 |