개요
객체(Object)란 속성과 동작으로 구성되어 있는 것으로,
Java에서는 속성과 동작을 각각 필드(Field)와 메소드(Method)라고 부릅니다.
현실에서 사람이 혼자 행동하지 않고 서로가 상호 작용하며 살아가는 것처럼,
프로그램 속의 객체도 서로가 상호 작용하며 코드가 진행됩니다.
객체는 메소드를 통해 상호 작용하며, 이를 메소드 호출이라고 합니다.
클래스(Class)는 객체 생성을 위한 필드와 메소드의 정의가 포함됩니다.
따라서, 클래스는 객체를 만들기 위한 틀이라고 비유할 수 있습니다.
클래스로부터 만든 객체를 해당 클래스의 인스턴스(Instance)라고 부르며,
클래스를 통해 객체를 만드는 것을 인스턴스화라고 합니다.
객체를 하나씩 조립해서 완성된 프로그램을 만드는 기법을 객체 지향 프로그래밍(OOP: Object-Originated Programming)이라고 부릅니다.
클래스 선언 및 객체 생성
클래스 선언
지금까지 사용한 모든 예제는 클래스였습니다.
클래스는 객체를 만들기 위한 틀이지만,
지금까지는 객체로 만들지 않고 main 메소드만 사용하여 코드 실행을 목적으로 클래스를 이용했습니다.
main 메소드는 Java 프로그램에서 진입점(Entry Point)의 역할을 하여,
프로세서가 코드에 진입해서 실행을 시작할 수 있게 합니다.
main 메소드가 있는 클래스를 실행 클래스라고 부릅니다.
한편, main 메소드가 없는 클래스는 실행 진입점이 없기 때문에, 객체 생성 과정을 거쳐 사용해야 합니다.
클래스는 캐멀 케이스(Camel Case)로 보통 작명됩니다.
캐멀 케이스란, 단어의 시작부분을 대문자로 표기하는 것입니다.
HelloWorld, JavaClass와 같이 표기하는 것을 예시로 들 수 있습니다.
위 클래스에는 실행 진입점의 역할을 담당하는 main 메소드가 없으므로, 실행이 되지 않습니다.
소스 파일 자체가 클래스가 아닌, 클래스를 담는 역할을 하므로,
위와 같이 한 소스 파일에 클래스를 두 개 이상 선언할 수 있고,
이 경우 선언된 클래스의 개수만큼 바이트코드가 생성됩니다.
위의 코드를 실행할 경우, MyClass.class와 MyClass2.class라는 두 개의 바이트코드가 생성됩니다.
하지만, public 접근 제어자는 소스 파일의 이름과 동일한 클래스에만 붙일 수 있으므로,
하나의 소스 파일에는 하나의 클래스만을 선언하는 것을 권장합니다.
객체 생성
객체를 생성하기 위해서는 우선 클래스를 선언해야 합니다.
흔히, 객체를 붕어빵, 클래스를 붕어빵 틀로 비유하는데,
붕어빵을 만들기 위해서는 붕어빵 틀이 필요한 것과 같은 이유입니다.
클래스로부터 객체를 생성하려면, new 연산자를 사용해야 합니다.
new 연산자 뒤에는 생성자(constructor)가 와야 하는데, 여기서 생성된 객체는 메모리 힙(heap)에 생성됩니다.
new 연산자를 사용하면, 해당 객체의 주솟값을 스택 영역에 반환(return)하며,
객체도 래퍼런스 변수이므로, 래퍼런스 변수와 같이 해당 주솟값을 참조하게 됩니다.
c1과 c2라는 객체가 생성하였습니다.
c1과 c2는 MyClass 클래스의 인스턴스이고, new 연산자를 사용한만큼 객체가 메모리에 생성됩니다.
같은 공장에서 만들어진 두 개의 핸드폰이 서로 다르듯,
같은 클래스에서 생성된 두 개의 객체는 자신만의 고유한 정보를 가지면서 메모리에서 활동합니다.
요약하자면, MyClass 클래스로부터 만들어진 MyClass 객체는 heap 영역에 저장되고,
stack 영역에 저장된 c1과 c2는 서로 다른 두 개의 MyClass 객체의 주솟값을 가지며,
heap 영역에 있는 MyClass 객체를 참조합니다.
그리고, 둘은 같은 클래스로부터 만들어졌을지라도 서로 완전 독립 관계입니다.
클래스는 크게 라이브러리 클래스와 실행 클래스로 나뉩니다.
라이브러리 클래스는 다른 클래스에서 이용될 목적으로 설계되는 클래스고,
실행 클래스는 코드를 실행하기 위해 설계되는 클래스입니다.
실행 클래스는 진입점인 main 메소드를 포함하고, 반드시 1개만 존재하여야 합니다.
위의 경우, MyClass.java는 라이브러리 클래스,
CreatingObject.java는 실행 클래스가 됩니다.
실행 클래스 내에서도 라이브러리 클래스의 역할을 겸할 수 있습니다.
클래스의 구성 멤버
클래스는 객체에 포함되는 것을 정의합니다.
구성 멤버에는 크게 필드(Field), 생정자(Constructor), 메소드(Method)가 있습니다.
필드
필드는 객체의 고유한 정보를 저장하는 곳으로, 변수(Variable)로 비유될 수 있습니다.
하지만, 필드와 변수는 다른 개념이며, 생성자 또는 메소드 내에서 선언과 소멸되는 변수와 달리,
필드는 객체 내에서 사용되며, 객체가 소멸하기 전까지 계속 존재한다는 차이점이 있습니다.
필드의 경우 클래스 내 어디서든 존재할 수 있지만,
예외적으로 생성자와 메소드의 내부에는 존재할 수 없습니다.
생성자와 메소드 내에 존재하는 것은 필드라고 부르지 않고,
지역 변수(Local Variable)라고 부릅니다.
필드는 ‘클래스 멤버 변수’라고도 부르기도 합니다.
필드는 프리미티브 타입(int, float, boolean 등)뿐만 아니라,
래퍼런스 타입(배열, 인터페이스 등)으로도 사용할 수 있습니다.
필드는 따로 초기화를 하지 않는 경우, 기본값으로 자동 초기화합니다.
위와 같이 클래스 내부에서 필드를 호출하기 위해서는 기존 변수를 불러내는 방식과 동일하게,
필드명을 입력하여 호출할 수 있습니다. 실행 클래스가 아니므로, 코드가 실행되지 않습니다.
📢 출력 예시
12345678
이 학교는 운영 중입니다.
이 학교는 폐교되었습니다.
다른 클래스에서 FieldClass의 필드값을 가져오고 싶은 경우, 먼저 FieldClass 객체를 생성해야 합니다.
객체가 생성되기 전까지, 필드는 생성되지 않습니다.
필드 값을 호출하는 것은 위와 같이, ‘객체 이름.필드 이름’의 형태로 호출할 수 있습니다.
생성자
생성자는 클래스명과 이름이 동일한 메소드이며,
객체를 생성할 때 객체를 초기화하는 역할을 합니다.
생성자는 다른 메소드들과 달리 자료형이 없고, 반환하는 것도 없습니다.
생성자는 new 연산자를 통해 클래스로부터 객체를 생성할 때 호출됩니다.
객체 초기화는, 필드를 초기화하거나 메소드를 호출하여 객체를 사용할 준비를 하는 것을 말하며,
생성자가 실행되면, 객체는 heap 영역에 생성되고 stack 영역에 그 객체의 주솟값을 저장하게 됩니다.
모든 클래스는 적어도 하나의 생성자를 가지고 있으며,
클래스 내에 생성자를 명시하지 않은 경우, 기본 생성자(Default Constructor)가 바이트코드 내에 생성됩니다.
따라서, 직접 생성자를 명시하지 않아도 객체를 생성할 때 new 연산자 뒤에 생성자를 호출할 수 있습니다.
그러나, 클래스 내에 생성자를 하나라도 명시한 경우 기본 생성자가 생성되지 않습니다.
생성자를 선언할 때, a_, b_와 같이 매개변수 이름을 지은 이유는,
생성자(메소드)가 같은 이름의 변수를 호출할 때는 매개변수를 먼저 호출하기 때문에,
코드에 충돌이 있을 수 있기 때문입니다.
하지만, 코드가 길어질수록 간결한 것이 좋기 때문에
생성자의 매개변수 이름과 필드의 이름을 동일하게 하는 것을 권장합니다.
충돌을 막기 위해, this를 사용하는데, this는 객체 자신의 참조를 의미합니다.
this를 사용 경우, 이는 필드가 됩니다.
생성자 오버로드
C++에서는 매개변수만 다르다면, 함수명이 같아도 여러 개의 함수를 정의할 수 있습니다.
이를 오버로딩(overloading)이라고 합니다.
Java도 마찬가지로, 생성자와 메소드를 오버로딩할 수 있으며,
오버로딩하려면 다음 중 하나의 조건을 만족해야 합니다.
- 매개변수 자료형이 다름.
ex) void f (int a) / void f (double a) - 두 번째 매개변수의 자료형이 다름.
ex) void f (int a, int b) / void f (int a, double b) - 매개변수의 개수가 다름.
ex) void f (int a) / void f (int a, int b) - 매개변수의 종류는 같지만, 순서가 다름.
ex) void f (int a, double b) / void f (double a, int b)
그러나, 다음과 같은 조건인 경우 함수를 오버로딩할 수 없습니다.
- 매개변수의 이름이 다르지만, 자료형이 같음.
ex) void f (int a) / void f (int b) - return 타입이 다르지만, 매개변수의 자료형이 같음.
ex) void f (int a) / int f (int a)
* 자료형이 다르면 오버로딩 가능
위의 코드에서는 특정 필드값을 입력하지 않아도, 객체가 초기화될 수 있습니다.
오버로딩한 생성자가 많은 경우, 중복된 코드가 발생할 수 있습니다.
이때, 위와 같이 this() 코드를 사용하여, 다른 생성자 자체를 오버로딩할 수도 있습니다.
메소드
메소드는 객체의 동작을 의미하며, 다른 언어에서 함수(function)로 비유될 수 있습니다.
메소드는 자신의 필드값을 변경하는 역할을 할 뿐만 아니라,
다른 객체를 생성하여 다양한 상호 작용을 하는 수단이기도 합니다.
외부로부터 매개변수를 받아 실행에 이용하고, 그 결과를 외부로 반환할 수도 있습니다.
메소드 선언은 선언부(Method Signature)와 실행 블록으로 구성됩니다.
선언부에는 리턴 타입, 메소드 이름, 매개 변수 선언이 포함됩니다.
위와 같이 메소드를 선언하고 사용할 수 있습니다.
매개변수의 개수를 모를 경우의 메소드 선언
주어진 수의 합을 구하는 것과 같이, 메소드의 매개변수의 개수를 모를 경우,
매개변수를 배열 타입으로 선언하거나, …을 사용해서 선언할 수 있습니다.
📢 출력 예시
sum1의 values1 결과: 6
sum2의 values1 결과: 6
sum1의 values2 결과: 15
sum2의 values2 결과: 15
매개변수를 배열 타입으로 선언할 경우 메소드를 호출하기 전에 배열을 생성해야 하지만,
…을 사용할 경우, 따로 배열을 생성하지 않고 값만 전달하므로,
배열을 생성할 필요가 없다는 장점을 가지고 있습니다.
객체 내부에서의 메소드 호출
객체 내부에서 메소드를 호출할 경우, 메소드의 이름만 명시하면 됩니다.
자신의 객체 내에서 호출을 바로 할 수 있기 때문입니다.
메소드 오버로딩
메소드도 마찬가지로 생성자처럼 오버로딩할 수 있습니다.
위와 같이 자료형이 달라도 오버로딩을 통해 plus 메소드를 호출할 수 있습니다.
this 참조
다음은 cpp에서 사용한 this 포인터 예시입니다.
📢 출력 예시
class1 pointer: 0000009C2ED7F644 ✨
class1 this: 0000009C2ED7F644 ✨
✨는 매 실행마다 변경될 수 있는 값을 의미합니다.
C++과 Java는 this를 이용합니다.
이들은 모두 객체 자기 자신의 주솟값을 가리킨다는 공통점을 가지지만,
둘은 몇 가지 차이를 보입니다.
C++에서 this는 주솟값을 가리키는 포인터(pointer)이므로, this 포인터를 사용할 때는,
화살표(->)를 사용합니다. 그런 반면, Java에서의 this는 참조입니다.
C++에서는 메소드가 호출될 때, 객체로부터 주솟값을 넘겨받게 되는데, 이 값이 바로 this입니다.
직관적으로 본다면, 객체 안에 속한 그 안에 있는 메소드들은 하나의 객체 안에 포함된 것처럼 보이지만,
실제로 객체가 정의될 때, 객체가 메모리에 할당이 되고, 객체 안에 있는 메소드도 메모리에 할당됩니다.
이때, 메소드는 객체가 할당된 주소가 아닌, 다른 주소에 할당되게 됩니다.
즉, 메모리상으로 메소드는 객체 안에 포함된 것이 아닙니다.
그렇기 때문에, 멤버 메소드가 호출될 경우 객체의 주솟값을 받게 되는 것입니다.
Java는 객체가 처음 생성될 때, heap 영역에 객체를 저장하고,
스택 영역에는 객체를 참조할 수 있는 변수를 저장하게 됩니다.
하지만, 객체가 생성되기 이전에 처음으로 클래스가 참조될 때,
자바 가상 머신(JVM)은 해당 클래스를 로딩하고, 클래스의 바이트코드가 method 영역에 저장됩니다.
method 영역에 저장된 메소드가 heap 영역에 저장된 객체를 this를 통해 참조하여
해당 인스턴스의 데이터에 접근할 수 있게 됩니다.
객체가 메소드를 호출할 때, 객체는 메소드 영역에 저장된 자신의 클래스에 정의된 메소드 코드를 사용하는데,
메소드는 heap 영역에 있는 객체의 데이터(필드)에 접근할 수 있으며,
이를 통해 객체의 상태를 읽거나 변경할 수 있게 됩니다.
메소드 내에서, this는 현재 객체를 가리키는 참조를 담당하며,
이를 통해 메소드는 실행 중인 객체의 필드에 접근하고, 객체의 다른 메소드를 호출할 수 있습니다.
정리
내용이 길어, 분할하여 올립니다.
다음에는 정적 멤버와 접근 제어 연산자 등을 다룹니다.
'Java > 기본 개념' 카테고리의 다른 글
7. 클래스와 객체 (2) (0) | 2024.05.23 |
---|---|
5. 조건문과 반복문 (0) | 2024.05.20 |
4. 연산자 (0) | 2024.05.17 |
3. 변수와 자료명 (2) (0) | 2024.05.16 |
2. 변수와 자료명 (1) (1) | 2024.05.15 |