객체지향 프로그래밍 2

Jaca·2021년 8월 29일
0

상속

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

  • 조상 클래스 : 부모(parent) 클래스, 상위(super) 클래스, 기반(base) 클래스
  • 자손 클래스 : 자식(child) 클래스, 하위 (sub) 클래스, 파생된(derived) 클래스
class Parent {}
class Child extends Parent {}

자손 클래스는 조상 클래스의 모든 멤버를 상속받기 때문에, Child 클래스는 Parent 클래스의 멤버들을 포함한다고 할 수 있다.

Child 클래스에 새로운 코드가 추가되어도 조상인 Parent 클래스는 아무런 영향도 받지 않는다.
조상 클래스가 변경되면 자손 클래스는 자동적으로 영향을 받지만, 자손 클래스가 변경되는 것은 조상 클래스에 아무런 영향을 주지 못한다.

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

상속 이외에도 클래스를 포함(Composite) 관계를 설정하여 재사용하는 방법이 있다.

class Circle{
    int x;
    int y;
    int r;
}

class Point{
    int x;
    int y;
}

------------>

class Circle{
    Point c = new Point();
    int r;
}

이 처럼 한 클래스를 작성하는데 다른 클래스를 멤버변수로 선언하는 것이다.
이러한 방식은 클래스를 작성하기도 쉽고, 간결하며, 단위 클래스 별로 코드가 작게 나뉘어 있기 때문에 코드를 관리하는데도 수월하다.

상속이냐 포함이냐?

얼핏 보면 상속을 시키나 포함관계를 맺어주나 큰 차이가 없어 보일 때가 많다.
이럴 때는 '~은 ~이다(is - a)' 와 '~은 ~을 가지고 있다(has - a)' 를 넣어 문장을 만들어보면 클래스 간의 관계가 명확해 진다.

Object 클래스

Object 클래스는 모든 클래스 상속 계층도의 최상위에 있는 조상 클래스 이다.
다른 클래스로부터 상속 받지 않는 모든 클래스들은 자동적으로 Object 클래스로부터 상속받도록 한다.

그래서 자바의 모든 클래스는 Object 클래스의 멤버들을 상속 받기 때문에, toString() 이나 equals(Object o) 같은 메서드를 따로 정의하지 않고 사용할 수 있다.

오버 라이딩(Overriding)

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

오버 라이딩의 조건

오버라이딩은 메서드의 내용만을 새로 작성하는 것이므로 메서더의 선언부는 조상의 것과 완전히 일치해야 하고, 아래의 조건을 만족해야 한다.

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

요약하자면 선언부가 일치해야 한다는 것.
다만 접근 제어자와 예외는 제한된 조건하에서만 다르게 변경할 수 있다.

1. 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
조상 클래스에 정의된 메서드가 protected 라면, 이를 오버라이딩 하는 자손 클래스의 메서드는 접근 제어자가 protectedpublic 이어야 한다.
2. 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
오버라이딩을 할 때 예외를 적게 선언할 수 있다.
문제는 예외의 개수만이 중요한 것은 아니라는 점.
조상 클래스의 메서드보다 적은 개수의 예외를 선언하더라도 Exception 은 모든 예외의 최상위 조상이므로 가장 많은 개수의 예외를 선언한 셈이다.

super

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

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

static 메서드는 인스턴스와 관련이 없음을 이전에 보았다.
그래서 this와 마찬가지로 super 또한 static 메서드에서는 사용할 수 없다.

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

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

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

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

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

제어자(modifier)

제어자는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여한다. 제어자의 종류는 크게 접근 제어자와 그 외의 제어자로 나눌 수 있다.

  • 접근 제어자 : public, protected, default, private
  • 그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

제어자는 클래스나 멤버 변수와 메서드에 주로 사용되며, 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능하다.(접근 제어자 제외)

static - 클래스의, 공통적인

인스턴스 변수는 하나의 클래스로부터 생성되었더라도 각기 다른 값을 유지하지만, static 변수는 인스턴스에 관계 없이 같은 값을 가진다.

static이 붙은 멤버변수와 메서드, 초기화 블럭은 인스턴스가 아닌 클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용할 수 있다.

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

변수에 사용되면 값을 변경할 수 없는 상수가 되고, 메서드에 사용되면 오버라이딩을 할 수 없게되고 클래스에 사용되면 자신을 확장하는 자손클래스를 정의할 수 없게 된다.

final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 하지만, 인스턴스변수의 경우 생성자에서 초기화 되도록 할 수 있다.
클래스 내에 매개변수를 갖는 생성자를 선언하여, 인스턴스를 생성할 때 final이 붙은 멤버변수를 초기화하는데 필요한 값을 생성자의 매개변수로부터 제공받는 것이다.
이 기능을 활용하면 각 인스턴스마다 각기. 다른 final 멤버변수를 만드는 것이 가능하다.

abstract - 추상의, 미완성의

메서드나 클래스에 붙히며, 완성시키지 않고 틀만 제공하는 구조를 만들 때 사용된다.

  • 추상 클래스 : 클래스 내에 추상 메서드가 선언되어 있음, 아직 완성되지 않은 미완성 설계도임 -> 인스턴스가 생성되지 않는다.
  • 추상 메서드 : 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임

접근 제어자

멤버 또는 클래스에 사용되어, 해당 멤버나 클래스가 외부에서 접근하지 못하도록 제한하는 역할을 한다.

  • private : 같은 클래스 내에서만 접근 가능
  • default : 같은 패키지 내에서만 접근 가능
  • protected : 같은 패키지 내에서, 다른 패키지의 자손 클래스에서 접근 가능
  • public : 접근 제한이 없음

