본문 바로가기

Java/기초

[Java] 객체지향 프로그래밍 3

1. 상속(ingeritance)

1) 상속의 정의와 장점

기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다. 상속을 통해서 클래스를 작성하면 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있고 코드를 공통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 매우 용이하다.

이러한 특징은 코드의 재사용성을 높이고 코드의 중복을 제거하여 프로그램의 생산성과 유지보수에 크게 기여한다.

자바에서 상속을 구현하는 방법은 새로 작성하고자 하는 클래스의 이름 뒤에 상속받고자 하는 클래스의 이름을 키워드 'extends'와 함께 써 주기만 하면 된다.

[참고] 서로 상속관계에 있는 두 클래스를 아래와 같은 용어를 사용해서 표현하기도 한다.

- 조상 클래스 : 부모(parent)클래스, 상위(super)클래스, 기반(base)클래스

- 자손 클래스 : 자식(child)클래스, 하위(sub)클래스, 파생된(derived)클래스

[참고] 접근 제어자(access modifier)가 private 또는 default인 멤버들은 상속되지 않는다기보다 상속은 받지만 자손 클래스로부터의 접근이 제한되는 것이다.

자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버도 함께 생성되기 때문에 따로 조상 클래스의 인스턴스를 생성하지 않고도 조상 클래스의 멤버들을 사용할 수 있다.

 

2. 포함관계

상속외에도 클래스를 재사용하는 또 다른 방법이 있는데, 그것은 클래스 간에 '포함(composite)'관계를 맺어 주는 것이다. 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것을 뜻한다.

class Point {   int x; int y; // x좌표, y 좌표   }

class Circle {

  Point c = new Point();// 원점

  int r ;

}

 

3. 클래스간의 관계 결정하기

클래스를 작성하는데 있어서 상속관계를 맺어 줄 것인지 포함관계를 맺어 줄 것인지 결정하는 것은 때때로 혼돈스러울 수 있다.

두 경우를 비교해 보면 Circle클래스를 작성하는데 있어서 Point클래스를 포함시키거나 상속받도록 하는 것은 결과적으로 별 차이가 없어 보인다.

그럴 때는 '~은 ~이다(is-a)'와 '~은 ~을 가지고 있다.(has-a)'를 넣어서 문장을 만들어 보면 클래스 간의 관계가 보다 명확해 진다.

- 원(Circle)은 점(Point)이다. -> Circle is a Point

- 원(Circle)은 점(Point)을 가지고 있다. -> Circle has a Point

원은 원점(Point)과 반지름으로 구성되므로 위의 두 문장을 비교해 보면 두 번째 문장이 더 옳다는 것을 알 수 있을 것이다.

- 상속관계 : '~은 ~이다.(is-a)'

- 포함관계 : '~은 ~을 가지고 있다.(has-a)'

 

3. 단일상속(single ingeritance)

다른 객체자향언어인 C++에서는 여러 조상 클래스로부터 상속받는 것이 가능한 '다중상속(multiple inheritance)을 허용하지만 자바에서는 단일 상속만 허용한다. 그래서 하나 이상의 클래스로부터 상속을 받을 수 없다.

다중상속을 허용하면 여러 클래스로부터 상속받을 수 있기 때문에 복합적인 기능을 가진 클래스를 쉽게 작성할 수 있다는 장점이 있지만, 클래스간의 관계가 매우 복잡해진다는 것과 서로 다른 클래스로부터 상속받은 멤버간의 이름이 같은 경우 구별할 수 있는 방법이 없다는 단점을 가지고 있다.

단일 상속은 클래스간의 관계가 보다 명확해지고 코드를 더욱 신뢰할 수 있게 만들어 준다는 점에서 다중상속보다 유리하다.

 

4. Object클래스 - 모든 클래스의 조상

Object클래스는 모든 클래스의 상속계층도의 최상위에 있는 조상클래스이다. 다른 클래스로부터 상속 받지 않는 모든 클래스들은 자동적으로 Object클래스로부터 상속받게 함으로써 toString()이나 equals(Object o)와 같은 메서드를 따로 정의하지 않고도 사용할 수 있다.

 

5. 오버라이딩(overriding)

조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버라이딩이라고 한다.

오버라이딩이 성립 하기 위해서는 자손 클래스에서 오버라이딩하는 메서드는 조상클래스의 메서드와

1) 이름이 같아야 한다.

2) 매개변수가 같아야 한다.

3) 반환타입이 같아야 한다.

[참고] JDK1.5부터 '공변 반환타입(covariant return type)'이 추가되어, 반환타입을 자손 클래스의 타입으로 변경하는 것은 가능하도록 조건이 완화되었다.

 

조상 클래스의 메서드를 자손클래스에서 오버라이딩할 때

1) 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경 할 수 없다.

- ex. 조상 클래스에 정의된 멤서드의 접근 제어자가 protected라면, 이를 오버라이딩하는 자손 클래스의 메서드는 접근 제어자가 pretected나 public이어야 한다.

[참고] 접근제어자(https://wikidocs.net/232)

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

2) 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.

