Notice
Recent Posts
Recent Comments
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Archives
Today
Total
관리 메뉴

SYDev

[혼자 공부하는 자바] Chapter 8. 인터페이스 본문

Programming Lang/Java

[혼자 공부하는 자바] Chapter 8. 인터페이스

시데브 2025. 1. 29. 19:44

1. 인터페이스 역할

  • 인터페이스(Interface): 사전적인 의미로 두 장치를 연결하는 접속기, 두 장치를 객체로 본다면 두 객체를 연결하는 역할
  • 주로 다형성 구현하는 경우 

-> 객체 B가 객체 C로 변경됐을 때, 객체 B를 직접 호출하는 코드의 경우에는 객체 C를 호출하는 구조로 변경해줘야 함

-> 인터페이스를 사용하는 경우 이런 불필요함 X

 

2. 인터페이스와 구현 클래스 선언

  • 인터페이스는 .java 형태의 소스파일로 작성, .class 형태로 컴파일되기 때문에 물리적 형태는 클래스와 동일
  • but, 소스를 작성할 때 선언하는 방법과 구성 멤버가 다름

2.1. 인터페이스 선언

- 접근 제한자

  • 같은 패키지 내에서만 사용 가능한 default
  • 패키지와 상관 없이 사용하는 public
interface 인터페이스명 { ... }            // default 접근 제한
public interface 인터페이스명 { ... }     // public 접근 제한

 

- 인터페이스 멤버

public interface 인터페이스명 {
    // public 상수 필드
    // public 추상 메소드
    // public 디폴트 메소드
    // public 정적 메소드
    // private 메소드
    // private 정적 메소드
}

- 인터페이스 예제

package ch8.sec2;

public interface RemoteControl {
	// public 추상 메소드
	public void turnOn();
}

 

2.2. 구현 클래스 선언

  • 객체 A가 인터페이스의 추상 메소드를 호출하면, 객체 B의 메소드를 실행 
  • 객체 B는 인터페이스에 선언된 추상 메소드와 동일한 선언부를 가징 메소드를 가지고 있어야 함 
  • 이때 객체 B를 인터페이스를 구현한(implement) 객체라 부름
public class B implements 인터페이스명 { ... }

-> 인터페이스를 구현하고 있음을 선언부에 명시해야 함

-> implements 키워드: 인터페이스를 통해 해당 클래스를 사용 가능하다는 뜻

- 예제

package ch8.sec2;

public class Television implements RemoteControl {
	@Override
	public void turnOn() {
		System.out.println("TV를 켭니다.");
	}
}

 

2.3. 변수 선언과 구현 객체 대입

  • 인터페이스도 하나의 타입 -> 변수의 타입으로 사용 가능
  • 인터페이스는 참조 타입 -> null 대입 가능
RemoteControl rc;
RemoteControl rc = null;
  • 인터페이스를 통해 구현 객체를 사용하려면, 인터페이스 변수에 구현 객체를 대입해야 함 (주소 매칭)
rc = new Television();
rc.turnOn();

- 예제

package ch8.sec2;

public class TelevisionExample {
	public static void main(String[] args) {
		RemoteControl rc = new Television();
		rc.turnOn();
        	rc = new Audio();
        	rc.turnOn();
	}
}

 

3. 상수 필드

  • 인터페이스에 선언된 필드는 모두 상수 필드 특성을 가지기 때문에, 생략 가능 -> 자동적으로 컴파일 과정에서 추가
  • 상수 필드는 구현 객체와 관련 없는 interface 소속 멤버이므로, 인터페이스로 바로 접근해서 읽기 가능
[ public static final ] 타입 상수명 = 값;

 

 

4. 추상 메소드

  • 마찬가지로 생략하더라도 컴파일 과정에서 자동으로 추가
  • 인터페이스의 추상 메소드는 기본적으로 public 접근 제한을 가짐 > public보다 더 낮은 접근 제한으로 재정의 불가능
[ public abstract ] 리턴타입 메소드명( 매개변수, ... );

 

5. 디폴트 메소드

  • 인터페이스에는 완전한 실행 코드를 가진 디폴트 메소드 선언 가능
  • 추상 메소드와 달리 실행부( { ... } ) 존재
  • 재정의 시, public 접근 제한자를 반드시 붙이고, default 키워드를 생략해야 한다.
[ public ] default 리턴타입 메소드명( 매개변수, ... ) { ... }

 

