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
관리 메뉴

개발자입니다

[비트캠프] 58일차(12주차3일) - Java(람다, 메서드 레퍼런스, 제네릭), myapp-24, 25 본문

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

[비트캠프] 58일차(12주차3일) - Java(람다, 메서드 레퍼런스, 제네릭), myapp-24, 25

끈기JK 2023. 1. 27. 12:30

 

중첩 클래스의 종류

 

클래스 안에 static 클래스가 있으면 static nested class 라 한다.

클래스 안에 non-static 클래스가 있으면 non-static nested class 라 한다.

메서드 안에 클래스가 있으면 local class 라 한다.

인스턴스를 생성하고 상속받은 클래스를 바로 정의한 것을 anonymous class 라 한다.

 

 

 

중첩 클래스와 .class 파일

 

클래스 안의 static class X { } 를 컴파일 하면 A$X.class 파일이 만들어진다.

클래스 안의 class Y { } 를 컴파일 하면 A$Y.class 파일이 만들어진다.

메서드 안의 class Z { } 를 컴파일 하면 A$1Z.class 파일이 만들어진다.

익명 클래스를 컴파일 하면 A$1.class 파일이 만들어진다.

 

.class 파일에 중첩 클래스 정보와 Nest Member 정보가 나온다.

// Compiled from Exam0210.java (version 17 : 61.0, super bit)
public class com.eomcs.oop.ex11.a.Exam0210 {

Inner classes:
    [inner class info: #21 com/eomcs/oop/ex11/a/Exam0210$1, outer class info: #0
     inner name: #0, accessflags: 0 default],
    [inner class info: #31 com/eomcs/oop/ex11/a/Exam0210$1C, outer class info: #0
     inner name: #33 C, accessflags: 0 default],
    [inner class info: #34 com/eomcs/oop/ex11/a/Exam0210$A, outer class info: #1 com/eomcs/oop/ex11/a/Exam0210
     inner name: #36 A, accessflags: 8 static],
    [inner class info: #37 com/eomcs/oop/ex11/a/Exam0210$B, outer class info: #1 com/eomcs/oop/ex11/a/Exam0210
     inner name: #39 B, accessflags: 0 default]

Nest Members:
   #21 com/eomcs/oop/ex11/a/Exam0210$1,
   #31 com/eomcs/oop/ex11/a/Exam0210$1C,
   #34 com/eomcs/oop/ex11/a/Exam0210$A,
   #37 com/eomcs/oop/ex11/a/Exam0210$B

 

 

 

static nested class 와 non-static nested class 의 차이

 

static class X { } 는 컴파일러가 X() { - } 기본 생성자 추가한다.

class Y { } 는 컴파일러가 바깥 클래스의 객체 주소를 담은 레퍼런스 A this$0 추가한다. 생성자에서 바깥 클래스의 인스턴스를 받을 수 있도록 Y(A arg) { this$0 = arg; } 파라미터 추가한다.

Y obj = new Y(); 는 new Y(this); 로 컴파일러가 바깥 클래스의 객체 주소를 넘기는 코드로 자동 변경한다.

 

 

 

 

예제 소스

 

 

com.eomcs.oop.ex11.d

 

 

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

class A {

  void m1() {
    // 메서드 안에 정의하는 클래스를 "local class"라 한다.
    // - 특정 메서드 안에서만 사용되는 경우 로컬 클래스로 정의한다.
    // - 쓸데없이 외부로 노출하지 않기 위함.
    // - 노출을 줄이면 유지보수에 좋다.
    // - 로컬 클래스에서 로컬 이라는 말은 '이 메서드 안에서만 사용할 수 있다'는 뜻이다.
    // - 그냥 사용 범위에 대한 제한을 가리키는 말이다.
    // - 메서드를 호출할 때 클래스가 정의된다는  뜻이 아니다.
    class X {

    }
    X obj = new X();
  }

  static void m2() {
    // 메서드 안에 정의하는 클래스를 "local class"라 한다.
    class X {

    }
    X obj = new X();
  }
}

public class Exam0110 {

  public static void main(String[] args) {
    A outer = new A();
    outer.m1();
  }

}

로컬 클래스는 1부터 숫자가 붙는다. 같은 이름을 가지면 숫자를 증가시키며 저장한다.

 

 

local class : 사용 범위
package com.eomcs.oop.ex11.d;

class B {
  void m1() {
    class X {

    }
    X obj = new X();
  }

  static void m2() {
    // 다른 메서드에 정의된 클래스는 사용할 수 없다. 
    //    X obj; // 컴파일 오류!
  }
}

public class Exam0120 {

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

}

 

 

local class : .class 파일명
package com.eomcs.oop.ex11.d;

class B2 {
  void m1() {
    class X {
      // 로컬 클래스의 .class 파일명
      // => [바깥클래스명]$[정의된순서][로컬클래스명].class
      // => 예) B2$1X.class
    }
    class Y {
      // 로컬 클래스의 .class 파일명
      // => [바깥클래스명]$[정의된순서][로컬클래스명].class
      // => 예) B2$1Y.class
    }
  }

  static void m2() {
    class Y {
      // 로컬 클래스의 .class 파일명
      // => [바깥클래스명]$[정의된순서][로컬클래스명].class
      // => 예) B2$2Y.class
    }
    class X {
      // 로컬 클래스의 .class 파일명
      // => [바깥클래스명]$[정의된순서][로컬클래스명].class
      // => 예) B2$2X.class
    }
    class Z {
      // 로컬 클래스의 .class 파일명
      // => [바깥클래스명]$[정의된순서][로컬클래스명].class
      // => 예) B2$1Z.class
    }
  }
}

public class Exam0130 {

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

}

 

 

local class : 인스턴스 메서드와 로컬 클래스
package com.eomcs.oop.ex11.d;

class C {
  int v1 = 1;

  void m1() {
    // 인스턴스 메서드는 C 인스턴스 주소를 this 변수에 저장한다.

    class X {
      // 그래서 인스턴스 메서드 안에 정의된 로컬 클래스는
      // 바깥 클래스의 인스턴스를 사용할 수 있다.
      // .class 파일을 확인해보면 바깥 클래스이 인스턴스 주소를 저장하는 필드가 선언되어 있다.
      // 또한 생성자에도 바깥 클래스의 인스턴스 주소를 받는 파라미터가 선언되어 있다.
      //
      //      class com.eomcs.oop.ex11.d.C$1X {
      //        final synthetic com.eomcs.oop.ex11.d.C this$0;
      //
      //        C$1X(com.eomcs.oop.ex11.d.C arg0);
      //           0  aload_0 [this]
      //           1  aload_1 [arg0]
      //           2  putfield com.eomcs.oop.ex11.d.C$1X.this$0 : com.eomcs.oop.ex11.d.C [10]
      //
      void f() {
        System.out.printf("v1 = %d\n", C.this.v1);
      }
    }
    X obj = new X(); // 컴파일 ==> new X(this); 
    obj.f();
  }
}

public class Exam0210 {

  public static void main(String[] args) {
    C c = new C();
    c.m1();
  }

}

 

 

local class : 스태틱 메서드와 로컬 클래스
package com.eomcs.oop.ex11.d;

class C2 {
  int v1 = 1;

  static void m1() {
    // 스태틱 메서드는 C2 인스턴스 주소를 저장할 this라는 변수가 없다.

    class X {
      // 그래서 바깥 클래스의 인스턴스를 사용할 수 없다.
      // .class 파일을 확인해보면 바깥 클래스의 인스턴스를 보관할 필드가 선언되어 있지 않다.
      // 또한 생성자에도 바깥 클래스의 인스턴스를 받는 파라미터가 선언되어 있지 않다.
      //
      //      class com.eomcs.oop.ex11.d.C2$1X {
      //
      //        C2$1X();
      //          0  aload_0 [this]
      //          1  invokespecial java.lang.Object() [8]
      //
      void f() {
        //        System.out.printf("v1 = %d\n", C2.this.v1); // 컴파일 오류!
      }
    }
    X obj = new X(); // 컴파일 해도 같다. ===> new X();
    obj.f();
  }
}

public class Exam0220 {

  public static void main(String[] args) {
    C2 c = new C2();
    c.m1();
  }

}

 

 

local class에서 바깥 메서드의 로컬 변수 접근
package com.eomcs.oop.ex11.d;

// 계산기 사용법을 정의한다.
interface Calculator {
  double compute(int money);
}

class CalculatorFactory {

  static Calculator create(float interest) {

    class CalculatorImpl implements Calculator {

      @Override
      public double compute(int money) {
        return money + (money * interest);
        // interest는 create() 함수의 로컬 변수이다.
        // CalculatorImpl 객체를 생성하여 리턴한 후에는 interest 로컬 변수는 스택에서 사라진 상태일 것이다.
        // 나중에 compute()를 호출할 때 interest 변수는 없을텐데, 어떻게 된 것인가?
        // => 로컬 클래스에서 메서드의 로컬 변수를 사용한다면
        //    컴파일러는 로컬 클래스에 바깥 메서드의 로컬 변수 값을 저장할 필드를 추가한다.
        //    또한 로컬 클래스의 객체를 생성할 때 생성자에 로컬 변수의 값을 넘겨 줄 것이다.
      }
    }

    return new CalculatorImpl();
  }
}

public class Exam0310 {

  public static void main(String[] args) {
    Calculator c1 = CalculatorFactory.create(0.02f);
    Calculator c2 = CalculatorFactory.create(0.08f);

    System.out.printf("%.2f\n", c1.compute(1235_0000));
    System.out.printf("%.2f\n", c2.compute(1235_0000));
  }

}

로컬 클래스의 .class 파일을 보면 바깥 메서드의 필드를 저장할 변수를 선언하는 것을 알 수 있다.

// Compiled from Exam0310.java (version 17 : 61.0, super bit)
class com.eomcs.oop.ex11.d.CalculatorFactory$1CalculatorImpl implements com.eomcs.oop.ex11.d.Calculator {
  
  // Field descriptor #8 F
  private final synthetic float val$interest;

 

 

local class에서 바깥 메서드의 로컬 변수 접근
package com.eomcs.oop.ex11.d;

class D {

  void m1() {
    int a = 100;
    int b = 200;

    class X {
      // 로컬 클래스에서 바깥 메서드(m1())의 로컬 변수(a,b) 값을 사용한다면 
      // 1) 다음과 같이 컴파일 할 때 그 값을 저장할 필드(a)를 자동으로 추가한다.
      //    => 사용하는 변수에 대해서만 필드를 생성한다.
      //    => 로컬 변수 b는 사용하지 않기 때문에 b 값을 받을 필드는 생성하지 않는다.
      //      int a;
      //      D outer;
      // 
      // 2) 또한 로컬 클래스의 객체를 생성할 때 그 값을 사용할 수 있도록 
      //    생성자에 파라미터를 추가한다.
      //      public X(D outer, int a) {
      //        this.a = a;
      //        this.outer = outer;
      //      }
      // 
      // .class 파일의 코드:
      //      class com.eomcs.oop.ex11.d.D$1X {
      //        
      //        final synthetic com.eomcs.oop.ex11.d.D this$0;
      //        
      //        private final synthetic int val$a;
      //        
      //        D$1X(com.eomcs.oop.ex11.d.D arg0, int arg1);
      //        ...
      //
      void f1() {
        // 그래서 다음과 같이 
        // 로컬 클래스에서는 바깥 메서드의 로컬 변수를 자기것인양 사용할 수 있는 것이다.
        System.out.println(a); // m1() 에 선언된 로컬 변수를 가리킨다.
      }
    }

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

  static void m2() {
    int a = 100;
    int b = 200;

    // 스태틱 메서드의 로컬 클래스도 인스턴스 메서드의 로컬 클래스와 같다.
    // 단, 스태틱이기 때문에 바깥 클래스의 인스턴스 주소를 받는 필드는 
    // 자동으로 생성되지 않는다.
    // 
    // .class 파일의 코드:
    //    class com.eomcs.oop.ex11.d.D$2X {
    //      
    //      private final synthetic int val$b;
    //      
    //      D$2X(int arg0);
    //      ..
    class X {
      void f1() {
        System.out.println(b);
      }
    }

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

public class Exam0311 {

  public static void main(String[] args) {
    D obj = new D();
    obj.m1();
    obj.m2();
  }

}

 

 

local class에서 바깥 메서드의 로컬 변수 접근 I
package com.eomcs.oop.ex11.d;

class D2 { 
  int v1 = 1;

  void m1() {
    int v2 = 2;

    class X {
      int v3 = 3;

      void f() {
        int v4 = 4;
        System.out.printf("v4 = %d\n", v4); // f()의 로컬 변수
        System.out.printf("v3 = %d\n", this.v3); // X 인스턴스의 변수
        System.out.printf("v1 = %d\n", D2.this.v1); // D2의 인스턴스 변수

        // 바깥 메서드의 로컬 변수를 사용할 때는 
        // 자신의 메서드인양 사용하면 된다.
        System.out.printf("v2 = %d\n", v2); // D2.m1()의 로컬 변수
      }
    }

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

public class Exam0321 {

  public static void main(String[] args) {
    D2 obj = new D2();
    obj.m1();
  }

}

 

 

local class에서 바깥 메서드의 로컬 변수 접근 II
package com.eomcs.oop.ex11.d;

class D22 {
  int v1 = 1;

  void m1() {
    //    int v1 = 10;

    class X {
      //      int v1 = 100;

      void f() {
        //        int v1 = 1000;
        System.out.printf("v1 = %d\n", v1);
        // this 를 생략하면 다음 순서로 변수를 찾는다.
        // 1) 로컬 변수
        // 2) 인스턴스 변수
        // 3) 메서드에 선언된 로컬 변수
        // 4) 바깥 클래스의 인스턴스 변수 또는 스태틱 변수
      }
    }

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

public class Exam0322 {

  public static void main(String[] args) {
    D22 obj = new D22();
    obj.m1();
  }

}

 

 

로컬 클래스에서 메서드에 선언된 로컬 변수 접근하기
package com.eomcs.oop.ex11.d;

class D3 {

  void m1() {
    final int v1 = 1;
    int v2 = 2;
    int v3 = 3;
    //    v3 = 30;

    class X {
      void f() {
        // 로컬 클래스에서는 바깥 메서드의 로컬 변수를 사용할 수 있다.
        // 1) final 로 선언된 경우
        System.out.printf("v1 = %d\n", v1);

        // 2) final 로 선언된 것은 아니지만 값을 한 번만 할당한 경우.
        System.out.printf("v2 = %d\n", v2);

        // => 값을 여러 번 할당한 경우에는 접근할 수 없다.
        System.out.printf("v3 = %d\n", v3); // 컴파일 오류!

        // 결론!
        // - 상수 값이거나 상수에 준하는 경우(값을 한 번만 할당한 경우)
        //   로컬 클래스에서 메서드의 로컬 변수를 사용할 수 있다.
        // - 즉 로컬 클래스에서 바깥 메서드의 로컬 변수를 사용하는 상황?
        //   값을 조회하는 용도할 때.
        //- 왜?
        //   로컬 객체가 사용하는 로컬 변수는
        //   메서드 호출이 끝났을 때 제거되기 때문이다.
      }
    }

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

public class Exam0330 {

  public static void main(String[] args) {
    D3 obj = new D3();
    obj.m1();
  }

}

 

 

로컬 클래스가 로컬 변수를 조회용으로만 사용하는 이유?
package com.eomcs.oop.ex11.d;

public class Exam0340 {


  public static void main(final String[] args) {
    final Exam0340 obj = new Exam0340();
    final Runner r = obj.createRunner("홍길동");
    // createRunner()의 리턴 값은
    // 이 메서드에 선언된 로컬 클래스인 A의 객체이다.
    // A 객체는 Runner 규칙에 따라 만들었기 때문에
    // Runner 레퍼런스에 저장할 수 있다.
    // 이 레퍼런스를 사용하여 A 객체의 run() 메서드를 호출해 보자!
    r.run();

    // 로컬 클래스의 객체가 로컬 변수의 값을 조회용으로만 사용하는 이유?
    // => 위의 코드에서 createRunner() 호출이 끝나면 이 메서드가 실행되는 동안
    // 생성되었던 모든 로컬 변수는 스택 메모리에서 제거된다.
    // 즉 createRunner()의 name 파라미터가 제거된다는 것이다.
  }

  Runner createRunner(String name) {
    // Exam0340 객체 주소를 받는 내장 변수 this 가 있다.
    //
    class A implements Runner {
      // 컴파일러는 바깥 클래스의 객체 주소를 받을 필드는 추가한다.
      // 또한 바깥 클래스의 객체 주소를 받는 생성자를 추가한다.
      /*
       * Exam0340 outer; // 바깥 클래스의 주소를 받을 필드
       *
       * String paramName; // run() 메서드에서 사용할 로컬 변수을 값을 받을 필드
       *
       * public A(Exam0365 obj, String str) { outer = obj; paramName = str;}
       *
       */

      @Override
      public void run() {
        // 바깥 메서드의 로컬 변수는 메서드 호출이 완료되는 순간
        // 스택 메모리에서 제거되기 때문에
        // 로컬 클래스의 객체에서 사용할 수 없는 상황이 발생할 것이다.
        // 그래서 컴파일러는 바깥 메서드의 로컬 변수 값을 저장할
        // 필드를 클래스에 추가한다.
        // 또한 로컬 변수의 값을 받는 생성자를 만든다.
        // 따라서 run() 메서드를 호출하는 시점에는
        // A 로컬 객체에 name 변수의 값이 들어 있기 때문에
        // 그래도 사용할 수 있는 것이다.
        // => 다음 코드의 name은 createRunner() 메서드의 파라미터가 아니다.
        // => A 인스턴스가 생성될 때 값을 따로 복제한 필드를 가리키는 것이다.
        //
        System.out.printf("%s님이 달립니다!", name);

        // 위 문장은 다음과 같이
        // 컴파일러가 로컬 클래스의 필드(예: paramName)를 사용하는 문장으로 바꾼다.
        // 그래서 createRunner() 메서드 호출이 끝나더라도
        // name 값을 사용할 수 있는 것이다.
        //
        // System.out.printf("%s님이 달립니다!", this.paramName);

      }
    }
    return new A();
    // 컴파일러는 로컬 클래스의 객체를 생성할 때
    // 바깥 클래스의 객체 주소와 로컬 객체가 사용하는
    // 메서드의 로컬 변수 값을 전달하기 위해
    // 다음과 같이 컴파일러가 만든 생성자를 호출한다.
    //
    // return new A(this, name);
  }
}


interface Runner {
  void run();
}

 

 

anonymous class : 인터페이스를 구현한 익명 클래스 정의
package com.eomcs.oop.ex11.e;

public class Exam0110 {
  // 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
  // => 인터페이스는 규칙을 정의한 것이기 때문에 인스턴스 멤버라는 개념이 존재하지 않는다.
  interface A {
    void print();
  }

  class X {}

  static class Y {}

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

    // 1) 로컬 클래스로 인터페이스 구현하기
    class My implements A {
      String name = "홍길동"; 

      @Override
      public void print() {
        System.out.printf("Hello, %s!\n", this.name);
      }
    }

    A obj = new My();
    obj.print();
  }
}

 

 

package com.eomcs.oop.ex11.e;

public class Exam0111 {
  // 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
  // => 인터페이스는 규칙을 정의한 것이기 때문에 인스턴스 멤버라는 개념이 존재하지 않는다.
  interface A {
    void print();
  }

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

    // 2) 익명 클래스로 인터페이스 구현하기
    // => 인스턴스를 한 번 만 생성할 것이라면,
    //    로컬 클래스로 정의하는 것 보다 익명 클래스로 정의하는 것이 더 낫다.
    // => 특히 객체를 사용하려는 곳에 바로 익명 클래스를 정의하면
    //    읽기 쉽기 때문에 소스 코드를 유지보수 하기가 더 좋다.
    //
    // 익명 클래스 = 클래스 정의 문법 + 인스턴스 생성 문법
    //      new 수퍼클래스명() {클래스 정의}
    //      new 인터페이스명() {클래스 정의}
    //
    // 익명 클래스로 인터페이스 구현하기
    // 문법:
    // => 인터페이스명 레퍼런스 = new 인터페이스명() {};
    //    - 수퍼 클래스에 존재하는 생성자를 호출한다.

    //
    A obj2 = new A() {
      @Override
      public void print() {
        System.out.println("Hello!");
      }
    };
    obj2.print();

    new A() {
      @Override
      public void print() {
        System.out.println("Hello!");
      }
    }.print();
  }
}

// 익명 클래스를 정의하는 과정
// 1단계 : 로컬 클래스
/*
class X implements A {
  @Override
  public void print() {
    System.out.println("Hello!");
  }
};
 */

// 2단계 : 클래스의 이름을 지운다.
/*
class implements A {
  @Override
  public void print() {
    System.out.println("Hello!");
  }
};

=> 그러나 클래스의 이름이 없기 때문에 인스턴스를 생성할 수가 없다.
A obj = new ();
 */

// 3단계 : 클래스 이름이 없기 때문에 'class', 'implements' 키워드는 없앤다.
/*
A {
  @Override
  public void print() {
    System.out.println("Hello!");
  }
};
 */

// 4단계 : 클래스 이름이 없기 때문에 따로 인스턴스를 생성할 수 가 없다.
//         바로 생성해야 한다.
// => 인터페이스 이름 바로 앞에 'new' 키워드를 둔다.
/*
new A {
  @Override
  public void print() {
    System.out.println("Hello!");
  }
};
 */

// 5단계 : 익명 클래스의 생성자가 없기 때문에 수퍼 클래스의 생성자를 호출한다.
// => 객체 생성할 때 항상 생성자를 호출해야 하는데,
//    클래스에 이름이 없으면 생성자를 만들 수 없다.
//    따라서 호출할 익명 클래스의 생성자가 없다.
//    (실제는 내부에 익명 클래스의 기본 생성자가 만들어진다.)
//    그래서 수퍼 클래스의 생성자를 호출해야 한다.
//    자바의 모든 클래스는 따로 수퍼 클래스를 지정하지 않으면
//    java.lang.Object 클래스가 수퍼 클래스로 자동 설정된다.
//    바로 그 Object 클래스의 생성자를 호출하도록 지정해야 한다.
//    Object 클래스의 생성자는 기본 생성자 하나 뿐이다.
//    인터페이스 이름 뒤에 기본 생성자를 호출하는 괄호를 추가한다.
/*
new A() {
  @Override
  public void print() {
    System.out.println("Hello!");
  }
};
 */

// 6단계 : 익명 클래스의 레퍼런스 선언
// => 익명 클래스는 이름이 없기 때문에
//    익명 클래스로 레퍼런스를 선언할 수 없다.
//    그래서 익명 클래스가 구현한 인터페이스나
//    상속 받는 수퍼 클래스로 레퍼런스를 선언해야 한다.
/*
A obj = new A() {
  @Override
  public void print() {
    System.out.println("Hello!");
  }
};
 */

 

 

anonymous class : 클래스를 상속 받은 익명 클래스 정의
package com.eomcs.oop.ex11.e;

public class Exam0120 {

  // 클래스는 static과 non-static이 구분된다.
  static abstract class A {
    abstract void print();
  }

  public static void main(final String[] args) {
    // 1) 로컬 클래스로 서브 클래스 만들기
    class My extends A {
      @Override
      public void print() {
        System.out.println("Hello!");
      }
    }

    A obj = new My();
    obj.print();
  }
}

 

 

package com.eomcs.oop.ex11.e;

public class Exam0121 {

  // 클래스는 static과 non-static이 구분된다.
  static abstract class A {
    abstract void print();
  }

  public static void main(final String[] args) {
    // 2) 익명 클래스로 서브 클래스 만들기
    // => 인스턴스를 한 번 만 생성할 것이라면,
    //    로컬 클래스로 정의하는 것 보다 익명 클래스로 정의하는 것이 더 낫다.
    // => 특히 객체를 사용하려는 곳에 바로 익명 클래스를 정의하면
    //    읽기 쉽기 때문에 소스 코드를 유지보수에 더 좋다.
    //
    // 익명 클래스로 인터페이스 구현하기
    // 문법:
    // => 수퍼클래스 레퍼런스 = new 수퍼클래스() {};
    //    - 수퍼 클래스에 존재하는 생성자를 호출한다.
    //
    A obj2 = new A() {
      @Override
      void print() {
        System.out.println("Hello!");
      }

      void m() {
        System.out.println("이 메서드를 어떻게 호출할까?");
      }
    };
    obj2.print();
    // obj2.m();  // obj2는 A 타입이다. A에는 m() 이 정의되어 있지 않다. 컴파일 오류!
  }
}

 

 

anonymous class : 클래스를 상속 받은 익명 클래스 정의 - 생성자
package com.eomcs.oop.ex11.e;

public class Exam0122 {

  // default constructor 가 없는 클래스
  static class A {
    String message;
    int value;

    A(String message) {
      this.message = message;
    }

    A(String message, int value) {
      this.message = message;
      this.value = value;
    }

    void print() {
      System.out.printf("A의 print(): %s, %d\n", this.message, this.value);
    }
  }

  public static void main(final String[] args) {
    // 3) 익명 클래스의 생성자
    // => 익명 클래스의 객체를 만들 때 호출하는 생성자는
    //    수퍼 클래스에 존재하는 생성자여야 한다.
    //
    // 문법:
    // => 수퍼클래스 레퍼런스 = new 수퍼클래스(파라미터, ...) {};
    //    - 컴파일러는 호출하는 수퍼 클래스의 생성자와 동일한 파라미터를 갖는 생성자를
    //      익명 클래스에 추가한다.
    //
    A obj2 = new A("오호라", 100) {
      // bytecode를 확인해보라!
    };
    obj2.print();
  }
}

 

 

anonymous class - 수퍼 클래스와 인터페이스를 동시에 지정할 수 있을까?
package com.eomcs.oop.ex11.e;

public class Exam0130 {

  static abstract class A {
    public abstract void print();
  }

  interface B {
    String getName();
  }

  public static void main(final String[] args) {
    // 클래스도 상속 받고 인터페이스도 구현하는 익명 클래스를 만들 수 있을까?
    // => 안된다.
    // => 둘 중 하나만 상속 받거나 구현해야지, 동시에 다 할 수 없다.
    //
    A obj = new A implements B() { // 컴파일 오류!
      @Override
      public void print() {
        System.out.println("Hello!");
      }
      public String getName() {
        return "부질없는 짓";
      }
    };
    obj.print();
  }
}

 

 

anonymous class - 여러 개의 인터페이스를 구현할 수 있을까?
package com.eomcs.oop.ex11.e;

public class Exam0140 {

  interface A {
    void m1();
  }

  interface B {
    void m2();
  }

  class X implements A, B {
    public void m1() {};
    public void m2() {}
  }
  
  public static void main(final String[] args) {
    // 여러 개의 인터페이스를 구현할 수 있을까?
    // => 안된다. 익명 클래스에는 그런 문법은 없다.
    //
    Object obj = new A, B() { // 컴파일 오류!
      @Override
      public void print() {
        System.out.println("Hello!");
      }
    };
    obj.print();
  }
}

 

 

anonymous class - 생성자
package com.eomcs.oop.ex11.e;

public class Exam0210 {

  interface A {
    void print();
  }

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

    A obj = new A() {

      // 1) 익명 클래스는 생성자를 직접 정의할 수 없다.
      // - 그러나 컴파일러가 컴파일할 때 익명 클래스의 생성자를 만든다.
      //
      // 2) 대신 인스턴스 블록으로 생성자를 대신한다.
      // - 인스턴스 블록에 작성한 코드는
      //   결국 컴파일러가 자동 생성한 생성자에 들어간다.
      // - 그래서 인스턴스 블록에 작성한 코드가 실행될 것이다.
      {
        System.out.println("익명 클래스의 인스턴스 블록 실행!");
      }

      @Override
      public void print() {
        System.out.println("Hello!");
      }

    };
  }
}

// 수퍼 클래스 생성자 호출
// - 모든 클래스의 생성자는 항상 수퍼 클래스의 생성자를 먼저 호출한다.
// - 인터페이스를 구현한 익명 클래스는 java.lang.Object 클래스를 상속 받는다.
// - 따라서 익명 클래스의 생성자는 Object 클래스의 생성자를 먼저 호출한다.

 

 

anonymous class - 생성자 II
package com.eomcs.oop.ex11.e;

public class Exam0220 {

  static class A {
    public A() {
      System.out.println("A() 호출!");
    }
  }

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

    A obj = new A() {

      // 1) 익명 클래스는 생성자를 직접 정의할 수 없다.
      // - 그러나 컴파일러가 컴파일할 때 익명 클래스의 생성자를 만든다.
      //
      // 2) 대신 인스턴스 블록으로 생성자를 대신한다.
      // - 인스턴스 블록에 작성한 코드는
      //   결국 컴파일러가 자동 생성한 생성자에 들어간다.
      // - 그래서 인스턴스 블록에 작성한 코드가 실행될 것이다.
      //
      // 3) 물론 그전에 먼저 수퍼 클래스의 생성자가 실행될 것이다.
      {
        System.out.println("익명 클래스의 인스턴스 블록 실행!");
      }

      // 컴파일러는 익명 클래스의 생성자를 다음과 같이 추가할 것이다.
      //      A$1() {
      //        super();
      //        System.out.println("익명 클래스의 인스턴스 블록 실행!");
      //      }

    };
  }
}

// 수퍼 클래스 생성자 호출
// - 모든 클래스의 생성자는 항상 수퍼 클래스의 생성자를 먼저 호출한다.

 

 

anonymous class - 호출할 수퍼 클래스의 생성자 지정하기
package com.eomcs.oop.ex11.e;

public class Exam0230 {

  static class A {

    String name;

    public A() {
      System.out.println("A() 호출됨!");
      name = "이름 없음";
    }

    public A(String name) {
      System.out.println("A(String) 호출됨!");
      this.name = name;
    }

    public void print() {
      System.out.println(name);
    }
  }

  public static void main(String[] args) {

    // 익명 클래스를 정의할 때 호출할 수퍼 클래스의 생성자를 지정할 수 있다.
    // - 문법:
    //     new 수퍼클래스명(파라미터,...) {}
    // - 즉 생성자에 넘겨주는 파라미터로 호출될 생성자를 지정한다.

    // 1) 수퍼 클래스의 기본 생성자 호출하기
    // - 컴파일러는 익명 클래스에 기본 생성자를 추가한다.
    //
    A obj = new A() {};
    obj.print();

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

    // 2) 수퍼 클래스의 다른 생성자 호출하기
    // - 다음과 같이 익명 클래스의 인스턴스를 만들 때 값을 지정하면
    //   그 타입의 값을 받는 수퍼 클래스의 생성자가 호출된다.
    // - 컴파일러는 익명 클래스에 String 파라미터를 가진 생성자를 추가한다.
    //
    obj = new A("홍길동") {};
    obj.print();

    // 물론 다음과 같이 해당 타입의 값을 받을 생성자가 없으면
    // 컴파일 오류가 발생한다.
    //    obj = new A(100) {}; // 컴파일 오류!
  }
}

 

 

anonymous class - 수퍼 클래스의 메서드 오버라이딩 하기
package com.eomcs.oop.ex11.e;

public class Exam0310 {

  static class A {
    public void print() {
      System.out.println("반갑습니다!");
    }
  }

  public static void main(final String[] args) {
    A obj = new A() {
      @Override
      public void print() {
        System.out.println("정말 반갑습니다!");
      }
    };
    obj.print();
  }
}

 

 

anonymous class - 익명 클래스가 놓이는 장소: 스태틱 필드
package com.eomcs.oop.ex11.e;

public class Exam0410 {
  // 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
  interface A {
    void print();
  }

  // 스태틱 필드의 값을 준비할 때 익명 클래스를 사용할 수 있다.
  static A obj = new A() {
    @Override
    public void print() {
      System.out.println("Hello!");
    }
  };
}

 

 

anonymous class - 익명 클래스가 놓이는 장소: 인스턴스 필드
package com.eomcs.oop.ex11.e;

public class Exam0420 {
  // 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
  interface A {
    void print();
  }

  // 인스턴스 필드의 값을 준비할 때 익명 클래스를 사용할 수 있다.
  A obj = new A() {
    @Override
    public void print() {
      System.out.println("Hello!");
    }
  };
}

 

 

anonymous class - 익명 클래스가 놓이는 장소: 로컬 클래스
package com.eomcs.oop.ex11.e;

public class Exam0430 {
  // 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
  interface A {
    void print();
  }

  void m1() {
    // 로컬 변수의 값을 준비할 때 익명 클래스를 사용할 수 있다.
    A obj = new A() {
      @Override
      public void print() {
        System.out.println("Hello!");
      }
    };
    obj.print();
  }

  public static void main(String[] args) {
    Exam0430 r = new Exam0430();
    r.m1();
  }
}

 

 

anonymous class - 익명 클래스가 놓이는 장소: 파라미터
package com.eomcs.oop.ex11.e;

public class Exam0440 {
  // 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
  interface A {
    void print();
  }

  static void m1(A obj) {
    obj.print();
  }

  public static void main(String[] args) {

    // 1) 로컬 클래스 만들기
    class X implements A {
      @Override
      public void print() {
        System.out.println("XXXXX");
      }
    }
    m1(new X());

    // 2) 익명 클래스 만들기
    A obj = new A() {
      @Override
      public void print() {
        System.out.println("익명 클래스!!!");
      }
    };
    m1(obj);

    // 3) 익명 클래스를 파라미터 자리에 바로 삽입
    m1(new A() {
      @Override
      public void print() {
        System.out.println("안녕!!!");
      }
    });
  }
}

 

 

anonymous class - 익명 클래스가 놓이는 장소: 리턴
// anonymous class - 익명 클래스가 놓이는 장소: 리턴
package com.eomcs.oop.ex11.e;

class My {
  static void m1() {
    System.out.println("오호라!!!!");
  }

  void m2() {
    System.out.println("와우~~~~!");
  }
}

public class Exam0450 {
  // 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
  interface A {
    void print();
  }

  static A create0() {
    class X implements A {
      @Override
      public void print() {
        System.out.println("Hello0!");
      }
    }
    return new X();
  }

  static A create1() {
    A a = new A() {
      @Override
      public void print() {
        System.out.println("Hello1!");
      }
    };
    return a;
  }

  static A create2() {
    return new A() {
      @Override
      public void print() {
        System.out.println("Hello2!");
      }
    };
  }

  static A create3() {
    return () -> System.out.println("Hello3!");
  }

  static A create4() {
    return My::m1;  // 메서드 레퍼런스
    //    return () -> My.m1();
  }

  static A create5() {
    return new My()::m2;
  }

  public static void main(String[] args) {
    A obj0 = create0();
    obj0.print();

    A obj1 = create1();
    obj1.print();

    A obj2 = create2();
    obj2.print();

    A obj3 = create3();
    obj3.print();

    A obj4 = create4();
    obj4.print();

    A obj5 = create5();
    obj5.print();
  }
}

 

 

 

com.eomcs.oop.ex12

 

 

Lambda 문법 - 익명 클래스 vs 람다
package com.eomcs.oop.ex12;

public class Exam0110 {

  // 다음과 같이 추상 메서드가 한 개 있는 인터페이스를 "functional interface"라고 부른다.
  // => 이런 경우에 람다 문법을 사용할 수 있다.
  // => 인터페이스는 static 을 붙이지 않아도 static 멤버가 사용할 수 있다.
  interface Player {
    void play();
  }

  public static void main(String[] args) {

    // 익명 클래스로 인터페이스 구현하기
    Player p1 = new Player() {
      @Override
      public void play() {
        System.out.println("익명 클래스");
      }
    };
    p1.play();

    // 람다 문법으로 인터페이스 구현하기
    // => 메서드 한 개짜리 인터페이스를 좀 더 간결하게 구현하기 위해 만든 문법이다.
    // => 뻔한 코드 생략!
    Player p2 = () -> System.out.println("익명 클래스");
    p2.play();

  }
}

// 익명 클래스와 .class 파일 
// => 자바의 nested class 는 모두 별도의 .class 파일을 갖는다.
// => 위의 main()에 정의된 로컬 익명 클래스는 다음과 같은 이름의 .class 파일로 컴파일 된다.
//      Exam0110$1.class
//
// 람다와 .class 파일 
// => 람다는 별도의 .class 파일을 생성하지 않는다.
// => 컴파일러는 람다 코드를 해당 클래스의 스태틱 메서드로 정의한다.
// => 그리고 리플렉션 API를 사용하여 이 메서드를 호출하도록 변경한다. 
// => 람다 문법이 초기에 등장했을 때는 익명 클래스로 변환되었다.
// => 그러나 최근에는 메서드로 변환한 후 호출하는 방식으로 바뀌었다.
// => 예) 
// 원래의 자바코드:
//    Player p2 = () -> {
//      System.out.println("람다");
//    };
//
// 컴파일러가 변환한 코드:
//    private static synthetic void lambda$0();
//    0  getstatic java.lang.System.out : java.io.PrintStream [33]
//    3  ldc <String "람다"> [39]
//    5  invokevirtual java.io.PrintStream.println(java.lang.String) : void [41]
//    8  return
//      Line numbers:
//        [pc: 0, line: 27]
//        [pc: 8, line: 28]
//
// => 람다를 호출하는 코드는 자동 생성된 메서드를 호출하는 코드로 변환된다.

 

 

Lambda 문법 - 람다 body
package com.eomcs.oop.ex12;

public class Exam0120 {

  // "functional interface" = 추상 메서드가 한 개만 있는 인터페이스
  interface Player {
    void play();
  }

  public static void main(String[] args) {
    // 1) 한 문장일 때는 중괄호를 생략할 수 있다.
    Player p1 = () -> System.out.println("테스트1");
    p1.play();

    // 2) 물론 중괄호를 명확히 적어도 된다.
    Player p2 = () -> {
      System.out.println("테스트2");
    };
    p2.play();

    // => 파라미터가 없다고 괄호를 생략할 수는 없다.
    //    Player p3 = -> System.out.println("테스트3"); // 컴파일 오류!
  }
}

 

 

Lambda 문법 - 람다 파라미터 I
package com.eomcs.oop.ex12;

public class Exam0130 {

  interface Player {
    void play(String name);
  }

  public static void main(String[] args) {
    // 1) 파라미터는 괄호() 안에 선언한다.
    Player p1 = (String name) -> System.out.println(name + " 님 환영합니다.");
    p1.play("홍길동");

    // 2) 파라미터 타입을 생략할 수 있다.
    Player p2 = (name) -> System.out.println(name + " 님 환영합니다.");
    p2.play("홍길동");

    // 3) 파라미터가 한 개일 때는 괄호도 생략할 수 있다.
    Player p3 = name -> System.out.println(name + " 님 환영합니다.");
    p3.play("홍길동");
  }
}

 

 

Lambda 문법 - 람다 파라미터 II
package com.eomcs.oop.ex12;

public class Exam0140 {

  interface Player {
    void play(String name, int age);
  }

  public static void main(String[] args) {
    // 1) 파라미터는 괄호() 안에 선언한다.
    Player p1 = (String name, int age) -> System.out.printf("%s(%d)님 환영합니다.\n", name, age);
    p1.play("홍길동", 20);

    // 2) 파라미터 타입을 생략할 수 있다.
    Player p2 = (name, age) -> System.out.printf("%s(%d)님 환영합니다.\n", name, age);
    p2.play("임꺽정", 30);

    // 3) 파라미터가 여러 개일 때는 괄호를 생략할 수 없다.
    //    Player p3 = name, age -> System.out.printf("%s(%d)님 환영합니다.\n", name, age);
    //    p3.play("임꺽정", 30);
  }
}

 

 

Lambda 문법 - 람다 리턴
package com.eomcs.oop.ex12;

public class Exam0150 {

  interface Calculator {
    int compute(int a, int b);
  }

  public static void main(String[] args) {
    // 1) 리턴 값은 return 명령을 사용하여 처리한다.
    Calculator c1 = (a, b) -> {
      return a + b;
    };
    System.out.println(c1.compute(10, 20));

    // 2) 한 문장으로 된 표현식(=값을 리턴하는 한 문장의 코드)인 경우 괄호 생략할 수 있다.
    // => 문장은 문장인데 값을 리턴하는 문장을 '표현식(expression)' 이라 부른다.
    // => 단 괄호를 생략할 때 return 키워드도 생략해야 한다. 있으면 컴파일 오류!
    Calculator c2 = (a, b) -> a + b;
    System.out.println(c2.compute(10, 20));

    // Math.max()는 int 값을 리턴한다. 그래서 이 메서드를 호출하는 문장은 표현식이다.
    Calculator c3 = (a, b) -> Math.max(a, b);
    System.out.println(c3.compute(10, 20));

    // 값을 리턴해야 하는데 람다 문장에서 값을 리턴하지 않으면 컴파일 오류!
    //    Calculator c4 = (a, b) -> System.out.println(a + ",", b); // 컴파일 오류!
    //    System.out.println(c4.compute(10, 20));
  }

}

 

 

lambda 문법 : 익명 클래스를 사용할 수 있는 곳에는 모두 람다 사용 가능
package com.eomcs.oop.ex12;

public class Exam0160 {
  interface A {
    void print();
  }

  // 스태틱 필드
  static A obj1 = () -> System.out.println("스태틱 필드");

  //인스턴스 필드
  A obj2 = () -> System.out.println("인스턴스 필드");

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

    // 로컬 변수
    A obj3 = () -> System.out.println("로컬 변수!");

    // 파라미터
    m1(() -> System.out.println("파라미터"));

    // 리턴 값
    A obj4 = m2();
  }

  static void m1(final A obj) {
    obj.print();
  }

  static A m2() {
    // 리턴 문장
    return () -> System.out.println("리턴 문장");
  }
}

 

 

Lambda 문법 - functional interface의 자격
package com.eomcs.oop.ex12;

public class Exam0210 {

  // 추상 메서드가 한 개짜리 인터페이스여야 한다.
  interface Player {
    void play();
  }

  public static void main(String[] args) {

    // 추상 메서드를 한 개만 갖고 있는 인터페이스에 대해
    // 람다 문법으로 익명 클래스를 만들 수 있다.
    Player p = () -> System.out.println("Player...");
    p.play();
  }
}

 

 

package com.eomcs.oop.ex12;

public class Exam0220 {

  // 추상 메서드가 두 개 이상이면 람다 문법으로 구현할 수 없다.
  interface Player {
    void play();
    void stop();
  }

  public static void main(String[] args) {
    // 추상 메서드가 두 개 이상인 경우 람다 문법을 사용할 수 없다.
    //    Player p = () -> System.out.println("Player..."); // 컴파일 오류!
  }

}

 

 

package com.eomcs.oop.ex12;

public class Exam0230 {

  // 여러 개의 메서드가 있다 하더라도 추상 메서드가 한 개이면 된다.
  interface Player {

    static String info() {
      return "Player입니다.";
    }

    default void stop() {}

    void play();
  }

  public static void main(String[] args) {
    // static 메서드나 default 메서드가 몇개이든 그 개수는 중요하지 않다.
    // 추상 메서드가 한 개이면 람다 문법을 사용할 수 있다.
    Player p = () -> System.out.println("Player...");
    p.play();
    System.out.println(Player.info());
  }
}

 

 

package com.eomcs.oop.ex12;

public class Exam0240 {

  static abstract class Player {
    public abstract void play();
  }

  public static void main(String[] args) {
    // 인터페이스가 아닌 추상 클래스는 람다 구현의 대상이 아니다!
    //    Player p = () -> System.out.println("Player..."); // 컴파일 오류!
  }

}

 

 

아규먼트에 람다(lambda) 활용
package com.eomcs.oop.ex12;

public class Exam0310 {

  static interface Player {
    void play();
  }

  static void testPlayer(Player p) {
    p.play();
  }

  public static void main(String[] args) {
    // 로컬 클래스
    class MyPlayer implements Player {
      @Override
      public void play() {
        System.out.println("실행!");
      }
    }

    testPlayer(new MyPlayer());

  }
}

 

 

package com.eomcs.oop.ex12;

public class Exam0311 {

  static interface Player {
    void play();
  }

  static void testPlayer(Player player) {
    player.play();
  }

  public static void main(String[] args) {
    // 간단한 인터페이스 구현체 조차
    // 다음과 같이 따로 정의하여 사용하면 매우 불편하다.
    Player player = new Player() {
      @Override
      public void play() {
        System.out.println("실행~~~~");
      }
    };

    testPlayer(player);
  }

}

 

 

package com.eomcs.oop.ex12;

public class Exam0312 {

  static interface Player {
    void play();
  }

  static void testPlayer(Player player) {
    player.play();
  }

  public static void main(String[] args) {
    // 아규먼트 부분에 바로 익명 클래스를 정의하는 코드를 두면
    // 코드를 해석하기가 편하다.
    testPlayer(new Player() {
      @Override
      public void play() {
        System.out.println("실행~~~~");
      }
    });
  }

}

 

 

package com.eomcs.oop.ex12;

public class Exam0313 {

  static interface Player {
    void play();
  }

  static void testPlayer(Player player) {
    player.play();
  }

  public static void main(String[] args) {
    // 아규먼트 자리에 lambda 문법을 사용하면 더 편하다!
    testPlayer(() -> System.out.println("실행~~~~"));
  }

}

 

 

아규먼트에 람다(lambda) 활용 II - 파라미터와 리턴 값이 있는 람다 만들기
package com.eomcs.oop.ex12;

public class Exam0320 {

  static interface Calculator {
    int compute(int a, int b);
  }

  static void test(Calculator c) {
    System.out.println(c.compute(100, 200));
  }

  public static void main(String[] args) {

    // 람다
    // 파라미터와 리턴 값이 있는 메서드 구현하기
    test((a, b) -> a + b);

  }

}

 

 

package com.eomcs.oop.ex12;

public class Exam0321 {

  static interface Calculator {
    int compute(int a, int b);
  }

  static void test(Calculator c) {
    System.out.println(c.compute(100, 200));
  }

  public static void main(String[] args) {
    // 익명 클래스로 정의한다면?
    test(new Calculator() {
      @Override
      public int compute(int a, int b) {
        return a + b;
      }
    });
  }

}

 

 

아규먼트에 람다(lambda) 활용 III - 여러 개의 문장이 있는 경우
package com.eomcs.oop.ex12;

public class Exam0330 {

  interface Calculator {
    int compute(int a, int b);
  }

  static void test(Calculator c) {
    System.out.println(c.compute(100, 200));
  }

  public static void main(String[] args) {
    // 여러 문장을 실행하는 경우 블록 {}으로 감싸라! (블록을 생략할 수 없다.)
    test((a, b) -> {
      int sum = 0;
      for (int i = a; i <= b; i++) {
        sum += i;
      }
      return sum;
    });
  }

}

 

 

package com.eomcs.oop.ex12;

public class Exam0331 {

  interface Calculator {
    int compute(int a, int b);
  }

  static void test(Calculator c) {
    System.out.println(c.compute(100, 200));
  }

  public static void main(String[] args) {
    // 익명 클래스로 정의한다면?
    test(new Calculator() {
      @Override
      public int compute(int a, int b) {
        int sum = 0;
        for (int i = a; i <= b; i++) {
          sum += i;
        }
        return sum;
      }
    });
  }

}

 

 

리턴 문장에 람다(lambda) 활용
package com.eomcs.oop.ex12;

public class Exam0410 {

  static interface Interest {
    double compute(int money);
  }

  // 팩토리 메서드
  // => Interest 구현체를 생성하여 리턴하는 메서드
  // 
  static Interest getInterest(final double rate) {
    // 로컬 클래스로 인터페이스 구현한 후 객체 리턴하기
    class InterestImpl implements Interest {
      double rate;

      public InterestImpl(double rate) {
        this.rate = rate;
      }

      @Override
      public double compute(int money) {
        return money + (money * rate / 100);
      }
    }
    return new InterestImpl(rate);
  }

  public static void main(String[] args) {
    Interest i1 = getInterest(1.5);
    System.out.printf("금액: %.2f\n", i1.compute(1_0000_0000));

    Interest i2 = getInterest(2.5);
    System.out.printf("금액: %.2f\n", i2.compute(1_0000_0000));
  }

}

 

 

package com.eomcs.oop.ex12;

public class Exam0411 {

  static interface Interest {
    double compute(int money);
  }

  static Interest getInterest(final double rate) {
    // 로컬 클래스로 인터페이스 구현한 후 객체 리턴하기
    class InterestImpl implements Interest {
      // 로컬(또는 익명 로컬) 클래스 안에서 바깥 메서드의 로컬 변수를 사용하면
      // 컴파일러는 자동으로 그 값을 보관하기 위해
      // 필드를 추가한다.
      // 또한 그 값을 받을 수 있도록 생성자를 변경한다.
      // 따라서 개발자가 직접 필드나 생성자를 정의할 필요가 없다.
      //
      @Override
      public double compute(int money) {
        return money + (money * rate / 100);
      }
    }
    // 또한 개발자는 메서드의 로컬 변수 값을
    // 로컬 클래스의 생성자에 전달하기 위해 직접 작성할 필요가 없다.
    // 컴파일러가 자동으로 추가한다.
    //
    return new InterestImpl();
  }

  public static void main(String[] args) {
    Interest i1 = getInterest(1.5);
    System.out.printf("금액: %.2f\n", i1.compute(1_0000_0000));

    Interest i2 = getInterest(2.5);
    System.out.printf("금액: %.2f\n", i2.compute(1_0000_0000));
  }

}

 

 

package com.eomcs.oop.ex12;

public class Exam0412 {

  static interface Interest {
    double compute(int money);
  }

  static Interest getInterest(final double rate) {
    // 익명 클래스로 인터페이스 구현한 후 객체 리턴하기
    // => 객체를 한 개만 생성할 것이라면 익명 클래스로 정의하라.
    Interest i = new Interest() {
      @Override
      public double compute(int money) {
        return money + (money * rate / 100);
      }
    };
    return i;
  }

  public static void main(String[] args) {
    Interest i1 = getInterest(1.5);
    System.out.printf("금액: %.2f\n", i1.compute(1_0000_0000));

    Interest i2 = getInterest(2.5);
    System.out.printf("금액: %.2f\n", i2.compute(1_0000_0000));
  }

}

 

 

package com.eomcs.oop.ex12;

public class Exam0413 {

  static interface Interest {
    double compute(int money);
  }

  static Interest getInterest(final double rate) {
    // 익명 클래스로 인터페이스 구현한 후 객체 리턴하기
    return new Interest() {
      @Override
      public double compute(int money) {
        return money + (money * rate / 100);
      }
    };
  }

  public static void main(String[] args) {
    Interest i1 = getInterest(1.5);
    System.out.printf("금액: %.2f\n", i1.compute(1_0000_0000));

    Interest i2 = getInterest(2.5);
    System.out.printf("금액: %.2f\n", i2.compute(1_0000_0000));
  }

}

 

 

package com.eomcs.oop.ex12;

public class Exam0414 {

  static interface Interest {
    double compute(int money);
  }

  static Interest getInterest(final double rate) {
    // 람다 문법으로 인터페이스 구현한 후 객체 리턴하기
    return money -> money + (money * rate / 100);
  }

  public static void main(String[] args) {
    Interest i1 = getInterest(1.5);
    System.out.printf("금액: %.2f\n", i1.compute(1_0000_0000));

    Interest i2 = getInterest(2.5);
    System.out.printf("금액: %.2f\n", i2.compute(1_0000_0000));
  }

}

 

 

메서드 레퍼런스 - 스태틱 메서드 레퍼런스
package com.eomcs.oop.ex12;

public class Exam0510 {

  static class MyCalculator {
    public static int plus(int a, int b) {return a + b;}
    public static int minus(int a, int b) {return a - b;}
    public static int multiple(int a, int b) {return a * b;}
    public static int divide(int a, int b) {return a / b;}
  }

  interface Calculator {
    int compute(int x, int y);
  }

  public static void main(String[] args) {
    // 메서드 한 개짜리 인터페이스의 구현체를 만들 때,

    // 1) 익명 클래스 활용
    Calculator obj1 = new Calculator() {
      @Override
      public int compute(int x, int y) {
        return x * y;
      }
    };

    // 2) 람바 문법 활용
    Calculator obj2 = (x, y) -> x * y;

    // 3) 기존에 작성한 클래스의 스태틱 메서드를 재활용하기
    // => 인터페이스의 메서드 규격과 일치하는 메서드가 있다면,
    //    그 메서드를 람다 구현체로 대체할 수 있다.
    // => 새로 코드를 작성할 필요가 없어 매우 편리하다.
    // => 규격? 메서드의 파라미터 타입/개수/순서, 리턴 타입
    // => 문법:
    //    클래스명::메서드명
    Calculator c1 = MyCalculator::plus;  // MyCalculator의 스태틱 메서드인 plus()를 가지고 구현체를 자동 생성!
    Calculator c2 = MyCalculator::minus;
    Calculator c3 = MyCalculator::multiple;
    Calculator c4 = MyCalculator::divide;

    System.out.println(c1.compute(200, 17)); // compute() ==> plus()
    System.out.println(c2.compute(200, 17)); // compute() ==> minus()
    System.out.println(c3.compute(200, 17)); // compute() ==> multiple()
    System.out.println(c4.compute(200, 17)); // compute() ==> divide()
  }
}

 

 

메서드 레퍼런스 - 스태틱 메서드 레퍼런스 구현 원리
package com.eomcs.oop.ex12;

public class Exam0520 {

  static class MyCalculator {
    public static int plus(int a, int b) {
      return a + b;
    }

    public static int minus(int a, int b) {
      return a - b;
    }

    public static int multiple(int a, int b) {
      return a * b;
    }

    public static int divide(int a, int b) {
      return a / b;
    }

    public static int power(int a) {
      return a * 2;
    }
  }

  interface Calculator {
    int compute(int a, int b);
  }

  public static void main(String[] args) {
    // 스태틱 메서드 레퍼런스로 Calculator 구현체를 만드는 방법
    //
    Calculator c01 = MyCalculator::plus;
    Calculator c02 = MyCalculator::minus;
    Calculator c03 = MyCalculator::multiple;
    Calculator c04 = MyCalculator::divide;
    // Calculator c05 = MyCalculator::power; // 해당 메서드를 가지고 구현체를 만들 수 없다.

    // 위의 코드는 내부적으로 다음과 같다.
    //
    Calculator c1 = new Calculator() {
      @Override
      public int compute(int a, int b) {
        // 기존 메서드가 메서드 레퍼런스로 전달 가능한지 여부는
        // 다음 코드를 참고하라.
        // 인터페이스에 정의된 메서드(예: compute())가 호출되었을 때,
        //
        // 그 파라미터 값은 메서드 레퍼런스로 지정된
        // 스태틱 메서드(예: plus())에게 전달될 것이다.
        // => 그래서 스태틱 메서드의 파라미터는 항상
        //    인터페이스 메서드에 정의된 파라미터 값을 받을 수 있어야 한다.
        //
        // 스태틱 메서드의 리턴 값은
        // 인터페이스 메서드에 정의된 대로 리턴할 수 있어야 한다.
        // => 그래서 스태틱 메서드의 리턴 타입은
        //    인터페이스 메서드의 리턴 타입과 일치하거나
        //    그 타입으로 바꿀 수 있어야 한다.
        //
        return MyCalculator.plus(a, b);
      }
    };

    System.out.println(c1.compute(200, 17));
  }
}

 

 

메서드 레퍼런스 - 스태틱 메서드 레퍼런스
package com.eomcs.oop.ex12;

public class Exam0530 {

  static class MyCalculator {
    public static int plus(int a, int b) {
      return a + b;
    }

    public static int minus(int a, int b) {
      return a - b;
    }

    public static int multiple(int a, int b) {
      return a * b;
    }

    public static int divide(int a, int b) {
      return a / b;
    }
  }

  interface Calculator1 {
    double compute(int a, int b);
  }

  interface Calculator2 {
    float compute(int a, int b);
  }

  interface Calculator3 {
    short compute(int a, int b);
  }

  interface Calculator4 {
    void compute(int a, int b);
  }

  interface Calculator5 {
    Object compute(int a, int b);
  }

  interface Calculator6 {
    String compute(int a, int b);
  }

  public static void main(String[] args) {

    // 리턴 타입 int ===> double
    Calculator1 c1 = MyCalculator::plus; // OK!
    // 위 문장은 다음 문장과 같다.
    //    Calculator1 c1 = new Calculator1() {
    //      @Override
    //      public double compute(int a, int b) {
    //        return MyCalculator.plus(a, b);
    //      }
    //    };
    System.out.println(c1.compute(100, 200));

    // 리턴 타입 int ===> float
    Calculator2 c2 = MyCalculator::plus; // OK!
    System.out.println(c2.compute(100, 200));



    // 리턴 타입 int ===> short
    //    Calculator3 c3 = MyCalculator::plus; // 컴파일 오류!
    // 위 문장은 다음과 같다.
    //    Calculator3 c3 = new Calculator3() {
    //      @Override
    //      public short compute(int a, int b) {
    //        return MyCalculator.plus(a, b); // 컴파일 오류!
    //      }
    //    };


    // 리턴 타입 int ===> void
    Calculator4 c4 = MyCalculator::plus; // OK!
    // 위 문장은 다음과 같다.
    //    Calculator4 c4 = new Calculator4() {
    //      @Override
    //      public void compute(int a, int b) {
    //        MyCalculator.plus(a, b); // OK!
    //      }
    //    };
    c4.compute(100, 200); // plus() 메서드의 리턴 값은 무시한다.

    // 리턴 타입 int ===> Object
    Calculator5 c5 = MyCalculator::plus; // OK!
    // 위 문장은 다음과 같다.
    //    Calculator5 c5 = new Calculator5() {
    //      @Override
    //      public Object compute(int a, int b) {
    //        return MyCalculator.plus(a, b); // OK! 
    //        //이유? plus()가 리턴한 int 값이 오토박싱 되기 때문이다.
    //      }
    //    };
    System.out.println(c5.compute(100, 200));

    // 리턴 타입 int ===> String
    //    Calculator6 c6 = MyCalculator::plus; // 컴파일 오류!
    // 위 문장은 다음과 같다.
    //    Calculator6 c6 = new Calculator6() {
    //      @Override
    //      public String compute(int a, int b) {
    //        return MyCalculator.plus(a, b); // 컴파일 오류!
    //      }
    //    };


    // => 메서드 레퍼런스를 지정할 때 리턴 타입의 규칙:
    // 1) 같은 리턴 타입
    // 2) 암시적 형변환 가능한 타입
    // 3) auto-boxing 가능한 타입
    // 4) void
    // 결론,
    // 메서드 레퍼런스가 가리키는 실제 메서드를 호출한 후
    // 그 메서드가 리턴한 값이
    // 인터페이스에 정의된 메서드의 리턴 값으로 사용할 수 있다면
    // 문제가 없다.
    //
  }
}

 

 

package com.eomcs.oop.ex12;

public class Exam0540 {

  static class MyCalculator {
    public static int plus(int a, int b) {
      return a + b;
    }

    public static int minus(int a, int b) {
      return a - b;
    }

    public static int multiple(int a, int b) {
      return a * b;
    }

    public static int divide(int a, int b) {
      return a / b;
    }
  }

  static interface Calculator1 {
    int compute(byte a, byte b);
  }

  static interface Calculator2 {
    int compute(short a, short b);
  }

  static interface Calculator3 {
    int compute(long a, long b);
  }

  static interface Calculator4 {
    int compute(float a, float b);
  }

  static interface Calculator5 {
    int compute(Object a, Object b);
  }

  static interface Calculator6 {
    int compute(String a, String b);
  }

  static interface Calculator7 {
    int compute(Integer a, Integer b);
  }

  static interface Calculator8 {
    int compute(int a);
  }

  static interface Calculator9 {
    int compute(int a, int b, int c);
  }

  public static void main(String[] args) {

    // 파라미터 타입: byte, byte ===> int, int
    Calculator1 c1 = MyCalculator::plus; // OK!
    //
    //    Calculator1 xx = new Calculator1() {
    //      @Override
    //      public int compute(byte a, byte b) {
    //        return MyCalculator.plus(a, b); // OK!
    //      }
    //    };

    // 파라미터 타입: short, short ===> int, int
    Calculator2 c2 = MyCalculator::plus; // OK!
    //
    //    Calculator2 c2 = new Calculator2() {
    //      @Override
    //      public int compute(short a, short b) {
    //        return MyCalculator.plus(a, b); // OK!
    //      }
    //    };

    // 파라미터 타입: long, long ===> int, int
    //    Calculator3 c3 = MyCalculator::plus; // 컴파일 오류!
    //
    //    Calculator3 c3 = new Calculator3() {
    //      @Override
    //      public int compute(long a, long b) {
    //        return MyCalculator.plus(a, b); // 컴파일 오류!
    //      }
    //    };

    // 파라미터 타입: float, float ===> int, int
    // Calculator4 c4 = MyCalculator::plus; // 컴파일 오류!

    // 파라미터 타입: Object, Object ===> int, int
    // Calculator5 c5 = MyCalculator::plus; // 컴파일 오류!

    // 파라미터 타입: String, String ===> int, int
    // Calculator6 c6 = MyCalculator::plus; // 컴파일 오류!

    // 파라미터 타입: Integer, Integer ===> int, int
    Calculator7 c7 = MyCalculator::plus; // OK
    //
    //    Calculator7 c7 = new Calculator7() {
    //      @Override
    //      public int compute(Integer a, Integer b) {
    //        return MyCalculator.plus(a, b); // OK!
    //        // 이유? 오토언박싱 때문이다. 
    //        // MyCalculator.plus(a.intValue(), b.intValue()) 코드로 변경된다.
    //      }
    //    };

    // 파라미터 타입: int ===> int, int
    //    Calculator8 c8 = MyCalculator::plus; // 컴파일 오류!
    //
    //    Calculator8 c8 = new Calculator8() {
    //      @Override
    //      public int compute(int a) {
    //        return MyCalculator.plus(a, ?); // 컴파일 오류!
    //        // compute()는 int 값 한 개만 받는데, plus()는 int 값 두 개를 요구한다.
    //      }
    //    };

    // 파라미터 타입: int, int, int ===> int, int
    //Calculator9 c9 = MyCalculator::plus; // 컴파일 오류!
    //
    //    Calculator9 c9 = new Calculator9() {
    //      @Override
    //      public int compute(int a, int b, int c) {
    //        return MyCalculator.plus(a, b, c); // 컴파일 오류!
    //        // compute()는 int 값 세 개를 받아서 plus()에 세 개 모두 전달한다. 
    //        // 그러나 plus()는 int 파라미터가 두 개만 있다.
    //      }
    //    };

    // 메서드 레퍼런스를 지정할 때 파라미터 타입 규칙:
    // => 인터페이스 규칙에 따라 받은 값을
    //    실제 메서드에 그대로 전달할 수 있다면 가능하다.
  }
}

 

 

메서드 레퍼런스 - 인스턴스 메서드 레퍼런스
package com.eomcs.oop.ex12;

public class Exam0610 {

  static class Calculator {
    double rate;

    public Calculator(double rate) {
      this.rate = rate;
    }

    public double year(int money) {
      return money * rate / 100;
    }

    public double month(int money) {
      return money * rate / 100 / 12;
    }

    public double day(int money) {
      return money * rate / 100 / 365;
    }
  }

  static interface Interest {
    double compute(int money);
  }

  public static void main(String[] args) {
    // 메서드 한 개짜리 인터페이스의 구현체를 만들 때
    // 기존 인스턴스 메서드를 람다 구현체로 사용할 수 있다.
    // => 단 인터페이스에 선언된 메서드의 규격과 일치해야 한다.
    // => 보통 특정 인스턴스 값을 가지고 작업해야 할 경우에 이 방식을 사용한다.
    // => 규격? 파라미터 타입 및 개수, 리턴 타입
    // => 문법:
    // 인스턴스::메서드명
    //
    Calculator 보통예금 = new Calculator(0.5);
    Calculator 정기예금 = new Calculator(1.5);
    Calculator 청년행복예금 = new Calculator(10);

    System.out.println("[보통예금]");

    Interest i1 = 보통예금::year; // 인스턴스 메서드를 메서드 레퍼런스로 지정한다.
    //
    // 람다 문법으로 표현하면:
    //    Interest i1 = money -> 보통예금.year(money);

    System.out.printf("년 이자: %.1f\n", i1.compute(10_0000_0000));

    i1 = 보통예금::month;
    System.out.printf("월 이자: %.1f\n", i1.compute(10_0000_0000));

    i1 = 보통예금::day;
    System.out.printf("일 이자: %.1f\n", i1.compute(10_0000_0000));

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

    System.out.println("[정기예금]");
    Interest i2 = 정기예금::year;
    System.out.printf("년 이자: %.1f\n", i2.compute(10_0000_0000));

    i2 = 정기예금::month;
    System.out.printf("월 이자: %.1f\n", i2.compute(10_0000_0000));

    i2 = 정기예금::day;
    System.out.printf("일 이자: %.1f\n", i2.compute(10_0000_0000));
  }
}

 

 

메서드 레퍼런스 - 인스턴스 메서드 레퍼런스 구현 원리
package com.eomcs.oop.ex12;

public class Exam0620 {

  static class Calculator {
    double rate;

    public Calculator(double rate) {
      this.rate = rate;
    }

    public double year(int money) {
      return money * rate / 100;
    }

    public double month(int money) {
      return money * rate / 100 / 12;
    }

    public double day(int money) {
      return money * rate / 100 / 365;
    }
  }

  static interface Interest {
    double compute(int money);
  }

  public static void main(String[] args) {

    Calculator 보통예금 = new Calculator(0.5);

    // 인스턴스 메서드 레퍼런스로 Calculator 구현체를 만드는 방법
    //
    // Iterest i1 = 보통예금::year;

    // 위의 코드는 내부적으로 다음과 같다.
    // 1) 람다 표현
    //    Interest i1 = money -> 보통예금.year(money);
    //
    // 2) 익명 클래스
    Interest i1 = new Interest() {
      @Override
      public double compute(int money) {
        // 인스턴스 메서드 레퍼런스는 실제
        // 인터페이스 구현체에서 다음과 같이 메서드로 호출된다.
        return 보통예금.year(money);
      }
    };

    System.out.printf("년 이자: %.1f\n", i1.compute(10_0000_0000));
  }
}

 

 

메서드 레퍼런스 - 활용예
package com.eomcs.oop.ex12;

import java.util.function.Predicate;

public class Exam0630 {

  public static void main(String[] args) {

    // Predicate<String> 인터페이스 구현체 준비하기

    // 1) 로컬 클래스로 인터페이스 구현체 만들기
    class MyPredicate<T> implements Predicate<T> {
      @Override
      public boolean test(T value) {
        return ((String)value).isEmpty();
      }
    }
    Predicate<String> p1 = new MyPredicate<>();

    // 2) 익명 클래스로 인터페이스 구현체 만들기
    Predicate<String> p2 = new Predicate<>() {
      @Override
      public boolean test(String value) {
        return value.isEmpty();
      }
    };

    // 3) 람다로 인터페이스 구현체 만들기
    Predicate<String> p3 = value -> value.isEmpty();

    // 4) 메서드 레퍼런스를 사용하여 기존 클래스의 메서드를 인터페이스 구현체로 사용하기
    // => 의미: "Predicate 인터페이스 구현체로서 String의 isEmpty()를 사용하겠다"
    Predicate<String> p4 = String::isEmpty;
  }

}

 

 

메서드 레퍼런스 - 생성자 레퍼런스
package com.eomcs.oop.ex12;

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

public class Exam0710 {

  interface ListFactory {
    List create();
  }

  public static void main(String[] args) {

    // 인터페이스에 정의된 메서드가
    // 생성자의 형식과 일치하다면
    // 메서드 레퍼런스로 생성자를 지정할 수 있다.
    //
    // 1) 익명 클래스로 인터페이스 구현
    //    ListFactory f1 = new ListFactory() {
    //      public List create() {
    //        return new ArrayList();
    //      }
    //    };

    // 2) 람다 문법으로 인터페이스 구현
    //    ListFactory f1 = () -> new ArrayList();

    // 3) 메서드 레퍼런스로 인터페이스 구현 
    ListFactory f1 = ArrayList::new;

    // 인터페이스의 메서드를 호출하면
    // 지정된 클래스의 인스턴스를 만든 후 생성자를 호출한다.
    List list = f1.create(); // new ArrayList();

    System.out.println(list instanceof ArrayList);
    System.out.println(list.getClass().getName());
  }
}

 

 

메서드 레퍼런스 - 생성자 레퍼런스 구현 원리
package com.eomcs.oop.ex12;

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

public class Exam0720 {

  static interface ListFactory {
    List create();
  }

  public static void main(String[] args) {

    // 생성자 레퍼런스를 지정하는 것은
    // 다음과 같이 익명 클래스를 만드는 것과 같다.
    // => ListFactory f1 = ArrayList::new;

    ListFactory f1 = new ListFactory() {
      @Override
      public List create() {
        return new ArrayList();
      }
    };

    List list = f1.create();

    System.out.println(list instanceof ArrayList);
    System.out.println(list.getClass().getName());
  }
}

 

 

메서드 레퍼런스 - 생성자 레퍼런스
package com.eomcs.oop.ex12;

public class Exam0730 {

  static class Message {
    String name;

    public Message() {
      this.name = "이름없음";
    }

    public Message(String name) {
      this.name = name;
    }

    public void print() {
      System.out.printf("%s님 반갑습니다!\n", name);
    }
  }

  static interface Factory1 {
    Message get();
  }

  static interface Factory2 {
    Message get(String name);
  }

  public static void main(String[] args) {
    // 생성자 레퍼런스를 지정할 때,
    // 인터페이스 메서드의 파라미터에 따라 호출할 생성자가 결정된다.

    Factory1 f1 = Message::new; // Message() 생성자를 가리킨다.
    //    Factory1 f1 = () -> new Message(); // 위의 문장과 같다.

    Factory2 f2 = Message::new; // Message(String) 생성자를 가리킨다.
    //    Factory2 f2 = n -> new Message(n); // 위의 문장과 같다.

    Message msg = f1.get(); // ==> new Message()
    msg.print();

    msg = f2.get("홍길동"); // ==> new Message("홍길동")
    msg.print();

  }
}

 

 

package com.eomcs.oop.ex12;

public class Exam0740 {

  static class Message {
    String name;

    public Message() {
      this.name = "이름없음";
    }

    public Message(String name) {
      this.name = name;
    }

    public void print() {
      System.out.printf("%s님 반갑습니다!\n", name);
    }
  }

  static interface Factory1 {
    Message get();
  }

  static interface Factory2 {
    Message get(String name);
  }

  static interface Factory3 {
    Message get(String name, int age);
  }

  public static void main(String[] args) {

    Factory1 f1 = Message::new; // Message() 생성자를 가리킨다.
    //=> 즉 컴파일러는 Message 의 기본 생성자를 호출하는 Factory 구현체를 만들어 리턴한다.
    //
    //    Factory1 f1 = new Factory1() {
    //      @Override
    //      public Message get() {
    //        return new Message();
    //      }
    //    };

    Factory2 f2 = Message::new; // Message(String) 생성자를 가리킨다.
    //=> 즉 컴파일러는 Message 의 생성자 중에서 
    //   String을 파라미터 받는 생성자를 호출하는 Factory 구현체를 만들어 리턴한다.
    // 
    //    Factory2 f2 = new Factory2() {
    //      @Override
    //      public Message get(String name) {
    //        return new Message(name);
    //      }
    //    };

    // Factory(String,int) 생성자가 없기 때문에 컴파일 오류!
    //    Factory3 f3 = Message::new; // 컴파일 오류!
    //=> 즉 컴파일러는 Message 의 생성자 중에서 
    //   String과 int를 파라미터 받는 생성자를 호출하는 Factory 구현체를 만들어야 하는데,
    //   Message 클래스에는 String과 int를 파라미터로 받는 생성자가 없기 때문에 
    //   Factory 구현체를 만들 수 없다.
    //   그래서 컴파일 오류가 발생한다!

  }
}

 

 

메서드 레퍼런스 - 생성자 레퍼런스 활용
package com.eomcs.oop.ex12;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Supplier;

public class Exam0750 {

  @SuppressWarnings("unchecked")
  static <T> Collection<T> prepareNames(Supplier<Collection<T>> factory, T... values) {
    Collection<T> list = factory.get(); // => new ArrayList<String>()
    for (T value : values) {
      list.add(value);
    }
    return list;
  }

  static <T> void print(Iterator<T> i) {
    while (i.hasNext()) {
      System.out.print(i.next() + ",");
    }
    System.out.println();
  }

  public static void main(String[] args) {
    // '람다'는 새로 메서드를 구현해야 하지만,
    // '메서드 레퍼런스'는 기존 클래스의 메서드를 재활용 할 수 있다.
    //

    // 생성자 레퍼런스
    // 문법:
    // => 클래스명::new
    //
    // 인터페이스 구현체를 직접 만들지 않고 기존에 존재하는 메서드를 구현체로 사용하는 문법이 
    // "메서드 레퍼런스" 이다.
    // => 스태틱 메서드 레퍼런스, 인스턴스 메서드 레퍼런스, 생성자 레퍼런스 
    Collection<String> c1 = prepareNames(ArrayList<String>::new, 
        "홍길동", "임꺽정", "유관순", "임꺽정");
    System.out.println("------------------------");

    Collection<String> c2 = prepareNames(HashSet<String>::new, 
        "홍길동", "임꺽정", "유관순", "임꺽정");
    print(c2.iterator());

  }

}

 

 

package com.eomcs.oop.ex12;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Supplier;

public class Exam0751 {

  @SuppressWarnings("unchecked")
  static <T> Collection<T> prepareNames(Supplier<Collection<T>> factory, T... values) {
    Collection<T> list = factory.get(); // => new ArrayList<String>()
    for (T value : values) {
      list.add(value);
    }
    return list;
  }

  static <T> void print(Iterator<T> i) {
    while (i.hasNext()) {
      System.out.print(i.next() + ",");
    }
    System.out.println();
  }

  public static void main(String[] args) {
    // 1) 로컬 클래스 사용하여 Supplier 구현체 만들기
    class MySupplier<T> implements Supplier<T> {
      @Override
      public T get() {
        return (T) new ArrayList<>();
      }
    };
    Supplier<Collection<String>> supplier = new MySupplier<Collection<String>>();
    Collection<String> list = prepareNames(supplier, "홍길동", "임꺽정", "유관순", "임꺽정");
    print(list.iterator());

  }

}

 

 

package com.eomcs.oop.ex12;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Supplier;

public class Exam0752 {

  @SuppressWarnings("unchecked")
  static <T> Collection<T> prepareNames(Supplier<Collection<T>> factory, T... values) {
    Collection<T> list = factory.get(); // => new ArrayList<String>()
    for (T value : values) {
      list.add(value);
    }
    return list;
  }

  static <T> void print(Iterator<T> i) {
    while (i.hasNext()) {
      System.out.print(i.next() + ",");
    }
    System.out.println();
  }

  public static void main(String[] args) {

    // 2) 익명 클래스 사용하여 Supplier 구현체 만들기
    Supplier<Collection<String>> supplier = new Supplier<Collection<String>>() {
      @Override
      public Collection<String> get() {
        return new ArrayList<>();
      }
    };
    Collection<String> list = prepareNames(supplier, "홍길동", "임꺽정", "유관순", "임꺽정");
    print(list.iterator());

  }

}

 

 

package com.eomcs.oop.ex12;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Supplier;

public class Exam0753 {

  @SuppressWarnings("unchecked")
  static <T> Collection<T> prepareNames(Supplier<Collection<T>> factory, T... values) {
    Collection<T> list = factory.get(); // => new ArrayList<String>()
    for (T value : values) {
      list.add(value);
    }
    return list;
  }

  static <T> void print(Iterator<T> i) {
    while (i.hasNext()) {
      System.out.print(i.next() + ",");
    }
    System.out.println();
  }

  public static void main(String[] args) {

    // 3) 익명 클래스 사용하여 Supplier 구현체 만들기 II
    Collection<String> list = prepareNames(
        new Supplier<Collection<String>>() {
          @Override
          public Collection<String> get() {
            return new ArrayList<>();
          }
        }, 
        "홍길동", "임꺽정", "유관순", "임꺽정");
    print(list.iterator());

  }

}

 

 

package com.eomcs.oop.ex12;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Supplier;

public class Exam0754 {

  @SuppressWarnings("unchecked")
  static <T> Collection<T> prepareNames(Supplier<Collection<T>> factory, T... values) {
    Collection<T> list = factory.get(); // => new ArrayList<String>()
    for (T value : values) {
      list.add(value);
    }
    return list;
  }

  static <T> void print(Iterator<T> i) {
    while (i.hasNext()) {
      System.out.print(i.next() + ",");
    }
    System.out.println();
  }

  public static void main(String[] args) {

    // 4) 람다 문법으로 Supplier 구현체 만들기
    Collection<String> list = prepareNames(
        () -> new ArrayList<String>(), 
        "홍길동", "임꺽정", "유관순", "임꺽정");
    print(list.iterator());

  }

}

 

 

인터페이스 구현체를 만드는 다양한 방법
package com.eomcs.oop.ex12;

public class Exam0810 {

  interface Factory {
    Object create();
  }

  static class Car {}

  public static void main(String[] args) {
    // 1) 로컬 클래스로 인터페이스 구현체를 만든다.
    class CarFactory implements Factory {
      @Override
      public Object create() {
        return new Car();
      }
    }
    printCar(new CarFactory());

    // 2) 익명 클래스로 인터페이스 구현체를 만든다.
    printCar(new Factory() {
      @Override
      public Object create() {
        return new Car();
      }
    });

    // 3) 람다로 인터페이스 구현체를 만든다.
    printCar(() -> new Car());

    // 4) 기존에 존재하는 메서드를 인터페이스 구현체로 사용한다.
    printCar(Exam0810::createCar);

    // 5) 기존 클래스의 생성자를 인터페이스 구현체로 사용한다.
    printCar(Car::new);

    System.out.println("완료!");
  }

  public static Car createCar() {
    return new Car();
  }

  public static void printCar(Factory factory) {
    Object obj = factory.create();
    System.out.printf("만든 객체: %s\n", obj);
  }
}

 

 

List 와 forEach() - forEach() 사용 전
package com.eomcs.oop.ex12;

import java.util.ArrayList;

public class Exam0910 {

  public static void main(String[] args) {
    ArrayList<String> names = new ArrayList<>();
    names.add("홍길동");
    names.add("임꺽정");
    names.add("유관순");
    names.add("김구");
    names.add("안중근");

    for (int i = 0; i < names.size(); i++) {
      System.out.println(names.get(i));
    }
  }
}

 

 

List 와 forEach() - forEach() 사용 전 II
package com.eomcs.oop.ex12;

import java.util.ArrayList;

public class Exam0911 {

  public static void main(String[] args) {
    ArrayList<String> names = new ArrayList<>();
    names.add("홍길동");
    names.add("임꺽정");
    names.add("유관순");
    names.add("김구");
    names.add("안중근");

    for (String name : names) {
      System.out.println(name);
    }
  }
}

 

 

List 와 forEach() - forEach() 사용 후
package com.eomcs.oop.ex12;

import java.util.ArrayList;
import java.util.function.Consumer;

public class Exam0920 {

  public static void main(String[] args) {
    ArrayList<String> names = new ArrayList<>();
    names.add("홍길동");
    names.add("임꺽정");
    names.add("유관순");
    names.add("김구");
    names.add("안중근");

    class MyConsumer<T> implements Consumer<T> {
      @Override
      public void accept(T item) {
        System.out.println(item);
      }
    }
    names.forEach(new MyConsumer<String>());
  }
}

 

 

package com.eomcs.oop.ex12;

import java.util.ArrayList;
import java.util.function.Consumer;

public class Exam0921 {

  public static void main(String[] args) {
    ArrayList<String> names = new ArrayList<>();
    names.add("홍길동");
    names.add("임꺽정");
    names.add("유관순");
    names.add("김구");
    names.add("안중근");

    names.forEach(new Consumer<String>() {
      @Override
      public void accept(String item) {
        System.out.println(item);
      }
    });
  }
}

 

 

package com.eomcs.oop.ex12;

import java.util.ArrayList;

public class Exam0923 {

  public static void main(String[] args) {
    ArrayList<String> names = new ArrayList<>();
    names.add("홍길동");
    names.add("임꺽정");
    names.add("유관순");
    names.add("김구");
    names.add("안중근");

    names.forEach(System.out::println);
    //
    //    names.forEach(new Consumer<String>() {
    //      @Override
    //      public void accept(String t) {
    //        System.out.println(t);
    //      };
    //    });
  }
}

 

 

 

 

myapp

 

 

24. List가 특정 타입의 목록만 다룰 수 있게 맞춤 설정하는 방법: 제네릭 문법 적용

 

### 24. List가 특정 타입의 목록만 다룰 수 있게 맞춤 설정하는 방법: 제네릭 문법 적용 
- 한 개의 클래스를 정의하여 타입 별로 클래스가 있는 것처럼 사용한다.   
- 즉 타입 파라미터를 이용하여 목록에서 특정 타입의 값만 다루도록 제한한다.     
- 형변환 없이 목록에 들어 있는 데이터를 꺼낸다. 
- 이를 통해 각 타입 별로 전용 클래스가 있는 효과를 누린다.

 

package bitcamp.util;

// 사용하는 시점에서 타입을 고정하기
// => Iterator를 사용하는 시점에 어떤 타입의 데이터를 다룰 것인지 지정한다.
//    예) Iterator<Board>
public interface Iterator<T> {
  boolean hasNext(); // 꺼낼 값이 있는지 검사할 때
  T next(); // 값을 꺼낼 때
}
package bitcamp.util;

// iterator()가 리턴하는 객체(Iterator)가 어떤 타입의 데이터를 다룰 것인지 지정한다.
//
public interface Iterable<T> {
  Iterator<T> iterator();
}

 

package bitcamp.util;

// List 인터페이스를 구현하는 시점에서 다룰 데이터의 타입을 지정한다.
// => 예) List<Board>
public interface List<E> extends Iterable<E> {
  void add(E value);
  Object[] toArray();
  E get(int index);
  E set(int index, E value);
  boolean remove(E value);
  int indexOf(E value);
  int size();
}
package bitcamp.util;

// 이 클래스를 상속 받는 쪽에서
// 이 클래스가 다룰 데이터의 타입을 지정한다.
//
public abstract class AbstractList<E> implements List<E> {

  protected int size;

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

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

  @Override
  public Iterator<E> iterator() {

    // => return 문 + anonymous class
    return new Iterator<E>() {
      int cursor;
      @Override
      public boolean hasNext() {
        return cursor >= 0 && cursor < AbstractList.this.size();
      }
      @Override
      public E next() {
        return AbstractList.this.get(cursor++);
      }
    };
  }
}
package bitcamp.util;

// LinkedList 를 생성할 때 어떤 데이터를 다룰지 타입을 지정한다.
// 예) new LinkedList<Student>();
//
public class LinkedList<E> extends AbstractList<E> {

  private Node<E> head;
  private Node<E> tail;

  @Override
  public void add(E value) {
    Node<E> 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<E> cursor = this.head;

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

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

    Node<E> cursor = head;
    int i = 0;

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

    return null;
  }

  @Override
  public boolean remove(E value) {
    Node<E> prevNode = null;
    Node<E> deletedNode = null;
    Node<E> 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(E b) {
    Node<E> cursor = head;
    int i = 0;

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

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

    Node<E> cursor = head;
    int i = 0;

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

  // Node 객체에 담을 데이터의 타입이 무엇인지 지정한다.
  // 예) new Node<Student>();
  //
  static class Node<T> {
    T value;
    Node<T> next;

    public Node() {}

    public Node(T value) {
      this.value = value;
    }
  }
}
package bitcamp.util;

import java.util.Arrays;

// 이 클래스의 인스턴스를 생성하는 쪽에서
// 이 클래스가 다룰 데이터의 타입을 지정한다.
//
public class ArrayList<E> extends AbstractList<E> {

  private static final int SIZE = 3;
  protected Object[] objects = new Object[SIZE];

  @Override
  public void add(E 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);
  }

  @SuppressWarnings("unchecked")
  @Override
  public E get(int index) {
    super.get(index);
    return (E)this.objects[index];
  }

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

  @Override
  public boolean remove(E 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(E object) {
    for (int i = 0; i < this.size; i++) {
      if (objects[i].equals(object)) {
        return i;
      }
    }
    return -1;
  }

}

 

 

package bitcamp.myapp.dao;

import java.sql.Date;
import bitcamp.myapp.vo.Board;
import bitcamp.util.Iterator;
import bitcamp.util.List;

public class BoardDao {

  List<Board> list;

  public BoardDao(List<Board> list) {
    this.list = list;
  }

  int lastNo;

  public void insert(Board board) {
    board.setNo(++lastNo);
    board.setCreatedDate(new Date(System.currentTimeMillis()).toString());
    list.add(board);
  }

  public Board[] findAll() {
    Board[] boards = new Board[list.size()];
    Iterator<Board> i = list.iterator();
    int index = 0;
    while (i.hasNext()) {
      boards[index++] = i.next();
    }
    return boards;
  }

  public Board findByNo(int no) {
    Board b = new Board();
    b.setNo(no);

    int index = list.indexOf(b);
    if (index == -1) {
      return null;
    }

    return list.get(index);
  }

  public void update(Board b) {
    int index = list.indexOf(b);
    list.set(index, b);
  }

  public boolean delete(Board b) {
    return list.remove(b);
  }
}
package bitcamp.myapp.dao;

import java.sql.Date;
import bitcamp.myapp.vo.Student;
import bitcamp.util.Iterator;
import bitcamp.util.List;

public class StudentDao {

  List<Student> list;

  int lastNo;

  public StudentDao(List<Student> list) {
    this.list = list;
  }

  public void insert(Student s) {
    s.setNo(++lastNo);
    s.setCreatedDate(new Date(System.currentTimeMillis()).toString());
    list.add(s);
  }

  public Student[] findAll() {
    Student[] students = new Student[list.size()];
    Iterator<Student> i = list.iterator();
    int index = 0;
    while (i.hasNext()) {
      students[index++] = i.next();
    }
    return students;
  }

  public Student findByNo(int no) {
    Student s = new Student();
    s.setNo(no);

    int index = list.indexOf(s);
    if (index == -1) {
      return null;
    }
    return list.get(index);
  }

  public void update(Student s) {
    int index = list.indexOf(s);
    list.set(index, s);
  }

  public boolean delete(Student s) {
    return list.remove(s);
  }

}
package bitcamp.myapp.dao;

import java.sql.Date;
import bitcamp.myapp.vo.Teacher;
import bitcamp.util.Iterator;
import bitcamp.util.List;

public class TeacherDao {

  List<Teacher> list;

  int lastNo;

  public TeacherDao(List<Teacher> list) {
    this.list = list;
  }

  public void insert(Teacher t) {
    t.setNo(++lastNo);
    t.setCreatedDate(new Date(System.currentTimeMillis()).toString());
    list.add(t);
  }

  public Teacher[] findAll() {
    Teacher[] teachers = new Teacher[list.size()];
    Iterator<Teacher> i = list.iterator();
    int index = 0;
    while (i.hasNext()) {
      teachers[index++] = i.next();
    }
    return teachers;
  }

  public Teacher findByNo(int no) {
    Teacher t = new Teacher();
    t.setNo(no);

    int index = list.indexOf(t);
    if (index == -1) {
      return null;
    }

    return list.get(index);
  }

  public void update(Teacher t) {
    int index = list.indexOf(t);
    list.set(index, t);
  }

  public boolean delete(Teacher t) {
    return list.remove(t);
  }

}

 

Handler 앞부분에 제네릭 적용한다.

package bitcamp.myapp.handler;

import bitcamp.myapp.dao.BoardDao;
import bitcamp.myapp.vo.Board;
import bitcamp.util.LinkedList;
import bitcamp.util.Prompt;

public class BoardHandler {

  private BoardDao boardDao = new BoardDao(new LinkedList<Board>());
package bitcamp.myapp.handler;

import bitcamp.myapp.dao.StudentDao;
import bitcamp.myapp.vo.Student;
import bitcamp.util.ArrayList;
import bitcamp.util.Prompt;

public class StudentHandler {

  private StudentDao memberDao = new StudentDao(new ArrayList<Student>());
package bitcamp.myapp.handler;

import bitcamp.myapp.dao.TeacherDao;
import bitcamp.myapp.vo.Teacher;
import bitcamp.util.ArrayList;
import bitcamp.util.Prompt;

public class TeacherHandler {

  private TeacherDao teacherDao = new TeacherDao(new ArrayList<Teacher>());

 

 

 

25. 기존 List 구현체를 자바 컬렉션 API로 교체하기: java.util 패키지의 클래스 사용 

 

### 25. 기존 List 구현체를 자바 컬렉션 API로 교체하기: java.util 패키지의 클래스 사용 
- 기존의 List 관련 클래스를 모두 자바 컬렉션 API로 변환한다

 

우리가 만든 List, AbstractList, LinkedList, LinkedListTest, ArrayList, ArrayListTest, Iterable, Iterator 삭제한다.

 

Dao 3개에 아래 import로 바꾼다.

import java.util.Iterator;
import java.util.List;

 

BoardHandler는 아래 import로 바꾼다.

import java.util.LinkedList;

StudentHandler, TeacherHandler는 아래 import로 바꾼다.

import java.util.ArrayList;

 

 

 


 

 

조언

 

*

 

 

 


 

과제

 

예제 소스 학습

 

com/eomcs/oop/ex11/overview

com/eomcs/algorithm/data_structure

com/eomcs/basic/ex03~08

com/eomcs/generic