Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[비트캠프] 57일차(12주차2일) - Java(중첩 클래스: static, non-static, local, 익명), myapp-23, backend-app 익명 클래스 적용 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[비트캠프] 57일차(12주차2일) - Java(중첩 클래스: static, non-static, local, 익명), myapp-23, backend-app 익명 클래스 적용

끈기JK 2023. 1. 26. 12:06

 

예제 코드

 

com/eomcs/oop/ex09/c

 

 

 

인터페이스 다중 상속

 

인터페이스 다중 상속이 가능한 경우는 같은 이름의 메서드의 리턴 타입이 같을때이다.

불가능한 경우는 같은 이름의 메서드인데 리턴 타입이 다를때이다. 리턴 타입이 다른 같은 시그너처를 갖는 메서드가 여러개 존재할 수 없다.

리턴 타입이 다른 메서드가 있기 때문에 다중상속 불가! → 어떤 메서드를 상속받느냐에 따라 구현시 달라질 수 있기 때문

 

 

인터페이스 다중 상속과 메서드 중복
package com.eomcs.oop.ex09.c;


public class Exam0130 {

  interface ProtocolA {
    void rule0();
    void rule1();
  }

  interface ProtocolB {
    void rule0();
    void rule2();
  }

  // ProtoclA와 ProtocolB에 같은 이름의 메서드가 있더라도
  // 메서드 시그너처(이름, 파라미터, 리턴타입)가 같다면
  // 다중 상속이 가능하다.
  // - 클래스와 달리 메서드를 구현하기 전이라서 충돌날 일이 없다.
  //
  interface ProtocolC extends ProtocolA, ProtocolB {
    void rule3();
  }

  // 인터페이스를 구현할 때는
  // 수퍼 인터페이스의 메서드까지 모두 구현해야 한다.
  class ProtocolImpl implements ProtocolC {

    // ProtocolA, ProtocolB 규칙 동시 준수!
    @Override
    public void rule0() {System.out.println("rule1()");}

    // ProtocolA 규칙 준수!
    @Override
    public void rule1() {System.out.println("rule1()");}

    // ProtocolB 규칙 준수!
    @Override
    public void rule2() {System.out.println("rule2()");}

    // ProtocolC 규칙 준수!
    @Override
    public void rule3() {System.out.println("rule3()");}
  }

  void test() {

    ProtocolImpl obj = new ProtocolImpl();

    // 1) 인터페이스 레퍼런스로 구현체의 주소 받기
    ProtocolC c = obj;
    ProtocolB b = obj;
    ProtocolA a = obj;

    // 2) 메서드 호출
    // - 해당 인터페이스의 규칙에 따라서만 호출할 수 있다.

    c.rule0(); // OK
    c.rule1(); // OK
    c.rule2(); // OK
    c.rule3(); // OK
    System.out.println("-------------------------------");


    b.rule0(); // OK
    //    b.rule1(); // 컴파일 오류!
    b.rule2(); // OK
    //    b.rule3(); // 컴파일 오류!
    System.out.println("-------------------------------");

    a.rule0(); // OK
    a.rule1(); // OK
    //    a.rule2(); // 컴파일 오류!
    //    a.rule3(); // 컴파일 오류!
    System.out.println("-------------------------------");
  }

  public static void main(String[] args) {
    new Exam0130().test();
  }
}

 

 

인터페이스 다중 상속 불가!
package com.eomcs.oop.ex09.c;

public class Exam0140 {

  interface ProtocolA {
    void rule0();
    void rule1();
  }

  interface ProtocolB {
    int rule0();
    void rule2();
  }

  // ProtocolA와 ProtocolB에 메서드 시그너처에서 이름, 파라미터는 같지만
  // 리턴 타입이 다르다면 다중 상속이 불가능하다.
  // - 어느 수퍼 인터페이스의 메서드를 상속 받느냐에 따라
  //   동작이 달라지기 때문이다.
  //
  //  interface ProtocolC extends ProtocolA, ProtocolB { // 컴파일 오류!
  //    void rule3();
  //  }

  public static void main(String[] args) {
  }
}

 

 

인터페이스 다중 구현
package com.eomcs.oop.ex09.c;


public class Exam0210 {

  interface ProtocolA {
    void rule1();
  }

  interface ProtocolB {
    void rule2();
  }

  // 인터페이스는 단지 규칙이기 때문에
  // (즉 아직 구현하지 않은 메서드이기 떄문에)
  // 다중 구현이 가능하다.
  class ProtocolImpl implements ProtocolA, ProtocolB {
    // ProtocolA 규칙 준수!
    @Override
    public void rule1() {System.out.println("rule1()");}

    // ProtocolB 규칙 준수!
    @Override
    public void rule2() {System.out.println("rule2()");}
  }

  void test() {

    ProtocolImpl obj = new ProtocolImpl();

    // 1) 인터페이스 레퍼런스로 구현체의 주소 받기
    ProtocolB b = obj;
    ProtocolA a = obj;

    // 2) 메서드 호출
    // - 해당 인터페이스의 규칙에 따라서만 호출할 수 있다.
    b.rule2(); // OK
    //    b.rule1(); // 컴파일 오류!
    System.out.println("-------------------------------");

    a.rule1(); // OK!
    //    a.rule2(); // 컴파일 오류!
  }

  public static void main(String[] args) {
    new Exam0210().test();
  }
}

 

 

인터페이스 다중 구현과 메서드 중복
package com.eomcs.oop.ex09.c;

public class Exam0220 {

  interface ProtocolA {
    void rule0();
    void rule1();
  }

  interface ProtocolB {
    void rule0();
    void rule2();
  }

  // 다중 인터페이스를 구현할 때 중복된 메서드가 있다면,
  // - 메서드 시그너처(이름, 파라미터, 리턴타입)만 같으면
  //   다중 구현에 문제가 없다.
  // - 왜냐하면 구현 메서드가 인터페이스를 모두 만족시키기 때문이다.
  //
  class ProtocolImpl implements ProtocolA, ProtocolB {
    // ProtocolA 입장에서는 rule0() 규칙 준수!
    // ProtocolB 입장에서도 rule0() 규칙 준수!
    @Override
    public void rule0() {System.out.println("rule1()");}

    // ProtocolA 규칙 준수!
    @Override
    public void rule1() {System.out.println("rule1()");}

    // ProtocolB 규칙 준수!
    @Override
    public void rule2() {System.out.println("rule2()");}
  }

  void test() {

    ProtocolImpl obj = new ProtocolImpl();

    // 1) 인터페이스 레퍼런스로 구현체의 주소 받기
    ProtocolB b = obj;
    ProtocolA a = obj;

    // 2) 메서드 호출
    // - 해당 인터페이스의 규칙에 따라서만 호출할 수 있다.
    b.rule2(); // OK
    //    b.rule1(); // 컴파일 오류!
    System.out.println("-------------------------------");

    a.rule1(); // OK!
    //    a.rule2(); // 컴파일 오류!
  }

  public static void main(String[] args) {
    new Exam0220().test();
  }
}

 

 

인터페이스 다중 구현이 불가한 경우!
package com.eomcs.oop.ex09.c;

public class Exam0230 {

  interface ProtocolA {
    void rule0();
    void rule1();
  }

  interface ProtocolB {
    int rule0();
    void rule2();
  }

  // 다중 인터페이스를 구현이 불가한 경우,
  // - 메서드 이름만 같고
  //   메서드 시그너처의 다른 부분(파라미터, 리턴타입)이 다른 경우.
  // - 두 인터페이스를 모두 만족시키지 못하기 때문이다.
  //
  //  class ProtocolImpl implements ProtocolA, ProtocolB {
  //    // ProtocolA 입장에서는 rule0() 규칙 준수!
  //    // ProtocolB 입장에서는 rule0() 규칙을 준수하지 못했다.
  //    // - 리턴 타입이 다르다.
  //    @Override
  //    public void rule0() {}
  //
  //    // ProtocolB 입장에서는 rule0() 규칙 준수!
  //    // ProtocolA 입장에서는 rule0() 규칙을 준수하지 못했다.
  //    // - 리턴 타입이 다르다.
  //    @Override
  //    public int rule0() {return 0;}
  //
  //    // 두 메서드를 모두 정의하면 되지 않을까?
  //    // - 메서드 오버로딩 문법 상 리턴 타입만 다른 메서드를
  //    //   동시에 정의할 수 없다.
  //
  //    // ProtocolA 규칙 준수!
  //    @Override
  //    public void rule1() {}
  //
  //    // ProtocolB 규칙 준수!
  //    @Override
  //    public void rule2() {}
  //  }

  public static void main(String[] args) {
  }
}

 

 

메서드의 시그너처가 다르다면 당연히 다중 구현 가능
package com.eomcs.oop.ex09.c;

public class Exam0231 {

  interface ProtocolA {
    void rule0(int a);
    void rule1();
  }

  interface ProtocolB {
    int rule0();
    void rule2();
  }

  class ProtocolImpl implements ProtocolA, ProtocolB {
    // ProtocolA 입장에서는 rule0() 규칙 준수!
    // ProtocolB 입장에서는 rule0() 규칙을 준수하지 못했다.
    // - 리턴 타입이 다르다.
    @Override
    public void rule0(int a) {}

