인터페이스

Bummy·2023년 9월 3일
0

JAVA

목록 보기
8/11
post-thumbnail

8.1 인터페이스의 역할

  • 인터페이스는 객체의 사용 방법을 정의한 타입
  • 객체의 교환성을 높여주기 때문에 다형성을 구현하는 매우 중요한 역할을 함
  • 인터페이스는 개발 코드와 객체가 서로 통신하는 접점 역할을 한다.
  • 개발 코드가 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출시킨다. 개발 코드는 객체의 내부 구조를 알 필요가 없고 인터페이스의 메소드만 알고 있으면 된다.

8.2 인터페이스 선언

8.2.1 인터페이스 선언

  • 인터페이스 선언은 class 키워드 대신에 interface 키워드를 사용한다.
public interface 인터페이스명 {...}
  • 클래스는 필드, 생성자, 메소드를 구성 멤버로 가지는 반면에 인터페이스는 상수와 메소드만을 멤버로 가진다.
interface 인터페이스명 {
	//상수 
	타입 상수명 =;
	//추상 메소드
	타입 메소드명(매개변수, ...);
	//디폴트 메소드
	default 타입 메소드명(매개변수, ...){...}
	//정적 메소드 
	static 타입 메소드명(매개변수){...}
}

상수 필드(Constant Field)

  • 인터페이스는 필드는 선언할 수 없지만 상수 필드는 선언이 가능하다.
  • 상수를 선언할 때는 반드시 초기값을 대입해야한다.

추상 메소드(Abstract Method)

  • 객체가 가지고 있는 메소드를 설명한 것으로 호출할 때 어떤 매개값이 필요하고, 리턴 타입이 무엇인지만 알려준다.
  • 실제 실행부는 객체(구현 객체)가 가지고 있다.

디폴트 메소드(Default Method)

  • 인터페이스에 선언되지만 사실은 구현 객체가 가지고 있는 인스턴스 메소드

정적 메소드(Static Method)

  • 디폴트 메소드와는 달리 객체가 없어도 인터페이스만으로 호출이 가능하다.

8.2.2 상수 필드 선언

  • 인터페이스는 데이터를 저장할 수 없기 때문에 데이터를 저장할 인스턴스 또는 정적 필드를 선언할 수 없다.
  • 대신 상수 필드만 선언할 수 있으며 public, static, final을 생략하더라도 자동적으로 컴파일 과정에서 public static final의 특성이 붙는다.
[public static final ] 타입 상수명 =;

8.2.3 추상 메소드 선언

  • 인터페이스를 통해 호출된 메소드는 최종적으로 구현 객체에서 실행된다.
  • 인터페이스의 메소드는 실행 블록이 필요 없는 추상 메소드로 선언한다.
  • 추상 메소드는 리턴 타입, 메소드명, 매개변수만 기술되고 중괄호 {}를 붙이지 않는 메소드를 말한다.
  • public abstract의 특성을 갖기 때문에 public abstract를 생략하더라도 자동적으로 컴파일 과정에서 붙게된다.
[public abstract] 리턴타입 메소드명(매개변수, ...);

8.2.4 디폴트 메소드 선언

  • default 키워드가 리턴 타입 앞에 붙고 public 특성을 갖는다.
[public] default 리턴타입 메소드명(매개변수, ...){...}

8.2.5 정적 메소드 선언

  • 클래스의 정적 메소드와 형태는 동일
  • public 특성을 갖기 때문에 public을 생략하더라도 자동적으로 컴파일 과정에서 붙게 된다.
[public] static 리턴 타입 메소드명(매개변수, ...){...}

8.3 인터페이스 구현

  • 개발 코드가 인터페이스 메소드를 호출하면 인터페이스는 객체의 메소드를 호출한다.
  • 객체는 인터페이스에서 정의된 추상 메소드와 동일한 메소드 이름, 매개 타입, 리턴 타입을 가진 실체 메소드를 가지고 있어야 한다.
  • 이러한 객체를 인터페이스의 구현(implement) 객체라고 하고, 구현 객체를 생성하는 클래스를 구현 클래스라고 한다.

