본문 바로가기
CodeStatesBootCamp/Review

Section 1 - Unit 6 : [Java] 객체지향 프로그래밍 심화 Review

by JKROH 2023. 2. 27.
반응형
Review 에서는 학습한 내용을 다시금 기록합니다.
Unit Review는 학습한 내용 중 기존에 알고 있었지만 정확하게 이해하지 못하던 정보와 새롭게 알게된 정보를 기록합니다. 추가적인 설명을 요하는 부분은 댓글로 남겨주세요.
Section Review는 전반적인 Section을 되돌아보고 학습했던 시간과 과정, 내용을 총괄하여 기록합니다.

<<객체지향 프로그래밍 심화>>


상속

* 상속

  • 상속이란 기존의 클래스를 재활용하여 새로운 클래스를 작성하는 것이다.
  • 하위 클래스가 상위 클래스를 상속받으면, 상위 클래스의 필드, 메서드, 이너 클래스 등의 멤버를 사용할 수 있다.
  • 추상화를 통해 여러 클래스의 공통점을 담은 하나의 상위 클래스를 만들어 이를 상속받으면 불필요한 코드의 중복을 방지할 수 있다.
  • 자바에서는 단일 상속만을 허용한다.

* 포함 관계

  • 포함은 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하는 것을 의미한다.
  • 예를 들어, Category 클래스는 필드 변수로 List<Menu> 를 포함할 것다.
  • 일반적으로 상속보다는 포함 관계를 많이 사용한다.
  • 두 클래스 간의 관계를 통해 상속할 것인지, 포함할 것인지를 구분할 수 있다.
    • A는 B이다(IS~A)로 관계를 정의할 수 있으면 상속한다 : 프로그래머는 사람이다, 가수는 사람이다.
    • A는 B를 포함한다(HAS~A)로 관계를 정의할 수 있으면 상속한다 : 카테고리는 메뉴를 포함한다.

* 메서드 오버라이딩

  • 메서드 오버라이딩은 상위 클래스로부터 상속받은 메서드와 동일한 이름의 메서드를 재정의하는 것을 의미한다.
  • 메서드 오버라이딩은 상위 클래스에 정의된 메서드를 하위 클래스에 맞게 변경하고자 할 때 사용한다.
  • 메서드를 오버라이딩 하기 위해서는 아래의 세 가지 규칙을 지켜야한다.
    • 메서드의 선언부(메서드 이름, 매개변수, 반환타입)가 오버라이딩 하려는 상위 클래스의 메서드와 완전히 일치해야한다.
    • 접근 제어자의 범위가 상위 클래스의 메서드보다 같거나 넓어야 한다.
    • 상위 클래스의 메서드보다 더 적은 예외를 선언해야 한다.

* super 키워드와 super()

  • super와 super()는 this 와 this()를 연상하게 쉽게 이해할 수 있다.
  • this는 객체 자신을, this()는 객체 자신의 생성자를 호출한다.
  • 마찬가지로 super는 자신의 상위 클래스를, super()는 상위클래스의 생성자를 호출한다.
  • super는 상위 클래스와 하위 클래스에서 이름이 같은 변수를 사용하는 등의 상황에서 이들을 구분하기 위해 사용한다. 이 역시 this 키워드를 사용했던 이유를 연상하 이해가 쉽다.
  • super()는 생성자의 첫 줄에 작성되어야 하며, 상위 클래스에 기본 생성자가 있어야 사용할 수 있다.

* 클래스의 정점, Object 클래스

  • Object 클래스는 자바의 모든 클래스의 최상위에 위치한다. 따라서 모든 클래스는 Object 클래스를 상속받는다.
  • 모든 클래스가 Object를 상속받기 때문에, 명시적으로 extends Object를 작성하지 않아도 자동으로 상속이 이뤄진다.
  • Object 클래스의 대표적인 메서드들은 다음과 같다.
메서드명 반환 타입 내용
toString() String 객체 정보를 문자열로 출력한다.
equals(Object obj) boolean 등가 비교 연산(==)과 
hashCode() int 객체의 위치정보와 관련하여 Hashtable 또는 HashMap에서 동일 객체여부를 판단한다.
객체의 동등 여부를 판단하기 위해서는 equals()뿐만이 아니라 hashCode()도 정의해야한다.
wait() void 현재 쓰레드를 일시정지한다.
notify() void 일시정지 중인 쓰레드를 재동작한다.

캡슐화

