본문 바로가기
네이버클라우드/JAVA 웹 프로그래밍

JAVA 30일차 (2023-07-04) 자바 기초 DAY28_자바 프로그래밍_추상클래스

by prometedor 2023. 7. 4.
- 자바 프로그래밍(com.eomcs.oop)
  - 추상클래스(ex07) 사용법
    - Template Method 패턴(GoF)

 

ex07.a

Exam01.java

추상 클래스와 인스턴스

// 추상 클래스와 인스턴스
package com.eomcs.oop.ex07.a;

abstract class A {

}

public class Exam01 {

  public static void main(String[] args) {
    // 추상 클래스는 인스턴스를 생성할 수 없다.
    //    A obj1 = new A(); // 컴파일 오류!

    // 그러나 레퍼런스는 선언할 수 있다.
    A obj2 = null;
  }

}

ㄴ 추상 클래스는 인스턴스를 생성할 수 없지만 레퍼런스는 선언할 수 있음

ㄴ A obj2 = null;  => A 를 상속 받아 만든 인스턴스를 저장할 목적

추상 클래스는 클래스 앞에 abstract를 붙인다.

추상 클래스의 목적
- 추상 메서드가 있든 없든 상관없이 추상 클래스를 만들 수 있다.
- 서브 클래스에게 공통 필드나 메서드를 상속해주는 것이 목적이다.
- 직접 사용하지 않는다.
- 여러 클래스를 같은 타입으로 묶기 위함이다.
- 상속에서 generalization을 통해 수퍼 클래스를 정의하는 경우에 그 수퍼 클래스를 주로 추상 클래스로 만든다.

 

 

ex07.a

Exam02.java

추상 클래스와 추상 메서드

// 추상 클래스와 추상 메서드
package com.eomcs.oop.ex07.a;

// 추상 클래스
abstract class A2 {

  public abstract void m1();

  // 추상 메서드는 구현할 수 없다.
  //  public abstract void m2() {} // 컴파일 오류!
}

// 일반 클래스(concrete class)
class A2Sub extends A2 {
  @Override
  public void m1() {
    // 서브 클래스에서 추상 메서드를 구현해야 한다.
  }
}

public abstract class Exam02 extends A2 {
  // 서브 클래스에서 추상 메서드를 구현하지 않는다면,
  // 추상 메서드인 채로 남아 있기 때문에
  // 추상 클래스가 되어야 한다.
}

ㄴ 메서드 몸체가 없음

ㄴ 추상 클래스나 인터페이스에서만 선언 가능

ㄴ 서브 클래스 마다 구현이 다를 수 있는 경우에 사용 => 서브 클래스에서 추상 메서드를 구현

ㄴ 서브 클래스가 반드시 구현해야 하는 메서드가 있을 경우에 사용

추상 메서드
- 메서드 선언부에 abstract를 붙인다.
- 메서드 몸체(body)가 없다.
- 추상 클래스나 인터페이스에서만 선언할 수 있다.

추상 메서드의 용도
- 서브 클래스 마다 구현이 다를 수 있는 경우에 사용한다.
- 서브 클래스가 반드시 구현해야 하는 메서드가 있다면 추상 메서드로 선언하라!
- 추상 메서드를 상속 받은 서브 클래스는 반드시 추상 메서드를 구현해야 한다.
  만약 구현하지 않으면 서브 클래스도 추상 클래스가 되어야 한다.
  => 추상 메서드를 갖는 클래스는 오직 추상 클래스만이 가능하기 때문이다.

 

ex07.a

Exam03.java

추상 클래스 레퍼런스와 메서드 호출

// 추상 클래스 레퍼런스와 메서드 호출
package com.eomcs.oop.ex07.a;

abstract class A3 {
  public abstract void m1();
}

class A3Sub extends A3 {
  @Override // 이 애노테이션은 빼도 된다.
  public void m1() {
    System.out.println("A3Sub.m1() 호출됨!");
  }

  public void m2() {
    System.out.println("A3Sub.m2() 호출됨!");
  }
}

