본문 바로가기
Book/Java의 정석

[Chapter 7] 객체지향 프로그래밍 2

by slchoi 2022. 4. 1.
728x90
SMALL

'자바의 정석 3rd Editon'을 공부하며 정리한 내용입니다.

1. 상속(inheritance)


1. 상속의 정의와 장점

  • 상속: 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것
  • 상속을 통해 클래스를 작성하면 적은 양의 코드로 새러은 클래스를 작성할 수 있고 코드를 공통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 매우 용이
  • 코드의 재사용성을 높이고 코드의 중복을 제거해 프로그램의 생산성과 유지보수에 기여

자바에서 상속을 구현하는 방법

  • 새로 작성하고자 하는 클래스의 이름 뒤에 상속받고자 하는 클래스의 이름을 키워드 extends와 함께 써주면 됨
    class Child extends Parent {
      // ...
    }
  • 조상 클래스 부모(parent) 클래스, 상위(super) 클래스, 기반(base) 클래스
  • 자손 클래스 자식(child) 클래스, 하위(sub) 클래스, 파생된(derived) 클래스
  • 자손 클래스는 조상 클래스의 모든 멤버를 상속 받음

    • Parent 클래스에 age라는 정수형 변수를 멤버변수로 추가하면, 자손 클래스는 조상의 멤버를 모두 상속받기 ㄸ문에, Child 클래스는 자동적으로 age라는 멤버변수가 추가된 것 같은 효과를 얻음
  • 조상 클래스가 변경되면 자손 클래스는 자동적으로 영향을 받지만, 자손 클래스가 변경되는 것은 조상 클래스에 아무런 영향을 주지 않음

  • 자손 클래스는 조상 클래스의 모든 멤버를 상속 받으므로 항상 조상 클래스보다 같거나 많은 멤버를 가짐. 상속에 상속을 거듭할수록 상속받는 클래스의 멤버 개수는 점점 늘어나게 됨

  • 생성자와 초기화 블럭은 상속되지 않음. 멤버만 상속됨

  • 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많음

  • 같은 내용의 코드를 하나 이상의 클래스에 중복적으로 추가해야하는 경우에는 상속관계를 이용해 코드의 중복을 최소화해야 함

  • 조상 클래스만 변경해도 모든 자손 클래스에, 자손의 자손 클래스에까지 영향을 미치기 때문에, 클래스간의 상속관계를 맺어 주면 자손 클래스들의 공통적인 부분은 조상 클래스에서 관리하고 자손 클래스는 자신에 정의된 멤버들만 관리하면 되므로 각 클래스의 코드가 적어져 관리가 쉬워짐

자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성됨

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

  • 클래스 간에 포함(Composite) 관계를 맺어주면 클래스를 재사용하는 것이 가능
  • 클래스 간의 포함관계를 맺어주는 것은 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것을 의미
// Circle class
class Circle {
    int x;    // x좌표
    int y;    // y좌표
    int r;    // 반지름
}
// Point class
class Point {
    int x;    // x좌표
    int y;    // y좌표
}
// Point 클래스를 재사용해서 Circle 클래스 작성
class Circle {
    Point c = new Point();    // 원점
    int r;
}
  • 하나의 거대한 클래스를 작성하는 것보다 단위별로 여러 개의 클래스를 작성한 다음 이 단위 클래스들을 포함관계로 재사용하면 보다 간결하고 쉽게 클래스를 작성할 수 있음
  • 작성된 단위 클래스들은 다른 클래스를 작성하는데 재사용될 수 있음

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

  • 상속관계를 맺어줄 것인지 포함관계를 맺어 줄 것인지 결정하는 것이 혼돈스러울 경우, '~은 ~이다(is-a)'와 '~은 ~을 가지고 있다(has-a)'를 넣어서 문장을 만들어보면 클래스 간의 관계가 명확해짐

    1. 원(Circle)은 점(Point)이다 - Circle is a Point
    2. 원(Circle)은 점(Point)을 가지고 있다 - Circle has a Point
    • 원은 원점(Point)과 반지름으로 구성되므로 위의 두 문장을 비교해보면 두 번째 문장이 더 옳다는 것을 알 수 있음
  • '~은 ~이다'라는 문장이 성립한다면 상속 관계를, '~은 ~을 가지고 있다'는 문장이 성립한다면 포함관계를 맺어주면 됨

