1. 상속(inheritance)
1.1 상속의 정의와 장점
상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다. 상속을 통해서 클래스를 작성하면 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있고 코드를 공통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 매우 용이하다. 상속의 특징은 코드의 재사용성을 높이고 코드의 중복을 제거하여 프로그램의 생산성과 유지보수에 크게 기여하는 것이다.
class Child extends Parent {
//. . .
}
상속을 구현하는 방법은 새로 작성하고자 하는 클래스의 이름 뒤에 상속받고자 하는 클래스의 이름을 키워드 'extends'와 함께 써주면 된다. Parent, Child 두 클래스는 서로 상속 관계에 있다고 하며, 상속해주는 클래스를 '조상 클래스'라 하고 상속 받는 클래스를 '자손 클래스'라 한다.
조상 클래스 - 부모(parent)클래스, 상위(super)클래스, 기반(base)클래스
자손 클래스 - 자식(child)클래스, 하위(sub)클래스, 파생된(derived)클래스
class Parent {
int age;
}
class Child extends Parent{
void play()
System.out.println("놀자");
}
}
Parent클래스에 aeg라는 정수형 변수를 멤버변수로 추가하면, 자손 클래스는 조상의 멤버를 모두 상속받기 때문에, Child클래스는 자동적으로 age라는 멤버변수가 추가된 것과 같은 효과를 얻는다. 그러나 Child 클래스에 새로운 코드가 추가되어도 조상인Parent클래스는 아무런 영향도 받지 않는다. 여기서 알 수 있는 점은, 조상 클래스가 변경되면 자손 클래스는 자동적으로 영향을 받게 되지만 자손클래스가 ㅂㄴ경되는 것은 조상 클래스에 아무런 영향을 주지 못한다는 것이다.

