2021. 7. 20. 21:55ㆍJava
이번 포스팅에선 OOP의 핵심 개념 캡슐화, 상속, 추상화, 다형성에 대해서 다뤄 보겠습니다.
이전 포스팅은 아래를 참고해주세요.
https://steadycode.tistory.com/59
상속(Inheritance)
객체지향에서 상속은 두 클래스를 부모, 자식 관계 맺어주는 것을 말합니다.
자손 클래스는 생성자를 제외한 부모 클래스의 모든 멤버를 상속 받습니다. 그 덕분에 코드의 재사용성을 높일 수 있습니다.
단, Java에선 단일 상속만 가능하며 주로 비중 높은 클래스 하나만 상속하고 나머지는 포함관계로 처리합니다.
포함 관계(Composite)
포함 관계는 클래스안에 멤버로 참조 변수를 선언하는 것을 말합니다.
class Circle {
Point c = new Point(); // 포함 관계
int r;
}
Object 클래스
Object 클래스는 모든 클래스의 조상이며 모든 클래스는 Object클래스를 상속 받습니다.
컴파일시 우리가 작성한 코드에 자바 컴파일러가 알아서 Object를 상속하는 코드를 작성해줍니다.
오버라이딩(Overriding)
오버라이딩은 자손 클래스에서 상속받은 조상 클래스의 메서드를 상황에 맞게 재정의하는 것을 말합니다.
오버라이딩이 성립하기 위해선 아래의 세가지 조건을 만족해야 합니다.
- 선언부가 조상 클래스의 메서드와 일치해야함
- 접근 제어자를 조상 클래스보다 좁은 범위로 설정할 수 없다.
- 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
super()
super()는 부모 클래스의 생성자를 호출하는 메서드입니다.
자식 클래스의 인스턴스를 생성하게 되면 상속 받은 부모 클래스의 멤버도 포함되어있습니다.
따라서 부모 클래스의 멤버도 초기화 해주려면 부모 클래스의 생성자도 호출해주어야 하고 계속 거슬러 올라가서 마지막에 Object 클래스의 생성자까지 호출됩니다.
개발자가 생성자의 맨 첫줄에 this()나 super()를 이용해서 다른 생성자를 호출하지 않았다면, 자바 컴파일러가 알아서 부모 클래스의 기본 생성자인 super(); 를 맨 첫줄에 작성해줍니다.
때문에 모든 클래스의 기본 생성자를 작성하는 습관을 들이는게 좋습니다.
캡슐화(encapsulation)
캡슐화는 외부로부터 직접 접근을 막고 메서드를 통한 간접 접근을 통해서만 데이터 변경할 수 있도록 하는 것을 말합니다.
외부에서 알 필요없는 정보를 감추기 위해서나 외부의 잘못된 접근으로 객체가 손상되는 것을 막기위해 사용합니다.
캡슐화를 이해하기 위해서 아래의 접근 제어자를 알아야 합니다.
- public: 접근 제한이 없다.
- protected: 같은 패키지내에서 접근 가능, 그리고 다른 패키지더라도 자손 클래스에서 접근 가능
- (default): 같은 패키지내에서 접근 가능
- private: 같은 클래스내에서만 접근 가능
다형성(polymorphism)
다형성은 조상 타입 참조 변수에 여러가지 형태의 자손 타입 객체를 대입하는 것을 말합니다.
단, 반대인 자손 타입 참조 변수에 조상 타입 객체를 대입할 순 없습니다.
Child child = new Child();
Parent parent = child; // O -> 자동 형변환
Parent parent = new Parent();
Child child = parent; // X
Child child = (child)parent; // O -> 명시적 형변환은 가능하다
참조 변수의 형변환
다형성이 가능한 것은 자손/조상 관계에는 형변환이 가능하기 때문입니다.
단, 형변환하기 전에 instanceof 연산자로 자손/조상 관계인지 확인해야 되고 잘못된 형변환은 ClassCastException(런타임 에러)을 초래할 수 있습니다.
ClassCastException
자손 타입 참조 변수에 조상 타입 객체를 대입했을 때, 실제 메모리엔 없는 기능을 참조 변수를 통해 호출하려고 하면 발생하는 에러입니다.
컴파일시에는 이상 없다가 런타임중에 해당 코드가 실행되면 에러가 발생합니다.
다형성의 단점
지금까지 참조 변수의 타입과 인스턴스의 타입을 일치시켜서 객체를 생성한 방법을 보겠습니다.
인스턴스가 7가지의 기능을 제공한다면 참조 변수를 통해 7가지의 기능을 모두 사용할 수 있었습니다.
A a = new A();
그러나 다형성을 통해서 객체를 생성하면, 인스턴스가 7개의 기능을 제공한다면 참조 변수는 그보다 작은 기능밖에 제공하지 못합니다.
왜냐하면 자식 타입인 인스턴스는 상속받은 부모를 더 구체화하고 확장한 것이기 때문에 부모 타입인 참조 변수가 더 작은 개념이기 때문입니다.
instanceof로 매번 형변환 가능여부를 체크해야하고 사용할 수 있는 멤버의 수도 줄어들며, 최악의 경우엔 ClassCastException까지 발생하는데 다형성을 쓰는 이유가 무엇일까요?
그 이유는 아래와 같습니다.
- 다형적 매개변수
- 하나의 배열안에 여러 종류 객체 다루기
1. 다형적 매개변수
원래 메서드의 매개변수는 지정한 타입 하나만 넘겨줄 수 있었습니다.
그러나 참조형 매개변수는 메서드 호출시, 자신과 같은 타입이나 자손 타입의 인스턴스를 넘겨줄 수 있습니다.
class Product {
int price;
int bonusPoint;
}
class TV extends Product {}
class Computer extends Product {}
class Audio extends Product {}
class Buyer {
int money = 1000;
int bonusPoint = 0;
// TV 구입하기
void buy(TV tv) {
money -= tv.price;
bonusPoint += tv.bonusPoint;
}
// Computer 구입하기
void buy(Computer c) {
money -= c.price;
bonusPoint += c.bonusPoint;
}
// Audio 구입하기
void buy(Audio a) {
money -= a.price;
bonusPoint += a.bonusPoint;
}
}
class Ex {
public static void main(String[] args) {
Buyer buyer = new Buyer();
Tv tv = new Tv();
Computer c = new Computer();
Audio a = new Audio();
buyer.buy(tv);
buyer.buy(c);
buyer.buy(a);
}
}
위의 코드는 Buyer가 TV와 Computer, Audio를 구입하기 위해서 오버로딩으로 같은 이름의 메서드를 여러번 선언하여 코드가 중복된 것을 확인할 수 있습니다.
class Product {
int price;
int bonusPoint;
}
class TV extends Product {}
class Computer extends Product {}
class Audio extends Product {}
class Buyer {
int money = 1000;
int bonusPoint = 0;
// 구입하기
void buy(Product p) {
money -= p.price;
bonusPoint += p.bonusPoint;
}
}
class Ex {
public static void main(String[] args) {
Buyer buyer = new Buyer();
Tv tv = new Tv();
Computer c = new Computer();
Audio a = new Audio();
buyer.buy(tv);
buyer.buy(c);
buyer.buy(a);
}
}
다형성을 통해서 위의 코드처럼 코드의 중복을 줄일 수 있습니다.
2. 하나의 배열로 여러 종류 객체 다루기
기존의 배열엔 하나의 타입만 넣을 수 있었지만,
다형성을 통해서 조상 타입 배열에 여러 타입의 자손 객체를 넣을 수 있게 되었습니다.
TV tv = new TV();
Computer c = new Computer();
Audio a = new Audio();
Product[] arr = new Product[3];
arr[0] = tv;
arr[1] = c;
arr[2] = a;
이렇게 다형성을 사용하면 적은 결합도로 인해 반복 코드를 줄이고 코드의 재사용성을 높일 수 있습니다.
다형성은 추상 클래스와 인터페이스에 관하여 포스팅할 때 더 자세히 다루도록 하겠습니다.
추상화
추상화는 대상을 구체적으로 나타낸 것이 아니라 핵심 기능만 간추려 표현한 것입니다.
'추상'이란 뜻은 '미완성'이라는 뜻과 같습니다.
추상화를 통해 일단 대략적인 기능만 선언해놓고 객체를 생성하는 시점에 이를 구체화하는 방식으로 프로그램을 작성할 수 있습니다.
추상화를 이용하면 관계 없는 클래스끼리도 공통적인 것들을 추출하여 하나의 묶음으로 다룰 수 있습니다.
변동사항이 생기면 추상화한 코드만 변경하여 이를 구현한 객체들을 한꺼번에 변경할 수 있습니다.
또한 클래스의 선언과 객체 생성 사이에 추상화라는 중간 단계가 존재한다고도 볼 수 있습니다.
에러가 발생하면 코드 전체로 문제가 확산하는 걸 막습니다.(수정해야할 코드를 줄여줍니다.)
다만 과도한 추상화라는 부가적인 작업 생성해서 성능의 저하가 올 수 있습니다.
즉, 추상화는 코드의 가독성을 높이고 변동 사항이 생길 때 구체화된 코드보다 유연하게 대응할 수 있도록 돕습니다.
추상화에 대한 내용은 추상 클래스와 인터페이스에 대한 포스팅을 통해서 구체적으로 다루도록 하겠습니다.
Ref.
https://www.youtube.com/playlist?list=PLW2UjW795-f6xWA2_MUhEVgPauhGl3xIp
https://codingnomads.co/blog/what-is-object-oriented-programming-oop-concepts-in-java
https://choi3950.tistory.com/25
https://velog.io/@ha0kim/2020-12-21
'Java' 카테고리의 다른 글
[Java] 컬렉션 프레임워크 (3) (0) | 2021.07.28 |
---|---|
[Java] 컬렉션 프레임워크 (2) (0) | 2021.07.27 |
[Java] 컬렉션 프레임워크 (1) (0) | 2021.07.26 |
[Java] 추상 클래스와 인터페이스 (0) | 2021.07.21 |
[Java] 객체지향 개념 (1) (0) | 2021.07.19 |