    // ProtocolB 입장에서는 rule0() 규칙 준수!
    // ProtocolA 입장에서는 rule0() 규칙을 준수하지 못했다.
    // - 리턴 타입이 다르다.
    @Override
    public int rule0() {return 0;}

    // 두 메서드를 모두 정의하면 되지 않을까?
    // - 메서드 오버로딩 문법 상 리턴 타입만 다른 메서드를
    //   동시에 정의할 수 없다.

    // ProtocolA 규칙 준수!
    @Override
    public void rule1() {}

    // ProtocolB 규칙 준수!
    @Override
    public void rule2() {}
  }

  public static void main(String[] args) {
  }
}

 

 

디폴트 메서드의 다중 구현
package com.eomcs.oop.ex09.c;

public class Exam0310 {

  interface ProtocolA {
    void rule1();
    default void rule3() {
      System.out.println("**ProtocolA.rule3()**");
    }
  }

  interface ProtocolB {
    void rule2();
    default void rule3() {
      System.out.println("====> ProtocolB.rule3()");
    }
  }

  static class ProtocolImpl implements ProtocolA, ProtocolB {
    // ProtocolA 규칙 준수!
    @Override
    public void rule1() {
      System.out.println("ProtocolImpl.rule1()");
    }

    // ProtocolB 규칙 준수!
    @Override
    public void rule2() {
      System.out.println("ProtocolImpl.rule2()");
    }

    // 인터페이스들에 같은 시그너처를 갖는 default 메서드가 여러 개 있을 경우,
    // 어떤 메서드를 상속 받느냐에 따라 실행 결과가 달라지기 때문에 
    // 다중 클래스 상속이 불가능 한 것처럼 
    // 이 경우에도 다중 인터페이스 구현이 불가능 하다.
    // 
    // 그러나, 다음과 같이 클래스에서 default 메서드를 오버라이딩을 한다면,
    // 어차피 인터페이스에서 구현한 default 메서드를 사용하지 않기 때문에 
    // 이 경우에는 다중 구현이 가능한다.
    // 
    //    @Override
    //    public void rule3() {
    //      System.out.println("ProtocolImpl.rule3()");
    //    }
  }

  public static void main(String[] args) {
    ProtocolImpl obj = new ProtocolImpl();
    obj.rule1();
    obj.rule2();
    obj.rule3();
  }
}

 

 

 

인터페이스와 추상클래스

 

  • 그림 왼쪽 : 인터페이스 구현 → 인터페이스의 모든 메서드를 정의
  • 그림 중간 : 인터페이스를 구현하는 Abstract 클래스를 생성. 메서드 구현 = 빈 body를 갖춘다 = 껍데기만 씌운다. 이를 상속하는 ProtocolImpl 에서 인터페이스를 직접 구현하는 대신 추상클래스를 상속 받아서 간접적으로 구현 → 필요한 메서드만 오버라이딩 한다. → 구현이 간결하다.
  • 그림 오른쪽 : CarSpec 인터페이스를 Abstract 클래스에서 일부 구현한다. 이를 상속받는 곳에서 필수 구현, 선택 구현 한다.

 

 

인터페이스와 추상 클래스 : 인터페이스 직접 구현
package com.eomcs.oop.ex09.d;

public class Exam0110 {

  interface ProtocolA {
    void rule1();
    void rule2();
    void rule3();
    void rule4();
  }

  // 인터페이스를 준수한다는 것은
  // 인터페이스의 모든 규칙을 구현해야 함을 의미한다.
  class ProtocolAImpl implements ProtocolA {
    @Override
    public void rule1() {}

    @Override
    public void rule2() {}

    @Override
    public void rule3() {}

    @Override
    public void rule4() {}
  }
}

 

 

인터페이스와 추상 클래스 : 추상 클래스의 도움 받기
package com.eomcs.oop.ex09.d;

public class Exam0120 {

  interface ProtocolA {
    void rule1();
    void rule2();
    void rule3();
    void rule4();
  }

  // 추상클래스에서 인터페이스의 규칙을 모두 미리 구현해 둔다.
  // 물론 최소 상태로 구현한다.
  abstract class AbstractProtocolA implements ProtocolA {
    @Override
    public void rule1() {}

    @Override
    public void rule2() {}

    @Override
    public void rule3() {}

    @Override
    public void rule4() {}
  }

  // 인터페이스를 준수하는 클래스를 만들어야 할 경우,
  // 직접 인터페이스를 구현하는 대신에
  // 다음과 같이 추상 클래스를 상속 받는다.
  // - 수퍼 클래스에서 인터페이스를 구현했다면,
  //   그 서브 클래스들도 인터페이스를 구현한 것이 된다.
  class ProtocolAImpl extends AbstractProtocolA {
    // 이 방식의 장점은 오버라이딩을 통해
    // 인터페이스 규칙 중 필요한 규칙만 구현할 수 있다.
    @Override
    public void rule2() {
      System.out.println("ProtocolAImpl.rule2()");
    }
  }

  void test() {
    ProtocolAImpl obj = new ProtocolAImpl();

    // 수퍼 클래스가 인터페이스를 구현했다면,
    // 그 서브 클래스는 자동으로 인터페이스를 구현한 것이 된다.
    // 증명!
    //
    ProtocolA a = obj;

    a.rule2();
  }

  public static void main(String[] args) {
    new Exam0120().test();
  }
}

 

 

인터페이스와 추상 클래스 : 추상 클래스의 도움 받기
  // CarSpec에 선언된 on(), off() 메서드는 수퍼 클래스에서 미리 구현했기 때문에
  // 서브 클래스에서 다시 구현할 필요가 없어 편하다!
  //
  // 서브 클래스는 수퍼 클래스가 구현하지 않은 나머지 메서드만 구현하면 된다.
  @Override
  public void run() {
    System.out.println("씽씽~~ 달린다!");
  }
}

class Truck extends AbstractCar {
  @Override
  public void run() {
    System.out.println("덜컹 덜컹 달린다!");
  }
}


// 5) 인터페이스 규칙에 따라 구현체 사용하기
public class Exam0130 {

  void test() {
    play(new Tico());
    System.out.println("----------------------");

    play(new Sonata());
    System.out.println("----------------------");

    play(new Truck());
    System.out.println("----------------------");
  }

  void play(CarSpec car) {
    car.on();
    car.run();
    car.off();
  }

  public static void main(String[] args) {
    new Exam0130().test();
  }
}

 

 

 

인터페이스와 default 메서드

 

《interface》Computer 에 touch() 규칙 추가하기 위한 2가지 방법이 있다.

  • 《interface》Computer 를 《interface》Computer2 가 상속하고 새 규칙을 추가한다. 이를 《Concrete》NewComputer1 이 구현한다. 이를 테스트 하는 코드가 있을때 기존 인터페이스에는 touch() 가 없어 테스트를 실행할 수 없다. 규칙 추가 → 컴파일 오류 발생
  • 《interface》Computer 에 default touch() {-} 를 정의한다. 이를 《Concrete》NewComputer2 가 구현한다. default 메서드로 규칙을 추가하면 기존의 클래스들에서 컴파일 오류가 발생하지 않는다.

 

package com.eomcs.oop.ex09.e.project1;

import com.eomcs.oop.ex09.e.Computer;

public class FirstComputer implements Computer {
  @Override
  public void compute() {
    System.out.println("단순히 계산을 수행한다!");
  }
}
package com.eomcs.oop.ex09.e.project2;

import com.eomcs.oop.ex09.e.Computer;

public class SecondComputer implements Computer {
  public void compute() {
    System.out.println("멀티태스킹 기능도 수행한다!");
  }
}
package com.eomcs.oop.ex09.e.project3;

import com.eomcs.oop.ex09.e.Computer;

public class ThirdComputer implements Computer {
  public void compute() {
    System.out.println("게이밍 컴퓨터!!!");
  }
}

 

package com.eomcs.oop.ex09.e;

public interface Computer {
  // 1) 1세대 컴퓨터
  // - 초창기 컴퓨터는 계산하는 기능이 중요했다.
  // - FirstComputer, SecondComputer, ThirdComputer
  void compute();

  // 2) 2세대 컴퓨터
  // 어느덧 세월이 흘러, 터치 스크린이 등장하면서
  // 컴퓨터에서 터치가 가능한 단계로 업그레이드 하게 되었다.
  // 그래서 컴퓨터라면 이제 터치도 가능해야 한다고 결론을 내렸고,
  // 규칙을 추가하게 된다.
  //
  //  void touch(); // 추가하는 순간 기존 클래스들에서 컴파일 오류 발생!

  // 문제점
  // => 규칙을 변경하면, 그 규칙에 따라 만든 모든 클래스를 변경해야 한다.
  // => 내가 만든 규칙에 따라 내가 클래스를 만들었다면 내가 변경하면 되지만,
  //    내가 만든 규칙을 다른 많은 개발자들이 가져가서 클래스를 만들어 사용하고 있다면,
  //    내가 쉽게 규칙을 변경할 상황이 아닌 것이다.
  //    규칙 즉 인터페이스의 메서드를 변경하거나 제거, 추가하는 순간
  //    이 인터페이스를 구현한 모든 클래스들에서 컴파일 오류가 발생하게 된다.
  //
  // 하지만 그렇다고 해서 계속 옛날 규칙을 가져갈 수 없고,
  // 새 프로젝트에는 변경된 규칙으로 클래스를 만들고 싶다.
  // 새 규칙을 새 인터페이스(예: Computer2)로 정의할 순 있지만,
  // 그렇게 하면 기존에 진행한 모든 프로젝트들과 호환되지 않는 문제가 발생한다.
  //
  // 기존 규칙을 변경하되, 
  // 기존 구현체(기존 규칙에 따라 작성한 클래스)에는 영향을 끼치고 싶지 않을 때
  // 바로 다음 문법을 사용하라!
  // "디폴트 메서드(default method)" - Java8에서 추가한 문법이다.
  //
  default void touch() {
    // 구현할 코드 있으면 작성하고, 없으면 빈 채로 둔다.
  }

