[java] 추상클래스(abstract class)

추상클래스

- 추상클래스는 미완성 설계도에 비유할 수 있다. 클래스가 미완성이라는 것은 멤버의 개수에 관계된 것이 아니라, 단지 미완성 메서드(추상메서드)를 포함하고 있다는 의미이다.

- 미완성 설계도로 완성된 제품을 만들 수 없듯이 추상클래스로 인스턴스는 생성할 수 없다. 추상클래스는 상속을 통해서 자손클래스에 의해서만 완성될 수 있다.

- 추상클래스는 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 갖는다. 즉 서로 다른 세 개의 클래스를 따로 그리는 것보다는 이들의 공통부분만을 그린 추상클래스를 만들어 놓고, 이 추상클래스를 이용해서 각각의 설계도를 완성하는 것이 훨씬 효율적이다.

└ex. 사람이라는 클래스가 있으며 걷다라는 메서드가 존재한다. 또한 사람클래스를 상속받는 영희와 철수 클래스가 존재한다. 사람의 걷다라는 공통 메서드를 상속받은 영희와 철수가 사용할 수 있다. 그러나 사람마다 걷는 속도가 다르기 때문에 사람마다 그 속도를 조절해 주어야한다. 이때 만약 사람 클래스에서 걷다라는 메서드를 일반 메서드로 지정하면 사람클래스를 상속받는 영희와 철수 클래스는 모두 같은 속도로 걸을 수 밖에 없게 된다. 하지만 걷다라는 메서드를 추상메서드로 정의하고 영희와 철수 클래스에서 각각 구현해주면 영희와 철수는 자신의 걸음걸이대로 걸을 수 있게된다.

더보기

-----------------------------------------------------------

예제1.

class Person{

public void walk(){

System.out.println("천천히 걷습니다.");

}

}

 

class Younghee extends Person{}

class Chulsu extends Person{}

 

public class Test {

public static void main(String[] args) {

Younghee y = new Younghee();

Chulsu c = new Chulsu();

 

y.walk();

c.walk();

}

}

 

실행결과

천천히 걷습니다.

천천히 걷습니다.

-----------------------------------------------------------

예제2.

abstract class Person{

abstract void walk();

}

 

class Younghee extends Person{

void walk(){

System.out.println("천천히 걷습니다.");

}

}

class Chulsu extends Person{

void walk(){

System.out.println("빨리 걷습니다.");

}

}

 

public class Test {

public static void main(String[] args) {

Younghee y = new Younghee();

Chulsu c = new Chulsu();

 

y.walk();

c.walk();

}

}

 

실행결과

천천히 걷습니다.

빨리 걷습니다.

-----------------------------------------------------------

 

- 여기서 '그냥 오버라이딩해서 사용하면 안되나요?'하고 생각할 수 도 있는데, 굳이 abstract를 붙여서 추상메서드로 선언하는 이유는 자손 클래스에서 추상메서드를 반드시 구현하도록 강요하기 위해서이다. 만일 추상메서드로 정의되어 있지 않고 빈 몸통만 가진 일반메서드로 정의되어 있다면, 상속받는 자손 클래스에서는 이 메서드들이 온전히 구현된 것으로 인식하고 오버라이팅을 통해 자신의 클래스에 맞도록 구현하지 않을 수도 있기 때문이다.

- 문법

abstract class 클래스명 { ... }

- 좀 더 쉽게 이해하고 싶다면 추상클래스를 미완성클래스라고 인식하기보다 그저 구현부가 없는 추상메소드를 포함하고 있는 클래스라고 생각하라.

- 일반클래스와 전혀 다르지 않다. 추상클래스에도 생성자가 있으며, 멤버변수와 메서드도 가질 수 있다.

[참고] 추상메서드를 포함하고 있지 않은 클래스에도 키워드 'abstract'를 붙여서 추상클래스로 지정할 수도 있다. 추상메서드가 없는 완성된 클래스라 할지라도 추상클래스로 지정되면 클래스의 인스턴스를 생성할 수 없다.

 

추상메서드(abstract mmethod)

- 일반 메서드는 선언부와 구현부로 구성되어 있는 반면에 추상메서드는 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상메서드이다. 즉, 설계만 해 놓고 실제 수행될 내용은 작성하지 않았기 때문에 미완성 메서드인 것이다.

