본문 바로가기
IT & AI/AI 지식

JAVA 인터페이스, 중첩 클래스, 예외 처리 방법

by 빛나는해커 2024. 11. 22.

1.  인터페이스

 

인터페이스

  • 인터페이스는 클래스 간에 공통으로 구현해야 할 메소드를 정의하기 위해 사용된다.
  • 자바에서 interface 키워드를 사용하여 인터페이스를 선언할 수 있다.
  • 인터페이스는 메소드 선언만 포함하며, 메소드의 구현은 하지 않는다.
public interface Drivable {
    void drive();
    void stop();
}
  • 인터페이스는 여러 클래스에서 동일한 메소드를 구현하도록 강제하여 코드의 일관성을 유지하고 다형성을 지원한다.
  • 인터페이스를 구현하려면 implements 키워드를 사용한다.
  • 구현 클래스는 인터페이스의 모든 메소드를 반드시 구현해야 한다.
public class Car implements Drivable {
    @Override
    public void drive() {
        System.out.println("자동차가 주행 중입니다.");
    }

    @Override
    public void stop() {
        System.out.println("자동차가 멈췄습니다.");
    }
}
  • 위 예제에서 Car 클래스는 Drivable 인터페이스를 구현하여 drive()와 stop() 메소드를 정의하였다.
  • 인터페이스를 사용하여 다형성을 구현할 수 있다.
  • 인터페이스 타입의 변수는 구현 객체를 참조할 수 있으며, 이를 통해 동일한 인터페이스를 구현한 객체들이 같은 방식으로 호출될 수 있다.
Drivable myCar = new Car();
myCar.drive();
myCar.stop();
  • 인터페이스 타입 변수 myCar는 Car 객체를 참조하고 있으며, Drivable 인터페이스에 정의된 메소드만을 호출할 수 있다.

타입 변환과 다형성

  • 인터페이스를 구현한 클래스의 객체는 자동으로 인터페이스 타입으로 변환될 수 있다.
  • 이를 통해 구현 객체를 인터페이스 타입으로 간단히 사용할 수 있다.
Drivable vehicle = new Car();  // Car 객체가 Drivable 타입으로 자동 변환
  • 이렇게 변환하면 인터페이스에 정의된 메소드만 사용할 수 있지만, 이를 통해 코드의 유연성을 높일 수 있다.
  • 인터페이스 타입의 필드는 다양한 구현 객체를 참조할 수 있다.
  • 이를 통해 여러 객체를 같은 인터페이스 타입으로 처리할 수 있어 코드의 재사용성과 유연성을 높인다.
Drivable vehicle1 = new Car();
Drivable vehicle2 = new Truck();
  • 이와 같이 하나의 인터페이스 타입 필드를 통해 여러 구현 객체를 참조할 수 있다.
  • 메소드의 매개변수를 인터페이스 타입으로 선언하면, 다양한 구현 객체를 전달받아 처리할 수 있다.
public void operateVehicle(Drivable vehicle) {
    vehicle.drive();
    vehicle.stop();
}

operateVehicle(new Car());  // Car 객체 전달
operateVehicle(new Truck());  // Truck 객체 전달
  • operateVehicle() 메소드는 Drivable 타입을 매개변수로 받기 때문에, Car와 Truck 객체 모두 전달받아 처리할 수 있다.
  • 자동 타입 변환된 객체를 다시 원래의 타입으로 변환하려면 강제 타입 변환(다운캐스팅)을 수행해야 한다.
  • 이때 instanceof 연산자를 사용하여 변환이 가능한지 확인한 후 안전하게 캐스팅한다.
Drivable myVehicle = new Car();
if (myVehicle instanceof Car) {
    Car myCar = (Car) myVehicle;
    myCar.drive();
}
  • instanceof 연산자를 사용하여 객체가 특정 클래스의 인스턴스인지 확인한 후 캐스팅하면 런타임 오류를 방지할 수 있다.
  • 객체가 특정 인터페이스를 구현했는지 확인하려면 instanceof 연산자를 사용한다.
if (myVehicle instanceof Drivable) {
    System.out.println("myVehicle은 Drivable 인터페이스를 구현합니다.");
}
  • 인터페이스는 다른 인터페이스를 상속받아 사용할 수 있다.
  • 이를 통해 여러 인터페이스의 기능을 결합하여 확장할 수 있다.
public interface Flyable {
    void fly();
}

public interface AdvancedDrivable extends Drivable, Flyable {
    void autoPilot();
}
  • AdvancedDrivable 인터페이스는 Drivable와 Flyable 인터페이스를 상속받아 두 인터페이스의 모든 메소드를 포함한다.

2.  중첩 클래스와 중첩 인터페이스

 

중첩 클래스와 중첩 인터페이스 소개

  • 중첩 클래스는 클래스 내에 선언된 클래스로, 외부 클래스와 밀접한 관계가 있을 때 사용된다.
  • 중첩 클래스는 외부 클래스의 멤버에 접근할 수 있으며, 코드를 논리적으로 묶어 가독성을 높이는 데 유용하다.
public class OuterClass {
    class InnerClass {
        void display() {
            System.out.println("이것은 중첩 클래스입니다.");
        }
    }
}
  • 중첩 클래스는 public, private, protected 접근 제한자를 가질 수 있다.
  • 이를 통해 외부 클래스에서만 사용할 수 있는 클래스나 특정 클래스만 접근할 수 있는 클래스를 정의할 수 있다.
  • 중첩 인터페이스는 클래스 내에 선언된 인터페이스로, 외부 클래스와 관련된 동작을 정의할 때 사용된다.
public class Button {
    interface OnClickListener {
        void onClick();
    }
}
  • 위 코드에서 OnClickListener 인터페이스는 Button 클래스에 특정한 동작을 정의하기 위해 사용된다.