  // 위에 touch()라는 새 규칙을 추가하더라도
  // 기존에 작성한 FirstComputer, SecondComputer, ThirdComputer 클래스 모두
  // 컴파일 오류가 발생하지 않는다.
  // 왜?
  // 구현된 메서드이기 때문이다.
  //
  // 가능한 일반 클래스의 메서드처럼 사용하지 말아라!
  // 새 규칙을 추가하는 의미로 default 메서드를 사용해야지
  // 일반 클래스의 메서드처럼 상속해 줄 목적으로 default 메서드를 만들어서는 안된다.
  //
  // default 메서드의 단점:
  // => 인터페이스에 선언하는 메서드는 기본이 추상 메서드이기 때문에
  //    그 인터페이스를 구현하는 클래스는 반드시 해당 메서드를 정의해야 한다.
  //    메서드 정의를 강제하는 것이다.
  // => 그러나 default 메서드는 이미 구현되어 있기 때문에
  //    클래스에서 반드시 정의할 필요는 없다.
  //    즉 강제로 정의하게 만들 수 없는 것이 문제이다.
  // => 그래서 default 메서드의 용도는
  //    기존에 정의된 인터페이스에 새 규칙을 추가할 때
  //    기존 프로젝트에 영향을 끼치지 않기 위함이다.
  //    따라서 새 인터페이스를 정의할 때는 default의 사용을 자제해야 한다.
}

 

 

 

 

myapp

 

 

23. 중첩 클래스 활용

 

bitcamp.util 패키지 내 LinkedList 가 Node, ListIterator, Iterator 를 사용한다. ArrayList 가 ListIterator, Iterator 를 사용한다.

→ Refactoring(코드개선) 한다.

LinkedList 내에 Node 를 두는 스태틱 중첩 클래스를 생성한다.

《interface》List 를 일부 구현한 AbstractList 를 생성하고 LinkedList, ArrayList 에서 상속한다. ListIterator 는 LinkedList, ArrayList 에서만 사용하므로 AbstractList 에 포함시킨다.

 

 

package bitcamp.util;

public class LinkedList extends AbstractList {

  private Node head;
  private Node tail;

  @Override
  public void add(Object value) {
    Node node = new Node(value);
    if (this.tail == null) { // size == 0, head == null
      this.head = this.tail = node;

    } else {
      this.tail.next = node;
      this.tail = node;
    }

    this.size++;
  }

  @Override
  public Object[] toArray() {
    Object[] values = new Object[this.size];
    int index = 0;
    Node cursor = this.head;

    while (cursor != null) {
      values[index++] = cursor.value;
      cursor = cursor.next;
    }
    return values;
  }

  @Override
  public Object set(int index, Object value) {
    if (index < 0 || index >= this.size) {
      throw new IndexOutOfBoundsException("인덱스가 유효하지 않습니다.");
    }

    Node cursor = head;
    int i = 0;

    while (cursor != null) {
      if (i == index) {
        Object old = cursor.value;
        cursor.value = value;
        return old;
      }
      cursor = cursor.next;
      i++;
    }

    return null;
  }

  @Override
  public boolean remove(Object value) {
    Node prevNode = null;
    Node deletedNode = null;
    Node cursor = this.head;

    while (cursor != null) {
      if (cursor.value.equals(value)) {
        deletedNode = cursor;
        break;
      }
      prevNode = cursor;
      cursor = cursor.next;
    }

    if (deletedNode == null) {
      return false;
    }

    if (prevNode == null) {
      this.head = this.head.next;
      deletedNode.next = null;
      if (this.head == null) {
        this.tail = null;
      }

    } else {
      prevNode.next = deletedNode.next;
      deletedNode.next = null;
      if (prevNode.next == null) {
        this.tail = prevNode;
      }
    }
    this.size--;
    return true;
  }

  @Override
  public int indexOf(Object b) {
    Node cursor = head;
    int i = 0;

    while (cursor != null) {
      if (cursor.value.equals(b)) {
        return i;
      }
      cursor = cursor.next;
      i++;
    }
    return -1;
  }

  @Override
  public Object get(int index) {
    super.get(index);

    Node cursor = head;
    int i = 0;

    while (i < index) {
      cursor = cursor.next;
      i++;
    }
    return cursor.value;
  }

  // - LinkedList 클래스에서만 사용하는 클래스라면
  //   LinkedList 클래스 안에 두는 것이 유지보수에 더 낫다.
  // - 패키지 외부에 노출되지 않기 때문에 다른 개발자가 헷갈릴 이유가 없다.
  //
  // => 스태틱 중첩 클래스(static nested class)
  //
  static class Node {
    Object value;
    Node next;

    public Node() {}

    public Node(Object value) {
      this.value = value;
    }
  }
}

 

package bitcamp.util;

import java.util.Arrays;

public class ArrayList extends AbstractList {
    
  private static final int SIZE = 3;
  protected Object[] objects = new Object[SIZE];

  @Override
  public void add(Object object) {
    if (size == objects.length) {
      objects = Arrays.copyOf(objects, objects.length + (objects.length >> 1));
    }
    this.objects[this.size++] = object;
  }

  @Override
  public Object[] toArray() {
    return Arrays.copyOf(objects, size);
  }

  @Override
  public Object get(int index) {
    super.get(index);
    return this.objects[index];
  }

  @Override
  public Object set(int index, Object object) {
    Object old  = this.objects[index];
    this.objects[index] = object;
    return old;
  }

  @Override
  public boolean remove(Object object) {
    int index = indexOf(object);
    if (index == -1) {
      return false;
    }

    for (int i = index + 1; i < this.size; i++) {
      this.objects[i - 1] = this.objects[i];
    }
    this.objects[--this.size] = null;
    return true;
  }

  @Override
  public int indexOf(Object object) {
    for (int i = 0; i < this.size; i++) {
      if (objects[i].equals(object)) {
        return i;
      }
    }
    return -1;
  }

}

 

 

 

nested class : static / non-static

 

LinkedList 의 메서드들에서 인스턴스 필드인 head, tail 을 사용한다. Node 를 사용한다.

↑ 인스턴스 필드 사용 = 인스턴스 사용(this) = 인스턴스 메서드가 되어야 한다.

 

Node 는 LinkedList 를 사용하지 않는다. 인스턴스(필드, 메서드) 사용 안하므로 static 이 되어야 한다.

Node 를 다른 클래스가 사용하지 않는다 = nested class

 

 

package bitcamp.util;

public abstract class AbstractList_1 implements List {

  protected int size;

  @Override
  public Object get(int index) {
    if (index < 0 || index >= this.size) {
      throw new IndexOutOfBoundsException("인덱스가 무효합니다.");
    }
    return null;
  }

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

  @Override
  public Iterator iterator() {
    return new ListIterator(this);
  }

  // AbstractList 클래스에서만 사용하는 클래스라면
  // 이 클래스 안에 두는 것이 유지보수에 좋다.
  //
  // => 스태틱 중첩 클래스(static nested class)
  //
  static class ListIterator implements Iterator {

    List list;
    int cursor;

    public ListIterator(List list) {
      this.list = list;
    }

    @Override
    public boolean hasNext() {
      return cursor >= 0 && cursor < list.size();
    }

    @Override
    public Object next() {
      return list.get(cursor++);
    }
  }

}

 

 

 

nested class : local class

 

《interface》List 를 《abstract》AbstractList 가 구현한다. AbstractList 와 other class 가 《interface》Iterator 를 사용한다. 다른 클래스가 사용한다 = 패키지 멤버

《concrete》ListIterator 는 다른 클래스가 사용 안함. AbstractList 만 사용 = nested class. iterator() {-} 메서드에서만 사용 = local class

ListIterator 는 AbstractList 사용한다. 바깥 클래스의 인스턴스 사용 = 인스턴스 중첩 클래스

 

 

package bitcamp.util;

public abstract class AbstractList_2 implements List {

  protected int size;

  @Override
  public Object get(int index) {
    if (index < 0 || index >= this.size) {
      throw new IndexOutOfBoundsException("인덱스가 무효합니다.");
    }
    return null;
  }

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

