2022.08.05/자바 정리/Iterator 패턴과 중첩 클래스 사용법

Jimin·2022년 8월 5일
1

비트캠프

목록 보기
17/60

  • Iterator 패턴과 중첩 클래스 사용법

데이터 자료 구조와 조회 조건에 상관없이 일관된 방법으로 조회하기: Iterator 패턴 적용

  • 게시글을 최신 순으로 나열한다.
    • 게시글을 조회할 때 역순으로 조회한다.

board-app 적용

1단계 - BoardDao에서 목록을 출력할 때 최신 순으로 나열한다.

  • board.dao.BoardDao 클래스 변경
    • findAll() 메서드 변경: LinkedList가 리턴해준 배열을 거꾸로 만들어 리턴한다.
    • 문제: 데이터 조회 방식에 따라 코딩이 달라진다.
    • 유지 보수에 좋은 방식: 데이터 조회 방식에 상관없이 동일한 방식으로 조회하고 싶다.
    • 해결책?
      • 데이터 조회를 수행하는 코드를 별도의 객체로 분리하여 관리한다.
      • 데이터 조회 방식을 일관되게 하기 위해 인터페이스를 이용하여 규격화 한다.
  • findAll() method in BoardDao class
 public Board[] findAll() {
    Board[] arr = list.toArray(new Board[0]);

    // 역순으로 정렬하여 리턴한다.
    Board[] arr2 = new Board[arr.length];
    for(int i=0;i<arr2.length;i++) {
      arr2[i] = arr[arr.length-i-1];
    }
    return arr2;
  }

Iterator 패턴과 중첩 클래스 사용법

  • iterator 디자인 패턴 : 데이터 목록에서 값을 꺼내는 것을 별도의 객체로 분리하는 설계 방식
    • 즉 데이터 목록을 관리하는 객체를 직접 사용하여 값을 꺼내는 것이 아니라, 값을 꺼내는 주는 별도의 객체의 도움을 받아 값을 꺼낸다.
    • 값을 꺼내주는 객체를 "Iterator"라 부른다.
    • 값을 꺼내주는 객체의 사용법을 통일하기 위하여 인터페이스로 사용 규칙을 정의한다.
    • 각각의 데이터 목록 관리 객체는 Iterator 규칙에 따라 값을 꺼내는 객체를 리턴한다.

Iterator 패턴 적용 전

  • HashSet은 데이터를 저장할 때 데이터를 가지고 특정 계산식의 결과값을 인덱스로 사용하여 저장한다.
    • 특정 계산식의 결과값?
      → 해시값(hash value) = 정수로된 ID값
    • 배열의 인덱스처럼 0에서 시작하는 값으로 조회할 수 없다.
      ⇒ get() 메소드가 존재하지 않는다.

- 결론!

  • 데이터 저장방식(data structure)에 따라 데이터를 조회하는 방식이 다르다.
    ⇒ 즉, 데이터 조회를 위한 코딩이 일관성이 없다.

    ⇒ 데이터 목록 객체의 종류(ArrayList, LinkedList, Stack, Queue)에 상관 없이 일관된 방법으로 꺼내기 위해 Iterator를 사용하자!
    → 각각의 데이터 자료주고에서 제공하는 메서드를 사용하여 데이터를 직접 꺼내지 말고 값을 꺼내주는 객체의 도움을 받자!

Iterator 패턴 적용 후

  • 데이터 조회를 다른 객체에게 맡긴다.
  • 데이터 조회방식을 통일시킬 수 있다!
    ⇒ 데이터 조회의 일관성 확보!
    ⇒ 유지보수가 쉬워진다.

Iterator 패턴

  • 데이터 조회 코드를 객체화 시켜 분리함으로써
    → 자료구조에 상관없이 일관된 방법으로 데이터 조회 수행
    → 객체화 ⇒ 다양한 조회 방식을 제공

Low Coupling

  • Iterator 인터페이스를 이용하면, Test01 클래스가 데이터 목록을 불러 올 때, 이용할 각 클래스들의 구체적인 이름을 알 필요가 없다.