1.4 단일 상속(single inheritance)

  • 다른 객체지향언어인 C++에서는 여러 조상 클래스로부터 상속받는 것이 가능한 '다중상속(multiple inheritance)'을 허용하지만 자바에서는 오직 단일 상속만 허용
    • 둘 이상의 클래스로부터 상속을 받을 수 없음
    • 다중상속 장점: 여러 클래스로부터 상속받을 수 있기 때문에 복합적인 기능을 가진 클래스를 쉽게 작성할 수 있음
    • 다중상속 단점: 클래스간의 관계가 매우 복잡해진다는 것과 서로 다른 클래스로부터 상속받은 멤버간의 이름이 같은 경우 구별할 수 있는 방법이 없다는 단점을 가짐
  • 단일 상속이 하나의 조상 클래스만을 가질 수 있기 때문에 다중상속에 비해 불편한 점도 있지만, 클래스 간의 관계가 명확해지고 코드를 더욱 신뢰할 수 있게 만들어준다는 점에서 다중상속보다 유리
    class Tv {
      boolean power;     // 전원상태(on/off)
      int channel;        // 채널
      void power()       {     power = !power; }
      void channelUp()   {     ++channel; }
      void channelDown() {    --channel; }
    }
    class VCR {
      boolean power;     // 전원상태(on/off)
     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();
      }
      void stop() {
          vcr.stop();
     }
      void rew() {
          vcr.rew();
     }
      void ff() {
          vcr.ff();    
     }
    }
  • 자바는 다중상속을 허용하지 않으므로 Tv 클래스를 조상으로 하고, VCR 클래스는 TVCR 클래스에 포함시킴
    • TVCR 클래스에 VCR 클래스의 메서드와 일치하는 선언부를 가진 ㅁ메서드를 선언하고 내용은 VCR 클래스의 것을 호출해서 사용하도록 함
    • 외부적으로는 TVCR 클래스의 인스턴스를 사용하는 것처럼 보이지만 내부적으로는 VCR 클래스의 인스턴스를 생성해서 사용하는 것
  • 이렇게 함으로써 VCR 클래스의 메서드의 내용이 변경되더라도 TVCR 클래스의 메서드들 역시 변경된 내용이 적용되는 결과를 얻을 수 있음

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

  • Object 클래스는 모든 클래스 상속계층도의 최상위에 있는 조상 클래스
  • 다른 클래스로부터 상속 받지 않는 모든 클래스들은 자동적으로 Object 클래스로부터 상속받게 함
  • 만일 다른 클래스로부터 상속을 받는다고 하더라도 상속계층도를 따라 조상클래스, 조상클래스의 조상클래스를 찾아 올라가다 보면 결국 마지막 최상위 조상은 Object 클래스일 것
  • Object 클래스에는 toString(), equals()와 같은 모든 인스턴스가 가져야 할 기본적인 11개의 메서드가 정의되어 있음 => 9장 참고

2. 오버라이딩(overriding)


2.1 오버라이딩이란?

  • 오바라이딩: 조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것
    • 상속받은 메서드를 그대로 사용하기도 하지만, 자손 클래스 자신에 맞게 변경해야하는 경우가 많은데 이럴 때 조상의 메서드를 오버라이딩함

2.2 오버라이딩의 조건

  • 오버라이딩은 메서드의 내용만을 새로 작성하는 것이므로 메서드의 선언부는 조상의 것과 완전히 일치해야 함

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

  • 이름이 같아야 함
  • 매개변수가 같아야 함
  • 반환타입이 같아야 함
  • 접근 제어자(access modifier)와 예외(exception)은 제한된 조건 하에서만 다르게 변경 가능

1. 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없음
2. 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없음
3. 인스턴스메서드를 static 메서드로 또는 그 반대로 변경할 수 없음

  • 조상 클래스에 정의된 static 메서드를 자손 클래스에서 똑같은 이름의 static 메서드로 정의할 수 있나요?
    정의할 수 있음. 하지만 각 클래스에 별개의 static 메서드를 정의한 것일 뿐 오버라이딩이 아님. 각 메서드는 클래스 이름으로 구별될 수 있으며, 호출할 때눈 참조변수.메서드이름() 대신 클래스이름.메서드이름()으로 하는 것이 바람직. static 멤버들은 자신들이 정의된 클래스에 묶여있다고 생각할 것

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

오버로딩(overloading) 기존에 없는 새로운 메서드를 정의하는 것(new)
오버라이딩(overriding) 상속받은 메서드의 내용을 변경하는 것(change, modify)

2.4 super

  • super: 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조 변수

    • 멤버변수와 지역변수의 이름이 같을 때 this를 붙여서 구별했듯이 상속받은 멤버와 자신의 멤버와 이름이 같을 때는 super를 붙여서 구분
  • 조상 클래스로부터 상속받은 멤버도 자손 클래스 자신의 멤버이므로 super 대신 this 사용 가능

    • 조상 클래스의 멤버와 자손 클래스의 멤버가 중복 정의되어 서로 구별해야하는 경우에만 super를 사용하는 것이 좋음
  • 조상의 멤버와 자신의 멤버를 구별하는데 사용된다는 점을 제외하고는 super와 this는 근본적으로 같음

    • 모든 인스턴스 메서드에는 자신이 속한 인스턴스의 주소가 지역변수로 저장되는데, 이것이 참조변수인 this와 super의 값이 됨
    • 조상 클래스에 선언된 멤버변수와 같은 이름의 멤버변수를 자손 클래스에서 중복해서 정의하는 것이 가능하며 참조변수 super를 이용해서 서로 구별 가능
  • 메서드도 super를 써서 호출 가능. 조상 클래스의 메서드를 자손 클래스에서 오버라이딩한 경우에 super를 사용

    class Point {
      int x;
      int y;
      String getLocation() {
          return "x :" + x + ", y :" + y;
      }
    }
    class Point3D extends Point() {
      int z;
      String getLocation() {
          // return "x :" + x + ", y :" + y + ", z :" + z;
             return super.getLocation() + ", z :" + z;
      }
    }
  • 조상 클래스의 메서드의 내용에 추가적으로 작업을 덧붙이는 경우라면 super를 사용해 조상 클래스의 메서드를 포함시키는 것이 좋음

    • 후에 조상 클래스의 메서드가 변경되더라도 변경된 내용이 자손 클래스의 메서드에 자동적으로 반영될 것이기 때문
  • static 메서드(클래스 메서드)는 인스턴스와 관련이 없음

    • this와 마찬가지로 super 역시 static 메서드에서는 사용할 수 없고 인스턴스 메서드에서만 사용 가능

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

  • this()는 같은 클래스의 다른 생성자를 호출하는 데 사용되지만, super()는 조상 클래스의 생성자를 호출하는데 사용
  • 자손 클래스의 인스턴스를 생성하면 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성됨
    • 이 때 조상 클래스 멤버의 초기화 작업이 수행되어야 하기 때문에 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 함
  • 생성자의 첫 줄에서 조상 클래스의 생성자를 호출해야하는 이유는 자손 클래스의 멤버가 조상 클래스의 멤버를 사용할 수도 있으므로 조상의 멤버들이 먼저 초기화되어 있어야 하기 때문
  • 조상 클래스 생성저의 호출은 클래스의 상속관계를 거술러 올라가면서 계속 반복되며 마지막으로 모든 클래스의 최고 조상인 Object 클래스의 생성자인 Object()까지 가서야 끝이 남

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

  • 인스턴스를 생성할 때 클래스를 선택하는 것만큼 생성자를 선택하는 것도 중요
    • 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
    • 생성자 - 선택한 클래스의 어떤 생성자를 이용해서 인스턴스를 생성할 것인가?
  • 조상 클래스의 멤버변수는 조상의 생성자에 의해 초기화되도록 해야 함