└why? 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스(추상클래스)에서는 선언부만을 작성하고, 실제 내용은 상속받는 클래스에서 구현하도록 비워 두는 것이다.

- 문법

/*주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다.*/

abstract 리턴타입 메서드명();  // 구현부가 없으므로 괄호{}대신 문장의 끝을 알리는 ';'을 적는다.

- 추상클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 조상인 추상클래스의 추상메서드를 모두 구현해주어야 한다. 만일 조상으로부터 상속받은 추상메서드 중 하나라도 구현하지 않는다면, 자손클래스 역시 추상클래스로 지정해 주어야 한다.

- 메서드를 사용하는 쪽에서는 메서드가 실제로 어떻게 구현되어있는지 몰라도 메서드의 이름과 매개변수, 리턴타입, 즉 선언부만 알고 있으면 되므로 내용이 없을 지라도 추상메서드를 사용하는 코드를 작성하는 것이 가능하며, 실제로는 자손클래스에 구현된 완성된 메서드가 호출되도록 할 수 있다.

 

추상클래스의 작성

- 상속이 자손 클래스를 만드는데 조상 클래스를 사용하는 것이라면, 이와 반대로 추상화는 기존의 클래스의 공통부분을 뽑아내서 조상 클래스를 만드는 것이라고 할 수 있다.

- 상속계층도를 따라 내려갈수록 클래스는 점점 기능이 추가되어 구체화 정도가 심해지며, 상속계층도를 따라 올라갈수록 클래스는 추상화의 정도가 심해진다고 할 수 있다. 즉, 상속계층도를 따라 내려 갈수록 세분화되며, 올라갈수록 공통요소만 남게 된다.

추상화 - 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업

구체화 - 상속을 통해 클래스를 구현, 확장하는 작업

- 또한 추상클래스를 상속받는 클래스들의 인스턴스들을 하나의 묶음으로 다룰 수 있다. 다형성에서 배웠듯이 조상 클래스타입의 참조변수로 자손 클래스의 인스턴스를 참조하는 것이 가능하기 때문에 조상클래스타입에 자손 클래스의 인스턴스를 담을 수 있는 것이다.

- 조상클래스에 추상메서드가 정의되어 있다 하더라도 메서드는 참조변수의 타입에 관계없이 실제 인스턴스에 구현된 것이 호출된다.

[참고] 추상클래스를 떠나서도 메서드는 참조변수의 타입에 관계없이 실제 인스턴스에 구현된 것이 호출된다. 예제 참고.

더보기

-----------------------------------------------------------

예제1.

class Person{

void walk(){

System.out.println("걷습니다.");

}

}

 

class Younghee extends Person{

void walk(){  //Overiding

System.out.println("천천히 걷습니다.");

}

}

 

public class Test {

public static void main(String[] args) {

Person y = new Younghee();  // 다형성 및 promtion을 볼 수 있다.

y.walk();

}

}

 

실행결과

천천히 걷습니다.

-----------------------------------------------------------

- Younghee()객체를 만들어 Person 타입의 참조변수 y에 대입하였으므로 walk()메서드를 호출할경우 Person의 walk()가 호출될것 같으나 메서드는 참조변수의 타입에 관계없이 실제 인스턴스에 구현된 것이 호출되기 때문에 Younghee의 walk()가 호출된다.

 

-----------------------------------------------------------

예제2.

class Person{

String name = "사람";

}

 

class Younghee extends Person{

String name = "영희";

}

 

public class Test {

public static void main(String[] args) {

Person y = new Younghee();

System.out.println(y.name);

}

}

 

실행결과

사람

-----------------------------------------------------------

- 그러나 변수의 경우는 타입을 따라간다.

- 다형성을 이용해 자손클래스의 인스턴스를 조상클래스 타입으로 참조하게 했을 경우, 조상클래스의 메서드와 조상클래스와 자손클래스의 교집합된 메서드만 visible된다.

-------------------------------------------------------------------

class Person{

String name = "사람";

void walk(){}

void sleep(){}

}

 

class Younghee extends Person{

String name = "영희";

void draw(){}

}

 

public class Test {

public static void main(String[] args) {

Person y = new Younghee();

y.walk();

y.sleep();

y.draw();  // error msg : The method draw() is undefined for the type Person

Younghee yh =(Younghee)y; // casting

yh.draw();

}

}

-------------------------------------------------------------------