Java Standard - 객체지향 프로그래밍2
상속과 다형성 등 객체지향과 관련된 내용을 알아보자.
상속
상속의 정의와 장점
- 상속
- 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것
- 키워드
extends
- 조상 클래스를 확장(extends)한다는 의미로 키워드를extends
를 씀- 생성자와 초기화 블럭은 상속되지 않음, 멤버만 상속
- 멤버 개수 - 자손 클래스 ≥ 조상 클래스
- 자손 클래스의 인스턴스 생성시, 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 인스턴스로 생성됨
- 키워드
접근 제어자가 private, default인 멤버들도 상속은 되지만 자손 클래스가 접근 할 수 없는 것
- 장점
- 코드의 재사용성을 높힘
- 코드의 중복을 제거
- 프로그램의 생산성과 유지보수에 기여
- 코드의 중복을 제거
- 관계
- 조상 클래스 - 상속해주는 클래스, 부모(parent) 클래스 = 상위(super) 클래스 = 베이스(base) 클래스
- 자손 클래스 - 상속 받는 클래스, 자식(child) 클래스 = 하위(sub) 클래스 = 파생된(derived) 클래스
→ 이 두 클래스는 서로 상속 관계에 있다고 한다.
표현 그림 좌: 상속 계층도(상속 관계도), 우:다이어그램
- 상속 계층도(상속 관계도) : 클래스는 타원으로, 상속 관계는 화살표로 해서 클래스 간의 상속 관계를 표현한 그림
- 다이어그램: 상속 관계를 다이어그램으로도 표현 할 수 있다.
클래스간의 관계 - 포함관계
- 포함관계
- 한 클래스의 멤버변수로 다른 클래스의 타입의 참조변수를 선언하는 것
1
2
3
4
5
6
7
8
9
class Circle{
Point point; //좌표
int r; // 반지름
}
class Point{
int x; //x좌표
int y; //y좌표
}
car와 같은 클래스를 작성한다고 할때, car의 구성요소인 engine, door와 같은 클래스를 미리 작성해놓고 이를 car의 멤버변수로 선언해 포함관계를 맺어주면 클래스를 작성하는 것도 쉽고 코드도 간결해서 이해하기 쉽다
클래스간의 관계 결정하기
- 상속: ~은 ~이다, is - a
- 원(Circle)은 점(Point)이다 → 이상함 ⇒ 상속 관계가 아님
- 원(Circle)은 도형(Shape)이다 → 자연스러움 ⇒ 상속 관계가 맞음
- 포함: ~은 ~를 가지고 있다, has - a
- 원(Circle)은 점(Point)이다 → 자연스러움 ⇒ 포함 관계가 맞음
- 원(Circle)은 도형(Shape)을 가지고 있다 → 이상함 ⇒ 포함 관계가 아님
⇒ 매번 이렇게 딱 떨어지는 것은 아니지만, 감을 잡기 좋은 예
참조변수를 출력하거나, 덧셈연산자를 이용한 참조변수와 문자열 결합에는 참조변수.toString()을 자동적으로 호출되어 참조변수를 문자열로 대치한 후 처리한다.
단일상속
자바에서는 오직 단일 상속만을 허용함
만약 다중 상속을 허용한다면
- 클래스간의 관계가 매우 복잡해짐
- 부모 클래스들 중 메서드 이름이 같다면, 구별할 수 있는 방법이 없음 → 선언부를 모두 다르게 해야한다면, 부모 클래스를 변경하는 거라 해당 메서드를 사용하는 자식 클래스는 모두 변경되어야한다.
- 단일 상속을 해서 장점
- 관계가 보다 명확해짐 → 더욱 신뢰할 수 있게 만들어줌
Object클래스 - 모든 클래스의 조상
- Object 클래스
- 모든 클래스 상속계층도에 최상위에 있는 조상 클래스
- 그동안 toString(), equals(Object o)와 같은 메서드들을 따로 정의하지 않아도 사용할 수 있던 이유는 이 메서드들은 다 Object클래스에 정의되어있고, 모든 클래스는 Object를 상속받기 때문이다.
다른 클래스로부터 상속을 받지 않는 모든 클래스들은 자동적으로 Object 클래스로부터 상속받게 함으로써 이것을 가능하게 만듬 ex) A클래스와 B클래스가 있고 A → B 인 상황이라면, A는 B를 상속받아 Object를 상속받지 못하지만, B는 아무것도 상속받지 않아 자동적으로 Object를 상속받게 함으로써 A까지 Object를 간접적으로 상속받게 됨
오버라이딩
오버라이딩이란?
- 오버라이딩
- 조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것, 내용만 새로 작성하는 것
- 사용하는 경우
- 상속받은 메서드를 자손 클래스에 맞게 변경해야하는 경우
오버라이딩의 조건
- 조건
- 메서드의 선언부가 같아야함
- 이름과 매개변수, 반환타입이 같아야 함
- 접근제어자는 조상 클래스의 메서드보다 좁은 범위로 변경 불가능
- 조상 클래스의 많은, 넓은 예외를 선언 불가능
- static ↔ 인스턴스 변경 불가능
- 이름과 매개변수, 반환타입이 같아야 함
공변 반환타입 JDK 1.5에서 추가된 개념으로, 부모 클래스의 반환 타입은 자식 클래스의 반환 타입으로 변경 가능하게 한다.
조상 클래스와 같은 이름의 static 메서드 정의 가능, 오버라이딩이 아니라 각각의 메서드
오버로딩 vs 오버라이딩
- 오버로딩
- 새로운 매서드를 정의하는 것
- 오버라이딩
- 상속 받은 메서드의 내용을 변경하는 것
super
- super
- 상속받은 멤버를 참조하는데 사용되는 참조변수
this
로도 참조가 가능, 조상 클래스의 멤버와 자손 클래스의 멤버가 중복 정의되어 구별해야할 때 사용- 모든 인스턴스 메서드에는 자신이 속한 인스턴스의 주소가 지역변수로 저장, 이것이
this
와super
같은 이름의 멤버변수가 조상 클래스에도 자손 클래스에도 존재? 가능하다! 이때는
this
와super
를 이용해 접근 해야하고, 이때는 각각 다른 값을 참조한다.
조상 클래스 메서드의 덧붙이는 작업이라면 → super를 이용할 것! 이후에 조상 클래스에서 내용 변경이 된다면, 그 내용이 같이 적용이 되기 때문에
super() - 조상 클래스의 생성자
- 정의
- 조상 클래스의 생성자를 호출하는데 사용
- 사용이유
- 자손 클래스의 인스턴스 = 자손 멤버 + 조상 멤버인 하나의 인스턴스, 이때 여기서 조상 멤버의 초기화 작업이 수행 되어야해서
- 조상 클래스의 생성자가 먼저 호출 되어야하는 이유: 자손 클래스의 멤버가 조상 클래스의 멤버를 사용할 수도 있기 때문에
⇒ 모든 클래스의 첫 줄에는 자신의 다른 생성자this()
또는 조상의 생성자super()
를 호출 해야함
- 순서
- 자손 클래스의 생성자 → 조상 클래스의 생성자 → 조상 멤버 초기화 완료 → 자손 멤버 초기화 완료
왜 자신의 다른 생성자를 호출할까? → 확인해봐야함!! 예를 들어서, 자손 클래스의 오버로딩된 여러 생성자가 있다고 생각해보자. 이럴때는 첫 줄에 this()를 이용해 다른 생성자를 호출 할 수 있게 되어있는데, 이 this()로 호출된 생성자 첫 줄에 super()가 있거나, 생략 되어있을 것이기 때문에 조상 클래스의 생성자를 먼저 생성하는 문법에도 위배되지 않고, 다른 생성자를 사용하는것에도 문제가 되지 않는다.
package와 import
패키지(package)
- 정의
- 클래스의 묶음으로 클래스 또는 인터페이스를 포함시킬 수 있음
- 목적
- 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리 할 수 있음
- 물리적인 클래스와 패키지
- 클래스
- 하나의 클래스 파일(.class)
- jar파일
- 클래스 파일들을 압축한 것
- 패키지
- 하나의 디렉토리
- 하위 패키지를 가질 수 있고,
.
으로 구분됨→ 디렉토리로 그대로 표현됨
war와 jar의 차이?
패키지의 선언
- 선언
- 소스파일에서 주석과 공백을 제외한 첫번째 문장으로
package 패키지명
을 적으면 됨- 하나의 소스파일에 단 한번만 선언 될 수 있음
- 모든 클래스는 반드시 하나의 패키지에 포함되어야함
- 하나의 소스파일에 단 한번만 선언 될 수 있음
- 네이밍 규칙
- 소문자를 하는 것을 원칙으로 함, 대소문자를 모두 허용
- 이름 없는 패키지
- 자바에서 기본적으로 제공하는 패키지, 패키지를 지정하지 않은 클래스가 속하는 곳으로 패키지를 지정하지 않은 클래스들은 모든 클래스들은 같은 패키지에 속하는 것
- 컴파일 옵션
-d
- 패키지의 위치를 찾아 클래스 파일을 생성, 패키지와 동일한 디렉터리 구조까지
-cp
- 일시적으로 클래스 패스를 지정 할 수 있음
- 클래스패스 CLASSPATH
- 의미
- 컴파일러나 JVM등이 클래스의 위치를 찾는데 사용되는 경로
- 등록하는 방법
- 환경변수에 CLASSPATH를 만들거나 이 내용에 프로젝트의 최상위 경로를 추가
import문
- 역할
- 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 제공하는 것
import문과 성능은 전혀 관련이 없다. 컴파일 시간이 조금 더 걸릴 뿐
import문의 선언
- 선언
- package문 다음, class선언문 이전에
import 패키지명.클래스명
orimport 패키지명.*
을 적으면 됨, 여러 번 선언하는 것이 가능
- import 패키지명.*
- 실행 시 성능상의 차이는 전혀 없고, 단지 컴파일러가 컴파일시에 해당 패키지에서 클래스 이름을 찾는 수고를 더 할 뿐임
- 하위 패키지의 클래스까지 포함하는 것은 아님 →
java.text.*
과java.util.*
를java.*
로 대체할 수 없음
- java.lang 패키지는 모든 소스파일에 묵시적으로 import문이 선언되어있음
- 매우 빈번히 사용되는 중요한 클래스들이 속한 패키지이기 때문에, 따로 지정하지 않아도 됨
- static import문을 사용하는 이유
- static import문을 사용하면 static멤머 호출시 클래스를 생략할 수 있음 → 코드 간결
제어자(modifier)
제어자란?
- 제어자
- 클래스, 변수, 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여함
종류
- 접근 제어자: public, protected, default, private
- 그외의 제어자: static, final, abstract, native, transient, synchronized, volatile, strictfp
여러 제어자를 조합해 사용하는 것이 가능하지만, 접근제어자는 한번에 한가지만 사용 할 수 있다.
static - 클래스의, 공통적인
- static
클래스의
,공통적인
의 의미를 가지고 있음- 사용될 수 있는 곳
- 멤버변수, 메서드, 초기화 블럭
왜 생성자에는 static이 붙을 수 없을까? 생성자는 인스턴스를 생성하면서 인스턴스 변수를 초기화하는데에 목적을 두기 때문에 static을 붙일 수 없음
final - 마지막의, 변경될 수 없는
- final
마지막의
,변경될 수 없는
의 의미를 지니고 있음- 사용될 수 있는 곳
- 클래스 - 확장할 수 없는 클래스, 다른 클래스의 조상이 될 수 없음
- 메서드 - 변경될 수 없는 메서드, 오버라이딩을 통해 재정의 될 수 없음
- 멤버변수 - 값을 변경할 수 없는 상수
- 지역변수 - 값을 변경할 수 없는 상수
- 메서드 - 변경될 수 없는 메서드, 오버라이딩을 통해 재정의 될 수 없음
- 생성자를 이용한 final 멤버변수의 초기화
- 원래 final이 붙은 상수는 선언과 동시에 초기화를 진행해야하는데, 인스턴스 변수는 어떻게 해야할까? 생성자를 이용해 인스턴스를 생성할 때 초기화에 필요한 값을 매개변수로 전달받아 초기화한다. 이렇게하면 클래스의 인스턴스 변수를 상수로 선언해도 각 인스턴스마다 다른 값을 가질 수 있다.
abstract - 추상의, 미완성의
- abstract
미완성
이라는 의미를 지니고 있음추상 메서드를 선언하거나, 추상 메서드를 포함하고 있는 클래스라는 것을 선언하는데 사용됨
- 사용될 수 있는 곳
- 클래스 - 추상 메서드가 선언되어 있음
- 메서드 - 추상 메서드임
- 추상 클래스는 인스턴스를 생성할 수 없고, 추상 메서드가 없어도
abstract
키워드를 붙여 추상 클래스로 만드는 경우도 있다.
- 메서드 - 추상 메서드임
접근 제어자(access modifier)
- 접근 제어자
- 외부에서 접근하지 못하도록 제한하는 역할
- 사용될 수 있는 곳
- 클래스, 멤버변수, 메서드, 생성자
종류와 범위
같은 클래스 | 같은 패키지 | 자손 클래스 | 전체 | |
---|---|---|---|---|
public | O | O | O | O |
protected | O | O | O | |
default | O | O | ||
private | O |
접근 제어자를 이용한 캡슐화
- 캡슐화
- 필요한 데이터 이외에는 외부에서 접근하지 못하도록 하는 것, 작업 내용과 같은 부분을 클래스 내부로 감추는 것
- 접근 제어자를 사용하는 이유
- 클래스의 내부에 선언된 데이터를 보호하기 위해, 데이터를 외부에서 함부로 변경하지 못하도록 하기위해 외부로부터 접근은 제한하는 것
- 외부에서 데이터 변경 방지
- 내부 작업을 위한 임시변수, 메서드 등의 멤버들을 클래스 내부로 감추기 위해서
- 외부에서 데이터 변경 방지
⇒ 외부에서 접근할 필요가 없는것을 감춤으로써 복잡성을 줄임, 나중에 코드 수정을 해야할때 접근 제어자의 범위에 따라 확인해야 할 곳의 범위가 차이가 상당히 많이 남
- 생성자의 접근 제어자
- 생성자의 접근 제어자를
private
으로 지정하고 static 메서드로 인스턴스를 생성해서 반환한다면, 인스턴스의 개수를 제한 할 수도 있다. (다만 이 클래스는 생성자가 private이기때문에 다른 클래스의 조상이 될 수 없다. 그렇기 때문에 class앞에 final을 붙여 상속할 수 없는 클래스라는 것을 알리는 것이 좋다.)
제어자(modifier)의 조합
제어자를 조합해 사용할 때 주의해야 할 사항
메서드
- static과 abstract는 함께 사용할 수 없음: static은 몸통이 있어야지만 사용할 수 있어서
- abstract메서드의 접근제어자가 private일 수 없음: abstract은 자손에서 구현, private이면 자손에서 접근 불가능이기 때문
- private와 final을 같이 사용할 필요는 없음: 접근 제어자가 private이면 오버라이드 될 수 없기 때문에 둘 중 하나만 사용해도 의미가 충분함
클래스
- abstract와 final을 동시에 사용할 수 없음: 서로 모순, abstract은 상속으로 완성, final은 확장 불가능
다형성(polymorphism)
다형성이란?
- 다형성
- 여러가지 형태를 가질 수 있는 능력, 상속과 깊은 관계가 있음
- 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 구현 → 조상 클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 함
- 한 인스턴스를 참조해도 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 다름 → 참조변수의 타입이 인스턴스에서 사용할 수 있는 멤버의 개수를 결정
- 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 구현 → 조상 클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 함
- 참조변수
- 참조변수에 실제로 저장되는 값은 null 또는 4byte의 주소값 임
- 참조변수의 타입으로 알 수 있는 것 - 객체의 종류, 사용할 수 있는 멤버의 수
참조변수의 형변환
- 참조변수의 형변환
- 참조 변수도 형변환이 가능, 바로 윗조상뿐 아니라 연결만 되어있다면 모든 조상과 자식으로 가능
- 조건 - 상속관계에 놓여있는 클래스 사이에서만 가능 자손 → 조상(업캐스팅): 형변환 생략가능 자손 ← 조상(다운캐스팅): 형변환 생략 불가능
- 참조변수의 형변환의 의미
- 참조변수의 타입을 변환하는 것, 인스턴스를 변환하는 것이 아님
- 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절 하는 것 뿐 (참조변수의 타입이 변경되니까 그에 따라 사용할 수있는 멤버가 달라짐)
- 인스턴스는 변하지 않기 때문에 나중에 다시 인스턴스와 맞는 타입으로 참조변수를 맞춰준다면 모든 멤버를 사용 할 수 있음
- 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절 하는 것 뿐 (참조변수의 타입이 변경되니까 그에 따라 사용할 수있는 멤버가 달라짐)
- 조상타입의 인스턴스를 자손타입의 참조변수로 참조하는 것은 불가
1
2
3
4
5
6
7
8
9
Car car = new Car();
Car car2 = null;
FireEngine = fe = null;
car.drive();
fe = (FireEngine)car;
fe.drive();
car2 = fe;
car2.drive();
위 코드를 실행하면, 컴파일 시에는 타입만 체크하기 때문에 문제가 없지만 실행시에 문제가 발생함 그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요함
instanceof 연산자
- instanceof
- 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해
- 방법 - 참조변수
instanceof
타입- 결과 -
boolean
,true
가 나온다면 참조변수가 검사한 타입으로 형변환 해도 된다는 것을 뜻함 - 방법 - 참조변수
참조변수와 인스턴스의 연결
부모 클래스와 자식 클래스가 같은 이름 인스턴스 변수를 정의했다면, 참조변수의 타입에 따라 서로 다른 인스턴스 변수를 사용된다.
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
class 부모 extends{
int age = 50;
void printAge(){
System.out.println("말안해!");
}
}
class 자식 extends 부모{
int age = 20;
void printAge(){
System.out.println("내나이 : "+ age);
}
}
class test{
public void main(String[] args){
부모 p = new 부모();
자식 c = new 자식();
c.printAge();
p.printAge();
}
}
/*
내나이 : 20
내나이 : 50
*/
❓ 정확하게 어떤 어떤 부분이 차이가 나는건지 궁금함 [ ] p.age ≠ c.age → 직접 접근, 오버라이드 된 메서드, 그냥 메서드 전부다 이렇게 다르게?!! [ ] p의 printAge() = c의 printAge() → 같다면 왜 같은지? 왜?
매개변수의 다형성
- 의미
- 상속관계에 놓여있는 부모클래스를 매개변수의 타입으로 하는 것
- 해당 부모의 자손 타입의 참조변수면 어느 것이나 매개변수로 받아들일 수 있다는 뜻
- 장점 - 다양한 참조변수 대신 공통의 부모가 있다면, 그 부모로 매개변수로 하면 메서드 오버로딩을 하지 않고 간단히 정의할 수 있음
- 해당 부모의 자손 타입의 참조변수면 어느 것이나 매개변수로 받아들일 수 있다는 뜻
여려 종류의 객체를 배열로 다루기
여러 종류의 객체를 배열로 다루기 위해서는 가장 가까운 공통 조상 클래스 타입의 참조변수 배열을 생성해 객체들을 저장하면됨( 모든 객체의 공통 조상 object로 해도 됨 )
추상클래스(abstract class)
추상클래스란?
- 추상클래스
- 미완성 메서드를 포함하고 있다는 뜻,
abstract
키워드를 붙임, 미완성 설계도에 비유- 인스턴스를 생성할 수 없고, 상속을 통해 자손클래스에 의해서만 완성 될 수있음
- 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서의 중요한 의미
- 인스턴스를 생성할 수 없고, 상속을 통해 자손클래스에 의해서만 완성 될 수있음
추상메서드(abstract method)
- 추상메서드
- 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨둔 것, 미완성 메서드 설계만 해놓음
- 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성 되었는지 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워두는 것
- 선언부의 중요성 - 메서드를 호출하는 곳에서는 선언부만 알고 있으면 됨, 추상메서드를 사용하는 코드를 작성하는 것이 가능하며, 실제로는 구현된(완성된) 메서드가 호출 되도록 할 수 있다.
- 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성 되었는지 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워두는 것
추상 메서드를 사용하는 이유? 추상클래스를 참조변수로 선언시 여기서는 구현하지 않더라도, 자식 클래스에서 구현한다면 해당 내용으로 호출 되니까 이것을 위한 것인 걸까?
- 문법: 맨 앞에
abstract
를 붙이고, 구현부 대신;
으로 문장의 끝을 알리는 것
추상클래스의 작성
단어의 의미
- 추상: 구체적 표상이나 개념에서 공통된 성질을 뽑아 이를 일반적인 개념으로 파악하는 정신 작용 → 여러 구체적인 것에서 공통적인 성직을 뽑아 일반적인 개념으로 파악하는 것
- 추상화: 클래스간의 공통점을 찾아 공통의 조상을 만드는 작업
- 구체화: 상속을 통해 클래스를 구현, 확장하는 작업
abstract
를 붙여 추상메서드로 선언하는 이유
- 자손클래스에서 추상메서드를 반드시 구현하도록 강요하기위해(반드시 필요한 메서드라)
- 자손클래스 자신에 맞게 구현하도록
작성하는 방법
- 공통된 부분을 뽑아 하나의 클래스로 만듬
- 각 클래스 마다 다르게 구현되어야하는 메서드는 추상메서드로 만듬
- 각 클래스는 공통 클래스를 상속받고, 추상메서드를 구현함
인터페이스(interface)
인터페이스란?
- 정의
- 일종의 추상클래스, 오직 추상메서드와 static 상수만을 멤버로 가질 수 있음, 기본 설계도에 비유
- 역할
- 그 자체로 사용되기 보다는 다른 클래스를 작성하는데 도움을 줄 목적으로 작성됨
- 작성
- 키워드는
interface
를 사용하고, 접근제어자는default
와public
만 사용 가능함- 모든 멤버변수는 public static final, 생략가능
- 모든 메서드는 public abstract, 생략가능 (단, static메서드와 디폴트 메서드는 예외)
- 모든 멤버변수는 public static final, 생략가능
인터페이스의 상속
- 인터페이스는 인터페이스로부터만 상속 받을수 있음
- 다중상속이 가능함, 한 class가 여러 인터페이스를 상속 받을 수 있음
- 최고 조상이 없음, class 상속에서의 최고 조상은 Object class인데 이와 같은 개념이 없음
인터페이스의 구현
- 인터페이스는 구현한다는 의미의
implememts
를 사용( class는 확장의 의미로extends
사용) - 인터페이스 내용의 일부만 구현한다면, class 앞에 abstract를 붙여 추상 클래스로 선언해야함
‘able’ 로 끝나는 인터페이스의 이름의 의미 어떠한 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위해, 구현한 클래스는
~를 할 수 있는
능력을 갖추었다는 의미
인터페이스를 이용한 다중상속
C++에서는 가능한 다중상속이 자바에서는 불가능하다 → 단점으로 지적되어 인터페이스를 이용해 다중상속이 가능하다는 것을 보여주는 것이지 인터페이스의 참다운 의미는 다중상속이 아니다. 자바에서 다중상속을 구현하는 경우는 거의 없다.
다중상속시 중복 처리 방법
- 멤버변수는 충돌 할 일이 거의 없고 static변수라 클래스명을 붙여서 구분 메서드 선언 충돌시에는 조상 클래스쪽의 메서드를 상속받으면 됨 ⇒ 충돌은 해결되지만, 다중상속의 장점을 잃음
- 두 조상 클래스(TV,VCR) 중에 비중이 높은 쪽(TV)을 선택해 다른 한쪽(VCR)은 내부 멤버로 포함시키는 방식
1
2
3
4
5
6
class TV{...}// 상속 받을 클래스 1
class VCR{...}// 상속 받을 클래스 2
class TVCR extends TV{//새로 작성할 클래스
VCR vcr = new vcr();
}
- 두 조상 클래스(a,b) 중에 한쪽(a)의 필요한 부분을 뽑아서 인터페이스로 만든 다음 구현하는 방식
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class TV{...}// 상속 받을 클래스 1
class VCR{
int powerOn(){...}
}// 상속 받을 클래스 2
interface IVCR{//VCR클래스의 메서드들의 선언부만 그대로 작성
int powerOn();
}
class TVCR extends TV implements IVCR{//새로 작성할 클래스
VCR vcr = new vcr();
int powerOn(){
return vcr.powerOn(); //코드를 작성하는 대신 vcr인스턴스의 메서드를 호출함
}
}
→ 2번과 같이 인터페이스를 작성하지 않고 VCR 클래스는 TVCR클래스에 포함하는 것만으로도 충분하지만, 인터페이스를 이용하면 다형적 특성을 이용할 수 있는 장점이 있음
여기서의 다형적 특성이란? 구현체를 VCR클래스가 아니라 다른 방식으로 구현해도 된다는 다형적 특성?을 이야기 한 것 같다는 생각을 함. TVCR에서 인터페이스를 상속받아 구현해놓으면, 구현방법이 어찌 되었든 TVCR 클래스를 사용하는쪽 입장에서는 코드를 수정하지 않아도 되는 다형적 특성?!을 얘기한 것이 아닐까 싶음
인터페이스를 이용한 다형성
- 인터페이스 타입으로도 이를 구현한 클래스를 참조 할 수 있음
- 인터페이스 타입의 매개변수가 갖는 의미: 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인터페이스가 매개변수로 제공 되어야한다는 것
- 인터페이스 리턴 타입이 갖는 의미: 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것
→ 이렇기 때문에 List<E>
타입으로 줄 수 있었음
인터페이스의 장점
개발시간의 단축: 인터페이스가 작성되면 이를 통해 프로그램을 작성하는 것이 가능하기 때문
호출 하는 쪽: 메서드의 선언부만 알면됨 → 인터페이스에 나와있음 구현하는 쪽: 인터페이스만 구현하면 됨 → 인터페이스를 작성한 이후에는 양쪽에서 동시에 작업이 가능함
표준화 가능: 인터페이스로 기본 틀을 작성해서
기본 틀을 인터페이스로 먼저 작성 한 후 구현함으로써 보다 일관되고 정형화된 프로그램의 개발
관계 없는 것들에 관계를 맺어줌: 인터페이스를 구현하게 함으로써 관계를 맺어줌
공통부모
Object
Class가 있다고는 하지만, 관계로써 의미가 있는 Class가 아니고, 여러 클래스를 상속이 아니면 관계를 만들기가 어려운데, 이때 인터페이스를 통해 관계를 생성함- moveable 인터페이스를 상속받은 클래스가 있다면, 움직일 수 있는 클래스들! 했을 때 car, robot,,,서로 다른 class라서 상속으로는 관계를 맺어줄 수 없을 때 상속이 아닌 다른 관계를 맺어 줄 수 있음
독립적인 프로그래밍이 가능함: 인터페이스를 이용해 선언과 구현의 분리, 독립적인 프로그램 작성가능
클래스 - 클래스(직접적인 관계) → 클래스 - 인터페이스 - 클래스(간접적인 관계): 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하도록
관계를 맺어주는 것의 예시(1) - 게임
- Tank(탱크),Dropship(수송선) - 기계
- SCV(건설인부): 기계들을 수리하는 메서드가 있음
SCV가 기계 유닛들을 수리할 방법
메서드 오버로딩 → 가능한데 비효율적
1 2
void repair(Tank t){...} void repair(Dropship d){...}
→ 만약 수리해야하 할 유닛의 개수가 여러개라면? 메서드의 내용은 크게 다르지 않아 중복되는 내용이 유닛 개수만큼 이라면?
하지만, 둘의 공통 조상으로는 Unit이고, Marine은 수리대상이 아니기 때문에 매개변수를 공통조상으로 변경하는 것은 부적합하다.
interface로 관계를 생성해서 이용
1 2 3 4 5 6 7 8 9 10 11 12 13
interface Repairable{}//빈내용으로 작성 class Tank extents GroundUnit implements Repairable{...} class Dropship extents AirUnit implements Repairable{...} class SCV extents GroundUnit implements Repairable{ void repair(Repairable r){//타입 체크용으로 인터페이스 사용 if(r instanceof Unit){// 형변환 하기위해 체크 Unit u = (Unit) r;//형변환해서 사용 //인터페이스는 타입체크용도라서 아무것도 못함(형변환 이유) //수리하는 코드 작성 } } }
관계를 맺어주는 것의 예시(2) - 건물
- 4가지 건물의 종류가 있을때, barrack과 Factory만 이동이 가능한 기능을 추가
기능을 추가하는 방법
- 각 class에 메서드 작성 → 메서드가 중복됨
구현하고자 하는 메서드를 정의하는 interface를 정의한 후 각 class에서 상속받아 구현
1 2 3 4 5 6 7 8 9 10 11 12 13
interface Liftable{ /** 건물을 들어 올린다 */ public void liftoff(); /**건물을 이동한다 */ public void move(int x, int y); /** 건물을 정지한다 */ public void stop(); /** 건물을 착륙시킨다 */ public void land(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
class Barrack extends Building implements Liftable{ //각 클래스에 맞게 내용 구현 public void liftoff(){...} public void move(int x, int y){...} public void stop(){...} public void land(){...} } class Factory extends Building implements Liftable{ //각 클래스에 맞게 내용 구현 public void liftoff(){...} public void move(int x, int y){...} public void stop(){...} public void land(){...} }
버전 1 - 각 클래스에 맞게 정의해서
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 LiftableImpl implements Liftable{//구현체 생성 public void liftoff(){...} public void move(int x, int y){...} public void stop(){...} public void land(){...} } class Barrack extends Building implements Liftable{ LiftableImpl l = new LiftableImpl();//구현체 이용 public void liftoff(){l.liftoff();} public void move(int x, int y){l.move(x,y);} public void stop(){l.stop();} public void land(){l.land();} } class Factory extends Building implements Liftable{ LiftableImpl l = new LiftableImpl();//구현체 이용 public void liftoff(){l.liftoff();} public void move(int x, int y){l.move(x,y);} public void stop(){l.stop();} public void land(){l.land();} }
버전 2 - 구현체 이용
DataBase 관련 클래스 예시
만약 특정 DB를 사용하는데 필요한 클래스를 사용해 프로그램을 작성했다면? → 나중에 DB 변경을 하려면, DB관련 부분은 모두 변경해야했을 것!
그렇게 하지 않으려면?
- DB관련 인터페이스 정의 → java에서
- db 커넥션시 메서드 명은 connect로 한다.
- 이때 매개변수로는 host, port, user, password를 받는다 등….
- 이러한 인터페이스를 상속받은 DB 클래스를 작성해서 제공 → 각 DB 회사
- 우리는 이렇게 작성된 class를 이용, 그렇기 때문에 DB를 변경하더라도 그 DB가 java에서 제공한 인터페이스를 상속받아 class를 작성했을 것이기 때문에 맨 윗부분!만 변경하고, 나머지는 변경하지 않아도 됨!!
인터페이스의 이해
- 인터페이스를 이해하기 위한 포인트
- 클래스를 1. 사용하는 쪽과 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 25 26 27 28
class A{ public void a(B b){ b.b(); } } class B{ public void b(){ System.out.println("b()"); } } class C{ public void b(){ System.out.println("c()"); } } class InterfaceTest{ public static void main(String[] args[]){ A a = new a(); a.a(new B()); a.a(new C());//에러 -> 호출 불가능!! } } //실행결과 b()
간접적인 관계: 한쪽 변경 ≠ 나머지 한쪽도 변경, 인터페이스를 이용해 간접적으로 호출!
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
Interface I{ void b(); } class B implements I{ public void b(){ System.out.println("b()"); } } class C implements I{ public void b(){ System.out.println("c()"); } } class A{ public void a(I i){ i.b(); } } class InterfaceTest{ public static void main(String[] args[]){ A a = new a(); a.a(new B()); a.a(new C()); } } //실행결과 b(); c();
⇒ 이처럼 인터페이스를 이용해 간접적인 방법을 이용해 클래스B가 아닌, 다른 클래스(C)를 제공 받아도 문제가 없었다.
- 구현체를 전달받는 방법
- 매개변수를 통해 인터페이스 I를 구현한 인스턴스를 동적으로 제공 받음 - ex) Thread Class
- 제3자를 통해서 전달 받음 - ex) JDBC class
디폴트 메서드와 static메서드
- static메서드
- 원래 추가하지 못할 이유가 없었음, static메서드는 인스턴스와 관계없기 때문에
- 규칙을 단순화하려고, 모든 메서드는 추상 메서드여야한다는 규칙에 예외를 두지 않음 → 이로인해 인터페이스와 관련된 static메서드는 별도의 클래스에 따로 둠(Collections클래스)
⇒ JDK 1.8 부터 가능함, 알던 것과 같은 내용
- 디폴트 메서드
- 정의: 추상메서드의 기본적인 구현을 제공하는 메서드,
default
키워드를 붙임
- 정의: 추상메서드의 기본적인 구현을 제공하는 메서드,
접근제어자가 default인 메서드가 아님
- 추상 메서드와 달리 몸통이 있어야함
- 추상 메서드가 아니기 때문에, 해당 인터페이스를 구현한 클래스를 변경하지 않아도 됨
- 조상 클래스에 새로운 메서드를 추가 한 것과 동일
- 디폴트 메서드와 기존 메서드와 중복되었을 시 해결규칙
디폴트 메서드와 여러 인터페이스의 중복
인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야함
디폴트 메서드와 조상 클래스의 메서드의 중복
조상클래스의 메서드가 상속되고, 디폴트 메서드는 무시됨
내부클래스(inner class)
클래스 내부에 선언되는 점 외에는 일반적인 클래스와 같기에, 내부 클래스의 몇가지 특징만 이해하면 됨
사용빈도가 높지 않기에 기본 원리와 특징을 이해하는 정도까지만 학습해도 충분함
내부 클래스란?
- 의미: 클래스 내에 선언된 클래스
- 사용 이유: 두 클래스가 서로 긴밀한 관계에 있어서
- 장점
- 두 클래스의 멤버간에 서로 쉽게 접근할 수 있음
- 캡슐화: 외부에는 불필요한 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있음
내부 클래스의 종류와 특징
종류: 변수의 선언 위치와 같음
- 인스턴스 클래스: 외부 클래스의 멤버변수 선언 위치에 선언, 인스턴스 멤버들과 관련된 작업
- 스태틱 클래스: 외부 클래스의 멤버변수 선언 위치에 선언, 스태틱 멤버들과 관련된 작업
- 지역 클래스: 외부 클래스의 메서드나 초기화블럭안에 선언, 선언된 내부에서만 사용될 수 있음
- 익명 클래스: 클래스의 선언과 객체의 생성을 동시에하는 이름 없는 클래스(일회용)
내부 클래스의 선언
1
2
3
4
5
6
7
8
class Outer{
class InstanceInner{} // 인스턴스 클래스
static class StaticInner{} // 스태틱 클래스
void myMethod(){
class LocalInner(){} // 지역 클래스
}
}
내부 클래스의 선언 위치 = 변수의 선언위치
내부 클래스의 제어자와 접근성
- 제어자 abstract나 final, 접근제어자 등의 제어자도 선언 가능
- static멤버는 static클래스만 가능하지만, static final 변수 즉 상수 이므로 모든 내부 클래스에서 정의 가능
- 접근성
- 인스턴스 클래스 → 외부클래스의 인스턴스 멤버: 바로 사용 가능(private도 가능, static도 가능)
- 스태틱 클래스 → 외부클래스의 인스턴스 멤버: 바로 사용 불가능
- 지역 클래스 → 외부클래스의 인스턴스 멤버: 바로 사용 불가능, 상수만! 가능
- 내부 클래스 내에서
this
또는내부클래스명.this
를 통해 서로 구별할 수 있음
- 컴파일시 생성되는 파일
- 한 파일안에 여러 class를 정의를 하면, 각 class별로 class파일이 만들어짐
- 내부 클래스의 컴파일된 class 파일 이름은
외부 클래스명$내부 클래스명.class
이고, 지역 클래스 경우에만 내부 클래스명 앞에 숫자가 붙음(중복 방지를 위해)
익명 클래스(anonymous class)
- 정의: 이름이 없는 클래스
- 특징
- 이름이 없음 → 생성자를 가질 수 없음
- 선언과 객체의 생성을 동시에 함
- 단 한번만 사용될 수 있음
- 오직 하나의 객체만을 생성함(일회용 클래스)
조상 클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해 정의 → 단 한개의 클래스를 상속받거나 혹은 단 한개의 인터페이스만 구현할 수 있음
1 2 3
new 조상클래스이름 or 구현인터페이스이름 (){ //멤버선언 }
- 컴파일된 class 파일 이름은
외부 클래스명$숫자.class
임