[JAVA] 자바에서의 인터페이스 정의 및 역할

Re_Go·2024년 5월 27일
0

JAVA

목록 보기
17/37
post-thumbnail
post-custom-banner

자바에서 클래스의 상속은 주로 단일 상속을 지원합니다. 가령 A라는 상위 클래스와 B라는 상위 클래스가 있다면 C라는 하위 클래스는 A와 B 둘 중 하나만을 상속할 수 있는거죠.

그렇다고 다중 상속을 구현할 수 없는 것은 아닙니다. 인터페이스 객체를 여럿으로 상속 받는 방법으로 다중 상속 구현이 가능한데요. 이번 챕터에서는 자바에서 이러한 다중 상속을 가능케 하는 인터페이스에 대해서 알아보겠습니다.

1. 인터페이스?

인터페이스의 사전적 정의는 서로 다른 두 개의 시스템, 장치 사이에서 정보나 신호를 주고받는 경우의 접점이나 경계면을 의미하며, 간단히 얘기해서 사용자와 기기 사이에서 응답을 서포트 해주는 장치라고 보시면 되는데요.

자바에서의 인터페이스란 각 객체에 공통적으로 제공할 구현하지 않은 메서드들의 집합을 의미하는데요. 앞서 말씀드린 대로 클래스는 이러한 인터페이스를 여러 개 다중 상속 받아 각 인터페이스 마다의 메서드를 재정의 및 사용하는 것이 가능합니다.

또한 인터페이스는 이러한 정의만 한 메서드들을 갖고 있기 때문에, 추상 메서드의 연장 선상이라고도 할 수 있습니다.

이러한 인터페이스의 선언은 클래스를 선언하는 것과 같이 interface 키워드를 인터페이스 이름명 앞에 작성해주면 되며, 상속을 받을 경우 클래스 뒷쪽에 implements를 붙인 후 상속 받을 인터페이스를 차례대로 작성해주면 됩니다.

interface Shape{
}

class Rect implements Shape{
}

주의할 점은 인터페이스는 기본적으로 abstract 기능이 적용되어 있기 때문에 굳이 abstract 이름에 키워드를 명시하지 않아도 되며, 메서드의 네이밍에 접근 제어자는defaultpublic, ' private 만 사용이 가능합니다.

interface Shape{
	void calc1() {} // 유효 : 기본적인 클래스는 메서드에 접근 제어자를 명시하지 않으면  default지만, 인터페이스에서는 메서드에 public이 기본값으로 붙음
    private calc2() {} // 유효 : 자바 9 이상에서 유효
    default calc3() {} // 유효 : 기본 메서드
    protected calc4() {} // 무효 : 패키지 내의 모든 클래스에서 접근 가능하기 때문에 상속 받은 클래스만 재정의 해야하는 목적상 맞지 않습니다.
}

class Rect implements Shape{
}

또한 인터페이스에서 선언하는 필드는 기본적으로 public static final의 특성을 갖고 있는데요. 조금만 생각을 해봐도, 인터페이스의 상속을 받은 클래스에서 직접 구현을 해주어야 하는 만큼 인터페이스는 갖고만 있는 역할을 수행하기 때문에 어떠한 특정 필드의 값을 변경할 필요가 없고, 단지 제공하는 역할을 해야하기 때문입니다.

// 인터페이스의 기본 골자
interface Shape{
	// 필드
	public static double PI = Math.PI;
    // 메서드
	double calcArea();
	void output();
}

2. 인터페이스의 상속

우선 인터페이스를 상속하기 위해서는 클래스를 상속 받을 때와 마찬가지로 implements 키워드를 사용해 상속을 정의한 후 필드와 생성자를 작성해 줍니다.

class Rect implements Shape{

	private int width, height;

	public Rect() {}
}

그 다음 상속 받은 메서드를 구현해 주는데, 이때 구현해 줄 메서드를 구현하고 싶지 않은 경우 선언은 하되 반환 타입이 있을 경우 그 타입을, 없을 경우 아무것도 적지 않습니다.


	// 반환타입이 double 이므로 0.0 작성
	@Override
	public double calcArea() {
		return 0.0;
	}

	// 반환 타입이 void 이므로 작성 안해도 무관
	@Override
	public void output() {

	}

만약 구현을 한다면 추상 메서드를 상속 받아 작성 하는 것과 같이 재정의 해주면 되는데요. 이때 오버라이드 기호는 생략해도 됩니다. 어차피 컴파일러가 해당 메서드는 인터페이스로부터 상속 받은 메서드임을 알기 때문이죠.


	// 오버라이드 기호를 생략하고 재정의
	public double calcArea() {
		return (double) width * height;
	}

    // 오버라이드 기호를 붙이고 재정의
	@Override
	public void output() {
		System.out.println(
				"가로 : " + width + "\n" +
				"세로 : " + height + "\n" +
				"크기 : " + calcArea() + "\n"
		);
	}

그 다음 선언한 필드와 물려 받은 메서드의 조합을 이용해 코드를 실행해 주면 됩니다.