public class Exam03 {
  public static void main(String[] args) {
    A3 obj;

    // 추상 클래스의 인스턴스는 생성 불가!
    //    obj = new A3(); // Error!

    // 추상 메서드를 구현한 서브 클래스 만이 인스턴스 생성 가능!
    obj = new A3Sub();

    // 오버라이딩 규칙에 따라
    // - 레퍼런스가 실제 가리키는 객체의 클래스에서부터 메서드를 찾는다.
    obj.m1();

    // 참고!
    //
    // - 레퍼런스가 실제 가리키는 객체가 A3Sub 라 하더라도
    //   레퍼런스 타입의 범위를 넘어서 메서드를 호출할 수는 없다.
    //    obj.m2(); // 컴파일 오류!

    // - 물론 실제 인스턴스 타입으로 형변환 후에는 가능한다.
    ((A3Sub)obj).m2();
  }

  static void test(A3 obj) {
    obj.m1();
    // obj는 A3의 레퍼런스이다.
    // A3의 m1() 은 추상 메서드이다.
  }

}

ㄴ obj 는 A3의 레퍼런스이므로 서브 클래스들의 인스턴스 주소를 담을 수 있음

ㄴ static void test(A3 obj) => A3 의 서브 클래스의 인스턴스 주소를 받음

 추상 메서드는 구현하지 않은 메서드이기 때문에
 일반 클래스(=구현 클래스; concrete class)는 추상 메서드를 가질 수 없다.
 오직 추상 클래스만이 추상 메서드를 가질 수 있다.
 이유 =>
 - 일반 클래스는 인스턴스를 생성할 수 있다.
 - 인스턴스로 메서드를 호출하기 때문에
   일반 클래스에 완전히 정의되지 않은 메서드가 있다면,
   호출할 때 오류가 발생할 것이다.
obj는 A3의 레퍼런스이다.
A3의 m1() 은 추상 메서드이다.

obj 파라미터에 넘어오는 인스턴스(주소)는 A3의 인스턴스가 아니라 A3의 자식 클래스의 인스턴스일 것이다.
=> obj를 가지고 m1() 를 호출할 때는 
   실제 obj가 가리키는 인스턴스의 클래스에서 m1() 메서드를 찾아 호출한다.
=> 인스턴스를 만들었다는 것은 일반 클래스라는 뜻이고, 일반 클래스에는 추상 메서드가 없다.
   ㄴ 그러므로 A3를 상속 받은 일반 클래스는 반드시 m1() 메서드를 구현했다는 의미다.

 

 

ex07.a

Exam04.java

추상 클래스와 추상 메서드 정리

// 추상 클래스와 추상 메서드 정리!
package com.eomcs.oop.ex07.a;

abstract class A4 {

  public static int value1 = 100;
  static void m1() {System.out.println("A4.m1() 호출됨!");}

  public String value2 = "Hello!";
  void m2() {System.out.println("A4.m2() 호출됨!");}

  
  public abstract void m3();
}

class A4Sub extends A4 {
  @Override // 이 애노테이션은 빼도 된다. 참고, 메서드를 구현하는 것도 오버라이딩으로 본다.
  public void m3() {
    System.out.println("A4Sub.m3() 호출됨!");
  }
}

public class Exam04 {
  public static void main(String[] args) {
    A4 obj = new A4Sub();
    System.out.println(A4.value1);
    System.out.println(obj.value2);
    A4.m1();
    obj.m2();
    obj.m3();
  }
}

ㄴ 서브클래스에게 메서드 구현을 강제하고 싶을 때도 추상 메서드를 활용!

ㄴ static 변수는 클래스 이름으로 호출하자 (인스턴스 변수로도 가능하지만 클래스 이름으로 하는 것이 좋음 => 인스턴스 변수와 헷갈릴 수 있기 때문)

추상 클래스의 용도는
- 서브 클래스들이 가져야할 공통 변수나 메서드를 제공하는 것이다.
- 그래서 다음과 같이 일반 변수나 메서드를 정의할 수 있다.

추상 클래스는 추상 메서드도 가질 수 있다.
  의미?
  - 서브클래스마다 구현이 다를 경우
    굳이 수퍼 클래스에서 정의할 필요가 없기 때문에
    완전히 정의하지 않은 추상 메서드로 남겨둔다.
  - 서브클래스에게 메서드 구현을 강제하고 싶을 때도 추상 메서드를 활용한다.
    추상 메서드를 상속 받은 서브 클래스는 반드시 정의해야 하기 때문이다.
    서브 클래스가 상속 받은 추상 메서드를 구현하지 않는다면
    추상 메서드를 그냥 보유하기 때문에 일반 클래스가 될 수 없다.
    즉 추상 클래스가 되어야 한다.

 

