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

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

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



학습 목차


  1. 추상 클래스
  2. 인터페이스
  3. 다형성
  4. 열거 자료형
  5. 익명 클래스

학습 개요


  • 추상 클래스의 의미를 이해한다
  • 생성자를 정의하는 방법과 static 필드와 메소드 및 final 필드와 메소드의 의미 학습
  • 객체의 생성과 초기화 및 메소드의 오버로딩 학습
  • 기존 클래스를 이용해 새로운 클래스 정의하기 위한 클래스 상속 개념 이해

학습 목표


  1. 추상 클래스의 의미 이해
  2. 인터페이스의 선언과 사용법 이해
  3. 다형성을 응용해 프로그램 작성 가능
  4. 열거 자료형과 익명 클래스의 용도 설명 가능



1. 추상 클래스


1-1. 추상 메소드

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

1-2. 추상 클래스

  • 클래스 정의에 abstract 키워드 사용
    • 데이터 필드나 일반 메소드 포함 가능
    • 객체 생성 불가
      • 구체적이지 못한 불완전한 클래스라는 의미
    • 추상 메소드를 포함하는 클래스는 반드시 추상 클래스
    1
    2
    3
    
      abstract public class Shape { // 추상 클래스
      	abstract public double getArea(); // 추상 메소드
      }
    
    1
    
      Shape s = new Shape("red"); // 컴파일 오류 발생
    

1-3. 추상 클래스의 사용

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



2. 인터페이스


2-1. Java의 인터페이스

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

2-2. 인터페이스의 정의

  • 문법은 클래스 정의와 유사
  • 정의할 때 키워드 class 대신에 interface를 사용
    • abstract 보통 생략
  • 메소드는 기본적으로(생략하더라도) public abstract
    • 몸체가 없으며, 반환형, 이름, 매개 변수 목록만 표시
  • default 메소드와 static 메소드도 가능
    • 몸체 구현 필요
    • 기본적으로(생략하더라도) public
  • 데이터 필드는 항상(생략 가능) public static final
    • 클래스 상수만 가능

2-3. 인터페이스의 사용

  • 추상 클래스와 마찬가지로 자식 클래스에 상속되어 사용됨
    • 인터페이스를 상속(부모 인터페이스를 구현)받은 자식 클래스는 모든 추상 메소드를 구현해주어야함
  • 의미적으로는 관련이 없으나 기능적으로 유사한 클래스들을 묶을 때 인터페이스 사용 가능
    ex) 대소 비교가 가능한 객체들의 자료형을 묶을 때
  • 인터페이스를 상속받아 자식 인터페이스 정의 가능
    • 인터페이스의 상속(또는 확장)

2-4. 인터페이스의 상속

  • 자식 인터페이스가 부모 인터페이스를 상속받는 경우
    • 인터페이스를 상속받아 인터페이스를 정의할 때, 키워드 extends 사용
    • 여러 인터페이스를 상속 받는 다중 상속 가능
      1
      
        interface 자식 인터페이스 extends 부모 인터페이스 { }
      

