abstract란 한국어로는 추상으로 번역된다.
✔️ abstract
: 상속을 강제하는 일종의 규제
: 직접적으로 클래스나 메서드를 사용할 수 없다.
➡️ abstract 클래스나 메서드를 사용하기 위해서 반드시 상속해서 사용하도록 강제하는 것
✔️ 추상 메서드
: 메서드의 시그니처만이 정의된 비어있는 메서드
// 추상 클래스: 반드시 상속하여 사용해야 함. -> 상속받는게 없으니 Error
abstract class A {
//추상 메서드
public abstract int b(); {
}
//본체인 메서드는 abstract 키워드를 가질 수 없다.
public abstract int c() {
System.out.println("Hello");
} // Error
//추상 클래스 내에는 추상 메서드가 아닌 메서드가 존재 할 수 있다.
public void d(){
System.out.println("world");
}
}
public class AbstractDemo {
public static void main(String[] args) {
A obj = new A();
}
}
❌ 에러발생
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Cannot instantiate the type A
메서드 b의 선언 부분에는 abstract라는 키워드가 등장하고 있다.
이 키워드의 메서드 b는 메서드의 시그니처만 정의 되어 있고 이 메소드의 구체적인 구현은 하위 클래스에서 오버라이딩 해야 한다는 의미다.
이게 무슨 말인지 아래의 예시를 보자.
public abstract int c () { System.out.println("Hello"); }
위의 코드는 메서드의 구체적인 내용이 들어가 있다.
이것은, 실제 내용을 갖고 있는 메서드인데 abstract가 들어가 있다.
그래서 에러가 난 것이다.
abstract를 사용하면 저런 로직은 사용하면 안된다.
이렇게 내용이 비어있는 메서드를 추상 메서드라고 부른다.
추상 메소드를 하나라도 포함하고 있는 클래스는 추상 클래스가 되고, 자연스럽게 클래스의 이름 앞에 abstract가 붙는다.
A obj = new A();
위와 같이, 추상 클래스 A를 인스턴스화하면 오류가 발생한다.
그것은 추상 클래스는 구체적인 메서드의 내용이 존재하지 않기 때문에 인스턴스화시켜서 사용할 수 없기 때문이다.
즉, 상속의 재료로만 쓰이는 것. (강제로 오버라이드!)
그럼 어떻게 해야 클래스 A를 사용할 수 있을까?
또 이렇게 불편한 추상 클래스는 왜 사용하는 것일까?
위의 문제를 해결하기 위해서는 이러한 조건이 있다.
- 클래스 A를 상속한 하위 클래스를 만든다.
- 추상 메소드를 오버라이드해서 내용있는 메서드를 만든다.
abstract class A{
public abstract int b();
public void d(){
System.out.println("world");
}
}
class B extends A{
@Override
public int b() {
return 1;
}
}
public class AbstractDemo {
public static void main(String[] args) {
B obj = new B();
System.out.println(obj.b());
}
}
클래스 B는 클래스 A를 상속했다. 그리고 클래스 A의 추상 메소드인 메소드 b를 오버라이딩하고 있다. 그 결과 클래스 A를 사용할 수 있었다.
추상 클래스는 상속을 강제하기 위한 것이다. 즉, 부모 클래스에는 메소드의 시그니처만 정의해놓고 그 메서드의 실제 동작 방법은 이 메서드를 상속 받은 하위 클래스의 책임으로 위임하고 있다.
사실 코드를 이런 식으로 작성하는 경우는 작은 규모의 프로젝트에서는 거의 없다.
계산기 예제를 통해서 추상 클래스의 용도를 생각해보자.
abstract class Calculator {
int left;
int right;
public void setOprands(int left, int right) {
this.left = left;
this.right = right;
}
int _sum() {
return this.left + this.right;
}
public abstract void sum();
public abstract void avg();
public void run() {
sum();
avg();
}
}
class CalculatorDecoPlus extends Calculator {
public void sum() {
System.out.println("+ sum :"+ _sum());
}
public void avg() {
System.out.println("+ avg :"+(this.left+this.right)/2);
}
}
class CalculatorDecoMinus extends Calculator {
public void sum() {
System.out.println("- sum :"+ _sum());
}
public void avg() {
System.out.println("- avg :"+(this.left+this.right)/2);
}
}
public class CalculatorDemo {
public static void main(String[] args) {
CalculatorDecoPlus c1 = new CalculatorDecoPlus();
c1.setOprands(10, 20);
c1.run();
CalculatorDecoMinus c2 = new CalculatorDecoMinus();
c2.setOprands(10, 20);
c2.run();
}
}
[결과값]
+ sum :30
+ avg :15
- sum :30
- avg :15
위의 예제는 합계(sum)를 실행하고 평균(avg)을 실행하는 절차를 메서드 run을 통해서 한 번에 실행되도록 한 코드이다.
그런데 경우에 따라서 합계와 평균을 화면에 출력하는 모습을 달리해야 하는 경우가 있다고 치자. 그런 경우에 상황에 따라서 동작 방법이 달라지는 메서드(sum, avg)는 추상 메소드로 만들어서 하위 클래스에서 구현하도록 하고 모든 클래스의 공통분모(setOprands, run)의 경우에는 상위 클래스에 두어서 코드의 중복, 유지보수의 편의성 등을 꾀할 수 있다.
abstract class Shape {
int x;
int y;
public void translate(int x, int y) {
this.x = x;
this.y = y;
}
public abstract void draw();
}
class Rectangel extends Shape {
int width;
int height;
public void draw() {
System.out.println("사각형 그리기 메서드");
}
}
class Circle extends Shape {
int radus;
public void draw() {
System.out.println("원 그리기 메서드");
}
}
public class AbstractTest {
public static void main(String[] args) {
Shape s = new Circle();
s.draw();
}
}
//자바버거 프렌차이즈(본사)
abstract class JavaBurger {
//오버라이드 금지
public final void welcome() { //일의 순서 못바꿈. 애초에 접근이 안됨.
greeting();
order();
serve();
sayGoodBye();
}
protected abstract void greeting(); //고객 응대방식은 각자 하되, 나머지는 본사의 규칙을 따라라.
private void order() {
System.out.println("자바버거의 메뉴를 보여주고 주문을 받는다.");
}
private void serve() {
System.out.println("주문한 자바버거를 서빙한다.");
}
private void sayGoodBye() {
System.out.println("안녕히 가세요~ 자바버거에 또 오세요 ~");
}
}
//자바버거부산
class JavaBurgerBusan extends JavaBurger {
@Override
public void greeting() {
System.out.println("어서오이소 ~ 자바버거입니더 ~");
}
}
//자바버거서울
class JavaBurgerSeoul extends JavaBurger {
@Override
public void greeting() {
System.out.println("어서오세요 ~ 자바버거입니다 ~");
}
}
//자바버거제주
class JavaBurgerJeju extends JavaBurger {
@Override
public void greeting() {
System.out.println("혼자옵셔예 ~ 자바버거입니더 ~");
}
}
public class JavaTest {
public static void main(String[] args) {
new JavaBurgerBusan().welcome();
new JavaBurgerJeju().welcome();
new JavaBurgerSeoul().welcome();
}
}