[Java] 인터페이스

2024. 1. 22. 01:07공부 중/Java

1. abstract class

 

가. 추상 클래스 정의

 

클래스들의 공통 모듈을 모아서 상속 구조로 정리

public class Vehicle {
    private int curX, curY;

    public void reportPosition() {
        System.out.printf("차종: %s: 현재 위치: (%d, %d)%n", curX, curY);
    }

    public void addFuel() {
        System.out.println("모든 운송 수단은 연료가 필요");
    }
}
public class ElectricCar extends Vehicle{
  private int curX, curY;

  public void reportPosition() {
      System.out.printf("차종: %s: 현재 위치: (%d, %d)%n", this.getClass().getSimpleName(), curX, curY);
  }

  public void addFuel() {
      System.out.printf("차종: %s: 연료 주입: %s%n", this.getClass().getSimpleName(), "충전");
  }
}
public class DieselSUV extends Vehicle{
  private int curX, curY;

  @Override
  public void reportPosition() {
      System.out.printf("차종: %s: 현재 위치: (%d, %d)%n", this.getClass().getSimpleName(), curX, curY);
  }

  @Override
  public void addFuel() {
      System.out.printf("차종: %s: 연료 주입: %s%n", this.getClass().getSimpleName(), "경유");
  }
}

Vehicle에서 구현한 코드는 반드시 자손 클래스에서 재정의해서 사용하기 때문에 무의미함.

 

메서드의 선언부만 남기고 구현부는 ;로 대체

 

abstract 키워드를 메서드 선언부에 추가.

 

객체를 생성할 수 없다는 의미로 클래스 선언부에 abstract 추가.

 

abstract public class Vehicle {
    private int curX, curY;

    abstract public void reportPosition();

    abstract public void addFuel();
}

이런 형태를 Abstract method design pattern이라고 한다.

 

abstract 클래스는 상속 전용의 클래스이고 자식은 반드시 abstract method를 재정의 해야 한다.

 

재정의 하지 않은 경우 자식 클래스도 abstract 클래스로 선언해야 함.

 


나. 추상 클래스를 사용하는 이유

 

  • abstract 클래스는 구현의 강제를 통해 프로그램의 안정성 향상.
    : abstract method는 클래스 다이어그램에서 italic으로 표기된다.

  • 인스턴스화되면 안 되는 클래스인 경우.
    : 예시로 동물, 사람은 추상적인 개념이지 현실에 존재하지 않음
    : 무조건 상속받아서 사용하도록 강제 때문에 abstractfinal이랑 같이 사용할 수 없음

 


2. interface

 

abstract를 더 abstract시켰다.

 

최고 수준의 추상화 단계.

 

  • 인터페이스
    : 서로 다른 두 시스템, 장치, 소프트웨어 따위를 서로 이어 주는 부분, 또는 그런 접속 장치

 


가. 규칙

public interface OMyInterface {
    public static final int Member1 = 10;
    int MEMBER2 = 10;

    public abstract void method1(int param);
    void method2(int param);
}
  • 모든 멤버변수public static final이며 생략 가능
    → 모든 구현체에서 동일한 값을 가진 변수가 있는 경우 한 번만 생성.
    → 표준화를 위해서 저마다 다른 값을 가지는 멤버변수는 인터페이스에 선언할 수 없음.

  • 모든 메서드public abstract이며 생략 가능
    (단, jdk 8에서 default method와 static method 추가함.)

 


나. 인터페이스 상속

 

인터페이스는 다중 상속이 가능하다.

public interface Fightable {
    int fire();
}
public interface Transformable {
    void changeShape(boolean isHeroMode);
}
public interface Heroable extends Fightable, Transformable {
    void upgrade();
}

extends 키워드를 사용해서 인터페이스를 상속한 인터페이스를 만들 수 있다.

 

public class IronMan extends Person implements Heroable {

    int weaponDamage = 100;

    @Override
    public int fire() {
        System.out.printf("빔 발사: %d만큼의 데미지를 가함%n", weaponDamage);
        return this.weaponDamage;
    }

    @Override
    public void changeShape(boolean isHeroMode) {
        String status = isHeroMode?"장착" : "제거";
        System.out.printf("장갑 %s%n", status);
     }

    @Override
    public void upgrade() {
        System.out.printf("무기 성능 개선");
    }

}

implements 키워드를 사용해서 구체화한다.

 


다. 인터페이스의 다형성

 

다형성은 조상 클래스뿐 아니라 조상 인터페이스에도 적용된다.

 

그렇다고 “인터페이스는 클래스다”는 아니다.

(그렇기 때문에 Object 클래스를 상속받지 않는다.)

 

하지만 인터페이스는 어떻게 보면 ‘클래스 유형?’이라고 할 수 있다.

(인터페이스의 컴파일 결과가 .class이고, 인터페이스는 타입으로 객체를 해당 타입으로 참조할 수 있다.)

 

그렇기 때문에 인터페이스도 instanceof를 사용할 수 있다.

public class IronManTest {

    public static void main(String[] args) {
        IronMan iman = new IronMan();
        Object obj = iman;
        Heroable hero = iman;
        Fightable fight = iman;
        Transformable trans = iman;
    }
}

 


3. 인터페이스의 필요성

 

  1. 구현의 강제성 → 표준화
  2. 인터페이스를 통한 간접적인 클래스 사용 → 손쉬운 모듈 교체 지원
  3. 서로 상속의 관계가 없는 클래스들에게 인터페이스를 통한 관계 부여 → 다형성 확장
  4. 모듈 간 독립적 프로그래밍 가능 → 개발 기간 단축

 


가. 표준화

