1. 인터페이스의 역할
- 자바에서 인터페이스는 객체의 사용 방법을 정의한 타입이다.
- 인터페이스는 객체의 교환성을 높여주기 때문에 다형성을 구현하는 매우 중요한 역할을 한다.
- 인터페이스는 개발 코드와 객체가 서로 통신하는 접점 역할을 한다. 개발 코드가 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출한다. 그렇기 때문에 개발 코드는 객체의 내부 구조를 알 필요가 없고 인터페이스의 메소드만 알고 있으면 된다.
- 개발코드가 직접 객체의 메소드를 호출하면 간단한데 왜 중간에 인터페이스를 두는걸까?
개발코드를 수정하지 않고, 사용하는 객체를 변경할 수 있도록 하기 위해서다.
인터페이스는 여러개의 객체들과 사용이 가능하므로 어떤 객체를 사용하느냐에 따라 실행 내용과 리턴값이 다를 수 있다.
따라서 개발 코드 측면에서는 코드 변경 없이 실행 내용과 리턴값을 다양화할 수 있다는 장점을 가지게 된다.
2. 인터페이스 선언
- 인터페이스는 '~.java' 형태의 소스 파일로 작성되고 컴파일러(javac.exe)를 통해 '~.class'형태로 컴파일 되기 때문에 물리적인 형태는 클래스와 동일하다.
1)인터페이스 선언
[public] interface 인터페이스명 {...}
-클래스는 필드, 생성자, 메소드를 구성 멤버로 가지는데 비해 인터페이스는 상수와 메소드만을 구성 멤버로 가진다.
-인터페이스는 객체로 생성할 수 없기 때문에 생성자를 가질 수 없다.
-자바 7버전까지는 실행 블록이 없는 추상 메소드만 선언이 가능했지만 자바 8 버전 이후로는 디폴트 메소드와 정적 메소드도 선언이 가능하다.
-상수 필드(Constant Field)
상수를 선언할 때는 반드시 초기값을 대입해야한다.
public interface RemoteControl{
public int MAX_VOLUME = 10;
publid int MIN_VOLUME = 0;
}
-> 인터페이스에 선언된 필드는 모두 public static final 특성을 갖는다.
-추상 메소드(Abstract Method)
추상 메소드는 객체가 가지고 있는 메소드를 설명한 것으로 호출할 때 어떤 매개값이 필요하고, 리턴 타입이 무엇인지만 알려준다. 실제 실행부는 객체(구현 객체)가 갖고 있다.
public interface RemoteControl {
//추상 메소드
public void turnOn();
public void turnOff();
public void setVolume(int volume);
}
-디폴트 메소드(Default Method)
디폴트 메소드는 인터페이스에 선언되지만 사실은 객체가 가지고 있는 인스턴스 메소드라고 생각해야한다.
//디폴트 메소드
default void setMute(boolean mute){
if(mute) System.out.println("무음 처리합니다.");
else System.out.println("무음 해제합니다.");
}
-> 실행 내용까지 작성
-정적 메소드 (static Method)
디폴트 메소드와는 달리 객체가 없어도 인터페이스만으로 호출이 가능하다.
//정적 메소드
static void changeBattery() {
System.out.println("건전지를 교환합니다.");
}
3. 인터페이스 구현
-개발 코드가 인터페이스 메소드를 호출하면 인터페이스는 객체의 메소드를 호출한다. 객체는 인터페이스에서 정의된 추상 메소드와 동일한 메소드 이름, 매개 타입, 리턴 타입을 가진 실체 메소드를 가지고 있어야한다. 이러한 객체를 인터페이스의 구현 이라고 하고 , 구현 객체를 생성하는 클래스를 구현 클래스라고 한다.
1) 구현 클래스
public class 구현클래스명 implements 인터페이스명{
//인터페이스에 선언된 추상 메소드의 실체 메소드 선언
}
-인터페이스를 사용하기 위해선
인터페이스 변수;
변수 = 구현 객체;
2) 익명 구현 객체
-일회성의 구현 객체는 따로 클래스를 만들지 않고 익명 구현 객체를 활용한다.
인터페이스 변수 = new 인터페이스(){
//인터페이스에 선언된 추상 메소드의 실체 메소드 선언
}
public class RemoteControlExample{
public static void main(String [] args){
RemoteControl rc = new RemoteControl(){
public void turnOn(){/*실행문*/}
public void turnOff(){/*실행문*/}
public void setVolume(int volume){/*실행문*/}
};
}
}
-> RemoteControl은 인터페이스로 turnOn(), turnOff(), setVolume() 3가지 메소드를 정의하고 있다.
3)다중 인터페이스 구현 클래스
public class 구현클래스명 implements 인터페이스A, 인터페이스B {
//인터페이스 A에 선언된 추상 메소드의 실체 메소드 선언
//인터페이스 B에 선언된 추상 메소드의 실체 메소드 선언
}
-다중 인터페이스를 구현할 경우, 구현 클래스는 모든 인터페이스의 추상 메소드에 대해 실체 메소드를 작성해야한다. 만약 하나라도 없으면 추상 클래스로 선언해야 한다.
4. 인터페이스의 사용
-인터페이스로 구현 객체를 사용하려면 다음과 같이 인터페이스 변수를 선언하고 구현 객체를 대입해야한다.
-인터페이스 변수는 참조 타입이기 때문에 구현 객체가 대입될 경우 구현 객체의 번지를 저장한다.
-예를 들어 RemoteControl 인터페이스로 구현 객체인 Television 과 Audio를 사용하려면 다음과 같이 RemoteControl 타입 변수를 선언하고 구현 객체를 대입해야한다.
RemoteControl rc;
rc = new Television();
rc = new Audio();
1)추상 메소드 사용
구현 객체가 인터페이스 타입에 대입되면 인터페이스에 선언된 추상 메소드를 개발 코드에서 호출할 수 있게 된다.
RemoteControl rc = new Television();
rc.turnOn(); //Television의 turnOn() 실행
rc.turnOff(); //Television의 turnOff() 실행
2)디폴트 메소드 사용
-디폴트 메소드는 인터페이스 내에서 선언되지만 인터페이스에서 바로 사용할 수 없다.
-디폴트 메소드는 추상 메소드가 아닌 인스턴스 메소드이므로 구현 객체가 있어야 사용할 수 있다.
RemoteControl.setMute(true); //호출 불가 (구현 객체가 필요함)
RemoteControl rc = new Television();
rc.setMute(true);
-디폴트 메소드는 인터페이스의 모든 구현 객체가 가지고 있는 기본 메소드라고 생각하면 된다. 그러나 어떤 구현 객체는 디폴트 메소드의 내용이 맞지 않아 수정이 필요할 수도 있다.
- 구현 클래스를 작성할 때 디폴트 메소드를 오버라이딩하면 된다.
3)정적 메소드 사용
-인터페이스의 정적 메소드는 인터페이스로 바로 호출이 가능하다.
public class RemoteControlExample {
public static void main(String[] args){
RemoteControl.changeBattery();
}
}
5. 타입 변환과 다형성 (하나의 타입인데 대입되는 객체에 따라서 실행 결과가 다양한 형태로 나오는 성질)
- 상속뿐만 아니라 인터페이스도 다형성을 구현하는 기술이 사용된다.
- 부모 타입에 어떤 자식 객체를 대입하느냐에 따라 실행 결과가 달라지듯이, 인터페이스 객체에 어떤 구현 객체를 대입하느냐에 따라 실행 결과가 달라진다.
- I 인터페이스를 이용해서 프로그램을 개발했고 구현 클래스는 A이다. 개발 완료 후 테스트를 해보니 A클래스에 문제가 생겨서 다른 클래스를 사용해야한다. 이런 경우 I 인터페이스에 새로운 구현 클래스를 대입해주기만 하면된다.
(만약 인터페이스를 사용하지 않고 개발 설계를 했다면, A클래스에 문제가 생겨 B클래스로 바꿔야 할 때 매개변수, 타입, 메소드명을 일일이 찾아서 맞춰줘야한다.
1)자동 타입 변환
- 구현 객체가 인터페이스 타입으로 변환되는 것은 자동 타입 변환(Promotion)에 해당한다
2)필드의 다형성
- 앞서 상속 파트에서 봤던 Car , Tire(인터페이스), HanKookTire(구현 클래스), KumhoTire(구현 클래스) 예시와 비슷하다.
public class Car {
Tire frontLeftTire = new HankooTire();
Tire frontRightTire = new HankooTire();
Tire backLeftTire = new HankooTire();
Tire backRightTire = new HankooTire();
}
-> Tire는 인터페이스이고 HankookTire는 Tire 인터페이스를 implements하고 있다.
Car myCar = new Car();
myCar.frontLeftTire = new KumhoTire();
myCar.frontRightTire = new KumhoTire();
-> 초기값으로 대입한 구현 객체 대신 다른 구현 객체를 대입할 수 있다.
public interface Tire{
public void roll(); //roll()호출 방법 설명
}
public class HankookTire implements Tire{
@Override
public void roll(){
System.out.println("한국 타이어가 굴러갑니다.");
}
}
public class KumhoTire implements Tire{
@Override
public void roll(){
System.out.println("금호 타이어가 굴러갑니다.");
}
}
public class Car{
Tire frontLeftTire = new HankookTire();
Tire frontRightTire = new HankookTire();
Tire backLeftTire = new HankookTire();
Tire backRightTire = new HankookTire(); //인터페이스 타입 필드 선언과 초기 구현 객체 대입
void run(){
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTire.roll(); //인터페이스에서 설명된 roll() 메소드 호출
}
}
<필드 다형성 테스트>
public class CarExample {
public staic void main(String[] args){
Car myCar = new Car();
myCar.run();
myCar.frontLeftTire = new KumhoTire();
myCar.frontRightTire = new KumhoTire(); // 다른 구현 객체 대입
myCar.run();
}
}
-> 한국 타이어가 굴러갑니다
한국 타이어가 굴러갑니다
한국 타이어가 굴러갑니다
한국 타이어가 굴러갑니다
금호 타이어가 굴러갑니다
금호 타이어가 굴러갑니다
한국 타이어가 굴러갑니다
한국 타이어가 굴러갑니다
3) 인터페이스 배열로 구현 객체 처리
-이전 예제에서는 Car클래스에서 4개의 타이어필드를 인터페이스로 각각 선언했지만 배열로 선언 가능하다.
Tire[] tires = {
new HankookTire(),
new HankookTire(),
new HankookTire(),
new HankookTire()
};
-Car 클래스의 run 메소드도 각각 작성하는게 아니라 반복문으로 작성
void run(){
for(Tire tire : tires){
tire.roll();
}
}
- 위에서 작성한 CarExample도 아래와 같이 수정해야한다.
public class CarExample{
public static void main(String [] args){
Car myCar = new Car();
myCar.run();
myCar.tires[0] = new KumhoTire();
myCar.tires[1] = new KumhoTire();
myCar.run();
}
}
4)매개 변수의 다형성
- 메소드 호출시 매개변수의 타입은 인터페이스로, 호출시에는 구현 객체를 대입한다.
public class Driver{
public void drive(Vehicle vehicle){
vehicle.run();
}
}
-> driver() 메소드에는 Vehicle 타입의 매개 변수가 선언되어 있다.
public interface Vehicle{
public void run();
}
->위와 같이 Vehicle 은 인터페이스 타입이다.
-만약 Bus가 Vehicle 인터페이스를 구현한다면, 매개변수로 Bus타입의 객체가 올 수 있다. (Bus -> Vehicle 자동 타입 변환 발생)
-> Vehicle 인터페이스를 구현하고 있는 구현 클래스의 메소드 내용에 따라 실행결과가 달라질 수 있다.
5)강제 타입 변환(Casting)
- 구현 객체가 인터페이스 타입으로 형변환이 되면, 인터페이스에 선언된 메소드만 사용가능하다는 제약이 따른다.
예를 들어 인터페이스의 메소드가 3개이고 구현 클래스에서 정의된 메소드가 5개라면 호출 가능한 메소드는 3개가 된다.
-하지만 경우에 따라 다시 구현 클래스의 나머지 2개 메소드를 사용할 일이 있을 수 있기 때문에 강제 캐스팅한다
구현 클래스 변수 = (구현 클래스) 인터페이스 변수;
6)객체 타입 확인 (instanceof)
- 상속과 내용 같음
6. 인터페이스 상속
- 인터페이스도 다른 인터페이스를 상속할 수 있다. (다중 상속 허용)
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2{...}
-하위 인터페이스를 구현하는 클래스는 하위클래스의 메소드 뿐만 아니라 상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드를 갖고 있어야한다. 따라서 아래와 같이 타입 변경이 가능하다.
하위인터페이스 변수 = new 구현클래스(...);
상위인터페이스1 변수 = new 구현클래스(...);
상위인터페이스2 변수 = new 구현클래스(...);
-->하지만 하위인터페이스로 타입 변환되면 상하위 모든 메서드 사용이 가능하지만
상위인터페이스로 타입 변환되면 상위 인터페이스의 메서드만 사용이 가능하고 하위 인터페이스의 메서드 사용은 불가하다.
7. 디폴트 메소드와 인터페이스 확장
-디폴트 메소드는 인터페이스에 선언된 인스턴스 메서드이기 때문에 구현 객체가 있어야 사용할 수 있다.
-인터페이스에 디폴트 메소드 추가를 허용한 이유
-> MyInterface(인터페이스) - MyClassA(구현 클래스) 가 있는데 초반에는 추상 메소드 method1()만 존재했다.
-> 시간이 지나고 인터페이스의 기능 추가를 위해 추상 메서드 method2()를 추가했는데 엉뚱한 MyClassA에서 오류가 났다
-> 그래서 method2()를 디폴트 메소드로 추가하면 구현 클래스에서 굳이 실체 메소드를 작성할 필요가 없다.
-디폴트 메소드가 있는 인터페이스 상속 (활용 방법)
- 디폴트 메소드를 단순히 상속만 받는다
- 디폴트 메소드를 재정의해서 실행 내용을 변경한다.
- 디폴트 메소드를 추상 메소드로 재선언한다
'JAVA > 이것이 자바다' 카테고리의 다른 글
이것이 자바다 Ch 10 예외 처리 (0) | 2022.10.08 |
---|---|
이것이 자바다 Ch 9 중첩 클래스와 중첩 인터페이스 (0) | 2022.09.13 |
이것이 자바다 Ch 7 상속 (0) | 2022.09.08 |
이것이 자바다 ch 6 클래스 (0) | 2022.09.05 |
이것이 자바다 Ch 5 참조 타입 (0) | 2022.09.05 |