  @Override
  public Iterator iterator() {

    // iterator() 메서드 안에서만 사용하는 클래스라면
    // 이 메서드 안에 두는 것이 유지보수에 좋다.
    //
    // => local class
    //
    class ListIterator implements Iterator {

      int cursor;

      // 바깥 클래스의 인스턴스를 사용하기 위해 생성자에서 그 주소를 받을 필요가 없다.
      // 왜?
      // - 컴파일러가 바깥 클래스의 객체를 주소를 보관할 필드를 자동으로 생성하고
      // - 바깥 클래스의 객체 주소를 받을 수 있게 기존 생성자를 자동으로 변경한다.
      // - 생성자가 없다면 바깥 클래스의 객체 주소를 받는 생성자를 자동으로 추가한다.
      // - 따라서 다음과 같이 개발자가 직접 필드와 생성자를 추가할 필요가 없다.
      // - 와우~!!! 편리해라!
      //   대신 바깥 클래스의 인스턴스를 사용하려면 다음과 같이 객체를 지정해야 한다.
      //
      //       바깥 클래스명.this.인스턴스멤버
      //
      //      List list;
      //      public ListIterator(List list) {
      //        this.list = list;
      //      }

      @Override
      public boolean hasNext() {
        return cursor >= 0 && cursor < AbstractList_2.this.size();
      }

      @Override
      public Object next() {
        return AbstractList_2.this.get(cursor++);
      }
    }

    // 로컬 클래스의 생성자를 호출할 때
    // 바깥 클래스의 인스턴스 주소를 넘길 필요가 없다.
    // 컴파일러가 대신 처리한다.
    // 바깥 클래스의 인스턴스 주소를 넘길 수 있도록 생성자 호출을 변경한다.
    //
    //    return new ListIterator(this);
    //
    return new ListIterator();
  }

}

 

 

 

nested class : anonymous class

 

Iterator obj = new Iterator() { - };  에서 new 는 인스턴스 생성, Iterator 는 인터페이스, () 는 수퍼클래스(Object) 생성자 호출 이다. 이로 생성된 인스턴스 주소가 obj 에 저장된다. 중괄호 내부는 class body 이다.

 

 

package bitcamp.util;

public abstract class AbstractList_3 implements List {

  protected int size;

  @Override
  public Object get(int index) {
    if (index < 0 || index >= this.size) {
      throw new IndexOutOfBoundsException("인덱스가 무효합니다.");
    }
    return null;
  }

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

  @Override
  public Iterator iterator() {

    // - iterator() 메서드 안에서만 사용하는 클래스라면
    //   이 메서드 안에 두는 것이 유지보수에 좋다.
    // - 인스턴스를 한 개만 만들어 사용하고 클래스의 크기도 작다면,
    //   익명 클래스로 만드는 것이 코드를 간결하게 만든다.
    //
    // => anonymous class = 클래스 정의 + 객체 생성 코드
    //
    Iterator obj = new Iterator() {
      int cursor;
      @Override
      public boolean hasNext() {
        return cursor >= 0 && cursor < AbstractList_3.this.size();
      }
      @Override
      public Object next() {
        return AbstractList_3.this.get(cursor++);
      }
    };

    return obj;
  }

}

 

 

 

 

backend-app

 

 

App.java
package bitcamp.bootapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class App {

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }

  // 스프링부트가 클래스를 자동 생성하지 않는다면,
  // 개발자가 직접 객체를 생성해서 리턴해야 한다.
  // 단, 스프링 부트가 아래 메서드를 호출하게 하려면 표시를 해야 한다.
  @Bean
  public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
      @Override
      public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
        .allowedOrigins("http://localhost:5500", "http://127.0.0.1:5500")
        .allowedMethods("*");
      }
    };
  }
}

 

 

 

 

예제 소스

 

com/eomcs/oop/ex11/a~c

 

 

top level class : public
// top level class : public
package com.eomcs.oop.ex11.a.sub;

// top level class 의 접근 제한자
// - public : 다른 패키지에서도 접근 가능
// - (package-private) : 다른 패키지 접근 불가!
public class A {

}

 

top level class : (package-private)
package com.eomcs.oop.ex11.a.sub;

//top level class 의 접근 제한자
// - public : 다른 패키지에서도 접근 가능
// - (package-private; modifier 가 없다) : 다른 패키지 접근 불가!
class B {

}

 

top level class : 접근 범위
package com.eomcs.oop.ex11.a.sub;

// top level class
// - 패키지에 소속된 클래스다.
//
public class Exam0110 {
  public static void main(String[] args) {
    // public 으로 공개된 클래스는 다른 패키지에서 접근 할 수 있다.
    com.eomcs.oop.ex11.a.sub.A obj1 = new com.eomcs.oop.ex11.a.sub.A();

    // public 으로 공개되지 않은 클래스는 같은 패키지에서만 접근할 수 있다.
    com.eomcs.oop.ex11.a.sub.B obj2 = new com.eomcs.oop.ex11.a.sub.B(); // OK!

    // 같은 패키지의 클래스를 사용할 때는 패키지명을 생략할 수 있다.
    // import 도 할 필요가 없다.
    A obj3 = new A();
    B obj4 = new B();

  }
}

 

 

top level class : 접근 범위
package com.eomcs.oop.ex11.a;

// top level class
// - 패키지에 소속된 클래스다.
//
public class Exam0110 {
  public static void main(String[] args) {
    // public 으로 공개된 클래스는 다른 패키지에서 접근 할 수 있다.
    com.eomcs.oop.ex11.a.sub.A obj1 = new com.eomcs.oop.ex11.a.sub.A();

    // public 으로 공개되지 않은 클래스는 다른 패키지에서 접근할 수 없다.
    //    com.eomcs.oop.ex11.a.sub.B obj2 = new com.eomcs.oop.ex11.a.sub.B(); // 컴파일 오류!
  }
}

 

 

nested class : 종류
package com.eomcs.oop.ex11.a;

public class Exam0210 {
  // 중첩 클래스
  // => 특정 클래스 안에서만 사용되는 클래스가 있다면 중첩 클래스로 선언하라.
  // => 즉 노출 범위를 좁히는 것이 유지보수에 좋다.
  //
  // 1) static nested class
  // => 바깥 클래스의 인스턴스에 종속되지 않는 클래스.
  // => top level class 와 동일하게 사용한다.
  static class A {}

  // => 다른 스태틱 멤버
  static int a; // 스태틱 필드 = 클래스 필드
  static void m1() {} // 스태틱 메서드 = 클래스 메서드
  static {} // 스태틱 블록

  // 2) non-static nested class = inner class
  // => 바깥 클래스의 인스턴스에 종속되는 클래스.
  //    중첩 클래스에서 바깥 클래스의 인스턴스 멤버를 사용한다는 뜻이다.
  //    바깥 클래스의 인스턴스 없이 작업할 수 없는 경우
  //    중첩 클래스를 non-static nested class 로 정의한다.
  // => 바깥 클래스의 인스턴스 없이 생성할 수 없다.
  class B {}

  // => 다른 인스턴스 멤버
  int b; // 논스태틱 필드 = 인스턴스 필드
  void m2() {}; // 논스태틱 메서드 = 인스턴스 메서드
  {} // 인스턴스 블록

  public static void main(String[] args) {

    // 3) local class
    // => 특정 메서드 안에서만 사용되는 클래스.
    class C {}
    
    // 4) anonymous class
    // => 클래스의 이름이 없다.
    //    이름이 없으니, new 명령으로 따로 인스턴스를 생성할 수 없다.
    // => 문법 = 클래스를 정의 하는 문법 + 인스턴스를 만드는 문법
    //    즉, 클래스를 정의하는 동시에 인스턴스를 생성해야 한다.
    // => 클래스 이름이 없기 때문에 익명 클래스는 생성자를 정의할 수 없다.
    //    만약 인스턴스의 값을 초기화시키기 위해 복잡한 코드를 작성해야 한다면,
    //    "인스턴스 블록"을 사용하여 인스턴스 초기화 코드를 작성하라!
    // => 단 한 개의 인스턴스만 생성해서 사용할 경우 익명 클래스를 적용한다.
    // => 문법
    //      new 수퍼클래스() {수퍼 클래스를 상속 받은 익명 클래스 정의}
    //      new 인터페이스() {인터페이스를 구현한 익명 클래스 정의}
    // => 주의!
    //      new extends 수퍼클래스 implements 인터페이스 {익명 클래스 정의} <== 이런 문법은 없다.
    //      new 수퍼클래스 implements 인터페이스() {인터페이스를 구현한 익명 클래스 정의}
    //      new 인터페이스1, 인터페이스2, 인터페이스3() {인터페이스를 구현한 익명 클래스 정의}
    //      수퍼 클래스를 지정하거나 인터페이스를 지정하거나 둘 중 하나만 해야 한다.
    
    Object obj = new Object() {
      // Object 클래스를 상속 받은 익명 클래스를 만들고,
      // m1() 메서드를 추가한다.
      public void m1() {
        System.out.println("Hello!");
      }
    };
    
  }
}

 

 

nested class : 접근 제어
package com.eomcs.oop.ex11.a;

public class Exam0310 {
  //중첩 클래스도 클래스의 멤버이기 때문에 필드나 메서드처럼 접근 제한자를 붙일 수 있다.
  private static class A1 {} 
  static class A2 {}
  protected static class A3 {}
  public static class A4 {}

  private class B1 {} 
  class B2 {}
  protected class B3 {}
  public class B4 {}
}

 

 

nested class : 로컬 클래스의 접근 제어
package com.eomcs.oop.ex11.a;

public class Exam0311 {

  static void m1() {
    // 로컬 변수처럼 로컬 클래스에는 접근 제어 modifier 를 붙일 수 없다.
    //    private class A1 {} // 컴파일 오류!
    //    protected class A2 {} // 컴파일 오류!
    //    public class A3 {} // 컴파일 오류!

    class A4 {} // OK!
  }

