인터페이스(Interface)

기록하는 용도·2022년 8월 10일
0

Interface

자바 인터페이스 개념

"다양한 계층 구조 형성(다중 상속의 효과)을 통한 다형성 적용"

인터페이스를 이용하면 다양한 계층 구조 형성을 통해 다형성을 극대화할 수 있다.
-> 자바는 단일 상속이고 다중 상속의 장점을 인터페이스로 채택한 것이다.

인터페이스는 상속의 장점 중 멤버를 하위에게 물려주지는 않는다. (재사용성은 x)
인터페이스는 다양한 계층 구조 형성에 초점을 맞춘다.

인터페이스는 객체화 될 수 없다. (abstract class와 공통적인 면)
인터페이스는 인스턴스 변수와 구현된 객체의 멤버 메서드를 가질 수 없다.
(예외적으로 jdk 1.8 이상에서는 default 메서드와 static 메서드를 가질 수 있다.)



인터페이스 문법

interface는 public static final 상수와 abstract method로 구성되어있다.

public interface Flyer{
	String ID="javaking"; 
    //public static final로 자동 인식 (상수)
	void fly(); 
    // 구현부를 가질 수 없기에 public abstract로 자동 인식 -> 추상 메소드 역할로 하위에게 구현 강제
	}
    
public class Bird implements Flyer{ // 반드시 상위 인터페이스의 추상메소드를 구현해야한다.
	public void fly(){
		구현부
	}	
}



예제1)


위 StarUML을 보고 유추할 수 있는것은 Flyer interface를 implements하는 Bird, Airplane, SuperMan이 있다는 것과 Flyer interface에는 fly() 라는 추상메소드가 구현되어있다는 것이다.

public class TestInterface1 {
	public static void main(String[] args) {
		//상위 인터페이스 타입의 변수로 하위 객체를 참조할 수 있다. -> 다형성
		Flyer f1 = new Bird();
		f1.fly();
		
		Flyer f2 = new Airplane();
		f2.fly();
		
		Flyer f3 = new SuperMan();
		f3.fly();
//		사용자 측에서는 flyer 인터페이스를 사용하는것을 안다면, 난다는 것을 알 수 있다.
		
//		Flyer 인터페이스의 static final 상수에 접근
		System.out.println(Flyer.ID);
		//final 변수이므로 재할당은 불가하다.
	}
}
public interface Flyer {
	void fly();
	
	String str = "날다";
	
}
public class Airplane implements Flyer {

	@Override
	public void fly() {
		System.out.println("비행기가 날다");
	}

}
public class SuperMan implements Flyer {

	@Override
	public void fly() {
		System.out.println("슈퍼맨이 날다");
	}

}



예제1.2) service class와 함께 구현해보기

예제1에서 만들어놓은 Bird 클래스를 import하고, service 클래스에서 succeed 함수를 실행할 때 Bird 객체를 넣어 호출하면
"새가 날다"
라는 문장을 출력하도록 해보자.

package step2;

import step1.Bird;

public class TestInterface2 {
	public static void main(String[] args) {
		FlyerService service = new FlyerService();
		service.succeed(new Bird());
	}
}
import step1.Flyer;

public class FlyerService {
	public void succeed(Flyer f) {
		f.fly();
	}
}

새가 날다

마찬가지로 예제1의 출력과 같이 Airplane, SuperMan 객체를 넣고 같은 결과를 출력하도록 해보자.

service.succeed(new SuperMan());
service.succeed(new Airplane());

위 두 문장을 main 함수에 넣어주면 같은 결과를 출력할 수 있다.


예제3)


예제1과 같은 예제이다. 예제2처럼 service 클래스와 함께 코드로 구현해보는 예제이다.

먼저 StarUML을 보면 CDPlayer, GomPlayer, Youtube는 재생하기인 play() 메소드를 갖고있다.
이는 인터페이스의 개념인 "다양한 계층 구조 형성(다중 상속의 효과)을 통한 다형성 적용"
을 확인할 수 있는 예제이다.
GomPlayer, Youtube, CDPlayer는 play()라는 '재생하기' 기능을 동일하게 갖고있다. 각자 다른 객체이지만 동일한 기능을 갖고있다는 것이다.
즉 '재생하기'버튼이라는 하나의 소통방식으로 다양한 객체(GomPlayer,Youtube,CDPlayer)들이 각자의 방식으로 동작하는 것이다. 이는 다형성의 장점이기도 하다.

  1. 세 개의 클래스(GomPlayer, CDPlayer, Youtube)는 play()라는 "재생하기" 기능을 동일하게 갖고있으므로 Player라는 interface를 구현(implements)하도록 하게한다.
