인터페이스는 기본 설계도라고 생각하면 된다.
기본 설계도라고 하면 추상클래스가 생각날 것이다. 하지만 인터페이스는 추상클래스와는 차이가 있다.
| 차이점 | 메소드 | 다중상속 | 필드 |
|---|---|---|---|
| 추상 클래스 | 일반 메소드 | ○ | 상수, 변수 필드 포함가능 |
| 인터페이스 | 추상메서드 (자바 8 이후 default, static 메소드 추가) | X | 상수필드만 포함가능 |
추상 클래스는 IS - A : ~는 ~ 이다.
인터페이스는 HAS - A : ~는 ~를 할 수 있다.
로 생각하면 조금 쉬운데 예시를 한번 보겠다.
public abstract class Person {
public final int age; //변수
public void walk(){
System.out.println("두 다리로 걷는다.");
}
}
public interface SwimAble{
public abstract void swim();
}
public class Jieun extends Person implements SwimAble {
public Jieun(int age){
this.age = age;
}
@Override
public void swim(){
System.out.println("할 줄 아는 정도이다.");
}
}
다음과 같이 Jieun은 사람이다. 에서 사람은 추상메소드로 작성하고 Jieun은 수영을 할수있다. 같은 행동은 인터페이스로 작성하였다. 수영을 사람이면 모두 할 수 있는 것은 아니므로 Person에 작성하는 것은 어색한 것이다. 이렇게 작성하면 Jieun은 자전거를 탈수있다. 같은 행동은 인터페이스로 작성해 다중상속또한 가능하다.
위에서 예시로 들었던 것처럼 interface 키워드를 사용하여 정의한다.
접근제어자 interface 인터페이스이름{
public static final 타입 상수이름 = 값;
public abstract 메소드이름(매개변수목록);
}
다음과 같이 인터페이스를 정의할 수 있으며 몇가지의 특징을 가진다.
인터페이스는 추상 클래스와 마찬가지로 자신이 직접 인스턴스를 생성할 수 없어 인터페이스가 포함하고 있는 추상 메소드를 구현해 줄 클래스를 작성해주어야 한다.
class 클래스이름 implements 인터페이스이름{
@Override
인터페이스의 추상메소드이름(){
//구현
}
}
다형성을 위해 자손클래스의 인스턴스를 부모타입의 참조변수로 참조하는 것이 가능하다. 인터페이스도 이를 구현한 클래스의 부모라고 생각하면 해당 인터페이스 타입의 참조변수로 클래스의 인스턴스를 참조할 수 있고, 인터페이스 타입으로 형변환도 가능하다.
public interface Animal{
void sound();
}
public class Dog implements Animal{
@Override
public void sound(){
System.out.println("멍멍");
}
public sleep(){
System.out.println("자고 있습니다.");
}
}
public class Main{
public static void main(String[] args){
Animal dog = new Dog();
dog.sound();
//dog.sleep(); X
((Dog)dog).sleep();
}
}
다음과 같이 dog객체를 Animal 인터페이스 타입으로 생성해 dog.sleep()은 생성이 불가능하고 ((Dog)dog).sleep()으로 캐스팅하여 사용해야 한다.
인터페이스의 가장 큰 장점은 다음과 같이 다중상속이 가능하다는 것이다.
public interface SwimAble{
void SwimAble();
}
public interface DriveAble{
void DriveAble();
}
public class Jieun implements SwimAble, DriveAble{
@Override
public void SwimAble(){
System.out.println("수영할 수 있다.");
}
@Override
public void DriveAble(){
System.out.println("운전할 수 있다.");
}
}
원래 기존의 인터페이스는 추상 메소드만 작성할 수 있었다. 하지만 추상메소드는 추가하면 이를 구현한 구현체에 모두 추가해주어야 한다. 그래서 나온 것이 기본 메소드이다.
interface Dog{
void move();
default void sound(){
System.out.println("멍멍");
}
}
다음과 같이 default 키워드를 통해 선언하고 public은 앞서 배웠던 것처럼 생략이 가능하다.
하지만 이렇게 될 경우 다중상속에 컴파일에러가 발생한다. (예시는 IS-A라 인터페이스로 작성하는 것이 적합하진 않다.)
interface Dog{
default void sound(){
System.out.println("멍멍");
}
}
interface Cat{
default void sound(){
System.out.println("냐옹");
}
}
class Pet implements Dog,Cat{
// Compile error
}
이런 경우 클래스에서 중복되는 sound() 디폴트 메소드를 재정의하면 사용이 가능하다.
class Pet implements Dog,Cat{
@Override
public void sound(){
Dog.super.say();
}
}
이렇게 하면 sound() 중 원하는 default 메소드를 선택할 수 있다.
default메소드와 함께 static메소드도 자바 8에서 등장하였다. static키워드를 사용하여 정의하게 되면 구현체에서는 이를 재정의할 수 없다.
interface User{
static String getType(){
return "human";
}
void say();
}
class American implements User{
public void say(){
System.out.println("HI");
}
}
class Korean implements User{
public void say(){
System.out.println("안녕");
}
}
class App{
public static void main(String[] args){
User kor = new Korean();
String userType = User.getType();
String korType = kor.getType(); //error
}
}
static 키워드의 경우 인터페이스에 바로 접근하여 사용해야하고 인터페이스를 상속받은 클래스에 접근할 시에는 에러가 발생한다.
Java8에서 default, static 키워드가 등장해 중복코드를 제거하고 일일이 구현을 해주지 않아도 되게 되었다. 하지만 이 코드들은 외부에서 접근이 가능하게된다. 이 점을 보완하기 위해 private메소드가 등장하였고 인터페이스 내부에서만 접근할 수 있는 메소드를 작성할 수 있게 되었다.
📚Reference
본 스터디는 2020 백기선님의 자바스터디의 커리큘럼을 참고하여 진행하고 있습니다.