  void m2() {
    // 로컬 변수처럼 로컬 클래스에는 접근 제어 modifier 를 붙일 수 없다.
    //    private class B1 {} // 컴파일 오류!
    //    protected class B2 {} // 컴파일 오류!
    //    public class B3 {} // 컴파일 오류!

    class B4 {} // OK!
  }
}

 

 

static nested class : 클래스 정의와 인스턴스 생성
package com.eomcs.oop.ex11.b;

class A {
  static class X {

  }
}

public class Exam0110 {

  public static void main(String[] args) {
    // 레퍼런스 선언
    A.X obj;

    // 인스턴스 생성
    obj = new A.X();
  }

}

 

 

static nested class : 선언할 수 있는 멤버
package com.eomcs.oop.ex11.b;

class A2 {

  static class X {
    // top level class 처럼 스태틱 멤버 선언 가능
    static int v1;
    static void m1() {}
    static {}

    // top level class 처럼 인스턴스 멤버 선언 가능
    int v2;
    void m2() {}
    {}
  }

}

public class Exam0111 {

  public static void main(String[] args) {
    // 레퍼런스 선언
    A2.X obj;

    // 인스턴스 생성
    obj = new A2.X();
  }

}

 

 

static nested class : 다른 멤버에 접근하기
package com.eomcs.oop.ex11.b;

class B {
  // 클래스 멤버
  static int v1;
  static void m1() {}

  // 인스턴스 멤버
  int v2;
  void m2() {}

  static void test() {
    B.v1 = 100;
    B.m1();

    // test() 메서드는 같은 멤버이기 때문에 
    // 다음과 같이 클래스 이름을 생략할 수 있다.
    v1 = 100;
    m1();

    // 스태틱 멤버는 this라는 빌트인 변수가 없기 때문에 
    // 인스턴스 멤버에 접근할 수 없다.
    //    v2 = 100; // 컴파일 오류!
    //    m2(); // 컴파일 오류!
  }
}

public class Exam0210 {
  public static void main(String[] args) {
    // 스태틱 멤버는 클래스명으로 접근 가능
    B.v1 = 100;
    B.m1();
    B.test();
  }
}

 

 

static nested class : 다른 멤버에 접근하기
package com.eomcs.oop.ex11.b;

class B2 {
  // 클래스 멤버
  static int v1;
  static void m1() {}

  // 인스턴스 멤버
  int v2;
  void m2() {}

  static class X {
    void test() {
      // 일반적으로 클래스의 스태틱 멤버(필드,메서드)에 접근할 때는
      // 다음과 같이 그 클래스의 이름을 명시해야 한다.
      B2.v1 = 100;
      B2.m1();

      // 클래스 X도 B2의 test() 메서드처럼 B2 클래스의 멤버이기 때문에
      // B2 클래스의 멤버에 접근할 때는
      // 다음과 같이 바깥 클래스 이름을 생략할 수 있다.
      v1 = 200;
      m1();

      // 스태틱 중첩 클래스도 test() 처럼 스태틱 멤버이기 때문에
      // 바깥 클래스의 인스턴스 주소를 담는 B2.this 라는 인스턴스 멤버가 없다.
      // 따라서 바깥 클래스의 인스턴스 멤버에 접근할 수 없다.
      //      v2 = 100; // 컴파일 오류!
      //      m2(); // 컴파일 오류!
    }
  }
}

public class Exam0211 {

  public static void main(String[] args) {
    B2.X obj = new B2.X();
    obj.test();
  }

}

 

 

static nested class : 다른 멤버가 중첩 클래스 사용하기
package com.eomcs.oop.ex11.b;

class C {
  static void m1() {
    // 같은 스태틱 멤버는 사용 가능!
    X obj = new X();
    obj.test();
  }

  void m2() {
    // 인스턴스 멤버는 스태틱 멤버 사용 가능!
    m1(); // OK!

    X obj = new X();
    obj.test();
  }

  static class X {
    void test() {
      System.out.println("X.test()");
    }
  }
}

public class Exam0310 {

  public static void main(String[] args) {
    C.m1();

    C outer = new C();
    outer.m2();
  }

}

 

 

static nested class : import static 사용 전
package com.eomcs.oop.ex11.b;

import com.eomcs.oop.ex11.b.sub.M;

class D {
  static int v1;

  static void m1() {}

  static class X {
    static void test() {
      System.out.println("test()");
    }
  }
}

public class Exam0410 {

  public static void main(String[] args) {
    // 같은 패키지 클래스
    D.v1 = 100;
    D.m1();
    D.X obj = new D.X();

    // 다른 패키지 클래스
    M.v2 = 200;
    M.m2();
    M.Y obj2 = new M.Y();
  }

}
package com.eomcs.oop.ex11.b.sub;

public class M {
  // 다른 패키지에서 접근 할 수 있도록 public 으로 공개
  public static int v2;

  public static void m2() {}

  public static class Y {
    public void test() {
      System.out.println("M.Y.test()");
    }
  }
}

 

 

static nested class : import static 사용 후
package com.eomcs.oop.ex11.b;

// 스태틱 멤버를 임포트하기
import static com.eomcs.oop.ex11.b.E.m1;
import static com.eomcs.oop.ex11.b.E.v1;
import static com.eomcs.oop.ex11.b.sub.M.m2;
import static com.eomcs.oop.ex11.b.sub.M.v2;
// 중첩 클래스를 import 할 때는 static을 적지 않는다.
import com.eomcs.oop.ex11.b.E.X;
import com.eomcs.oop.ex11.b.sub.M.Y;

class E {
  static int v1;

  static void m1() {}

  static class X {
    void test() {
      System.out.println("test()");
    }
  }
}

public class Exam0420 {

  public static void main(String[] args) {
    // 같은 패키지 클래스의 스태틱 멤버
    // => import static 으로 미리 스태틱 멤버의 패키지 정보를 알려주면,
    //    마치 같은 클래스의 멤버인양
    //    클래스 이름 없이 사용할 수 있다.
    v1 = 100;
    m1();
    X obj = new X();

    // 다른 패키지 클래스의 스태틱 멤버
    // => import static 으로 미리 스태틱 멤버의 패키지 정보를 알려주면,
    //    마치 같은 클래스의 멤버인양
    //    클래스 이름 없이 사용할 수 있다.
    v2 = 200;
    m2();
    Y obj2 = new Y();
  }

}

 

 

static nested class : import static 사용 후
package com.eomcs.oop.ex11.b;

// 각각의 스태틱 멤버를 지정하는 대신 
// 다음과 같이 wildcard(*)를 사용하여 전체 스태틱 멤버를 한 번에 지정할 수 있다.
import static com.eomcs.oop.ex11.b.F.*;
import static com.eomcs.oop.ex11.b.sub.M.*;
import com.eomcs.oop.ex11.b.F.X;
import com.eomcs.oop.ex11.b.sub.M.Y;

class F {
  static int v1;

  static void m1() {}

  static class X {
    void test() {
      System.out.println("test()");
    }
  }
} 

public class Exam0430 {

  public static void main(String[] args) {
    // 같은 패키지 클래스의 스태틱 멤버
    // => import static 으로 미리 스태틱 멤버의 패키지 정보를 알려주면,
    //    마치 같은 클래스의 멤버인양
    //    클래스 이름 없이 사용할 수 있다.
    v1 = 100;
    m1();
    X obj = new X();

    // 다른 패키지 클래스의 스태틱 멤버
    // => import static 으로 미리 스태틱 멤버의 패키지 정보를 알려주면,
    //    마치 같은 클래스의 멤버인양
    //    클래스 이름 없이 사용할 수 있다.
    v2 = 200;
    m2();
    Y obj2 = new Y();
  }

}

 

 

inner class : 클래스 정의와 인스턴스 생성
package com.eomcs.oop.ex11.c;

class X {} // Top Level Class

class A {
  static void m1(int a) {
    // static 메서드는 A의 인스턴스를 저장하는 this라는 변수가 없다.
  }

  void m2(int b) {
    // non-static 메서드는 A의 인스턴스를 저장할 this라는 변수가 있다.
  }

  class X { // inner class
    // 컴파일러는 inner 클래스를 컴파일 할 때 다음과 같이
    // - 바깥 클래스의 인스턴스 주소를 저장할 필드를 추가하고,
    // - 바깥 클래스의 인스턴스의 주소를 파라미터로 받는 생성자를 만든다.
    //
    //    A outer;
    //    public X(A obj) {
    //      this.outer = obj;
    //    }
  }

  static class Y {}

}

public class Exam0110 {

  public static void main(String[] args) {
    // 레퍼런스 선언
    A.X obj;
    A.Y obj2;

    // 인스턴스 생성
    obj2 = new A.Y();  // 스태틱 중첩 클래스는 바깥 클래스의 인스턴스가 없어도 생성할 수 있다.
    //    obj = new A.X(); // 컴파일 오류! 바깥 클래스의 인스턴스 주소 없이 생성 불가!

    A.m1(0);  // 스태틱 멤버를 사용할 때는 인스턴스가 없어도 된다.
    // A.m2(0);  // 인스턴스 멤버를 사용하려면 반드시 해당 객체가 있어야 한다.

    //1) 바깥 클래스의 인스턴스 준비
    A outer = new A();

    // non-static 메서드를 호출하려면 인스턴스 주소가 필요하듯이,
    outer.m2(0);

    //2) inner class의 인스턴스를 생성할 때도 바깥 클래스의 인스턴스 주소가 필요하다.
    obj = outer.new X();

    // 컴파일러는 컴파일 할 때 다음과 같이
    // - 바깥 클래스의 객체를 생성자에 전달하는 코드로 변경한다.
    //    obj = new A.X(outer);

  }

}

 

 