public interface Player {
	void play();
}

이렇게 play() 메소드를 갖고있는 Player라는 interface를 생성한다.

  • 인터페이스의 모든 메소드는 public abstract이어야하며, 이를 생략할 수 있다. 즉, 하위에서 이 추상 메소드를 구현해야만을 알 수 있다.
  1. 재생기인 Player를 구현 혹은 사용(implements)하는 세 개의 클래스를 생성한다. (GomPlayer, Youtube, CDPlayer)
public class CDPlayer implements Player {
	@Override
	public void play() {
		System.out.println("CD플레이어 재생하다");
	}
}
public class GomPlayer implements Player{
	
	public void play() {
		System.out.println("곰플레이어 재생하다");
	}
}
public class Youtube implements Player{
	@Override
	public void play() {
		System.out.println("유튜브 재생하다");
	}
}

세 개의 클래스는 재생하기라는 재생하기라는 동일한 기능이 있으므로 이 기능을 가진 인터페이스를 구현(implements)하도록 코드를 생성해준다.
Player 인터페이스는 public abstract로 만들어진 play()기능이 있으므로 이를 구현하는 세개의 하위 클래스들은 필수적으로 오버라이딩해주어야한다.
각자 출력 문구를 작성해주었다.

  1. main 메소드에 각각의 개체를 만들어 실행하도록 한다.
public class TestInterface3 {
	public static void main(String[] args) {
		Player p1 = new GomPlayer();
		p1.play();
		
		Player p2 = new Youtube();
		p2.play();
		
		Player p3 = new GomPlayer();
		p3.play();
		
	}
}

곰플레이어 재생하다
유튜브 재생하다
곰플레이어 재생하다

가 출력되는것을 볼 수 있다.

  • service class를 이용해 출력하기
  1. main 메소드 변경과 service 클래스 생성하기
public class TestInterface3 {
	public static void main(String[] args) {
		PlayerService service = new PlayerService();
		service.proceed(new GomPlayer());
		service.proceed(new Youtube());
		service.proceed(new CDPlayer());
	}
}
public class PlayerService {
	public void proceed(Player p) {
		p.play();
	}
}

service를 통해서 proceed 메소드에 재생하고싶은 객체를 넣어 출력할 수 있도록 했다.
service 객체를 만들어 호출하면 그에 맞는 play() 메소드가 실행되는 것이다.
PlayerService 클래스의 함수에 Player p를 넣은 뜻은 Player를 구현(implements)한 클래스 만이 매개변수로 들어올 수 있다는 뜻이고, 세개의 클래스는 Player의 하위 클래스이기때문에 Override 작업한 메소드들이 실행됨을 알 수 있다.

곰플레이어 재생하다
유튜브 재생하다
CD플레이어 재생하다

결과는 동일하다.



인터페이스를 이용한 다중상속

인터페이스는 인터페이스로부터만 상속받을 수 있으며, 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다. 상속의 개념과 마찬가지로 조상 인터페이스로부터 정의된 멤버를 모두 상속받는다.

class B{
	
}

interface Ex1{
	void e1();
}

interface Ex2{
	void e2();
}

class C extends B implements Ex1, Ex2{
	public void e1() {
		//구현부
	}
	public void e2() {
		//구현부
	}
}

이 예제를 보면 클래스 C는 B를 상속받고 또 Ex1과 Ex2인 두개의 인터페이스를 다중 상속(구현)하고있다.
상속과 구현 동시에 가능하다는 것이다.

  • 클래스 C가 두 개의 인터페이스 Ex1, Ex2의 public abstract 형태의 구현되지 않은 메소드를 오버라이드 해야함을 알 수 있다.

자바 클래스는 다양한 인터페이스를 구현할 수 있다.
-> 다양한 계층 구조 형성
-> 다형성 적용

interface Ex3 extends Ex1, Ex2{
	void e3();
}
class D implements Ex3{
	@Override
	public void e1() {
	}
	@Override
	public void e2() {
	}
	@Override
	public void e3() {
	}
}