ex07.a

Exam05.java

추상 클래스와 추상 메서드의 활용

// 추상 클래스와 추상 메서드의 활용
package com.eomcs.oop.ex07.a;

abstract class Letter {

  // 수퍼 클래스는 서브 클래스에게 구현된 멤버를 상속해준다.
  String content;

  public void setContent(String content) {
    this.content = content;
  }

  // 수퍼 클래스에서 기능이 어떻게 동작하는지 정의한다.
  // => 템플릿의 역할을 하는 메서드를 수퍼 클래스에 둔다.
  // => 자세한 구현은 서브 클래스에 맡긴다.
  public void print() { // <== 템플릿 메서드 디자인 패턴에서 "템플릿 메서드"에 해당한다. 
    this.printHeader();
    System.out.println(this.content);
    System.out.println();
    System.out.printf("              From %s!\n", this.getSign());
    System.out.println();
    this.printFooter();
  }

  // 세부 사항에 대한 것은
  // 서브 클래스에게 구현을 맡긴다.
  public abstract void printHeader();
  public abstract void printFooter();
  public abstract String getSign();
}

// 상세한 기능에 대한 구현은 다음과 같이 서브 클래스에게 맡긴다.
class LoveLetter extends Letter {

  @Override
  public void printHeader() {
    System.out.println("♥♥♥♥♥♥♥♥♥♥♥♥♥♥ [사랑을 그대에게] ♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥");
  }

  @Override
  public String getSign() {
    return "당신의 영원한 팔로워, 홍길동";
  }

  @Override
  public void printFooter() {
    System.out.println("♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪");

  }

  public void x() {}
}

class ReportLetter extends Letter {
  @Override
  public void printHeader() {
    System.out.println("[비트캠프 강의 보고서]");
  }

  @Override
  public String getSign() {
    return "강사 홍길동";
  }

  @Override
  public void printFooter() {
    System.out.println("==========================================");

  }
}

public class Exam05 {
  public static void main(String[] args) {
    Letter letter = new LoveLetter();
    letter.setContent("눈이 녹으면 무엇이 될까요?\n"
        + "봄이 온다 합니다.\n"
        + "따뜻한 봄이 기다려지네요.");
    letter.print();

    Letter letter2 = new ReportLetter();
    letter2.setContent("강의중!");
    letter2.print();
  }
}

Template method - 추상클래스를 통해 알고리즘의 뼈대를 정의함

수퍼 클래스에서 틀을 잡고 세세한 동작의 구현은 서브 클래스에게 맡김

ㄴ 추상 클래스와 추상 메서드를 함께 활용

 

 

추장 클래스와 추상 메서드의 활용

ex07.b

BubbleSort.java

package com.eomcs.oop.ex07.b;

public class BubbleSort {

  public void run(int[] values) {
    int size = values.length;
    for (int i = 0; i < size - 1; i++) {
      for (int j = 0; j < size - i - 1; j++) {
        if (values[j] > values[j + 1]) {
          //System.out.printf("%d <==> %d\n", values[j], values[j + 1]);
          int temp = values[j];
          values[j] = values[j + 1];
          values[j + 1] = temp;
        }
      }
    }
  }
}

 

ex07.b

QuickSort.java

package com.eomcs.oop.ex07.b;

public class QuickSort {

  int partition(int[] arr, int low, int high)
  {
    int pivot = arr[high];
    int i = (low-1); // index of smaller element
    for (int j=low; j<high; j++)
    {
      // If current element is smaller than or
      // equal to pivot
      if (arr[j] <= pivot)
      {
        i++;

        // swap arr[i] and arr[j]
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
      }
    }

    // swap arr[i+1] and arr[high] (or pivot)
    int temp = arr[i+1];
    arr[i+1] = arr[high];
    arr[high] = temp;

    return i+1;
  }