Rect rect = new Rect(5,8);	

rect.output(); // rect가 재정의 한 메서드가 호출

또한 클래스는 다른 클래스 상속 받고 있을 때 다른 인터페이스 또한 상속 받을 수 있는데요. 이 경우 클래스는 하나만, 인터페이스는 다중 상속을 받을 수 있습니다.

  1. 클래스 및 인터페이스 정의
// Eatable 클래스 정의
public class Eatable {
    // Eatable 클래스의 멤버 변수 및 메서드
    public void consume() {
        System.out.println("This is eatable.");
    }
}

// Cookable 인터페이스 정의
interface Cookable {
    void cook();
}

// Sellable 인터페이스 정의
interface Sellable {
    void sell();
}
  1. 상속 및 실행
public class Apple extends Eatable implements Cookable, Sellable {
    // Cookable 인터페이스 메서드 구현
    @Override
    public void cook() {
        System.out.println("Cooking an apple.");
    }

    // Sellable 인터페이스 메서드 구현
    @Override
    public void sell() {
        System.out.println("Selling an apple.");
    }

    // Apple 클래스의 추가 멤버 변수 및 메서드
    public void grow() {
        System.out.println("Growing an apple.");
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        apple.consume();  // Eatable 클래스의 메서드
        apple.cook();     // Cookable 인터페이스의 메서드
        apple.sell();     // Sellable 인터페이스의 메서드
        apple.grow();     // Apple 클래스의 메서드
    }
}

3. 상속된 인터페이스의 쓰임새

기본적으로 인터페이스 또한 클래스인 만큼 상속이 가능한데요.

상속을 받은 클래스에서는 인터페이스에 정의되어 있는 추상 메서드를 필수로 구현해야 하는 강제성을 부여받습니다.

이때 인터페이스와 그 안의 필드 멤버 및 메서드는 물론, 인터페이스를 구현하는 클래스 쪽에서도 구현 할 멤버 메서드의 접근 제한자를 임의로 private이나 protected로 변경하는 것을 허용하지 않는 점(인터페이스의 특성을 고려하면 public에서 다른 접근 제한자로 변경되는 것은 말이 안됩니다만.)을 유념해야 합니다.

// 인터페이스 정의
public interface Vehicle {
    // 인터페이스의 상수
    int MAX_SPEED = 100; // public static final int MAX_SPEED = 100; 로 처리됨

    // 인터페이스의 메서드
    void start(); // public abstract void start(); 로 처리됨
    void stop();  // public abstract void stop(); 로 처리됨
}

// 인터페이스를 구현하는 클래스
public class Car implements Vehicle {
    // Vehicle 인터페이스의 상수 및 메서드 구현
    @Override
    public void start() {
        System.out.println("Car started");
    }

    @Override
    public void stop() {
        System.out.println("Car stopped");
    }

    // 상속 받은 메서드를 protected로 변경할 수 없음
    protected void start() { ... } // 에러: 인터페이스의 메서드는 protected가 될 수 없습니다.
}

4. 인터페이스에서의 다형성

앞서 소개해드린 클래스의 다형성은 상속을 통해서 구현이 가능하다고 했는데요. 인터페이스의 다형성도 유사한 방식을 사용할 수 있습니다.

아래의 코드는 자동 형변환과 다형성을 설명하는 예시 코드입니다.

interface Act{
	void study();
}

class Student{
	//Act 인터페이스 타입의 student를 매개변수로 받으면 해당 매개변수를 받게되면 각각의 인스턴스들으니 Act 인터페이스타입으로 자동 형변환 되는데, 이때 해당 객체의 각각의 study 메서드를 호출합니다.
	void read(Act student) {
		student.study();
	}
}

class Cheolsu implements Act{
	public void study() {
		System.out.println("철수가 공부를 합니다.");
	}
}
class Younghee implements Act{
	public void study() {
		System.out.println("영희가 공부를 합니다.");
	}
}

public class school {
	public static void main(String[] args) {
	
	Student student = new Student();
	
	Cheolsu cheolsu = new Cheolsu();
	Younghee younghee = new Younghee();
	
	// 아래와 같이 타입 변환을 주지 않아도 각각의 객체에서 재정의 된 추상 메서드를 호출할 수 있지만
	cheolsu.study();
	younghee.study();
	// Student 클래스의 구현처럼 상속을 받는 공통의 인터페이스로 타입 변환을 한 뒤 해당 객체들의 메서드를 호출하게 할 수 있습니다.
	student.read(cheolsu);
	student.read(younghee);
    // 위의 코드들은 아래의 코드를 단축한 코드이기도 합니다.
    // Act student = new Cheolsu();
    // Act student = new younghee();
	}
}

그 외에 인스턴스 확인법이나 기타 사항들은 클래스의 상속과 유사합니다.

profile
인생은 본인의 삶을 곱씹어보는 R과 타인의 삶을 배워 나아가는 L의 연속이다.
post-custom-banner

0개의 댓글