3. package와 import


3.1 패키지(package)

  • 패키지: 클래스의 묶음
    • 패키지에는 클래스 또는 인터페이스를 포함시킬 수 있음
    • 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리할 수 있음
  • 같은 이름의 클래스라도 서로 다른 패키지에 존재하는 것이 가능하므로, 자신만의 패키지 체계를 유지함으로써 다른 개발자가 개발한 클래스 라이브러리의 클래스와 이름이 충돌하는 것을 피할 수 있음
  • 클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리
  • 패키지도 다른 패키지를 포함할 수 있으며 점'.'으로 구분
  • 하나의 소스파일에는 첫 번째 문장으로 단 한 번의 패키지 선언만을 허용
  • 모든 클래스는 반드시 하나의 패키지에 속해야 함
  • 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있음
  • 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리

3.2 패키지의 선언

package 패키지명;
  • 클래스나 인터페이스의 소스파일(.java)의 맨 위에 한 줄만 적어주면 됨
  • 패키지 선언문은 반드시 소스파일에서 주석과 공백을 제회한 첫 번째 문장잉야 하며, 하나의 소스파일에 단 한 번만 선언될 수 있음
    • 해당 소스파일에 포함된 모든 클래스나 인터페이스는 선언된 패키지에 속하게 됨
  • 패키지명은 대소문자를 모두 허용하지만, 클래스명과 쉽게 구분하기 위해서 소문자로 하는 것을 원칙으로 함
  • 소스파일에 자신이 속할 패키지를 지정하지 않은 클래스는 자동적으로 '이름 없는 패키지'에 속하게 됨. 패키지를 지정하지 않는 모든 클래스들은 같은 패키지에 속하게 됨
  • 큰 프로젝트나 Java API와 같은 클래스 라이브러리를 작성하는 경우에는 미리 패키지를 구성하여 적용

3.3 import 문

  • 클래스의 코드를 작성하기 전에 import문으로 사용하고자 하는 클래스의 패키지를 미리 명시해주면 소스코드에 사용되는 클래스이름에서 패키지명은 생략할 수 있음
  • import문의 역할: 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 제공하는 것
    • 컴파일 시 컴파일러는 import문을 통해 소스파일에 사용된 클래스들의 패키지를 알아 낸 다음, 모든 클래스 이름 앞에 패키지명을 붙여줌

이클립스는 단축키 ctrl + shift + o를 누르면, 자동으로 import문을 추가해줌

3.4 import문의 선언

소스파일(*.java)의 구성

  1. package문
  2. import문
  3. 클래스 선언

import문 선언 방법

import 패키지명.클랫스명;
         or
import 패키지명.*;
  • 같은 패키지에서 여러 개의 클래스가 사용될 때, import문을 여러 번 사용하는 대신 패키지명.*을 이요해서 지정된 패키지에 속한 모든 클래스를 패키지명 없이 사용할 수 있음
  • java.lang 패키지는 매우 빈번히 사용되는 중요한 클래스들이 속한 패키지이기 때문에 따로 import문을 지정하지 않아도 됨

3.5 static 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만으로 참조 가능
    System.out.println(Math.random()) -> out.println(random())
    
    

4. 제어자(modifier)


4.1 제어자란?

  • 제어자(modifier): 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여
  • 접근 제어자 public, protected, default, private
    • 그 외 static, ifinal, abstract, native, transient, synchronized, volatile, strictfp
  • 제어자는 클래스나 멤버변수와 메서드에 주로 사용되며, 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능
  • 단, 접근 제어자는 한 번에 네 가지 중 하나만 선택해서 사용해야 함

4.2 static - 클래스의, 공통적인

  • static은 '클래스의' 또는 '공통적인'의 의미를 가짐
  • 인스턴스 변수는 하나의 클래스로부터 생성되었더라도 각기 다른 값을 유지하지만, 클래스 변수(static 멤버변수)는 인스턴스에 관계없이 같은 값을 가짐
    • 하나의 변수를 모든 인스턴스가 공유하기 때문
  • static이 붙은 멤버변수와 메서드, 그리고 초기화 블럭은 인스턴스가 아닌 클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용 가능
  • 인스턴스메서드와 static 메서드의 근본적인 차이는 메서드 내에서 인스턴스 멤버를 사용하는가의 여부에 있음

static이 사용될 수 있는 곳 - 멤버변수, 메서드, 초기화 블럭

  • 대상이 멤버변수인 경우

    • 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 됨
    • 클래스 변수는 인스턴스를 생성하지 않고도 사용 가능
    • 클래스가 메모리에 로드될 때 생성됨
  • 대상이 메서드인 경우

    • 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 됨
    • static 메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없음
  • 인스턴스 멤버를 사용하지 않는 메서드는 static을 붙여 static 메서드로 선언하는 것이 좋음

    • 가능하다면 static 메서드로 하는 것이 인스턴스를 생성하지 않고도 호출이 가능해서 더 편리하고 속도도 더 빠름