  public void start(int[] arr, int low, int high) {
    if (low < high)
    {
      /* pi is partitioning index, arr[pi] is
          now at right place */
      int pi = partition(arr, low, high);

      // Recursively sort elements before
      // partition and after partition
      start(arr, low, pi-1);
      start(arr, pi+1, high);
    }
  }
}

 

ex07.b

Exam01.java

추장 클래스와 추상 메서드의 활용 : 적용 전

// 추상 클래스와 추상 메서드의 활용: 적용 전
package com.eomcs.oop.ex07.b;

import java.util.Arrays;

public class Exam01 {

  static int[] createRandomNumbers(final int size) {
    int[] arr = new int[size];
    for (int i = 0; i < size; i++) {
      arr[i] = i;
    }

    int count = size >> 1;
    for (int i = 0; i < count; i++) {
      int index1 = (int)(Math.random() * size); 
      int index2 = (int)(Math.random() * size);
      int temp = arr[index1];
      arr[index1] = arr[index2];
      arr[index2] = temp;
    }
    return arr;
  }

  public static void main(String[] args) {

    int[] values = createRandomNumbers(100000);
    int[] values2 = Arrays.copyOf(values, values.length);

    BubbleSort s1 = new BubbleSort();
    QuickSort s2 = new QuickSort();

    display(s1, values);
    display(s2, values2);

  }

  // 정렬을 수행하는 객체와 값을 주면
  // 그 값을 정렬한 후 출력하는 메서드이다.
  static void display(BubbleSort sorter, int[] values) {

    //System.out.println("[정렬 전]");
    //printNumbers(values);

    long start = System.currentTimeMillis();

    // BubbleSort 사용법에 맞춰 정렬을 수행한다.
    sorter.run(values);

    long end = System.currentTimeMillis();
    System.out.printf("걸린시간: %d\n", end - start);

    //System.out.println("[정렬 후]--------------------------");
    //printNumbers(values);
  }

  static void display(QuickSort sorter, int[] values) {

    //System.out.println("[정렬 전]");
    //printNumbers(values);

    long start = System.currentTimeMillis();

    // QuickSort 사용법에 맞춰 정렬을 수행한다.
    sorter.start(values, 0, values.length - 1);

    long end = System.currentTimeMillis();
    System.out.printf("걸린시간: %d\n", end - start);

    //System.out.println("[정렬 후]--------------------------");
    //printNumbers(values);
  }

  static void printNumbers(int[] values) {
    for (int  value : values) {
      System.out.print(value + ",");
    }
    System.out.println();
  }
}

ㄴ BubbleSort 보다는 QuickSort 사용하는 것이 더 걸린 시간이  적게 걸림

두 개의 정렬 객체가 서로 다른 타입이기 때문에
정렬을 수행하고 출력할 메서드를 따로 따로 만들어야 한다.
클래스의 사용법도 달라서 불편하다.
BubbleSort 객체는 run()을 호출해야 하고,
QuickSort 객체는 start()를 호출해야 한다.

 

 

generalization 기법 이용

ex07.c

Sorter.java

package com.eomcs.oop.ex07.c;

// 정렬을 수행하는 클래스를 한 타입으로 묶기 위해
// 다음 클래스를 정의하였다.
//
public class Sorter {
  public void sort(int[] values) {};
}

 

ex07.c

Exam01.java

정렬 객체를 일관성 있게 사용하려면 같은 타입으로 묶어야 함 => 상속의 generalization 기법 이용

// 정렬 객체를 일관성 있게 사용하려면 같은 타입으로 묶어야 한다. 
// 상속의 generalization 기법 이용!
package com.eomcs.oop.ex07.c;

public class Exam01 {

  public static void main(String[] args) {
    int[] values = {23, 7, 12, 15, 9, 2, 22, 8, 11, 25, 13, 5};
    int[] values2 = {23, 7, 12, 15, 9, 2, 22, 8, 11, 25, 13, 5};

    // 정렬 객체의 사용 방법을 통일하면 메서드를 호출하기가 쉬워진다.
    // 
    // => 두 클래스를 같은 부모의 자식 클래스가 되게 하라.
    // => 같은 부모라는 뜻은 같은 메서드를 갖는다는 의미다.
    // => 즉 메서드가 같으니 사용법도 같다.
    //

    // 두 개의 정렬 객체가 같은 타입이기 때문에 사용하기 편하다.
    // => 언제든 다른 객체로 교체할 수 있어 유연하다.
    display(new BubbleSort(), values);
    display(new QuickSort(), values2);
  }

