본문 바로가기

멋쟁이사자처럼_부트캠프/Java

[멋쟁이사자처럼 부트캠프 TIL 회고] 백엔드 부트캠프 13기: Java 8일차 추상클래스, 인터페이스, Final

추상클래스

추상 클래스란?

추상 클래스(Abstract Class)는 추상 메서드를 하나 이상 포함하거나, 객체를 직접 생성할 수 없고 다른 클래스에서 상속받아 사용하도록 설계된 클래스를 말함.

 

특징

1. 객체 생성 불가

  • 추상 클래스 자체로는 객체를 생성할 수 없음.
  • 반드시 상속받은 하위 클래스에서 구현하여 사용해야 함.

예)

abstract class Animal {
    public abstract void sound();
}

Animal a = new Animal(); // 오류: 추상 클래스는 인스턴스화할 수 없음

 

 

2. 추상 메서드

  • abstract 키워드로 선언된 메서드로, 구현부가 없는 메서드.
  • 하위 클래스에서 반드시 오버라이딩(재정의) 해야 함.

예)

abstract class Animal {
    public abstract void sound(); // 구현 없음
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}

 

 

3. 일반 메서드 포함 가능

  • 추상 클래스는 일반 메서드와 필드를 가질 수 있음.
  • 상속받은 클래스에서 공통 기능을 구현하거나 데이터 초기화를 지원할 때 사용.

4. 상속의 기초

  • 추상 클래스는 상속을 통해 하위 클래스의 공통 기능을 정의하는 데 적합.

5. abstract 키워드

  • 클래스나 메서드에 abstract 키워드를 사용하여 선언.
  • 추상 메서드는 반드시 구현 없이 선언만 가능.

 

추상 클래스 구현 예제

1. 기본 예제

abstract class Animal {
    protected String name;

    // 생성자
    public Animal(String name) {
        this.name = name;
    }

    // 추상 메서드
    public abstract void sound();

    // 일반 메서드
    public void eat() {
        System.out.println(name + "이(가) 먹이를 먹습니다.");
    }
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}

class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void sound() {
        System.out.println("야옹");
    }
}

public class AbstractClassExample {
    public static void main(String[] args) {
        Animal dog = new Dog("강아지");
        dog.sound(); // 멍멍
        dog.eat();   // 강아지이(가) 먹이를 먹습니다.

        Animal cat = new Cat("고양이");
        cat.sound(); // 야옹
        cat.eat();   // 고양이이(가) 먹이를 먹습니다.
    }
}

 

2. 공통 기능 정의 및 확장

abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    public abstract double getArea();

    public String getColor() {
        return color;
    }
}

class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }

    @Override
    public double getArea() {
        return width * height;
    }
}

public class ShapeTest {
    public static void main(String[] args) {
        Shape circle = new Circle("Red", 5);
        Shape rectangle = new Rectangle("Blue", 4, 6);

        System.out.println("Circle Area: " + circle.getArea()); // Circle Area: 78.5398...
        System.out.println("Rectangle Area: " + rectangle.getArea()); // Rectangle Area: 24.0
    }
}

 

추상 클래스 사용 시점

  • 클래스 간에 공통된 속성과 메서드가 있을 때.
  • 일부 메서드는 공통으로 제공, 일부는 구현 강제가 필요할 때.
  • 상속을 통해 구조를 정의하고 확장성 있는 프로그램을 만들고자 할 때.

인터페이스

인터페이스란?

인터페이스(Interface)는 클래스나 프로그램의 구조를 정의하는 데 사용되는 청사진으로, 클래스가 구현해야 하는 메서드들의 집합. 인터페이스를 구현한 클래스는 해당 인터페이스에 정의된 모든 메서드를 반드시 구현해야 함.

 

특징

  1. 추상 메서드의 집합
    • 인터페이스에 선언된 메서드는 기본적으로 추상 메서드이며, 구현부가 없음.
    • 구현 클래스에서 반드시 오버라이딩(재정의) 해야 함.
  2. 필드는 상수만 가능
    • 인터페이스에서 선언된 필드는 public static final로 자동 지정됨.
    • 값을 변경할 수 없는 상수만 가질 수 있음.
  3. 다중 구현 가능
    • 한 클래스가 여러 개의 인터페이스를 implements 키워드로 구현 가능.
    • Java에서 다중 상속의 제한을 해결하는 데 사용.
  4. implements 키워드
    • 클래스를 선언할 때 implements 키워드를 사용해 인터페이스를 구현.
  5. Java 8 이후 변경 사항
    • default 메서드: 구현 클래스에서 선택적으로 오버라이드할 수 있는 기본 구현을 가진 메서드.
    • static 메서드: 객체 없이 인터페이스 자체에서 호출 가능한 메서드.
  6. 객체 생성 불가
    • 인터페이스 자체로는 객체를 생성할 수 없으며, 구현 클래스를 통해 인스턴스화해야 함.

인터페이스 정의 및 구현 예제

1. 기본 인터페이스 정의와 구현

interface Animal {
    // 추상 메서드
    void sound();

    // 상수
    String CATEGORY = "동물";
}

class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}

class Cat implements Animal {
    @Override
    public void sound() {
        System.out.println("야옹");
    }
}

public class InterfaceExample {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.sound(); // 멍멍

