Home [Java 프로그래밍] 5강 - 인터페이스와 다형성
Post
Cancel

[Java 프로그래밍] 5강 - 인터페이스와 다형성

💡해당 게시글은 방송통신대학교 김희천 교수님의 'Java 프로그래밍' 강의를 개인 공부 목적으로 메모하였습니다.



학습 개요


  • 객체 지향 개념을 적절히 활용하여 클래스를 정의하려면 상속, 메소드 재정의, 메소드 오버로딩, 인터페이스의 구현, 다형성의 활용법 등을 숙지해야 함
  • 유사 객체들의 공통 행위 양식을 정의하기 위한 인터페이스 개념을 이해하고 다형성을 활용한 객체 지향 프로그래밍 기법을 익히도록 함



학습 목표


  • 추상 클래스의 의미를 설명할 수 있음
  • 인터페이스의 선언과 사용법을 설명할 수 있음
  • 다형성을 활용하는 프로그램을 작성할 수 있음
  • 열거 자료형과 익명 클래스의 용도를 설명할 수 있음
  • 내부 클래스의 의미를 설명할 수 있음



강의록


추상 클래스와 인터페이스

추상 메소드

  • 메소드 선언에 abstract 키워드를 사용
  • 몸체의 구현이 없는 형식만 존재하는 메소드
    • 반환형, 메소드 이름과 인자에 관한 선언만 존재
    • 자식 클래스에 상속될 때, 몸체의 구현이 필요함
    • 상반된 의미의 final과 함께 사용 불가
    • 추상 메소드는 추상 클래스 또는 인터페이스에서 선언되어야 함
    1
    2
    3
    4
    
      abstract public class Shape {
          // 모양이 정해지지 않았기 때문에 면적 계산 불가
          abstract public double getArea(); // 추상 메소드
      }
    

추상 클래스

  • 클래스 정의에 abstract 키워드 사용
    • 추상 메소드를 포함할 수 있음
    • 데이터 필드나 일반 메소드를 포함할 수 있음
    • 객체 생성을 할 수 없음
      • 구체적이지 못한 불완전한 클래스라는 의미
      • ex) Shape이 추상 클래스라 가정

        1
        
          Shape s = new Shape("red"); // 컴파일 오류 발생
        

추상 클래스의 사용

  • 의미적으로 유사한 클래스(자식 클래스)를 묶고자 할 때 사용
    • 공통으로 사용할 데이터 필드와 메소드를 추상 클래스에서 정의
  • 추상 클래스는 불완전한 클래스
    • 기능적으로 구현하기 어려운 메소드가 존재
  • 추상 클래스는 자식 클래스로 상속되어 사용됨
    • 자식 클래스에서 추상 메소드를 구현해야 함
    • 그러면 자식 클래스는 객체 생성이 가능
    • 자식 클래스가 추상 메소드를 구현하지 않으면 계속해서 자식 클래스도 추상 클래스이어야 함
  • 추상 클래스는 일반 클래스와 인터페이스의 중간적 성격을 가짐

인터페이스

  • 100% 추상적 클래스
    • 인터페이스의 모든 메소드가 추상 메소드(public abstract)
  • 몸체 없이 구현되어야 할 메소드의 형식만 정의해 둠
    • 단, default 인스턴스 메소드와 static 메소드는 몸체를 구현함
      • 모든 메소드의 기본 접근 제어자는 public
    • 데이터 필드는 클래스 상수만 가능
      • public static final
  • 참조형이며 직접적 객체 생성 불가
  • 인터페이스의 이름은 보통 형용사
    • Runnable
    • Serializable
    • Comparable

인터페이스의 정의

  • 문법은 클래스 정의와 유사함
  • 정의할 때 키워드 class 대신에 interface를 사용
    • abstract는 생략하는 것이 일반적임
  • 메소드의 접근 제어자는 기본적으로(생략하더라도) public abstract
    • 몸체가 없으며, 반환형, 이름, 매개변수 목록만 표시
  • default 인스턴트 메소드와 static 메소드도 가능
    • 이 경우 몸체 구현해야 함
    • 기본적으로(생략하더라도) 접근 제어자는 public
  • 데이터 필드는 항상(생략 가능) public static final
    • 클래스 상수만 가능함

인터페이스의 사용

  • 추상 클래스와 마찬가지로 자식 클래스에 상속(구현)되어 사용됨
    • 인터페이스를 상속(부모 인터페이스를 구현)받은 자식 클래스는 모든 추상 메소드를 구현해주어야함
  • 의미적으로는 관련이 없으나 기능적으로 유사한 클래스들을 묶을 때 인터페이스를 사용할 수 있음
    • ex) 대소 비교가 가능한 클래스(사각형, 사람…)
  • 인터페이스를 상속 받아 자식 인터페이스를 정의할 수 있음
    • 인터페이스의 상속(또는 확장)