inner class : 선언할 수 있는 멤버
package com.eomcs.oop.ex11.c;

class A2 {
  class X {
    // inner class 는 스태틱 멤버를 가질 수 없다.
    // 스태틱 멤버는 오직
    // - top level class 나
    // - static nested class
    // 만이 가질 수 있다.
    //
    // - Java16 부터는 inner class도 스태틱 멤버를 가질 수 있다.
    //
    //    static int v1; // 컴파일 오류!
    //    static void m1() {} // 컴파일 오류!
    //    static {} // 컴파일 오류!

    int v2;
    void m2() {}
    {}
  }
}

public class Exam0111 {
  public static void main(String[] args) {

    // 바깥 클래스의 인스턴스를 먼저 만든 다음에
    // inner 클래스의 인스턴스를 만든다.
    A2 outer = new A2();
    A2.X obj = outer.new X(); // => new X(outer)

    // 물론 다음과 같이 위의 두 줄을 한 줄로 표현할 수 있다.
    A2.X obj2 = new A2().new X(); // => new X(new A2())
  }
}

 

 

inner class : 바깥 클래스의 스태틱 멤버에 접근하기
package com.eomcs.oop.ex11.c;

class B {
  // 클래스 멤버
  static int v1 = 10;
  static void m1() {}

  class X {
    void test() {
      // 바깥 클래스든 패키지 멤버 클래스든 스태틱 멤버를 사용할 때는 
      // 다음과 같이 클래스 이름으로 해당 멤버를 사용한다.
      System.out.println(B.v1);
      B.m1();
      System.out.println("-------------------------");

      // 그런데 중첩 클래스에서 바깥 클래스의 스태틱 멤버에 접근할 때는 
      // 바깥 클래스 이름을 생략할 수 있어 편하다.
      // 왜? 
      // - 중첩 클래스도 바깥 클래스의 멤버이기 때문에
      //   (즉 X 도 v1, m1() 처럼 B 클래스의 멤버이다)
      //   같은 멤버에 접근할 때는 클래스 명을 생략할 수 있다.
      System.out.println(v1); 
      m1(); 
      System.out.println("-------------------------");
    }
  }
}

public class Exam0210 {

  public static void main(String[] args) {
    B outer = new B();
    B.X obj = outer.new X();

    obj.test();
  }

}

 

 

inner class : 바깥 클래스의 인스턴스 멤버 접근하기
package com.eomcs.oop.ex11.c;

class B2 {

  // 인스턴스 멤버
  int v2;
  void m2() {
    System.out.println("B2.v2 = " + this.v2);
  }

  class X {
    // 바깥 객체의 주소를 저장할 빌트인 필드
    //    B2 this$0;

    // inner 객체를 생성할 때 바깥 객체의 주소를 받는 생성자
    //    public X(B2 p) {
    //      this.this$0 = p;
    //    }

    void test() {
      // 바깥 객체의 인스턴스 멤버에 접근하려면,
      // inner 객체에 보관된 바깥 객체 주소를 사용해야 한다.
      // 즉 컴파일러가 내부적으로 자동 생성한 바깥 객체 주소를 담는 필드를 사용해야 한다.
      // 문제는 컴파일러가 자동 생성한 필드 이름이 뭔지 모른다.
      // 그래서 자바는 inner 객체에 보관된 바깥 객체를 가리키는 문법을 제공하고 있다.
      // =>   바깥클래스명.this
      // 위의 문법을 이용하여 바깥 객체에 접근할 수 있다.
      // 즉 inner 객체를 만들 때 사용한 바깥 객체에 접근하고 싶다면 
      // =>  B2.this  문법을 사용하라!
      // 
      System.out.println(B2.this.v2); // ---> this$0.v2
      B2.this.m2();
    }
  }
}

public class Exam0220 {

  public static void main(String[] args) {
    B2 outer = new B2();
    outer.v2 = 100;
    outer.m2();


    B2 outer2 = new B2();
    outer2.v2 = 200;
    outer2.m2();

    // inner 객체 생성
    B2.X inner = outer.new X(); // --> new X(outer)
    B2.X inner2 = outer2.new X(); // --> new X(outer2)

    inner.test();
    inner2.test();

    B2 outer3 = null;
    B2.X inner3 = outer3.new X(); 
  }

}

 

 

inner class : 바깥 클래스의 인스턴스 멤버 접근하기 II
package com.eomcs.oop.ex11.c;

class B3 {

  // 인스턴스 멤버
  int v1 = 10;

  class X {
    int v1 = 100;

    void test() {
      int v1 = 1000;

      System.out.printf("v1 = %d\n", v1); // 로컬 변수
      System.out.printf("this.v1 = %d\n", this.v1); // 인스턴스 변수
      System.out.printf("B3.this.v1 = %d\n", B3.this.v1); // 바깥 객체의 인스턴스 변수
    }
  }
}

public class Exam0230 {

  public static void main(String[] args) {
    B3 outer = new B3();
    outer.v1 = 11;

    B3.X x1 = outer.new X();
    x1.test();
    System.out.println("--------------------");

    B3.X x2 = outer.new X();
    x2.test();
    System.out.println("--------------------");

    B3 outer2 = new B3();
    outer2.v1 = 22;

    B3.X x3 = outer2.new X();
    x3.test();
    System.out.println("--------------------");
  }

}

 

B3 outer = new B3(); 하면 인스턴스 생성되고 필드 v1 에 값 10 들어간다. outer 에 주소 200 들어간다. outer.v1 = 11; 하면 주소 200의 v1 에 11 저장한다.

B3.X x1 = outer.new X(); 하면 인스턴스 필드 v1 및 바깥 클래스의 인스턴스 주소를 담는 변수 this$0 생성하고 200을 저장한다. x1에 인스턴스 주소 2700을 저장한다.

x1.test(); 하면 x1 의 주소 2700 을 test() 의 this에 넘긴다.

B3.X x2 = outer.new X(); 하면 인스턴스 필드 v1 및 바깥 클래스의 인스턴스 주소를 담는 변수 this$0 생성하고 200을 저장한다. x2에 인스턴스 주소 3000을 저장한다.

x2.test(); 하면 x2의 주소 3000 을 test() 의 this에 넘긴다.

 

B3 outer2 = new B3(); 하면 인스턴스 생성되고 필드 v1 에 값 10 들어간다. outer2 에 주소 300 들어간다. outer2.v1 = 22; 하면 주소 300의 v1 에 22 저장한다.

B3.X x3 = outer2.new X(); 하면 인스턴스 필드 v1 및 바깥 클래스의 인스턴스 주소를 담는 변수 this$0 생성하고 300을 저장한다. x3에 인스턴스 주소 3000을 저장한다.

x3.test(); 하면 x3 의 주소 4500 을 test() 의 this에 넘긴다.

 

 

inner class : 바깥 클래스의 인스턴스 멤버 접근하기 III
package com.eomcs.oop.ex11.c;

class B4 {

  // 인스턴스 멤버
  int v1 = 10;

  class X {
    int v2 = 100;

    void test() {
      int v3 = 1000;

      // 컴파일러가 변수를 찾을 때 순서: 
      // => 로컬 변수 ---> 인스턴스 변수 ---> 바깥 객체의 인스턴스 변수
      // => 컴파일러는 변수를 찾은 후에 해당 변수의 문법에 맞게 코드를 변경한다.
      //
      System.out.printf("v3 = %d\n", v3); // 로컬 변수: v3 
      System.out.printf("this.v2 = %d\n", v2); // 인스턴스 변수: this.v2 로 변경 
      System.out.printf("B4.this.v1 = %d\n", v1); // 바깥 객체의 인스턴스 변수: B4.this.v1 으로 변경 
    }
  }
}

public class Exam0240 {

  public static void main(String[] args) {
    B4 outer = new B4();
    outer.v1 = 11;

    B4.X x1 = outer.new X();
    x1.test();
    System.out.println("--------------------");

    B4.X x2 = outer.new X();
    x2.test();
    System.out.println("--------------------");

    B4 outer2 = new B4();
    outer2.v1 = 22;

    B4.X x3 = outer2.new X();
    x3.test();
    System.out.println("--------------------");
  }

}

 

 

inner class : 다른 멤버가 중첩 클래스 사용하기
package com.eomcs.oop.ex11.c;

class C {

  static void m1() {
    // 스태틱 멤버는 인스턴스 멤버를 사용할 수 없다.
    //
    X obj; // 레퍼런스 선언은 가능!

    //    obj = new X(); // 컴파일 오류! 인스턴스 생성 불가능!

    // 이유?
    // - 인스턴스 멤버를 사용하려면 인스턴스 주소가 있어야 한다.
    // - 스태틱 메서드는 인스턴스 주소를 담고 있는 this 변수가 존재하지 않는다.
  }

  void m2() {
    // 인스턴스 메서드는 인스턴스 주소를 담고 있는 this 변수가 있다.
    // 그래서 inner class 를 사용할 수 있다.
    X obj = this.new X();
    obj.test();

    X obj2 = new X(); // 인스턴스 필드나 메서드와 마찬가지로 this를 생략할 수 있다.
    obj2.test();
  }