자손 클래스는 조상 클래스의 모든 멤버를 상속받으므로 항상 조상 클래스보다 같거나 많은 멤버를 갖는다.
상속에 상속을 거듭할수록 상속받는 클래스의 멤버 개수는 점점 늘어나게 된다. 그래서 상속을 받는다는 것은 조상 클래스를 확장(extend)한다는 의미로 해석할수도 있으며 이것이 상속에 사용되는 키워드가 'extends'인 이유이기도 하다.조상 클래스만 변경해도 모든 자손 클래스에, 자손의 자손 클래스에까지 영향을 미치기 때문에, 클래스 간의 상속관계를 맺어 주면 자손 클래스들의 공통적인 부분은 조상 클래스에서 관리하고 자손 클래스는 자신의 정의된 멤버들만 관리하면 되므로 각 클래스의 코드가 적어져서 관리가 쉬워진다.
-생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
-자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
-자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.
class Parent{}
class child extends Parent{}
class child2 extends Parent{}
클래스 Child와 Child2rk ahen Parent클래스를 상속받고 있으므로 Parent클래스와 Child클래스, 그리고 Parent와 Child2클래스는 서로 상속관계에 있지만 클래스Child와 Child2간에는 서로 아무런 관계도 성립되지 않늗나. 클래스 간의 관계에서 형제 관계와 같은 것은 없다. 부모와 자식의 관계(상속)만이 존재할 뿐이다.
1.2 클래스간의 관계 - 포함관계
상속이외에도 클래스를 재사용하는 방법이 있는데, 그것은 클래스간에 '포함(Composite)' 관계를 맺어주는 것이다. 클래스 간의 포함관계를 맺어 주는 것은 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것을 뜻한다. 하나의 거대한 클래스를 작성하는 것보다 단위별로 여러 개의 클래스를 작성한 다음, 이 단위 클래스들을 포함관계로 재사용하면 보다 간결하고 손쉽게 클래스를 작성할 수 있다. 또한 작성된 단위 클래스들은 다른 클래스를 작성하는데 재사용될 수 있을것이다.
class Car{
Engnine e = new Engine();
Door[] d = new Door[4];
//. . .
}
위와 같읕 Car클래스를 작성할 때, Car클래스의 단위구성요소인 Engine, Door와 같은 클래스를 미리 작성해 놓고 이 들을 Car클래스의 멤버변수로 선언하여 포함관계를 맺어주면, 클래스를 작성하는 것도 쉽고 코드도 간결해서 이해하기 쉽다고 관리하는데도 수월하다.
1.3 클래스간의 관계 결정하기
클래스를 작성하는데 있어서 상속관계를 맺어 줄 것인지 포함관계를 맺어 줄 것인지 결정할 때는 '~은 ~이다(is~a)'와 '~은 ~을 가지고 있다(has a)'를 넣어서 문장을 만들어 보면 클래스 간의 관계가 보다 명확해 진다.
두 문장 중에서는 두번째 문장이 더 옳다는 것을 알 수 있다.'~은 ~이다'라는 문장이 성립한다면 서로 상속관계를 맺어 주고, '~은 ~을 가지고 있다.'는 문장이 성립한다면 포함관계를 맺어주면 된다.
1.4 단일상속(single inheritance)
다른 객체지향언어인 C++에서는 여러 조상 클래스로부터 상속받는 것이 가능한 '다중상속(multiple inheritance)을 허용하지만 자바에서는 오직 단일 상속만을 허용한다. 그래서 둘 이상의 클래스로부터 상속을 받을 수 없다.
다중상속을 허용하면 여러 클래스로부터 상속을 받을 수 있기 때문에 복합적인 기능을 가진 클래스를 쉽게 작성할 수 있다는 장점이 있지만, 클래스간의 관계가 매우 복잡해진다는 것과 다른 클래스로부터 상속받는 멤버간의 이름이 같은 경우 구별할 수 있는 방법이 없다는 단점을 가지고 있다.
class Tv{
boolean power;
int channel;
void power(){power = !power;}
void channelUp(){++channel;}
void channelDown(){--channel;}
}
class VCR{
boolean power;
int counter = 0;
void power(){power = !power;}
void play(){ /*내용 생략*/ }
void stop(){ /*내용 생략*/ }
void rew(){ /*내용 생략*/ }
void ff(){ /*내용 생략*/ }
}
class TVCR extends Tv{
VCR VCR = new VCR();
int counter = vcr.counter;
void play(){
vcr.play();
}
voic stop(){
vcr.stop();
}
voic rew(){
vcr.rew();
}
voic ff(){
vcr.ff();
}
}
자바는 다중상속을 허용하지 않으므로 Tv클래스를 조상으로 하고, VCR클래스는 TVCR클래스에 포함시켰다. 그리고 TVCR클래스에 VCR클래스의 메서드와 일치하는 선언부를 가진 메서드를 선언하고 내용은 VCR클래스의 것을 호출해서 사용하도록 했다. 외부적으로는 TVCR클래스의 인스턴스를 사용하는 것처럼 보이지만 내부적으로는 VCR클래스의 인스턴스를 생성해서 사용하는 것이다.
이렇게 함으로써 VCR클래스의 메서드의 내용이 변경되더라도 TVCR클래스의 메서드들 역시 변경된 내용이 적용되는 결과를 얻을 수 있을 것이다.
1.5 Object클래스 - 모든 클래스의 조상
Object클래스는 모든 클래스 상속계층도의 최상위에 있는 조상클래스이다. 다른 클래스로부터 상속 받지 않는 모든 클래스들은 자동적으로 Object클래스로부터 상속받게 함으로써 이것을 가능하게 한다.자바의 모든 클래스들은 Object클래스의 멤버를 상속받기 때문에 Object클래스에 정의된 멤버들을 사용할 수 있다. 그 동안 toString()이나 equals(Object o)와 같은 메서드를 따로 정의하지 않고도 사용할 수 있었던 이유는 이 메서드들이 Object클래스와 같은 모든 인스턴스가 가져야 할 기본적인 11개의 메서드가 정의되어 있기 때문이다.
toString() 메서드
Object 클래스에서 기본으로 제공하며 toString() 메서드는 해당 인스턴스에 대한 정보를 문자열로 반환한다.
이때 반환되는 문자열은 클래스 이름과 함께 구분자로 '@'가 사용되며, 그 뒤로 16진수 해시코드(hash code)가 추가된다.
16진수 해시 코드 값은 인스턴스의 주소를 가리키는 값으로, 인스턴스마다 모두 다르게 반환된다.
equals() 메서드
equals() 메서드는 해당 인스턴스를 매개변수로 전달받는 참조 변수와 비교하여, 그 결과를 반환한다.
equals()는 '=='연산자와 동일한 기능을 한다. 같은 객체일 경우에는 true를 리턴하고 객체가 다를 경우 false를 리턴한다.
close() 메서드
clone() 메서드는 해당 인스턴스를 복제하여 새로운 인스턴스를 생성해 반환한다. 하지만 Objet 클리스의 clone()메서드는 단지 필드의 값만을 복사하므로, 필드의 값이 배열이나 인스턴스면 제대로 복제할 수 없다.
이러한 경우에는 해당 클래스에서 clone() 메서드를 오버라이딩 하여, 복제가 제대로 이루어지도록 재정의 해야 한다.
그 외에 Object클래스에 정의된 메서드들은 아래와 같다.
| 메서드 | 설명 |
| protected Object clone() | 해당 객체의 복제본을 생성하여 반환 |
| boolean equals(Object obj) | 해당 객체와 전달받은 객체가 같은지 여부를 반환 |
| protected void finalize() | 해당 객체를 더는 아무도 참조하지 않아 가비지 컬렉터가 객체의 리소스를 정리하기 위해 호출 |
| Class<T> getClass() | 해당 객체의 클래스 타입을 반환 |
| int hashCode() | 해당 객체의 해시 코드값을 반환 |
| void notify() | 해당 객체의 대기(wait)하고 있는 하나의 스레드를 다시 실행할 때 호출 |
| void notifyAll() | 해당 객체의 대기(wait)하고 있는 모든 스레드를 다시 실행할 때 호출 |
| String toString() | 해당 객체의 정보를 문자열로 반환 |
| void wait() | 해당 객체의 다른 스레드가 notify()나 notifyAll() 메서드를 실행할 때까지 재 스레드를 일시적으로 대기(wait)시킬 때 호출함 |
| void wait(long timeout) | 해당 객체의 다른 스레드가 notify()나 notifyAll() 메서드를 실행하거나 전달받은 간이 지날 때까지 현제 스레드를 일시적으로 대기(wait)시킬 때 호출 |
| void wait(long timeout, int nanos) | 해당 객체의 다른 스레드가 notify()나 notifyAll()메서드를 실행하거나 전달받은 시간이 지나거나 다른 스레드가 현재 스레드를 인터럽트(interrupt)할 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출 |
2. 오버라이딩(overriding)
2.1 오버라이딩이란?
오버라이딩이란, 조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 말한다. 상속 받은 메서드를 그대로 사용하기도 하지만, 자손 클래스 자신에 맞게 변경해야 하는 경우가 많다. 이럴 때 조상의 메서드를 오버라이딩한다.
2.2 오버라이딩의 조건
오버라이딩은 메서드의 내용만을 새로 작성하는 것이므로 선언부는 조상의 것과 완전히 일치해야 한다. 요약하면 선언부가 서로 일치해야 한다는 것이다. 다만 접근제어자(access modifier)와 예외(exception)는 제한된 조건 하에서만 다르게 변경할 수 있다.
오버라이딩의 성립 조건 : 자손클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와
-이름이 같아야 한다
-매개변수가 같아야 한다
-반환타입이 같아야 한다
1. 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
: 만일 조상 클래스에 정의된 메서드의 접근제어자가 protected라면, 이를 오버라이딩하는 자손 클래스의 메서드는 접근 제어자가 이보다 커야 한다. 대부분의 경우 같은 범위의 접근 제어자를 사용한다.
2. 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
3.인스턴스메서드를 static메서드로 또는 그 반대로 변경할 수 없다.
Q. 조상 클래스에 정의된 static 메서드를 자손 클래스에서 똑같은 이름의 static메서드로 정의할 수 있나?
A.가능하자미나 이것은 각 클래스에 별개의 static메서드를 정의한 것일 분 오버라이딩이 아니다. 각 메서드는 클래스이름으로 구별될 수 있으며, 호출할 때는 '참조변수.메서드이름()'대신 '클래스이름.메서드이름()'으로 하는 것이 바람직하다. static멤버들은 자신들이 정의된 클래스에 묶여있다고 생각하면 도니다.
2.3 오버로딩 vs 오버라이딩
오버로딩(overloading) : 기존에 없는 새로운 메서드를 정의하느것(new)
오버라이딩(overriding) : 상속받은 메서드의 내용을 변경하는 것(change, modify)