4.3 final - 마지막의, 변경될 수 없는

  • 거의 모든 대상에 사용 가능
  • 변수에 사용되면 값을 변경할 수 없는 상수가 되며, 메서드에 사용되면 오버라이딩을 할 수 없게 되고 클래스에 사용되면 자신을 확장하는 자손 클래스를 정의하지 못하게 됨

final이 사용될 수 있는 곳 - 클래스, 메서드, 멤버변수, 지역변수

  • 대상이 클래스인 경우
    • 변경될 수 없는 클래스, 확장될 수 없는 클래스가 됨
    • final로 지정된 클래스는 다른 클래스의 조상이 될 수 없음
    • 대표적인 final 클래스로 String, Math가 있음
  • 대상이 메서드인 경우
    • 변경될 수 없는 메서드
    • final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없음
  • 대상이 멤버변수, 지역변수인 경우
    • 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 됨

생성자를 이용한 final 멤버 변수의 초기화

  • final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 하지만, 인스턴스변수의 경우 생성자에서 초기화 되도록 할 수 있음
  • 클래스 내에 매개변수를 갖는 생성자를 선언하여, 인스턴스를 생성할 때 final이 붙은 멤버변수를 초기화하는데 필요한 값을 생성자의 매개변수로부터 제공받는 것
    • 이 기능을 활용하면 각 인스턴스마다 final이 붙은 멤버변수가 다른 값을 갖도록 하는 것이 가능
    • 이것이 불가능하다면 클래스에 선언된 final이 붙은 인스턴스변수는 모든 인스턴스에서 같은 값을 가져야만 할 것
class Card {
    final int NUMBER;        // 상수지만 선언과 함께 초기화 하지 않고
    final String KIND;        // 생성자에서 단 한번만 초기화할 수 있다.
    static int width  = 100;    
    static int height = 250;
    Card(String kind, int num) {    
        KIND = kind;
        NUMBER = num;
    }
    Card() {
        this("HEART", 1);
    }
    public String toString() {
        return KIND +" "+ NUMBER;
    }
}
class FinalCardTest {
    public static void main(String args[]) {
        Card c = new Card("HEART", 10);
//        c.NUMBER = 5;        // error 발생
        System.out.println(c.KIND);
        System.out.println(c.NUMBER);
        System.out.println(c); // System.out.println(c.toString());
    }
}

4.4 abstract - 추상의, 미완성의

  • 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언하는데 사용됨
  • 클래스에 사용되어 클래스 내에 추상메서드가 존재한다는 것을 쉽게 알 수 있게 함
    • 자세한 내용은 6번 확인

abstract가 사용될 수 있는 곳 - 클래스, 메서드

  • 대상이 클래스인 경우

    • 클래스 내에 추상 메서드가 선언되어 있음을 의미
  • 대상이 메서드인 경우

    • 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알림
  • 추상 클래스는 아직 완성되지 않은 멧서드가 존재하는 '미완성 설계도'이므로 인스턴스를 생성할 수 없음

    abstract class AbstractTesk {    // 추상 클래스 (추상 메서드를 포함하는 클래스)
      abstract void move();    // 추상 메서드 (구현부가 없는 메서드) 
    }
  • 드문 경우지만 추상 메서드가 없는 클래스, 즉 완성된 클래스도 abstract를 붙여 추상 클래스로 만드는 경우도 있음

    • ex) java.awt.event.WindowAdapter
    • 이런 클래스는 인스턴스를 생성해도 할 수 있는 것이 아무것도 없음
    • 인스턴스를 생성하지 못하게 클래스 앞에 제어자 'abstract'를 붙여 놓은 것
    • 이 클래스 자체로는 쓸모없지만, 다른 클래스가 이 클래스를 상속받아 일부의 원하는 메서드만 오버라이딩해도 된다는 장점이 있음
    • 이 클래스가 없다면 아무런 내용도 없는 메서드를 잔뜩 오버라이딩해야 함

4.5 접근 제어자 (access modifier)

  • 접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 겁근하지 못하도록 제한하는 역할을 함
  • 클래스나 멤버면수, 메서드, 생성자에 접근 제어자가 지정되어 있지 않다면, 접근 제어자가 default임을 의미

접근 제어자가 사용될 수 있는 곳 - 클래스, 멤버변수, 메서드, 생성자

  • private 같은 클래스 내에서만 접근 가능

  • default 같은 패키지 내에서만 접근 가능

  • protected 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근 가능

  • public 접근 제한이 없음

  • 접근 범위가 넓은 쪽에서 좁은 쪽의 순으로 왼쪽부터 나열하면

    public > protected > (default) > private

  • 대상에 따라 사용할 수 있는 접근 제어자

대상 사용가능한 접근 제어자
클래스 public, (default)
메서드 public protected, (default), private
멤버변수 public protected, (default), private
지역변수 없음

접근 제어자를 이용한 캡슐화

접근 제어자 사용 이유

  • 외부로부터 데이터를 보호하기 위해

  • 외부에는 불필요한, 내부적으로만 사용되는, 부분을 감추기 위해

  • 캡슐화(encapsulation): 데이터가 유효한 값을 유지하도록, 또는 비밀번호와 같은 데이터를 외부에서 함부로 변경하지 못하도록 하기 위해 외부로부터의 접근을 제한하는 것

  • 메서드 하나를 변경해야 한다고 가정했을 때

    • 접근 제어자가 public이라면, 메서드를 변경한 후에 오류가 없는지 테스트해야하는 범위가 넓음
    • 접근 제어자가 default라면, 패키지 내부만 확인하면 됨
    • 접근 제어자가 private라면, 클래스 하나만 확인하면 됨