public interface Printer {
    void print(String fileName);
}

동일한 인터페이스를 구현하는 모든 클래스는 사용법이 동일하다.

 


나. 비상속 관계 클래스 간 다형성 확장

void badeCase(){
    Object[] objs = {new HandPhone(), new DigitalCamera());

    for(Object obj : objs){
        if(obj instanceof HandPhone) {
            HandPhone phone = (HandPhone)obj;
            phone.charge();
        }else if(obj instanceof DigitalCamera) {
            DigitalCamera camera = (DigitalCamera)obj;
            camera.charge();
        }
    }
}

HandPhoneDigitalCamera 간 상속 관계가 성립하지 않기 때문에 charge()하기 어렵다.

 

둘의 공통 조상인 Object까지 거슬러 올라가야 하는데 Object에는 charge() 메서드가 없다.

 

매번 형변환을 수행하고 charge()를 수행해야 하는데 굉장히 비효율적이다.

 

void goodCase() {
    Chargeable[] objs = {new HandPhone(), new DigitalCamera()};

    for(Chargeable obj : objs) {
        obj.charge();
    }
}

 


다. 독립적인? 협업

 

사전에 인터페이스를 정의하면 정확한 비즈니스 로직은 각자 알아서 구현할 수 있다.

 

클라이언트를 위한 UI를 만드는 팀과 비즈니스 로직을 구현하는 팀이 있다고 가정한다.

 

둘 간에 인터페이스를 약속하면 UI 팀은 해당 인터페이스를 대충 구현한 Stub을 사용해서 완성된 비즈니스 로직이 없어도 작업할 수 있다. (보통 자동으로 생성됨)

 

비즈니스 로직팀은 UI를 고려하지 않고 비즈니스 로직 구현에만 집중할 수 있다.

 

인터페이스를 사이에 두고 병렬 작업이 가능해서 생산성이 크게 향상된다.

 


라. defualt method, static method

 

jdk 1.8부터 인터페이스에 선언된 구현부가 있는 메서드를 사용할 수 있게 되었다. (완전한 추상화가 깨졌다.)

public interface MyInter {
    public static final int MAX = 1;
    default void m1() {

    }
    static void m2() {

    }
}
class MyInterImpl implements MyInter{

}
class Test{
        void test() {
            MyInterImpl impl = new MyInterImpl();
            impl.m1();
            MyInter.m2();
        }
}

default는 접근 제한자가 아니라 그냥 키워드다.

 

접근 제한자는 public만 사용할 수 있음 (생략 가능)

 

기존의 인터페이스를 기반으로 동작하는 라이브러리가 존재한다고 가정한다.

 

기존의 인터페이스에 새로운 기능을 추가하면 모든 구현체들이 새로 추가된 abstract 메서드를 구현해야 한다.

 

default 메서드는 abstract가 아니므로 반드시 구현해야 할 필요는 없다.

 

implements 하지 않고 사용하거나, 모든 구현체들은 완전 동일한 기능만 사용해야 하는 경우 static으로 선언할 수 있다. (인터페이스에 귀속. 하위 구현체에서 재정의 불가)

 


마. default method 충돌

 

  • jdk 1.7 이하의 java에서는 interface method에 구현부가 없으므로 충돌이 없었음.
  • jdk 1.8부터 default method가 생기면서 동일한 이름을 갖는 구현부가 메서드가 충돌.

 

충동이 발생하면 아래와 같은 방법으로 처리된다.

 

  1. super class의 method 우선. default method 무시.
  2. 하나의 interface에서 default method를 제공하고 다른 interface에서도 같은 이름의 메서드(default 유무 무관)가 있을 경우 sub class는 반드시 override 해서 충돌을 해결해야 함.

 


4. abstract vs interface

 

  • 인터페이스는 다중 상속이 가능하다.
  • 인테페이스는 일반 메서드를 가질 수 없다.
  • 인터페이스의 모든 멤버변수는 public static final이다. 다른 형태의 멤버변수를 상속하려면 추상 클래스가 필요하다.

 


5. generic

 

다양한 타입의 객체를 다루는 메서드, 컴파일 단계에서 타입 체크.

 

클래스 또는 인터페이스 선언 시 <>에 타입 파라미터 표시

Stack s = new Stack();
s.push("String");
s.push(1234);

String ss = (String)s.pop();
int si = (int)s.pop();

Object로 넣으면 편하지만 빼면서는 형 변환에 주의해야 한다.

 

public static void main(String[] args) {
    Stack<String> s = new Stack<String>();
    s.push(1234+"");
    s.push("1234");
    s.push(Integer.toString(1234));
    s.push(String.valueOf(1234));

    for(String str : s) {
        System.out.println(str);
    }
}

Generic을 사용하면 꺼내면서 형 변환을 할 필요가 없다.

 

대신 타입을 맞춰 넣어야 한다.

 


1) 와일드 카드(?)

Stack<Object> s2;
Stack<?> s3;

?를 사용할 수 있다. Object와 같다.

 

Stack<String> s4;
Stack<? extends String> s5;

Stack<Integer> s6;
Stack<? extends Integer> s7;

Stack<? super String> s8;
  • Generic type<? extends T> : T 또는 T의 자손
  • Generic type<? super T> : T 또는 T의 조상

 

문서를 볼 때 가끔 나온다.

 


'공부 중 > Java' 카테고리의 다른 글

[Java] 예외 처리  (0) 2024.01.28
[Java] String  (0) 2024.01.22
[Java] 다형성  (0) 2024.01.22
[Java] 상속  (0) 2024.01.21
[Java] 객체지향  (0) 2024.01.21