3) 인스턴스메서드를 static메서드로 또는 그 반대로 변경할 수 없다.

* 조상 클래스에서 정의된 static메서드를 자손 클래스에서 똑같은 이름의 static메서드로 정의할 수 있나요?

-> 가능하다. 하지만, 이것은 각 클래스에 별개의 static메서드를 정의한 것일 뿐 오버라이딩이 아니다. 각 메서드는 클래스이름으로 구별될 수 있으며, 호출할 떄는 '참조변수.메서드이름()'대신 '클래스이름.메서드이름()'으로 하는 것이 바람직 하다. static멤버들은 자신들이 정의된 클래스에 묶여 있다고 생각하면 된다.

 

※ 오버로딩 vs 오버라이딩

- 오버로딩(overloading) : 기존에 없는 새로운 메서드를 정의하는 것(new)

- 오버라이딩(overriding) : 상속받은 메서드의 내용을 변경하는 것(change, modify)

 

6. super

자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다. 멤머변수외 지역변수의 이름이 같을 때 this를 사용해서 구별했듯이 상속받은 멤버와 자신의 클래스에 정의된 멤버의 이름이 같을 때는 super를 사용해서 구별할 수 있다.

조상 클래스로부터 상속받은 멤버도 자손 클래스 자신의 멤버이므로 super대신 this를 사용할 수 있다. 그래도 조상의 클래스의 멤버와 자손클래스의 멤버가 중복 정의되어 서로 구별해야하는 경우에만 super를 사용하는 것이 좋다.

[참고] 조상의 멤버와 자신의 멤버를 구별하는데 사용된다는 점을 제외하고는 super와 this는 근본적으로 같다. 모든 인스턴스메서드에는 자신이 속한 인스턴스의 주소가 지역변수로 저장되는데, 이것이 참조변수인 this와 super의 값이 된다.

statuc메서드(클래스메서드)는 인스턴스와 관련이 없다. 그래서 this와 마찬가지로 super 역시 static메서드에서는 사용할 수 없고 인스턴스메서드만 사용할 수 있다.

 

6.1 super() - 조상 클래스의 생성자

this()와 마찬가지로 super()역시 생성자이다. this()는 같은 클래스의 다른 생성자를 호출하는 데 사용되지만, super()는 조상 클래스의 생성자를 호출하는데 사용된다.

자손 클래스의 인스턴스를 생성하면, 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다. 그래서 자손 클래스의 인스턴스가 조상 클래스의 멤버들을 사용할 수 있는 것이다.

이 때 조상 클래스 멤버의 초기화 작업이 수행되어야 하기 때문에 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 한다.

생성자의 첫 줄에서 조상클래스의 생성자를 호출해야하는 이유는 자손 클래스의 멤버가 조상 클래스의 멤버를 사용할 수도 있으므로 조상의 멤버들이 먼저 초기화되어 있어야 하기 때문이다. 이와 같은 조상 클래스 생성자의 호출은 마지막으로 모든 클래스의 조상인 Object클래스의 생성자인 Object()까지 가서야 끝이 난다.

Object클래스를 제외한 모든 클래스의 생성자 첫 줄에는 생성자,this() 또는 super(),를 호출해야 한다. 그렇지 않으면 컴파일러가 자동적으로 'super();'를 생성자의 첫 줄에 삽입한다.

 

7. package와 import

7.1 패키지(package)

패키지란 클래스의 묶음이다. 패키지에는 클래스 또는 인터페이스를 포함시킬 수 있으며. 서로 관련된 클래스끼리 그룹 단위로 묶어 높음으로써 클래스를 효율적으로 관리할 수 있다. 클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리이다.

[참고] 클래스 파일들을 압축한 것이 jar파일(*.jar)이며, 이 파일의 압축을 풀면 *.class파일들로 이루어져 있다.

* 패키지 규칙

- 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.

- 모든 클래스는 반드시 하나의 패키지에 속해야 한다.

- 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다.

- 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.

 

7.2 import문

소스코드를 작성할 때 다른 패키지의 클래스를 사용하려면 패키지명이 포함된 클래스 이름을 사용해야 한다.

이클립스는 단축키 'ctrl + shift + o'를 누리면, 자동으로 import문을 추가해주는 편리한 기능을 가지고 있다.

[참고] import문은 프로그램의 성능에 전혀 영향을 미치지 않는다. import문을 많이 사용하면 컴파일 시간이 아주 조금 더 걸린 뿐이다.

* static import문

static import문을 사용하면 static멤버를 호출할 때 클래스 이름을 생략할 수 있다. 특정 클래스의 static멤버를 자주 사용할 때 편리하다. 그리고 코드도 간결해 진다.

'Java > 기초' 카테고리의 다른 글

[Java] 객체지향 프로그래밍 5  (0) 2020.08.30
[Java] 객체지향 프로그래밍 4  (0) 2020.07.19
[Java] 객체지향 프로그래밍 2  (0) 2020.07.08
[Java] 객체지향 프로그래밍 1  (0) 2020.06.24
[Java] 배열(array)  (0) 2020.06.23