티스토리 뷰

Java

[Java] 8.인터페이스

nohriter 2022. 9. 7. 09:00

자바의 인터페이스에 대해 알아보겠습니다.

 

 

 

 

1. 인터페이스


인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메소드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 일반 메서드 또는 멤버 변수를 가질 수 없다. 오직 추상메소드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다. 추상클래스가 미완성 설계도라면, 인터페이스는 밑그림만 그려져 있는 기본 설계도라 할 수 있다. 인터페이스도 추상클래스처럼 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.

💡 기존에는 인터페이스에 일반 메소드를 구현할 수 없었지만, 자바 8 버전부터 default 예약어를 통해 일반 메서드 구현이 가능하다.

 

 

2. 인터페이스 정의하는 방법


인터페이스를 작성하는 것은 클래스를 작성하는 것과 같다. 다만 키워드로 class 대신 Interface를 사용한다는 것만 다르다. 그리고 interface에도 클래스처럼 접근제어자로 public 또는 default만 사용할 수 있다.

interface 인터페이스이름 {
	public static final 타입 상수이름 = 값;
	public abstract 메서드이름(매개변수목록);
}

 

제약사항

1. 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.

2. 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다. (단, jdk.1.8부터 static 메서드와 default 메서드는 예외)

 

 

인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 생략된 제어자는 컴파일 시에 컴파일러가 자동적으로 추가해준다.

interface PlayingCard {
    public static final int SPADE = 4;
    final int DIAMOND = 3;	// public static final int DIAMOND = 3;
    static int HEART = 2;	// public static final int CLOVER = 2;
    int CLOVER = 1;			// public static final int CLOVER = 1;

    public abstract String getCardNumber();
    String getCardKind() 	// public abstract String getCardKind();
}

 

 

2. 인터페이스 구현하는 방법


인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스 클래스를 작성해야 하는데, 그 방법은 추상클래스가 자신을 상속받는 클래스를 정의하는 것과 다르지 않다. 다만 클래스는 확장한다는 의미의 키워드 'extends'를 사용하지만 인터페이스는 구현한다는 의미의 키워드 'implements'를 사용한다.

 

class 클래스이름 implements 인터페이스이름 {
    // 인터페이스에 정의된 추상메서드를 모두 구현해야 한다.
}
class Fither implements Fightable {
    public void move(int x, int y) { /* 내용 생략 */ }
    public void attack(Unit u) { /* 내용 생략 */ }    
}

 

 

3. 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법


다형성을 공부하면 자손클래스의 인스턴스를 부모 타입의 참조 변수로 참조하는 것이 가능하다는 것을 알 수 있다.

인터페이스도 이를 구현한 클래스의 부모라 할 수 있으므로 해당 인터페이스 타입의 참조 변수로 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로 형변환도 가능하다.

 

public interface Animal {
    void sound();
}
public class Dog implements Animal {
 
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
 
    public void sleep() {
        System.out.println("쿨쿨 Zzz");
    }
}
public class Lion implements Animal {
 
    @Override
    public void sound() {
        System.out.println("크아앙");
    }
 
    public void hunting() {
        System.out.println("사냥을 합니다.");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal lion = new Lion();
 
        dog.sound();
        lion.sound();
 
      //  dog.sleep();     compile error 
      //  lion.hunting();  compile error 
      
      ((Dog)dog).sleep(); 	  // O
      ((Lion)lion).hunting();     // O
    }
}

dog은 Animal 인터페이스 타입이기 때문에 dog.sleep() 메서드는 호출하지 못하므로, Dog 클래스로 캐스팅하여 사용해야 한다.

 

 

4. 인터페이스 상속


인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다.

interface Moveable {
	void move(int x, int y);
}

interface Attackable {
	void attack(Unit u);
}

interface Fightable extends Movable, AttackeAble {...}

클래스 상속과 마찬가지로 자손 인터페이스(Fightable)은 조상 인터페이스(Moveable, Attacable)에 정의된 멤버를 모두 상속받는다.

그래서 Fightable 자체에는 정의된 멤버가 하나도 없지만 조상 인터페이스로부터 상속받은 두 개의 추상메서드를 멤버로 갖게 된다.

 

💡 인터페이스는 클래스와 달리 Object클래스와 같은 최고 조상이 없다.

 

 

5. 인터페이스의 default 메서드와 static 메서드


 default 메서드

이전에는 인터페이스에 추상 메서드만 선언할 수 있었는데, JDK 1.8부터 default 메서드와 static 메서드를 추가할 수 있게 되었다.

조상 클래스에 새로운 메서드를 추가하는 것은 별 일이 아니지만, 인터페이스의 경우에는 보통 큰일이 아니다. 인터페이스에 새로운 메서드를 추가한다는 것은, 추상 메서드를 추가한다는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현해야 하기 때문이다.

 

인터페이스가 변경되지 않으면 제일 좋겠지만, 아무리 설계를 잘해도 언젠가 변경은 발생하기 마련이다.

JDK의 설계자들은 고심 끝에 default 메서드라는 것을 고안해 내었다. 

 

