[JAVA] 인터페이스(Interface)

DANI·2023년 9월 26일
0

JAVA를 공부해보자

목록 보기
6/29
post-thumbnail

📕 인터페이스(Interface)란?

클래스를 이용하여 다중 상속을 할 경우 메소드 출처의 모호성 등 여러 가지 문제가 발생할 수 있어 자바에서는 클래스를 통한 다중 상속은 지원하지 않는다.

하지만 다중 상속의 이점을 버릴 수는 없기에 자바에서는 인터페이스라는 것을 통해 다중 상속을 지원하고 있습니다.

인터페이스(interface)란 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미합니다.

즉, 인터페이스는 클래스들이 필수로 구현해야 하는 추상 자료형입니다. 쉽게 말하자면 객체의 사용방법을 가이드라인 하는 것

다만 인터페이스는 추상 클래스보다 추상화 정도가 높아 추상 클래스와 다르게 구현부가 있는 일반 메서드, 일반 변수 멤버 등을 가질 수 없다. 즉, 인터페이스는 구현된 게 아무것도 없는 기본 설계도라고 할 수 있다. 인터페이스 또한 인스턴스를 생성할 수 없다.

💡 추상클래스와 인터페이스의 차이점


자바에서 추상 클래스는 추상 메소드뿐만 아니라 생성자, 필드, 일반 메소드도 포함할 수 있습니다. 하지만 인터페이스(interface)는 오로지 추상 메소드(abstract)와 상수(final)만을 포함할 수 있습니다. 생성자X 따라서 구현된 코드가 없기 때문에 당연히 인터페이스로 인스턴스도 사용할 수 없습니다. 인터페이스도 추상 클래스와 비슷하게 다른 클래스를 작성하는데 도움을 주는 목적으로 작성한다.




❓ 인터페이스(Interface)를 사용 하는 이유?

  • 인터페이스를 구현한 클래스들을 하나의 인터페이스 타입으로 다룰 수 있다. (인터페이스 멤버만 사용 가능)

  • 기능(메소드)의 구현을 강제함으로써, 클래스의 설계 또는 표준화를 유도 할 수 있습니다.

  • 인터페이스는 껍데기만 존재하여 클래스 상속 시 발생했던 모호함이 없습니다. 고로 다중 상속이 가능합니다.

  • 자식클래스는 부모 인터페이스의 추상 메서드를 반드시 오버라이딩해야 합니다.

  • 추상 클래스를 통해 객체들 간의 네이밍을 통일할 수 있고 이로 인해 소스의 가독성과 유지보수가 향상됩니다.

  • 확장에는 열려있고 변경에는 닫혀있는 객체 간 결합도(코드 종속성)를 낮춘 유연한 방식의 개발이 가능합니다.


💡 인터페이스가 코드 종속성을 낮춘다는 게 무슨 말인가요?


코드 종속성은 각각의 메서드 간의 결합도를 의미하며 인터페이스를 활용하면 한 메서드를 수정하였을 때, 다른 메서드도 수정해야 하는 상황을 줄여준다는 의미입니다. 인터페이스로 추상 메서드를 지정하면 메서드의 input값과 output값이 고정되어 있습니다. 예를 들자면 public abstract String myName(String name) 이라는 메서드는 input값, output값 모두 String으로 고정되어 있죠. 이 메서드를 구현하는 객체에서 아무리 수정하더라도 input값과 output 값은 String으로 고정되어 있어 변경에 대한 영향도가 작습니다. 그래서 인터페이스는 "변경에 강하다", "확장에는 열려있고 변경에는 닫혀있다" 라고 말하는 것입니다.




