학습 목차
- 추상 클래스
- 인터페이스
- 다형성
- 열거 자료형
- 익명 클래스
학습 개요
- 추상 클래스의 의미를 이해한다
- 생성자를 정의하는 방법과
static
필드와 메소드 및final
필드와 메소드의 의미 학습 - 객체의 생성과 초기화 및 메소드의 오버로딩 학습
- 기존 클래스를 이용해 새로운 클래스 정의하기 위한 클래스 상속 개념 이해
학습 목표
- 추상 클래스의 의미 이해
- 인터페이스의 선언과 사용법 이해
- 다형성을 응용해 프로그램 작성 가능
- 열거 자료형과 익명 클래스의 용도 설명 가능
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. 추상 클래스, 인터페이스, 클래스의 형변환
- 인터페이스와 클래스는 모두 사용자 정의형
extends
와implements
에 따라 상위/하위 자료형(부모/자식) 관계가 설정- 상위 유형의 변수는 하위 객체의 참조 값을 가질 수 있음
- 상위 유형의 변수가 가리키는 객체의 실제 유형에 따라 수행되는 메소드가 결정됨(동적 바인딩)
- 메소드 호출 시, 변수의 선언 유형으로 정하지 않음
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를 상속받는 익명 클래스를 정의하고, 동시에 객체를 생성