Backend/Java

[Java] 객체지향 프로그래밍 2(Object-oriented Programming 2) - TIL 221219

짜잉이 2022. 12. 19. 16:33

📖 자바의 정석 Chapter 07 참고


1. 상속(inheritance)

1.1 상속의 정의와 장점

  • 상속: 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것.
  • 상속을 통해 클래스를 작성하면 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있고 코드를 공통적으로 관리할 수 있기 때문에 코드의 추가와 변경이 용이하다.
  • class Child extends Parent { // ... }
    상속받고자 하는 클래스 이름을 키워드 'extends' 뒤에 함께 써준다.
    이 두 클래스는 서로 상속 관계에 있다고 하며, 상속해주는 클래스를 '조상클래스', 상속 받는 클래스를 '자손 클래스'라 한다.
조상 클래스: 부모(parent)클래스, 상위(super)클래스, 기반(base)클래스
자손 클래스: 자식(child)클래스, 하위(sub)클래스, 파생된(derived)클래스
  • 자손 클래스는 조상클래스의 모든 멤버(멤버변수나 메서드)를 상속받는다. 상속을 받는다는 것은 조상 클래스를 확장(extend)한다는 의미이다. 
- 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
- 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
  • 같은 내용의 코드를 하나 이상의 클래스에 중복적으로 추가해야 하는 경우에는 상속관계를 이용해서 부모 클래스(조상 클래스)에 추가햐여 코드의 중복을 최소화해야한다.
  • 자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버도 함께 생성되기 때문에 따로 조상 클래스의 인스턴스를 생성하지 않고도 조상 클래스의 멤버들을 사용할 수 있다. 
자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.

 

1.2 클래스간의 관계 - 포함관계

  • 상속이외에도 클래스를 재사용하는 방법으로 클래스 간에 '포함(Composite)'관계를 맺어주는 방법이 있다.
  • 클래스 간의 포함관계를 맺어 주는 것한 클래스의 멤버변수다른 클래스 타입의 참조변수를 선언하는 것이다.
  • 단위별로 여러 개의 클래스를 작성한 다음, 이 단위 클래스들을 포함관계로 재사용하면 간결하고 손쉽게 클래스를 작성할 수 있다. 
// 원을 표현하기 위한 클래스
class Circle {
    int x; // 원점의 x좌표
    int y; // 원점의 y좌표
    int r; // 반지름(radius)
}

// 좌표 상의 한 점을 다루기 위한 클래스
class Point {
    int x; // x좌표
    int y; // y좌표
}


// Point클래스를 재사용해서 Circle클래스를 작성함
// 한 클래스를 작성하는 데 다른 클래스를 멤버변수로 선언하여 포함시킴
class Circle {
    Point c = new Point(); // 원점
    int r;
}

 

1.3 클래스간의 관계 결정하기

  • 상속관계 vs 포함관계 구분하기
상속관계 : '~은 ~이다(is-a)'
원(Circle)은 점(Point)이다. - Circle is a Point.

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

1.4 단일 상속(single inheritance)

  • 자바에서는 둘 이상의 클래스로부터 상속을 받을 수 없다. 오직 단일 상속만을 허용한다.

1.5 Object클래스 - 모든 클래스의 조상

  • 다른 클래스로부터 상속 받지 않는 모든 클래스들은 컴파일러가 자동적으로 'extends Object'를 추가하여 Object클래스로부터 상속받도록 한다.
  • Object클래스는 모든 클래스 상속계층도의 최상위에 있는 모든 클래스의 조상클래스이다.

 


2. 오버라이딩(overriding)

2.1 오버라이딩이란?

  • 오버라이딩: 조상 클래스로부터 상속받은 메서드의 내용을 자손 클래스 자신에 맞게 변경하는 것
  • override의 사전적 의미는 '~위에 덮어쓰다(overwrite)'이다.

 

2.2 오버라이딩의 조건

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

- 이름이 같아야 한다.
- 매개변수가 같아야 한다.
- 반환타입이 같아야 한다.

=>한마디로 요약하면 선언부가 서로 일치해야 한다.

 

단, 접근 제어자(access modifier)와 예외(exception)는 제한된 조건 하에서만 다르게 변경할 수 있다.

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

1. 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
2. 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
(단순히 선언된 예외의 개수로 판단하지 말 것!
Exception은 모든 예외의 최고 조상. 가장 많은 예외를 던질 수 있게 선언한 것이다.)
3. 인스턴스메서드static메서드로 또는 그 반대로 변경할 수 없다. (인스턴스메서드 <-> static메서드 변경 X)

 

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

-> 오버라이딩 X. 각 클래스에 별개의 static 메서드를 정의한 것이다.
메서드는 클래스이름으로 구별될 수 있다. 호출할 때는 '클래스이름.메서드이름()'으로 하는 것이 바람직하다.
static멤버들은 자신들이 정의된 클래스에 묶여있다.

 

2.3 오버로딩 vs. 오버라이딩

오버로딩(overloading): 기존에 없는 새로운 메서드를 정의하는 것(new)
오버라이딩(overriding): 조상으로부터 상속받은 메서드내용을 변경하는 것(change, modify)
class Parent {
    void parentMethod() {}
}

class Child extends parent {
    void parentMethod() {}         // 오버라이딩
    void parentMethod(int i) {}    // 오버로딩
    
    void childMethod() {}          
    void childMethod(int i) {}     // 오버로딩
}

 

2.4 super

  • super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다.
  • 상속받은 멤버(조상 클래스의 멤버)자신의 멤버(자손클래스의 멤버)와 중복 정의되어 있어 이름이 같을 때 super를 붙여서 구별할 수 있다.
  • 변수만 아니라 메서드 역시 super를 써서 호출할 수 있다. 조상 클래스의 메서드를 자손 클래스에서 오버라이딩한 경우 super를 사용한다. 
  • static메서드(클래스메서드)는 인스턴스와 관련이 없다. 그래서 this와 마찬가지로 super 역시 static메서드에서는 사용할 수 없고 인스턴스메서드에서만 사용할 수 있다.
  • 같은 이름의 멤버변수 x가 조상클래스와 자손클래스에 모두 있을 때는 super.x는 조상 클래스로부터 상속 받은 멤버변수를 뜻하며, this.x는 자손 클래스에 선언된 멤버변수를 뜻한다.
  • 조상클래스의 메서드의 내용에 추가적으로 작업을 덧붙이는 경우라는 이처럼 super를 사용해서 조상클래스의 메서드를 포함시키는 것이 좋다.

 

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

  • super()조상 클래스의 생성자를 호출하는데 사용된다.
  • 자손 클래스의 생성자의 첫 줄에서 조상 클래스의 생성자가 호출되어야 한다.
  • Object클래스를 제외모든 클래스의 생성자첫 줄에 반드시 자신의 생성자( 생성자.this() ) 또는 조상의 생성자( super() )를 호출해야 한다. 그렇지 않으면 컴파일러가 자동으로 'super();'를 생성자의 첫 줄에 삽입한다. 
  • 조상 클래스의 멤버변수는 조상의 생성자에 의해 초기화되도록 해야한다.

 


3. package와 import

3.1 패키지(package)

  • 패키지서로 관련된 클래스들을 그룹 단위로 묶어 놓은 것이다. 클래스 또는 인터페이스를 포함시킬 수 있다.
  • 클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리이다. 그래서 어떤 패키지에 속한 클래스는 해당 디렉토리에 존재하는 클래스파일(.class)이어야 한다.
- 하나의 소스파일에는 첫 번째 문장으로 단 한 번의 패키지 선언만을 허용한다.
- 모든 클래스는 반드시 하나의 패키지에 속해야 한다.
- 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다.
- 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.

3.2 패키지의 선언

  • package 패키지명;
  • 패키지의 선언은 클래스나 인터페이스의 소스파일(.java)의 맨 위에 한 줄을 적으면 되며, 하나의 소스파일에 단 한 번만 선언될 수 있다. 패키지명은 소문자로 작성하는 것이 원칙이다.

3.3 import문

  • import문의 역할은 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보 제공하는 것이다.
  • import문으로 사용하고자 하는 클래스의 패키지를 미리 명시해주면 소스코드에 사용되는 클래스이름에서 패키지명은 생략할 수 있다.

3.4 import문의 선언

  • 일반적인 소스파일(*.java)의 구성 순서
    1) package문
    2) import문
    3) 클래스 선언
  • import문 선언
    import 패키지명.클래스명; 
    또는 
    import 패키지명.*; 
  • import문은 package문과 달리 한 소스파일에 여러 번 선언할 수 있다.

3.5 static import문

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문을 사용하면 static멤버를 호출할 때 클래스 이름을 생략할 수 있다.
    e.g. System.out.println(Math.random()); -> out.println(random());