        Animal cat = new Cat();
        cat.sound(); // 야옹

        // 인터페이스 상수 사용
        System.out.println(Animal.CATEGORY); // 동물
    }
}

 

2. 다중 인터페이스 구현

interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

class Duck implements Flyable, Swimmable {
    @Override
    public void fly() {
        System.out.println("오리가 날아갑니다.");
    }

    @Override
    public void swim() {
        System.out.println("오리가 헤엄칩니다.");
    }
}

public class MultiInterfaceExample {
    public static void main(String[] args) {
        Duck duck = new Duck();
        duck.fly();  // 오리가 날아갑니다.
        duck.swim(); // 오리가 헤엄칩니다.
    }
}

 

Java 8 이후 추가 기능

1. default 메서드

  • 기본 구현을 가지며, 구현 클래스에서 선택적으로 오버라이드 가능.
interface Device {
    void turnOn();

    default void turnOff() {
        System.out.println("디바이스를 끕니다.");
    }
}

class Phone implements Device {
    @Override
    public void turnOn() {
        System.out.println("전화기를 켭니다.");
    }

    @Override
    public void turnOff() {
        System.out.println("전화기를 끕니다.");
    }
}

public class DefaultMethodExample {
    public static void main(String[] args) {
        Device phone = new Phone();
        phone.turnOn();  // 전화기를 켭니다.
        phone.turnOff(); // 전화기를 끕니다.
    }
}

 

2. static 메서드

  • 인터페이스 이름을 통해 호출 가능.
interface Utility {
    static void printInfo() {
        System.out.println("Utility 인터페이스의 static 메서드입니다.");
    }
}

public class StaticMethodExample {
    public static void main(String[] args) {
        Utility.printInfo(); // Utility 인터페이스의 static 메서드입니다.
    }
}

 

언제 인터페이스를 사용할까?

  • 서로 다른 클래스가 공통적으로 가져야 할 행동(기능)을 정의할 때.
  • 다중 상속의 장점을 활용하고 싶을 때.
  • 특정 행동(메서드)을 반드시 구현해야 하는 계약을 설정할 때.

 


추상 클래스와 인터페이스 차이

특징 추상 클래스 인터페이스
목적 공통된 기능 정의 및 상속 행동(기능) 정의
메서드 구현 추상 메서드와 일반 메서드 모두 포함 가능 구현 없이 메서드 선언만 가능
(Java 8 이후 default 지원)
다중 상속 불가능 (extends 키워드 사용) 가능 (implements 키워드 사용)
extends vs implements extends 키워드 사용 implements 키워드 사용
필드 일반 필드와 상수 모두 사용 가능 public static final 상수만 가능

 


Final

1. final 클래스

final 키워드는 클래스, 메소드, 필드에 사용될 수 있으며, 이를 통해 해당 요소가 변경되지 않도록 보장. final 클래스를 사용하면 해당 클래스를 상속할 수 없게 만듭니다. 즉, final 클래스는 다른 클래스의 부모가 될 수 없으며, 상속을 통한 클래스의 변경을 막습니다. 이는 클래스를 고정된 형태로 유지하려는 의도가 있음.

final class MyClass {
    // 클래스 내용
}

 ...
class SubClass extends MyClass { // 오류 발생
    // 상속 불가
}

1-1. Java JDK에서 final 클래스

Java의 표준 라이브러리에도 final 클래스를 많이 찾아볼 수 있음. 예를 들어, String 클래스는 final로 선언되어 있어 상속이 불가능합니다. 이를 통해 String 클래스가 불변(immutable) 속성을 유지하도록 보장함. String을 상속하면 불변성을 깨트릴 수 있기 때문에 상속을 허용하지 않습니다.

final class String {
    // String 클래스는 상속 불가
}

2. final 필드

final로 선언된 필드는 값을 한 번만 할당할 수 있습니다. 즉, 초기화 이후에는 값을 변경할 수 없습니다. 주로 불변 객체를 만들 때 사용되며, 상수 값을 선언할 때 유용합니다.

public class MyClass {
    final int MAX_VALUE = 100; // final 필드

    public void changeValue() {
        MAX_VALUE = 200; // 오류 발생: final 필드는 값을 변경할 수 없음
    }
}

 

MAX_VALUE는 한 번 초기화된 후 값을 변경할 수 없음.

3. final 메소드

final 메소드는 오버라이딩을 할 수 없음. 즉, final로 선언된 메소드는 자식 클래스에서 재정의할 수 없으며, 부모 클래스에서 구현된 메소드의 동작을 그대로 사용할 수 있게 강제함. 이는 메소드의 기능을 변경하지 않도록 보장하려는 목적이 있습니다.

class ParentClass {
    final void display() {
        System.out.println("This is the final method.");
    }
}

class ChildClass extends ParentClass {
    @Override
    void display() { // 오류 발생: final 메소드는 오버라이드할 수 없음
        System.out.println("Overridden method.");
    }
}

위 코드에서 display() 메소드는 final로 선언되어 있어 자식 클래스에서 오버라이딩할 수 없음.

 

 

이렇게 final은 클래스, 필드, 메소드에서 사용될 수 있으며, 변경 불가능한 요소를 만들거나 상속, 오버라이딩을 제한하는 등의 용도로 사용됨.