중첩 클래스

class A{ //← class A 안에 있는 class들은 모두 Nested class(중첩 클래스)들이다.
	static class X{} //← static nested class (after2)
    class Y {} //← non-static nested class = inner class (after3)
    void m1(){
    	class Z{} //← local class
    }
}

class B{} //← package member class (after1)

Iterator 단계별로 구현하기


1. 패키지 멤버로 Iterator 클래스 정의하기
2. 클래스 멤버(스태틱 중첩 클래스)로 Iterator 클래스 정의하기
3. 인스턴스 멤버(논 스태틱 주첩 클래스 = inner class)로 Iterator 클래스 정의하기
4. 로컬 클래스로 Iterator 클래스 정의하기
5. 익명 클래스로 Iterator 클래스 정의하기

  • ArrayList, LinkedList, Stack, Queue 모두에 적용해야하는데, ArrayList만 대표적으로 코드 정리할 것이당

0단계 - Iterator 인터페이스 만들기

  • Iterator interface
// 데이터 조회를 수행하는 객체 사용법 정의 
public interface Iterator<E> {

  // 목록에 조회할 데이터가 있는지 검사할 때 호출
  boolean hasNext();

  // 목록에서 데이터를 한 개 꺼낼 때 호출
  E next();
}
  • Test01 class
public class Test01 {

  public static void main(String[] args) {
    ArrayList<String> list1 = new ArrayList<>();
    list1.add("aaa");
    list1.add("bbb");
    list1.add("ccc");
    list1.add("ddd");
    
    
   	Iterator<String> iterator = list1.iterator();
    while (iterator.hasNext()) {
      System.out.print(iterator.next() + ",");
    }
    System.out.println();
  }
}

1단계 - 패키지 멤버로 Iterator 클래스 정의하기

  • ArrayListIterator class
// ArrayList에서 데이터를 꺼내줄 객체
//
public class ArrayListIterator<E> implements Iterator<E> {
  ArrayList<E> list;
  int index = 0;
  public ArrayListIterator(ArrayList<E> list) {
    this.list = list;
  }
  @Override
  public boolean hasNext() {
    return index < list.size();
  }
  @Override
  public E next() {
    return list.get(index++);
  }
}
  • ArrayList class
    • ArrayListIterator에 this 를 넘겨주는데, 여기서 this는 test01 클래스에서 생성한 list1에 들어 있는 주소값을 가르킨다.
    • 즉, test01이 ArrayList class에게 iterator 객체를 만들어 달라고 요구했고
      그 뒤, ArrayList 클래스가 ArrayListIterator 클래스에게 자신의 주소값 this를 넘겨주고 자신이 가지고 있는 메소드들(size(), get())을 이용하라고 하는 것이다.
public class ArrayList<E> {

  static final int DEFAULT_SIZE = 5;

  Object[] arr;
  int size;

  public ArrayList() {
    this(0); //여기서 0은 밑의 생성자에 0을 넘겨줘서 사용할 새로운 배열을 생성하게 한다.
  }

  public ArrayList(int capacity) {
    if (capacity > DEFAULT_SIZE)
      arr = new Object[capacity];
    else 
      arr = new Object[DEFAULT_SIZE];
  }

  public Object[] toArray() {
    Object[] list = new Object[this.size];
    for (int i = 0; i < this.size; i++) {
      list[i] = this.arr[i];
    }
    return list;
  }

  public void add(E value) {
    if (this.size == arr.length) 
      increase();

    arr[this.size++] = value;
  }

  public int insert(int index, E value) {
    if (index < 0 || index >= size)
      return -1;

    if (this.size == arr.length) 
      increase();

    for (int i = size - 1; i >= index; i--)
      this.arr[i + 1] = this.arr[i];

    this.arr[index] = value;
    size++;

    return 0;
  }

  @SuppressWarnings("unchecked")
  public E get(int index) {
    if (index < 0 || index >= size)
      return null;

    return (E) this.arr[index];
  }