8.3.1 구현 클래스

  • 인터페이스 타입으로 사용할 수 있음 알려주기 위해 클래스 선언부에 implement 키워드를 추가하고 인터페이스명을 명시해야 한다.
public class 구현 클래스명 implement 인터페이스명 {
	//인터페이스에 선언된 추상 메소드의 실체 메소드 선언
}
💡 인터페이스의 모든 메소드는 기본적으로 public 접근 제한을 갖기 때문에 public보다 더 낮은 접근 제한으로 작성할 수 없다.

8.3.2 익명 구현 객체

  • 구현 클래스를 만들어 사용하는 것이 일반적이고, 클래스를 재사용할 수 있기 때문에 편리하지만, 일회성의 구현 객체를 만들기 위해 소스 파일을 만들고 클래스를 선언하는 것은 비효율적
  • 소스 파일을 만들지 않고도 구현 객체를 만들 수 있는 방법을 자바는 제공하는데 이것이 익명 구현 객체
  • 주로 UI 프로그래밍에서 이벤트를 처리하기 위해, 임시 작업 스레드를 만들기 위해 익명 구현 객체를 많이 활용한다.
인터페이스 변수 = new 인터페이스(){
	//인터페이스에 선언된 추상 메소드의 실제 메소드 선언
};
  • 인터페이스(){}는 인터페이스를 구현해서 중괄호 {}와 같이 클래스를 선언하라는 뜻이고, new 연산자는 이렇게 선언된 클래스를 객체로 생성한다.
  • 중괄호{} 에서는 인터페이스에 선언된 모든 추상 메소드들의 실체 메소드를 작성해야하며 그렇지 않으면 컴파일 에러가 발생한다.

8.3.3 다중 인터페이스 구현 클래스

  • 인터페이스 A와 인터페이스 B가 객체의 메소드를 호출할 수 있으려면 객체는 이 두 인터페이스를 모두 구현해야한다.
public class 구현클래스명 implements 인터페이스A, 인터페이스B {
	//인터페이스 A에 선언된 추상 메소드의 실체 메소드 선언
	//인터페이스 B에 선언된 추상 메소드의 실체 메소드 선언
}
  • 다중 인터페이스를 구현할 경우, 구현 클래스는 모든 인터페이스의 추상 메소드에 대해 실체 메소드를 작성해야한다. 만약 하나라도 없으면 추상 클래스로 선언해야 한다.

8.4 인터페이스 사용

  • 인터페이스로 구현 객체를 사용하려면 인터페이스 변수를 선언하고 구현 객체를 대입해야 한다.
  • 인터페이스 변수는 참조 타입이기 때문에 구현 객체가 대입될 경우 구현 객체의 번지를 저장한다.
인터페이스 변수;
변수 = 구현객체;

RemoteControl = rc;
rc = new Television();
rc = new Audio();
  • 개발 코드에서 인터페이스는 클래스의 필드, 생성자 또는 메소드의 매개 변수, 생성자 또는 메소드의 로컬 변수로 선언될 수 있다.

8.4.1 추상 메소드 사용

  • 구현 객체가 인터페이스 타입에 대입되면 인터페이스에 선언된 추상 메소드를 개발 코드에서 호출할 수 있게 된다.
RemoteControl rc = new Television();
rc.turnOn(); //Telvision의 turnOn() 실행
rc.turnOff(); //Telvisoin의 turnOff() 실행

8.4.2 디폴트 메소드 사용

  • 디폴트 메소드는 인터페이스에 선언되지만, 인터페이스에서 바로 사용할 수 없다.
  • 디폴트 메소드는 추상 메소드가 아닌 인스턴스 메소드이므로 구현 객체가 있어야 사용할 수 있다.
RemoteControl rc = new Telvision();
rc.setMute(true);
  • setMute() 메소드를 호출하려면 다음과 같이 구현 객체가 필요하다.

8.4.3 정적 메소드 사용

  • 인터페이스의 정적 메소드는 인터페이스로 바로 호출이 가능하다.