  static void display(Sorter sorter, int[] values) {

    // 정렬 객체의 클래스가 뭔지 상관없다.
    // 그 클래스를 사용할 때는 공통 분모가 되는
    // 수퍼 클래스의 메서드를 호출한다.
    sorter.sort(values);

    // 정렬된 값을 출력한다.
    for (int  value : values) {
      System.out.print(value + ",");
    }
    System.out.println();
  }
}

 

ex07.c

Exam02.java

// 수퍼 클래스가 concrete 클래스일 때 문제점
package com.eomcs.oop.ex07.c;

public class Exam02 {

  public static void main(String[] args) {
    int[] values = {23, 7, 12, 15, 9, 2, 22, 8, 11, 25, 13, 5};

    display(new Sorter(), values);

    // Sorter 클래스를 추상 클래스로 만드는 것이 바람직하다.
    // 추상 클래스인 경우 직접 인스턴스를 만들어 사용할 수 없기 때문이다.

  }

  static void display(Sorter sorter, int[] values) {
    sorter.sort(values);

    for (int  value : values) {
      System.out.print(value + ",");
    }
    System.out.println();
  }
}

 

ㄴ Sorter를 직접 사용하는 것은 아무런 의미가 없음
     => 정렬을 수행하는 메서드인 sort()가 아무 일도 안 함
           ㄴ 그럼에도 불구하고 다른 개발자가 Sorter 클래스를 직접 사용하는 것을 막을 수 없음
                => Sorter 클래스는 인스턴스를 만들 수 있는 concrete 클래스이기 때문

 

ex07.d

Sorter.java

추상 클래스 사용

package com.eomcs.oop.ex07.d;

// 정렬을 수행하는 클래스를 한 타입으로 묶기 위해
// 다음 클래스를 정의하였다.
// => 추상 클래스로 선언하면 인스턴스 생성을 막을 수 있다.
// => 즉 서브 클래스를 정의할 때 상속 받아 쓰는 용도로 한정할 수 있다.
public abstract class Sorter {
  public void sort(int[] values) {};
}

ㄴ Sorter 클래스를 추상클래스로 선언

 

 

 

ex07.d

Exam01.java

수퍼 클래스를 추상 클래스로 선언하기

// 수퍼 클래스를 추상 클래스로 선언하기
package com.eomcs.oop.ex07.d;

public class Exam01 {

  public static void main(String[] args) {
    int[] values = {23, 7, 12, 15, 9, 2, 22, 8, 11, 25, 13, 5};

    // Sorter 클래스를 추상 클래스로 선언했기 때문에
    // 이제 Sorter의 인스턴스를 생성을 막을 수 있다.
    //
    // display(new Sorter(), values); // 컴파일 오류!

    display(new BubbleSort(), values); // OK!
    display(new QuickSort(), values); // OK!

  }

  static void display(Sorter sorter, int[] values) {
    sorter.sort(values);
    for (int value : values) {
      System.out.print(value + ",");
    }
    System.out.println();
  }
}

ㄴ Sorter 클래스를 추상클래스로 선언하여 인스턴스 생성을 막음

 

ex07.d

MergeSort.java

package com.eomcs.oop.ex07.d;

// 기존의 정렬 클래스처럼 동일한 방법으로 사용하려면
// Sorter를 상속 받아 같은 타입으로 묶어야 한다.
// 그래서 Sorter 를 상속 받았다.
//
public class MergeSort extends Sorter {

