본문 바로가기

IT & AI/AI 지식

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

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

안녕하세요! 오늘 다뤄볼 내용은 JAVA의 코드의 유연성과 안정성을 높이는 핵심 요소입니다.

객체 지향 프로그래밍의 핵심인 인터페이스, 코드 구조를 효율적으로 만들어주는 중첩 클래스, 그리고 프로그램의 안정성을 높여주는 예외 처리 방법에 대해 알아보겠습니다. 

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 변수, 연산자, 데이터 입출력

안녕하세요! 문과 출신 방구석 데이터 전문가와 함께하는 JAVA 기초 글에 오신걸 환영합니다. 지난 글까지는 파이썬과 C언어를 다뤄보았는데 JAVA는 C언어와 비슷한 부분이 많아서 C언어를 잘 아시

actshiny.com

 

JAVA 조건문, 반복문, 참조 타입(배열) 정리

안녕하세요! 지난 글에서 JAVA의 기본적인 내용에 대해 알아보았습니다. 이번에는 조건문, 반복문, 참조타입, 배열에 대해서 정리해보았습니다. C언어를 공부하신 분들은 JAVA와 크게 다르지 않다

actshiny.com

 

JAVA 객체 지향 프로그래밍: 클래스, 상속

안녕하세요! 지난 글까지 JAVA의 전반적인 내용에 대해 다뤄보았습니다. C언어와 파이썬(python)과 겹치는 내용들이 많았는데요. 이번에는 JAVA 프로그래밍의 핵심인 클래스와 상속에 대해 정리해보

actshiny.com

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

반응형