생성자의 접근 제어자

  • 생성자에 접근 제어자를 사용해 인스턴스의 생성을 제한할 수 있음
    • 생성자의 접근 제어자는 클래스의 접근 제어자와 같지만, 다르게 지정할 수 있음
  • 생성자의 접근 제어자를 private로 지정하면, 외부에서 생성자에 접근할 수 없으므로 인스턴스를 생성할 수 없게 되지만 클래스 내부에서는 인스턴스를 생성할 수 있음
  • 생성자가 private인 클래스는 다른 클래스의 조상이 될 수 없음
    • 이유: 자손클래스의 인스턴스를 생성할 때 조상 클래스의 생성자를 호출해야만 하는데, 생성자의 접근 제어자가 private이므로 자손 클래스에서 호출하는 것이 불가능하기 때문
    • 클래스 앞에 final를 추가해 상속할 수 없는 클래스라는 것을 알리는 것이 좋음

4.6 제어자(modifier)의 조합

  • 대상에 따라 사용할 수 있는 제어자
대상 사용가능한 제어자
클래스 public, (default), final, abstract
메서드 모든 접근 제어자, final, abstract, statit
멤버변수 모든 접근 제어자, final, static
지역변수 final

제어자를 조합해서 사용할 때 주의해야할 사항

1. 메서드에 static과 abstract를 함께 사용할 수 없음
static 메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문

2. 클래스에 abstract와 final을 동시에 사용할 수 없음
클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고 abstract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순되기 때문

3. abstract 메서드의 접근 제어자가 private일 수 없음
abstract 메서드는 자손 클래스에서 구현해주어야 하는데 접근 제어자가 private이면, 자손 클래스에서 접근할 수 없기 때문

4. 메서드에 private과 final을 같이 사용할 필요는 없음
접근 제어자가 private인 메서드는 오버라이딩될 수 없기 때문. 둘 중 하나만 사용해도 의미가 충분

5. 다형성(polymorphism)


5.1 다형성이란?

  • 다형성: 여러 가지 형태를 가질 수 있는 능력
    • 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현
  • 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 함
    • 인스턴스 타입과 참조변수 타입이 일치하는 것이 보통이자만, 서로 상속 관계에 있는 경우 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것이 가능
  • 인스턴스를 같은 타입의 참조변수로 참조하는 것과 조상타입의 참조변수로 참조하는 것의 차이점
    • 조상 클래스의 참조변수로는 자식 클래스의 인스턴스 중에서 조상 클래스의 멤버들(상속받은 멤버 포함)만 사용 가능
    • 생성된 자식 인스턴스의 멤버 중에서 조상 클래스에 정의되지 않은 멤버는 사용 불가
    • 둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버 개수가 달라짐
  • 자손 타입의 참조변수로 조상 타입의 인스턴스를 참조하는 것은 불가능
    • 자손 타입의 참조변수로 조상 타입의 인스턴스를 참조하는 것은 존재하지 않는 멤버를 사용하고자 할 가능성이 있으므로 허용하지 않음
    • 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 함

정리

  • 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있음
  • 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수 없음

5.2 참조변수의 형변환

  • 참조변수도 형변환 가능. 단, 서로 상속관계에 있는 클래스 사이에서만 가능하므로 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손타입의 참조변수로의 형변환만 가능
    • 자손타입의 참조변수를 조상타입으로 형변환하는 경우에는 형변환 생략 가능

자손타입 -> 조상타입 (Up-casting): 형변환 생략 가능
자손타입 <- 조상타입(Down-casting): 형변환 생략 불가

  • 캐스트 연산자를 사용해 ()안에 변환하고자 하는 타입의 이름(클래스명)을 적어주면 됨
    • 업 캐스팅의 경우, 참조변수가 다룰 수 있는 멤버의 개수가 실제 인스턴스가 갖고 있는 멤버의 개수보다 적을 것이 분명하므로 형변환 생략 가능
    • 다운 캐스팅의 경우, 인스턴스의 멤버 개수보다 참조변수가 사용할 수 있는 멤버의 개수가 더 많아지므로 문제가 발생할 수 있어 형변환 생략 불가
      • 형변환을 수행하기 전에 instanceof 연산자를 사용해서 참조변수가 참조하고 있는 실제 인스턴스 타입을 확인하는 것이 안전
  • 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않음
  • 참조변수의 형변환을 통해, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위를 조절
class CastingTest2 {
    public static void main(String args[]) {
        Car car = new Car();
        Car car2 = null;
        FireEngine fe = null;
        car.drive();
        fe = (FireEngine)car;        // 8번째 줄. 컴파일은 OK. 실행 시 에러가 발생
        fe.drive();
        car2 = fe;
        car2.drive();
    }
}
  • 8번째 줄 실행시 에러가 발생하는 이유
    • 참조변수 car가 참조하고 있는 인스턴스가 Car 타입(조상 타입)의 인스턴스이기 때문
    • 조상타입의 인스턴스를 자손타입의 참조변수로 참조하는 것은 허용하지 않음
    • 컴파일 시에는 참조변수간의 타입만 체크하기 때문에 실행 시 생성될 인스턴스의 타입에 대해서는 알지 못함. 따라서 컴파일 시에는 문제가 없지만, 실행 시에는 에러가 발생

서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않음
참조변수가 가리키는 인스턴스 타입이 무엇인지 확인하는 것이 중요!!

