책읽기

[Java의 정석][Chapter-7] 객체지향 프로그래밍 2 (1/2)

pythaac 2021. 8. 15. 05:01
이 글은 "Java의 정석 (남궁 성 지음)"을 읽고 주관적으로 요약한 글입니다. 

출처 : http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788994492032#N

 

1. 상속 (inheritance)

1) 상속

  • 정의
    • 기존의 클래스를 재사용하여, 새로운 클래스를 작성하는 것
    • 클래스의 이름 뒤에 상속받고자 하는 클래스 이름을 키워드 'extends'와 함께 사용
class Child extends Parent { }
  • 장점
    • 코드의 양을 줄임
      - 적은 양의 코드로 새로운 클래스를 작성 가능
    • 코드를 공통적으로 관리
      - 코드의 추가/변경이 매우 용이
    • 재사용성 / 중복 제거 / 생산성과 유지보수
  • 구성
    • 조상 클래스
      - 부모 클래스, 상위 클래스, 기반 클래스
      - parent class, super class, base class
    • 자손 클래스
      - 자식 클래스, 하위 클래스, 파생된 클래스
      - child class, sub class, derived class
    • 형재 클래스는 없다
      - 같은 부모 클래스로부터 상속받은 두 클래스는 아무 관계가 없음
      - 두 클래스에 공통으로 추가될 내용이 있으면 부모 클래스에 추가
      - 이러한 이유로 코드의 중복이 줄고 작업양이 줄어든다
  • 상속 관계
    • 프로그램이 커질수록 클래스 간의 관계가 복잡해지므로, 관계를 쉽게 이해하기 위한 수단이 필요
    • 상속 계층도(class hierarchy)
      - 클래스 간의 상속 관계를 그림으로 표현한 것
    • 다이어그램
      - Child 클래스는 Parent 클래스의 멤버들을 포함
      - Parent 클래스의 멤버가 Child 클래스에 자동으로 추가됨
  • 상속 내용
    • 멤버만 상속되고, 생성자와 초기화 블럭(즉, 초기화 관련내용)은 상속되지 않는다
    • 자손 클래스의 멤버 수는 항상 조상 클래스보다 많거나 같다
    • private 또는 default인 멤버들은 상속되지 않는다기보다, 상속 받지만 자손 클래스로부터 접근이 제한된 것이다
    • Parent class로부터 상속받은 Child class를 상속한 GrandChild class 또한 Parent class 멤버를 상속받는다
      - 즉, Parent class의 수정은 모든 자손 클래스에게 영향을 준다
    • 자손 클래스의 인스턴스 생성은 조상 클래스 멤버 + 자손 클래스 멤버가 하나의 인스턴스로 생성되는 것이다

2) 포함 (Composite)

  • 클래스의 재사용 방법
    • 상속 (extends)
    • 다른 클래스의 참조변수를 포함 (composite)
  • 정의
    • 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것
Circle c = new Circle(new Point(150, 150), 50);
  • 클래스 재사용 방법 결정하기
    • ~은 ~이다 (is-a)
      - 상속
    • ~은 ~을 가지고 있다 (has-a)
      - 포함
    • 항상 이 방식으로 구별이 가능하진 않으나 감은 잡을 수 있음

3) 단일상속

  • 둘 이상의 클래스로부터 상속받을 수 없음
    • C++에서는 여러 조상 클래스로부터 상속받을 수 있었음
    • Java는 단일 상속만 허용
  • 클래스 간의 관계가 복잡해지지 않음
    • 복잡함 : A에게 상속 받은 power()와 B에게 상속 받은 power()중 어느 걸 사용해야하나?
    • 클래스 간의 관계가 명확해지고 코드의 신뢰성을 높임

4) Object 클래스

  • 모든 클래스 상속계층도의 최상위 조상 클래스
    • 상속 받지 않는 모든 클래스는 자동으로 Object 클래스로부터 상속됨
    • 컴파일러가 자동으로 'extends Object'를 추가
  • 이로 인해 유용한 메서드 사용 가능
    • toString(), equals 같은 메서드를 정의 없이 사용할 수 있는 이유

 

2. 오버라이딩 (overriding)

1) 오버라이딩

  • 정의
    • 조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것
class Point {
    int x;
    int y;
    
    String getLocation() {
        return "x : " + x + ", y : " + y;
    }
}

class Point3D extends Point {
    int z;
    
