학습 개요
- 객체 지향 개념을 적절히 활용하여 클래스를 정의하려면 상속, 메소드 재정의, 메소드 오버로딩, 인터페이스의 구현, 다형성의 활용법 등을 숙지해야 함
- 유사 객체들의 공통 행위 양식을 정의하기 위한 인터페이스 개념을 이해하고 다형성을 활용한 객체 지향 프로그래밍 기법을 익히도록 함
학습 목표
- 추상 클래스의 의미를 설명할 수 있음
- 인터페이스의 선언과 사용법을 설명할 수 있음
- 다형성을 활용하는 프로그램을 작성할 수 있음
- 열거 자료형과 익명 클래스의 용도를 설명할 수 있음
- 내부 클래스의 의미를 설명할 수 있음
강의록
추상 클래스와 인터페이스
추상 메소드
- 메소드 선언에
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
- 단, default 인스턴스 메소드와
- 참조형이며 직접적 객체 생성 불가
- 인터페이스의 이름은 보통 형용사
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; } }
추상 클래스, 인터페이스, 클래스의 형변환
- 인터페이스와 클래스는 모두 사용자 정의형
extends
와implements
에 따라 상위/하위 자료형(부모/자식) 관계가 설정 됨- 상위 유형의 변수는 하위 객체의 참조 값을 가질 수 있음
- 상위 유형의 변수가 가리키는 객체의 실제 유형에 따라 수행되는 메소드가 결정됨(동적 바인딩)
- 실행 중 메소드 호출 시, 변수의 선언 유형으로 정하지 않음
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
클래스가 될 수 있음 - 일반적으로 내부 클래스는 외부 클래스의 필드와 관련된 작업을 처리
- 내부(inner) 클래스라고 하며, 외부(outer) 클래스의 멤버가 됨
- 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
메소드는 몸체가 있어야 함
- 단, default 인스턴스 메소드와
- 의미적으로 유사한 클래스를 묶을 때는 추상 클래스로, 기능적으로 유사한 클래스를 묶을 때는 인터페이스를 사용함
- 다형성은 메소드 오버라이딩과 오버로딩, 클래스 간 상속과 형변환, 인터페이스의 구현과 형변환, 메소드 동적 바인딩을 통해 구현될 수 있음
- 열거 자료형은 여러 상수 값을 미리 정의하기 위한 자료형이며, 각 상수 값은 하나의 객체와 같음
- 익명 클래스는 이름이 없는 클래스로, 일회성으로 상속 또는 구현을 통해 자식 객체를 생성하는 용도로만 사용되는 클래스를 의미 함
연습 문제
다음 프로그램을 실행했을 때 예상되는 출력은?
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
밑줄 친 ㄱ에 ㄴ에 들어갈 키워드는 순서대로 무엇인가?
1 2 3
interface Able { } interface B /** ㄱ **/ Able { } class C /** ㄴ **/ Able { }
a.
implements
,extends
밑줄 친 부분의 의미를 정확히 설명하시오. 단, CSuper는 클래스 이름이다.
1
CSuper sub = new CSuper() { } ;
a. CSuper 클래스를 상속받는 익명 클래스를 정의하고, 동시에 익명 클래스의 객체를 생성한다.