2-5. 인터페이스의 구현

  • 자식 클래스가 부모 인터페이스를 상속받는 경우
    • 자식은 부모가 나열한 기능(추상 메소드)을 구현해야함
    • 구현을 통해 클래스를 정의할 때 implements를 사용
      1
      2
      3
      4
      
        class MovablePoint implements Movable { }
        class 자식클래스 extends 부모클래스  
        	implements 부모인터페이스1, 부모인터페이스2 { } 
        // 부모 클래스를 상속받아 부모인터페이스를 상속받아 자식 클래스 몸체 구현
      
  • 인터페이스 구현
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
      interface Movable {
      	void moveUp(); // public 추상 메소드
      	void moveDown();
      	void moveLeft();
      	void moveRight();
      }
        
      public class MovableTest {
          public static void main(String[] args) {
              Movable m1 = new MovablePoint(5, 5);
              System.out.println(m1); // m1.toString 자동 호출
              m1.moveUp(); // y 좌표 증가
              System.out.println(m1);
              m1.moveRight(); // x 좌표 증가
              System.out.println(m1);
          }
      }
      // 출력 결과
      // Point at (5, 5)
      // Point at (5, 6)
      // Point at (6, 6)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
      class MovablePoint implements Movable { // Movable 인터페이스 상속 받아 클래스 생성
      	private int x, y; // 데이터 필드
      	public MovalePoint(int x, int y) { // 생성자에서 x, y 좌표 초기화
      		this.x = x;
      		this.y = y;
      	}
        	
      	public String toString() {
      		return "Point at (" + x + "," + y + ")";
      	}
        
      // Movable 인터페이스의 추상 메소드 몸체 구현
      	public void moveUp() { y++; } 
      	public void moveDown() { y--; }
      	public void moveLeft() { x--; }
      	public void moveRight() { x++; }
      }
    

2-7. 디폴트 메소드

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

2-8. 추상 클래스, 인터페이스, 클래스의 형변환

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



3. 다형성


3-1. 다형성

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

3-2. 다형성과 형변환

  • 형변환
    • 상속 관계에 있는 클래스 간에는 타입 변환이 가능
      • 전혀 다른 두 클래스 간에는 타입 변환이 금지됨
    • 하위 클래스에서 상위 클래스로의 형 변환은 문제 없음
      • 업캐스팅이라 하며 자동으로 형 변환 가능
      • 참조형 변수는 같은 유형의 객체 또는 하위 객체를 참조할 수 있음
        1
        
          Animal animal = (Animal) new Dog(); // 하위 객체 참조
        

3-3. 다형성과 오버라이딩

  • 클래스의 다형성
    • 부모 클래스로부터 상속받은 메소드를 자식 클래스에서 오버라이딩 가능
    • 부모와 자식에서 같은 이름의 메소드가 다른 기능을 수행
      • 같은 이름과 매개 변수 및 반환형을 가지나 몸체가 다름
  • 인터페이스의 다형성
    • 자식 클래스들에서 상위 인터페이스의 메소드를 다르게 구현
  • 클래스 상속과 다형성 사용 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      class A {
      	public void func( ) {
      		System.out.println("a");
      	}
      }
      class B extends A {
      	public void func( ) {
      	System.out.println("b");
      	}
      }
      class C extends B {
      	public void func( ) {
      	System.out.println("c");
      	}
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      public class PolymorphTest {
      	public static void main(String args[ ])
      {
      		A a = new B( );
      		a.func( ); // B 클래스의 func 메소드 호출
      		a = new C( );
      		a.func( ); // C 클래스의 func 메소드 호출
      	}
      }
      // 출력 결과
      // b
      // c
    
  • 클래스 상속과 다형성 사용 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
      class Employee {
      	int nSalary;
      	String szDept = null;
        
      	public void doJob( ) {
      		System.out.println("Do something");
      	}
      }
        
      class Sales extends Employee {
      	public Sales( ) { szDept = "Sales Dept"; }
        	
      	public void doJob( ) {
      		System.out.println("Do sales");
      	}
      }
        
      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 Company1 {
      	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
    



4. 열거 자료형


4-1. 열거형 정의

  • 열거형은 미리 정의된 상수 값을 만들기 위한 자료형
  • enum을 사용하여 정의
  • 열거형으로 선언된 변수에는 미리 지정된 값만 대입 가능
  • 상수 값을 배열로 리턴하는 static 메소드로 values() 제공

    1
    2
    3
    
      enum Day { // Enum 유형 Day
      	SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
      }
    
    1
    2
    3
    4
    5
    
      // main 함수에서
      Day day = Day.MONDAY;
      for (Day d : Day.values( )) {
      	System.out.println(d); // 각 원소 출력
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
      public class EnumDay {
      	enum Day { // Enum 유형 Day
      		SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
      	}
        	
      	public static void main(String[] args) {
      		Day day = Day.MONDAY;
      		System.out.println(day);
        
      		for (Day d : Day.values( )) {
      			System.out.println(d); // 각 원소 출력
      		}
      	}
      }
        
      // 출력 결과
      // MONDAY
      // SUNDAY
      // MONDAY
      // TUESDAY
      // WEDNESDAY
      // THURSDAY
      // FRIDAY
      // SATURDAY
    

4-2. 열거형의 생성자와 메소드

  • 상수 선언이 필드나 메소드보다 먼저 정의되어야 하며 세미콜론(;)으로 끝나야함
  • 열거형 정의에 필드와 메소드를 포함 가능
  • 생성자는 열거형과 같은 이름을 가지며 접근 제어자는 생략 또는 private이어야함
  • 열거형에서 상수 값은 마치 하나의 객체와 같음
  • 열거형의 생성자는 상수 값을 설정(객체 생성)할 때 자동 호출 됨
  • 열거형 사용
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
      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);
      	}
      }
        
      public class EnumTest2 {
      	public static void main(String args[ ]) {
      		BaseballTeam bt = BaseballTeam.LG;
        
      		System.out.println(bt.winsRate( ));
      	}
      }
    