📖 인터페이스(Interface) 구현 방식

  • 인터페이스는 상수와 추상메소드로 구성되어 있다. (자바8부터 default 메소드와 static 메소드 사용가능)
  • 인터페이스 안의 모든 상수는 public static final(생략가능) 타입입니다.

  • 인터페이스 안의 모든 추상메소드는 abstract public(생략가능) 타입니다.

    인터페이스에 정의된 모든 멤버에 적용되는 사항이기 때문에 편의상 생략 가능하게 지원하는 것이다. 생략된 제어자는 컴파일 시에 컴파일러가 자동으로 추가해 준다

    인터페이스 내의 추상메소드는 접근제어자가 public이기 때문에 오버라이딩할 경우에도 접근제어자를 public으로 해줘야 함(private, protected등으로 더 강하게 할 수 없음)

  • 클래스에서 인터페이스의 구현은 implements 키워드를 사용하여 구현할 인터페이스를 지정후, 추상메소드를 모두 오버라이드 하여 내용을 완성 하여야 합니다.

  • 인터페이스는 다른 인터페이스를 extends 키워드로 상속 받을 수 있으며, 다중 상속이 가능합니다.


🔴 인터페이스(Interface) 구현 방식

interface 인터페이스이름{
    public static final 타입 상수이름 =;
    public abstract 타입 메서드이름(매개변수목록);
}
class 클래스이름 implements 인터페이스이름 { ... }

📝 인터페이스(Interface) 구현 예시

🔴 RemoteControl 인터페이스

package interface_example01;

import java.util.Date;

public interface RemoteControl {
	// 상수
	int MAX_VOLUME = 10; // 제한할 최대 볼륨크기
	int MIN_VOULME = 0; // 제한할 최소 볼륨크기
	
	// 추상 메소드 : 구현클래스에서 반드시 구현해야함
	void turnOn(); 
	void turnOff();
	void setVolume(int volume);
	int getVolume();
	
	// 디폴트 메소드 : 구현클래스에서 오버라이딩 해도되고 안해도 되고(선택적)
	default void date(Date date) {
		System.out.println(date);
	}
	
	// 정적 메소드 : 오버라이딩 불가!
	static void powerSavingMode() {
		System.out.println("절전모드를 킵니다.");
	}
}

🔴 Searchable 인터페이스

package interface_example01;

public interface Searchable {
	void search(String url); // 추상메소드
}

🔴 Television 클래스 implements RemoteControl

package interface_example01;

public class Television implements RemoteControl{

	private int volume;
	
	@Override // 추상메소드 오버라이딩
	public void turnOn() {
		System.out.println("TV를 켭니다.");
	}

	@Override // 추상메소드 오버라이딩
	public void turnOff() {
		System.out.println("TV를 끕니다.")
	}

	@Override // 추상메소드 오버라이딩
	public void setVolume(int volume) {
		if(volume>=MAX_VOLUME) {
			this.volume = MAX_VOLUME;
		} else if(volume<= MIN_VOULME) {
			this.volume = MIN_VOULME;
		} else {
			this.volume = volume;
		}
	}
	
    @Override // 추상메소드 오버라이딩
	public int getVolume() {
		return this.volume;
	}	
}

🔴 SmartTelevision 클래스 implements RemoteControl, Searchable

package interface_example01;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SmartTelevision implements RemoteControl, Searchable {

	private int volume;
    
    // 날짜 포맷을 위한 객체 생성
	SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy년 MM월 dd일"); 
			
	@Override // 추상메소드 오버라이딩
	public void turnOn() {
		System.out.println("SmartTV를 켭니다.");
	}

	@Override // 추상메소드 오버라이딩
	public void turnOff() {
		System.out.println("SmartTV를 끕니다.");
	}

	@Override // 추상메소드 오버라이딩
	public void setVolume(int volume) {
		if(volume>=MAX_VOLUME) {
			this.volume = MAX_VOLUME;
		} else if(volume<= MIN_VOULME) {
			this.volume = MIN_VOULME;
		} else {
			this.volume = volume;
		}
	}
    
	@Override // 추상메소드 오버라이딩
	public int getVolume() {
		return this.volume;
	}

	@Override // 추상메소드 오버라이딩
	public void search(String url) {
		System.out.println("\"" + url + "\"" + "을 검색합니다.");
	}
	
    @Override // 디폴트메소드 오버라이딩
	public void date(Date date) {
		System.out.println(simpleDateFormat.format(date));
	}	
}

🔴 Main 클래스

package interface_example01;

import java.util.Date;

public class Example {