5.3 instanceof 연산자

  • 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자를 사용
  • 주로 조건문에 사용되며, instanceof의 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치
    • 연산의 결과로 boolean값인 true와 false 중의 하나를 반환
  • instanceof를 이용한 연산결과로 true를 얻었다는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 의미
    • 값이 null인 참조변수에 대해 instanceof 연산을 수행하면 false를 결과로 얻음
  • 조상타입의 참조변수로는 실제 인스턴스의 멤버들을 모두 사용할 수 없기 때문에, 실제 인스턴스와 같은 타입의 참조변수로 형병환을 해야만 인스턴스의 모든 멤버들을 사용할 수 있음
  • 실제 인스턴스와 같은 타입의 instanceof연산 이외에 조상타입의 instanceof 연산에도 true를 결과로 얻으며, instanceof 연산의 결과가 true라는 것은 검사한 타입으로 형변환해도 문제가 없다는 의미

5.4 참조변수와 인스턴스의 연결

  • 조상 클래스에 선언된 멤버변수와 같은 이름의 인스턴스 변수를 자손 클래스에 중복으로 정의했을 때, 조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조변수로 자손 인스턴스를 참조하는 경우 서로 다른 결과를 얻음

    • 메서드의 경우 조상 클래스의 메서드를 자손 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스 메서드(오버라이딩된 메서드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라짐
    • static 메서드는 static 변수처럼 참조변수의 타입에 영향을 받음. 참조변수의 타입에 영향을 받지 않는 것은 인스턴스 메서드 뿐. 따라서 static 메서드는 반드시 참조변수가 아닌 클래스이름.메서드()로 호출
  • 멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용됨

    • 중복 정의되지 않은 경우, 조상타입의 참조변수를 사용했을 때와 자손타입의 참조변수를 사용했을 때의 차이는 없음
  • 멤버변수들은 주로 private으로 접근을 제한하고, 외부에서는 메서드를 통해서만 멤버변수에 접근할 수 있도록 함

    • 인스턴스 변수에 직접 접근하면, 참조변수의 타입에 따라 사용되는 인스턴스변수가 달라질 수 있으므로 주의

5.5 매개변수의 다형성

class Product {
    int price;            // 제품의 가격
    int bonusPoint;        // 제품구매 시 제공하는 보너스점수

    Product(int price) {
        this.price = price;
        bonusPoint =(int)(price/10.0);    // 보너스점수는 제품가격의 10%
    }
}
class Tv extends Product {
    Tv() {
        // 조상클래스의 생성자 Product(int price)를 호출한다.
        super(100);            // Tv의 가격을 100만원으로 한다.
    }

    public String toString() {    // Object클래스의 toString()을 오버라이딩한다.
        return "Tv";
    }
}
class Computer extends Product {
    Computer() {
        super(200);
    }

    public String toString() {
        return "Computer";
    }
}
class Buyer {            // 고객, 물건을 사는 사람
    int money = 1000;    // 소유금액
    int bonusPoint = 0;    // 보너스점수

    void buy(Product p) {
        if(money < p.price) {
            System.out.println("잔액이 부족하여 물건을 살수 없습니다.");
            return;
        }

        money -= p.price;            // 가진 돈에서 구입한 제품의 가격을 뺀다.
        bonusPoint += p.bonusPoint;    // 제품의 보너스 점수를 추가한다.
        System.out.println(p + "을/를 구입하셨습니다.");
    }
}
class PolyArgumentTest {
    public static void main(String args[]) {
        Buyer b = new Buyer();

        b.buy(new Tv());
        b.buy(new Computer());

        System.out.println("현재 남은 돈은 " + b.money + "만원입니다.");
        System.out.println("현재 보너스점수는 " + b.bonusPoint + "점입니다.");
    }
}
/* 실행결과
Tv을/를 구입하셨습니다.
Computer을/를 구입하셨습니다.
현재 남은 돈은 700만원입니다.
현재 보너스점수는 30점입니다.
*/
  • buy(Tv t)로 할 경우 Tv밖에 살 수 없어 다른 제품들도 구입할 수 있는 메서드가 추가로 필요
  • 메서드의 매개변수에 다형성을 적용해 buy(Product p)와 같이 표현하면 하나의 메서드로 처리 가능
    • 매개변수가 Product 타입의 참조변수라는 것은, 메서드의 매개변수로 Product 클래스의 자손타입의 참조변수면 어느 것이나 매개변수로 받아들일 수 있다는 뜻
    • 다른 제품 클래스를 추가할 때 Product 클래스를 상속받기만 하면, buy(Product p) 메서드의 매개변수로 받아들여질 수 있음

5.6 여러 종류의 객체를 배열로 다루기

  • 조상타입의 참조변수로 자손타입의 객체를 참조하는 것이 가능하므로, Product 클래스가 Tv, Computer, Audio 클래스의 조상일 때 아래와 같이 할 수 있음
    // 1. 참조변수 배열로 처리하지 않을 경우
    Product p1 = new Tv();
    Product p2 = new Computer();
    Product p3 = new Audio();
    // 2. 참조변수 배열로 처리한 경우
    Product p[] = new Product[3];
    p[0] = new Tv();
    p[1] = new Computer();
    p[2] = new Audio();
  • 조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있음
  • 묶어서 다루고 싶은 객체들의 상속관계를 따져 가장 가까운 공통조상 클래스 타입의 참조변수 배열을 생성해 객체들을 저장

Vector 클래스

  • Vector 클래스는 내부적으로 Object 타입의 배열을 가지고 있어 이 배열에 객체를 추가하거나 제거할 수 있게 작성됨

  • 배열의 크기를 알아서 관리해주기 때문에 저장할 인스턴스의 개수에 신경쓰지 않아도 됨

  • 단지 동적으로 크기가 관리되는 객체 배열

  • Vector 클래스의 주요 메서드

    메서드/생성자 설명
    Vector() 10개의 객체를 저장할 수 있는 Vector 인스턴스 생성. 10개 이상의 인스턴스가 저장되면, 자동적으로 크기가 증가됨
    boolean add(Object o) Vector에 객체를 추가. 추가에 성공하면 결과값으로 true, 실패하면 false 반환
    boolean remove(Object o) Vector에 저장되어 있는 객체를 제거. 제거에 성공하면 true, 실패하면 false 반환
    boolean isEmpty() Vector가 비어있는지 검사. 비어있으면 true, 비어있지 않으면 false 반환
    Object get(int index) 지정된 위치(index)의 객체를 반환. 반환타입이 Object 타입이므로 적절한 타입으로의 형변환 필요
    int size() Vector에 저장된 객체의 개수를 반환

6. 추상클래스(abstract class)


6.1 추상클래스란?

  • 추상클래스는 미완성 설계도
  • 클래스가 미완성이라는 것은 멤버의 개수에 관계된 것이 아니라, 단지 미완성 메서드(추상 메서드)를 포함하고 있다는 의미
  • 추상클래스로는 인스턴스를 생성할 수 없으며, 추상클래스는 상속을 통해 자손클래스에 의해서만 완성될 수 있음
  • 추상클래스는 새로운 클래스를 작성하는데 있어 바탕이 되는 조상클래스로서 중요한 의미를 가짐
abstract class 클래스이름 {
    ...
}
  • 추상클래스는 키워드 abstract를 붙이면 됨
    • 이 클래스를 사용할 때, 클래스 선언부의 abstract를 보고 이 클래스에는 추상메서드가 있으니 상속을 통해 구현해주어야 한다는 것을 쉽게 알 수 있음
  • 추상클래스는 추상메서드를 포함하고 있다는 것을 제외하고는 일반 클래스와 전혀 다르지 않음
    • 추상클래스에도 생성자가 있으며, 멤버변수와 메서드를 가질 수 있음

6.2 추상메서드(abstract method)

  • 추상메서드: 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것
  • 추상메서드 사용 이유
    • 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래서에서는 선언부만 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려 주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워 두는 것
    • 추상클래스를 상속받는 자손 클래스는 조상의 추상 메서드를 상황에 맞게 적절히 구현해주어야 함
/*주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명*/
abstract 리턴타입 메서드이름();
  • abstract를 앞에 붙여주고, {} 대신 문장의 끝을 알리는 ';'을 적어줌
  • 추상클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 조상인 추상클래스의 추상 메서드를 모두 구현해주어야 함
    • 만일 조상으로부터 상속받은 추상메서드 중 하나라도 구현하지 않는다면, 자손클래스 역시 추상클래스로 지정해 주어야 함

6.3 추상클래스의 작성

  • 여러 클래스에 공통적으로 사용될 수 있는 클래스를 바로 작성하기도 하고, 기존의 클래스의 공통적인 부분을 뽑아서 추상클래스로 만들어 상속하는 경우도 있음
  • 상속이 자손 클래스를 만드는데 조상 클래스를 사용하는 것이라면, 반대로 추상화는 기존의 클래스의 공통부분을 뽑아내서 조상 클래스를 만드는 것
    • 상속계층도를 따라 내려올수록 클래스는 점점 기능이 추가되어 구체화의 정도가 심해지며, 상속계층도를 따라 올라갈수록 클래스는 추상화의 정도가 심해진다고 할 수 있음
    • 상속계층도를 따라 내려 갈수록 세분화되며, 올라갈수록 공통요소만 남게 됨

추상화 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업
구체화 상속을 통해 클래스를 구현, 확장하는 작업

추상메서드로 하는 대신 아무 내용이 없는 일반 메서드({})로 작성할 수 있는데 추상메서드로 선언하는 이유

  • 자손 클래스에서 추상메서드를 반드시 구현하도록 강요하기 위해서
  • 추상메서드로 정의되어 있지 않고 빈 몸통만 가지도록 정의되어 있다면, 상속받는 자손 클래스에서는 이 메서드들이 온전히 구현된 것으로 인식하고 오버라이딩을 통해 자신의 클래스에 맞도록 구현하지 않을 수 있음
  • abstract를 사용해 추상메서드로 정의해놓으면, 자손 클래스를 작성할 때 내용을 구현해주어야 한다는 사실을 인식하고 자신의 클래스에 알맞게 구현할 것

7. 인터페이스(interface)


7.1 인터페이스란?

  • 인터페이스: 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없음
    • 오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않음
  • 추상클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인터페이스는 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'라고 할 수 있음

7.2 인터페이스의 작성

interface 인터페이스이름 {
    public static final 타입 상수이름 = 값;
    public abstract 메서드이름(매개변수목록);
}
  • interface에는 접근제어자로 public 또는 default만 사용 가능

인터페이스 멤버 제약 사항

  • 모든 멤버변수는 public static final이어야 하며, 생략 가능
  • 모든 메서드는 public abstract이어야 하며, 생략 가능. 단, static 메서드와 디폴트 메서드는 예외(jdk 1.8부터)
  • 원래는 인터페이스의 모든 메서드는 추상메서드이어야 하는데, JDK 1.8부터 인터페이스에 static 메서드와 디폴트 메서드의 추가를 허용하는 방향으로 변경됨

7.3 인터페이스의 상속

  • 인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중상속이 가능
  • 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속 받음

7.4 인터페이스의 구현

class 클래스이름 implements 인터페이스이름 {
    // 인터페이스에 정의된 추상 메서드를 구현해야 함
}
  • 인터페이스는 그 자체로는 인스턴스를 생성할 수 없음
  • 인터페이스에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 하는데, 방법은 추상클래스가 자신을 상속받는 클래스를 정의하는 것과 같음
    • 인터페이스는 구현한다는 의미의 키워드 implements를 사용
  • 인터페이스의 메서드 중 일부만 구현한다면, abstract를 붙여 추상클래스로 선언해야 함
  class 클래스이름 extends 상속받을인터페이스이름 implements 구현할인터페이스이름{
      ...
  }
  • 상속과 구현을 동시에 할 수 있음

7.6 인터페이스를 이용한 다형성

  • 해당 인터페이스 타입의 참조변수로 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능
    Fightable f = (Fightable)new Fight();
    or
    Fightable f = new Fighter();
  • 인터페이스 Fightable을 클래스 Fighter가 구현했을 때, Fighter 인스턴스를 Fightable타입의 참조변수로 참조하는 것이 가능
  • Fightable 타입의 참조변수로는 인터페이스 Fightable에 정의된 멤버들만 호출이 가능
  • 인터페이스는 메서드의 매개변수의 타입으로 사용 가능
    • 인터페이스 타입의 매개변수가 갖는 의미: 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다는 것
  • 메서드의 리턴타입으로 인터페이스의 타입을 지정해주는 것도 가능
    • 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미

7.7 인터페이스의 장점

  • 개발시간 단축
  • 표준화 가능
  • 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있음
  • 독립적인 프로그래밍이 가능

1. 개발시간 단축

  • 인터페이스가 작성되면, 이를 사용해 프로그램을 작성하는 것이 가능
    • 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문
  • 동시에 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하게 하면, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있음

2. 표준화 가능

  • 프로젝트의 기본 틀을 인터페이스로 작성한 후, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 일관되고 정형화된 프로그램의 개발이 가능

3. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있음

  • 상속관계에 있지도 않고, 같은 조상 클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있음

4. 독립적인 프로그래밍이 가능

  • 인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제구현에 독립적인 프로그램을 작성하는 것이 가능
  • 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해 간접적으로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능

7.8 인터페이스의 이해

  • 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있음
  • 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 됨 (내용은 몰라도 됨)

7.9 디폴트 메서드와 static 메서드

  • 인터페이스의 static 메서드는 접근 제어자가 항상 public이며, 생략 가능함

default 메서드

  • 인터페이스에 메서드를 추가한다는 것은, 추상 메서드를 추가한다는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현해야 함
  • 디폴트 메서드는 추상 메서드의 기본적인 구현을 제곻나는 메서드
    • 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 됨
  • 디폴트 메서드는 앞에 키워드 default를 붙이며, 추상 메서드와 달리 일반 메서드처럼 몸통 {}이 있어야 함
  • 디폴트 메서드는 접근 제어자가 public이며, 생략 가능함
  • 새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생하는데, 충돌을 해결하는 규칙은 아래와 같음

1. 여러 인터페이스의 디폴트 메서드 간의 충돌

  • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 함

2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌

  • 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시됨

8. 내부 클리스(inner class)


8.1 내부 클래스람?

  • 클래스 내에 선언된 클래스
  • 클래스에 다른 클래스를 선언하는 이유는 두 클래스가 서로 긴밀한 관계에 있기 때문

내부 클래스 장점

  • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있음
  • 코드의 복잡성을 줄일 수 있음 (캡슐화)

8.2 내부 클래스의 종류와 특징

내부 클래스 특징
인스턴스 클래스(instance class) 외부 클래스의 멤버변수 선언위치에 선언. 외부 클래스의 인스턴스멤버처럼 다뤄짐. 주로 외부 클래스의 인스턴스멤버들과 관련된 작업에 사용될 목적으로 선언됨
스태틱 클래스(static class) 외부 클래스의 멤버변수 선언위치에 선언. 외부 클래스의 static 멤버처럼 다뤄짐. 주로 외부 클래스의 static 멤버, 특히 ststic 메서드에서 사용될 목적으로 선언됨
지역 클래스(local class) 외부 클래스의 메서드나 초기화블럭 안에 선언. 선언된 영역 내부에서만 사용 가능
익명 클래스(anonymous class) 클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스 (일회용)

8.4 내부 클래스의 제어자와 접근성

  • 인스턴스클래스와 스태틱 클래스는 외부 클래스의 멤버변수와 같은 위치에 선언되며, 멤버변수와 같은 성질을 가짐
  • 내부 클래스가 외부 클래스의 멤버와 같이 간주되고, 인스턴스 멤버와 static 멤버 간의 규칙이 내부 클래스에도 똑같이 적용됨
  • 내부 클래스도 클래스기 때문에 abstract나 final 같은 제어자를 사용할 수 있고, 멤버변수들처럼 private, protected와 접근제어자도 사용 가능
  • 내부 클래스 중 스태틱 클래스만 static 멤버를 가질 수 있음
    • final과 static이 동시에 붙는 변수는 상수이므로 모든 내부 클래스에서 정의 가능
  • 인스턴스 클래스는 외부 클래스의 인스턴스멤버를 객체생성 없이 바로 사용 가능하지만, 스태틱 클래스는 외부 클래스의 인스턴스멤버를 객체생성 없이 바로 사용 불가
  • 인스턴스 클래스는 스태틱 클래스의 멤버들을 객체생성 없이 사용 가능하지만, 스태틱 클래스에서는 인스턴스 클래스의 멤버들을 객체생성 없이 사용 불가

8.5 익명 클래스(anonymous class)

new 조상클래스이름 () {
    // 멤버 선언
}
or
new 구현인터페이스이름 () {
    // 멤버 선언
}
  • 클래스의 선언과 객체 생성을 동시에 하기 때문에 단 한번만 사용가능하고 하나의 객체만을 생성할 수 있는 일회용 클래스
  • 이름이 없기 때문에 생성자를 가질 수 없으며, 조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없음
    • 하나의 클래스를 상속받거나 하나의 인터페이스만 구현 가능

Chapter 7 끝!!!

728x90
LIST

댓글