5. 익명 클래스


5-1. 익명 클래스

  • 일회성으로 1개의 객체를 생성하기 위한 클래스
    • 클래스 정의와 동시에 객체 생성 가능
  • 슈퍼 클래스를 상속받거나 인터페이스를 구현하도록 익명 클래스 정의
    1
    2
    
      new 슈퍼클래스 ( ) {부모가 클래스인 익명 클래스 정의} // 슈퍼클래스의 자식 객체 생성
      new 인터페이스 ( ) {부모가 인터페이스인 익명 클래스 정의} // 인터페이스를 구현하는 자식 객체 생성
    
    • 중괄호가 익명 클래스의 몸체
    • 단독 선언 없이 부모 클래스, 부모 인터페이스 상속하여 사용
  • 클래스를 상속받는 익명 클래스
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
      public class AnonymousTest {
      		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에서 데이터 필드 호출
      			... ...
      	}
      }
      // 출력 결과
      // super1
      // super2
      // 10
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      class CSuper {
      	public int a = 10;
      	public void method1() {
      		System.out.println("super1");
      	}
      	public void method2() {
      		System.out.println("super2");
      	}
      }
    
  • 인터페이스를 구현한 익명 클래스
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      public class AnonymousTest {
      	public static void main(String args[]) {
      		MyInterface sub = new MyInterface( ) {
      			public void method( ) {
      				System.out.println("sub1");
      			}
      		};
        
      		sub.method( );
      	}
      }
      // 출력 결과
      // sub1
    
    1
    2
    3
    
      interface MyInterface {
      	public void method( ); // 추상 메소드
      }
    



학습 정리


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



연습 문제


Q1.

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
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( );
  }
}

// 다음 프로그램을 실행했을 때 예상되는 출력은?
// 1. a
// 2. b
// 3. 컴파일 오류
// 4. 실행 오류

// 2. b

Q2.

1
2
3
4
5
6
7
8
9
10
11
interface Able { }
interface B /** ㄱ **/ Able { }
class C /** ㄴ  **/ Able { }

// 밑줄 친 ㄱ에 ㄴ에 들어갈 키워드는 순서대로 무엇인가?
// 1. ㄱ: extends    ㄴ: extends
// 2. ㄱ: extends    ㄴ: implements
// 3. ㄱ: implements    ㄴ: implements
// 4. ㄱ: implements    ㄴ: extends

// 2. ㄱ: extends    ㄴ: implements

Q3.

1
2
3
4
CSuper sub = new CSuper( ) {  } ;
// 아래 밑줄 친 부분의 의미를 정확히 설명하시오.

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