* 캡슐화

  • 캡슐화란 특정 객체 안에 관련된 속성과 기능을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것을 의미한다.
  • 캡슐화는 하는 이유는 크게 두 가지가 있다.
    • 외부에서 해당 데이터에 접근하는 것을 막음으로써, 데이터를 보호한다.
    • 내부적으로만 사용되는 데이터가 외부에 노출되는 것을 방지한다.
  • 즉, 캡슐화의 핵심은 정보를 은닉하고 각 객체를 독립적으로 유지하는 것이다.
  • 이는 코드를 효과적으로 유지보수 하기 위해서도 필요하다.

* 패키지

  • 패키지는 연관되어 있는 클래스와 인터페이스의 묶음을 의미한다.
  • 패키지를 이용하면 클래스의 충돌을 방지할 수 있다. 그러나 애초에 이름이 같은 클래스는 가급적 사용하지 말자.

- import 문

  • import문은 다른 패키지 내의 클래스를 사용하기 위해 사용한다.

* 접근 제어자

- 제어자 (Modifier)

  • 제어자는 클래스, 필드, 메서드, 생성자 등에 부가적인 의미를 부여하는 키워드이다.
  • 제어자는 크게 접근 제어자와 기타 제어자로 구분할 수 있다.
    • 접근 제어자 : public, protected, private, (default)
    • 기타 제어자 : static, final, abstract, native, synchronized 등

- 접근 제어자

  • 접근 제어자는 캡슐화를 구현하기 위한 핵심적인 방법으로 사용된다.
  • 접근 제어자를 사용하면 클래스 외부로의 불필요한 데이터 노출을 방지할 수 있고, 외부로부터 데이터가 임의로 변경되지 않도록 막을 수 있다.
  • 접근 제어자는 앞서 언급한 4가지가 있으며, default는 아무런 접근 제어자를 붙이지 않은 기본 설정을 의미한다.
    • private : 동일한 클래스 내에서만 접근 가능하다.
    • protected : 동일한 패키지 내의 다른 클래스, 또는 패키지는 다르지만 해당 메서드가 사용된 클래스를 상속한 클래스에서 접근 가능하다.
    • public : 누구나 접그할 수 있다.
    • default : 동일한 패키지 내에서만 접근할 수 있다.

다형성

* 다형성

  • 다형성은 하나의 객체가 여러 형태를 가질 수 있는 성질을 의미한다.
  • 자바에서의 다형성은 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것을 의미한다.
  • 즉, 상위 클래스 타입의 참조변수를 통해서 하위 클래스의 객체를 참조할 수 있도록 허용한 것이다.
  • 예를 들어, Person 타입은 Programmer, Musician, Swimmer 모든 객체를 참조할 수 있다. 그 직업 무엇이든 간에 사람이라는 사실은 변하지 않는다.
  • 따라서 Person JK = new Programmer(); 도, Person JK = new Swimmer(); 도 가능하다.
  • 이렇게 만든 참조변수는 당연히 상위 클래스의 멤버만 사용할 수 있다.
  • 그렇기 때문에 Programmer JK = new Person(); 은 성립하지 않는다. Programmer 가 사용할 수 있는 멤버의 갯수가 더 많기 때문이다. 기왕 프로그래머가 됐는데, 프로그래머가 할 수 있는 일은 못하면 억울하잖아.

* 참조변수의 타입 변환

  • 참조변수의 타입 변환은 사용할 수 있는 멤버의 개수를 조절하는 것을 의미한다. 앞서 살펴봤던 이야기와 일맥상통한다.
  • 참조변수의 타입 변환을 위해서는 세 가지 조건을 충족해야 한다
    • 서로 상속관계에 있는 상위 클래스 - 하위 클래스 사이에만 타입 변환이 가능하다.
    • 하위 클래스 타입에서 상위 클래스 타입으로의 타입 변환(업캐스팅)은 형변환 연산자(괄호)를 생략할 수 있다.
    • 상위 클래스에서 하위 클래스 타입으로 변환(다운캐스팅)은 형변환 연산자(괄호)를 반드시 명시해야합니다.
      • 다운캐스팅은 업캐스팅 되어 있는 참조변수에 한해서만 가능하다.
  • 그러나 실질적으로 해당 기능을 자주 사용하지는 않는다. 만들어놓고 바꿀거면 애초에 바뀐 후의 객체로 만들면 된다.
  • 객체의 타입을 상황에 따라 바꾼다는 것은 애초에 해당 객체를 역할에 맞지 않게 설계하거나 생성했을 가능성이 높다는 뜻이다.