	public static void main(String[] args) {
		SmartTelevision smarttv = new SmartTelevision(); // SmartTelevision타입의 객체 생성
		RemoteControl tv_smart = smarttv; // 인터페이스 타입으로 형변환

		System.out.println("ㅡㅡㅡㅡㅡㅡ인터페이스타입의 smarttv객체ㅡㅡㅡㅡㅡㅡ");
        // 추상메소드(SmartTelevision에서 오버라이딩됨)
		tv_smart.turnOn(); 
		tv_smart.setVolume(20); 
		System.out.println(tv_smart.getVolume()); 
		tv_smart.turnOff(); 

		tv_smart.date(new Date()); // 인터페이스의 디폴트메소드(오버라이딩됨)
		
		RemoteControl.powerSavingMode(); // 인터페이스의 정적메소드
		
		Searchable search_tv_smart = smarttv; // 인터페이스 타입으로 형변환
		search_tv_smart.search("안녕하세요"); // 추상메소드(SmartTelevision에서 오버라이딩됨)
		
		System.out.println("ㅡㅡㅡㅡㅡㅡ인터페이스타입의 tv객체ㅡㅡㅡㅡㅡㅡ");
		RemoteControl tv = new Television();
        // 추상메소드(Television에서 오버라이딩됨)
		tv.turnOn();
		tv.turnOff();
		tv.setVolume(5);
		System.out.println(tv.getVolume());
        
		tv.date(new Date()); // 인터페이스의 디폴트메소드(오버라이딩X)
	}
}

🔵 실행결과

ㅡㅡㅡㅡㅡㅡ인터페이스타입의 smarttv객체ㅡㅡㅡㅡㅡㅡ
SmartTV를 켭니다.
10
SmartTV를 끕니다.
20230928일
절전모드를 킵니다.
"안녕하세요"을 검색합니다.
ㅡㅡㅡㅡㅡㅡ인터페이스타입의 tv객체ㅡㅡㅡㅡㅡㅡ
TV를 켭니다.
TV를 끕니다.
5
Thu Sep 28 13:29:04 KST 2023

인터페이스의 디폴트메소드를 오버라이딩한 smarttelevision타입의 객체에서는 오버라이딩되어 날짜포맷이 된 현재날짜가 호출되고, 인터페이스의 디폴트메소드를 오버라이딩 하지 않은 Television타입의 객체에서는 인터페이스의 날짜 포맷이 되지 않은 디폴트 메소드가 호출됨.




❓ 인터페이스에서 디폴트메소드와 정적메소드를 사용하는 이유?


java8 버전부터 새로이 추가된 스트림이나 람다와 같은 함수형 프로그래밍을 컬렉션(Collection) 클래스에서 사용하기 위해, 기존에 만들어놓았던 인터페이스들을 구현하고 있는 컬렉션 클래스들의 구조에서 특정한 기능을 추가해야 되는 상황이 오게 되었다.

그런데 만일

기존 인터페이스에 추상 메서드를 추가 해버리면, 이 인터페이스를 구현하고 있는 모든 구현 클래스도 변경이 필요해지기 때문에 추상 메서드 대신 디폴트 메서드를 새로이 추가하여 해결하였다고 보면 된다.

또한 앞으로 외부 자바 라이브러리를 이용할때, 라이브러리의 각종 인터페이스에 디폴트 메서드들이 정의되어 있을텐데 이를 익명클래스로 메서드를 호출하거나 오버라이딩 해서 재정의하거나 할때 자주 애용된다.

그리고 static 메서드는 인스턴스와 관계없는 독립적인 메서드기 때문에 인터페이스에 추가해도 상관없지만, 규칙을 단순히 할 필요가 있어서 자바에서는 허용되지 않았었다.

이 때문에 인터페이스와 관련된 static 메서드는 별도의 클래스에 따로 두어야 했다. 대표적으로 java.util.Collection 인터페이스가 있는데, 이 인터페이스와 관련된 static 메서드들이 인터페이스에는 추상 메서드만 선언할 수 있다는 원칙 때문에 별도의 클래스인 Collections 라는 클래스에 들어가게 되었다.

그렇지만 역시 자바8에 와서 위의 제약은 없어지게 되었다.



