상속(inheritance)
상속의 정의와 장점
상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.
코드의 재사용성을 높이고 코드의 중복을 제거하여 프로그램의 생산성과 유지보수에 크게 기여한다.
조상클래스 : 부모(parent)클래스, 상위(super)클래스, 기반(base)클래스
자손클래스 : 자식(child)클래스, 하위(sub)클래스, 파생된(derived)클래스
자손클래스는 조상 클래스의 모든 멤버를 상속받는다.(생성자, 초기화블럭 제외)
조상 클래스가 변경되면 자손 클래스는 자동적으로 영향을 받게 되지만, 자손 클래스가 변경되는 것은 조상 클래스에 아무런 영향을 주지 못한다.
상속을 거듭할수록 상속받는 클래스의 멤버 개수는 점점 늘어나게 된다. (상속의 키워드가 ‘extends’인 이유이기도 함.)
-생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
-자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.
클래스간의 관계 - 포함(Composite)관계
클래스 간의 포함관계를 맺어 주는 것은 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것을 뜻한다.
포함관계를 맺어주면, 단위클래스별로 코드가 작게 나뉘어 있어서 코드를 관리하기 수월하다.
클래스간의 관계 결정하기
상속관계 : ~은 ~이다(is-a) ex)원은 점이다.
포함관계 : ~은 ~을 가지고있다(has-a) ex)원은 점을 가지고 있다.
is-a문장이 성립하면 서로 상속관계를 맺어주고, has-a문장이 성립하면 포함관계를 맺어준다.
단일 상속(single inferitance)
자바에서는 오직 단일 상속만을 허용한다. 그래서 둘 이상의 클래스로부터 상속을 받을 수 없다. 그로인해 클래스 간의 관계가 보다 명확해지고 코드를 더욱 신뢰할 수 있다.
Object클래스 - 모든 클래스의 조상
Object클래스는 모든 클래스 상속계층도의 최상위에 있는 조상클래스이다. 다른 클래스로부터 상속 받지 않는 모든 클래스들은 자동적으로 Object클래스로부터 상속받게 된다. 그동안 toString()이나 equals(Object o)와 같은 메서드를 따로 정의하지 않고도 사용할 수 있었던 이유는 이 메서드들이 Object클래스에 정의된 것들이기 때문이다.
오버라이딩(overriding)
오버라이딩이란?
조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것. 사전정 의미는 ‘~위에 덮어쓰다’이다.
오버라이딩의 조건
오버라이딩은 메서드의 내용만 새로 작성하는 것이므로 메서드의 선언부는 조상의 것과 완전히 일치해야 한다.
자손 클래스에서 오버라이딩하는 메서드는 조상클래스의 메서드와
-이름이 같아야 한다.
-매개변수가 같아야 한다.
-반환타입이 같아야 한다.
즉 오버라이딩을 통하여 구현부만 변경이 가능하다. 다만 접근 제어자와 예외는 제한된 조건 하에서만 다르게 변경할 수 있다.
접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경 할 수 없다.
public > protected > (default) > private
대부분의 경우 같은 범위의 접근 제어자를 사용한다.
조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
예외의 개수 또한 적거나 같아야 한다.
인스턴스메서드를 static메서드로 또는 그 반대로 변경할 수 없다.
오버로딩vs오버라이딩
둘은 관계가 없다.
오버로딩(overloading) : 기본에 없는 새로운 메서드를 정의하는 것(new) → 이름이 같을 뿐
오버라이딩(overriding) : 상속받은 메서드의 내용을 변경하는 것(change, modify) → 기존의 것을 변경
super (this와 유사함)
super는 자손클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다.
멤버변수와 지역변수의 이름이 동일할 때 this를 붙여서 구별했듯이 상속받은 멤버와 자신의 멤버와 이름이 동일할 경우 super를 붙여서 구별할 수 있다.
this와 마찬가지로 super역시 static메서드에서는 사용할 수 없고 인스턴스메서드에서만 사용가능하다.
super() - 조상 클래스의 생성자
super()는 조상 클래스의 생성자를 호출하는데 사용된다.
생성자의 첫 줄에서 조상클래스의 생성자를 호출해야하는 이유는 자손클래스의 멤버가 조상 클래스의 멤버를 사용할 수도 있으므로 조상의 멤버들이 먼저 초기화되어 있어야 하기 떄문이다.
Object클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자, this() 또는 super(),를 호출해야 한다. 그렇지 않으면 컴파일러가 자동적으로 super();를 생성자의 첫 줄에 삽입한다.
package와 import
패키지(package)
클래스의 묶음이다. 클래스 또는 인터페이스를 포함시키며, 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리할 수 있다.
클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리이다.
- 하나의 소스파일에는 첫 번째 문장으로 단 한번의 패키지 선언만을 허용한다.
- 모든 클래스는 반드시 하나의 패키지에 속해야 한다.
- 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다.
- 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.
패키지의 선언
클래스의 인터페이스의 소스파일(.java)의 맨 위에 다음과 같이 한 줄만 적어주면 된다.
package 패키지명;
대소문자를 모두 허용하나 클래스명과 쉽게 구분하기 위해 소문자로 하는 것을 원칙으로 한다.
소스파일에 자신이 속할 패키지를 지정하지 않은 클래스는 자동적으로 ‘이름 없는 패키지’에 속하게 된다.
Class path
- 클래스파일(*.class)의 위치를 알려주는 경로
- 환경변수 classpath로 관리, 구분자로 ‘;’사용.
환경변수 : OS에서 관리하는 변수
classpath를 환경변수에 추가해주는 이유 : 실행 시 JVM이 클래스를 찾을 수 있다.
JVM은 실행하려는 클래스 파일을 찾지 못하면 환경변수에 등록된 classpath를 차례대로 찾아본다.
import문
클래스의 코드를 작성하기 전에 import문으로 사용하고자 하는 클래스의 패키지를 미리 명시해주면 소스코드에 사용되는 클래스이름에서 패키지명은 생략할 수 있다.
컴파일 시에 컴파일러는 import문을 통해 소스파일에 사용된 클래스들의 패키지를 알아 낸 다음, 모든 클래스이름 앞에 패키지명을 붙여 준다.
import문의 선언
일반적인 소스파일(*.java)의 구성
- package문
- import문
- 클래스 선언
클래스이름을 지정해주는 대신 ‘*’을 사용하면, 컴파일러는 해당 패키지에서 일치하는 클래스이름을 찾아야 하는 수고를 더 해야 할 것 이다. 실행 시 성능상의 차이는 전혀 없다.
System과 String 같은 java.lang패키지의 클래스들을 패키지명 없이 사용할 수 있었던 이유는 모든 소스파일에는 묵시적으로 import java.lang.*;문장이 선언되어 있기 때문이다.
static import문
static import문을 사용하면 static멤버를 호출할 때 클래스 이름을 생략할 수 있다.
제어자(modifier)
제어자란?
제어자는 클래스, 변수 또는 메서드의 선언부와 함께 사용되어 부가적인 의미를 부여한다.
- 접근 제어자 : public, protected, default, private (4개 중 1개씩만 사용)
- 그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp
제어자는 클래스나 멤버변수와 메서드에 주로 사용되며, 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능하다. 단 접근제어자는 한번에 하나만 사용가능.
static - 클래스의, 공통적인
클래스변수(static멤버변수)는 인스턴스에 관계없이 같은 값을 갖는다. 그 이유는 하나의 변수를 모든 인스턴스가 공유하기 때문이다.
static이 붙은 멤버변수와 메서드, 그리고 초기화 블럭은 인스턴스가 아닌 클래스와 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용할 수 있다.
static이 사용될 수 있는 곳 - 멤버변수, 메서드, 초기화 블럭
static이 멤버변수에 붙을 경우
- 모든 인스턴스에 공통적으로 사용되는 클래스변수가 된다.
- 클래스변수는 인스턴스를 생성하지 않고도 사용 가능하다.
- 클래스가 메모리에 로드될 때 생성된다.
static이 메서드에 붙을 경우
- 인스턴스를 생성하지 않고도 호출이 가능한 static메서드가 된다.
- static메서드 내에서는 인스턴스멤버들을 직접 사용할 수 없다. ( 객체를 만들어야 사용 가능하니까)
final - 마지막의, 변경될 수 없는
final은 거의 모든 대상에 사용될 수 있다.
클래스에 사용될 경우
- 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다. 그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다.
메서드에 사용될 경우
- 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다.
멤버변수/지역변수에 사용될 경우
- 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다.
abstract - 추상의, 미완성의
메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언하는데 사용된다.
클래스에 abstract가 사용될 경우
- 클래스 내에 추상 메서드가 선언되어 있음을 의미함
메서드에 abastact가 사용될 경우
- 선언부만 작성하고 구현부는 작성하지 않은 추상메서드임을 의미함.
추상클래스는 미완성 메서드가 존재하는 ‘미완성 설계도’이므로 인스턴스를 생성하지 못한다.
이 클래스 자체로는 쓸모가 없지만, 다른 클래스가 이 클래스를 상속받아서 일부의 원하는 메서드만 오버라이딩해도 된다는 장점이 있다.
접근 제어자(access modifier)
접근제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다.
접근 제어자가 사용될 수 있는 곳 - 클래스, 멤버변수 , 메서드 , 생성자
- private : 같은 클래스 내에서만 접근 가능
- (default) : 같은 패키지 내에서만 접근 가능 (접근 제어자 없을 경우 default임을 뜻함)
- protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능
- public : 접근제한 없음
접근범위 : public > protected > (default) > private
클래스에는 public과 default만 올 수 있고, 메서드와 멤버변수에는 4가지 모두 사용 가능.
접근 제어자를 이용한 캡슐화
접근제어자를 사용하는 이유
- 외부로부터 데이터를 보호하기 위해서
- 외부에는 불필요한, 내부적으로만 사용되는, 부분을 감추기 위해서
데이터가 유효한 값을 유지하도록, 또는 외부에서 함부로 변경하지 못하도록 하기 위해서는 외부로부터의 접근을 제한하는것이 필요한데 이것을 데이터 감추기(data hiding)라고 하며, 객체지향개념의 캡슐화에 해당하는 내용이다.
제어자(modifier)의 조합
제어자를 조합하여 사용할 때 주의사항
- 메서드에 static과 abstract를 함께 사용할 수 없다. static메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문이다.
- 클래스에 abstract와 final을 동시에 사용할 수 없다. 클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고 abstract는 상송을 통해서 완성되어야 한다는 의미이므로 서로 모순이기 때문이다.
- abstract메서드의 접근 제어자가 private일 수 없다. abstract메서드는 자손클래스에서 구현해주어야 하는데 접근제어자가 private이면 자손클래스에서 접근할 수 없기 때문이다.
- 메서드에 private과 final을 같이 사용할 필요는 없다. 접근제어자가 private인 메서드는 오버라이딩이 될 수 없기 때문에 이 둘 중 하나만 사용해도 의미가 충분하다.
다형성(polymorphism)
다형성이란?
조상타입 참조변수로 자손타입 객체를 다루는 것.
‘여러 가지 형태를 가질 수 있는 능력’을 의미하며 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다는 것이다.
CationTv c = new CaptionTv();
Tv t = new CaptionTv();
위의 코드에서 Tv클래스가 부모이고 CationTv클래스가 자손이라고 할 경우에 c와 t는 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.
자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 존재하지 않는 멤버를 사용하고자 할 가능성이 있으므로 허용되지 않는다. 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.
참조변수의 타입이 참조변수가 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 개수를 결정한다!
참조변수의 형변환
자손타입 → 조상타입(Up-casting) : 형변환 생략가능
자손타입 ← 조상타입(Down-casting) : 형변황 생략불가
*부모-자식 관계는 형변환 가능하나 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용불가. 그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인 필요.
(컴파일까지는 되지만 실행하던 중간에 에러가 날 수 있다.)
형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.
단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것뿐이다.
instanceof연산자
참조변수가 참조하고 있는 인스턴스의 실제타입을 알아보기 위해 사용한다.
참조변수의 형변환 가능 여부 확인에 사용.
연산의 결과로 boolean값인 true 와 false 중 하나를 반환한다.
어떤 타입에 대한 instanceof연산의 결과가 truf라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.
참조변수와 인스턴스의 연결
조상 타입의 참조변수와 자손 타입의 참조변수의 차이점은 사용할 수 있는 멤버의 개수에 있다.
그리고 메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 겨웅에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.
매개변수의 다형성
다형성 장점 두가지
- 다형적 매개변수
- 하나의 배열로 여러종류 객체 다루기
참조형 매개변수는 메서드 호출시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다.
class Product {
int price;//제품의 가격
int bonusPoint;//제품구매 시 제공하는 보너스 점수
}
class Tv extends Product{}
class Computer extends Product{}
class Audio extends Produce{}
class Buyer {
int money = 1000;//소유금액
int bonusPoint = 0;//보너스점수
void buy(Tv t){
//Buyer가 가진 돈에서 제품의 가격만큼 뺀다.
money = money - t.price;
//Buyer의 보너스점수에 제품의 보너스점수를 더한다.
bonusPoint = bonusPoint + t.bonusPoint;
}
}
만약 Buyer가 Tv를 사려고 할 때, buy메서드를 매개변수를 Tv t로 하여 구입을 했다.
근데 컴퓨터도 사야하고 오디오도 사야한다면!?
void buy(Product p){
money = money - p.money;
bonusPoint = bonusPoint - p.bonusPoint;
}
buy메서드의 매개변수를 Product로 변경해주니 매번 오버라이딩 할 필요가 없이 메서드 하나로 해결했다.
매개변수가 Product타입의 참조변수라는 것은, 메서드의 매개변수로 Product클래스의 자손타입의 참조변수면 어느 것이나 매개변수로 받아들일 수 있다는 뜻이다.
여러 종류의 객체를 배열로 다루기
조상타입의 참조변수로 자손타입의 객체를 참조하는 것이 가능하다. 즉, 조상타입의 배열에 자손들의 객체를 담을 수 있다.
추상클래스(abstract class)
추상클래스란?
클래스를 설계도에 비유한다면, 추상클래스는 미완성 설계도에 비유할 수 있다. 클래스가 미완성이라는 것은 미완성 메서드(추상메서드)를 포함하고 있다는 의미이다.
미완성 설계도로 제품을 만들 수 없듯이, 추상클래스로 인스턴스는 생성할 수 없다.
추상클래스는 상속을 통해서 자손클래스에 의해서만 완성될 수 있다.
(추상클래스에도 생성자가 있으며, 멤버변수와 메서드도 가질 수 있다.)
미완성 설계도같은 추상클래스를 왜 쓸까?
아이폰을 예로 들었을 때, 아이폰 13과 14는 기능이 거의 같고 생김새도 비슷하다. 다른점은 14가 신형이라 조금 더 기능이 많다던지 카메라가 더 좋을 뿐,,, 이럴때 어느정도 아이폰의 틀을 갖춘 미완성 아이폰 설계도가 있다면 아이폰14를 처음부터 끝까지 만들 필요가 없고 미완성된 설계도에서 기능을 조금 추가하면 아이폰14가 완성되어 수고스러움을 덜어준다.
추상메서드(abstract method)
메서드 = 선언부 + 구현부(몸통)
추상메서드 = 선언부
서언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상메서드이다.
/* 회원가입을 위한 메서드 */
abstract 리턴타입 join();
추상메서드는 앞에 ‘abstract’를 붙여 주고, 구현부가 없으므로 {} 없이 ;을 적어준다.
(아무런 내용도 없이 {}만 있어도 일반 메서드로 간주한다.)
추상클래스로부터 상속받은 자손클래스는 오버라이딩을 통해 추상메서드를 모두 구현해주어야 하는데, 모든 추상메서드를 구현했을 경우 자손클래스에 ‘abstract’를 붙이지 않아도 된다. 하지만 하나라도 추상메서드가 남아있다면 자손클래스 역시 추상클래스로 지정해 주어야 한다.
추상메서드를 사용하는 이유?
메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스에서는 선언부만 작성하고, 주석을 덧붙여 정보를 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워두는 것이다.
추상클래스의 작성
추상 : 낱낱의 구체적 표상이나 개념에서 공통된 성질을 뽑아 이를 일반적인 개념으로 파악하는 정신 작용.
추상화는 기존의 클래스의 공통부분을 뽑아내어 조상 클래스를 만드는 것이라고 할 수 있다.
상속계층도를 따라 올라갈수록 공통요소만 남게 되는 것이다.
추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업
구체화 : 상속을 통해 클래스를 구현, 확장하는 작업
class Marine { //보병
int x,y;//현재위치
void move(int x, int y){/*지정된 위치로 이동*/}
void stop() {/*현재 위치에 정지*/}
void stimPack() {/*스팀팩을 사용한다.*/}
}
class Tank { //탱크
int x,y;//현재위치
void move(int x, int y){/*지정된 위치로 이동*/}
void stop() {/*현재 위치에 정지*/}
void changeMode() {/*공격모드를 변환한다.*/}
}
class Dropship { //수송선
int x,y;//현재위치
void move(int x, int y){/*지정된 위치로 이동*/}
void stop() {/*현재 위치에 정지*/}
void load() {/*선택된 대상을 태운다.*/}
void unload() {/*선택된 대상을 내린다.*/}
}
위의 코드에서 공통부분 발견!
abstract class Unit{
int x,y;
abstract void move(int x, int y);
void stop() {/*현재 위치에 정지*/}
}
class Marine extends Unit{ //보병
void move(int x, int y){/*지정된 위치로 이동*/}
void stimPack() {/*스팀팩을 사용한다.*/}
}
class Tank extends Unit{ //탱크
void move(int x, int y){/*지정된 위치로 이동*/}
void changeMode() {/*공격모드를 변환한다.*/}
}
class Dropship extends Unit{ //수송선
void move(int x, int y){/*지정된 위치로 이동*/}
void load() {/*선택된 대상을 태운다.*/}
void unload() {/*선택된 대상을 내린다.*/}
}
Unit클래스로 공통부분을 추출하였다. 이 때, move메서드가 추상메서드로 선언된 것에는 자손클래스가 자신에게 알맞게 구현해야 한다는 의미가 담겨있다.
추상화된 코드는 구체화된 코드보다 유연하여 변경에 용이하다는 장점이 있다.
인터페이스(interface)
인터페이스란?
추상메서드의 집합, 구현된 것이 전혀 없는 설계도(껍떼기) → 모든 멤버가 public
인터페이스는 추상클래스보다 추상화정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다.
오직 추상메서드와 상수만을 멤버로 가질 수 있다.
추상메서드와의 차이점
추상메서드는 일반클래스인데 추상메서드를 가진다.(일부가 미완성) 부분적으로만 완성된 ‘미완성 설계도’
인터페이스는 추상메서드만 존재한다. 구현된거1도 없음. 밑그림만 그려져 있는 ‘기본 설계도’
인터페이스의 작성
interface 인터페이스이름 {
public static final 타입 상수이름 = 값;
public abstract 메서드이름(매개변수목록);
}
인터페이스는 키워드를 class대신 interface를 사용하고 접근제어자로 public 또는 default를 사용할 수 있다.
인터페이스 멤버들의 제약사항
- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다. 단, static메서드와 디폴트 메서드는 예외(JDK1.8부터)
인터페이스의 상속
인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중상속(조상이 여러개)이 가능하다.
*인터페이스는 클래스와 달리 Object클래스와 같은 최고 조상이 없다.
인터페이스의 구현
인터페이스에 정의된 추상 메서드들을 구현하는 것.
클래스는 확장한다는 의미의 키워드 ‘extends’를 사용하지만 인터페이스는 구현한다는 의미의 키워드 ‘implements’를 사용한다.
만일 구현하는 인터페이스의 메서드 중 일부만 구현한다면, abstract를 붙여 추상클래스로 선언해야한다.
추상클래스와 인터페이스
공통점 : 추상메서드를 가진다.(둘다 미완성)
차이점 : 인터페이스는 인스턴스변수를 가질 수 없다. 그리고 구현된게 전혀 없다.
인터페이스를 이용한 다중상속
인터페이스는 static상수만 정의할 수 있으므로 조상클래스의 멤버변수와 충돌하는 경우는 거의 없고 충돌된다 하더라도 클래스 이름을 붙여서 구분이 가능하다. 그리고 추상메서드는 구현내용이 없으므로 조상클래스의 것과 선언부가 일치하면 조상쪽의 메서드를 상속받으면 되므로 문제가 없다.
그러나 이렇게 하면 다중상속의 장점을 잃어버린다. 다음은 인터페이스를 이용한 다중상속의 예제코드이다.
public class Tv {
protected boolean power;
protected int channel;
protected int volume;
public void power(){ power != power; }
public void channelUp() { channel++; }
public void channelDown() { channel--; }
public void volumeUp(){ volume++; }
public void volumeDown(){ volume--; }
}
public class VCR{
protected int counter;//VCR의 카운터
public void play(){ //Tape를 재생한다. }
public void stop(){ //재생을 멈춘다. }
public void reset(){ conter = 0; }
public int getCounter(){ return counter; }
public void setCounter(int c){ counter = c; }
}
public interface IVCR{
public void play();
public void stop();
public void reset();
public int getCounter();
public void setCounter(int c);
}
//IVCR인터페이스를 구현하고 Tv클래스로부터 상속받는 TVCR클리스 작성(다중상속 구현)
public class TVCR extends Tv implements IVCR{
VCR vcr = new VCR();
public void play(){ vcr.play(); }//코드를 작성하는 대신 VCR인스턴스의 메서드 호출
public void stop(){ vcr.stop(); }
public void reset(){ vcr.reset(); }
public int getCounter(){ return vcr.getCounter(); }
public void setCounter(int c){ vcr.setCounter(c); }
}
인터페이스를 이용한 다형성
인터페이스 타입 매개변수는 인터페이스를 구현한 클래스의 객체만 가능하다.
예시)인터페이스 Fightable을 클래스 Fighter가 구현했을 때, Fighter인스턴스를 Fighterable타입의 참조변수로 참조하는 것이 가능하다.
Fightable f = (Fightable) new Fighter();
//or
Fightable f = new Fighter();
메서드의 리턴타입으로 인터페이스의 타입을 지정하는 것 역시 가능하다.
Fightable method(){
...
Fighter f = new Fighter();
return f;
}
리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.
인터페이스의 장점
- 개발시간을 단축시킬 수 있다. 메서드를 호출하는 쪽에서 선언부만 알면 되기 때문에 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있다.
- 표준화가 가능하다. 기본 틀을 인터페이스로 작성하여 보다 일관되고 정형화된 프로그램의 개발이 가능하다.
- 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다. 서로 아무 관계 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하게 함으로 써 관계를 맺어줄 수 있다.
- 독립적인 프로그래밍이 가능하다. 인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 독립적인 프로그램 작성이 가능하다.(느슨한 결합)
인터페이스의 이해
인터페이스를 이해하기 위해서 염두에 두어야할 두가지
- 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
- 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다.(내용은 몰라도 된다.)
interface I{
public abstract void methodB();
}
class B implements I{
public void methodB(){
System.out.println("methodB in B class");
}
}
class A{//A-B 강한결합 : 직접적인 관계
public void methodA(B b){
b.methodB();
}
}
class A{//A-I-B 느슨한결합 : 간접적인 관계
public void methodA(I i){
i.methodB();
}
}
A클래스는 B클래스의 메서드를 호출하지만 I로 한겹 포장을 했기 때문에 클래스 A는 껍떼기 안에 어떤 알맹이가 들어 있는지 몰라도 된다.
디폴트 메서드와 static메서드
원래는 인터페이스에 추상 메서드만 선언할 수 있는데, JDK1.8부터 디폴트 메서드와 static메서드도 추가할 수 있게 되었다.(static메서드는 인스턴스와 관계가 없는 독립적인 메서드이기 때문에 추가하지 못할 이유 없음)
디폴트 메서드
인터페이스에 메서드를 추가한다는 것은, 추상 메서드를 추가한다는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현해야 한다는 뜻이다.
그래서 생겨난게 디폴트 메서드(default method)이다.
디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 추가되어도 해당 구현 클래스를 변경하지 않아도 된다.
디폴트 메서드는 앞에 default키워드를 붙이며, 일반 메서드처럼 {}이 있어야 한다. 접근제어자는 public이며 생략가능하다.
새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우 해결법(외우기 귀찮으면 그냥 직접 오버라이딩)
- 여러 인터페이스의 디폴트 메서드 간의 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야 한다.
- 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
- 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.(조상것이 우선)
내부 클래스(inner class)
내부 클래스란?
클래스 내에 선언된 클래스이다.
내부 클래스의 장점
- 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
- 코드의 복잡성을 줄일 수 있다.(외부에는 불필요한 클래스를 감춤,캡슐화)
내부 클래스의 종류와 특징
class Outer{
int iv = 0; //인스턴스변수
static int cv = 0; //스태틱변수
void myMethod(){
int lv = 0; //지역변수
}
}
class Outer{
class InstanceInner{} //인스턴스클래스
static class StaticInner{} //스태틱클래스
void myMethod(){
class LocalInner{} //지역클래스
}
}
내부 클래스의 제어자와 접근성
내부 클래스도 클래스이기 때문에 abstract나 final과 같은 제어자를 사용할 수 있을 뿐만 아니라, 멤버변수들처럼 private, protected과 접근제어자도 사용이 가능하다.
내부 클래스 중에서 스태틱 클래스(StaticInner)만 static멤버를 가질 수 있다.
다만 final과 static이 동시에 붙은 변수는 상수(constant)이므로 모든 내부 클래스에서 정의가 가능하다.
인스턴스클래스는 외부클래스의 인스턴스멤버를 객체생성 없이 바로 사용할 수 있지만, 스태틱 클래스는 외부 클래스의 인스턴스멤버를 객ㅊ체생성 없이 사용할 수 없다.
마찬가지로 인스턴스클래스는 스태틱 클래스의 멤버들을 객체생성 없이 사용할 수 있지만, 스태틱 클래스에서는 인스턴스 클래스의 멤버들을 객체생성 없이 사용할 수 없다.
인스턴스멤버 → static멤버 (o)
인스턴스멤버 ← static멤버 (x)
class Inner{
private int outerIv = 0;
static int outerCv = 0;
class InstanceInner {
int iiv = outerIv; //외부 클래스의 private멤버도 접근가능.
int iiv2 = outerCv;
}
static class StaticInner{
//int siv = outerIv; //스태틱 클래스는 외부 클래스의 인스턴스멤버에 접근불가.
static int scv = outerCv;
}
void myMethod(){
int lv = 0; //지역변수(메서드 종료와 함께 소멸)
final int LV = 0; //JDK1.8부터 final 생략 가능
class LocalInner{
int liv = outerIv;
int liv2 = outerCv;
//외부 클래스의 지역변수는 final이 붙은 변수(상수)만 접근가능.
int liv3 = lv; //error!!(JDK1.8부터는 에러 아님 : final없어도 값이 바뀌지 않으면 상수취급)
int liv4 = LV; //OK!
}
}
}
컴파일시 생성되는 파일명 : ‘외부 클래스명$내부 클래스명.class’
익명 클래스(anonymous class)
익명 클래스는 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.
new 조상클래스이름(){
//멤버 선언
}
//또는
new 구현인터페이스이름(){
//멤버 선언
}
new 조상이름(){}으로 선언.
이름이 없어서 생성자도 가질 수 없으며, 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다.
오로지 단 하나의 클래스를 상속받거나 단 하나의 인터페이스만을 구현할 수 있다.
'Java' 카테고리의 다른 글
람다식(Lambda expression) (1) | 2022.09.30 |
---|---|
java.lang패키지와 유용한 클래스 (0) | 2022.09.03 |
예외처리 (0) | 2022.08.17 |
객체지향 프로그래밍1 (0) | 2022.08.17 |