  @SuppressWarnings("unchecked")
  public E set(int index, E value) {
    if (index < 0 || index >= size)
      return null;

    E old = (E) this.arr[index];
    this.arr[index] = value;
    return old;
  }

  @SuppressWarnings("unchecked")
  public E remove(int index) {
    if (index < 0 || index >= size)
      return null;

    E old = (E) this.arr[index];

    for (int i = index; i < size - 1; i++) 
      this.arr[i] = this.arr[i+1];

    size--;

    return old;
  }

  public int size() {
    return this.size;
  }

  private void increase() {
    int originSize = arr.length;
    int newSize = originSize + (originSize >> 1);
    Object[] temp = new Object[newSize];
    for (int i = 0; i < this.arr.length; i++) {
      temp[i] = this.arr[i];
    }
    arr = temp;
  }

  // Iterator 구현체를 제공한다.
  public Iterator<E> iterator() {
    return new ArrayListIterator<E>(this);
  }
}

2단계 - 클래스 멤버(스태틱 중첩 클래스)로 Iterator 클래스 정의하기

  • 패키지 멤버로 두었을 때(1단계)의 문제점
    • ArrayListIterator는 오직 ArrayList 클래스에서만 생성한다.
    • 즉 ArrayList가 아닌 클래스에서 생성할 일이 없다.
    • 그럼에도 불구하고 패키지 멤버이기 때문에 전체 패키지에 공개되어 있다.
  • 해결책
    • 각 Iterator 클래스를 그 Iterator를 생성하는 클래스 안으로 넣어서 쓸데없는 노출을 막는다.
    • 외부의 객체는 Iterator 인터페이스 규칙에 따라 사용할 수 있어 중첩 클래스로 만들어도 괜찮다.
  • ArrayList class
public class ArrayList<E> {

  ...(동일 코드 생략)

  // Iterator 구현체를 제공한다.
  public Iterator<E> iterator(){
    return new ArrayListIterator<E>(this); // list 변수 주소 값이 this에 넘어간다.
  }

  // static nested class(스태틱 중첩 클래스)
  static class ArrayListIterator<E> implements Iterator<E> {
    ArrayList<E> list;
    int index=0;

    public ArrayListIterator(ArrayList<E> list) {
      this.list = list;
    }
    @Override
    public boolean hasNext() {
      return index < list.size();
    }
    @Override
    public E next() {
      return list.get(index++);
    }
  }

}

3단계 - 인스턴스 멤버(논 스태틱 주첩 클래스 = inner class)로 Iterator 클래스 정의하기

  • ArrayListIterator 클래스를 인스턴스 멤버로 구현하면, ArrayList 클래스의 iterator() 메소드에서 ArrayListIterator 클래스 개체를 new를 통해 호출할 때 this를 넘겨주지 않아도,
    컴파일러가 자동으로 넘겨준다.
  • 바깥 클래스의 인스턴스를 사용하는 경우 논클래스 중첩 클래스로 선언하라!
    • 왜? → 바깥 클래스의 인스턴스 주소를 받은 필드와 생성자 파라미터가 자동으로 추가된다.
    • 즉, 개발자가 바깥 클래스의 인스턴스 주소를 다룰 필요가 없다.
    • 대신, 바깥클래스명.this.멤버와 같이 접근이 가능하다!
  • ArrayList class
public class ArrayList<E> {

   ...(동일 코드 생략)
   
  // Iterator 구현체를 제공한다.
  public Iterator<E> iterator() {
    // 생성자를 호출할 때 바깥 클래스의 인스턴스 주소를 넘겨주지 않아도 
    // 컴파일러가 넘겨주는 코드로 자동 변환한다.
    // 
    return new ArrayListIterator<E>();

    // 위의 코드는 컴파일 될 때 다음과 같이 바뀐다.
    // return new ArrayListIterator<E>(this);
  }

  // non-static nested class(스태틱 중첩 클래스) = inner class
  class ArrayListIterator<T> implements Iterator<T> {

    // 논스태틱 중첩 클래스인 경우 바깥 클래스의 인스턴스 주소를 받을 필드가 자동 추가된다.
    // 따라서 다음과 같이 개발가 직접 추가할 필요가 없다.
    // ArrayList<E> this$0; // 자동 생성 된다.

