[Java] 인터페이스와 다형성


인터페이스와 다형성


1. 인터페이스와 다형성


인터페이스는 이를 구현한 클래스의 조상이라 할 수 있으므로, 해당 인터페이스의 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있다. 인터페이스 타입으로 형변환도 가능하다.
(특정한 인터페이스를 구현하고 있는 클래스가 있을 때 이 클래스의 데이터 타입으로 인터페이스를 지정 할 수 있다.)
Fightable t = (fightable) new Fighter();
또는
Fighetable f = new Figher();
인터페이스 Fightable을 클래스 Fighter가 구현했을 때, Fighter 인스턴스를 Fightable타입의 참조변수로 참조하는 것이 가능하다.
Fightable 타입의 참조변수로 인터페이스 Fightable에 정의된 멤버들만 호출이 가능하다.

void attack(Fightable f){
...
}
인터페이스 타입의 매개변수가 갖는 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다는 것이다.
그래서 attack메서드를 호출할 때는 매개변수로 Fightable 인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다.

class Fightable extends implements Fightable{
public void move(int x, int y){
///내용 생략
}
public void attack(Fightable f){
//내용 생략
}
}
위와 같이 Fightable 인터페이스를 구현한 Fightable클래스가 있을 때, attack메서드의 매개변수로 Fighter인스턴스를 넘겨 줄 수 있다. 즉, attack(new Fighter())와 같이 할 수 있다는 것이다.
그리고 다음과 같이 메서드의 리턴타입으로 인터페이스의 타입을 지정하는 것 역시 가능하다.

Fightable method(){
//...
return new Fighter();
}
리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.
위의 코드에서는 method()의 리턴타입이 Fightable인터페이스이기 때문에 메서드의 return문에서 Fightable인터페이스를 구현한 Fightable클래스의 인스턴스를 반환한다.

(1) 예제
interface I{ }
class C implements I{ }
public class PolymorphismDemo2 {
    public static void main(String[] args) {
        I obj = new C();
    }
}
위의 코드를 통해서 알 수 있는 것은 클래스 C의 데이터 타입으로 인터페이스 I가 될 수 있다는 점이다. 이것은 다중 상속이 지원되는 인터페이스의 특징과 결합해서 상속과는 다른 양상의 효과를 만들어낸다. 아래 코드를 보자.

interface I2{
    public String A();
}
interface I3{
    public String B();
}
class D implements I2, I3{
    public String A(){
        return "A";
    }
    public String B(){
        return "B";
    }
}
public class PolymorphismDemo3 {
    public static void main(String[] args) {
        D obj = new D();
        I2 objI2 = new D();
        I3 objI3 = new D();
         
        obj.A();
        obj.B();
         
        objI2.A();
        //objI2.B();//I2 인터페이스에 B 메소드가 없다.
         
        //objI3.A();
        objI3.B();
    }
}
주석처리된 메소드 호출은 오류가 발생하는 것들이다. objI2.b()에서 오류가 발생하는 이유는 objI2의 데이터 타입이 인터페이스 I이기 때문이다. 인터페이스 I는 메소드 A만을 정의하고 있고 I를 데이터 타입으로 하는 인스턴스는 마치 메소드 A만을 가지고 있는 것처럼 동작하기 때문이다.
이것은 인터페이스의 매우 중요한 특징 중의 하나를 보여준다. 인스턴스 objI2의 데이터 타입을 I2로 한다는 것은 인스턴스를 외부에서 제어할 수 있는 조작 장치를 인스턴스 I2의 맴버로 제한한다는 의미가 된다. 인스턴스 I2와 I3로 인해서 하나의 클래스가 다양한 형태를 띄게 되는 것이다.
cf.) 하나의 인터페이스를 구현하는 여러 개의 클래스가 있다면 각각의 클래스를 인스턴스화 했을 때 데이터 타입으로 각가의 클래스가 공통적으로 구현하고 있는 인터페이스를 데이터 타입으로 해서 같은 데이터 타입으로 가지고 있지만 실제 클래스가 무엇이냐에 따라 다르게 동작할 수 있다.

ex.) MyMp3 mp3 = new TonyMp3( );
‘MyMp3 기준을 만족하는 제품 TonyMp3 객체‘이런 해석으로 보면 성립되는 구문이다. 이것이 바로 인터페이스를 변수 타입으로 선언한다는 의미이다.
반드시 기억해야할 사실은 컴파일러가 따지는 것은 단순히 변수의 타입이라는 것이다. 
즉 MyMp3 mp3 = new TonyMp3( );에서 컴파일러는 뒤의가 MyMp3의 기 객체 기능이 구현된 객체이기만 하면 된다. 때문에 mp3.XXX를 호출할 때 컴파일러가 따지는 것은 인터페이스인 
MyMp3에 호출하려는 XXX가 존재하는지만이 중요할 뿐이다.
→ 인터페이스는 어떤 객체를 직접 알고서 호출하는 방식이 아니라. 어떤 객체가 어떤 기능적인 스펙을 만족한다는 것을 더 중요하게 생각한다.
따라서 인터페이스를 이용해서 어떤 호출을 할 때 중요한 것은 실제 객체가 무엇인가가 중요한 것이 아니라 인터페이스에 해당하는 메소드가 존재하는지가 더 중요하다.