2.4 super
super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다. 멤버변수와 지역변수의 이름이 같을 때 this를 붙여서 구별했듯이 상속받은 멤버와 자신의 멤버와 이름이 같을 때는 super를 붙여서 구별할 수 있다. 조상클래스로부터 상속받은 멤버도 자손 클래스 자신의 멤버이므로 super대신 this를 사용할 수도 있다. 그래도 조상 클래스의 멤버와 자손 클래스의 멤버가 중복 정의되어 서로 구별해야하는 경우에만 super를 사용하는 것이 좋다.
2.5 super() - 조상 클래스의 생성자
this()와 마찬가지로 super()역시 생성자이다. this()는 같은 클래스의 다른 생성자를 호출하는데 사용되지만, super()는 조상 클래스의 생성자를 호출하는데 사용된다. 자손 클래스의 인스턴스를 생성하면, 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다. 그래서 자손 클래스의 인스턴스가 조상 클래스의 멤버들을 사용할 수 있는것이다. 이 때 조상 클래스 멤버의 초기화 작업이 수행되어야 하기 때문에 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 한다.
생성자의 첫 줄에서 조상 클래스의 생성자를 호출해야 하는 이유는 자손 클래스의멤버가 조상 클래스의 멤버를 사용할 수도 있으므로 조상의 멤버들이 먼저 초기화되어 있어야 하기 때문이다. 이와 같은 조상 클래스 생성자의 호출은 클래스의 상속관계를 거슬러 올라가면서 계속 반복된다. 마지막으로 모든 클래스의 최고 조상인 Object클래스의 생성자인 Object()까지 가서야 끝이 난다.
Object클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자, this() 또는 super(), 를 호출해야 한다. 그렇지 않으면 컴파일러가 자동적으로 'super();'를 생성자에 첫줄에 삽입한다.
인스턴스를 생성할 때는 클래스를 선택하는 것만큼 생성자를 선택하는 것도 중요하다.
1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
2. 생성자 - 선택한 클래스의 어떤 생성자를 이용해서 인스턴스를 생성할 것인가?
3. package와 import
3.1 패키지(package)
패키지란, 클래스의 묶음이다. 패키지에는 클래스 또는 인터페이스를 포함시킬 수 있으며, 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리할 수 있다. 같은 이름의 클래스 일지라도 서로 다른 패키지에 존재하는 것이 가능하므로, 자신만의 패키지 체계를 유지함으로써 다른 개발자가 개발한 클래스 라이브러리의 클래스와 이름이 충돌하는 것을 피할 수 있다.
지금까지는 단순히 클래스 이름으로만 클래스를 구분했지만, 사실 클래스의 실제 이름(full name)은 패키지명을 포함한 것이다. 클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리이다.
ex) java.lang.String은 java.lang 패키지에 속한 String클래스다.
-하나의 소스파일에는 첫 번재 문장으로 단 한 번의 패키지 선언만을 허용한다.
-모든 클래스는 반드시 하나의 패키지에 속해야 한다.
-패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다.
-패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.
3.2 패키지의 선언
package 패키지명;
패키지를 선언하는 것은 클래스나 인터페이스의 소스파일(.java)의 맨 위에 다음과 같이 한 줄만 적어주면 된다.
위와 같은 패키지 선언문은 반드시 소스파일에서 주석과 공백을 제외한 첫 번째 문장이어야 하며, 하나의 소스파일에 단 한번만 선언될 수 있다. 해당 소스파일에 포함된 클래스나 인터페이스는 선언된 패키지에 속하게 된다. 패키지명은 대소문자를 모두 허용하지만, 클래스명과 쉽게 구분하기 위해서 소문자로 하는 것을 원칙으로 하고 있다.
모든 클래스는 반드시 하나의 패키지에 포함되어야 하는데 그럼에도 지금까지 소스파일을 작성할 대 패키지를 선언하지 않고도 문제가 없었던 이유는 자바에서 기본적으로 제공하는 '이름없는 패키지(unnamed package)' 때문이다. 소스파일에 자신이 속할 패키지를 지정하지 않은 클래스는 자동적으로 '이름없는 패키지'에 속하게 된다.
3.3 import문
소스코드를 작성할 때 다른 패키지의 클래스를 사용하려면 패키지명이 포함된 클래스 이름을 사용해야 한다. 클래스의 코드를 작성하기 전에 import문으로 사용하고자 하는 클래스의 패키지를 미리 명시해주면 소스코드에 사용되는 클래스 이름에서 패키지 명은 생략할 수 있다. import문의 역할은 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 제공하는 것이다. 컴파일 시에 컴파일러는 import문을 통해 소스파일에 사용된 클래스들의 패키지를 알아낸 다음, 모든 클래스이름 앞에 패키지명을 붙여준다.
※ import문은 프로그램의 성능에 전혀 영향을 미치지 않는다.
3.4 import문의 선언
모든 소스파일(.java)에서 import문은 package문 다음에, 그리고 클래스 선언문 이전에 위치해야 한다. import문은 package문과 달리 한 소스파일에 여러 번 선언할 수 있다.
일반적인 소스파일(.java)의 구성
① package문
② import문
③ 클래스 선언
import문을 선언하는 방법은 'import 패키지명.클래스명;' 또는 'import 패키지명.*;'이다. 키워드 import와 패키지명을 생략하고자 하는 클래스의 이름을 패키지명과 함께 써주면 된다. 같은 패키지 내에서 여러 개의 클래스가 사용될 때 import문을 여러 번 사용하는 대신 '패키지명.*;'을 ㅈ이용해서 지정된 패키지에 속하는 모든 클래스를 패키지명 없이 사용할 수 있다.
ex)
import java.util.*; / import java.text.*; 를 import java.*; 처럼 사용 가능
3.5 static import문
import문을 사용하면 클래스의 패키지명을 생략할 수 있는 것과 같이 static import문을 사용하면 static멤버를 호출할 때 클래스 이름을 생략할 수 있다. 특정 클래스의 static멤버를 자주 사용할 때 편리하다. 그리고 코드도 간결해진다
import static java.lang.Integer.*; //Integer클래스의 모든 static메서드
import static java.lang.Math.random; //Math.random()만. 괄호 안붙임.
import static java.lang.System.out; //System.out.을 out만으로 참조가능
만일 위와 같이 static import문을 선언하였다면 아래의 코드와 같이 간략히 할 수 있다.

'Back-end > Java_T.I.L' 카테고리의 다른 글
| [TIL 220111] 자바의정석 7. 객체지향 프로그래밍 II (3) (0) | 2022.01.12 |
|---|---|
| [TIL 220110] 자바의정석 7. 객체지향 프로그래밍 II (2) (0) | 2022.01.10 |
| [TIL 220108] 자바의정석 6. 객체지향 프로그래밍 I (2) (0) | 2022.01.08 |
| [TIL 220107] 자바의정석 6. 객체지향 프로그래밍 I (1) (0) | 2022.01.08 |
| 9. 예외처리 (0) | 2022.01.06 |
댓글