인터페이스 (Interface)
- 클래스 사용의 권고사항을 제시하는 역할을 하는 특수형태의 클래스
- 아무런 구현이 되어 있지 않으며,
모든 메소드가 추상 메소드이다. (Java 1.8에서 변경됨) - be able to ~ , ~할수있는에 해당하는 기능을 부여하며 ~able로 네이밍하는경우가 많다.
인터페이스의 특징
- class 가 아닌 interface 키워드를 사용한다.
- public과 default 접근제어자 사용가능하다.
- 멤버 변수는 항상 "public static final"이다.
- 인터페이에는 클래스 멤버 변수(static) 밖에 생성하지 못한다.
- 인터페이스는 객체를 생성하지 않기 때문에 객체 멤버 변수가 없다.
- 객체를 생성할 수 없기 때문에 생성자도 없다.
- "public static final" 키워드는 생략 가능 하다.
멤버 메소드도 항상 "public abstract"이다. (Java 1.8에서 default 메소드 허용됨)- 정의상으로 모든 메서드가 추상메서드인 클래스이기 때문에 어찌며보면 당연한 이야기
- 오버라이딩을 위한 메서드이기 때문에 private 메서드는 의미가 없다
- "public abstract"는 생략 가능하다
- 정적 메소드도 선언이 가능하다.
- 정적 메소드는 추상메서드가 될 수 없다. (static abstract X)
- 정적 메소드는 구현부가 있어야 한다.
- 정적 메소드는 오버라이딩 할 수 없다.
- 정적 메소드는 추상메서드가 될 수 없다. (static abstract X)
public interface Idoable {
public static void staticDo() {
// do something;
}
void do(); // public abstract 생략 가능
}
public class DoSomething implements IDoable {
@override
public void do() {
//do something2;
}
}
public class Test {
public static void main(String[] args) {
Idoable.staticDo(); //인터페이스의 static 메소드 호출 방법
Idoable iDo = new DoSomething();
iDo.do();
}
}
- 인터페이스의 상속은 implements 키워드 사용한다.
- 클래스는 하나만 상속할 수 있으나, 인터페이스는 여러 개를 구현 가능하다.
- extends 에는 하나의 클래스만 적을 수 있지만, 인터페이스는 implements 하고 여러 개의 인터페이스를 적을 수 있음
- 이때 구상클래스(구현 클래스)에서는 인터페이스에 정의된 메소드를 모두 구현하여야 한다.
- 인터페이스를 이용하면 다중 상속을 한 것 처럼 사용할 수 있다.
- extends 에는 하나의 클래스만 적을 수 있지만, 인터페이스는 implements 하고 여러 개의 인터페이스를 적을 수 있음
- 인터페이스명을 자료형으로 하여 구현체의 객체를 참조할 수 있다.
- 상속관계의 경우와는 다르게 인터페이스의 객체는 생성되지 않는다. new Interface(); // 불가
- 그러나 메서드는 오버라이딩 하기 때문에 가상 메서드 호출은 일어난다.
- 이를 통하여 다형성을 구현할 수 있다.
- 오버라이딩 overriding : 하위 클래스에서 상위 클래스의 메소드를 재정의 하는 것
- 오버로딩 overloading : 이름은 동일하지만 매개변수가 다른 경우
- 추상메서드가 없다면 구현체에서 메서드 구현을 안해도 된다.
- 구현해야 할 메서드가 하나도 정의되지 않은 경우
- default 메소드만 존재하는 경우
- 인터페이스는 객체를 생성할 수 없다.
interface IFoo{
public static final int MEMBER_VAR = 10; // (상수는 대문자와 '_'조합)
// public static final 생략 가능
public abstract void methodA(int param); // public abstract 생략가능
void methodB(int param); // public abstract
}
class Foo implements IFoo{
@Override
public void methodA(int param) {
System.out.println(param);
}
@Override
public void methodB(int param) {
System.out.println(param);
}
}
- 인터페이스의 이름의 예시
- (1)interface Ifoo < class Foo
- 상속하는 자식에서 부모쪽으로 화살표 한다
- (2)interface Printable <- class Bar
- (1)과 같이 인터페이스명에 대문자 I를 붙여서 구분한다.
- 혹은 (2)와 같이 형용사로 표현하기 좋은 이름(~able)은 I를 생략한다.
- (1)interface Ifoo < class Foo
인터페이스 간의 상속
- 인터페이스로 인터페이스를 상속할 경우, extends를 사용한다.
- 인터페이스를 구현 할때만 implements를 사용한다.
- 클래스 <- 클래스 상속과 달리 다중 상속 가능하다.
- 인터페이스의 상속으로 다형성을 구현할 수 있다.
- 인터페이스를 상속한 인터페이스를 구현할 때는 상속받은 모든 부모 인터페이스의 메서드를 구현해야한다.
interface Walkable{
void walk();
}
interface Runnable{
void run();
}
interface Jumpable extends Walkable, Runnable{
void jump();// walk(), run()도 포함되어 있을 것
}
class Jumper implements Jumpable{
@Override
public void walk() {
System.out.println("walk");
}
@Override
public void run() {
System.out.println("run");
}
@Override
public void jump() {
System.out.println("jump");
}
}
JDK 1.8 이후의 인터페이스
- JDK 1.8 이후 인터페이스에서는 default 메소드를 구현할 수 있다.
- default 메서드는 모든 하위 클래스에 필수이며, 중복되는 메서드로 중복 구현을 방지하는 역할을 한다.
- 인터페이스의 철학(정의)과는 맞지 않으나, 모든 자식 메서드가 동일하게 구현되어야 하는 메소드가 생긴 경우 쉽게 기능을 추가하기위해 만들어졌다.
- default 메서드 특징
- default 메소드는 항상 public이다.
- default는 접근제어자의 default가 아니고 인터페이스의 default 메서드를 나타낸다.
- 인터페이스를 구현하는 모든 클래스에서 사용 될 메서드이기 때문에 private은 의미가 없다.
- default 메소드는 구현체에서는 반드시 구현할 필요는 없다.
- abstract 메서드의 경우 구현체에서 반드시 구현하여야 하지만 default 메서드의 경우 구현하지 않아도 된다.
- 자녀 클래스에서 구현하지 않은 default 메서드에 접근시 부모클래스의 default 메서드에 접근된다.
- default 메소드는 항상 public이다.
interface IFooTwo {
void abstractMethod();
default void defaultMethod(){ // 디폴트 메소드, 구현 클래스에서 오버라이드 가능
System.out.println("Default method"); // 구현을 해주어야함
}
}
class FooTwo implements IFooTwo{
@Override
public void abstractMethod() {
}
@Override // Overriding not necessary
public void defaultMethod(){
System.out.println("Overriden default method");
}
}
class SuperFoo{
public void defaultMethod(){
System.out.println("Default method in Super class");
}
}
class FooThree extends SuperFoo implements IFooTwo{
}
// 인터페이스의 static 메소드
interface IBar {
static void staticMethod(){
System.out.println("static method"); //바로 구현 가능
}
}
- 상속받은 클래스와 구현한 인터페이스의 모두 default method가 있다면 상속받은 부모클래스의 default method가 실행된다.
- 이때 인터페이스의 default method는 실행되지 않는다.
- 부모와 인터페이스에 모두 동일한 메서드가 있는 경우 부모클래스에 있는 메소드를 실행한다.
- 그렇다면 두 개 이상의 인터페이스에 동일한 default method가 있다면 구현 클래스에서 default method는 뭐가 호출될까???
- 인터페이스의 정적 메서드가 있는경우
- 인터페이스 명으로 호출 가능 ex ) IBar.staticMethod();
- 구현한 클래스명으로 호출 불가능 ex) // Bar.staticMethod();
- 상속에서는 가능하지만 인터페이스에서는 불가능 하다.
인터페이스와 추상메서드의 차이점
- 추상메서드는 멤버 변수가 있기 때문에 자녀 클래스들에 공통 속성을 설정할 수 있다. 또한 일반메서드와 추상메서드로 공통의 기능도 구현할 수 있다. 그래서 추상메서드는 어떤 개념에 상위 개념으로 세분화 되는 하위 클래스들을 대표하는 클래스로 사용할 수 있다.
- 대표적인 예로 Animal 이란 큰 대분류안에 많은 종류의 Animal 을 상속하는 동물클래스들을 작성할 수 있으며 그들의 공통 속성인 name, lifespan, color 것을 공통적으로 상속해 줄 수 있고 또한 공통 기능인 walk(),eat()등의 기능들도 상속해 줄 수 있다.
- 추상메서드의 역할을 is kind of(어떤 종류의) 라고 하나보다..
- 반면에 인터페이스는 추상메서드 밖에 없지만 다중 상속이 가능하기 때문에 세분화된 기능을 나타내는데 활용될 수 있다.
- 예를 들면 Animal의 하위클래스들 중에 세분화 기능이라고 볼 수 있는 flyable, ridable 등을 표시해줄 수 있으며 동물별로 여러개의 세분화 된 기능들을 추가하는데 사용할 수 있다
- 그래서 인터페이스의 역할을 able to(~를 할 수 있는)라고 하나보다..
- 부모 속성(또는 공통)의 멤버 변수가 반드시 필요한 경우라면 추상 클래스를 이용하고, 처리할 기능(able to)을 하나의 그룹으로 모아야 하는 경우에는 interface로 선언한다.
- Iterator 인터페이스 실제 예
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
// 직접 구현하는 방법
import java.util.Iterator;
public class PancakeHouseMenu implements Iterator<MenuItem> {
MenuItem[] items;
int position = 0;
public PancakeHouseMenu() {
items = new MenuItem[3];
MenuItem bread = new MenuItem("Bread");
MenuItem soup = new MenuItem("soup");
MenuItem juice = new MenuItem("juice");
items[0] = bread;
items[1] = soup;
items[2] = juice;
}
public boolean hasNext() {
if(position >= menuItems.length || items[position] == null)
return false;
else
return true;
}
public MenuItem next() {
MenuItem item = items[position];
position++;
return menuItem;
}
//not necessary
//public void remove() {}
}
// 간단한 방법
public class PancakeHouseMenu {
List<MenuItem> menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList<MenuItem>();
MenuItem bread = new MenuItem("Bread");
MenuItem soup = new MenuItem("soup");
MenuItem juice = new MenuItem("juice");
menuItems.add(bread);
menuItems.add(soup);
menuItems.add(juice);
}
public Iterator<MenuItem> createIterator() {
return menuItems.iterator();
}
}
//java.util.ArrayList 발췌
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
... 생략 ...
transient Object[] elementData; //transient : serialize 제외할 멤버변수 선언
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
...
}
'프로그래밍 > OOP_Pattern_TDD' 카테고리의 다른 글
OOP, 설계 원칙, 디자인 패턴, 코딩 규칙, 리팩터링, 테스트 케이스(또는 TDD) 사이의 관계 (1) | 2025.04.30 |
---|---|
스트림 성능 개선 & 람다식 간소화 사례 (0) | 2024.06.27 |
람다 메서드 참조 방식 (0) | 2024.06.26 |
[한글화 프로젝트] TDD는 죽었는가? (0) | 2024.05.23 |
OOP KISS 원칙과 YANGI 원칙 (0) | 2024.05.02 |