📖 인터페이스(Interface)의 디폴트메소드의 특징

  • 디폴트 메서드는 앞에 키워드 default 를 붙이며 일반 메서드처럼 구현부 { ... } 가 있어야 한다
  • 디폴트 메서드 역시 접근제어자가 public 이며 생략 가능하다.
  • 자식 클래스(구현체)에서 default 메소드를 오버라이딩 하여 재정의 가능하다.
  • 주의 해야할점은 인터페이스는 Object 클래스를 상속받지 않기 때문에, Object 클래스가 제공하는 기능(equals, hasCode)는 기본 메소드로 제공할 수 없다. 따라서 구현체가 직접 재정의를 해 주어야 한다.
  • 보통 인터페이스를 구현한 이후, 수정과정에서 인터페이스 모든 구현체에게 수정 없이 광역으로 함수를 만들어주고 싶을 때 사용된다. (대신 모든 구현체가 원하는 값을 return 하게 보장하기 위해 @implSpec 자바 doc 태그를 사용해 문서화 해줘야 한다)

📝 디폴트 메소드 구현 방법

 //디폴트 메소드
default 타입 메소드명(매개변수, ... ){
  //구현부 : 인터페이스에서 기본적으로 제공해주지만, 맘에 안들면 각자 구현해서 써라. (선택적)
}

🔴 디폴트 메소드 구현 예시

interface IJson {
    String printJson(); // 추상 메서드

    /**
     * @impspec 
     * printJson()의 결과를 대문자 변환한다.
     */
    default void uppperString() { // default 메서드
        // 구현 로직상, 추상 메서드인 printJson()의 반환 값이 정상적인 값이 될수도 있고 null이되서 예외 오류가 발생할 수 있으니 @impspec 문서화를 한다.
        String text = printJson().toUpperCase();
        System.out.println(text);
    }
}



🚫 디폴트 메소드의 다중 상속 문제

1. 다중 인터페이스들 간의 디폴트 메소드 충돌

🔴 InterfaceA

public interface InterfaceA {
	// 메소드 시그니처가 같은 디폴트 메소드
	default public void styleSame(){
        System.out.println("A 인터페이스의 디폴트 메서드 입니다.");
    }
}

🔴 InterfaceB

public interface InterfaceB {
	// 메소드 시그니처가 같은 디폴트 메소드
	default public void styleSame(){
        System.out.println("B 인터페이스의 디폴트 메서드 입니다.");
    }
}

🔴 MultiInterface 클래스

public class MultiInterface implements InterfaceA, InterfaceB{

	@Override
	public void styleSame() {
		InterfaceA.super.styleSame(); // 인터페이스A의 메소드 호출
		InterfaceB.super.styleSame(); // 인터페이스B의 메소드 호출
		System.out.println("오버리이딩된 디폴트메소드 입니다.");
	}
}

클래스에서 반드시 오버라이딩 해줘야 함.
만약 implements된 인터페이스의 메소드를 호출하고 싶다면 super 키워드를 이용하자!
인터페이스명.super.메소드

🔴 Main 클래스

public class Example {

	public static void main(String[] args) {
		 MultiInterface multi = new MultiInterface();
		 multi.styleSame();
		 
		 System.out.println("ㅡㅡㅡㅡㅡㅡㅡㅡ형변환ㅡㅡㅡㅡㅡㅡㅡㅡ");
		 InterfaceA interfaceAtype = (InterfaceA) multi;
		 interfaceAtype.styleSame();
	}
}

🔵 실행결과

A 인터페이스의 디폴트 메서드 입니다.
B 인터페이스의 디폴트 메서드 입니다.
오버리이딩된 디폴트메소드 입니다.
ㅡㅡㅡㅡㅡㅡㅡㅡ형변환ㅡㅡㅡㅡㅡㅡㅡㅡ
A 인터페이스의 디폴트 메서드 입니다.
B 인터페이스의 디폴트 메서드 입니다.
오버리이딩된 디폴트메소드 입니다.

인터페이스타입으로 형변환을 하더라도 인스턴스에서 오버라이딩된 디폴트 메소드가 호출된다!


2. 인터페이스의 디폴트 메소드와 부모 클래스 메소드 간의 충돌

🔴 InterfaceA

public interface InterfaceA {
	// 메소드 시그니처가 같은 디폴트 메소드
	default public void styleSame(){
        System.out.println("A 인터페이스의 디폴트 메서드 입니다.");
    }
}

