SyntaxHighlighter.all(); 게을러지고 싶어 부지런한 개발자 :: 게을러지고 싶어 부지런한 개발자

다형성이란? 


-여러 가지 형태를 가질 수 있는 능력으로, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함 


무슨 말이냐?? 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다는 것 

(※참고:  조상클래스 = 부모클래스, 자손클래스 = 자식클래스)


아래 예시를 보며 자바에서의 '다형성' 의미를 알아봅시다. 




예시) 

왼쪽 사진) TV클래스 (부모클래스)

오른쪽 사진) TV클래스를 상속받고 있는 CaptionTV 클래스 (자식클래스) 


 

생성된 TV클래스와 CaptionTV 클래스를 다루기 위해, 일반적으로 인스턴스의 타입과 일치하는 타입의 참조변수를 사용하면 아래와 같습니다. 

TV t = new TV();                          //TV클래스에서만 선언된 변수 및 메소드 호출가능

CaptionTV c = new CaptionTV();    //TV클래스를 상속받아서, TV클래스 & CaptionTV클래스에 선언된 변수 및 메소드 호출가능



하지만 두 클래스가 서로 상속관계에 있을 경우, 다음과 같이 부모 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능! 

 

TV t = new CaptionTV();    




그렇다면 인스턴스를 같은 타입의 참조변수로 참조하는 것과 부모타입의 참조변수로 참조하는 것은 어떤 차이가 있나?


: 둘 다 같은 타입의 인스턴스(ex. CaptionTV())지만 참조변수의 타입(ex. c와 t)에 따라 사용할 수 있는 멤버의 개수가 달라짐

부모클래스 타입의 참조변수 t로는 CaptionTV 인스턴스의 모든 멤버 사용 X 


CaptionTV c = new CaptionTV(); 


c.power;

c.channel;

c.power();

c.channelUp();

c.channelDown(); 

c.text;

c.caption(); 



    TV t = new CaptionTV(); 



t.power;

t.channel;

t.power();

t.channelUp();

t.channelDown(); 

t.text;                     //사용불가

t.caption();              //사용불가



 


참고로 

자식타입의 참조변수로 부모타입의 인서턴스 참조는 불가능함. 왜냐하면 실제 인스턴스인 TV의 멤버개수보다 자식클래스에서 사용할 수 있는 멤버 개수가 더 많기 때문에 이를 허용하지 않음 

ex)

1
2
3
4
5
6
7
8
9
10
11
12
13
class Parent { ... }
 
class Child extends Parent { ... }
 
...
 
Parent p = new Parent(); // 허용
 
Child c = new Child();   // 허용
 
Parent pc = new Child();  // 허용
 
Child cp = new Parent();  // 오류 발생



정리하면, 부모타입의 참조변수로 자식타입의 인스턴스를 참조할 수 있다.

하지만 자식타입의 참조변수로 조상타입의 인스턴스는 참조할 수 X 



다형성) 참조변수와 인스턴스의 연결 

-부모 클래스에 선언된 멤버변수와 같은 이름의 인스턴스변수를 자식 클래스에 중복했을 때, 

참조변수의 타입에 따라 서로 다른 결과를 얻음


Child cp = new Child();Parent pc = new Child();서로 다른 결과를 얻음 


메서드의 경우, 부모 클래스의 메소드를 자식클래스에서 오버라이딩한 경우에도 참조변수 타입과 관계없이 오버라이딩된 메소드가 호출되지만, 멤버변수의 경우조변수의 타입에 따라 달라짐


-그렇다면 멤버변수가 어떻게 달라지나?

    -부모클래스타입의 참조변수를 사용했을 때는 (ex. Parent p = new Child(); ) 부모클래스에 선언된 멤버변수가 사용됨

    -자식클래스타입의 참조변수를 사용했을 때는 (ex. Child c = new Child(); ) 자식클래스에 선언된 멤버변수가 사용됨 


 ex)

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
public class MainClass {
 
    public static void main(String[] args) {
        Parent p = new Child();  //부모클래스타입의 참조변수를 사용
        Child c = new Child();   //자식클래스타입의 참조변수를 사용
        
        System.out.println("p.x = " + p.x);  //멤버변수 호출
        p.method();                          //메소드 호출
        System.out.println("c.x =" + c.x);   //멤버변수 호출
        c.method();                          //메소드 호출
        
    }
    
    class Parent {
        int x = 100;
        void method() {
            System.out.println("Parent method");
        }
    }
    