  // Merges two subarrays of arr[].
  // First subarray is arr[l..m]
  // Second subarray is arr[m+1..r]
  void merge(int arr[], int l, int m, int r)
  {
    // Find sizes of two subarrays to be merged
    int n1 = m - l + 1;
    int n2 = r - m;

    /* Create temp arrays */
    int L[] = new int [n1];
    int R[] = new int [n2];

    /*Copy data to temp arrays*/
    for (int i=0; i<n1; ++i)
      L[i] = arr[l + i];
    for (int j=0; j<n2; ++j)
      R[j] = arr[m + 1+ j];


    /* Merge the temp arrays */

    // Initial indexes of first and second subarrays
    int i = 0, j = 0;

    // Initial index of merged subarry array
    int k = l;
    while (i < n1 && j < n2)
    {
      if (L[i] <= R[j])
      {
        arr[k] = L[i];
        i++;
      }
      else
      {
        arr[k] = R[j];
        j++;
      }
      k++;
    }

    /* Copy remaining elements of L[] if any */
    while (i < n1)
    {
      arr[k] = L[i];
      i++;
      k++;
    }

    /* Copy remaining elements of R[] if any */
    while (j < n2)
    {
      arr[k] = R[j];
      j++;
      k++;
    }
  }

  // Main function that sorts arr[l..r] using
  // merge()
  void sort(int arr[], int l, int r)
  {
    if (l < r)
    {
      // Find the middle point
      int m = (l+r)/2;

      // Sort first and second halves
      sort(arr, l, m);
      sort(arr , m+1, r);

      // Merge the sorted halves
      merge(arr, l, m, r);
    }
  }
}
 Sorter 클래스를 상속 받았지만
   Sorter 클래스의 sort() 메서드를 재정의하지 않았다.
   sort(int[], int, int) 메서드는 내부적으로 사용하기 위해 추가한 메서드이다.
   이렇게 Sorter를 상속 받아 서브 클래스를 만드는 것에는 문제가 없지만,
   이 클래스를 사용하는 쪽에서는 문제가 발생할 것이다!
   => 이 클래스를 사용하는 쪽에서는 Sorter 에서 정의한
   sort()라는 메서드가 정렬을 수행할 것이라고 믿기 때문이다.

 

ex07.d

Exam02.java

일반 메서드의 한계 - 서브 클래스에게 구현을 강요할 수 없다.

// 일반 메서드의 한계 - 서브 클래스에게 구현을 강요할 수 없다.
package com.eomcs.oop.ex07.d;

public class Exam02 {

  public static void main(String[] args) {
    int[] values = {23, 7, 12, 15, 9, 2, 22, 8, 11, 25, 13, 5};
    int[] values2 = {23, 7, 12, 15, 9, 2, 22, 8, 11, 25, 13, 5};
    int[] values3 = {23, 7, 12, 15, 9, 2, 22, 8, 11, 25, 13, 5};

    display(new BubbleSort(), values);
    display(new QuickSort(), values2);

    // 새로 추가한 MergeSort 클래스를 사용하여 데이터를 정렬해 보자!
    display(new MergeSort(), values3);
  }

  static void display(Sorter sorter, int[] values) {

    sorter.sort(values);

    for (int  value : values) {
      System.out.print(value + ",");
    }
    System.out.println();
  }
}

ㄴ MergeSort 는 오버라이딩이 아닌 오버로딩을 하여 파라미터로 넘어오는 객체가 MergeSort 라면 sorter.sort(values) 는 의미 없음

=> Sorter 의 서브 클래스들이 반드시 sort() 메서드를  구현하도록 강제하기

 - MergeSort는 Sorter를 상속 받아 만들었다.
     - 따라서 이 클래스를 사용하는 개발자 쪽에서는 Sorter를 상속 받았으니,
       당연히 정렬을 수행하기 위해 sort(int[]) 메서드를 호출할 것이다.
     - 그러나 MergeSort 클래스는 sort(int[]) 메서드를 오버라이딩 하지 않았다.
     - 개발자가 sort(int[])를 오버라이딩 하는 것을 놓쳤거나 일부러 하지 않은 것이다.
    
     이런 실수나 의도를 방지하고자 등장한 문법이 "추상 메서드"이다.
     서브 클래스에게 구현을 강제할 필요가 있을 때 추상 메서드로 선언하라!
     
 당연히 파라미터로 넘어 오는 클래스는 Sorter의 서브 클래스일 것이다.
     (Sorter는 추상 클래스이기 때문에 인스턴스를 생성할 수 없다.따라서 Sorter의 인스턴스가 넘어올 수는 없다.)
     따라서 정렬을 수행하려면 Sorter에 정의된 sort()를 호출할 것이다.
     여기서 문제가 발생한다.
     BubbleSort나 QuickSort 클래스는 sort()를 오버라이딩 하였다.
     그러나 MergeSort는 sort() 오버라이딩 하지 않았다.
     따라서 파라미터로 넘어 오는 객체가 MergeSort 라면 sorter.sort(values); 메서드를 호출하는 것은 아무런 의미가 없다.

 

 