    String getLocation() { // 2D에서 3D로 변경되었으므로 오버라이딩
        return "x : " + x + ", y : " + y + ", z : " + z;
    }
}
  • 제약
    • 오버라이딩 조건 = 메서드 선언부가 일치해야 함
      1. 메서드 이름이 같아야 함
      2. 매개변수가 같아야 함
      3. 반환타입이 같아야 함 (단, 부모 클래스의 반환타입으로 타입변환이 가능하면 허용)
    • 오버라이딩시 제약
      1. 접근 제어자를 더 좁은 범위로 변경할 수 없음
      2. 예외를 더 많이 선언할 수 없음 (조상 클래스에서 선언한 예외 범위 내)
      3. 인스턴스 메서드를 static으로, 또는 그 반대로 변경 불가능
        - static 메서드는 각 클래스에 별개로 정의됨
  • 오버로딩 vs 오버라이딩
    • 오버로딩
      - 메서드 추가
    • 오버라이딩
      - 메서드 변경

2) super

  • 정의
    • 참조변수
    • 조상 클래스로부터 상속받은 멤버를 참조할 때 사용
  • this
    • this는 지역변수와 멤버변수를 구분할 때 사용
    • 이처럼 super와 this로 상속받은 멤버와 자신의 멤버를 구분할 때 사용
    • 주의
      - super와 this는 근본적으로 같음 (메서드가 속한 인스턴스의 주소)
      - 따라서 상속받은 멤버도 this로 접근이 가능
      - 중복 정의되어 서로 구분이 필요할 경우만 super 사용
  • 사용
    • 인스턴스 메서드에서만 사용 가능
    • 메서드/변수 모두 사용 가능
  • super() - 조상 클래스의 생성자
    • 조상 클래스의 생성자 호출시 사용
      - 자손 클래스의 인스턴스 생성시 조상 클래스 멤버의 초기화 작업이 필요
      - 따라서 자손 클래스 생성자에서 조상 클래스 생성자가 호출되어야함
    • 생성자의 첫 줄에는 무조건 조상 클래스를 호출해야함
      - 없을 경우 컴파일러가 자동으로 'super();'를 첫줄에 삽입
      - 생성자가 정의되어 있으면 컴파일러가 삽입하지 않으므로, 정의해야함
    • 즉, 조상 클래스의 멤버는 조상 클래스의 생성자로 초기화되어야함

 

3. package와 import

1) package

  • 정의
    • 서로 관련된 클래스의 묶음
    • 클래스 또는 인터페이스 포함 가능
    • 물리적으로 하나의 디렉토리
      - 클래스가 물리적으로 하나의 클래스 파일인 것과 같음
      - 따라서 패키지에 속한 클래스는 해당 디렉토리에 존재하는 클래스 파일이어야 함
  • 기능
    • 관련 클래스를 묶어 효율적으로 관리
    • 같은 이름의 클래스 구분 가능
      - 다른 개발자가 개발한 라이브러리 이름과 충돌 방지
  • 조건
    • 소스파일 첫 번째 문장으로 단 한 번의 패키지 선언만 허용
    • 모든 클래스는 반드시 하나의 패키지에 속해야함
    • '.'을 구분자로 계층 구조 구성
    • 물리적으로 클래스 파일을 포함하는 하나의 디렉토리
  •  선언
    • package 패키지명;
    • 반드시 소스파일의 첫 번재 문장이어야함 (주석/공백 제외)
    • 해당 소스파일에 포함된 모든 클래스/인터페이스가 모두 패키지에 소속됨
    • 일반적으로 소문자
  • 이름없는 패키지 (unnamed package)
    • 패키지 미지정시 자동으로 '이름없는 패키지'에 속함
    • 따라서, 패키지를 지정하지 않은 모든 클래스가 같은 패키지에 속함
  • 클래스패스 (classpath)
    • 컴파일러나 JVM 등이 클래스 위치를 찾는데 사용되는 경로
    • 패키지의 루트 디렉토리를 클래스패스에 포함시켜야함
    • CLASSPATH 환경변수
  • 실행
    • 실행시 패키지명을 모두 적어줘야함
      - java com.codechobo.book.PackageTest

2) import

  • 역할
    • 다른 패키지의 클래스 사용시 매번 패키지명을 붙이기 불편함
      - 같은 패키지의 클래스는 패키지명 생략 가능
    • 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 제공
    • 따라서, 클래스 이름에서 패키지명을 생략하게 해줌
    • 컴파일시 모든 클래스이름 앞에 패키지명을 붙여줌
      - 프로그램 성능에 영향을 주지 않음
  • 선언
    • 일반적인 소스파일의 순서
      - package문
      - import문
      - 클래스 선언
    • 선언 방식
      - import 패키지명.클래스명;
      - import 패키지명.*;
  • 주의사항
    • import하는 패키지가 많으면 클래스의 소속 패키지를 구별하기 어려움
    • '*' 사용은 하위 패키지의 클래스까지 포함을 의미하지 않음
    • 즉, 아래 1과 2를 3으로 대체할 수 없음
      1. import java.util.*;
      2. import java.text.*;
      3. import java.*; -> 하위에 패키지들의 클래스를 포함하지 않는다
    • 모든 소스파일은 java.lang.*;가 묵시적으로 선언됨
  • static import
    • static 멤버를 호출할 때 클래스 이름 생략 가능