2. 다형성을 실행활에 비유
필자가 이해를 돕기 위해서 비유를 시도해보겠다. 누차 강조 하지만 비유는 비유일 뿐이다. 비유는 여러분의 머리속을 더욱 복잡하게 할 수 있다.
사람은 다면적인 존재다. Steve라는 사람이 있다. 이 사람은 집에서는 아버지이고 직업적으로는 프로그래머이고 또 종교단체 내에서는 신도(believer)가 될 수 있다. 하나의 사람이지만 그가 어디에 있는가? 누구와 관계하는가에 따라서 아버지이면서 프로그래머이고 또 신도인 것이다.
Rachel는 집에서는 엄마고 직장에서는 프로그래머다.
Steve와 Rachel이 같은 직장(Workspace)에 다니고 있다고 한다면 직장 입장에서는 두사람이 프로그래머라는 점이 중요할 뿐 이들의 가족관계나 종교성향에는 관심이 없다. 직장 입장에서 두사람은 프로그래머이고 프로그래머는 코딩을 통해서 무엇인가를 창조하는 사람들이다. 따라서 이들에게 업무를 요청할 때는 코딩을 요청하면 된다. 하지만 두 사람의 실력이나 성향에 따라서 코딩의 결과물은 달라질 것이다. 이러한 관계를 굳이 코드로 만들면 아래와 같다.

interface father{}
interface mother{}
interface programmer{
    public void coding();
}
interface believer{}
class Steve implements father, programmer, believer{
    public void coding(){
        System.out.println("fast");
    }
}
class Rachel implements mother, programmer{
    public void coding(){
        System.out.println("elegance");
    }
}
public class Workspace{
    public static void main(String[] args[]){
        programmer employee1 = new Steve();
        programmer employee2 = new Rachel();
         
        employee1.coding();
        employee2.coding();
    }
}
위의 코드를 보면 알겠지만 Steve와 Rachel의 사용자인 직장에서는 Steve와 Rachel의 인터페이스인 programmer를 통해서 두사람과 관계하게 된다. 두 사람이 어떤 종교나 가족관계를 가졌건 인터페이스 programmer을 가지고 있다면 고용할 수 있다. 회사에서는 코딩을 할 수 있는 사람이 필요하고 어떤 사람이 programmer라는 인터페이스를 구현하고 있다면 그 사람은 반드시 coding이라는 메소드를 구현하고 있을 것이기 때문이다. 또 두 사람에게 업무를 요청 할 때는 programmer라는 인터페이스의 메소드인 coding을 통해서 요청하면 된다. 하지만 두 사람의 성향이나 능력에 따라서 그 업무를 수행한 결과는 다른데 Steve는 빠르게 코딩하고 Rachel은 우아하게 코딩하고 있다.

3. 다형성 관련 예제(상속)

class AAA {
protected int x = 100;
}
class BBB extends AAA {
int y = 300;
}
class CCC extends AAA {
int z = 400;
}
public class Exam_12 {
public static void main(String[] ar) {
BBB bb = new BBB();
System.out.println("bb.x = " + bb.x);
System.out.println("bb.y = " + bb.y);
CCC cc = new CCC();
System.out.println("cc.x = " + cc.x);
System.out.println("cc.y = " + cc.z);
AAA ab = new BBB();
System.out.println("ab.x = " + ab.x);
//System.out.println("ab.y = " + ab.y);
AAA ac = new CCC();
System.out.println("ac.x = " + ac.x);
//System.out.println("ac.z = " + ac.z);
}
}
//다형성을 쓰는 이유: 한 가지 타입으로 관리하여 묶어서 사용하기 위함(ex. 한 가지 타입으로 배열로 묶어 사용할 수 있음)
//다형성을 안쓰면 매번 클래스를 생성해야 함. 대신 부모타입으로 객체생성하면 하위클래스의 멤버는 사용할 수 없다.

class AA {
public String toString() {
return "AAAA";
}
}

class BB {
public String toString() {
return "BBBB";
}
}

public class Exam_13 {
public static void main(String[] ar) {
// 두 객체를 Object 타입으로 담아서 배열로 관리하기
Object[] obj = new Object[2];
obj[0] = new AA();
obj[1] = new BB();

for (int i = 0; i < obj.length; ++i) {
System.out.println("obj[" + i + "] = " + obj[i]);
}
/*
* Object a = new AA(); Object b = new BB(); 
* System.out.println("a = " + a);//a.toString() 
* System.out.println("b = " + b);//b.toString()
*/
}
}

class CCCC {
int x = 100;
int y = 200;
}

class DDDD extends CCCC {
int y = 300;
int z = 400;
}

public class Exam_14 {
public static void main(String[] ar) {
// CCCC.x, DDDD.y, DDDD.z 사용가능
DDDD dd = new DDDD();
System.out.println("dd.x = " + dd.x);
System.out.println("dd.y = " + dd.y);
System.out.println("dd.z = " + dd.z);
// CCCC.x, CCCC.y 사용가능 / DDDD.z는 사용불가
CCCC cd = new DDDD();
System.out.println("cd.x = " + cd.x);
System.out.println("cd.y = " + cd.y);// 부모의 멤버 필드 사용
}
}

class FF {
public void aaa() {
System.out.println("FF=AAA");
}

public void bbb() {
System.out.println("FF=BBB");
}
}

class HH extends FF{
public void bbb() {
System.out.println("HHCCC");
}

public void ccc() {
System.out.println("HHDDD");
}
}

public class Exam_15 {
public static void main(String[] ar) {
HH hh = new HH();
hh.aaa();
hh.bbb();
hh.ccc();
FF fh = new HH();
fh.aaa();
// fh.bbb()는 HH.bbb()를 사용한다.--> fh.bbb
fh.bbb();
// ap.ccc();
}
}


'Programing > Java' 카테고리의 다른 글

[Java] public static void main(String [] args)  (0) 2014.12.23
[Java] 인터페이스  (0) 2014.12.23
[Java] 다형성  (0) 2014.12.21
[Java] 쓰레드의 동기화  (0) 2014.12.21
[Java] 쓰레드의 실행제어  (0) 2014.12.21