8.5 타입 변환과 다형성

  • 다형성은 하나의 타입에 대입되는 객체에 따라서 실행 결과가 다양한 형태로 나오는 성질을 말한다.
  • 인터페이스 타입에 어떤 구현 객체를 대입하느냐에 따라 실행 결과가 달라진다.
  • 프로그램을 개발할 때 인터페이스를 사용해서 메소드를 호출하도록 코딩을 했다면, 구현 객체를 교체하는 것은 매우 손쉽고 빠르게 할 수 있다. 프로그램 소스는 변함이 없는데, 구현 객체를 교체함으로써 프로그램의 실행 결과가 다양해질 수 있다. → 이것이 인터페이스의 다형성

8.5.1 자동 타입 변환(Promotion)

  • 구현 객체가 인터페이스 타입으로 변환되는 것은 자동 타입 변환(Promotion)에 해당한다.
  • 자동 타입 변환은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다.
  • 인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면 자식 객체 역시 인터페이스 타입으로 자동 변환 시킬 수 있다.
인터페이스 변수 = 구현 객체;
//자동 타입 변환

8.5.2 필드의 다형성

  • 필드의 다형성은 상속과 같이 타이어 클래스 타입으로 설명할 수 있다.
  • 한국 타이어와 금호 타이어는 공통적으로 타이어 인터페이스를 구현했기 때문에 모두 타이어 인터페이스에 있는 메소드를 가지고 있다. 따라서 타이어 인터페이스로 동일하게 사용할 수 있는 교체 가능한 객체에 해당한다.
  • 필드 타입으로 타이어 인터페이스를 선언하게 되면 필드값으로 한국 타이어 또는 금호 타이어 객체를 대입할 수 있다. 자동 타입 변환이 일어나기 때문에 아무런 문제가 없다.
💡 타이어 구현 객체가 교체 되어도 사용하는데는 전혀 문제가 없으며, 교체하기 전에는 한국 타이어의 roll() 메소드가 호출되었다면, 교체 후에는 금호 타이어의 roll() 메소드가 호출된다. Car() 메소드 수정 없이도 다양한 roll() 메소드의 실행 결과를 얻을 수 있게 된다. **→ 이것이 필드의 다형성이다.**

8.5.3 인터페이스 배열로 구현 객체 관리

  • 각각의 타이어 필드를 다음과 같이 인터페이스 배열로 관리할 수도 있다.
Tire[] tire = {
	new HankookTire();
	new HankookTire();
	new HankookTire();
	new HankookTire();
};
  • 배열로 선언하면 인덱스로 표현되므로 대입이나 제어문에서 활용하기 쉬워진다.
tires[1] = new KumhoTire();
  • 구현 객체들을 배열로 관리하면 제어문에서 가장 많은 혜택을 본다.
  • 다음과 같이 전체 타이어의 roll() 메소드를 호출하는 Car 클래스의 run() 메소드는 다음과 같이 for문으로 작성할 수 있다.
void run() {
	for(Tire tire : tires){
			tire.roll();
	}
}

8.5.4 매개 변수의 다형성

  • 자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다.
public class Driver {
	public void drive(Vehicle vehicle){ //매개변수에 vehicle 타입으로 선언됨
		vehicle.run();
	}
}
public interface Vehicle{
	public void run();
}
  • 만약 Bus가 구현 클래스라면 다음과 같이 Driver의 drive() 메소드를 호출할 때 Bus 객체를 생성해서 매개값으로 줄 수 있다.
Driver dirver = new Driver();

Bus bus = new Bus();

**driver.drive(bus); //자동 타입 변환 발생 Vehicle vehicle = bus;**
  • drive() 메소드는 Vehicle 타입을 매개 변수로 선언했지만, Vehicle을 구현한 Bus 객체가 매개값으로 사용되면 자동 타입 변환이 발생한다.

  • 매개 변수의 다형성 예제