다형성

객체지향개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미 하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 구현한다.

Tv 클래스와 Tv 클래스를 상속 받는 CaptionTv 클래스가 있다면, 아래와 같은 선언이 가능하다.

Tv t = new CaptionTv();
// 아래와 무슨 차이가 있을까
CaptionTv c = new CaptionTv();

인스턴스 t는 실제 인스턴스가 CaptionTv 타입이라해도, t의 참조변수는 Tv클래스의 멤버변수만 사용할 수 있고, CaptionTv의 변수는 사용할 수 없다.

참조변수의 형변환

기본형 변수와 같이 참조변수도 형변환이 가능하다.

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

Car 클래스의 자손으로 FireEngine과 Ambulance가 있다면

Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;

car = fe; // car = (Car)fe; 업캐스팅이라 생략된 형변환
fe2 = (FireEngine)car; // 다운캐스팅이라 생략 불가능

Car car2 = new Car();
fe2 = (FireEngine)car2; // 불가능. 아래의 이유

서로 상속관계에 있는 타입간의 형변환은 수행될 수 있으나, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다.

instanceof 연산자

참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자를 사용한다.
주로 조건문에 사용되며, instanceof의 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치한다.

(인스턴스 instanceof 타입)

인터페이스

인터페이스는 일종의 추상 클래스이다. 추상 클래스처럼 추상 메서드를 갖지만 추상화 정도가 더 높아, 일반 메서드나 멤버변수를 가질수가 없다.

오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외에 어떠한 요소도 허용되지 않는다.

인터페이스의 장점

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

인터페이스의 이해

인터페이스를 이해하기 위해서 인터페이스의 두가지 특성을 알아야 한다.

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

예를 들어 클래스A(User)와 클래스B(Provider)의 관계가 있다. 이를 'A-B' 관계라고 하자.

이 경우 A를 작성하려면 B가 이미 작성되어 있어야 한다.
그리고 B의 메서드 선언부가 변경되면, 이를 사용하는 A도 변경되어야 한다.
이 처럼 클래스 간의 관계는 Provider가 변경되면 User도 변경 되어야 한다는 단점이 있다.

그러나 클래스A가 클래스B를 직접 호출하지 않고, 인터페이스를 매개체로 해서 관계를 연결해주면 클래스B에 변경사항이 생기거나 클래스B와 같은 기능의 다른 클래스로 대체 되어도 클래스A는 전혀 영향을 받지 않도록 할 수 있다.

'A-B'의 직접적인 관계에서 'A-I-B'의 간접적인 관계로 바꿔 줄 수 있다.
인터페이스 I는 실제 구현내용(클래스B)를 감싸고 있는 껍데기이며, A는 I만 알면되지 그 내부 알맹이를 알 필요가 없다.

디폴트 메서드와 static 메서드

원래 인터페이스에 추상 메서드만 선언할 수 있었지만, JDK1.8부터 디폴트 메서드와 static 메서드를 추가할 수 있게 되었다.

디폴트 메서드

조상 클래스에 새로운 메서드를 추가하는 것은 별 일이 아니지만, 인터페이스의 경우에는 얘기가 다르다.
인터페이스에 메서드를 추가하게 되면, 이 인터페이스를 구현한 모든 클래스들에 새 메서드를 구현해야하기 때문이다.

그래서 디폴트 메서드가 추가 된 것이다.
디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.

디폴트 메서드는 앞에 defalut를 붙이며, 몸통({})이 있어야한다.
디폴드 메서드의 접근 제어자 역시 public이며 생략 가능하다.

새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생하기도 한다.
이 충돌을 해결하는 규칙은 아래와 같다.

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

    • 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야 한다.
  2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌 :

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

내부 클래스(inner class)

내부 클래스는 클래스 내에 선언된다는 점을 제외하고는 일반적인 클래스와 크게 다르지 않다.

내부 클래스란, 말 그대로 클래스 내에 선언된 클래스이다.
클래스 내에 다른 클래스를 선언하는 이유는 간단하다. 서로 긴밀한 관계에 있기 때문이다.

내부 클래스의 장점
1. 내부 클래스에서 외부 클래스의 멤버에 접근하기 쉽다.
2. 캡슐화를 통해 코드의 복잡성을 줄일 수 있다.

내부 클래스의 종류와 특징

내부 클래스의 종류도 변수의 선언위치에 따른 종류와 같다.
내부 클래스의 유효 범위와 성질이 변수와 비슷하므로 서로 비교하면 이해하기 쉽다.

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

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

인스턴스, 스태틱 클래스는 외부 클래스의 멤버변수와 같은 위치에 선언되며, 또한 멤버변수와 같은 성질을 갖는다. 따라서 내부 클래스가 외부 클래스의 멤버와 같이 간주되고, 인스턴스멤버와 static 멤버간의 규칙이 내부 클래스에도 똑같이 적용된다.

static 클래스만 static 멤버를 가질 수 있다.
내부 클래스에 static 변수를 선언해야한다면 static 클래스로 선언해야한다.
final static 변수는 상수이므로 모든 내부 클래스에서 사용 가능하다.

익명 클래스

익명 클래스란, 이름 없이 클래스의 선언과 객체의 생성을 동시에 하기 때문에 한번만 사용될 수 있고, 단 하나의 객체만 생성할 수 있는 일회용 클래스 이다.

이름이 없기에 생성자를 가질 수 없고, 단 하나의 클래스나 인터페이스를 상속, 구현할 수 있다.

profile
I am me

0개의 댓글