1
2
3
4
interface Comparable<T> {
    //다른 객체와 크기를 비교하는 메서드
    boolean isLargerThan(T o);
}
1
2
3
4
5
6
7
class Box implements Comparable<Box> {
    private int length, width, height;
    
    public boolean isLargerThan(Box otherBox) {

    }
}

인터페이스의 상속

  • 자식 인터페이스가 부모 인터페이스를 상속 받는 경우
    • 기존 인터페이스를 상속 받아 인터페이스를 정의할 때, 키워드 extends사용

      1
      
        interface 자식 인터페이스 extends 부모 인터페이스 { }
      
    • 여러 부모 인터페이스를 상속 받는 다중 상속 가능

    1
    2
    3
    4
    
      interface SuperInterface {
          public void func1();
          public void func2();
      }
    
    1
    2
    3
    
      interface SubInterface extends SuperInterface {
          public void func3();
      }
    

인터페이스의 구현

  • 자식 클래스가 부모 인터페이스를 상속 받는(구현 하는) 경우
    • 자식은 부모가 나열한 기능(추상 메소드)을 구현해야 함
    • 구현을 통해 클래스를 정의할 때 implements를 사용

      1
      2
      3
      
        class 자식클래스 extends 부모클래스 implements 부모인터페이스1, 부모인터페이스2 {
              
        }
      
    1
    2
    3
    4
    
      interface Movable {
          void add(double dx, double dy);
          void sub(double dx, double dy);
      }
    
    1
    2
    3
    4
    
      interface Scalable {
          void mul(double s);
          void div(double s);
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
      class Point implements Movable, Scalable {
        
          @Override
          public void add(double dx, double dy) {
    
          }
    
          @Override
          public void sub(double dx, double dy) {
    
          }
    
          @Override
          public void mul(double s) {
    
          }
    
          @Override
          public void div(double s) {
    
          }
      }
    

인터페이스의 구현의 예

1
2
3
interface Figure {
    double getArea();
}
1
2
3
4
5
6
7
8
9
10
11
12
class Triangle implements Figure {
    private double height, width;
    
    public Triangle(double h, double w) {
        height = h;
        width = w;
    }
    
    public double getArea() {
        return height * width * 0.5;
    }
}
1
2
3
4
5
6
7
8
9
public class Main {
    public static void main(String args[]) {
        Triangle t = new Triangle(3.0, 4.0);
        
        System.out.println(t.getArea());
    }
}

// 6.0

디폴트 메소드

  • 인터페이스에서 선언하는 메소드에 기본 구현(몸체)을 넣을 수 있음
    • 자식 클래스에서 상속 받을 때, 디폴트 메소드를 그대로 사용할 수 있음
    • 메소드 선언 시 default를 사용하고 몸체를 구현해 줌
  • 인터페이스에 새 메소드를 추가할 때, 기존 코드(클래스 정의)의 수정을 피하기 위함
    • 단순히 추상 메소드가 추가된다면, 이전 인터페이스를 구현한 클래스를 수정해야 함
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      interface DoIt {
          void doSomething(); // 추상 메소드
    
          int doSomethingElse(String s); // 추상 메소드
            
          // 아래를 새로 추가한다면?
          default boolean didItWork(int i, String s) {
              return true;
          }
      }
    

추상 클래스, 인터페이스, 클래스의 형변환

  • 인터페이스와 클래스는 모두 사용자 정의형
  • extendsimplements에 따라 상위/하위 자료형(부모/자식) 관계가 설정 됨
  • 상위 유형의 변수는 하위 객체의 참조 값을 가질 수 있음
  • 상위 유형의 변수가 가리키는 객체의 실제 유형에 따라 수행되는 메소드가 결정됨(동적 바인딩)
    • 실행 중 메소드 호출 시, 변수의 선언 유형으로 정하지 않음
    1
    2
    3
    
        
      SuperClass super = new SubClass(); // 자동 형 변환 upcasting
      super.method(); // method()를 SubClass에서 찾음
    
    • 컴파일할 때는 method()가 sup의 선언 유형에 정의되어 있는지 확인함

다형성

다형성

  • 유사하지만 다양한 형상이나 다양한 기능을 가진다는 뜻
    • 한 부모에서 나온 두 자식 객체는 비슷하지만 다름
    • 하나의 클래스에서 오버로딩된 메소드들은 유사하지만 조금씩 다른 기능을 수행함
    • 자식 클래스에서 재정의된 메소드는 부모의 것과 유사하지만 다른 기능을 수행함

다형성과 형변환

  • 형변환
    • 상속 관계에 있는 클래스 간에는 타입 변환이 가능함
      • 전혀 다른 두 클래스 간에는 타입 변환이 금지됨
    • 자식(하위) 클래스에서 부모(상위) 클래스로의 형 변환은 문제 없음
      • 업캐스팅이라 하며 자동으로 형 변환 가능함
      • 참조형 변수는 같은 유형 또는 자식 유형의 객체를 참조할 수 있음
      1
      
        Animal animal = (Animal) new Dog(); // 하위 객체 참조
      
  • 다형성의 활용 효과
    • 코드의 유연성과 재 사용성
    • 동적 바인딩을 통해 실제 유형을 명시적으로 다룰 필요가 없음

다형성과 오버라이딩

  • 클래스의 다형성
    • 부모 클래스로부터 상속받은 메소드를 자식 클래스에서 오버라이딩할 수 있음
    • 부모와 자식에서 같은 이름의 메소드가 다른 기능을 수행
      • 메소드 이름, 매개 변수, 반환형은 같으나 몸체의 구현이 다름
    • 서로 다른 자식 간에도 같은 이름의 메소드가 다른 기능을 수행
  • 인터페이스의 다형성
    • 자식 클래스들에서 상위 인터페이스의 메소드를 다르게 구현함

클래스 상속과 다형성 사용 예시

  • ex)

    1
    2
    3
    4
    5
    
      class A {
          public void func() {
              System.out.println("A");
          }
      }
    
    1
    2
    3
    4
    5
    
      class B extends A {
          public void func() {
              System.out.println("B");
          }
      }
    
    1
    2
    3
    4
    5
    
      class C extends B {
          public void func() {
              System.out.println("C");
          }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      public class Main {
          public static void main(String args[]) {
              A a = new B();
              a.func(); // B 클래스의 func 메소드 호출
              a = new C();
              a.func(); // C 클래스의 func 메소드 호출
          }
      }
        
      // B
      // C
    
  • ex)

    1
    2
    3
    4
    5
    6
    7
    8
    
      class Employee {
          int nSalary;
          String szDept = null;
        
          public void doJob() {
              System.out.println("Do something");
          }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      class Sales extends Employee {
          public Sales() {
              szDept = "Sales Dept"; 
          }
            
          public void doJob() {
              System.out.println("Do sales");
          }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      class Development extends Employee {
          public Development() {
              szDept = "Sales Dept";
          }
            
          public void doJob() {
              System.out.println("Do development");
          }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      public class Main {
          public static void main(String args[]) {
              Employee emp1, emp2; // 클래스 객체 변수 정의
        
              emp1 = new Sales();
              emp2 = new Development();
        
              emp1.doJob(); // Employee의 doJob 메소드 존재 확인 후 Sales 클래스의 doJob 메소드 호출
              emp2.doJob(); // Development 클래스의 doJob 메소드 호출
          }
      }
        
      // Do sales
      // Do development
    

열거 자료형

열거형

  • 미리 정의된 상수 값의 집합을 만들기 위한 자료형
    • enum을 사용하여 정의
    • 열거형으로 선언된 변수에는 미리 지정된 값만 대입 가능
    • 상수 값을 배열로 리턴하는 static 메소드인 values()를 제공
    1
    2
    3
    
      enum Day { // Enum 유형 Day
          SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    
      public class Main {
          public static void main(String[] args) {
              Day day = Day.MONDAY;			
              for (Day d : Day.values()) {
                  System.out.println(d); // 각 원소 출력
              }
          }
      }
    

열거형 정의 하기

  • 열거형 정의에 필드와 메소드를 포함할 수 있음
  • 상수 선언이 필드나 메소드보다 먼저 정의되어야 하며 이때 세미콜론(;)으로 끝나야 함
  • 열거형에서 상수 값은 마치 하나의 객체와 같음
    • 열거형이름.상수값의 형식을 사용
    • 열거형이름.상수값은 그 자체가 열거형의 인스턴스
  • 생성자는 열거형과 같은 이름을 가지며 접근 제어자는 생략 또는 private이어야 함
  • 열거형의 생성자는 열거형 상수(객체)에 대한 초기화를 수행함
    • 생성자는 상수가 사용될 때 한꺼번에 자동 호출 됨
  • 열거형 사용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      enum BaseballTeam {
          LG(40, 30), SS(30, 40), KT(20, 50),	SK(35, 35), NC(55, 15); // 상수 값 선언
        
          private final int win; // 데이터 필드 선언
          private final int lose;
        
          private BaseballTeam(int win, int	lose) { // 생성자
              this.win = win; 
              this.lose = lose;
          }
        
          public double winsRate() {
              return (win * 100.0) / (win + lose);
          }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      public class Main {
          public static void main(String args[]) {
              BaseballTeam bt = BaseballTeam.LG;
        
              System.out.println(bt.winsRate());
          }
      }
        
      // 57.142857142857146
    

익명 클래스

익명 클래스

  • 일회성으로 1개의 객체를 생성하기 위한 클래스
    • 익명 클래스 정의와 동시에 객체 생성할 수 있음
  • 부모 클래스를 상속 받거나 인터페이스를 구현하도록, 익명 클래스를 정의함
  • 익명 클래스의 정의 방법
    • 아래에서 중괄호가 익명 클래스의 몸체가 됨
    • 부모 클래스를 상속 받는 (익명 서브 클래스) 객체를 생성할 때

      1
      2
      3
      
        new 슈퍼클래스 () {
              
        } // 슈퍼클래스의 자식 객체 생성
      
    • 인터페이스를 구현 하는 (익명 구현 클래스) 자식 객체를 생성할 때

      1
      2
      3
      
        new 인터페이스 () {
              
        } // 인터페이스를 구현하는 자식 객체 생성
      

익명 서브 클래스의 객체 생성

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 Main {
    public static void main(String args[]) {
        CSuper sub = new CSuper() { // CSuper를 상속 받는 익명 클래스 정의 후 객체 생성
            public int b = 20;
            
            public void method1() {
                System.out.println("sub1"); 
            }
            
            public void method3() {
                System.out.println("sub3"); 
            }
    };
    
    sub.method1(); // CSuper를 상속 받는 하위 클래스의 객체를 가리킴
    sub.method2(); // CSuper를 상속 받는 하위 클래스의 객체에 method2가 없어 부모 클래스에서 호출
    
    System.out.println(sub.a); // sub의 선언 유형인 CSuper에서 데이터 필드 호출
    
    // sub.method3(); 컴파일 오류
    // System.out.println(sub.b); 컴파일 오류
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CSuper {
    public int a = 10;
    
    public void method1() {
        System.out.println("super1");
    }
    
    public void method2() {
        System.out.println("super2");
    }
}

// sub1
// super2
// 10

익명 구현 클래스의 객체 생성

1
2
3
interface MyInterface {
    public void method(); // 추상 메소드
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
    public static void main(String args[]) {
        MyInterface sub = new MyInterface() {
            public void method() {
                System.out.println("sub1");
            }
        };

        sub.method();
    }
}

// sub1

중첩 클래스

중첩 클래스

  • 외부 클래스 정의 내부에서 정의된 또 다른 클래스
    • 내부(inner) 클래스라고 하며, 외부(outer) 클래스의 멤버가 됨
      • 논리적 그룹화를 위한 것
    • 내부 클래스는 보통의 클래스와 다르게 private 또는 protected 클래스가 될 수 있음
    • 일반적으로 내부 클래스는 외부 클래스의 필드와 관련된 작업을 처리
  • non-static 중첩 클래스
    • 외부 클래스의 객체가 생성된 이후 사용 가능
      • 외부 클래스의 객체와 연관
    • 객체 생성 방법은 외부클래스객체변수.new 내부 클래스()
    • 메소드는 this 외에 외부 클래스 객체의 참조(외부클래스.this)를 가지고 있음
    • 외부 클래스의 모든 멤버에 접근할 수 있음
  • static 중첩 클래스
    • 외부 클래스의 객체 생성과 무관하게 사용 가능
    • 외부 클래스의 정적 멤버에 접근할 수 있음

중첩 클래스에서 같은 이름의 필드 참조하기

1
2
3
4
5
6
7
8
9
10
11
12
13
class OuterClass {
    public int x = 0;
    
    class InnerClass {
        public int x = 1;
        
        void methodInnerClass(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("OuterClass.this.x = " + OuterClass.this.x);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
    public static void main(String args[]) {
        OuterClass oc = new OuterClass();
        OuterClass.InnerClass ic = oc.new InnerClass();
        
        ic.methodInnerClass(2);
    }
}

// x = 2
// this.x = 1
// OuterClass.this.x = 0

내부 클래스의 사용

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import java.util.Iterator;

/**
 * 배열의 짝수 인덱스 요소들을 반복적으로 접근하기 위한 반복자 패턴 구현
 */
public class NestedClassTest {
    // 배열의 크기를 상수로 정의
    private final static int SIZE = 15;
    // 데이터를 저장할 배열 선언
    private int[] data = new int[SIZE];

    /**
     * 배열을 초기화하여 인덱스 값을 배열 요소에 저장
     */
    public NestedClassTest() {
        for (int i = 0; i < SIZE; i++)
            data[i] = i;
    }

    /**
     * 짝수 인덱스 요소들을 출력하는 메서드
     * 내부 클래스인 EvenIterator를 생성하여 짝수 인덱스의 요소들에 접근
     */
    public void printEven() {
        // 내부 클래스의 인스턴스 생성
        EvenIterator iterator = this.new EvenIterator();

        // 반복자를 사용하여 다음 요소가 있는 동안 반복
        while (iterator.hasNext())
            System.out.print(iterator.next() + " ");
        System.out.println();
    }

    /**
     * 짝수 인덱스 요소에만 접근하기 위한 내부 클래스(Inner Class)
     * 외부 클래스의 private 멤버(data 배열)에 직접 접근 가능
     */
    private class EvenIterator implements Iterator<Integer> {
        // 다음 접근할 인덱스를 저장 (0부터 시작)
        private int nextIndex = 0;

        /**
         * 다음 요소가 있는지 확인하는 메서드
         * 접근할 수 있는 요소가 남아있으면 true, 그렇지 않으면 false 반환
         */
        public boolean hasNext() {
            return (nextIndex <= SIZE - 1);
        }

        /**
         * 다음 요소를 반환하고 내부 인덱스를 2 증가시키는 메서드
         * 2씩 증가시켜 짝수 인덱스만 접근
         */
        public Integer next() {
            // 현재 인덱스 위치의 데이터를 Integer 객체로 변환
            Integer ret = Integer.valueOf(data[nextIndex]);
            // 다음 짝수 인덱스를 가리키도록 2 증가
            nextIndex += 2;

            return ret;
        }
    }

    public static void main(String args[]) {
        // NestedClassTest 객체 생성
        NestedClassTest nc = new NestedClassTest();
        // 짝수 인덱스 요소들 출력 (0, 2, 4, 6, 8, 10, 12, 14의 값)
        nc.printEven();
    }
}

// 0 2 4 6 8 10 12 14



학습 정리


  • 몸체가 없는 메소드를 추상 메소드라고 하며, 추상 클래스 또는 인터페이스에 포함될 수 있음
  • 인터페이스는 추상 메소드로만 구성됨
    • 단, default 인스턴스 메소드와 static 메소드는 몸체가 있어야 함
  • 의미적으로 유사한 클래스를 묶을 때는 추상 클래스로, 기능적으로 유사한 클래스를 묶을 때는 인터페이스를 사용함
  • 다형성은 메소드 오버라이딩과 오버로딩, 클래스 간 상속과 형변환, 인터페이스의 구현과 형변환, 메소드 동적 바인딩을 통해 구현될 수 있음
  • 열거 자료형은 여러 상수 값을 미리 정의하기 위한 자료형이며, 각 상수 값은 하나의 객체와 같음
  • 익명 클래스는 이름이 없는 클래스로, 일회성으로 상속 또는 구현을 통해 자식 객체를 생성하는 용도로만 사용되는 클래스를 의미 함



연습 문제


  1. 다음 프로그램을 실행했을 때 예상되는 출력은?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
     class A { 
         public void func() { 
             System.out.print("a"); 
         }
     }
        
     class B extends A {
         public void func() {
             System.out.print("b");
         }
     }
        
     class C extends B {
     }
        
     public class PolymorphTest{
         public static void main(String args[]) {
             A a = new C();
             a.func();
         }
     }
    

    a. B

  2. 밑줄 친 ㄱ에 ㄴ에 들어갈 키워드는 순서대로 무엇인가?

    1
    2
    3
    
     interface Able { }
     interface B /** ㄱ **/ Able { }
     class C /** ㄴ  **/ Able { }
    

    a. implements, extends

  3. 밑줄 친 부분의 의미를 정확히 설명하시오. 단, CSuper는 클래스 이름이다.

    1
    
     CSuper sub = new CSuper() {  } ;
    

    a. CSuper 클래스를 상속받는 익명 클래스를 정의하고, 동시에 익명 클래스의 객체를 생성한다.

[데이터베이스 시스템] 4강 - SQL

[파이썬 프로그래밍 기초] 5강 - 순차 구조