* instanceof 연산자

  • instanceof 연산자는 참조변수의 타입변환(캐스팅)이 가능한지 여부를 boolean 타입으로 확인할 수 있는 연산자다.
  • 프로젝트의 규모가 커질수록 캐스팅 조건을 일일이 살펴보는 것이 불가능에 가까워지기 때문에 instanceof연산자를 쓴다.
  • '참조변수 instanceof 타입' 의 형태로 사용할 수 있다.
  • 예를 들어, 'swimmer instanceof Person = true' , 'simmer instanceof Programmer = false' 의 형태로 사용 가능하다.

추상화

* 추상화

  • 추상화의 핵심은 객체들의 공통점을 뽑아내는 것이다.
  • 여러 클래스들이 산재할 때, 이들의 공통점을 모아 하나의 상위 개념을 만들어낸다면, 다형성을 보장하고 불필요한 코드의 중복을 줄일 수 있다.
  • 상속의 경우, 상위 타입의 변화는 연쇄적으로 하위 타입에 전파되기 때문에 상위 타입을 수정하는 매우 어려워진다. 그러나 추상화를 통한 인터페이스의 사용은, 변화에 더욱 용이하다. 

* abstract 제어자

  • abstract 제어자의 핵심은 '미완성' 에 있다.
  • 즉, 추상 메서드와 추상 클래스는 각각 구체화가 되지 않은 미완성된 메서드와 클래스이다.
  • 따라서 추상 메서드를 실행하거나, 추상 클래스로부터 객체 인스턴스를 생성하는 것은 불가능하다.

* 추상 클래스

  • 추상 클래스를 사용하면 상속 관계에 있어 하위 클래스를 유연하게 구할 수 있다.
  • 추상 클래스를 상위 타입으로 두고 하위 클래스를 상속하면, 하위 클래스는 상위 클래스의 추상 메서드를 오버라이딩 해 구현만 하면 된다.
  • 이 때, 애초에 오버라이딩 한 메서드가 추상 메서드이니, 메서드 바디는 하위 클래스의 마음대로 상황에 맞게 작성할 수 있다. 
  • 추상 클래스는 추상화의 구현에도 핵심적인 역할을 수행한다.
  • 상속계층도를 따져보면, 당연히 상위 계층의 추상화 정도가 높고, 하위 계층으로 내려갈수록 구체화된다.
  • 추상 클래스를 통해 추상적인 메서드와 클래스를 제공하면, 구체화 된 많은 하위 클래스들의 공통점만을 뽑아 표현할 수 있다.

* final 키워드

  • final 키워드는 필드, 지역 변수, 클래스 앞에 위치할 수 있으며 위치에 따라 의미가 달라진다
    • 클래스 앞 final : 변경, 확장, 상속이 불가능한 클래스
    • 메서드 앞 final : 오버라이딩이 불가능한 메서드
    • 변수 앞 final : 값의 변경이 불가능한 상수값 변수

* 인터페이스

  • 추상 클래스는 추상 메서드를 하나 이상 포함한다는 점 이외에는 일반 클래스와 차이가 없다.
  • 인터페이스는 추상 메서드와 상수만을 멤버로 가질 수 있다.
  • 즉, 추상화의 정도가 추상 클래스보다 더 높다.

- 인터페이스의 기본 구조

  • 내부의 모든 필드가 public static final로 정의된다.
  • 모든 메서드가 public abstract로 정의된다.

- 인터페이스의 구현

  • 특정 인터페이스를 구현하고자 하는 클래스는 해당 인터페이스에 정의된 모든 추상 메서드들을 구현해야 한다.

- 인터페이스의 장점

  • 인터페이스는 기본적으로 역할과 구현을 구분한다.
  • 만일 콘크리트 클래스를 직접 사용하게 된다면 요구사항의 변경에 따라 코드에도 변화가 일어난다.
  • 이는 큰 단위의 프로젝트에서는 지나치게 많은 코드의 수정을 요구하며, 또한 핵심 기능에는 변화가 없음에도 해당 기능의 코드를 수정해야하는 본질과 관계 없는 변화를 야기한다. 아래 예시를 통해 살펴보자.
    • 예를 들어, 알림을 보내는 기능을 구현할 때, 이메일로만 보내던 알림을 문자로, 카카오톡으로 보낸다고 해보자.
    • 각 기능에 따라 EmailNotifier, SNSNotifier, KakaoNotifier 객체를 만들고 사용한다면 핵심 기능인 notify()의 기능에는 변화가 없이 각 객체들을 요구 사항에 따라 추가해주어야 한다.
    • 그러나 Notifier 인터페이스를 이용한다면 이런 변화를 없앨 수 있다. 상황에 맞는 Notifier 객체를 사용하면 되기 때문이다.

 

반응형

댓글