🔴 abstract Class C

abstract public class C {
	 public void styleSame() {
	        System.out.println("C 클래스의 인스턴스 메서드 입니다.");
	    }
}

🔴 MultiInterface 클래스

public class MultiInterface extends C implements InterfaceA{
}

오버라이딩 하지 않았음!

🔴 Main 클래스

public class Example {

	public static void main(String[] args) {
		 MultiInterface multi = new MultiInterface();
		 multi.styleSame();
		 
		 System.out.println("ㅡㅡㅡㅡㅡㅡㅡㅡ형변환ㅡㅡㅡㅡㅡㅡㅡㅡ");
		 InterfaceA interfaceAtype = (InterfaceA) multi;
		 interfaceAtype.styleSame();
	}
}

🔵 실행결과

C 클래스의 인스턴스 메서드 입니다.
ㅡㅡㅡㅡㅡㅡㅡㅡ형변환ㅡㅡㅡㅡㅡㅡㅡㅡ
C 클래스의 인스턴스 메서드 입니다.

부모 클래스의 메소드가 상속되고 디폴트메소드는 무시된다.
만약 인터페이스의 디폴트 메소드가 필요할 경우 클래스에서 오버라이딩해주면 된다.




📖 인터페이스(Interface)의 정적메소드의 특징

  • 인스턴스 생성과 상관없이 인터페이스 타입으로 접근해 사용할 수 있는 메서드
  • 인터페이스 전용 static 메소드라 해서 특별한 것은 없다. 일반 클래스의 static 메소드와 다를 바 없다. (똑같이 취급 하면 된다)
  • 해당 타입 관련 헬퍼 또는 유틸리티 메소드를 제공할 때, 인터페이스에 스태틱 메소드로 제공하기도 한다.

📝 정적 메소드 구현 방법

 //정적 메소드
static 타입 메소드명(매개변수, ... ){
  //구현부 : 인터페이스에서 제공해주는 것으로 무조건 사용 (절대적)
}

📖 인터페이스(Interface)의 private 메소드의 특징

  • 자바9 버전에 추가된 메서드
  • 인터페이스에 default, static 메소드가 생긴 이후, 이러한 메소드들의 로직을 공통화하고 재사용하기 위해 생긴 메소드
  • private 메소드도 구현부를 가져야한다. 단, private 메소드는 인터페이스 내부에서만 돌아가는 코드이다. (인터페이스를 구현한 클래스에서 사용하거나 재정의 할 수 없음) 따라서 인터페이스 내부에서 private 메소드를 호출할때, default 메소드 내부에서 호출해야 하며, 만일 private static 키워드를 붙인 메소드는 static 메소드에서만 호출이 가능하다.

📝 정적 메소드 구현 방법

 //정적 메소드
private (static) 타입 메소드명(매개변수, ... ){
  //구현부 : 인터페이스에서 제공해주는 것으로 무조건 사용 (절대적)
}

🔴 정적 메소드 구현 예시

public interface InterfaceC {
	
	// private 메소드 : 내부에서만 호출 가능
	private void private_method() {
		System.out.println("A 인터페이스의 private 메소드 입니다");
	}
	
	// private static 메소드 : 내부에서만 호출 가능
	private static void privatestatic_method() {
		System.out.println("A 인터페이스의 private static 메소드 입니다");	
	}
	
	// static 메소드
	static void static_method() {
		System.out.println("A 인터페이스의 static 메소드 입니다.");
		privatestatic_method(); // 내부에서만 호출 가능함
	}
	
	default public void default_method(){
        System.out.println("A 인터페이스의 디폴트 메서드 입니다.");
        private_method(); // 내부에서만 호출 가능
        privatestatic_method(); // 내부에서만 호출 가능
	}
}


public class MultiInterface implements InterfaceC{
}

public class Example {
	public static void main(String[] args) {
		 MultiInterface multi = new MultiInterface();
		 multi.default_method();
		 
		 System.out.println("ㅡㅡㅡㅡㅡㅡㅡㅡ형변환ㅡㅡㅡㅡㅡㅡㅡㅡ");
		 InterfaceC interfaceCtype = (InterfaceC) multi;
		 interfaceCtype.default_method();
		 System.out.println("ㅡㅡㅡㅡㅡ인터페이스C의 스태틱메소드입니다.ㅡㅡㅡㅡㅡㅡ");
		 InterfaceC.static_method();
	}
}