    int index = 0;

    // 논스태틱 중첩 클래스인 경우 바깥 클래스의 인스턴스 주소를 받는 파라미터가 
    // 생성자가 자동으로 추가된다.
    // 개발자가 직접 추가할 필요가 없다.
    //    public ArrayListIterator(ArrayList<E> this$0) {
    //      this.this$0 = this$0;
    //    }

    @Override
    public boolean hasNext() {
      // 컴파일러가 자동으로 추가한,
      // 바깥 클래스의 인스턴스 주소를 담고 있는 변수를 사용하려면 다음과 같이 지정하라!
      //    바깥클래스명.this.멤버
      // 
      return index < ArrayList.this.size();
    }

    @SuppressWarnings("unchecked")
    @Override
    public T next() {
      // 컴파일러가 추가한 바깥 클래스의 인스턴스 주소를 사용하고 싶다면 
      // ArrayList.this 라고 지정하면 된다.
      // ArrayList.get() 메서드의 리턴 타입이 E 이다.
      // E가 가리키는 타입 정보는 다음과 같이 결국 ArrayListIterator를 만들 때 넘겨진다.
      //    new ArrayListIterator<E>();
      // 따라서 ArrayListIterator 내부에서 사용하는 T가 곧 ArrayList 의 E와 같다.
      // 결론! 다음과 같이 형변환 할 수 있다.
      return (T) ArrayList.this.get(index++);
    }
  }
}

4단계 - 로컬 클래스로 Iterator 클래스 정의하기

  • local class(로컬 클래스)
    • 로컬클래스란?
      메소드 안에 선언된 클래스
    • 바깥 클래스의 인스턴스를 사용하면서 특정 메서드 안에서만 사용할 클래스라면 로컬 클래스로 정의하라!
    • 물론 논스태틱 중첩 클래스처럼 바깥 클래스의 인스턴스 주소를 다루는 필드와 생성자 파라미터가 자동으로 추가된다.
  • ArrayList class
public class ArrayList<E> {

  ...(동일 코드 생략)

  // Iterator 구현체를 제공한다.
  public Iterator<E> iterator() {

    // local class(로컬 클래스)
    class ArrayListIterator<T> implements Iterator<T> {
      int index = 0;

      @Override
      public boolean hasNext() {
        return index < ArrayList.this.size();
      }

      @SuppressWarnings("unchecked")
      @Override
      public T next() {
        return (T) ArrayList.this.get(index++);
      }
    }

    return new ArrayListIterator<E>();
  }
}

5단계 - 익명 클래스로 Iterator 클래스 정의하기

  • anonymous class(익명 클래스)
    • 인스턴스를 한 개만 생성하는 클래스를 만들 경우 사용하는 문법이다.
  • 익명 클래스 사용 문법
인터페이스명 레퍼런스 = new 인터페이스명() {
    인터페이스에 선언된 메서드 구현
 };        
  • ArrayList class
public class ArrayList<E> {

  ...(동일 코드 생략)

  // Iterator 구현체를 제공한다.
  public Iterator<E> iterator() {

    // anonymous class(익명 클래스)
    Iterator<E> iterator = new Iterator<>() {
      int index = 0;

      @Override
      public boolean hasNext() {
        return index < ArrayList.this.size();
      }

      @Override
      public E next() {
        return ArrayList.this.get(index++);
      }
    };

    return iterator;
  }
}

변수 생성 없이 바로 리턴할 수도 있다.

public class ArrayList<E> {

  ...(동일 코드 생략)

  // Iterator 구현체를 제공한다.
  public Iterator<E> iterator() {

    // anonymous class(익명 클래스)
   return new Iterator<>() {
      int index = 0;

      @Override
      public boolean hasNext() {
        return index < ArrayList.this.size();
      }

      @Override
      public E next() {
        return ArrayList.this.get(index++);
      }
    };
  }
}
profile
https://github.com/Dingadung

0개의 댓글