본문 바로가기

coding/Java

[Java] 추상클래스, 추상메소드, 인터페이스

 

추상클래스와 추상메소드, 그리고 인터페이스에 대해 알아보자.

  • 추상메소드(Abstract Method): 선언은 되어 있으나 코드가 구현되어 있지 않은 메소드
  • 추상클래스(Abstract Class): 추상 메소드를 포함하거나, 추상 메소드가 없지만 abstract로 선언한 클래스
  • 인터페이스(Interface): 서로 다른 하드웨어 장치들이 상호 데이터를 주고받을 수 있는 규격

 

다음은 인터페이스의 예시이다.

 

interface PhoneInterface {
	public static final int TIMEOUT = 10000; //상수 필드, 초기화 필수
	public abstract void sendCall(); //추상 메소드
	public abstract void receiveCall(); //추상 메소드
	public default void printLogo() { //default 메소드(Java 8부터)
		System.out.println("** Phone **");
	};
}

 

참고로 final이 메소드 이름 앞에 붙으면 다른 클래스에서 오버라이딩 할 수 없고, 클래스 이름 앞에 붙으면 다른 클래스가 상속할 수 없다.

abstract 키워드는 생략할 수 있음. 중요한 것은 인터페이스에서의 메소드는 몸체를 갖지 않고, 다중상속이 가능하다. (상속이라고 해야하나...? 아무튼 원래 자바에서는 다중상속이 불가능한데 인터페이스는 여러 개를 갖다 쓰는 것이 가능하다)

Ex. class Robot extends Machine implements Movable, Runnable {...}

 

- 인터페이스는 객체를 생성할 수 없다.

인터페이스는 구현되지 않은 추상 메소드를 가질 수 있기 때문에 객체를 생성할 수 없다. 대신 인터페이스형 참조변수는 선언 가능함.

new PhoneInterface();  (X)
PhoneInterface phone; (O)

 

출처 : http://alecture.blogspot.com/2011/05/abstract-class-interface.html

 

인터페이스는 추상 메소드를 가지고 있으니 추상 클래스 중 하나라고 볼 수도 있을 것 같다. 대신 인터페이스를 implement해서 쓸 때는 인터페이스가 가지고 있는 추상 메소드를 모두 구현해야한다! 오버라이딩을 해주면 됨.

 

interface PhoneInterface {

	void sendCall(String info);
}

class IPhone implements PhoneInterface {

	public IPhone() { }
	
	@Override
	public void sendCall(String info) {
		System.out.print(info);
		System.out.println("Apple");
	}
}

class GalaxyPhone implements PhoneInterface {	
	
	public GalaxyPhone() {}
	
	@Override
	public void sendCall(String info) {
		System.out.print(info);
		System.out.println("Samsung");
	}
}

public class FinalTermTest {
	public static void main(String[] args) {
		String Info = "This is a phone of...";
		
		//아이폰으로 출력
		PhoneInterface ph1 = new IPhone();
		ph1.sendCall(Info);
		
		//갤럭시로 출력
		PhoneInterface ph2 = new GalaxyPhone();
		ph2.sendCall(Info);
	}
}

 

@Override를 붙여서 오버라이드 해주면 끝!

 

 

인터페이스의 상속

인터페이스 간 상속도 extends로 표현한다. 인터페이스와 클래스 간 상속만 implements고 클래스와 클래스, 인터페이스와 인터페이스는 extends를 쓴다고 외우면 편함.

만약 PhoneInterface에 전화를 거는 기능뿐만 아니라 알람을 울리는 기능도 추가되었다고 가정해보자. 인터페이스의 추상 메소드들은 implement시에 모두 구현해야하기 때문에 아이폰과 갤럭시에도 알람 기능을 추가해주어야 한다. 근데 폰의 종류가 계속 늘어난다면? 인터페이스에 기능도 계속 추가된다면? 코드를 계속 다시 수정해주어야 하는 번거로움이 생긴다. 이때 사용할 수 있는게 인터페이스의 상속인데, 아예 새로운 인터페이스를 구성하는 것이다.

 

interface PhoneInterface {

	void sendCall(String info);
}

interface AlarmPhone extends PhoneInterface {
	
	void ringAlarm(String noise);
	
}

 

이러면 기존의 IPhone 클래스와 GalaxyPhone 클래스를 수정할 필요가 없다. 대신 얘도 다른 클래스를 만들어서 AlarmPhone을 implement 해줘야 겠지. 수정의 번거로움은 줄어들지만 인터페이스가 무한대로 늘어날 수록 새로운 클래스도 계속 늘어난다.

해결법은 default 메소드! 메소드 선언시에 default를 명시하게 되면 인터페이스 내부에서도 코드가 포함된 메소드를 선언 할 수 있다. (원래 인터페이스는 추상 메소드로만 만들어진 추상 클래스라는 것을 기억하자)

 

default void ringAlarm(String noise) { ...구현이 되어있음 ...}

 

이러면 이제 인터페이스의 수를 더 늘리지 않아도 된다. 이미 저 ringAlarm 메소드에 알람을 울리는 기능이 구현이 되어있기 때문에 오버라이드를 해야 할 필요가 없으면 안해도 된다...! 물론 @Override를 이용해서 재정의 할 수도 있음. 너무너무 편하다^^! 하지만 인터페이스가 점점 추상 클래스로서의 정체성을 읽어가고 있다는 느낌을 받음. 그냥 일반적인 클래스에 가까워져 간다.

 

interface PhoneInterface {

	void sendCall(String info);
	default void ringAlarm() {
		System.out.println("ring ring ring");
	}
}

interface AlarmPhone extends PhoneInterface {
	
	void ringAlarm(String noise);
	
}

class IPhone implements PhoneInterface {

	public IPhone() { }
	
	@Override
	public void sendCall(String info) {
		System.out.print(info);
		System.out.println("Apple");
	}
}

class GalaxyPhone implements PhoneInterface {	
	
	public GalaxyPhone() {}
	
	@Override
	public void sendCall(String info) {
		System.out.print(info);
		System.out.println("Samsung");
	}
}

public class FinalTermTest {
	public static void main(String[] args) {
		String Info = "This is a phone of...";
		
		//아이폰으로 출력
		PhoneInterface ph1 = new IPhone();
		ph1.sendCall(Info);
		ph1.ringAlarm();
		
		//갤럭시로 출력
		PhoneInterface ph2 = new GalaxyPhone();
		ph2.sendCall(Info);
		ph2.ringAlarm();
	}
}