🔵 실행 결과

A 인터페이스의 디폴트 메서드 입니다.
A 인터페이스의 private 메소드 입니다
A 인터페이스의 private static 메소드 입니다
ㅡㅡㅡㅡㅡㅡㅡㅡ형변환ㅡㅡㅡㅡㅡㅡㅡㅡ
A 인터페이스의 디폴트 메서드 입니다.
A 인터페이스의 private 메소드 입니다
A 인터페이스의 private static 메소드 입니다
ㅡㅡㅡㅡㅡ인터페이스C의 스태틱메소드입니다.ㅡㅡㅡㅡㅡㅡ
A 인터페이스의 static 메소드 입니다.
A 인터페이스의 private static 메소드 입니다



📖 인터페이스의 다양한 활용도

📝 인터페이스의 다형성
📝 형제 관계를 맺어줌
📝 타입 접근 제한
📝 메서드 접근 제한
📝 마커 인터페이스

📝 인터페이스(Interface)의 다형성

💡 Collection

부모클래스 타입으로 자식 클래스 타입을 포함 시킬수 있다는 다형성의 법칙도 인터페이스에 고대로 적용이 가능하다.

클래스가 여러 개의 인터페이스를 구현하게 되면, 결과적으로 변수의 타입으로도 다양하게 쓰일 수 있다는 것을 의미하게 된다. 인터페이스 타입으로 변수를 선언하게 되면 사용하는 입장에서는 뒤에 오는 모든 객체는 간단히 인터페이스만 구현한 객체이면되기 때문에 좀 더 시스템이 유연해지는 계기를 마련하게 된다.

💡 객체는 클래스가 아닌 인터페이스로 참조하는 것이 좋다. 적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하면 좋다.


  • 객체는 인터페이스를 사용해 참조하라.
  • 적당한 인터페이스가 있다면 매개변수뿐만 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라.
  • 객체의 실제 클래스를 사용할 상황은 '오직' 생성자로 생성할 때 뿐이다.
  • 매개변수 타입으로는 클래스 보다는 인터페이스를 활용하라.

🔴 인터페이스 참조 예시1

// 나쁜 예) 클래스를 바로 타입으로 사용했다.
LinkedHashSet<Object> s = new LinkedHashSet<>();

// 좋은 예) 인터페이스를 타입으로 사용했다.
Set<Object> s = new LinkedHashSet<>();


📝 형제 관계를 맺어줌

🔴 인터페이스 참조 예시2

// 인터페이스
public interface Sports {
	void play();
}

// 클래스
public class Baseball extends USA implements Sports{
	@Override
	public void play() {
		System.out.println("야구를 합니다");
	}
}

// 클래스
public class Soccer extends EU implements Sports{
	@Override
	public void play() {
		System.out.println("축구를 합니다.");
	}
}
  
// 메인 클래스
  public class Example {
	public static void main(String[] args) {
		play(new Soccer());
		play(new Baseball());
	}

	static void play(Sports sports) { // Sports sports = new Soccer(); 형변환이 일어남
		sports.play();
	}
}

🔵 실행결과

축구를 합니다.
야구를 합니다

다른 부모 클래스를 상속하고 있는 Soccer 클래스와 Baseball 클래스를 하나의 타입으로 묶어서 사용할 필요가 있을때, 인터페이스를 implements 함으로써 마치 Soccer 와 Baseball 클래스를 묶은 형제 클래스 타입 Sports 를 만든것과 같다.



📝 타입 접근 제한

이는 위의 형제 관계 역할과 매우 비슷한 개념인데, 만일 똑같은 부모를 상속하고 있는 3개의 자식들중, 2개의 자식 클래스 타입만 받을 수 있는 메서드를 구현한다고 했을때 이용된다.

🔴 인터페이스 참조 예시3

interface Machine { } // SCV, Tank 클래스를 통합한 타입으로 이용하는 인터페이스

