Lang/Java

인터페이스

soooy0 2024. 12. 2. 22:28

인터페이스 역할

  • 두 객체를 연결하는 역할
  • 서버와 서버 간의 연결점의 의미
  • 객체A → 인터페이스 메소드 호출 -> 인터페이스 → 객체B 메서드 호출->객체B 리턴값을 인터페이스로 전달-> 인터페이스 —> 객체A로 리턴값 전달
  • 인터페이스 없이 객체 A가 직접 객체 B를 사용한다는 가정 하에, 객체B가 객체 C로 변경된다면 A의 소스코드도 B에서 C로 변경해주는 작업이 필요할 것이다.
  • 인터페이스를 사용한다면 객체A는 인터페이스의 메소드만 사용하면 되므로 B가 뭘로 바뀌는지는 상관 안함. 즉, 객체 A가 인터페이스 메소드를 호출한다면, 실제로 실행되는 것은 인터페이스의 추상메서드를 오버라이딩하여 구현한 객체B의 메소드가 실행되는 것. 따라서 어떤 구현 객체를 사용하느냐에 따라 결과가 다를 수 있음
  • 이러한 특징으로 인해 다형성을 구현할 때 상속보다도 인터페이스를 더 많이 사용하는 경우가 많다.

인터페이스 구현 클래스 선언

인터페이스는 물리적인 형태가 클래스와 동일하나(.java형태로 소스파일이 작성되어 .class로 컴파일 되기 때문), 소스를 작성할 때 선언 방법과 구성멤버가 다르다. (클래스처럼 타입으로도 사용 가능)

  • 인터페이스는 class 대신에 interface키워드를 사용한다.
  • 접근제한자는 default, public 가능
public interface 인터페이스 명{

//public fianl (상수) 필드
//public abstract (추상) 메서드
//public default(디폴트) 메서드
//public static(정적) 메서드
//private 메서드
//private static(정적) 메서드

}
  • 객체A가 인터페이스의 추상메소드를 호출하면 인터페이스는 객체 B의 메소드를 실행한다. 따라서 (인터페이스를 구현하고 있는)객체 B는 인터페이스에 선언된 추상메서드와 동일한 선언부를 가진 (오버라이딩) 메서드를 반드시 가지고 있어야 한다.
더보기

💡 즉! 객체B는 인터페이스를 implement(구현)한 객체이다.

 

public class B implements 인터페이스명{…}

 

이렇게 적어서 인터페이스에 정의된 추상메서드에 대한 실행 내용이 B에 구현되고 있음을 알려야 한다.

  • 인터페이스를 구현한 클래스는 인터페이스가 가진 모든 추상메서드를 재정의 해야 한다.

 

변수 선언과 구현 객체 대입

  • 인터페이스 내에는 상수와 추상메서드(위주)만 사용할 수 있다.
  • 인터페이스도 하나의 타입이므로 변수의 타입으로 사용 가능!!
  • 인터페이스는 참조 타입에 속하므로 (객체를 참조하고 있지 않다) null을 대입 가능
RemoteControl rc;
RemoteControl rc = null;

//RemoteControl은 인터페이스
  • 인터페이스를 통해 구현 객체를 사용하려면, 인터페이스 변수에 구현 객체를 대입해야 한다.(객체 생성 해야함) 구현 객체의 번지를 대입하는 것이다.
public interface RemoteControl{
	public void turnOn();
}	

public class Television implements RemoteControl{
	public void turnOn(){
	 system.out.println("Tv를 켭니다."); //추상메서드 완성시킴
	}
}

public class Ex1{

	RemoteControl rc;
	rc = new Television();
	//RemoteControl rc = new Television(); 합쳐서 쓰기 가능
	//if) Television이 RemoteControl을 implements 하고 있지 않다면, 변수 rc에 대입할 수 없다.
	//이렇게 구현 객체 대입 후에는 rc를 통해 인터페이스의 추상메서드를 호출할 수 있게 된다.
	rc.turnOn();
	}
}

//turnOn()이 호출되면 실제로 시행되는 것은 Television에서 오버라이딩 된 turnOn()메서드이다.
  • rc 변수에는 RemoteControl을 구현한 어떠한 객체든 대입이 가능하다. rc = new Audio();로 rc의 구현 객체를 변경한다면, Audio에 있는 turnOn(); 메서드가 실행된다. 구현 클래스의 메서드를 사용할 수 있게 됨.