  class X {
    void test() {
      System.out.println("X.test()");
    }
  }
}

public class Exam0310 {

  public static void main(String[] args) {
    C.m1();

    C outer = new C();
    outer.m2();

  }

}

 

 

inner class : import 사용
package com.eomcs.oop.ex11.c;

// 중첩 클래스를 직접 import 할 수 있다.
// => import 가 하는 일은 클래스를 로딩하는 것이 아니다!
// => 컴파일러에게 클래스의 위치를 알려주는 것이다.
import com.eomcs.oop.ex11.c.D.X;
import com.eomcs.oop.ex11.c.sub.M;
import com.eomcs.oop.ex11.c.sub.M.Y;

class D {
  class X {
    void test() {
      System.out.println("test()");
    }
  }
}

public class Exam0410 {

  public static void main(String[] args) {

    D outer = new D();
    X obj = outer.new X();
    obj.test();

    M outer2 = new M();
    Y obj2 = outer2.new Y();
    obj2.test();
  }

}
package com.eomcs.oop.ex11.c.sub;

public class M {
  // 다른 패키지에서 접근 할 수 있도록 public 으로 공개
  public class Y {
    public void test() {
      System.out.println("M.Y.test()");
    }
  }
}

 

 

inner class : 바깥 클래스의 인스턴스를 보관할 this 변수와 생성자
package com.eomcs.oop.ex11.c;

class E {

  void m(int a) {
    int b = 100;
  }

  class X {
    // 인스턴스 메서드는 this 라는 내장 변수에 인스턴스 주소를 보관한다.
    // 그렇다면 inner class는 어디에 보관할까?
    // - 자바 컴파일러는 바깥 클래스의 인스턴스 주소를 저장하기 위해 
    //   다음과 같은 필드를 추가한다.
    // - 컴파일 완료된 inner class의 .class 파일을 열어 보면,
    //   바깥 클래스의 객체 주소를 저장할 인스턴스 필드가 추가된 것을 확인할 수 있다.  
    //    
    //    final synthetic com.eomcs.oop.ex11.c.E this$0;
    //
    // - 위와 같이 바깥 클래스의 인스턴스 주소를 저장하고 있는 변수를 사용하려면
    //   다음과 같이 코드를 작성해야 한다.
    //   [문법] 바깥클래스명.this
    //   예) E.this 
    // 
    // 인스턴스 메서드에 있는 this 변수는 인스턴스 메서드를 호출할 때 객체 주소를 저장한다.
    // 그렇다면 inner class에서 바깥 클래스의 인스턴스 주소는 언제 저장할까?
    // - 바깥 클래스의 인스턴스를 가지고 inner class의 인스턴스를 생성할 때 저장한다.
    // - 즉 inner class의 생성자를 호출할 때 바깥 클래스의 인스턴스 주소를 파라미터로 전달한다.
    // - 이를 위해 컴파일러는 inner class를 컴파일 할 때 생성자를 변형한다.
    // 
    // - 다음과 같이 개발자가 기본 생성자를 작성했다고 가정하자.
    //    public X() {}
    //
    // - 컴파일러는 다음과 같이 바꾼다.
    //    public X(E outer) {}
    //
    // - 컴파일한 .class 파일을 보면 생성자가 다음과 같이 되어 있다.
    //    E$X(com.eomcs.oop.ex11.c.E arg0);  <== 바깥 클래스의 인스턴스 주소를 받는 파라미터가 있다.
    //    0  aload_0 [this]
    //    1  aload_1 [arg0]
    //    => 다음 줄 코드를 보면 컴파일러가 바깥 클래스의 인스턴스 주소를 저장하기 위해 추가한 this$0 라는 변수를 볼 수 있다.
    //       이 this$0 변수에 바깥 클래스의 인스턴스 주소가 들어 있는 arg0 값을 저장하고 있다.
    //    2  putfield com.eomcs.oop.ex11.c.E$X.this$0 : com.eomcs.oop.ex11.c.E [10]
    //    5  aload_0 [this]
    //    6  invokespecial java.lang.Object() [12]
    //    9  return
    //     Line numbers:
    //       [pc: 0, line: 5]
    //     Local variable table:
    //       [pc: 0, pc: 10] local: this index: 0 type: com.eomcs.oop.ex11.c.E.X
    //    ...
    // - 생성자의 파라미터로 받은 바깥 클래스의 객체 주소는 
    //   컴파일러가 추가한 인스턴스 필드에 보관된다.
    //
  }
}

public class Exam0510 {

  public static void main(String[] args) {
    E outer = new E();

    E.X obj = outer.new X();
    // inner class 의 인스턴스를 생성할 때 바깥 클래스의 객체 주소를 어떻게 전달할까?
    // - 컴파일러는 위의 기본 생성자 호출 코드를 
    //   바깥 클래스의 객체 주소를 받는 생성자 호출 코드로 변경한다.
    // - .class 파일에서 outer.new X() 코드에 해당하는 부분
    //     invokespecial com.eomcs.oop.ex11.c.E$X(com.eomcs.oop.ex11.c.E) [25]
    // - 위의 코드를 자바 소스 코드로 표현해보면,   
    //     E.x obj = new X(outer);
    // 
    //
    // 
  }

}

 

 

inner class : 바깥 클래스의 인스턴스를 보관할 this 변수와 생성자 II
package com.eomcs.oop.ex11.c;

class F {
  class X {
    // 컴파일러는 모든 생성자에
    // 바깥 클래스의 객체 주소를 받는 파라미터를 추가한다.
    X() {}
    // => .class 파일의 내용
    //    F$X(com.eomcs.oop.ex11.c.F arg0);
    //        0  aload_0 [this]
    //        1  aload_1 [arg0]
    //        2  putfield com.eomcs.oop.ex11.c.F$X.this$0 : com.eomcs.oop.ex11.c.F [10]
    //        5  aload_0 [this]
    //        6  invokespecial java.lang.Object() [12]
    //        9  return
    //         Line numbers:
    //           [pc: 0, line: 8]
    //         Local variable table:
    //           [pc: 0, pc: 10] local: this index: 0 type: com.eomcs.oop.ex11.c.F.X
    // => 즉 컴파일러는 다음 코드로 변경한다.
    //    X(F arg0) {}

    X(int a) {}
    // => 즉 컴파일러는 다음 코드로 변경한다.
    //    X(F arg0, int a) {}

    X(String s, int a) {}
    // => 즉 컴파일러는 다음 코드로 변경한다.
    //    X(F arg0, java.lang.String s, int a) {}
  }
}

public class Exam0520 {

  public static void main(String[] args) {
    F outer = new F();

    // 중첩 클래스의 인스턴스를 생성할 때,
    // 컴파일러는 바깥 클래스의 객체 주소를 생성자의 첫 번째 파라미터로 전달한다.
    // 즉 컴파일러가 만든 생성자를 호출하도록 코드를 변환한다.
    //
    F.X obj = outer.new X();
    // => invokespecial com.eomcs.oop.ex11.c.F$X(com.eomcs.oop.ex11.c.F) [25]
    // => 예) new X(outer)
    //

    F.X obj2 = outer.new X(100);
    // => invokespecial com.eomcs.oop.ex11.c.F$X(com.eomcs.oop.ex11.c.F, int) [28]
    // => 예) new X(outer, 100)
    //

    F.X obj3 = outer.new X("kim", 100);
    // => invokespecial com.eomcs.oop.ex11.c.F$X(com.eomcs.oop.ex11.c.F, java.lang.String, int) [33]
    // => 예) new X(outer, "kim", 100);
  }

}

 

 

inner class : inner 클래스에서 변수를 찾는 순서
package com.eomcs.oop.ex11.c;

class G {
  int v1 = 1;
  int v2 = 2;
  int v3 = 3; 

  class X {
    int v1 = 10;
    int v2 = 20;

    void m1(int v1) {

      // 중첩 클래스의 메서드에서 필드를 사용하기 
      System.out.println("G 객체:");
      System.out.printf("G.this.v1 = %d\n", G.this.v1);
      System.out.printf("G.this.v2 = %d\n", G.this.v2);
      System.out.printf("G.this.v3 = %d\n", G.this.v3);

      System.out.println("-------------------------");

      // inner 클래스의 인스턴스 필드 접근
      System.out.println("G.X 객체:");
      System.out.printf("this.v1 = %d\n", this.v1);
      System.out.printf("this.v2 = %d\n", this.v2);

      System.out.println("-------------------------");

      // 로컬 변수 접근
      System.out.println("로컬:");
      System.out.printf("v1 = %d\n", v1);

      System.out.println("-------------------------");
    }
  }
}

public class Exam0610 {

  public static void main(String[] args) {
    G outer = new G();

    G.X obj = outer.new X();
    obj.m1(100);

  }

}

 

 

inner class : inner 클래스에서 변수를 찾는 순서 II
package com.eomcs.oop.ex11.c;

class H {
  int v1 = 1;
  int v2 = 2;
  int v3 = 3; 

  class X {
    int v1 = 10;
    int v2 = 20;

