Java 클래스와 객체 | OOP, 상속, 인터페이스

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의 메서드는 호출 불가
    }
}

상속의 특징:

  1. 코드 재사용: 공통 기능을 부모 클래스에 정의
  2. 확장성: 새로운 기능을 자식 클래스에 추가
  3. 다형성: 부모 타입으로 자식 객체 참조 가능
  4. 단일 상속: 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;
    }
}

정리

핵심 요약

  1. 클래스: 객체의 설계도, 필드 + 메서드
  2. 생성자: 객체 초기화, 오버로딩 가능
  3. 상속: extends, super, @Override
  4. 인터페이스: implements, 다중 구현 가능
  5. 추상 클래스: abstract, 일부 구현 가능
  6. 캡슐화: private, public, protected

다음 단계

  • Java 컬렉션
  • Java Stream API
  • Java 람다

관련 글

  • JavaScript 클래스 | ES6 Class 문법 완벽 정리
  • Kotlin 클래스와 객체 | 클래스, 상속, 인터페이스
  • Python 클래스 | 객체지향 프로그래밍(OOP) 완벽 정리
  • C++ 클래스와 객체 |
  • C++ 상속과 다형성 |