추상 클래스와 추상화(Abstract)
기존의 사용하던 클래스들은 구체적으로 데이터를 담은 객체를 직접 다루는 클래스 였다. 하지만 추상 클래스는 데이터를 구체적으로 정의하지 않고 추상적인 데이터를 담고 있는 미완성 설계도이자 추상 메서드를 가지는 클래스를 말한다.
- 다른 클래스 작성에 도움을 주기 위한 것으로 인스턴스 생성이 불가능 하다.
- 상속을 통해 추상 메서드를 완성(오버라이딩)해야 인스턴스 생성이 가능해진다.
- 추상 메서드를 구현하는 것은 선언부를 만들어 주는 것을 말한다.
단순히 이런 특징을 가지는 클래스를 추상 클래스라고 하지만 추상 클래스가 무엇이고 왜 사용하는지에 대해 알아야만 추상 클래스의 용도를 이해할 수 있다.
컴퓨터 과학에서의 추상화는 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것을 말한다.
추상화는 이처럼 구체적인 사물들간의 공통점을 취하고 차이점을 버리는 일반화를 사용하거나, 중요한 부분을 강조하기 위해 불필요한 구체적인 세부 사항을 제거함으로써 단순하게 만드는것이다. 추상화를 반복할 수록 디테일이 사라지고 공통된 특징만 남게 된다. 공통점을 모아서 다룰 수 있게 대상화 한다.
객체 지향 관점에서의 추상화는 클래스를 정의할 때 불필요한 부분들을 생략하고 객체의 속성 중 중요한 것에만 중점을 두어 개략화 하는 것을 말한다. 클래스들의 중요하고 공통된 성질들을 추출하여 부모 클래스를 선정하는 개념과 이벤트 발생의 정확한 절차나 방법을 정의하지 않고 대표할 수 있는 표현으로 대체하는 것을 말한다.
추상화된 코드는 구체화된 코드보다 유연하다.
첫번쨰 코드는 구체적이다. 어떤 클래스의 객체를 만들어 동일한 타입의 참조변수로 다루고자 하며 객체의 모든 멤버를 컨트롤 할 수 있다.
GregorianCalendar cal = new GregorianCalendar(); // 구체적
Calendar cal = Calendar.getInstance(); // 추상적
두번째 코드는 추상적이다. gerInstance() 메서드는 Calendar 타입의 객체를 반환하는 메서드이다. 객체를 반환하기 위해 creatCalendar() 메서드를 호출하여 객체를 생성한다. 하지만 OS의 지역 설정에 따라(caltype) 다른 자식 객체를 생성하여 반환한다.
public static Calendar gerInstance(Local aLocale){
return creatCalendar(TimeZone.getDefault(), alocale);
}
private static Calendar creatCalendat(TimeZone zone, Locale.aLocale){
// ...
if(caltype != null){
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanse":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
//.....
구체적일 경우 나라가 바뀜에 따라 코드를 다시 작성해줘야 한다. 하지만 추상적으로 작성하였을 때는 나라가 바뀌어도 변화에 유연하게 반응할 수 있으며 나라가 추가되어도 메서드에 추가만 해줘도 된다.
객체지향의 중요한 개념은 추상화라는 개념을 우리는 코딩을 할때 알게 모르게 효과를 누리고 있다. math.random() 메서드의 경우 단순히 임의의 난수를 반환한다고 알고 있지만 메서드가 내부에서 어떤식으로 동작해 임의의 난수를 반환하는지에 대해 자세히 알지 못한다.
// 단순히 0.0 이상 1.0미만의 값을 반환받아 사용한다.
int random = (int)(Math.random()*10)+1;
System.out.println(random);
위의 예시와 같이 구체적으로 알지 못하는 클래스와 그 안에 있는 메서드를 사용해왔다. 이처럼 추상화가 잘되어 있다면 내부의 동작을 이해하지 않아도 가져다 사용할 수 있다.
지금 까지는 자연스럽게 내부의 동작을 구체적으로 알지 못하고 추상적으로 메서드의 동작을 가늠하고 결과를 받아사용 해왔지만 만약 내부의 동작 까지 알아야 했다면 프로그래밍을 빠르게 설계하고 구현하지 못했을 것이다.
추상 클래스와 추상 메서드(abstrac method)
앞서 말했듯 추상 메서드를 가지는 클래스를 추상 클래스라고 한다. 하지만 추상 메서드를 가졌는지 컴파일러는 알지 못한다. 따라서 자바에서는 추상 클래스임을 나타내기 위해 선언부에 abstract를 추가하여 컴파일러에게 추상 메서드를 가진 클래스임을 알려준다.
추상 메서드 (abstrac method)
추상 메서드는 구현부( 대괄호,{ } )가 없이 선언부에 제어자 abstract를 추가하여 선언자만을 작성한 미완성인 메서드를 말한다.
- 추상 메서드는 미완성 메서드이다.
- 구현부(몸통{})가 없는 메서드로 주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다.
- 꼭 필요하지만 자손마다 다르게 구현될 것으로 예상되는 경우 사용한다.
abstract class AbstractClass{ // 추상 클래스
abstract void abstractMethod(); //추상 메서드
}
추상 클래스를 상속받는 자식 클래스에 따라 상속 받는 메서드의 내용이 달라질수 있기 때문에에 구현부를 작성하지 않고 상속받는 자식 클래스에서 구현하도록 하기위해 비워둔다.
추상 클래스의 사용 목적
클래스의 공통 부분을 추출하여 선언한 클래스이다(중복 제거).
- 기존의 Car, Bike 클래스는 공통된 부분이 있다.
class Car{
int wheel = 4;
@Override
void run() {
System.out.println(wheel + "륜 자동차 입니다.");
}
}
class Bike{
int wheel = 2;
@Override
void run() {
System.out.println(wheel +"륜 오토바이 입니다.");
}
}
- 기존의 Car, Bike 클래스의 공통된 부분을 묶어 하나의 추상 클래스로 만든 뒤 상속을 통해 자식 클래스에서 물려받아 사용한다. 이는 공통 부분을 제거하여 코드의 중복을 제거하고 추상 클래스를 재사용을 가능하게 한다.
// 추상 클래스
abstract class Vechile{
int wheel;
abstract void run(); // 추상 메서드
}
class Car extends Vechile{
Car(){wheel = 4;};
@Override // 상속받은 추상 메서드를 오버라이딩을 통해 구현
void run() {
System.out.println(wheel + "륜 자동차 입니다.");
}
}
class Bike extends Vechile{
Bike(){wheel = 2;};
@Override // 상속받은 추상 메서드를 오버라이딩을 통해 구현
void run() {
System.out.println(wheel +"륜 오토바이 입니다.");
}
}
- 추상 클래스는 추상 메서드를 가지고 있어 인스턴스화가 불가능 한것을 제외하면 생성자와 인스턴스 멤버 변수와 메서드를 가질 수 있어 일반적인 클래스와 다르지 않다.
- 추상 클래스로 선언하지 않고 일반 클래스로 선언해도 공통 멤버를 제거할 수 있다. 만약 그렇게 한다면 일반 메서드는 사용자가 구현을 하지 않을 수도 있다.
구현을 강제할 수 있다.
다형성 이용해 위 예시의 클래스들을 업캐스팅을 하여 부모클래스의 배열에 저장할 수 있다. 이후 배열의 저장된 객체들이 공통적으로 가지는 메서드 run()을 실행하도록 하였다.
public class Main {
public static void main(String[] args) {
Vechile[] vup = new Vechile[2];
// 업케스팅
vup[0] = new Car();
vup[1] = new Bike();
for(int i = 0; i < vup.length; i++) {
vup[i].run();
}
}
}
만약 추상 클래스를 상속 받은 자식 클래스가 추상 메서드를 재정의하지 않았다면 오류를 통해 알려주어 바로 수정하여 사용할 수 있다.
// 추상 클래스
abstract class Vechile{
int wheel;
abstract void run(); // 추상 메서드
}
class Car extends Vechile{
Car(){wheel = 4;};
// run()메서드를 재정의를 하지 않았다
}
class Bike extends Vechile{
Bike(){wheel = 2;};
@Override // 상속받은 추상 메서드를 오버라이딩을 통해 구현
void run() {
System.out.println(wheel +"륜 오토바이 입니다.");
}
}
이처럼 추상 메소드가 포함된 추상 클래스를 상속받는 자식 클래스가 반드시 추상 메소드를 구현하도록 하기 위해 추상 메서드를 사용한다.
만약 추상 클래스가 아닌 일반 클래스를 상속받는 자식 클래스의 공통 메서드를 정의하지 않고 사용한다면 에러가 발생하지 않고 그냥 넘어가게 된다. 일반 클래스는 메서드의 오버라이딩을 강제하지 않기 때문이다.
// 일반 클래스
class Vechile{
int wheel;
void run() {}; // 일반 메서드, 구현부가 비어있다.
}
class Car extends Vechile{
Car(){wheel = 4;};
// run()메서드를 재정의를 하지 않았다
}
class Bike extends Vechile{
Bike(){wheel = 2;};
@Override // 상속받은 일반 메서드를 오버라이딩을 통해 구현
void run() {
System.out.println(wheel +"륜 오토바이 입니다.");
}
}
결국 부모 클래스의 run() 메서드가 실행되어 잘못된 동작하거나 아무일도 하지 않게 되어도 어디서 잘못된었는지 오류를 발생하지 않게 된다.
'Java' 카테고리의 다른 글
[JAVA] 내부 클래스(Inner Class) (0) | 2023.10.25 |
---|---|
[JAVA] 인터페이스(Interface) (1) | 2023.09.19 |
[JAVA]객체 지향 프로그래밍(Object Oriented Programming) (0) | 2023.08.30 |
[JAVA] 배열(Array) (0) | 2023.08.16 |
[JAVA] 조건문과 반복문 (0) | 2023.08.13 |