class GroundUnit { }

class Marine extends GroundUnit{ }
class SCV extends GroundUnit implements Machine{ }
class Tank extends GroundUnit implements Machine{ }

public class Main {
    public static void main(String[] args) {
        repair(new Marine()); // ! ERROR
    }

    static void repair(Machine gu) {
        // SVG와 탱크 타입만 받을 수 있게 인터페이스를 타입으로 하여 다형성을 적용
    }
}



📝 메서드 접근 제한

객체에서 사용할 수 있는 메서드를 제한 하는 효과도 있는데, 예를들어 A, B, C라는 인터페이스를 구현한 클래스를 반환할 때 A 타입으로 변환하게 되면 외부에서는 A 인터페이스의 메소드만 보이게 된다. 따라서 별도의 접근 제한을 이용하지 않고도 사용할 수 있는 메서드 접근 제한과 마찬가지 효과를 보게 하는 방법이다.

이런 이유로 오히려 거꾸로 클래스에 여러 가지 메소드를 만들어 둔 다음 인터페이스로 분리하는 작업을 진행하는 경우가 가끔 있다.


📝 마커 인터페이스

자바의 마커 인터페이스는 일반적인 인터페이스와 동일하지만 사실상 아무 메소드도 선언하지 않은 빈 껍데기 인터페이스를 말한다.

아무런 내용이 없는 빈 껍데기 인터페이스를 선언하고 적절한 클래스에 implements 시킴으로써, 추상화, 다형성 이런걸 떠나서

그냥 단순한 타입 체크용으로 사용하는 것이다.

이러한 마커 인터페이스의 대표적인 자바 인터페이스로는 Serializable, Cloneable 정도 있다.



📖 인터페이스(Interface)의 다중 상속

인터페이스는 다중 상속이 가능하고 구현 코드의 상속이 아니므로 타입 상속 이라고 한다

🔴 인터페이스 다중 상속 예시

public interface InterfaceA {
	void methodA();
}

public interface InterfaceB {
	void methodB();
}
  
// 다중상속 받는 인터페이스
public interface InterfaceAB extends InterfaceA, InterfaceB{
	void methodAB();
}
  
// 클래스 
public class Interface_inheritance implements InterfaceAB{

	@Override
	public void methodA() {
		System.out.println("methodA 입니다");
	}

	@Override
	public void methodB() {
		System.out.println("methodB 입니다");
	}

	@Override
	public void methodAB() {
		System.out.println("methodAB 입니다");
	}
}
  
// 메인 클래스
public class Example {
	public static void main(String[] args) {
		
		Interface_inheritance exam = new Interface_inheritance();
		exam.methodA();
		exam.methodB();
		exam.methodAB();
		
		// 인터페이스A타입으로 형변환
		System.out.println("ㅡㅡㅡㅡ인터페이스A타입으로 형변환ㅡㅡㅡㅡ");
		InterfaceA intera = exam;
		intera.methodA();
		
		// 인터페이스AB타입으로 형변환
		System.out.println("ㅡㅡㅡㅡ인터페이스AB타입으로 형변환ㅡㅡㅡㅡ");
		InterfaceAB interab = exam;
		interab.methodA();
		interab.methodB();
		interab.methodAB();
		
		// 인터페이스B타입으로 형변환
		System.out.println("ㅡㅡㅡㅡ인터페이스B타입으로 형변환ㅡㅡㅡㅡ");
		InterfaceB interb = exam;
		interb.methodB();
		
	}
}

🔵 실행 결과

methodA 입니다
methodB 입니다
methodAB 입니다
ㅡㅡㅡㅡ인터페이스A타입으로 형변환ㅡㅡㅡㅡ
methodA 입니다
ㅡㅡㅡㅡ인터페이스AB타입으로 형변환ㅡㅡㅡㅡ
methodA 입니다
methodB 입니다
methodAB 입니다
ㅡㅡㅡㅡ인터페이스B타입으로 형변환ㅡㅡㅡㅡ
methodB 입니다

참고 : https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4Interface%EC%9D%98-%EC%A0%95%EC%84%9D-%ED%83%84%ED%83%84%ED%95%98%EA%B2%8C-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC

0개의 댓글