default 메서드는 추상메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 default 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다. default 메서드는 앞에 키워드 "default"를 붙이며, 추상메서드와 달리 일반 메서드처럼 몸통{}이 있어야 한다. default 메서드 역시 접근 제어자는 public이며, 생략 가능하다.

 

interface MyInterface {
    void method();
    void newMethod(); // 추상 메서드
}

interface MyInterface {
    void method();
    default void newMethod() {...} // default 메서드
}

 

static 메서드

static 메서드는 인스턴스와 관계가 없는 독립적인 메서드이기 때문에 JDK1.8 이전에도 추가하지 못할 이유가 없었다.

인터페이스의 인스턴스 생성과는 관계없이 인터페이스 타입에서 호출이 가능한 정적 메서드 접근 제어자는 public으로 설정되어 있다.

static 메서드를 사용할 때에는 인터페이스 타입을 통해서 사용한다.

 

 

6. 인터페이스의 private 메서드


JDK 1.8에서 default 메서드와 static 메서드가 추가되었지만, 특정 기능을 처리하는 내부 메서드일 뿐인데도, 외부에 공개되는 public이라는 단점이 있었다. 이런 문제를 해결하기 위해 JDK 1.9에서 private 메서드와 private static 메서드가 추가되었다.

 

private 메서드의 사용은 인터페이스 내부에서 코드의 재사용성을 향상하는데, 예를 들어서 두 개의 default 메서드가 같은 코드를 공유해야 하는 경우 중복이 발생하게 되는데 이를 방지할 수 있고, interface에 대한 캡슐화를 유지 할 수 있다.

 

private 메서드를 사용하기 위한 4가지 규칙

  • private 인터페이스 메서드는 추상 메서드가 될 수 없다.
  • private 메서드는 인터페이스 내부에서만 사용될 수 있다.
  • private static 메서드는 다른 static, non-static 인터페이스 메서드에서 사용될 수 있다.
  • private non-static 메서드는 private static 메서드 내부에서 사용될 수 없다.

 

private 메서드 예제

public interface CustomInterface {

  void method1();

  default void method2() {
    method4(); // 디폴트 메서드 안의 private 메서드
    method5(); // non-static 메서드 안의 static 메서드
    System.out.println("default method 2");
  }

  static void method3() {
    method5(); // static 메서드 안의 private static 메서드
    System.out.println("static method");
  }

  private void method4() {
    System.out.println("private method 4");
  }

  private static void method5() {
    System.out.println("private staic method");
  }
}

public class CustomClass implements CustomInterface {

  @Override
  public void method1() {
    System.out.println("abstract method");
  }

  public static void main(String[] args) {
    CustomInterface instance = new CustomClass();
    instance.method1();
    instance.method2();
    CustomInterface.method3();
  }
}

 

결과

abstract method
private method 4
private staic method
default method 2
private staic method
static method

 

private 메서드 예제

import java.util.function.IntPredicate;
import java.util.stream.IntStream;

public interface CustomCalculator {

  default int addEvenNumbers(int... nums) {
    return add(n -> n % 2 == 0, nums);
  }

  default int addOddNumbers(int... nums) {
    return add(n -> n % 2 != 0, nums);
  }

  private int add(IntPredicate predicate, int... nums) {
    return IntStream.of(nums)
        .filter(predicate)
        .sum();
  }
}

public class Main implements CustomCalculator {

  public static void main(String[] args) {
    CustomCalculator demo = new Main();

    int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int sumOfEvens = demo.addEvenNumbers(nums);
    System.out.println(sumOfEvens);

    int sumOfOdds = demo.addOddNumbers(nums);
    System.out.println(sumOfOdds);
  }
}

 

결과

20
25

 

참고로 Odd라는 조건은 결국 Even의 논리적인 부정이므로 IntPredicate의 negate() 메서드를 사용해도 된다.

이 역시 IntPredicate의 default 메서드로 구현되어있습니다. (Functional Interface지만, default 메서드는 람다식을 만드는데 문제가 되지 않음을 알 수 있다.

public interface CustomCalculator {

  IntPredicate isEven = n -> n % 2 == 0;

  default int addEvenNumbers(int... nums) {
    return add(isEven, nums);
  }

  default int addOddNumbers(int... nums) {
    return add(isEven.negate(), nums);
  }

  private int add(IntPredicate predicate, int... nums) {
    return IntStream.of(nums)
        .filter(predicate)
        .sum();
  }
}

 

 

 

 

참고

 

백기선님 온라인 스터디 8주차 - 인터페이스

인터페이스를 아는것 보다 좀 더 깊이 공부해봅시다.

velog.io

 

  • 자바의정석

'Java' 카테고리의 다른 글

[Java] static 키워드  (1) 2022.09.01
[Java] 7.패키지  (1) 2022.08.31
[Java] 오버라이딩(Overriding)  (1) 2022.08.24
[Java] 자바는 왜 다중상속을 지원하지 않을까?  (0) 2022.08.24
[Java] 6.상속  (0) 2022.08.24
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/12   »
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
29 30 31
글 보관함