본 글은 객체와 클래스, 인스턴스 간 차이를 명확히 정리하기 위해 전공 서적들을 참고하여 쓴 글입니다. 실질적인 코드 작성에 도움이 되는 내용보다는 객체 지향 프로그래밍(OOP)에 대한 개념적인 내용이 대부분이며, 그나마 있는 코드도 OOP를 처음 접하는 초심자분들을 위해 제일 간단한 수준으로만 작성했으므로 참고 바랍니다.
객체 (Object)
객체 지향 프로그래밍에서 '객체'를 이해하기 위해서는 현실 세계 속의 객체와 소프트웨어 관점에서의 객체를 구분할 수 있어야 합니다.
우선 현실 세계 속의 객체란 '세상에 존재하는 의사나 행위가 미치는 대상'을 의미합니다. 사실 이 정의는 네이버 사전에 등재되어 있는 것인데, 적절해 보여서 가져왔습니다. 눈에 보이는 대표적인 객체는 사람, 자동차, 건물 등이 있습니다. 대표적이라고 했지만 사실상 눈에 보이는 모든 사물들은 객체가 될 수 있습니다. 반면 눈에 보이지 않는 것들도 객체가 될 수 있는데, 주문이나 게시글 등이 그 예시입니다.
위에서 언급한 객체들은 각자 속성이 있고, 관련되는 행위들이 있습니다. 예를 들어 사람이라는 객체는 나이와 성별, 신장, 몸무게 등의 속성이 있을 수 있고, '기상한다'나 '씻는다', '밥을 먹는다' 등 객체가 수행하는 관련 행위들이 있습니다. 세상에는 헤아릴 수 없는 객체가 존재하고, 각 개체는 셀 수 없는 속성과 관련 행동들을 가지고 있을 수 있습니다.
소프트웨어 관점에서의 객체는 현실 세계 속의 객체와 비슷하게 '속성'과 '관련 행위 및 행동'들을 가지고 있습니다. 하지만 그 둘을 구분하는 이유는 소프트웨어 속의 객체가 현실 세계 속의 객체와 동일한 것이 아니고, 현실 세계 속의 객체를 모방한 것도 아니기 때문입니다. <객체지향의 사실과 오해>에서는 객체지향(Object-Oriented)의 목표를 '현실 세계의 모방'이 아니라 '새로운 세계를 창조하는 것'이라고 표현합니다. 뜬구름 잡는 소리같이 들릴 수도 있는데, 하나 예시를 들어보겠습니다.
class GroupResponse {
String groupName;
List<Member> members;
List<Organization> organizations;
}
위 소스코드는 실제 토이 프로젝트를 할 때 활용했던 객체를 응용해서 만들어본 객체입니다. ('객체'라는 개념에 집중하기 위해 접근 제어자나 lombok 등은 모두 제거했습니다. 이 말이 이해가 안 되신다면 괄호 속의 내용은 지나쳐도 좋습니다.)
위와 같은 형태의 객체는 애플리케이션 속에서 빈번하게 등장할 수 있습니다. 위 객체를 보면, 아까 현실 세계에서의 객체를 설명할 때 "사람이라는 객체가 나이, 신장, 몸무게라는 속성을 가지고 있다" 정도처럼 간단해 보이지 않습니다. 실제로 GroupResonse는 위에서 보이는 3줄보다 한참 더 복잡한 구조를 가지고 있을 수도 있습니다. 이런 객체는 현실 세계에서 그 어떤 것을 모방한 것도 아닙니다. 현실 세계 속의 사물과 비유해서 설명하려고 해도 마땅한 것이 떠오르지 않습니다. 즉, 애플리케이션에서 필요한 형태에 맞춰 새로운 개념의 객체를 창조해낸 것입니다.
그럼에도 불구하고 굉장히 많은 서적이나 강의에서는 실세계와 연관지어서 객체를 설명하고 있습니다. 아래와 같이 말이죠.
class Person {
int age;
String name;
}
물론 애플리케이션 내에서도 Person처럼 실세계에서의 객체와 거의 동일한 형태의 객체를 사용할 수도 있습니다. 그렇지만 실세계를 모방하는 것은 실무적 관점에서 부적합하고 한계가 있습니다. 위와 같이 실세계와 연관 지어서 설명하는 이유는 객체 지향의 기본 사상을 이해하고 학습하는 데에 효과적이기 때문입니다.
다시 처음으로 돌아가서 정리해보겠습니다. 현실 세계 속의 '객체'와 소프트웨어 관점에서의 '객체'를 구분할 수 있어야 합니다. 현실 세계 속의 객체는 '세상에 존재하는 의사나 행위가 미치는 대상'으로, 현실 세계 속 그 무엇이든 객체가 될 수 있습니다. 반면 소프트웨어 과점에서의 객체는 '현실 세계에서의 객체와 유사하지만 그 이상의 창조물'이라고 표현할 수 있습니다. 창조물이라는 표현이 어색하기 그지없는데, 한마디로 표현할 수 있는 단어가 마땅히 생각나지 않습니다. 무엇이라 부르든 그 느낌을 아는 것이 중요하다고 생각합니다.
더 나아가 소프트웨어 관점에서의 객체를 만들기 위해서는 클래스(Class)가 필요합니다. 바로 아래에서 설명드리겠지만, 클래스는 객체를 창조해내기 위한 설계도라고 할 수 있습니다. 그리고 소프트웨어 내에서 클래스라는 설계도에 따라 객체를 실체화한다면 그것을 인스턴스라고 부릅니다. 더 자세한 이야기는 아래 설명에서 계속 하겠습니다.
클래스 (Class)
OOP를 지향하는 언어에서는 소프트웨어적으로 객체를 구현하기 위해 보통 클래스를 활용합니다. 클래스는 객체의 속성과 기능을 코드로 구현한 것이며, 이는 소프트웨어 관점에서의 객체를 만들기 위한 설계도라고 할 수 있습니다. 일반적으로 클래스로 객체를 표현할 때 "클래스를 정의한다"라고 합니다. 객체 지향의 컨셉을 이해하기 쉽게 실세계 속의 대표적인 객체 '사람'을 간단한 클래스로 만들어 보겠습니다.
class Person {
int age;
String name;
}
Person이라는 이름의 클래스를 만들었고, 앞으로 소프트웨어 내에서이 클래스(설계도)를 활용하여 객체를 만들게 됩니다. 이 클래스를 통해 만들어진 객체는 나이와 이름이라는 속성을 가지고 있는데, 이러한 속성을 멤버 변수라고 합니다.
아울러 클래스는 객체의 속성뿐만 아니고 관련 기능도 가지고 있을 수 있다고 언급했었습니다. 애플리케이션 내에서 해당 객체를 이용하여 활용하고 싶은 기능들을 추가하는 것이죠.
class Person {
int age;
String name;
void wakeUp() {
System.out.println(this.name + "야! 힘찬 하루가 시작되었다!");
}
}
이렇게 객체와 관련된 동작이나 행위는 멤버 함수 혹은 메서드라고 합니다. 사실 멤버 함수라고 부르시는 분은 아직 한 번도 못 봤고, 대부분 메서드라고 부릅니다. 조금 더 첨언하자면, 메서드는 일반적인 프로그래밍 개념에 있는 '함수'에 객체 지향 개념이 포함된 용어입니다. Person이라는 클래스(설계도) 안에 정의한 함수는 그 설계도를 통해 만들어진 객체만 사용할 수 있게 되는 겁니다. 그러한 함수를 메서드라고 부릅니다.
객체에 대한 설명에서 언급했던 서적 <객체지향의 사실과 오해>에서는 객체 지향에서 중요한 3가지 개념으로 협력과 역할, 책임을 꼽았습니다. 애플리케이션은 결국 상호작용하며 협력하는 자율적인 객체들로 구성되어야 하며, 각 객체들에게 적절한 역할과 책임을 부여하여 시스템을 분할하는 방식으로 설계해야 한다는 것입니다.
클래스는 위에서 언급한 주요 개념들이 먼저 확립된 이후, 객체들의 협력 관계를 코드로 옮기는 도구라고 할 수 있습니다. 적절한 객체에게 적절한 책임을 할당하는 올바른 설계가 우선시 되어야 바람직한 클래스가 만들어집니다.
인스턴스 (Instance)
소프트웨어적으로 객체를 구현하기 위해서는 클래스라는 설계도를 만들어야 한다고 했습니다. 인스턴스는 이 클래스를 바탕으로 객체를 실체화한 것입니다. 그렇게 실체화하는 것을 일반적으로 "클래스를 인스턴스화하다"라고 표현합니다. Java에서는 다양한 방식으로 인스턴스를 만들어낼 수 있는데, 가장 간단한 방식은 new 예약어를 통해 생성자를 호출하는 것입니다. 코드로 살펴보겠습니다.
Person person = new Person();
위 한 줄은 사실 상당히 복잡한 과정을 내포하고 있습니다. 앞서 언급했던 "객체를 실체화하다" 혹은 "클래스를 인스턴스화하다"와 같은 표현은 바로 이 과정을 의미합니다. 대입 연산자 = 를 기준으로 좌/우를 구분하여 확인해보겠습니다.
1. 대입 연산자 = 우측에 있는 new Person(); 를 통해 Person 클래스를 바탕으로 한 인스턴스를 만듭니다. 새롭게 생성된 인스턴스는 age와 name이라는 멤버 변수를 가지고 있는데, 이것들은 힙 메모리에 생성됩니다. 즉, new 예약어를 통해 클래스 생성자를 호출하면 인스턴스가 힙 메모리에 생성된다고 할 수 있습니다. (참고로 인스턴스가 가지고 있어야 할 메서드에 대한 정보는 인스턴스 생성 시 따로 힙 영역의 메모리를 차지하는 것이 아니고 Method 영역에 저장되어 있습니다. 이 부분이 어렵다면 나중에 JVM의 메모리 구조를 살펴봐주시고, 일단은 넘어갑시다.)
2. 대입 연산자 = 좌측에 있는 Person person은 Person이라는 클래스 자료형으로 person 변수를 선언한 것입니다. 이 변수는 지역 변수이기 때문에 스택 메모리에 생성됩니다.
3. 대입 연산자 = 를 통해 우측의 피연산자인 인스턴스의 메모리 주소 값(=해시 코드값)을 좌측의 피연산자인 참조 변수에 할당합니다. 좌측의 피연산자인 변수 person을 참조 변수라고 부르는데, 인스턴스의 메모리 주소 값을 참조하고 있기 때문입니다.
위 과정을 그림으로 표현하면 다음과 같습니다.
결론
객체
- 현실 속의 객체: 세상에 존재하는 의사나 행위가 미치는 대상
- 소프트웨어 관점에서의 객체: 현실 세계에서의 객체와 유사하지만 그 이상의 창조물
클래스
- '객체를 코드로 구현한 것' 혹은 '객체들의 협력 관계를 코드로 옮기는 도구'
- 소프트웨어 관점에서의 객체를 만들기 위한 설계도
인스턴스
- 클래스를 바탕으로 객체를 실체화한 것
- 실제로 메모리에 할당된 객체
참고 자료
1. 서적 <Do it! 자바 프로그래밍>
2. 서적 <객체지향의 사실과 오해>
3. kkennib님 블로그 - 객체와 인스턴스의 차이
'Dev > Java' 카테고리의 다른 글
[Java] 배열(Array) vs. 배열리스트(ArrayList) vs. 연결리스트(LinkedList) (2) | 2021.03.30 |
---|---|
[Java] gradle 환경에서 JMH를 사용하여 벤치마킹하기 (0) | 2021.03.22 |
[Java] 람다식에서 메서드 참조/생성자 참조 사용법 (0) | 2021.03.19 |