    void m1(int v1) {

      // this 를 명시하지 않았을 때 변수를 찾는 순서
      // 1) 로컬 변수를 찾는다.
      // 2) 메서드가 소속된 클래스의 인스턴스 필드를 찾는다.
      // 3) 바깥 클래스의 인스턴스나 스태틱 필드를 찾는다.

      System.out.printf("v1 = %d\n", v1);
      System.out.printf("this.v1 = %d\n", this.v1);
      System.out.printf("H.this.v1 = %d\n", H.this.v1);

      System.out.printf("v2 = %d\n", v2);
      System.out.printf("v3 = %d\n", v3);
    }
  }
}

public class Exam0620 {

  public static void main(String[] args) {
    H outer = new H();

    H.X obj = outer.new X();
    obj.m1(100);

  }

}

 

 

inner class 응용 I : 1단계 - 스태틱 중첩 클래스 사용
package com.eomcs.oop.ex11.c;

import java.util.ArrayList;
import java.util.List;

public class Exam0711 {

  public static void main(String[] args) {
    Musics1 m1 = new Musics1();
    m1.add("aaa.mp3");
    m1.add("bbb.mp3");
    m1.add("ccc.mp3");

    Musics1 m2 = new Musics1();
    m2.add("xxx.mp3");
    m2.add("yyy.mp3");

    // Player가 사용할 Musics 객체를 넘기기 위해 
    // 개발자가 직접 해당 생성자를 호출해 줘야 한다. 
    Musics1.Player p1 = new Musics1.Player(m1);
    Musics1.Player p2 = new Musics1.Player(m2);

    p1.play();
    p2.play();
  }
}

class Musics1 {

  List<String> songs = new ArrayList<>();

  public void add(final String song) {
    songs.add(song);
  }

  public void delete(final int index) {
    songs.remove(index);
  }

  static class Player {

    // 스태틱 중첩 클래스에서 바깥 클래스의 인스턴스를 사용하려면
    // 다음과 같이 바깥 클래스의 인스턴스 주소를 저장하는 변수를 개발자가 직접 선언해 줘야 한다.
    Musics1 musics;

    // 또한 바깥 클래스의 인스턴스 주소를 받는 파라미터를 
    // 개발자가 직접 생성자에 선언해 줘야 한다.
    public Player(Musics1 musics) {
      this.musics = musics;
    }

    public void play() {
      // 필드에 보관되어 있는 Musics 객체에서 음악 파일을 꺼내 플레이 한다.
      for (final String song : musics.songs) {
        System.out.println(song);
      }
      System.out.println("-----------------------------");
    }
  }
}

 

 

inner class 응용 I : 2단계 - 논스태틱 중첩 클래스(inner class) 사용
package com.eomcs.oop.ex11.c;

import java.util.ArrayList;
import java.util.List;

public class Exam0712 {

  public static void main(String[] args) {
    Musics2 m1 = new Musics2();
    m1.add("aaa.mp3");
    m1.add("bbb.mp3");
    m1.add("ccc.mp3");

    Musics2 m2 = new Musics2();
    m2.add("xxx.mp3");
    m2.add("yyy.mp3");

    // Player가 사용할 바깥 클래스 Musics2의 객체를 넘길 때는 
    // 다음과 같이 파라미터가 아니라 
    // 생성자 호출 문장 앞쪽에 놓는다. 
    //
    Musics2.Player p1 = m1.new Player();
    Musics2.Player p2 = m2.new Player();

    // 스태틱 중첩 클래스를 사용할 때는 다음과 같이 직접 생성자의 파라미터로 바깥 클래스의 객체를 넘겨줘야 했다. 
    //    Musics2.Player p1 = new Musics2.Player(m1);
    //    Musics2.Player p2 = new Musics2.Player(m2);

    p1.play();
    p2.play();
  }
}

class Musics2 {

  List<String> songs = new ArrayList<>();

  public void add(final String song) {
    songs.add(song);
  }

  public void delete(final int index) {
    songs.remove(index);
  }

  // 중첩 클래스가 바깥 클래스의 객체를 사용해야 한다면,
  // 스태틱 중첩 클래스로 만들지 말고 논스태틱 중첩 클래스(inner class)로 만들라.
  // 왜?
  // - 바깥 클래스의 인스턴스를 저장할 필드가 자동 생성된다.
  // - 생성자에 바깥 클래스의 인스턴스를 받는 파라미터가 자동으로 추가된다. 
  class Player {

    // 1) 논스태틱 중첩 클래스는 바깥 클래스의 인스턴스 주소를 저장할 필드가 
    //    자동으로 추가되기 때문에 다음과 같이 개발자가 따로 선언할 필요가 없다.
    //    Musics2 musics;

    // 2) 바깥 클래스의 인스턴스를 받는 파라미터가 생성자에 자동으로 추가되기 때문에 
    //    다음과 같이 바깥 클래스의 객체를 받는 파라미터를 개발자가 직접 선언할 필요가 없다.  
    public Player(/*Musics2 musics*/) {
      // 따라서 바깥 클래스의 객체 주소를 인스턴스 필드에 저장하는 코드를 작성할 필요가 없다.
      //      this.musics = musics;
    }

    public void play() {
      // 내부에 보관된 바깥 클래스의 객체를 사용하고 싶다면,
      // 다음과 같이 '바깥클래스명.this.멤버' 형식으로 접근한다. 
      for (final String song : Musics2.this.songs) {
        System.out.println(song);
      }
      System.out.println("-----------------------------");
    }
  }
}

 

 

inner class 응용 I : 리팩토링
package com.eomcs.oop.ex11.c;

import java.util.ArrayList;
import java.util.List;

public class Exam0713 {

  public static void main(String[] args) {
    Musics3 m1 = new Musics3();
    m1.add("aaa.mp3");
    m1.add("bbb.mp3");
    m1.add("ccc.mp3");

    Musics3 m2 = new Musics3();
    m2.add("xxx.mp3");
    m2.add("yyy.mp3");

    // 바깥 클래스의 인스턴스를 사용하는 inner 클래스라면
    // inner 클래스의 객체를 만드는 역할도 
    // 바깥 클래스가 하는게 유지보수에 더 낫다.
    // => GRASP 설계 기법에서 "정보를 가진자가 그 일을 하라.(Information Expert)"를 적용.
    Musics3.Player p1 = m1.createPlayer();
    Musics3.Player p2 = m2.createPlayer();

    p1.play();
    p2.play();
  }
}

class Musics3 {

  List<String> songs = new ArrayList<>();

  public void add(final String song) {
    songs.add(song);
  }

  public void delete(final int index) {
    songs.remove(index);
  }

  // inner 클래스의 객체를 생성하는 역할을 바깥 클래스가 맡는다.
  public Player createPlayer() {
    return new Player(); // ==> this.new Player();  // 바깥 클래스의 객체 주소 생략!
  }

  class Player {
    public void play() {
      for (final String song : Musics3.this.songs) {
        System.out.println(song);
      }
      System.out.println("-----------------------------");
    }
  }
}

 

 

inner class 응용 II : inner 클래스와 인터페이스
package com.eomcs.oop.ex11.c;

import java.util.ArrayList;
import java.util.List;

public class Exam0720 {

  public static void main(String[] args) {
    Musics4 m1 = new Musics4();
    m1.add("aaa.mp3");
    m1.add("bbb.mp3");
    m1.add("ccc.mp3");

    Musics4 m2 = new Musics4();
    m2.add("xxx.mp3");
    m2.add("yyy.mp3");

    // Musics4의 플레이어 객체를 생성한다.
    // 리턴 객체는 Player 규칙에 따라 만든 객체이다.
    Player p1 = m1.createPlayer();
    Player p2 = m2.createPlayer();

    p1.play();
    p2.play();
  }
}

// 음악 플레이어의 사용법을 정의한다. 
interface Player {
  void play();
}

class Musics4 {

  List<String> songs = new ArrayList<>();

  public void add(final String song) {
    songs.add(song);
  }

  public void delete(final int index) {
    songs.remove(index);
  }

  // Player 구현 객체를 리턴한다.
  // Player 구현체는 Musics4의 inner 클래스로 되어 있다.
  public Player createPlayer() {
    return new PlayerImpl(); // ==> this.new PlayerImpl();  // 바깥 클래스의 객체 주소 생략!
  }

  // 인터페이스 구현체를 inner 클래스로 정의한다.
  class PlayerImpl implements Player {
    public void play() {
      for (final String song : Musics4.this.songs) {
        System.out.println(song);
      }
      System.out.println("-----------------------------");
    }
  }
}

 

 

inner class 응용 III : inner 클래스와 인터페이스 2
package com.eomcs.oop.ex11.c;

import java.util.ArrayList;
import java.util.Iterator;

public class Exam0730 {
  public static void main(final String[] args) {
    final ArrayList<String> m1 = new ArrayList<>();
    m1.add("aaa.mp3");
    m1.add("bbb.mp3");
    m1.add("bbb.mp3");

    // ArrayList 도 Iterator 구현체를 inner class 로 갖고 있다.
    // iterator() 메서드는 이 구현체를 생성하여 리턴한다.
    //
    final Iterator i1 = m1.iterator();
    final Iterator i2 = m1.iterator();

    while (i1.hasNext()) {
      System.out.println(i1.next());
    }
    System.out.println("--------------------");

    while (i2.hasNext()) {
      System.out.println(i2.next());
    }
    System.out.println("--------------------");
  }
}

 

 

 


 

 

조언

 

*

 

 

 


 

과제

 

/