익명 객체

  • 익명 객체는 클래스를 정의하지 않고 한 번만 사용할 목적으로 객체를 생성할 때 사용된다.
  • 익명 클래스는 부모 클래스를 상속받거나 인터페이스를 구현하여 사용한다.
Car myCar = new Car() {
    @Override
    public void drive() {
        System.out.println("익명 객체가 주행 중입니다.");
    }
};
myCar.drive();
  • 인터페이스를 구현할 때도 익명 객체를 사용할 수 있다.
  • 구현해야 할 메소드가 한두 개뿐일 경우 간편하게 사용할 수 있다.
Button.OnClickListener listener = new Button.OnClickListener() {
    @Override
    public void onClick() {
        System.out.println("버튼이 클릭되었습니다.");
    }
};
listener.onClick();
  • 익명 객체는 생성된 메소드의 로컬 변수를 사용할 수 있지만, 이 로컬 변수는 final로 선언된 것처럼 변경할 수 없다.
public void start() {
    int speed = 10;
    Drivable vehicle = new Drivable() {
        @Override
        public void drive() {
            System.out.println("속도: " + speed);
        }
    };
    vehicle.drive();
}
  • 위 코드에서 speed 변수는 익명 객체 내에서 사용되지만, 값을 변경할 수 없다.

3.  예외 처리

 

예외 클래스

  • 예외는 프로그램 실행 중에 발생하는 오류를 의미하며, 자바에서는 예외를 처리하기 위해 Exception 클래스를 사용한다.
  • 예외 클래스는 발생한 오류의 유형을 나타내며, 이 예외를 처리해야만 프로그램이 비정상적으로 종료되지 않는다.
  • 예외는 크게 컴파일 시점에 확인되는 '검사 예외'와, 실행 중에 발생하는 '실행 예외'로 나뉜다.
  • 실행 예외(Runtime Exception)는 컴파일 시에는 발견되지 않지만, 실행 중에 발생하는 예외이다.
  • 예를 들어, 배열의 인덱스를 잘못 참조하거나, null 객체에 접근하려는 경우 실행 예외가 발생한다.
  • 이러한 예외는 프로그램 실행 중에 예기치 못한 오류를 방지하기 위해 반드시 처리해야 한다.
public class RuntimeExceptionExample {
    public static void main(String[] args) {
        String text = null;
        try {
            System.out.println(text.length());  // NullPointerException 발생
        } catch (NullPointerException e) {
            System.out.println("예외 발생: null 객체에 접근할 수 없습니다.");
        }
    }
}
  • 위 예제에서 NullPointerException이 발생할 수 있는 코드가 try 블록 내에 포함되어 있으며, catch 블록에서 예외를 처리하고 있다.

예외 처리

  • 예외 처리는 프로그램이 예외 상황에서도 정상적으로 동작할 수 있도록 하기 위한 기법이다.
  • 자바에서는 try, catch, finally 블록을 사용하여 예외를 처리한다.
public class ExceptionHandlingExample {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;  // ArithmeticException 발생
        } catch (ArithmeticException e) {
            System.out.println("예외 발생: 0으로 나눌 수 없습니다.");
        } finally {
            System.out.println("예외 처리 완료.");
        }
    }
}
  • 위 코드에서 try 블록에서 예외가 발생할 수 있는 코드를 실행하고, catch 블록에서 예외를 처리한다.
  • finally 블록은 예외 발생 여부와 관계없이 항상 실행된다.
  • 여러 종류의 예외가 발생할 수 있는 경우, 각 예외에 대해 별도의 catch 블록을 작성하여 처리할 수 있다.
  • 이를 통해 다양한 예외 상황에 대처할 수 있다.
public class MultipleExceptionExample {
    public static void main(String[] args) {
        try {
            int[] numbers = {1, 2, 3};
            System.out.println(numbers[5]);  // ArrayIndexOutOfBoundsException 발생
            String text = null;
            System.out.println(text.length());  // NullPointerException 발생
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("예외 발생: 배열 인덱스가 잘못되었습니다.");
        } catch (NullPointerException e) {
            System.out.println("예외 발생: null 객체에 접근할 수 없습니다.");
        }
    }
}
  • 위 코드에서는 배열의 인덱스를 잘못 참조하는 경우와 null 객체에 접근하려는 경우 각각의 예외를 별도의 catch 블록으로 처리하고 있다.
  • 메소드 내에서 발생할 수 있는 예외를 직접 처리하지 않고, 메소드를 호출한 쪽으로 떠넘길 수 있다.
  • 이를 '예외 떠넘기기'라고 하며, throws 키워드를 사용하여 선언한다.
  • 예외를 떠넘기면 해당 메소드를 사용하는 쪽에서 예외를 처리해야 한다.
public class ThrowsExample {
    public static void main(String[] args) {
        try {
            divide(10, 0);
        } catch (ArithmeticException e) {
            System.out.println("예외 발생: " + e.getMessage());
        }
    }

    public static void divide(int a, int b) throws ArithmeticException {
        if (b == 0) {
            throw new ArithmeticException("0으로 나눌 수 없습니다.");
        }
        System.out.println("결과: " + (a / b));
    }
}
  • divide() 메소드는 ArithmeticException을 발생시킬 수 있으며, throws 키워드를 통해 이 예외를 호출한 메소드로 떠넘긴다.
  • main() 메소드에서는 try-catch 블록을 사용하여 이 예외를 처리한다.
  • 이처럼 예외 처리는 프로그램의 안정성을 높이고, 예외 상황에 유연하게 대응할 수 있는 중요한 기법이다. 

JAVA 기초 인터페이스, 중첩 클래스, 예외 처리 소개 이미지