자바 객체
1. 개요
1. 개요
자바에서 객체는 객체 지향 프로그래밍의 핵심 개념으로, 클래스의 인스턴스이다. 이는 특정 클래스에 정의된 속성과 행동을 가진 구체적인 실체를 의미한다. 객체는 new 키워드와 생성자를 사용하여 메모리에 생성되며, 프로그램에서 실제로 작동하는 기본 단위가 된다.
객체는 주로 상태와 행동으로 구성된다. 상태는 필드라는 변수에 저장되며, 행동은 메서드를 통해 정의된다. 예를 들어, '자동차'라는 클래스에서 생성된 객체는 '현재 속도', '연료량' 같은 상태와 '가속()', '정지()' 같은 행동을 가질 수 있다. 각 객체는 고유한 식별자를 가지며, 이를 통해 서로 구분된다.
자바의 객체는 캡슐화, 상속, 다형성, 추상화라는 객체 지향 원리를 구현하는 기반이 된다. 캡슐화를 통해 데이터와 메서드를 하나로 묶고 내부 구현을 숨길 수 있으며, 상속을 통해 기존 클래스의 속성과 메서드를 새로운 클래스가 물려받을 수 있다. 또한 다형성은 하나의 인터페이스를 통해 다양한 타입의 객체를 동일하게 처리할 수 있게 한다.
이러한 객체의 생성, 사용, 관리 원리는 자바 가상 머신이 담당하며, 사용이 끝난 객체는 가비지 컬렉션에 의해 자동으로 메모리에서 제거된다. 객체는 자바 애플리케이션을 구성하는 가장 기본적이고 필수적인 요소이다.
2. 객체의 정의와 특징
2. 객체의 정의와 특징
2.1. 클래스와의 관계
2.1. 클래스와의 관계
자바에서 객체는 클래스의 구체적인 실체, 즉 인스턴스이다. 클래스는 객체를 만들기 위한 설계도나 틀에 해당하며, 객체는 이 설계도를 바탕으로 메모리에 실제로 생성된 존재이다. 하나의 클래스로부터 동일한 구조를 가진 여러 개의 객체를 생성할 수 있다.
예를 들어, '자동차'라는 클래스가 있다면, 이 클래스는 색상, 모델명 같은 속성(필드)과 주행, 정지 같은 기능(메서드)을 정의한다. 이 클래스를 사용하여 new Car()와 같이 new 연산자로 생성된 실제 자동차 객체들은 각각 고유한 색상 값을 가지며 주행 기능을 수행할 수 있다. 이처럼 클래스는 추상적인 형식을, 객체는 구체적인 실체를 나타낸다.
객체는 클래스에 정의된 필드와 메서드를 물려받아 자신의 상태와 행동을 가지게 된다. 따라서 객체를 효과적으로 사용하기 위해서는 먼저 해당 객체의 청사진인 클래스를 정확히 이해하는 것이 중요하다. 이 관계는 객체 지향 프로그래밍의 기본 개념을 이루는 핵심이다.
2.2. 상태와 행동
2.2. 상태와 행동
자바에서 객체의 핵심은 상태와 행동으로 구성된다. 상태는 객체가 가지고 있는 데이터를 의미하며, 필드 또는 인스턴스 변수라고 불리는 변수들로 표현된다. 예를 들어, 자동차 객체의 상태는 현재속도, 연료량, 차량번호와 같은 값들로 나타낼 수 있다. 이 상태는 객체가 생성될 때 초기화되며, 객체의 생존 기간 동안 변경될 수 있다.
행동은 객체가 수행할 수 있는 동작을 정의하며, 메서드를 통해 구현된다. 메서드는 객체의 상태를 읽거나 변경하는 로직을 포함한다. 앞선 자동차 객체의 경우 가속(), 감속(), 연료충전() 등의 메서드가 행동에 해당한다. 객체의 행동을 호출함으로써 객체의 상태가 변화하거나, 특정 작업이 수행된다.
상태와 행동은 밀접하게 연관되어 있다. 객체의 행동은 현재 자신이 가진 상태에 영향을 받으며, 동시에 행동의 결과는 상태를 변경시킨다. 예를 들어 가속() 메서드는 현재속도라는 상태를 증가시키는 방식으로 동작한다. 이렇게 데이터와 그 데이터를 조작하는 코드를 하나의 단위로 묶는 것은 객체 지향 프로그래밍의 기본 원리인 캡슐화를 실현하는 핵심적인 방법이다.
따라서 자바에서 객체를 설계한다는 것은 해당 객체가 어떤 상태(필드)를 유지하고, 외부에 어떤 행동(메서드)을 제공할지를 결정하는 작업이다. 잘 정의된 상태와 행동의 집합은 객체의 책임을 명확히 하고, 프로그램의 모듈성과 유지보수성을 높이는 데 기여한다.
3. 객체 생성
3. 객체 생성
3.1. new 연산자
3.1. new 연산자
new 연산자는 자바에서 객체를 생성하는 데 사용되는 핵심 연산자이다. 이 연산자는 클래스를 설계도 삼아 실제 사용 가능한 인스턴스를 메모리에 할당하고 초기화하는 역할을 한다. new 연산자를 사용하는 일반적인 문법은 클래스명 변수명 = new 생성자(); 형태이다. 여기서 new 뒤에 오는 생성자 호출은 객체의 초기 상태를 설정하는 데 필수적이다.
new 연산자의 동작 과정은 다음과 같다. 먼저, JVM은 해당 클래스가 메모리에 로드되어 있는지 확인하고, 필요한 경우 클래스 로더를 통해 로드한다. 그 후 힙 영역에 객체가 차지할 메모리 공간을 할당한다. 이어서 생성자를 실행하여 객체의 필드를 초기값으로 설정한다. 마지막으로, 새로 생성된 객체의 참조를 반환하여 변수에 저장할 수 있게 한다. 이 과정을 통해 클래스에 정의된 속성과 행동을 가진 구체적인 객체가 탄생한다.
new 연산자를 통해 생성된 각 객체는 서로 독립적인 메모리 공간을 가지므로, 하나의 객체 상태를 변경해도 다른 객체에 영향을 미치지 않는다. 이는 객체 지향 프로그래밍의 기본 원리 중 하나를 구현하는 방식이다. 모든 객체 생성은 new 연산자와 생성자의 조합으로 이루어지며, 이는 자바의 객체 지향적 특성을 구체화하는 핵심 메커니즘이다.
3.2. 생성자
3.2. 생성자
생성자는 객체가 생성될 때 호출되는 특별한 메서드이다. 생성자의 주된 역할은 새로 생성된 객체의 초기 상태를 설정하는 것이다. 생성자는 클래스의 이름과 동일한 이름을 가지며, 반환 타입을 명시하지 않는다. 객체는 new 연산자와 함께 생성자를 호출하여 생성된다. 예를 들어, Car myCar = new Car();와 같은 코드에서 Car() 부분이 생성자 호출에 해당한다.
생성자는 필요에 따라 매개변수를 받아 객체의 필드를 초기화할 수 있다. 이러한 생성자를 매개변수가 있는 생성자라고 부른다. 만약 클래스 내에 어떤 생성자도 명시적으로 정의하지 않으면, 자바 컴파일러가 자동으로 기본 생성자를 제공한다. 기본 생성자는 매개변수가 없으며, 객체의 필드를 기본값으로 초기화한다. 사용자가 하나라도 생성자를 정의하면, 컴파일러는 기본 생성자를 제공하지 않는다.
생성자는 메서드 오버로딩의 원리를 적용하여, 같은 이름(클래스 이름)으로 여러 개를 정의할 수 있다. 이는 서로 다른 매개변수 목록을 가진 여러 생성자를 정의하는 것을 의미하며, 객체를 생성할 때 제공하는 인자에 따라 적절한 생성자가 호출된다. 이를 통해 다양한 초기 조건을 가진 객체를 유연하게 생성할 수 있다.
또한, 생성자 내부에서 this() 키워드를 사용하여 같은 클래스의 다른 생성자를 호출할 수 있다. 이는 생성자 코드의 중복을 줄이고 초기화 로직을 한 곳에 집중시키는 데 유용하다. 생성자는 객체 지향 프로그래밍의 기본 개념인 캡슐화를 구현하는 데 중요한 역할을 하며, 객체가 유효한 상태로 시작되도록 보장한다.
4. 객체 사용
4. 객체 사용
4.1. 필드 접근
4.1. 필드 접근
객체의 필드에 접근하는 것은 객체의 상태를 읽거나 변경하는 기본적인 작업이다. 필드 접근은 일반적으로 점 연산자(.)를 사용하여 이루어진다. 점 연산자의 왼쪽에는 객체를 참조하는 참조 변수를, 오른쪽에는 접근하려는 필드의 이름을 명시한다. 이를 통해 특정 객체의 내부 데이터를 외부에서 조작할 수 있다.
필드에 대한 접근 권한은 접근 제어자에 의해 결정된다. public으로 선언된 필드는 클래스 외부의 어떤 코드에서도 직접 접근이 가능하다. 반면, private으로 선언된 필드는 해당 클래스 내부의 코드에서만 접근할 수 있으며, 이는 캡슐화 원칙의 핵심이다. 일반적으로 객체의 데이터는 private으로 보호하고, 필요한 경우 getter 메서드와 setter 메서드라는 공개된 메서드를 통해 간접적으로 접근하도록 설계한다.
객체의 필드는 인스턴스 변수와 클래스 변수(static 필드)로 구분된다. 인스턴스 변수에 접근하려면 반드시 객체의 인스턴스가 생성되어야 하며, 해당 인스턴스를 가리키는 참조를 통해 접근한다. 반면, 클래스 변수는 객체 생성 없이도 클래스 이름 자체를 통해 접근할 수 있다. 예를 들어, 객체참조.필드명 방식은 인스턴스 변수 접근에, 클래스명.필드명 방식은 클래스 변수 접근에 주로 사용된다.
4.2. 메서드 호출
4.2. 메서드 호출
메서드 호출은 객체의 행동을 실행하는 과정이다. 객체는 클래스에 정의된 메서드를 통해 특정 작업을 수행할 수 있으며, 이 메서드를 실행시키는 행위를 메서드 호출이라고 한다. 호출은 일반적으로 참조 변수와 점(.) 연산자를 사용하여 참조변수.메서드명(인자)의 형태로 이루어진다. 이때 메서드에 필요한 입력값을 인자 또는 매개변수로 전달할 수 있으며, 메서드는 실행 결과를 반환값으로 돌려줄 수 있다.
메서드 호출은 객체의 상태를 변경하거나, 객체 간의 상호작용을 가능하게 하는 핵심 메커니즘이다. 예를 들어, 계좌.입금(10000)이라는 호출은 계좌 객체의 잔액 필드를 증가시키는 행동을 수행한다. 또한 정적 메서드는 객체 생성 없이 클래스 이름을 통해 직접 호출할 수 있다는 점이 인스턴스 메서드와 다르다.
메서드 호출 과정에서 다형성이 중요한 역할을 한다. 상위 클래스 타입의 참조 변수가 하위 클래스의 객체를 참조할 때, 변수의 타입이 아닌 실제 객체의 타입에 정의된 메서드가 실행된다. 이는 메서드 오버라이딩과 결합되어 프로그램의 유연성을 크게 향상시킨다.
5. 객체 지향 원리와의 연관성
5. 객체 지향 원리와의 연관성
5.1. 캡슐화
5.1. 캡슐화
자바에서 캡슐화는 객체의 필드와 메서드를 하나의 단위로 묶고, 객체의 내부 데이터를 외부로부터 직접 접근하지 못하도록 감추는 것을 말한다. 이는 객체 지향 프로그래밍의 핵심 원리 중 하나로, 데이터를 보호하고 객체의 무결성을 유지하는 데 목적이 있다.
캡슐화를 구현하는 주요 수단은 접근 제어자이다. private 접근 제어자를 사용하여 필드를 선언하면, 해당 필드는 선언된 클래스 내부에서만 직접 접근이 가능하다. 외부에서 이 필드의 값을 읽거나 변경하려면, 반드시 public으로 선언된 게터와 세터 메서드와 같은 인터페이스를 통해야 한다. 이를 통해 객체는 외부에 노출할 필요가 없는 내부 구현을 숨기고, 데이터에 대한 유효성 검사와 같은 제어 로직을 메서드 내에 중앙 집중화할 수 있다.
캡슐화는 높은 응집도와 낮은 결합도를 달성하는 데 기여한다. 객체가 자신의 상태를 스스로 관리하게 함으로써 모듈의 독립성을 높이고, 내부 구현이 변경되더라도 이를 사용하는 외부 코드에 미치는 영향을 최소화한다. 이는 소프트웨어 유지보수와 재사용성을 크게 향상시키는 장점이 있다.
5.2. 상속
5.2. 상속
상속은 자바에서 객체 지향 프로그래밍의 핵심 원리 중 하나로, 기존에 존재하는 클래스의 속성과 기능을 새로운 클래스가 물려받아 사용하거나 확장할 수 있게 하는 메커니즘이다. 이를 통해 코드의 재사용성을 높이고, 계층적인 클래스 구조를 형성하여 유지보수와 확장이 용이한 프로그램을 설계할 수 있다. 상속을 받는 새로운 클래스를 자식 클래스 또는 서브클래스라고 하며, 상속을 해주는 기존 클래스를 부모 클래스 또는 슈퍼클래스라고 한다.
자바에서 상속은 extends 키워드를 사용하여 구현한다. 서브클래스는 슈퍼클래스의 public 및 protected 접근 제어자를 가진 필드와 메서드를 자동으로 상속받는다. 단, private 멤버는 상속되지 않으며, 생성자도 상속 대상이 아니다. 서브클래스는 상속받은 메서드를 그대로 사용하거나, 필요에 따라 메서드 오버라이딩을 통해 자신의 방식으로 재정의할 수 있다. 또한, 서브클래스는 자신만의 새로운 필드와 메서드를 추가하여 기능을 확장할 수 있다.
상속의 가장 큰 장점은 중복 코드를 제거하고 공통된 로직을 한 곳에서 관리할 수 있다는 점이다. 예를 들어, 동물이라는 슈퍼클래스를 만들고, 이름과 나이 같은 공통 속성과 먹다() 같은 공통 행동을 정의하면, 강아지나 고양이 같은 서브클래스는 이러한 기본 속성과 행동을 물려받은 상태에서 짖다()나 야옹하다() 같은 고유한 행동만 추가하면 된다. 이는 다형성을 구현하는 기반이 되며, 슈퍼클래스 타입의 참조 변수로 다양한 서브클래스 객체를 참조할 수 있게 한다.
자바는 단일 상속만을 지원한다. 즉, 하나의 클래스는 오직 하나의 직접적인 슈퍼클래스만 가질 수 있다. 이는 다중 상속에서 발생할 수 있는 모호성(예: 동일한 이름의 메서드를 두 부모 클래스가 가진 경우)을 방지하기 위한 설계적 선택이다. 다만, 인터페이스를 이용하면 하나의 클래스가 여러 개의 인터페이스를 구현(implements)할 수 있어, 다중 상속의 일부 이점을 얻을 수 있다.
5.3. 다형성
5.3. 다형성
다형성은 하나의 인터페이스나 메서드 호출이 여러 가지 다른 형태로 실행될 수 있는 객체 지향 프로그래밍의 핵심 원리이다. 자바에서 다형성은 주로 상속 관계와 메서드 오버라이딩, 그리고 참조 변수의 특성을 통해 구현된다. 상위 클래스 타입의 참조 변수가 하위 클래스의 인스턴스를 참조할 수 있으며, 이때 실제 호출되는 메서드는 참조 변수의 타입이 아닌 생성된 인스턴스의 클래스에 정의된 메서드가 된다. 이를 통해 코드는 일반적인 상위 클래스 타입을 사용하여 작성되지만, 실행 시점에는 구체적인 하위 클래스의 동작을 수행할 수 있다.
다형성을 활용하는 대표적인 방법은 메서드 오버라이딩이다. 하위 클래스에서 상위 클래스로부터 상속받은 메서드를 재정의하면, 상위 클래스 타입으로 참조하더라도 하위 클래스에서 오버라이딩한 메서드가 실행된다. 또한, 추상 클래스의 추상 메서드나 인터페이스에 선언된 메서드를 다양한 클래스가 각자의 방식으로 구현함으로써 다형성을 실현한다. 이는 시스템의 확장성을 높이고, 유지보수를 용이하게 만든다.
다형성의 이점은 코드의 재사용성과 유연성을 극대화한다는 점이다. 새로운 하위 클래스를 추가하더라도 기존에 상위 클래스 타입을 사용하는 코드를 수정할 필요가 없으며, 동일한 메서드 호출로 다양한 객체의 고유한 행동을 유도할 수 있다. 예를 들어, 동물이라는 상위 클래스 타입의 배열에 개, 고양이, 새와 같은 다양한 하위 클래스 객체를 저장하고, 소리내기()라는 메서드를 호출하면 각 객체의 클래스에 정의된 방식으로 소리가 출력된다. 이는 객체 지향 설계의 중요한 패턴인 의존성 주입과도 깊이 연관되어 있다.
6. 객체의 생명주기
6. 객체의 생명주기
6.1. 가비지 컬렉션
6.1. 가비지 컬렉션
자바에서 객체는 new 연산자를 통해 힙 메모리 영역에 생성된다. 프로그램이 실행되는 동안 수많은 객체가 생성되고, 더 이상 사용되지 않는 객체는 메모리에서 제거되어야 한다. 자바는 C++와 같은 언어와 달리, 개발자가 명시적으로 메모리를 해제하지 않는다. 대신 가비지 컬렉터가 자동으로 더 이상 참조되지 않는 객체를 찾아 메모리를 회수하는 가비지 컬렉션 기능을 제공한다.
가비지 컬렉션의 대상이 되는 객체는 어떤 참조 변수로도 접근할 수 없는 상태, 즉 '가비지'가 된 객체이다. 예를 들어, 특정 객체를 참조하던 변수에 null이 할당되거나, 변수가 스코프를 벗어나면, 해당 객체는 루트 세트로부터 연결 고리가 끊어진다. 자바의 가비지 컬렉터는 주기적으로 또는 특정 조건에서 실행되어 이러한 사용되지 않는 객체를 식별하고, 그 객체가 점유하고 있던 힙 메모리를 자동으로 회수하여 재사용 가능하게 만든다.
가비지 컬렉션의 주요 알고리즘으로는 Mark-and-Sweep, Generational Garbage Collection 등이 있다. 특히 Generational Garbage Collection은 대부분의 객체가 생성된 지 얼마 되지 않아 소멸한다는 경험적 관찰에 기반하여, 객체를 Young Generation과 Old Generation으로 나누어 관리함으로써 컬렉션 효율을 높인다. 이 과정에서 Minor GC와 Major GC(또는 Full GC)가 발생한다.
가비지 컬렐션은 메모리 관리의 부담을 줄여주지만, 실행 시점과 소요 시간을 개발자가 완전히 통제할 수 없다는 단점도 있다. 컬렉션 동작 중에는 애플리케이션의 모든 스레드가 일시 정지될 수 있어, 실시간 시스템이나 매우 짧은 지연 시간이 요구되는 환경에서는 주의가 필요하다. 이러한 문제를 완화하기 위해 다양한 가비지 컬렉터 구현체(예: Serial GC, Parallel GC, G1 GC, ZGC)가 제공되며, 애플리케이션의 특성에 맞게 선택할 수 있다.
