수업 시작에 앞서 상속과 인터페이스에 대하여...
자바는 프로그래밍언어 중 비교적 늦게 탄생한 언어로, 다른 언어들의 단점을 지켜보고 보완하여 개발하였다.
C++과 같은 다른 언어에서는 다중 상속을 허용한다. 다중 상속을 허용하면 여러 상위 클래스의 특징을 가져올 수 있어서 사용하기에는 편리하지만 클래스의 정체성을 잃어버리기 쉬워 혼란을 초래하는 단점이 있다.
그래서 자바는 클래스의 상속을 단일 상속으로 제한하였다. 단일 상속은 클래스를 본래의 용도에 맞게 설계할 수 있지만 획일적인 구성밖에 안되는 단점이 있다. 자바는 이 단점을 해결하기 위해 인터페이스의 다중 상속을 허용한다.
자바의 상속은 단일 상속으로 심플하게 구성하지만 인터페이스를 통해 다중 상속하고 그것을 구현하여 단일 상속의 단점을 보완한다.
정리하면,
클래스 - 클래스 : 단일 상속(extends)만 가능
클래스 - 인터페이스 : 여러 인터페이스 구현(implements) 가능
인터페이스 - 인터페이스 : 다중 상속(extends) 가능
'인터페이스'가 중요한 이유
인터페이스의 형태로(추상적인 형태로) 쪼개놓고 적시에 구현하여 사용함으로써객체 지향의 철학인 분리하고, 쪼개는 행위를 원활하도록 도와주고 클래스 간의 의존성을 약화시켜준다.
* 인터페이스(모든 메소드가 추상적) - 추상클래스(일단 추상적) - 일반적인 클래스(구체적)
인터페이스(Interface)
클래스를 위한 템플릿으로써의 기능을 수행하는 추상 클래스의 한 종류이다. (추상 클래스이므로 구현을 통해 설계된 클래스와 인터페이스 간의 형변환이 가능하다.)
- 추상 클래스와 비슷하지만, 설계도 내부가 완전히 미완성된 채로 남겨져있다. 추상클래스보다 더 추상적이다. 인터페이스 안에 존재하는 그 어떤 메소드도 몸체(정의부, 구현부)가 없기 때문에 사실상 실행 부분이 존재하지 않는다. 인터페이스를 구성하는 메소드는 모두 추상 메소드여야만 한다.
인터페이스의 선언
인터페이스는 클래스의 내부 구현을 제외한 참조형만 선언한 것이므로 메소드를 선언만 하고 정의는 할 수 없다. 또한 클래스에서의 변수는 값이 변할 수 있지만 인터페이스에서의 변수는 상수처럼 값이 바뀔 수 없기 때문에 선언 시에 미리 값을 할당해 놓아야 한다.
인터페이스의 구현
인터페이스는 클래스를 위한 템플릿이기 때문에 사용 가능한 인터페이스가 되기 위해서는 자바 프로그램에서 인터페이스를 구현해 주어야 하는데 이러한 기능을 하는 것이 『implements』 예약어이다.
클래스가 인터페이스를 implements 하면 인터페이스 내부의 모든 메소드를 정의(Overriding, 재정의)해야만 사용할 수 있다.
- 인터페이스를 구현하는 클래스는 인터페이스의 상위 인터페이스가 제공하는 추상 메소드를 포함한 모든 메소드를 구현하지 않을 경우 추상(abstract) 클래스로 선언해야 한다.
인터페이스의 형식
- 인터페이스는 추상 클래스의 일종으로, 메소드를 선언만 하고 정의는 없다.
- 인터페이스를 implements 할 경우 반드시 하위 클래스(구현하는 클래스)는 인터페이스의 모든 메소드를 오버라이딩(Overriding)해야 한다.
- 인터페이스는 자동적으로 다음과 같이 처리된다.
* 멤버 변수 : public static final == 상수
* 메소드 : public abstract == 추상메소드
- 다중 상속은 콤마(『,』)로 구분되며 여러 개의 인터페이스를 상속할 수 있다.
- 인터페이스끼리 상속할 경우는 『extends』 키워드를 사용한다. 클래스와 달리 다중 상속이 가능하다.
인터페이스 특징
- 인터페이스는 인터페이스 자체도 상속 가능하며 클래스와 달리 다중 상속이 가능하다.
기존의 c++ 언어 등에서 지원되는 다중 상속이 사용 과정에서 많은 문제점을 노출시켰기 때문에 자바에서는 다중 상속의 개념을 인터페이스라는 개념으로 변형하여 인터페이스를 통해 다중 상속을 구현하는 방법을 지원한다.
- 인터페이스 안의 메소드들은 접근제어지시자를 명시하지 않아도 『public』으로 설정된다.
- 인터페이스의 멤버 변수인 데이터는 접근제어지시자를 명시하지 않아도 기본 상수(primitive constant)인 static final 로 설정된다. (인터페이스로 인스턴스를 생성할 수는 없으므로 당연히 클래스변수-static변수-여야 한다.)
- 인터페이스의 메소드는 사용할 클래스가 구현해야하므로 항상 public 이며 final 이여서는 안되고, 추상메소드만 올 수 있으므로 abstract를 붙이지 않아도 그 의미를 가진다.
- 인터페이스를 구현하기 위해서는 『extends』 키워드 대신에 『implements』 키워드를 이용한다.
- 구현 : 클래스가 인터페이스를 implements(구현)하여 인터페이스의 모든 메소드를 Overriding 하고 사용한다. 하나 이상의 여러 인터페이스를 implements 할 수 있다.
- 상속 : 인터페이스가 다른 인터페이스를 상속받을 수 있으며 이 때, 『extends』 키워드를 사용한다. 또한, 클래스와 달리 인터페이스는 다중 상속이 가능하다.
extends 와 implements 의 경우의 수
상속 『extends』 의 경우 | 구현 『implements』 의 경우 |
클래스 extends 클래스 클래스 extends 추상클래스 |
추상클래스 implements 인터페이스 추상클래스 implements 인터페이스, 인터페이스, ... |
인터페이스 extends 인터페이스 인터페이스 extends 인터페이스, 인터페이스, ... |
클래스 implements 인터페이스 클래스 implements 인터페이스, 인터페이스, ... |
인터페이스 예제 코드 1 - 하나의 인터페이스 구현, 업캐스팅, 다운캐스팅
예제에 앞서, 인터페이스를 구현한 클래스는 상속 관계의 클래스 간의 형변환과 마찬가지로 형변환이 가능하다. 인스턴스가 생성되면서 해당 클래스와 구현한 인터페이스를 메모리에 올린다. 따라서 업캐스팅은 항상 가능하다.
예제에서 업캐스팅, 다운캐스팅 하는 과정을 살펴보자.
/*=================================
■■■ 클래스 고급 ■■■
- 인터페이스(Interface)
=================================*/
// 인터페이스
interface Demo
{
public static final double PI = 3.141592;
// 클래스 변수!! '왜 인스턴스 안에 클래스 변수가 있느냐?' 하면 혼난다~ static 변수니까!
// 인터페이스의 멤버 변수는 명시하지 않아도 자동으로 『public static final』이다.
public int a = 10; // == public static final int a = 10;
// 인터페이스의 메소드는 선언만 가능(정의, 구현 불가)
// 자동으로 『abstract』인 상태~!!!
//public abstract void print();
public void print();
//인터페이스의 메소드는 추상메소드여야 한다. 구현(정의)을 하면 안된다.
/*
public void print()
{
System.out.println("PI : " + PI);
}
*/
}
// 클래스
//1. class DemoImpl
//2. class DemoImpl extends Demo //-- 인터페이스 Demo를 extends는 불가능
//3. class DemoImpl implements Demo //-- 인터페이스 Demo를 implements하는 순간 일단 abstract 클래스
//
// 추상 클래스 - 인터페이스를 구현하는 추상 클래스
//4. abstract class DemoImpl implements Demo
// ↓ 인터페이스에 있는 메소드를 재정의(Override) 하므로써
// 클래스 - 인터페이스를 구현하는 (사용할 수 있는) 클래스가 된다.
class DemoImpl implements Demo
{
@Override
public void print()
{
System.out.println("인터페이스 메소드 재정의...");
}
public void write()
{
System.out.println("클래스에 정의된 메소드...");
}
}
// 클래스 - main() 메소드를 포함하고 있는 외부 클래스(동일 패키지)
public class Test122
{
public static void main(String[] args)
{
//Demo ob = new Demo(); //-- 생성 불가~!!!
//-- 인터페이스를 가지고 인스턴스 생성을 하는 것은 불가능하다.
//DemoImpl obTemp = new DemoImpl(); // 인터페이스의 메소드를 Override 했으니 가능 ○
//Demo ob = (Demo)obTemp; // 구현한 클래스를 인터페이스로 업캐스팅 가능
//Demo ob = obTemp; // (생략하여) 자동 형변환 가능
//--------------------------------------------------------------------
// ○ 업 캐스팅. 인터페이스는 일종의 추상 클래스로, 상위 클래스의 형태이다.
Demo ob = new DemoImpl();
// 인스턴스 생성 되면서 구현됐던 인터페이스 또한 메모리에 올라간다.
// 인터페이스 Demo, 클래스 DemoImpl 둘 다 메모리 할당 받음.
// ★★ 중요 이것으로 인해 업캐스팅 다운캐스팅 가능
ob.print();
//--==>> 인터페이스 메소드 재정의...
//ob.write();
//--==>> 에러 발생(컴파일 에러). 일단 업캐스팅 되면 인터페이스에 속하는 메소드만 사용 가능
//--------------------------------------------------------------------
// ○ 다운 캐스팅. Demo ob는 뿌리가 new DemoImpl 클래스이다. 그래서 다운캐스팅 가능.
((DemoImpl)ob).write();
//--==>> 클래스에 정의된 메소드...
//--------------------------------------------------------------------
// ○ 인터페이스의 멤버 변수에 접근
System.out.println(Demo.PI); //-- static 이기 때문에 가능!
//--==>> 3.141592
System.out.println(Demo.a); //-- static 이기 때문에 가능!
//--==>> 10
//Demo.a = 30; //-- final 이기 때문에 불가능! (상수)
//--==>> 에러 발생
}
}
인터페이스 예제 코드 2 - 두 개의 인터페이스 구현, 형변환
『@Override』 어노테이션(annotation)에 관하여...
상속 관계인 클래스의 메소드 오버라이딩 『@Override』 어노테이션(annotation)은 JDK 1.5(5.0)부터 지원한다. 하지만 인터페이스의 『@Override』 어노테이션은 JDK 1.6(6.0)부터 적용 가능한 문법이다.
따라서 JDK 1.5(5.0)에서는 인터페이스 메소드를 오버라이딩(Overriding)할 때 『@Override』 어노테이션(annotation)을 사용할 수 없다.
/*=================================
■■■ 클래스 고급 ■■■
- 인터페이스(Interface)
=================================*/
// 인터페이스
interface ADemo
{
public void write(); // public abstract void write(); 와 같다
}
// 인터페이스
interface BDemo
{
public void print(); // public abstract void print(); 와 같다
}
// ※ 인터페이스는 2개 이상을 구현(implements)할 수 있다.
// → 클래스가 다중 상속이 되지 않는 부분을 보완(보충)하는 개념
// 클래스
//1. class DemoImpl
//2. class DemoImpl extends ADemo, BDemo // 다중 상속 불가능
//3. class DemoImpl implements ADemo, BDemo // 다중 구현 가능.
// 일단 implements 하면 abstract (추상 클래스)가 됨
// 추상 클래스 - 두 인터페이스를 구현한 추상 클래스
//abstract class DemoImpl implements ADemo, BDemo
// ↓
// 클래스 - 메소드들을 Overriding 하여 두 인터페이스를 구현한 추상 클래스
class DemoImpl implements ADemo, BDemo
{
// 상속받은 클래스의 메소드 『@Override』 어노테이션(annotation)과 달리
// 인터페이스의 『@Override』 어노테이션은 JDK 1.6(6.0) 이후부터 적용 가능한 문법이다.
// 따라서 JDK 1.5(5.0)에서는 인터페이스 메소드를 오버라이딩(Overriding)할 때
// 『@Override』 어노테이션(annotation)을 사용할 수 없다.
@Override
public void write()
{
System.out.println("ADemo 인터페이스 메소드 write() ...");
}
@Override
public void print()
{
System.out.println("BDemo 인터페이스 메소드 print() ...");
}
}
// 클래스 - main() 메소드를 포함하는 외부의 다른 클래스(동일 패키지)
public class Test123
{
public static void main(String[] args)
{
//ADemo = new ADemo(); //-- 인터페이스 → 인스턴스 생성 불가
//BDemo = new BDemo(); //-- 인터페이스 → 인스턴스 생성 불가
// ADemo, BDemo 인터페이스를 구현(implements)한 클래스 (→ DemoImpl)
// DemoImpl 클래스 기반의 인스턴스 생성
DemoImpl ob1 = new DemoImpl();
ob1.write();
ob1.print();
//--==>> ADemo 인터페이스 메소드 write() ...
// BDemo 인터페이스 메소드 print() ...
// ----------------------------------------------------------------
// ○ 업캐스팅
ADemo ob2 = new DemoImpl(); //-- ADemo로 업 캐스팅
BDemo ob3 = new DemoImpl(); //-- BDemo로 업 캐스팅
//ob2.print(); //--==>> 에러 발생. print() 메소드는 BDemo의 메소드
//ob3.write(); //--==>> 에러 발생. write() 메소드는 ADemo의 메소드
ob3.print();
ob2.write();
//--==>> BDemo 인터페이스 메소드 print() ...
// ADemo 인터페이스 메소드 write() ...
// ----------------------------------------------------------------
// ○ 형변환
((BDemo)ob2).print();
((ADemo)ob3).write();
//-- ob2, ob3 둘 다 DemoImpl 클래스 기반의 인스턴스이고
// DemoImpl 클래스가 두 인터페이스를 모두 구현했기 때문에 가능하다.
// 만약 DemoImpl 클래스가 이들 중 한 인터페이스만 구현했다면
// 이 구문은 런타임 에러 발생하는 구문이 된다.
//--==>> BDemo 인터페이스 메소드 print() ...
// ADemo 인터페이스 메소드 write() ...
// ----------------------------------------------------------------
// ○ 다운 캐스팅
((DemoImpl)ob3).write();
//--==>> ADemo 인터페이스 메소드 write() ...
}
}
인터페이스 예제 코드 3 - 인터페이스와 구현하는 추상 클래스와 상속받는 클래스, 인스턴스 생성
1. 인터페이스는 인스턴스를 생성할 수 없다.
2. 인터페이스를 구현한 클래스(예제의 DemoImpl)가 모든 메소드를 오버라이딩 하지 않는다면 추상 클래스로 이므로 abstract 키워드가 붙는다. 인스턴스 생성할 수 없다.
3. 추상 클래스를 상속받아서 모두 오버라이딩한 클래스(예제의 DemoImplSub)로 인스턴스 생성이 가능하다.
/*=================================
■■■ 클래스 고급 ■■■
- 인터페이스(Interface)
=================================*/
// 인터페이스
interface Demo
{
public void write();
public void print();
}
// 클래스
//class DemoImpl implements Demo
// 인터페이스를 구현하는 추상 클래스. 일단 implements하면 abstract.
// 오버라이딩을 모두 해야 일반 클래스가 된다.
abstract class DemoImpl implements Demo
{
@Override
public void write()
{
System.out.println("write() 메소드 재정의 ...");
}
// public void print(); // Overriding 하지 않음
}
// 클래스
//abstract class DemoImpSub extends DemoImpl // 추상 클래스를 상속받으므로 일단은 추상 클래스
class DemoImplSub extends DemoImpl // 추상메소드를 모두 오버라이딩 하면 일반 클래스
{
@Override
public void print()
{
System.out.println("print() 메소드 재정의 ...");
}
}
// 클래스 - main() 메소드를 포함하는 외부의 다른 클래스(동일 패키지)
public class Test124
{
public static void main(String[] args)
{
//Demo ob1 = new Demo();
//-- 인터페이스 → 인스턴스 생성 불가
//DemoImpl ob2 = new DemoImpl();
//-- 추상클래스 → 인스턴스 생성 불가
DemoImplSub ob3 = new DemoImplSub();
//-- 추상 클래스를 상속받아 오버라이딩 → 인스턴스 생성 가능
ob3.write();
ob3.print();
}
}
// 실행 결과
/*
write() 메소드 재정의 ...
print() 메소드 재정의 ...
*/
인터페이스 예제 코드 4 - 두 인터페이스를 상속받은 인터페이스와 구현
/*=================================
■■■ 클래스 고급 ■■■
- 인터페이스(Interface)
=================================*/
// 인터페이스
interface ADemo
{
public void write();
}
// 인터페이스
interface BDemo
{
public void print();
}
// ※ 클래스는 다중 상속을 지원하지 않지만, 인터페이스는 다중 상속을 지원한다.
// 인터페이스 - 두 인터페이스(ADemo, BDemo)를 상속받은 인터페이스
interface CDemo extends ADemo, BDemo
{
public void test();
// public void write();
// public void print();
}
// 클래스
//class DemoImpl
// 두 인터페이스(ADemo, BDemo)를 상속받은 인터페이스(CDemo)를 구현한 추상 클래스
//abstract class DemoImpl implements CDemo // 일단 implements 하면 추상 클래스
// 두 인터페이스(ADemo, BDemo)를 상속받은 인터페이스(CDemo)를 구현한 후
// 모든 메소드를 재정의한 클래스
class DemoImpl implements CDemo // 모든 메소드를 overriding 하면 사용 가능한 일반 클래스
{
@Override
public void test()
{
System.out.println("test()...");
}
@Override
public void write()
{
System.out.println("write()...");
}
@Override
public void print()
{
System.out.println("print()...");
}
}
// 클래스 - main() 메소드를 포함하는 외부의 다른 클래스(동일 패키지)
public class Test125
{
public static void main(String[] args)
{
// 두 인터페이스를 상속받은 인터페이스를 구현하고
// 모든 메소드를 재정의한 클래스에 대한 인스턴스 생성
DemoImpl ob = new DemoImpl();
ob.test();
ob.write();
ob.print();
}
}
// 실행 결과
/*
test()...
write()...
print()...
계속하려면 아무 키나 누르십시오 . . .
*/
인터페이스를 활용한 예제 코드
/*=================================
■■■ 클래스 고급 ■■■
- 인터페이스(Interface)
=================================*/
// ○ 실습 문제
// 성적 처리 프로그램을 구현한다
// 단, 인터페이스를 활용할 수 있도록 한다.
// 실행 예)
// 인원 수 입력(1~10) : 2
// 1번째 학생의 학번 이름 입력(공백 구분) : 2012170 안혜지
// 국어 영어 수학 점수 입력 (공백 구분) : 90 100 85
// 2번째 학생의 학번 이름 입력(공백 구분) : 2012112 윤홍준
// 국어 영어 수학 점수 입력 (공백 구분) : 85 70 65
// 2012170 안혜지 90 100 85 275 91
// 수 수 우
// 2012112 윤홍준 85 70 65 220 73
// 우 미 양
// 계속하려면 아무 키나 누르세요...
import java.util.Scanner;
// 속성만 존재하는 클래스 → 자료형 활용
class Record
{
String hak, name; //-- 학번, 이름
int kor, eng, mat; //-- 국어, 영어, 수학 점수
int tot, avg; //-- 총점, 평균(편의 상 정수 처리)
}
// 인터페이스
interface Sungjuk
{
public void set(); //-- 인원 수 구성
public void input(); //-- 상세 데이터 입력
public void print(); //-- 결과 출력
}
// 문제 해결 과정에서 설계해야 할 클래스
class SungjukImpl implements Sungjuk
{
int num, i ,j;
Record[] arr;
String[] grade = {"수", "우", "미", "양", "가"};
@Override
public void set()
{
Scanner sc = new Scanner(System.in);
do
{
System.out.print("인원 수 입력(1~10) : ");
num = sc.nextInt();
}
while (num <1 || num>10);
arr = new Record[num];
for (i=0; i<arr.length; i++)
arr[i] = new Record();
}
@Override
public void input()
{
Scanner sc = new Scanner(System.in);
for (i=0; i<arr.length; i++)
{
System.out.print((i+1) + "번째 학생의 학번 이름 입력(공백 구분) : ");
arr[i].hak = sc.next();
arr[i].name = sc.next();
System.out.print("국어 영어 수학 점수 입력 (공백 구분) : ");
arr[i].kor = sc.nextInt();
arr[i].eng = sc.nextInt();
arr[i].mat = sc.nextInt();
arr[i].tot = arr[i].kor + arr[i].eng + arr[i].mat;
arr[i].avg = arr[i].tot/3;
}
}
@Override
public void print()
{
String kor, eng, mat;
System.out.println();
for (i=0; i<arr.length; i++)
{
kor = sungjuk(arr[i].kor);
eng = sungjuk(arr[i].eng);
mat = sungjuk(arr[i].mat);
System.out.printf("%s %s %5d %5d %5d %5d %5d\n",
arr[i].hak, arr[i].name, arr[i].kor, arr[i].eng, arr[i].mat,
arr[i].tot, arr[i].avg);
System.out.printf(" %s %s %s\n", kor, eng, mat);
}
}
private String sungjuk(int n)
{
String result;
if (n>=90)
result = grade[0];
else if(n>=80)
result = grade[1];
else if(n>=70)
result = grade[2];
else if(n>=60)
result = grade[3];
else
result = grade[4];
return result;
}
}
// main() 메소드를 포함하고 있는 외부 클래스(동일 패키지)
public class Test126
{
public static void main(String[] args)
{
Sungjuk ob;
// 문제 해결 과정에서 작성해야 할 ob 구성
ob = new SungjukImpl();
ob.set();
ob.input();
ob.print();
}
}
최근댓글