인터페이스에 선언 가능한 것들

  • 상수 필드 (public static final)
    • 인터페이스는 불변의 상수 public static final (상수) 필드를 멤버로 가질 수 있다.
    • 이 때, 상수는 객체와 관련없는 인터페이스 소속멤버이므로, 인터페이스로 바로 접근하여 상수값을 읽을 수 있다. ( RemoteControl.MAX_VOLUME);
    • (public static final)는 생략가능, 자동적으로 컴파일 과정에서 붙게 된다.
  • 추상메서드(abstract)
    • 인터페이스는 ((구현 클래스가 정의해야하는)) public 추상메서드를 멤버로 가질 수 있다.
    • 인터페이스에 추상메서드가 있다면, 해당 인터페이스를 구현하는 클래스에 추상메서드와 동일한 구현부를 갖는 오버라이딩 메서드가 있어야 한다.
    더보기
    💡 추상메서드를 오버라이딩 할 때, 추상메서드는 기본적으로 public 제한자이기 때문에, public 보다 더 낮은 접근 제한으로 재정의할 수 없다. 따라서 오버라이딩 메소드에 모두 public이 추가되어 있다.
    • 인터페이스 변수로 추상메서드를 호출할때, 어떤 구현 객체를 생성하여 사용하냐에 따라 오버라이딩 메서드 호출이 달라진다. 즉 실행내용이 달라지는 것! ((Television이 대입되면 TV 오버라이딩 메서드, CDplayer가 대입되면 CD오버라이딩 메서드))
  • 디폴트 메서드(default)
    • 인터페이스에는 완전한 실행코드를 가진 디폴트 메서드를 선언할 수 있다.
    • 추상메서드는 구현부가 없지만! —> 디폴트 메서드는 구현부가 있다!
    • 선언 방법은 default를 리턴 타입 앞에 붙다.
    [public] default 리턴타입 메소드명(매개변수, ...){...}
    
    • 디폴트 메서드의 선언부에는 상수필드를 읽거나, 추상메서드를 호출하는 코드를 작성할 수 있다.
    • 디폴트 메서드는 구현 객체가 필요한 메소드이다. 따라서 구현 객체를 인터페이스 변수에 대입하고 나서 디폴트 메서드를 호출한다.
    • 구현 클래스는 디폴트 메서드를 오버라이딩 해서 자신에 맞게 수정할 수 있다.
    더보기
    💡 이때! public 접근 제한자를 반드시 붙여야 하고, default 키워드를 생략해야한다는 점을 주의하자. 
  • 정적 메서드(static)
    • 클래스 메서드는 객체 없이도 인터페이스 만으로 호출 가능하다. (RemoteControl.changeBattery() 가능)
    [public | private] static 리턴타입 메소드명(매개변수, ...){...}
    
    • static 메서드 구현부 작성 시, 추상메소드, 디폴트 메서드, private 메서드 등을 호출할 수 없다. 상수 필드만 작성 가능! —> why? 위 메서드들은 반드시 구현 객체가 필요하기 떄문. 객체를 먼저 생성해주고 난 이후에 사용할 수 있는 인스턴스 메서드이기 때문이지!
    • staitc 메서드로 선언되면 메서드 영역에 올라간다.
  • private 메서드
    • 인터페이스의 상수, 추상, 디폴트, 정적 메서드는 모두 —> public 접근 제한자를 갖는다. 생략하더라도 컴파일에서 자동으로 붙음
    • 반면 private 도 선언 가능하다.
    • 여기서 구분해야할 것
      • private 메소드: 구현 객체가 필요함, default 메소드에서만(..) 호출 가능
      • private 정적 메소드: 구현 객체가 필요 없음, default, static 메서드 안에서 호출 가능

다중 인터페이스 구현

  • 구현 객체는 여러개의 인터페이스를 implements할 수 있다.
public class 구현클래스명 implements 인터페이스A, 인터페이스B{}

//단, 구현 클래스는 모든 인터페이스가 가진 추상메서드를 오버라이딩 해야 함.
  • 단, 인터페이스가 가진 추상 메서드를 재정의 해야한다는 것은 그대로 유지된다.
  • 인터페이스 A와 인터페이스 B를 구현한 객체는 두 인터페이스 타입의 변수에 각각 대입될 수 있다.
    • 인터페이스A 변수 = new 구현 클래스명();
    • 인터페이스B 변수 = new 구현 클래스명();
  • 구현 객체가 어떤 인터페이스( A or B)에 대입되느냐에 따라 변수를 통해 호출 할 수 있는 추상메서드가 결정된다.
  • 다만 구현 클래스는 A, B 인터페이스가 가진 모든 추상메서드를 오버라이딩 해야 한다.

인터페이스 상속

  • 인터페이스도 다른 인터페이스를 상속 가능. 단! 다중 상속 허용한다.
public interface 하위인터페이스 extends 상위 인터페이스1, 상위 인터페이스2{...}
  • 하위 인터페이스를 구현한 클래스는, 하위 인터페이스의 메소드 뿐만 아니라 상위 인터페이스의 모든 추상메서드를 오버라이딩해야한다.
  • 구현 객체는 하위 및 상위 인터페이스 변수에 대입될 수 있다.
  1. 하위 인터페이스 변수 = new 구현 클래스(…);
  2. 상위 인터페이스1 변수 = new 구현 클래스(…);
  3. 상위 인터페이스2 변수 = new 구현클래스(…);
더보기

💡 단! 구현객체가 하위 인터페이스 변수에 대입되면, 하위 및 상위 인터페이스의 추상메서드를 모두 호출할 수 있음.