StarUML은 이렇다. (아직 사용법이 익숙치않아 보기 어려움)
이 예제는 상위에 구현되고 상속받고 있는 모든 멤버들은 하위에서 전부 상속되어짐을 알 수 있는 예제이다.

클래스 D는 Ex3만을 구현(implements)하고있지만 Ex3은 Ex1과 Ex2 두개를 다중 상속하고있으며 이 두개의 인터페이스들은 구현부가 존재하지않는 메소드를 각각 갖고있기때문에 가장 하위의 클래스 D는 이 전부를 상속받아 오버라이딩 하고 있다는 것을 알 수 있다.

public class TestInterface4 {
	public static void main(String[] args) {
		D d = new D();
		Ex3 e3 = new D();
	}
}

상위 인터페이스 타입으로 하위 객체를 참조할 수 있다.

e3.e1();
e3.e2();
e3.e3();

마찬가지로 각자 상속받은 멤버들이 존재하는 것을 알 수 있다.



예제4)


예제1에서 몇가지 추가된 것을 볼 수 있다.
Bird, Airplane, SuperMan은 Flyer를 implements하고 있지만 이 세개의 클래스가 같은 조상을 갖고있다고 할 수는 없다.
Airplane은 전동기, Bird는 생물, SuperMan은 딱히 어느곳에 속한다고 할 수 없기때문이다.
이처럼 각자의 조상은 따로 있다고 할 때 이의 일부를 코드로 구현해 보는 예제이다.

1.Eagle class

public class TestInterface5 {
	public static void main(String[] args) {
		Flyer f = new Eagle();
		f.fly();
	}
}
import step1.Bird;

public class Eagle extends Bird{
	public void hunt() {
		System.out.println("사냥하다");
	}
	//Bird의 fly를 자신에 맞게 재정의 : 오버라이딩
	@Override
	public void fly() {
		System.out.println("독수리가 날다");
	}
}

Eagle class는 Bird class를 상속받고 Bird class는 Flyer interface를implements 하므로 Eagle class에서는 별도로 Flyer를 implements 하지 않아도 Flyer 인터페이스 타입으로 참조 가능하다.
-> 즉 다형성 적용됨



인터페이스의 예외

인터페이스 내에서는 모든 멤버변수는 public static final 이어야하며, 이를 생략할 수 있다.
모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
단, static메소드와 default 메소드는 jdk 1.8부터 예외이다. 이에 대한 예제를 알아보자.

interface Bank{
	void deposit();
}
  1. Bank 인터페이스에서 구현부가 없고 public abstract가 생략된 deposit() 메소드를 볼 수 있다.
    위에 예제와 마찬가지로 몇 종류의 bank는 이를 구현(implements)하게 된다면 반드시 오버라이딩해야한다.
class KaKaoBank implements Bank{
	
	@Override
	public void deposit() {
	}
}
class HanaBank implements Bank{
	@Override
	public void deposit() {
	}
}
  1. 만약 deposit() 메소드 이외에 onlineDeposit() 메소드가 Bank 인터페이스에서 추가된다면? KaKaoBank와 HanaBank, 만약 또다른 종류의 은행 클래스들이 있다면 전부 오버라이딩 해줘야한다.
    하지만 jdk 1.8 이상에서 default method, static method만 구현부를 가질 수 있음을 지원한다.
interface Bank{
	void deposit(); //public abstract 메소드
	
	default void onlineDeposit() {
		//예외적으로 구현부를 갖춘 default 메소드를 1.8이상에서 지원
	}
	
	public static void test() {
		// 예외적으로 구현부를 갖춘 static 메소드도 1.8 이상에서 지원
	}
}
class KaKaoBank implements Bank{
	
	@Override
	public void deposit() {
	}
	
	@Override
	public void onlineDeposit() {
		Bank.super.onlineDeposit();
	}
	
}

class HanaBank implements Bank{
	@Override
	public void deposit() {
	}
}

이처럼 추가된 onlineDeposit() 메소드를 필요로하는 경우에 KaKaoBank처럼 이를 오버라이딩 해도되고, 필요하지않다면 HanaBank처럼 오버라이딩 하지않아도 된다.

0개의 댓글