    class Child extends Parent {
        int x = 200;
        void method() {
            System.out.println("Child method");
        }
    }
}
 
cs


결과

 p.x = 100

Child Method

c.x = 200

Child Method





다형성) 참조변수의 형변환


-서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행 가능!! 

-그러나 기본형 변수의 형변환에서 작은 자료형에서 큰 자료형의 형변환은 생략 가능하듯, 참조형 변수의 형변환에서는

자식타입의 참조변수를 부모타입으로 형변환하는 경우에 명식적인 형변환 생략 가능 


● 자식타입 → 조상타입 (Up-casting) :      형변환 생략가능

● 조상타입 → 자식타입 (Down-casting) :  형변환 생략불가 

       -문법: (변환할타입의클래스이름) 변환할참조변수


ex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Parent { ... }
 
class Child extends Parent { ... }
 
class Brother extends Parent { ... }
 
...
 
Parent pa01 = null;
 
Child ch = new Child();
 
Parent pa02 = new Parent();
 
Brother br = null;
 
 
 
pa01 = ch;          // pa01 = (Parent)ch; 와 같으며, 타입 변환을 생략할 수 있음.
 
br = (Brother)pa02; // 타입 변환을 생략 
 
br = (Brother)ch;   // 직접적인 상속 관계가 아니므로, 오류 발생.
cs





다형성) instanceof 연산자

이러한 다형성으로 인해 런타임에 참조 변수가 실제로 참조하고 있는 인스턴스의 타입을 확인할 필요성 생김

자바에서는 instanceof 연산자를 제공하여, 참조 변수가 참조하고 있는 인스턴스의 실제 타입을 확인할 수 있도록 함 

자바에서 instanceof 연산자는 다음과 같이 사용함


문법:  참조변수 instanceof 클래스이름 


-주로 조건문에 사용되며 instanceof의 왼쪽에는 참조변수, 오른쪽에는 타입(클래스명)이 피연산자로 위치

-그리고 연산의 결과로 boolean값이 true와 false 중의 하나를 반환 

(true를 얻었다는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 뜻함


ex)

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
32
33
34
35
class Parent { }
 
class Child extends Parent { }
 
class Brother extends Parent { }
 
 
 
public class Polymorphism01 {
 
    public static void main(String[] args) {
 
        Parent p = new Parent();
 
        System.out.println(p instanceof Object); // true
 
        System.out.println(p instanceof Parent); // true
 
        System.out.println(p instanceof Child);  // false
 
        System.out.println();
 
 
 
        Parent c = new Child();
 
        System.out.println(c instanceof Object); // true
 
        System.out.println(c instanceof Parent); // true
 
        System.out.println(c instanceof Child);  // true
 
    }
 
}
cs






인터페이스(Interface)란?


-구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'


-추상메서드와 상수만을 멤버로 가질 수 있음

(원래는 인터페이스의 모든 메서드는 추상메서드이어야 하는데, JDK1.8부터 인터페이스에 static 메서드와 default 메서드의 추가를 허용하는 방향으로 번경됨!!!!) 


-인터페이스에 선언된 모든 추상메서드들은 클래스에서 구현되어야만 함 

(즉, 인터페이스에서는 기본 설계도로써 선언만 해놓고,인터페이스 설계도의 구현은 해당 인터페이스를 상속받는 클래스별로도 작성함) 

 


인터페이스 작성 

-interface를 키워드 사용


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


-모든 멤버변수는 public static final이며, 이를 생략 가능

-모든 메서드는 public abstract 이며, 이를 생략 가능


생략된 제어자는 컴파일 시에 컴파일러가 자동적으로 추가해주므로 에러 발생 X 




인터페이의 상속

인터페이스의 상속 : 클래스가 인터페이스를 구현한다는 의미의 'implements' 키워드 사용  

 - 인터페이스는 인터페이스로부터만 상속 받을 수 있으며, 클래스와 달리 다중상속 가능

 - 클래스의 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스의 정의된 멤버를 모두 상속 받음 

-인터페이스를 상속받는 클래스는 상속과 구현을 동시에 할 수 있음 


(설명)  

-->'Seolhyun 클래스는 Person클래스를 상속받으며, 동시에 Character인터페이스를 구현한다.'

 

1
2
3
4
5
class Seolhyun extends Person implements Character {

    public void move(int x, int y) {
        System.out.println("설현이 움직입니다.");
    }
}
cs




예시1) 장난감 인터페이스 



Toy.java  (인터페이스) : 장난감 작업 명세서만 있는 껍데기