하지만 상위 인터페이스 변수에 대입되면, 상위 인터페이스에 선언된 추상메서드만 호출 가능하다.

 

타입 변환

  • 인터페이스 타입변환은 인터페이스와 구현 클래스 간에서 발생한다.
  • 자동 타입 변환
    • 구현객체가 인터페이스 타입로 타입변환 시,
    • 인터페이스 변수에 구현 객체를 대입하면, 구현 객체는 자동으로 인터페이스 타입으로 타입변환된다.
    • 더보기
      인터페이스 변수 = (인터페이스 타입) 구현객체;
    • 상위 클래스가 인터페이스를 구현하고 있다면 하위 클래스도 인터페이스 타입으로 자동타입 변환될 수 있다.
      • 더보기
        인터페이스 A를 구현한 B,C 클래스
        B클래스를 상속한 D클래스
        C클래스를 상속한 E클래스

        B, C, D, E로부터 생성된 객체들은 모두 인터페이스 A로 자동 타입 변환 가능!!! 직, 간접적으로 구현하는 꼴임. 상위 B,C 클래스가 인터페이스 A를 구현하고 있기 때문

        인터페이스에 3개의 메소드, 구현클래스에 5개의 메소드 선언되면 인터페이스 변수로 호출 가능한 메서드는 3개뿐이다
    • 더보기
      💡 자동 변환되면, 인터페이스에 선언된 메서드만 사용 가능하다. (인터페이스 타입만 사용 가능 한것, 즉 참조변수의 타입을 봐야함!)
  • 강제 타입 변환
    • 인터페이스가 구현클래스로 타입변환 시,
    • 캐스팅 기호를 사용해서 인터페이스 타입을 구현 클래스 타입으로 변환시키는 것을 의미
    • 더보기
      구현클래스 변수 = (구현클래스) 인터페이스 변수;
    더보기
    💡 강제 캐스팅의 경우 구현 클래스의 메서드 5개, 인터페이스의 메서드 3개 모두 사용 가능하다. (참조 변수의 타입을 봐야함!)

 

다형성

  • 구현 B객체와 구현 C객체 중 어느 객체가 인터페이스에 대입되었냐에 따라서 객체 A의 메서드 호출  결과는 달라질 수 있다.
  • 인터페이스의 추상메소드는 구현 클래스에서 재정의 해야 하며, 재정의된 내용은 구현클래스마다 다르다. 따라서 다양한 실행 결과를 얻을 수 있다.
  • 필드의 다형성
  • 매개변수의 다형성
    • 매개변수 타입을 인터페이스로 선언하여 메서드 호출 시 다양한 객체를 대입할 수 있다.
    //인터페이스 선언
    public interface Vehicle{
    	void run();
    }
    
    public class Driver{
    	//매개변수를 인터페이스로 선언
    	void drive(Vehicle vehicle){
    		vehicle.run(); //인터페이스의 추상메서드 호출
    	}
    }
    
    public class Bus implements Vehicle{
    	public void run(){
    		System.out.prinln("버스가 달린다);
    } //인터페이스 Vehicle의 구현 클래스인 Bus
    
    //실행 클래스
    public class DriverEx{
    	public static void main(String[] args){
    	
    	Driver driver = new Driver();
    	
    	Bus bus = new Bus();
    	Taxi taxi = new Taxi();
    	
    	driver.drive(bus); //자동 타입변환 -> 오버라이딩 메소드 호출 -> 다형성
    	driver.drive(taxi); //자동 타입변환 -> 오버라이딩 메소드 호출 -> 다형성
    	
    	}
    }
    
    1) driver.drive(bus); //자동타입변환 발생 Vehicle vehicle = (Vehicle) bus;
    2) Bus는 Vehicle의 구현 클래스이기 때문에, Driver의 drive()호출 시, 
       Bus객체를 생성해서 매개값으로 줄 수 있음
    3) drive()메서드를 호출할 때, 인터페이스 Vehicle을 구현하는 어떤 객체라도 매개값으로 줄 수 있다.
    4) 어떤 객체를 주느냐에 따라 run() 메소드의 실행결과는 다르게 나온다.
    5) why? 구현 객체에서 오버라이딩된 run()메소드의 실행 내용이 다르기 때문
    
  • 객체타입확인(instanceof 사용 가능)
  • if(vehicle instanceof Bus){ //vehicle에 대입된 객체가 Bus일 경우 실행 Bus bus = (Bus) vehivle; }
  • 봉인된 인터페이스
    • 무분별한 자식 인터페이스 생성을 방지하기 위해 봉인된(sealed) 인터페이스를 선언할 수 있다.
    public sealed interface InterfaceA permits InterfaceV{...}
    
    • sealed 키워드 사용시, permits 뒤에 상속 가능한 자식 인터페이스를 지정한다.
    • InterfaceA를 상속하는 InterfaceB는 non sealed 키워드로 선언하거나 sealed 키워드를 사용해서 또 다른 봉인 인터페이스로 선언해야 한다.