6. 정적 메소드

  • 정적 메소드는 구현 객체 없이도 인터페이스만으로 호출 가능
  • 접근 제한자 생략 시에 public이 자동으로 컴파일 과정에서 추가됨
[ public | private ] static 리턴타입 메소드명( 매개변수, ... ) { ... }

 

7. private 메소드

  • 인터페이스의 상수 필드, 추상 메소드, 디폴트 메소드, 정적 메소드는 모두 public 접근 제한을 가짐
  • 인터페이스에 외부에서 접근할 수 없는 private 메소드 선언 가능
    • 디폴트 메소드, 정적 메소드 안에서만 호출 가능
    • 중복 코드를 줄이기 위한 용도
    • private method: 구현 객체 필요, private static method: 구현 객체 필요 X
package ch8.sec7;

public interface Service {
	// default method
	default void defaultMethod1() {
		System.out.println("defaultMethod1 종속 코드");
		defaultCommon();
	}
	
	default void defaultMethod2() {
		System.out.println("defaultMethod2 종속 코드");
		defaultCommon();
	}
	
	// private method
	private void defaultCommon() {
		System.out.println("defaultMethod 중복 코드A");
		System.out.println("defaultMethod 중복 코드B");
	}
	
	// static method
	static void staticMethod1() {
		System.out.println("staticMethod1 종속 코드");
		staticCommon();
	}
	
	static void staticMethod2() {
		System.out.println("staticMethod2 종속 코드");
		staticCommon();
	}
	
	// private static method
	private static void staticCommon() {
		System.out.println("staticMethod 중복 코드C");
		System.out.println("staticMethod 중복 코드D");
	}
}
package ch8.sec7;

public class ServiceImpl implements Service {

}

package ch8.sec7;

public class ServiceExample {
	public static void main(String[] args) {
		// 인터페이스 변수 선언과 구현 객체 대입
		Service service = new ServiceImpl();
		
		// call default method
		service.defaultMethod1();
		System.out.println();
		service.defaultMethod2();
		System.out.println();
		
		// call static method
		Service.staticMethod1();
		System.out.println();
		Service.staticMethod2();
		System.out.println();
	}

}

 

8. 다중 인터페이스 구현

  • 구현 객체는 여러 개의 인터페이스를 implements 가능
  • 구현 객체가 어떤 인터페이스 변수에 대입되느냐에 따라 변수를 통해 호출할 수 있는 추상 메소드가 결정됨

public class 구현클래스명 implements 인터페이스A, 인터페이스B {
    // 모든 추상 메소드 재정의
}
인터페이스A 변수 = new 구현클래스명( ... );
인터페이스B 변수 = new 구현클래스명( ... );

 

9. 인터페이스 상속

  • 인터페이스는 다른 인터페이스를 상속 가능, 클래스와 달리 다중 상속을 허용
  • 자식 인터페이스의 구현 클래스는 본인의 메소드뿐만 아니라, 부모 인터페이스의 모든 추상 메소드재정의해야 함
  • 구현 객체는 자식 및 부모 인터페이스 변수에 대입될 수 있음
public interface 자식인터페이스 extends 부모인터페이스1, 부모인터페이스2 { ... }
  • 구현 객체가 자식 인터페이스 변수에 대입 -> 자식 및 부모 인터페이스의 추상 메소드 모두 호출 가능
  • 구현 객체가 부모 인터페이스 변수에 대입 -> 부모 인터페이스에 선언된 추상 메소드만 호출 가능

- 예제

package ch8.sec9;

public interface InterfaceA {
	// abstract method
	void methodA();
}

package ch8.sec9;

public interface InterfaceB {
	// abstract method
	void methodB();
}

package ch8.sec9;

public interface InterfaceC extends InterfaceA, InterfaceB {
	// abstract method
	void methodC();
}

package ch8.sec9;

public class InterfaceCImpl implements InterfaceC {
	public void methodA() {
		System.out.println("InterfaceCImpl-methodA() 실행");
	}
	
	public void methodB() {
		System.out.println("InterfaceCImpl-methodB() 실행");
	}
	
	public void methodC() {
		System.out.println("InterfaceCImpl-methodC() 실행");
	}
}
package ch8.sec9;

public class ExtendExample {
	public static void main(String[] args) {
		InterfaceCImpl impl = new InterfaceCImpl();
		
		InterfaceA ia = impl;
		ia.methodA();
//		ia.methodB();
//		ia.methodC();
		System.out.println();
		
		InterfaceB ib = impl;
//		ib.methodA();
		ib.methodB();
//		ib.methodC();
		System.out.println();
		
		InterfaceC ic = impl;
		ic.methodA();
		ic.methodB();
		ic.methodC();
	}
}

 