1
2
3
4
5
6
7
public interface Toy {
    
    public void walk();
    public void run();
    public void alarm();
    public void light();    
}
cs



ToyAirplane.java (클래스) : 인터페이스에서 선언 된 메소드들을 ToyAirplane 클래스 성격에 맞게 구현 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ToyAirplane implements Toy {
 
    @Override
    public void walk() {
        System.out.println("The airplane can walk");        
    }
 
    @Override
    public void run() {
        System.out.println("The airplane can not run");
    }
 
    @Override
    public void alarm() {
        System.out.println("The airplane has alarm function");
        
    }
 
    @Override
    public void light() {
        System.out.println("The robot has no light function");                        
    }
}
cs


ToyRobot.java (클래스) : 인터페이스에서 선언 된 메소드들을 ToyRobot 클래스 성격에 맞게 구현 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ToyRobot implements Toy {
 
    @Override
    public void walk() {
        System.out.println("The robot can walk");
    }
 
    @Override
    public void run() {
        System.out.println("The robot can not run");        
    }
 
    @Override
    public void alarm() {
        System.out.println("The robot has alarm function");        
    }
 
    @Override
    public void light() {
        System.out.println("The robot has light function");                
    }
        
}
cs


Mainclass.java (실행하는 메인클래스) 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainClass {
 
    public static void main(String[] args) {
        //둘 다 Toy로 동일한 데이터 타입 가진 객체 생성 
        Toy robot = new ToyRobot();
        Toy airplane = new ToyAirplane();
        
        //객체를 참조하는 레퍼런스를 배열에 담기 
        Toy toys[] = {robot, airplane};
 
        for (int i = 0; i < toys.length; i++) {
            toys[i].walk();
            toys[i].run();
            toys[i].alarm();
            toys[i].light();
            System.out.println();
        }
    }
}
cs



실행 결과

 The robot can walk

The robot can not run

The robot has alarm function

The robot has light function


The airplane can walk

The airplane can not run

The airplane has alarm function

The robot has no light function



인터페이스의 디폴트(default) 메서드와 static 메서드 

원래는 인터페이스에 메세드 선언 시 추상 메서드 선언만 가능했으나, JDK1.8부터는 디폴트 메소드 & static 메세드 추가 가능.


 왜 필요할까? 

인터페이스가 변경 되면 인터페이스를 구현(즉 implement)하는 모든 클래스들은 변경되거나 새로 추가된 해당 메서드들을 모두 구현해야 하는 문제 있어 번거로움. 이를 해결하기 위해 디폴트 메서드와 static 메서드 등장!  


예시)

-원래 인터페이스에는 추상메소드들 선언만 해주고, 해당 인터페이스를 구현하는 클래스에서 따로 추상메소드들을 구현해야 하나, default메소드 & static 메소드들은 선언 및 구현 함께

-따라서 클래스에도 또 다시 구현 할 필요는 없음 


-예시에서 default 메소드와 static 메소드 호출방식에 유의해서 보길 바람

인터페이스에서 정의한 static메소드는 반드시 인터페이스명.메소드 형식으로 호출해야한다


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
32
33
34
35
36
37
    //Calculator 인터페이스
    public interface Calculator {
        public int plus(int i, int j);           //추상메소드 선언만  
        public int multiple(int i, int j);       //추상메소드 선언만 
        
        default int exec(int i, int j){            //default 메소드는 선언 및 구현 
            return i + j;
        }
        public static int exec2(int i, int j){   //static 메소드는 선언 및 구현 
            return i * j;
        }
    }
    
    //Calculator인터페이스를 구현한 MyCalculator클래스
    public 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;
        }
    }
 
    //인터페이스에서 정의한 static메소드는 반드시 인터페이스명.메소드 형식으로 호출해야한다.  
 
    public class MyCalculatorExam {
        public static void main(String[] args){
            Calculator cal = new MyCalculator();
            int value = cal.exec(510);  //default메소드 호출 
            System.out.println(value);
 
            int value2 = Calculator.exec2(510);  //static메소드 호출 
            System.out.println(value2);
        }
    }
cs


인터페이스의 장점

인터페이스를 사용하면 다중 상속이 가능할 뿐만 아니라 다음과 같은 장점을 가질 수 있습니다.

 

1. 대규모 프로젝트 개발 시 일관되고 정형화된 개발을 위한 표준화가 가능합니다.

2. 클래스의 작성과 인터페이스의 구현을 동시에 진행할 수 있으므로, 개발 시간을 단축할 수 있습니다.







+ Recent posts