ex07.e

Sorter.java

package com.eomcs.oop.ex07.e;

// 정렬을 수행하는 클래스를 한 타입으로 묶기 위해
// 다음 클래스를 정의하였다.
// => 추상 클래스로 선언하면 인스턴스 생성을 막을 수 있다.
// => 즉 서브 클래스를 정의할 때 상속 받아 쓰는 용도로 한정할 수 있다.
public abstract class Sorter {

  // 메서드를 추상 메서드로 선언하는 순간
  // => 모든 서브 클래스는 반드시 이 메서드를 구현해야 한다.
  // => 구현하지 않으면 추상 클래스가 될 수 밖에 없다.
  // => 서브 클래스에게 구현을 강제하는 효과가 있다.
  public abstract void sort(int[] values);
}

 

ex07.e

MergeSort.java

package com.eomcs.oop.ex07.e;

// 기존의 정렬 클래스처럼 동일한 방법으로 사용하려면
// Sorter를 상속 받아 같은 타입으로 묶여야 한다.
// 그래서 Sorter 를 상속 받았다.
//
public class MergeSort extends Sorter {

  // Sorter에서 상속 받은 메서드가 추상 메서드이기 때문에
  // 이 서브 클래스는 반드시 구현해야 한다.
  // 구현하지 않으면 이 클래스도 추상 클래스가 될 수 밖에 없다.
  @Override
  public void sort(int[] values) {
    sort(values, 0, values.length - 1);
  }

  //Merges two subarrays of arr[].
  // First subarray is arr[l..m]
  // Second subarray is arr[m+1..r]
  void merge(int arr[], int l, int m, int r)
  {
    // Find sizes of two subarrays to be merged
    int n1 = m - l + 1;
    int n2 = r - m;

    /* Create temp arrays */
    int L[] = new int [n1];
    int R[] = new int [n2];

    /*Copy data to temp arrays*/
    for (int i=0; i<n1; ++i)
      L[i] = arr[l + i];
    for (int j=0; j<n2; ++j)
      R[j] = arr[m + 1+ j];


    /* Merge the temp arrays */

    // Initial indexes of first and second subarrays
    int i = 0, j = 0;

    // Initial index of merged subarry array
    int k = l;
    while (i < n1 && j < n2)
    {
      if (L[i] <= R[j])
      {
        arr[k] = L[i];
        i++;
      }
      else
      {
        arr[k] = R[j];
        j++;
      }
      k++;
    }

    /* Copy remaining elements of L[] if any */
    while (i < n1)
    {
      arr[k] = L[i];
      i++;
      k++;
    }

    /* Copy remaining elements of R[] if any */
    while (j < n2)
    {
      arr[k] = R[j];
      j++;
      k++;
    }
  }

  // Main function that sorts arr[l..r] using
  // merge()
  void sort(int arr[], int l, int r)
  {
    if (l < r)
    {
      // Find the middle point
      int m = (l+r)/2;

      // Sort first and second halves
      sort(arr, l, m);
      sort(arr , m+1, r);

      // Merge the sorted halves
      merge(arr, l, m, r);
    }
  }
}

 

 

 

 

ex07.f

Exam01.java

추상 클래스를 인터페이스로 전환하기

// 추상 클래스를 인터페이스로 전환하기
package com.eomcs.oop.ex07.f;

public class Exam01 {

  public static void main(String[] args) {
    int[] values = {23, 7, 12, 15, 9, 2, 22, 8, 11, 25, 13, 5};
    int[] values2 = {23, 7, 12, 15, 9, 2, 22, 8, 11, 25, 13, 5};
    int[] values3 = {23, 7, 12, 15, 9, 2, 22, 8, 11, 25, 13, 5};

    // Sorter 추상 클래스처럼 필드나 구현 메서드 없이 추상 메서드만 있을 경우
    // 인터페이스로 정의하는 것이 더 낫다.
    //
    display(new BubbleSort(), values);
    display(new QuickSort(), values2);
    display(new MergeSort(), values3);

  }

