Java 클래스와 객체 | OOP, 상속, 인터페이스
이 글의 핵심
Java 클래스와 객체에 대해 정리한 개발 블로그 글입니다. public class Person {
들어가며
Java는 객체 지향 프로그래밍(OOP)(데이터와 동작을 객체·클래스로 묶어 설계하는 방식)을 널리 쓰는 언어입니다.
클래스는 객체를 찍어내는 설계도이고, new로 만든 인스턴스는 그 설계도를 바탕으로 만든 실물입니다. 같은 설계도로 여러 객체를 만들 수 있고, 필드·메서드는 설계도에 적힌 규격대로 동작합니다.
1. 클래스와 객체
클래스 정의
클래스는 객체의 설계도입니다:
public class Person {
// 1. 필드 (Field) - 인스턴스 변수
// private: 클래스 외부에서 직접 접근 불가 (캡슐화)
private String name;
private int age;
// 2. 생성자 (Constructor)
// 객체 생성 시 자동 호출되는 특수 메서드
public Person(String name, int age) {
// this: 현재 객체를 가리키는 참조
// this.name: 필드 name
// name: 매개변수 name
this.name = name;
this.age = age;
}
// 3. 메서드 (Method)
public void introduce() {
// 필드에 직접 접근 가능
System.out.println("안녕하세요, " + name + "입니다.");
}
// 4. Getter (필드 값 읽기)
public String getName() {
// 외부에서 private 필드를 읽을 수 있게 함
return name;
}
// 5. Setter (필드 값 쓰기)
public void setName(String name) {
// 외부에서 private 필드를 수정할 수 있게 함
// 유효성 검사 추가 가능
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 유효성 검사: 음수 나이 방지
if (age > 0) {
this.age = age;
} else {
System.out.println("나이는 양수여야 합니다");
}
}
}
// 사용 예제
public class Main {
public static void main(String[] args) {
// 객체 생성 (인스턴스화)
// new Person(): 생성자 호출
// person: 객체 참조 변수
Person person = new Person("홍길동", 25);
// 메서드 호출
person.introduce(); // 안녕하세요, 홍길동입니다.
// Getter로 필드 읽기
System.out.println("이름: " + person.getName()); // 홍길동
// Setter로 필드 수정
person.setAge(26);
System.out.println("나이: " + person.getAge()); // 26
// person.age = 30; // ❌ 컴파일 에러
// private 필드는 외부에서 직접 접근 불가
// 반드시 Getter/Setter 사용
}
}
캡슐화의 장점:
// ❌ 나쁜 예: public 필드
class BadPerson {
public int age; // 직접 접근 가능
}
BadPerson p = new BadPerson();
p.age = -10; // 유효하지 않은 값 설정 가능!
// ✅ 좋은 예: private 필드 + Setter
class GoodPerson {
private int age;
public void setAge(int age) {
if (age > 0 && age < 150) { // 유효성 검사
this.age = age;
} else {
throw new IllegalArgumentException("유효하지 않은 나이");
}
}
}
GoodPerson p = new GoodPerson();
// p.age = -10; // 컴파일 에러 (직접 접근 불가)
p.setAge(-10); // 예외 발생 (유효성 검사)
생성자 오버로딩
public class Person {
private String name;
private int age;
private String email;
// 기본 생성자
public Person() {
this("익명", 0, "");
}
// 이름만
public Person(String name) {
this(name, 0, "");
}
// 이름과 나이
public Person(String name, int age) {
this(name, age, "");
}
// 모든 필드
public Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
}
2. 상속 (Inheritance)
기본 상속
상속은 기존 클래스를 확장하여 새로운 클래스를 만드는 기법입니다:
// 부모 클래스 (Super Class, Base Class)
public class Animal {
// protected: 자식 클래스에서 접근 가능
// private보다 넓고, public보다 좁은 접근 범위
protected String name;
// 생성자
public Animal(String name) {
this.name = name;
}
// 메서드
public void makeSound() {
System.out.println("동물 소리");
}
public void sleep() {
// protected 필드 name 사용
System.out.println(name + "이(가) 잠을 잡니다.");
}
}
// 자식 클래스 (Sub Class, Derived Class)
public class Dog extends Animal {
// extends: 상속 키워드
// Dog는 Animal의 모든 필드와 메서드를 물려받음
// Dog만의 고유 필드
private String breed;
// 생성자
public Dog(String name, String breed) {
// super(name): 부모 생성자 호출 (필수)
// 반드시 첫 줄에 위치해야 함
super(name);
// 자식 클래스의 필드 초기화
this.breed = breed;
}
// 메서드 오버라이딩 (재정의)
@Override // 어노테이션: 오버라이딩임을 명시
public void makeSound() {
// 부모의 makeSound()를 재정의
System.out.println("멍멍!");
}
// Dog만의 고유 메서드
public void fetch() {
// 부모의 protected 필드 name 사용 가능
System.out.println(name + "이(가) 공을 가져옵니다.");
}
}
// 사용 예제
public class Main {
public static void main(String[] args) {
// Dog 객체 생성
Dog dog = new Dog("바둑이", "진돗개");
// 오버라이딩된 메서드 호출
dog.makeSound(); // 멍멍! (Dog의 메서드)
// 상속받은 메서드 호출
dog.sleep(); // 바둑이이(가) 잠을 잡니다. (Animal의 메서드)
// Dog만의 메서드 호출
dog.fetch(); // 바둑이이(가) 공을 가져옵니다.
// 다형성 (Polymorphism)
Animal animal = new Dog("멍멍이", "시바견");
// 부모 타입 변수에 자식 객체 할당 가능
animal.makeSound(); // 멍멍! (Dog의 메서드 호출)
// animal.fetch(); // ❌ 컴파일 에러
// Animal 타입이므로 Dog의 메서드는 호출 불가
}
}
상속의 특징:
- 코드 재사용: 공통 기능을 부모 클래스에 정의
- 확장성: 새로운 기능을 자식 클래스에 추가
- 다형성: 부모 타입으로 자식 객체 참조 가능
- 단일 상속: Java는 한 클래스만 상속 가능 (다중 상속 불가)
super 키워드
public class Employee extends Person {
private String department;
public Employee(String name, int age, String department) {
super(name, age); // 부모 생성자
this.department = department;
}
@Override
public void introduce() {
super.introduce(); // 부모 메서드 호출
System.out.println("부서: " + department);
}
}
3. 인터페이스 (Interface)
기본 인터페이스
public interface Drawable {
void draw(); // 추상 메서드
default void display() { // 기본 구현 (Java 8+)
System.out.println("화면에 표시");
}
static void info() { // 정적 메서드
System.out.println("Drawable 인터페이스");
}
}
public class Circle implements Drawable {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("원 그리기: 반지름 " + radius);
}
}
// 사용
Circle circle = new Circle(5.0);
circle.draw();
circle.display();
Drawable.info();
다중 인터페이스 구현
public interface Movable {
void move(int x, int y);
}
public interface Resizable {
void resize(double scale);
}
public class Shape implements Drawable, Movable, Resizable {
private int x, y;
private double size;
@Override
public void draw() {
System.out.println("도형 그리기");
}
@Override
public void move(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public void resize(double scale) {
this.size *= scale;
}
}
4. 추상 클래스 (Abstract Class)
public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// 추상 메서드
public abstract double area();
// 일반 메서드
public void printColor() {
System.out.println("색상: " + color);
}
}
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public class Rectangle extends Shape {
private double width, height;
public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
5. 캡슐화
접근 제어자
public class BankAccount {
private double balance; // private: 외부 접근 불가
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public boolean withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
return true;
}
return false;
}
public double getBalance() {
return balance;
}
}
6. 실전 예제
예제: 도서 관리 시스템
public class Book {
private String title;
private String author;
private int year;
private boolean available;
public Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
this.available = true;
}
public boolean borrow() {
if (available) {
available = false;
return true;
}
return false;
}
public void returnBook() {
available = true;
}
public void printInfo() {
System.out.println("제목: " + title);
System.out.println("저자: " + author);
System.out.println("출판년도: " + year);
System.out.println("대출 가능: " + (available ? "예" : "아니오"));
}
}
public class Library {
private List<Book> books;
public Library() {
books = new ArrayList<>();
}
public void addBook(Book book) {
books.add(book);
}
public Book findBook(String title) {
for (Book book : books) {
if (book.getTitle().equals(title)) {
return book;
}
}
return null;
}
}
정리
핵심 요약
- 클래스: 객체의 설계도, 필드 + 메서드
- 생성자: 객체 초기화, 오버로딩 가능
- 상속: extends, super, @Override
- 인터페이스: implements, 다중 구현 가능
- 추상 클래스: abstract, 일부 구현 가능
- 캡슐화: private, public, protected
다음 단계
- Java 컬렉션
- Java Stream API
- Java 람다
관련 글
- JavaScript 클래스 | ES6 Class 문법 완벽 정리
- Kotlin 클래스와 객체 | 클래스, 상속, 인터페이스
- Python 클래스 | 객체지향 프로그래밍(OOP) 완벽 정리
- C++ 클래스와 객체 |
- C++ 상속과 다형성 |