개념
왜 써야 돼?
💡 다중 구현을 통한 자유로운 상속 관계 ⇒ 클래스의 다형성보다 다채롭고 자유롭게 사용 가능
- 프로그램을 설계하고 조금 더 유연한 프로그램을 만들기 위함
- 추상화, 상속과 더불어 다형성이라는 객체 지향(OOP)의 특징을 구현하는 핵심
- 인터페이스의 다형성
- 부모클래스 타입으로 자식클래스 타입을 포함시킬 수 있음(상속) ⇒ 인터페이스 타입으로 그 인터페이스를 구현한 클래스 객체를 사용할 수 있음
interface Keyboard { } class Logitec_Keyboard implements Keyboard { } class Samsung_Keyboard implements Keyboard { } class Apple_Keyboard implements Keyboard { } public class Main { public static void main(String[] args) { // 인터페이스 타입 배열로 여러가지 클래스들을 한번에 타입 묶음을 할 수 있음 // 인터페이스 타입으로 객체 생성 Keyboard[] k = { new Logitec_Keyboard(), new Samsung_Keyboard(), new Apple_Keyboard(), }; } }
- 인터페이스의 다형성
- 결합도를 낮춰 유지보수성을 늘리는 디자인 패턴으로서의 역할도 수행함
- Spring Framework에서 인터페이스를 많이 사용함
기본
💡 추상 메서드의 집합
필드와 메서드의 제어자가 확정되어 있음
따라서 필드와 메서드 앞 제어자를 생략해도 컴파일러가 자동으로 각각의 제어자를 삽입함
- 모든 필드: public static final
- 대부분의 메서드: public abstract
- 예외: default, static
구조
interface 인터페이스명 {
public static final 자료형 필드명 = 값;
public abstract 리턴타입 메서드명();
}
ex)
interface A {
public static final int a = 3;
public abstract void abc();
}
interface A {
int a = 3;
void abc();
}
System.out.println(A.a); // static의 특징: 클래스명이나 인터페이스명으로 접근 가능
A.a = 4; // error! final의 특징: 값 변경 불가능
구현
💡 추상 메서드의 집합 → 어떻게 구현해야 할까?
keywrod: implements, 다중 구현
- 추상 메서드처럼 그 자체로 인스턴스를 생성할 수 없음
- 인터페이스도 구현부를 만들어주는 클래스에 구현(implements)되어야 함
- 인터페이스를 상속 받았으면, 자식 클래스에서 인터페이스가 포함하고 있는 추상 메소드를 구체적으로 구현함
- 특징점
- 다중 구현
- implements Animal, Pet
// 인터페이스와 클래스
interface Animal {
public abstract void cry();
}
interface Pet {
public abstract void play();
}
class Tail {
...
}
// 클래스 상속 + 인터페이스 구현 = 동시에 가능, 다중 구현 가능
class Cat extends Tail implements Animal, Pet {
public void cry(){
System.out.println("냥");
}
public void play(){
System.out.println("쥐잡기 놀이");
}
}
- 자식 클래스에 클래스 상속(extends)와 인터페이스 구현(implements) 동시에 가능
public interface IBehavior {
public abstract void play();
}
public class Soccer extends Sport implements IBehavior {
@Override
public void play() {
System.out.println("Playing Soccer");
}
}
구현
일부만 구현하고 싶다면?
interface Animal {
void walk();
void run();
void breed();
}
// Animal 인터페이스 중 일부만 구현
abstract class Mammal implements Animal {
public void walk(){...}
public void run(){...}
}
class Lion extends Mammal {
@Override
public void breed(){...}
}
인터페이스 - 다중 구현이 가능한 이유
- 클래스와 다르게 메소드 구현부가 없기 때문에 충돌 가능성이 없음
- 자손 인터페이스: 조상 인터페이스에 정의된 멤버들을 모두 상속 받음
- 하지만 필드는 static이기 때문에 구현체를 따라가지 않음
interface Changeable{ void change(); } interface Powerable{ void power(boolean b); } interface Controlable extends Changeable, Powerable { // 인터페이스끼리 다중 상속 -> 추상 멤버를 그대로 물려받음 } // 클래스에 통합된 인터페이스를 구현 class MyObject implements Controlable{ public void change(){ System.out.println("채널을 바꾸는 메서드"); } public void power(boolean b){ System.out.println("전원 온오프를 하는 메서드"); } } // 프로그램의 시작점 public class Main{ public static void main(String[] args) { // 인터페이스 다형성 - 업캐스팅 Controlable[] o = {new MyObject(), new MyObject()}; o[0].change(); o[1].power(true); // 각 단일 인터페이스로도 타입으로 사용 가능 Changeable inter1 = new MyObject(); inter1.change(); Powerable inter2 = new MyObject(); inter2.power(true); } }
인터페이스 상수 필드 상속 관계
- 클래스의 상속일 경우, 클래스 필드 멤버끼리 상속되어 덮어 씌워짐
- 인터페이스 필드들은 모두 pulbic static final이기 때문에 상속을 해도 독립적으로 운용됨
// 인터페이스와 클래스
interface Iflower{
int ex = 10; // public static final
}
interface IPlant extends Iflower{
int ex = 20; // public static final
}
class Tulip implements IPlant{
int ex = 30; // 그냥 인스턴스 변수
}
public class Main{
public static void main(String[] args){
// 클래스로 접근
Tulip t = new Tulip();
System.out.println(t.ex); // 30
// 인터페이스로 접근
System.out.println(Iflower.ex); // 10
System.out.println(IPlant.ex); // 20
}
}
default & static 메서드
default 메서드
💡 보통 인터페이스를 구현한 이후, 수정과정에서 인터페이스 모든 구현체에게 수정없이 광역으로 함수를 만들어 주고 싶을 때 사용
- 앞에 키워드 default를 붙이고, 일반 메서드처럼 구현부{…}가 있어야 함
- 접근 제어자: public이고 생략 가능
- 자식 클래스에서 default 메소드를 오버라이딩하여 재정의 가능
interface Calculator {
int plus(int i, int j);
int multiple(int i, int j);
default int sub(int i, int j){
return i-j;
}
}
class MyCalculator implements Calculator {
// 추상 메서드만 구현 (default 메서드는 굳이 x)
@Override
public int plus(int i, int j) {return i+j;}
public int multiple(int i, int j) {return i*j;}
}
public class Main{
public static void main(String[] args) {
MyCalculator mycal = new MyCalculator();
Calculator cal = (Calculator) mycal;
int value = cal.sub(5, 10);
System.out.println(value); // -5
}
}
- default 메서드의 다중구현 문제
- 똑같은 디폴트 메서드를 가진 두 인터페이스 → 하나의 클래스에 구현 ⇒ 아무런 조치를 취하지 않으면 컴파일 에러
- 인터페이스를 구현한 클래스 → 디폴트 메서드를 오버라이딩 ⇒ 하나로 통합
- 인터페이스의 디폴트 메서드와 부모 클래스 메서드간 충돌이 발생한다면?
⇒ 부모 클래스의 메서드가 상속되고 디폴트 메서드는 무시
- default 메서드의 super
- 인터페이스명.super.디폴트메서드
interface IPrint {
default void print(){
System.out.println("인터페이스의 디폴트 메서드");
}
}
class MyClass implements IPrint{
@Override
public void print(){
IPrint.super.print(); // 인터페이스 super 메서드 호출
System.out.println("인터페이스의 디폴트 메서드를 오버라이팅한 메서드");
}
}
public class Main {
public static void main(String[] args) {
MyClass cls = new MyClass();
cls.print();
// 인터페이스의 디폴트 메서드
// 인터페이스의 디폴트 메서드를 오버라이팅한 메서드
}
}
static 메서드
💡 인스턴스 생성과 상관없이 인터페이스 타입으로 접근하여 사용할 수 있는 메서드
interface Calculator {
public int plus(int i, int j);
public int multiple(int i, int j);
// 디폴트 메서드
default int sub(int i, int j){
return i - j;
}
**// 스태틱 메서드
public static void explain(){
System.out.println("interface static 메서드 입니다. 이 인터페이스는 pluc, multipe, sub 기능을 제공하는 메서드를 지니고 있습니다. (설명)");
}**
}
class MyCalculator implements Calculator {
@Override
public int plus(int i, int j) { return i + j; }
@Override
public int multiple(int i, int j) { return i * j; }
}
public class Main {
public static void main(String[] args){
// 클래스 처럼 static 메소드 호출 하면 된다.
**Calculator.explain(); // "interface static 메서드 입니다. 이 인터페이스는 pluc, multipe, sub 기능을 제공하는 메서드를 지니고 있습니다. (설명)"**
}
}
참고자료
반응형