  static void display(Sorter sorter, int[] values) {
    sorter.sort(values);
    for (int  value : values) {
      System.out.print(value + ",");
    }
    System.out.println();
  }
}

 

ex07.f

Sorter.java

package com.eomcs.oop.ex07.f;

// 추상 메서드만 있을 경우, 
// 객체 사용 규칙을 정의하는 "인터페이스" 문법으로 바꿔도 좋다.
public interface Sorter {

  // 인터페이스는 호출 규칙을 정의하는 것이기 때문에 
  // 모든 메서드는 기본이 public 이고, abstract 이다.
  // => 다음과 같이 메서드 선언에 public 과 abstract를 생략해도 된다.
  void sort(int[] values);
}

ㄴ public abastract 생략

 

ex07.f

MergeSort.java

package com.eomcs.oop.ex07.f;

public class MergeSort implements Sorter {

  // 인터페이스의 추상 메서드를 구현하는 것도 
  // "오버라이딩" 이라 부른다.
  @Override
  public void sort(int[] values) {
    sort(values, 0, values.length - 1);
  }

  //Merges two subarrays of arr[].
  // First subarray is arr[l..m]
  // Second subarray is arr[m+1..r]
  void merge(int arr[], int l, int m, int r)
  {
    // Find sizes of two subarrays to be merged
    int n1 = m - l + 1;
    int n2 = r - m;

    /* Create temp arrays */
    int L[] = new int [n1];
    int R[] = new int [n2];

    /*Copy data to temp arrays*/
    for (int i=0; i<n1; ++i)
      L[i] = arr[l + i];
    for (int j=0; j<n2; ++j)
      R[j] = arr[m + 1+ j];


    /* Merge the temp arrays */

    // Initial indexes of first and second subarrays
    int i = 0, j = 0;

    // Initial index of merged subarry array
    int k = l;
    while (i < n1 && j < n2)
    {
      if (L[i] <= R[j])
      {
        arr[k] = L[i];
        i++;
      }
      else
      {
        arr[k] = R[j];
        j++;
      }
      k++;
    }

    /* Copy remaining elements of L[] if any */
    while (i < n1)
    {
      arr[k] = L[i];
      i++;
      k++;
    }

    /* Copy remaining elements of R[] if any */
    while (j < n2)
    {
      arr[k] = R[j];
      j++;
      k++;
    }
  }

  // Main function that sorts arr[l..r] using
  // merge()
  void sort(int arr[], int l, int r)
  {
    if (l < r)
    {
      // Find the middle point
      int m = (l+r)/2;

      // Sort first and second halves
      sort(arr, l, m);
      sort(arr , m+1, r);

      // Merge the sorted halves
      merge(arr, l, m, r);
    }
  }
}

ㄴ 인터페이스의 추상 메서드를 구현하는 것도 "오버라이딩" 이라 부름

 

ex07.f

QuickSort.java

package com.eomcs.oop.ex07.f;

public class QuickSort implements Sorter {

  // 인터페이스의 추상 메서드를 구현하는 것도 
  // "오버라이딩" 이라 부른다.
  @Override
  public void sort(int[] values) {
    start(values, 0, values.length - 1);
  }

  private int partition(int[] arr, int low, int high)
  {
    int pivot = arr[high];
    int i = (low-1); // index of smaller element
    for (int j=low; j<high; j++)
    {
      // If current element is smaller than or
      // equal to pivot
      if (arr[j] <= pivot)
      {
        i++;

        // swap arr[i] and arr[j]
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
      }
    }

    // swap arr[i+1] and arr[high] (or pivot)
    int temp = arr[i+1];
    arr[i+1] = arr[high];
    arr[high] = temp;

    return i+1;
  }

  private void start(int[] arr, int low, int high) {
    if (low < high)
    {
      /* pi is partitioning index, arr[pi] is
          now at right place */
      int pi = partition(arr, low, high);

      // Recursively sort elements before
      // partition and after partition
      start(arr, low, pi-1);
      start(arr, pi+1, high);
    }
  }
}

ㄴ 인터페이스의 추상 메서드를 구현하는 것도 "오버라이딩" 이라 부름