// 1. static 멤버 호출
System.out.println(Math.random());

// 2. static import
import static java.lang.System.out;
import static java.lang.Math.random;
System.out.println(random();

 

4. 제어자 (modifier)

1) 제어자

  • 정의
    • 부가적인 의미 부여
    • 클래스, 변수, 메서드의 선언부에 위치
  • 제어자 종류
    • 접근 제어자
      - public, protected, default, private
    • 그 외 제어자
      - static, final, abstract, native, transient, synchronized, volatile, strictfp
  • 제어자 조합
    • 여러 제어자 조합 가능
    • 접근 제어자는 4가지 중 1개만 사용 가능
    • 일반적으로 접근 제어자를 가장 왼쪽에 위치 (제어자의 순서는 상관없음)

2) 다양한 제어자

  • static - 클래스의, 공통적인
    • 하나의 변수를 모둔 인스턴스가 공유
    • static이 붙은 다음 3가지는 인스턴스 생성 없이 사용 가능
      1. 멤버변수
      2. 메서드
      3. 초기화 블럭
    • static 메서드 vs 인스턴스 메서드
      - 근본적인 차이는 인스턴스 멤버의 사용 여부
      - static 메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없음
      - 따라서, 인스턴스 멤버를 사용하지 않는 메서드는 static 선언을 고려
      - static 메서드는 인스턴스 생성이 필요없어 편리하고 속도도 빠름
  • final - 마지막의, 변경될 수 없는
    • 사용 대상
      - 변수에 붙으면 상수
      - 메서드에 붙으면 오버라이딩 방지
      - 클래스에 사용하면 상속 방지
    • 대표적인 final 클래스
      - Math, String
    • final 변수의 초기화
      - 일반적으로 선언과 동시에 초기화
      - 인스턴스 변수의 경우, 생성자를 통해 초기화
      - 이를 통해 상수지만 인스턴스마다 다른 값을 만들 수 있음
  • abstract - 추상의, 미완성의
    • 추상 클래스 / 추상 메서드 선언에 사용
      - 추상 클래스 : 추상 메서드를 포함하는 클래스
      - 추상 메서드 : 메서드 선언부만 작성하고 구현하지 않은 메서드
    • 추상 클래스는 추상 메서드의 존재로 인스턴스 생성이 불가
    • 추상 메서드가 없는 추상 클래스
      - 드물게 인스턴스 생성이 필요없는 클래스에 사용
      - 다른 클래스가 상속받아 원하는 메서드만 오버라이딩 할 수 있는 장점

3) 접근 제어자

  • 정의
    • 멤버/클래스에 사용
      - 클래스는 public/(default)
    • 외부에서 접근하지 못하도록 제한하는 역할
    • 제어자가 없으면 default를 의미
  • 접근 범위
제어자 같은 클래스 같은 패키지 자손 클래스 전체
public ------------- ------------- ------------- ------------->
protected ------------- ------------- ------------->  
(default) ------------- ------------->    
private ------------->      
  • 캡슐화
    • 접근 제어자를 사용하는 이유 -> 캡슐화의 정의
      1. 외부로부터 데이터 보호
      2. 복잡성 제거
    • 외부로부터 데이터 보호
      - 데이터의 유효성 유지
      - 비밀번호 같은 데이터 변경 방지
    • 복잡성 제거
      - 내부 작업을 위해 임시로 사용되는 멤버변수
      - 부분작업 처리를 위한 메서드
      - 메서드 변경 / 오류 테스트 등에서 확인해야할 범위를 좁힐 수 있음
  • 생성자의 접근 제어자
    • 생성자의 접근 제어자로 인스턴스 생성을 제한할 수 있음
    • 싱글톤
      - private 생성자로 클래스 내부에서면 인스턴스 생성
      - 인스턴스를 생성해서 반환해주는 public 메서드만 제공
      - 이를 통해 사용할 수 있는 인스턴스 개수 제한 가능
      - 생성자가 private이면 다른 클래스의 조상이 될 수 없으므로, 클래스에 final을 추가해주는 것이 좋음
class Singleton {
	private static Singleton s = new Singleton();
    
    private Singleton() {
    	// ...
    }
    
    public static Singleton getInstance() {
		return s;
    }
    // ...
}
  • 제어자의 조합
    1. 메서드에 static과 abstract는 함께 사용 불가
    2. 클래스에 abstract와 final을 동시에 사용 불가
    3. 메서드에 abstract와 private 함께 사용 불가
    4. 메서드에 private와 final을 함께 사용할 필요 없음
      - private 메서드는 오버라이딩 될 수 없기 때문