10. 타입 변환

  • 인터페이스의 타입 변환은 인터페이스와  구현 클래스 간에 발생
  • 인터페이스 변수에 구현 객체 대입 -> 구현 객체인터페이스 타입으로 자동 타입 변환
  • 인터페이스 타입구현 클래스 타입으로 변환 -> 강제 타입 변환

10.1. 자동 타입 변환

인터페이스 변수 = 구현객체;

 

-> B, C, D, E로부터 생성된 객체는 모두 인터페이스 A로 자동 타입 변환 가능

 

10.2. 강제 타입 변환

구현클래스 변수 = (구현클래스) 인터페이스 변수;

-> 구현 클래스에 선언된 메소드 중, 인터페이스에 선언된 메소드만 사용 가능

-> setTime(), record()를 사용하고 싶으면 캐스팅 기호를 사용하여 원래 구현 클래스 타입으로 강제 타입 변환

- 예제

package ch8.sec10;

public interface Vehicle {
	// 추상 메소드
	void run();
}

package ch8.sec10;

public class Bus implements Vehicle {
	// 추상 메소드 재정의
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
	
	// 추가 메소드
	public void checkFare() {
		System.out.println("승차요금을 체크합니다.");
	}
}

package ch8.sec10;

public class CastingExample {
	public static void main(String[] args) {
		// 인터페이스 변수 선언과 구현 객체 대입 - 자동 타입 변환
		Vehicle vehicle = new Bus();
		
		vehicle.run();
//		vehicle.checkFare();
		
		// 인터페이스 변수 구현 클래스로 변환 - 강제 타입 변환
		Bus bus = (Bus) vehicle;
		bus.run();
		bus.checkFare();
	}
}

 

11. 다형성

  • 메소드 재정의 + 자동 타입 변환 -> 다형성
  • 인터페이스의 추상 메소드는 구현 클래스에서 재정의, 재정의되는 내용은 구현 클래스마다 다름

11.1. 필드의 다형성

  • tire1, tire2 필드에 어떤 타이어 구현 객체가 대입되어도, Car 객체는 타이어 인터페이스에 선언된 메소드만 사용하므로 문제 X
package ch8.sec11;

public interface Tire {
	// 추상 메소드
	void roll();
}

package ch8.sec11;

public class HankookTire implements Tire {
	// 추상 메소드 재정의
	@Override
	public void roll() {
		System.out.println("한국 타이어가 굴러갑니다.");
	}
}

package ch8.sec11;

public class KumhoTire implements Tire {
	// 추상 메소드 재정
	@Override
	public void roll() {
		System.out.println("금호 타이어가 굴러갑니다.");
	}
}

package ch8.sec11;

public class Car {
	// 필드
	Tire tire1 = new HankookTire();
	Tire tire2 = new HankookTire();
	
	// 메소드
	void run() {
		tire1.roll();
		tire2.roll();
	}
}
package ch8.sec11;

public class CarExample {
	public static void main(String[] args) {
		// 자동차 객체 생성
		Car myCar = new Car();
		
		// run() 메소드 실행
		myCar.run();
		
		// 필드 교체
		myCar.tire1 = new KumhoTire();
		myCar.tire2 = new KumhoTire();
		
		// run() 메소드 실행
		myCar.run();
	}
}

 

11.2. 매개변수의 다형성

  • 매개변수 타입을 인터페이스로 선언하면, 메소드 호출 시 다양한 구현 객체 대입 가능

-> 어떤 구현 객체가 오더라도, 인터페이스에 선언된 메소드만 사용하므로 문제 X

 

12. 객체 타입 확인

  • 상속에서 객체 타입 확인을 위해 사용한 instanceof 연산자를 인터페이스에서도 사용 가능

 

13. 봉인된 인터페이스

  • sealed: permits 뒤에 위치한 interface만 interfaceA의 자식 인터페이스가 될 수 있음
  • sealed 키워드를 사용한 경우, 자식 인터페이스인 InterfaceB는 non-sealed를 사용하여 선언하거나, sealed 키워드를 사용하여 또 다른 본인 인터페이스로 선언해야 함
public sealed interface InterfaceA permits InterfaceB { ... }
public non-sealed interface InterfaceB extends InterfaceA { ... }

참고자료