자바는 철저한 객체 지향 언어다. C가 함수 중심으로 절차를 짠다면, 자바는 객체들이 서로 메시지를 주고받으며 프로그램을 구성한다. 오늘은 객체 지향의 핵심인 클래스·객체·생성자·this·static을 정리했다.
1. 객체 지향과 클래스·객체
세상 모든 것은 고유한 속성(데이터)과 동작(기능)을 가진 객체로 볼 수 있다. 객체 지향은 이를 그대로 코드로 옮겨, 소프트웨어의 재사용성과 현실 모델링을 쉽게 한다. 그 4대 특성이 캡슐화·상속·다형성·추상화다.
- 클래스(Class): 객체의 설계도. 속성은 필드, 동작은 메서드로 정의한다.
- 객체(Object/Instance): 클래스를 new로 메모리에 구현한 실체. 한 클래스로 여러 객체를 만들 수 있다.
public class Circle {
int radius; // 필드 (속성)
public double getArea() { // 메서드 (동작)
return 3.14 * radius * radius;
}
public static void main(String[] args) {
Circle pizza = new Circle(); // ① 선언 + ② new로 생성
pizza.radius = 10; // ③ 점(.)으로 멤버 접근
System.out.println(pizza.getArea());
}
}
객체 활용은 항상 "① 레퍼런스 선언 → ② new로 생성 → ③ 점으로 멤버 접근"의 3단계를 거친다. C++에서 본 클래스·객체와 큰 틀은 같았는데, 자바는 new로만 객체를 만든다는 점이 통일돼 있어 오히려 단순했다.
2. 매개변수 전달: 기본형 vs 객체
메서드에 무엇을 넘기느냐에 따라 동작이 다르다. 이건 자바에서 매우 중요한 구분이다.
| 전달 대상 | 방식 | 원본 변경 |
|---|---|---|
| 기본형(int, double 등) | 값 복사 | 안 됨 |
| 객체 / 배열 | 레퍼런스(주소) 공유 | 됨 |
기본형은 값만 복사되어 메서드 안에서 바꿔도 원본은 그대로지만, 객체나 배열은 주소가 전달되어 메서드가 원본을 직접 고칠 수 있다. "이 메서드가 내 객체를 바꿀 수 있나?"를 늘 의식해야 하는 이유다. C의 값 호출/참조 호출이 자바에선 "기본형이냐 객체냐"로 갈린다는 게 핵심이었다.
3. 생성자와 this
객체를 생성할 때 자동 호출되어 멤버를 초기화하는 특별한 메서드다. 이름이 클래스 이름과 같고, 리턴 타입이 없다(void도 아님). 매개변수를 달리해 여러 개 오버로딩할 수 있다.
public class Book {
String title, author;
public Book(String t) { title = t; author = "작자미상"; } // 생성자 1
public Book(String t, String a) { title = t; author = a; } // 생성자 2
}
Book b1 = new Book("Java", "홍길동");
Book b2 = new Book("Bible"); // author는 "작자미상"
생성자를 하나도 안 만들면 컴파일러가 매개변수 없는 기본 생성자를 자동으로 넣어준다. 하지만 생성자를 하나라도 직접 선언하면 기본 생성자는 자동 생성되지 않는다. 그래서 매개변수 생성자만 만들어두고 new Book()을 호출하면 오류가 난다.
이 기본 생성자 함정을 직접 만났다. 매개변수 생성자만 만들고 new Book()을 호출했다가 컴파일이 안 돼서, "직접 선언하면 기본 생성자는 자동으로 안 생긴다"는 규칙을 그때 새겼다.
this는 "객체 자신"을 가리키는 레퍼런스다. 매개변수와 멤버 변수 이름이 같을 때 구분해준다. this()로 같은 클래스의 다른 생성자를 호출할 수도 있다.
public Circle(int radius) {
this.radius = radius; // this.radius=멤버, radius=매개변수
}
4. 메서드 오버로딩과 static
오버로딩은 같은 이름의 메서드를 매개변수 구성을 달리해 여러 개 두는 것이다. 이름+매개변수 구성을 시그니처라 하며, 리턴 타입은 오버로딩 구분과 무관하다.
void sum(int a, int b) { ... }
void sum(int a, int b, int c) { ... } // 개수 다름
void sum(double a, double b) { ... } // 타입 다름
static 멤버는 객체마다 따로 생기지 않고 클래스당 하나만 만들어져 모든 객체가 공유한다. 객체를 생성하지 않고도 클래스 이름으로 바로 접근한다(Math.abs 처럼). 단, static 메서드 안에서는 this를 쓸 수 없고 static 멤버에만 접근할 수 있다.
class Calc {
public static int abs(int a) { return a > 0 ? a : -a; }
}
System.out.println(Calc.abs(-5)); // 객체 없이 클래스 이름으로 호출
main이 static인 이유, Math.abs를 객체 없이 부르는 이유가 전부 "static은 클래스에 속한다"에서 나온다는 걸 알고 나니 흩어진 점들이 이어졌다.
5. 객체 소멸과 가비지 컬렉션
자바에는 C++의 delete 같은 객체 소멸 연산자가 없다. 생성(new)만 있고, 소멸은 JVM(Java Virtual Machine)이 맡는다. 가리키는 레퍼런스가 하나도 없어진 객체(가비지)를 JVM이 자동으로 찾아 메모리를 회수한다. C·C++에서 free/delete를 깜빡해 누수를 걱정하던 게 자바에선 사라진다는 게 큰 차이였다.
오늘 느낀 점
- C++에서 본 클래스·생성자·오버로딩이 자바에서도 거의 그대로 통해, 객체 지향 개념이 언어를 넘어 공유된다는 걸 느꼈다.
- 기본 생성자 함정을 직접 겪고서야 규칙이 박혔다. "생성자를 직접 만들면 기본 생성자는 안 생긴다"는 건 글로만 봤으면 또 틀렸을 것이다.
- 가비지 컬렉션 덕에 C에서 신경 쓰던 메모리 해제 부담이 사라진 게 가장 큰 편의였다. 대신 그 자동 관리에도 비용(멈춤)이 있다는 걸 알아둬야겠다.
한 걸음 더
- 객체를 담는 배열(객체 배열)은 생성이 두 단계다.
new Circle[3]은 객체 3개가 아니라 "객체를 가리킬 레퍼런스 3칸"만 만든다(전부 null). 그 후 각 칸에new Circle()로 실제 객체를 따로 넣어야 한다. 이 두 단계를 헷갈려 null인 칸에 접근하면 NullPointerException이 난다. - 가비지 컬렉션이 편하지만 공짜는 아니다. JVM이 가비지를 수거하는 동안 잠깐 멈춤(stop-the-world)이 생길 수 있어, 응답 속도가 중요한 시스템에선 이 멈춤을 줄이는 게 중요한 튜닝 주제다. "메모리를 자동 관리한다"는 편의의 이면에 그 비용이 있는 것이다.
- 객체 지향의 진짜 힘은 다음 단계(상속·다형성)에서 드러난다. 이 글의 클래스·캡슐화는 그 토대다. "데이터를 메서드 뒤에 숨긴다(캡슐화)"는 원칙을 잘 지켜두면, 나중에 객체를 갈아끼우거나 확장할 때 변경의 파급을 최소화할 수 있다.
'language > java' 카테고리의 다른 글
| [자바프로그래밍] 05. 배열과 예외처리 (0) | 2026.08.14 |
|---|---|
| [자바프로그래밍] 04. 반복문 (0) | 2026.08.11 |
| [자바프로그래밍] 03. 조건문 (if·switch) (0) | 2026.07.26 |
| [자바프로그래밍] 02. 변수·자료형·연산자 (0) | 2026.07.13 |
| [자바프로그래밍] 01. 자바 특징과 역사 (0) | 2026.07.01 |