JAVA Basic) 추상 클래스 - abstract class
Programming/Java 기초

JAVA Basic) 추상 클래스 - abstract class

728x90

 

목차


     

    <추상 클래스란?>

    일반적으로 추상적이란 것은 구체적이지 않고 막연한 것을 뜻한다. 대입해보면 '구체적이지 않은 클래스'가 된다.
    영어로는 abstract class. 추상 클래스가 아닌 클래스는 'concrete class - 구체적인 클래스'라고 한다. 지금까지 배운 클래스는 모두 concrete class이다. 

    추상 클래스는 항상 추상 메서드를 포함한다.
    추상 메서드는 구현 코드가 존재하지 않다. 그러니까 함수 몸체(body), { }가 없다.

    int add(int x, int y){
    	return x + y;
    } // { }안의 내용이 함수 몸체(body)

    중괄호 {}기 감싼 부분을 함수의 구현부(몸체)라고 한다. 이 부분이 없는 함수는 추상 함수(abstract function)이라고 부르며, 자바에서는 추상 메서드(abstract method)라고 한다.

    추상 메서드는 다음과 같이 선언만 하며 abstract예약어를 사용. 그리고 {}대신 ;를 쓴다.

     

    abstract int add(int x, int y);	//abstract예약어를 썼고, {}대신 ;를 사용

    다음과 같은 메서드는 추상메서드가 아니다. {}를 사용하면 메서드가 구현되었다고 볼 수 있다.
    {}안에 코드가 없을 뿐이다.

     

    int add(int x, int y){}	// {}가 쓰였으면 메서드가 구현되었다고 본다.

    정리하자면, 자바에서 추상 메서드는 abstract 예약어를 사용하여 선언만 하는 메서드이다.

    메서드 선언의 의미

    로직을 구현하는 것도 중요하지만, 더 중요한 것은 어떻게 구현할지를 결정하는 것이다.
    이런 과정을 '개발 설계'라고 한다.
    물론 설계 과정은 더 복잡하고 다양한 방법이 있을 수 있다.

    예를 들어 이런 경우를 생각해 보자.

    int add(int num1, int num2);

    위 처럼 선언한 메서드를 보면 두개의 정수를 입력받은 후 더하여 결과 값을 반환하는 것을 유추할 수 있다.
    즉 이 메서드의 선언부(declatration)만 봐도 어떤 일을 하는 메서드인지 알 수 있다.

    함수의 선언부 즉 반환 값, 함수 이름, 매개변수를 정의한다는 것은 곧 함수의 역할이 무엇인지, 어떻게 구현해야 하는지를 정의한다는 뜻이다.
    따라서 몸체를 구현하는 것보다 중요한 것은 함수 선언부를 작성하는 것이다.
    우리가 자바에서 사용하는 메서드 역시 마찬가지다. 메서드를 선언한다는 것은 메서드가 해야할 일을 명시해 두는 것이라고 할 수 있다.

    <추상 클래스 구현하기>

    추상 클래스의 구현 과정을 코드로 보기 전에 클래스 간의 관계를 살펴 볼 수 있는 '클래스 다이어그램'을 그려보자.

    클래스 다이어그램 작성

    클래스 다이어그램 맨 위에는 클래스 이름.
    그리고 아래에는 변수 이름을 쓰고,
    그 아래에는 메서드 이름을 쓴다.

    추상 클래스와 추상 메서드는 기울임꼴(이탤릭체)로 표시한다.

    Computer클래스는 추상 클래스.
    컴퓨터의 종류는 데스크톱과 노트북이 있다.
    그리고 노트북의 종류에는 MyNoteBook이 있다.

    Computer 클래스는 추상클래스이며 이를 상속받는 두 클래스 중 DeskTop클래스는 일반 클래스이고 NoteBook클래스는 추상 클래스이다.

    마지막으로 NoteBook클래스를 상속받은 MyNoteBook클래스도 일반 클래스이다.

    Computer클래스가 제공하는 메서드 중 display(), typing()은 추상 메서드이며 turnOn, turnOff()는 구현 코드가있는 메서드이다.

    Computer클래스

    package abstractex;
    
    public class Computer {
    	public void display();	//오류발생
    	public void typing();	//오류발생
    	
    	public void turnOn() {
    		System.out.println("전원을 켭니다.");
    	}
    	public void turnOff() {
    		System.out.println("전원을 끕니다.");
    	}
    }

    Computer 클래스 내부에 추상 메서드 display, typing()을 선언하고, 구현 메서드 turnOn, turnOff()메서드를 작성했다.
    그러나 display와 typing()메서드는 완전한 추상 메서드가 아니라서 오류가 발생했다.

    마우스를 올리면 다음과 같은 방법을 제시해준다.
    이 메서드의 몸체(body)부분을 작성하거나, 이 메서드를 추상메서드로 바꾸라는 설명이다.
    두 번째 옵션을 선택하면 abstract예약어가 생기는 것을 볼 수있다.

    package abstractex;
    
    public class Computer {	//오류 발생
    	public abstract void display();	//오류가 남아있음
    	public abstract void typing();	//오류가 남아있음
    	
    	public void turnOn() {
    		System.out.println("전원을 켭니다.");
    	}
    	public void turnOff() {
    		System.out.println("전원을 끕니다.");
    	}
    }

    display, typing() 메서드에 abstract 예약어가 생겼다. 하지만 클래스 이름에도 오류가 표시된다.
    왜냐하면 추상 메서드가 속한 클래스를 추상 클래스로 선언하지 않았기 때문이다.

    오류 내용을 보면 다음과 같다
    Computer를 추상 클래스로 선언하지 않았으므로 이 메서드에서 abstract예약어를 지우거나, Computer클래스를 추상 클래스로 만들라는 것.

    두 번째 옵션을 클릭하여 Computer를 추상 클래스로 바꾸자.

     

    package abstractex;
    
    public abstract class Computer {	//abstract예약어 추가
    	public abstract void display();
    	public abstract void typing();
    	
    	public void turnOn() {
    		System.out.println("전원을 켭니다.");
    	}
    	public void turnOff() {
    		System.out.println("전원을 끕니다.");
    	}
    }

    Computer 클래스를 이처럼 구현한 이유는
    'Computer를 상속받은 클래스 중 turnOn, turnOf()의 구현 코드는 공통 그러나 display, typing()은 하위 클래스에 따라 구현이 달라질 수 있다.'

    그래서 'Computer에는 구현하지 않고, 이 두 메서드 구현에 대한 책임을 상속받은 클래스에 위임한다'라는 의미이다.

    따라서 Computer 클래스의 추상 메서드는 DestTop과 NoteBook에서 구현하게 된다.
    상위 클래스에서는 하위 클래스도 공통으로 사용할 메서드는 '일반 메서드'로 구현하고, 하위 클래스마다 다르게 구현할 메서드는 '추상 메서드'로 선언해 두는 것.

    하위 클래스 코드 작성

    DeskTop 클래스를 만들어보자

    package abstractex;
    
    public class DeskTop extends Computer {
    	// DeskTop에 오류발생
    }

    상속받은 DeskTop 클래스에 오류가 표시된다. 오류 내용을 확인하면.

    Computer은 추상 클래스이다. 추상 클래스를 상속받은 클래스는 추상 메서드를 구현할 책임이 있다.
    따라서 추상 메서드를 모두 구현하든가,
    DeskTop클래스도 추상 클래스로 만들어야한다.

    예를 들어 추상 메서드가 두 개인데 그중 하나만 구현하면 이 역시 구현이 안 된 추상 메서드를 포함하는 것이므로 추상 클래스가 되어야 한다.

    'Add unimplemented methods 옵션'을 눌러보면 다음과 같은 코드가 생성된다.

    	@Override
    	public void display() {
    		// TODO Auto-generated method stub
    	}
    
    	@Override
    	public void typing() {
    		// TODO Auto-generated method stub
    	}

    주석(//이하)을 지우고 몸체 코드를 작성하자.

    package abstractex;
    
    public class DeskTop extends Computer {
    	@Override
    	public void display() {
    		System.out.println("DestTop display()");
    	}
    
    	@Override
    	public void typing() {
    		System.out.println("DestTop typing()");
    	}
    }

    상위 클래스인 Computer클래스에 포함된 추상 메서드 display, typing()를 재정의하게 된다. 이 몸체 부분에 원하는 코드를 구현하면 된다.

    마찬가지로 NoteBook클래스를 구현

    package abstractex;
    
    public abstract class NoteBook extends Computer {
    	@Override
    	public void display() {
    		System.out.println("NoteBook display()");
    	}
    }

    이 클래스에서는 상위 클래스의 추상 메서드를 모두 구현하지 않고 display() 하나만 구현.
    그러므로 NoteBook클래스는 추상 메서드(typing())를 하나만 가지고 있기 때문에 추상 클래스가 된다.

    NoteBook을 상속받은 MyNoteBook클래스는 다음과 같이 구현할 수 있다.

    package abstractex;
    
    public class MyNoteBook extends NoteBook {
    	@Override
    	public void typing() {
    		System.out.println("MyNoteBook typing()");
    	}
    }

    MyNoteBook은 모든 추상 메서드가 구현된 클래스이므로 abstract예약어를 사용하지 않는다.

    테스트하기

    추상 클래스를 만드는 이유
    이런 추상 클래스는 어디에 사용하기 위해 만드는 걸까? 일단 앞서 만든 클래스를 바탕으로 프로그램을 실행하는 테스트 클래스를 작성해보자.

    package abstractex;
    
    public class ComputerTest {
    	public static void main(String[] args) {
    		Computer c1 = new Computer();	//에러 발생
    		Computer c2 = new DeskTop();
    		Computer c3 = new NoteBook();	//에러 발생
    		Computer c4 = new MyNoteBook();
    	}
    }

    Computer클래스 형으로 인스턴스를 4개 생성했다.
    그런데 Computer와 NoteBook 클래스를 인스턴스로 생성할 수 없다고 오류가 난다.

    추상 클래스는 인스턴스로 생성할 수 없다.

    추상 클래스는 모든 메서드가 구현되지 않았으므로 인스턴스로 생성할 수 없다.

    예를 들어 다음과 같은 ABC클래스가 있다고 가정하자.

    abstract class ABC(){
    	abstract void a();
    	void b(){
    		System.out.println("b()");
    	}
    }

    ABC클래스는 추상 클래스이며 a() 추상 메서드를 가지고 있다.
    만약 ABC클래스를 생성하는 class abc = new ABC();문장이 가능하다면 abc.a()메서드를 호출했을 때 어떤 코드가 수행될까? 결론은 구현된 코드가 없어서 구현할 내용이 없다. 따라서 추상 클래스는 인스턴스로 만들 수 없다.
    ComputerTest클래스에서도 Computer와 NoteBook클래스는 추상 클래스이므로 인스턴스를 생성할 수 없다.
    하지만 추상 클래스에서도 '형 변환'을 사용할 수는 있다.

    추상 클래스에서 구현하는 메서드

    추상 클래스는 상속을 위해 만드는 클래스. 그럼 어떤 메서드를 구현하고, 어떤 메서드를 구현하지 않고 추상 메서드로 남겨두는 것일까?

    추상 클래스에서 구현하는 메서드는 하위 클래스에서도 사용할, 즉 하위 클래스에서도 구현 내용을 공유할 메서드를 구현한다.
    실제 하위 클래스에서 내용을 각각 다르게 구현해야 한다면, 구현 내용을 추상 메서드로 남겨두고 하위 클래스에서 구현을 위임하는 것.

    구현된 메서드 하위 클래스에서 공통으로 사용할 구현코드, 하위 클래스에서 재정의할 수도 있음
    추상 메서드 하위 클래스가 어떤 클래스냐에 따라 구현 코드가 달라짐

    Computer클래스는 전원을 켜고 끄는 turnOn(), turnOff()의 구현은 하위 클래스에서 공유할 수 있지만, display(), typing()의 구현 내용은 NoteBook인지 DeskTop인지에 따라 달라지므로 Computer클래스에서는 구현하지 않은 것이다.


    <추상 클래스와 프레임워크>

    실제 추상 클래스는 많은 프레임워크에서 사용하고 있는 구현방식이다.
    예를 들어 안드로이드를 생각해 보면, 안드로이드 앱을 만들 때 안드로이드 라이브러리에서 제공하는 많은 클래스를 사용한다. 이들 클래스 중에는 모두 구현된 클래스도 있지만, 일부만 구현되어 있어서 상속을 받아 구현하는 경우가 많이 있다.
    이때 안드로이드에서 구현해 놓은 코드는 내부적으로 사용하거나 상속받은 클래스가 공통으로 사용할 메서드인 것. 그리고 구현을 미뤄 놓은 메서드(추상 메서드)는 실제로 앱에서 어떻게 만드냐에 따라 다르게 구현해야할 내용으로 앱에서 구현하도록 선언만 해놓은 것이다. 


    <예제>

    다음은 Car 추상 클래스를 상속받은 Bus클래스와 AutoCar클래스를 표현한 클래스 다이어그램.
    1. CarTest.java파일을 보고 유추하여 클래스 다이어그램의 빈칸을 채워보자.
    2. 그리고 테스트 클래스인 CarTest.java의 출력 화면과 같이 출력되도록 Car, Bus, AutoCar클래스를 직접 구현해보자. 

    package chapter9.step1;
    
    public class CarTest {
    	public static void main(String[] args) {
    		Bus bus = new Bus();
    		AutoCar autoCar = new AutoCar();
    		
    		bus.run();
    		autoCar.run();
    		
    		bus.refuel();
    		autoCar.refuel();
    		
    		bus.takePassenger();
    		autoCar.load();
    		
    		bus.stop();
    		autoCar.stop();
    	}
    }

     

     

     

     

     

     

    예제 정답 

    1. run()    2. Bus    3. road()

    Car클래스

    package chapter9.step1;
    
    public abstract class Car {
    	public abstract void run();
    	public abstract void refuel();
    	
    	public void stop(){
    		System.out.println("차가 멈춥니다.");
    	}
    }

     

    Bus클래스

    package chapter9.step1;
    
    public class Bus extends Car {
    	@Override
    	public void run() {
    		System.out.println("버스가 달립니다.");
    	}
    	@Override
    	public void refuel() {
    		System.out.println("천연 가스를 충전합니다.");
    	}
    	
    	public void takePassenger() {
    		System.out.println("승객을 버스에 태웁니다.");
    	}
    }

     

    AutoCar클래스

    package chapter9.step1;
    
    public class AutoCar extends Car {
    	@Override
    	public void run() {
    		System.out.println("차가 달립니다.");
    	}
    	@Override
    	public void refuel() {
    		System.out.println("휘발유를 주유합니다.");
    	}
    	
    	public void load() {
    		System.out.println("짐을 싣습니다.");
    	}
    }

     

     


    [Do it! 자바 프로그래밍 입문] 도서로 공부하며 정리한 글입니다.

    300x250