public class Driver{
	public void drive(Vehicle vehicle){
		vehicle.run();
	}
}
public interface Vehicle{
	public void run();
}
public class Bus implements Vehicle{
	@Override
	public void run(){
		System.out.println("버스가 달립니다.");
	}

public class Taxi implements Vehicle{
	@Override
	public void run(){
		System.out.println("택시가 달립니다.");
	}
}
public class DriverExample{
	public static void main(String[] args){
		Driver driver = new Driver();

		Bus bus = new Bus();
		Taxi taxi = new Taxi();

		driver.drive(bus); //자동 타입 변환 발생
		driver.drive(taxi); //자동 타입 변환 발생
	}
}  

8.5.5 강제 타입 변환(Casting)

  • 구현 객체가 인터페이스 타입으로 자동 변환하면, 인터페이스에 선언된 메소드만 사용이 가능하다는 제약 사항이 따른다.
  • 구현 클래스에 있는 메소드나 필드를 사용해야할 경우 강제 타입 변환을 통해서 구현 클래스의 필드와 메소드를 사용할 수 있다.
구현 클래스 변수 = (구현 클래스) 인터페이스 변수;
//강제 타입 변환

8.5.6 객체 타입 확인(instanceof)

  • 강제 타입 변환은 구현 객체가 인터페이스 타입으로 변환되어 있는 상태에서 가능
  • 어떤 구현 객체가 변환되어 있는지 알 수 없는 상태에서 무작정 변환을 할 경우 ClassCastException이 발생할 수도 있다.
  • instanceof 연산자를 통해 어떤 구현 객체가 인터페이스 타입으로 변환되었는지 확인할 수 있다.
if(vehicle instanceof Bus){
	Bus bus = new Bus();
}

8.6 인터페이스 상속

  • 인터페이스도 다른 인터페이스를 상속할 수 있으며, 인터페이스는 클래스와 달리 다중 상속을 허용한다.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2{
	...
}
  • 하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메소드뿐만 아니라 상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드를 가지고 있어야한다.
하위인터페이스 변수 = new 구현클래스(...);
상위인터페이스1 변수 = new 구현클래스(...);
상위인터페이스2 변수 = new 구현클래스(...);
  • 하위 인터페이스로 타입 변환이 되면 상하위 인터페이스에 선언된 모든 메소드를 사용할 수 있으나, 상위 인터페이스로 타입 변환되면 상위 인터페이스에 선언된 메소드만 사용 가능하고 하위 인터페이스에 선언된 메소드는 사용할 수 없다.

8.7 디폴트 메소드와 인터페이스 확장

  • 디폴트 메소드는 인터페이스에 선언된 인스턴스 메소드이기 때문에 구현 객체가 있어야 사용할 수 있다.

8.7.1 디폴트 메소드의 필요성

  • 인터페이스에서 디폴트 메소드를 허용한 이유는 기존 인터페이스를 확장해서 새로운 기능을 추가하기 위해서이다.
  • 예를 들어 MyInterface라는 인터페이스와 이를 구현한 MyClassA라는 클래스가 있다. MyInterface에 기능을 추가해야해서 추상 메소드를 추가했는데 MyClassA에서 문제가 발생할 경우 MyInterface에 디폴트 메소드를 선언한다.
  • 디폴트 메소드는 추상 메소드가 아니기 때문에 구현 클래스에서 실체 메소드를 작성할 필요가 없다.

8.7.2 디폴트 메소드가 있는 인터페이스 상속

  • 부모 인터페이스에 디폴트 메소드가 정의 되어 있을 경우, 자식 인터페이스에서 디폴트 메소드를 활용하는 방법은 세 가지 방법이 있다.
    • 디폴트 메소드를 단순히 상속만 받는다.
    • 디폴트 메소드를 재정의(Override)해서 실행 내용을 변경한다.
    • 디폴트 메소드를 추상 메소드로 재선언한다
public interface ParentInterface{
	public void method1();
	public default void method2(){ //실행문
	}
}

public interface ChildInterface1 extends ParentInterface{
	public void method3();
}

→ 이 경우 ChildInterface1 인터페이스를 구현하는 클래스는 method1()과 method3()의 실체 메소드를 가지고 있어야 하며 ParentInterface의 method2()를 호출할 수 있다.

0개의 댓글