중첩 클래스
사용 범위 제한
특정 클래스 안에서만 or 특정 메서드 안에서만
사용 범위를 좁힌다
클래스가 필요하긴 필요한데 그 클래스를 관리하기 쉽도록 사용 폭을 줄이는 게 낫다
관리때문에 그런 거임. 실행 속도가 더 빨리지고 그런 거 없음.

Linked List 배워야 됨

com.eomcs.oop.ex11.h.test 만들기

com.eomcs.oop.ex11.h.test

ArrayList는 미리 데이터를 담을 배열을 준비한다.
그리고 값을 담는다.
꽉 차면 50% 정도 더 큰 배열을 만든 다음에
기존 배열에 있던 값을 복사해서 새로운 배열에 넣는다
기존 ArrayList는 garbage가 된다.
새 배열 만드는 작업을 반복 하다 보면 가비지가 많이 생긴다.
쓰든 안 쓰든 데이터를 담을 메모리를 준비해야 한다.
이만큼만 쓸 수도 있고 전체 꽉 채워서 쓸 수도 있고

ArrayList의 가장 큰 단점
값의 삽입, 삭제가 불편하다.
값을 삽입하려면 뒤로 밀어야 되고
값을 삭제하면 앞으로 당겨와야 한다.

⭐ ArrayList 공부하기
com.eomcs.algorithm.data_structure.array

리스트를 링크로
객차를 연결하듯이
레퍼런스를 준비해
값을 담아
이전 레퍼런스와 새 레퍼런스를 연결해
이런 식으로 목록을 관리하게 되면
처음부터 미리 몇 개를 준비할 필요도 없고
중간에 꽉 차면 배열 복제하는 거 안 해도 되고
가비지도 안 생김
삭제는 연결점만 바꾸면 됨
삽입은 레퍼런스를 만들고 값을 담고 새로운 노드를 가리키게 하고 새로운 노드는 다음 노드를 가리키게 하면 됨

단점
조회 속도가 느려진다

장점
삽입, 삭제가 쉽다

ArrayList
주로 조회하는 용도로 쓰는 경우

LinkedList
삽입, 삭제가 빈번히 일어나는 경우
메모리 관리 측면에서도 낫다

레퍼런스랑 레퍼런스를 어떻게 연결해..?

이전 항목의 주소 | 값 | 다음 항목의 주소

null | 값 | null

null | 값 | 300

200 | 값 | null

시작점이 누군지는 알아야 됨

head ← 시작 객체 주소를 저장
tail ← 끝 객체의 주소를 저장

전체가 연결된(linked) 리스트

값을 추가하면 꼬리의 주소가 바뀐다

자바에는 3개의 데이터를 담는 적절한 데이터 타입이 없다
데이터 타입을 새로 만든다
class를 정의한다

Node 클래스 만들기

package com.eomcs.oop.ex11.h.test;

public class Node {
  Node prev;
  Object value;
  Node next;

  public Node() {}

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

LinkedList 클래스 만들기

시작점을 노드 1개로 시작할지, 노드 없이 시작할지 결정하기 나름
이번에는 존재하지 않는 상태로 시작하겠음

package com.eomcs.oop.ex11.h.test;

public class LinkedList {
  Node head;
  Node tail;
  int size;  // 몇 개 저장했는지
}

Test 클래스 만들기

package com.eomcs.oop.ex11.h.test;

public class Test {

  public static void main(String[] args) {
    LinkedList list = new LinkedList();
    list.add("홍길동");
    list.add("임꺽정");
    list.add("유관순");
    list.add("안중근");
    list.add("윤봉길");
    list.add("김구");
    System.out.println(list.size());
  }

}

값을 담을 객체를 준비한다

값을 받는 생성자를 추가하자

// Node.java

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

필요하면 그때그때 추가하면 됨

new Node()

이전 노드가 있나 없나

tail = head = node;
① head = node 먼저 실행됨
    node의 값이 head에 놓인다
② tail =

// LinkedList.java

  public int size() {
    return size;
  }
// LinkedList.java

  public void add(Object value) {
    Node node = new Node(value);

    if (head == null) {
      tail = head = node;
    } else {
      node.prev = tail;
      tail.next = node;
      tail = node;
    }
    
    size++;
  }

새로 추가하는 노드의 앞 노드는 tail인 노드가 된다
node.prev = tail;

현재 tail인 노드의 next에 현재 추가하는 노드 주소를 넣는다
tail.next = node;

현재 추가하는 노드가 tail이 된다
tail = node;

꼬리 노드를 가리키게 한다

일단 새 노드를 만든다
새 노드에는 변수가 3개
값을 담는 변수, 이전 노드의 주소를 담는 변수, 다음 노드의 주소를 담는 변수
처음에는 null로 초기화된다

배열은 값만 저장하면 되는데
LinkedList는 이전 노드의 주소를 담는 변수, 다음 노드의 주소를 담는 변수까지 필요하다

package com.eomcs.oop.ex11.h.test;

public class Test {

  public static void main(String[] args) {
    LinkedList list = new LinkedList();
    list.add("홍길동");
    list.add("임꺽정");
    list.add("유관순");
    list.add("안중근");
    list.add("윤봉길");
    list.add("김구");
    System.out.println(list.size());

    System.out.println(list.get(2));
  }

}

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedList.html

node = node.next;
node의 next에 들어 있는 값을 node에 저장한다.
node에 저장된 주소로 찾아가서 그 인스턴스에 있는 next 변수의 값을 말한다.
변수가 놓이는 게 아니라 변수에 들어 있는 값이 놓이는 거
= 오른쪽에는 값이 놓인다

  public Object get(int index) {
    if (index < 0 || index >= size) {
      throw new IndexOutOfBoundsException();
    }

    Node node = head;
    int count = 0;
    while (count < index) {
      node = node.next;
      count++;
    }

    return node.value;
  }

throws 안 적습니까?
IndexOutOfBoundsException는 RuntimeException의 서브 클래스이다.
RuntimeException 에러는 굳이 안 적고 생략해도 된다

package com.eomcs.oop.ex11.h.test;

public class Test {

  public static void main(String[] args) {
    LinkedList list = new LinkedList();
    list.add("홍길동");
    list.add("임꺽정");
    list.add("유관순");
    list.add("안중근");
    list.add("윤봉길");
    list.add("김구");
    System.out.println(list.size());

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

}

list.remove(2);
2번째 항목을 지워보자

package com.eomcs.oop.ex11.h.test;

public class Test {

  public static void main(String[] args) {
    LinkedList list = new LinkedList();
    list.add("홍길동");
    list.add("임꺽정");
    list.add("유관순");
    list.add("안중근");
    list.add("윤봉길");
    list.add("김구");
    System.out.println(list.size());

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

    list.remove(2);
    for (int i = 0; i < list.size(); i++) {
      System.out.println(list.get(i));
    }
    System.out.println();
    System.out.println("------------------------------------------");
  }

}

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedList.html#remove(int)

get에 있는 유효성 검사를 그대로 복사한다

  public void remove(int index) {
    if (index < 0 || index >= size) {
      throw new IndexOutOfBoundsException();
    }
  }

삭제하는 노드가 가리키는 다음 노드의 주소를 담는다
다음 노드의 이전 노드에 이전 노드의 주소를 담는다
값을 땡겨오거나 그럴 필요가 없다

삭제하려는 노드를 찾아야 한다
get으로 찾을 수 있다

문제가 있다
Node를 리턴받아야 하는데
get() 메서드는 Object를 리턴한다.

get에서 노드를 찾는 코드를 복사한다.

  public void remove(int index) {
    if (index < 0 || index >= size) {
      throw new IndexOutOfBoundsException();
    }

    Node node = head;
    int count = 0;
    while (count < index) {
      node = node.next;
      count++;
    }
  }

코드가 중복되니까 밑에다가
remove나 get에서 사용할 getNode() 메서드를 따로 만든다

  public Node getNode(int index) {
    if (index < 0 || index >= size) {
      throw new IndexOutOfBoundsException();
    }

    Node node = head;
    int count = 0;
    while (count < index) {
      node = node.next;
      count++;
    }

    return node;
  }
  public Object get(int index) {
    Node node = getNode(index);

    return node.value;
  }

getNode 에서 어차피 인덱스 유효 검사를 함
getNode를 호출한 놈이 이 예외를 받는다
호출한 놈에 try - catch가 없으면 얘도 자기를 호출한 놈한테 예외를 던진다
이게 RuntimeException 이다. 중간에 throws 선언할 필요 없다.

  public void remove(int index) {
    Node node = getNode(index);
  }

for문으로 해도 되고

값을 원하는 게 아니라 Node를 받아야 되는데

레퍼런스 카운트

가비지 컬렉터가 와서 왕따를 제거

프로그램이 끝날 때까지 안 쓰는데 가비지가 아님

가비지가 되어야 하는데 가비지가 안 되는 상황
가비지 객체를 참조
서로 참조해서 절대 가비지가 안 되는 상황 발생

      node.prev = null;
      node.next = null;

마무리 작업이 중요
JVM이 종료가 될 때까지 계속 메모리가 종료될 때까지

요즘은 가비지 컬렉터 똑똑해짐

가비지 컬렉터가 거기까지 처리한다고 해도
시험 볼 때는
서로 가비지가 되어야 할
가비지가 잘 안 되는 상황까지 감지하고 있느냐를 확인

삭제된 노드는 다른 노드를 참조하지 않도록 초기화시킨다.
⟹ 삭제된 노드끼리 참조함으로써 가비지가 되지 않는 문제를 방지하기 위함.

🔹 맨 마지막거 지울 때
삭제한 이전 거를 tail로 가리키게끔 한다
null로 초기화 시켜서 연결을 끊어버린다 연결을 없앤다
모르는 관계로 만든다

    if (size == 1) {
      head = tail = null;
    }

🔹 중간값 삭제

      node.prev.next = node.next;
      node.next.prev = node.prev;

size--; // size의 값을 1 감소시킨다.
--size; // 위의 문장과 차이가 없다.
독립적인 하나의 문장으로 쓰인 경우에는 전위형과 후위형의 차이가 없다.

🔹 insert
add(int index, Object value)

맨 앞이랑 맨 뒤에 삽입하는 게 문제

맨 끝에 '삽입'
tail은 안 바뀐다

맨 끝에 집어 넣는 건 add
tail 바뀜

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/LinkedList.html

getNode() 메서드는 내부에서만 쓰이니까 private로 바꾼다

내가 삭제하기 전에

    // 삭제된 노드는 다른 노드나 객체를 참조하지 않도록 초기화시킨다.
    // => 삭제된 노드끼리 참조하는 경우 가비지가 되지 않는 문제가 발생한다.
    // => 삭제된 노드가 값 객체의 주소를 갖고 있으면 값 객체가 가비지가 되지 않는 문제가 발생한다.
    node.prev = null;
    node.next = null;
    Object deleted = node.value;
    node.value = null;
    size--;
    return deleted;

remove나 set은 이전 값을 리턴한다

LinkedList, ArrayList, Stack, Queue
­+ 이진트리
­+ 정렬
­+ 탐색
객체 주소를 다루는지, 레퍼런스 개념을 잘 잡고 있는지 보는 거

linkedlist2.step2 - Iterator 디자인 패턴

com.eomcs.algorithm.data_structure.linkedlist2.step2

Test.java에서 printList(LinkedList list)로 직접 값을 꺼내서 출력
LinkedList, ArrayList는 가능한데
Stack, Queue는 불가능
Stack이랑 Queue는 이렇게 못 꺼냄

  static void printList(LinkedList list) {
    for (int i = 0; i < list.size(); i++) {
      System.out.print(list.get(i) + ",");
    }
    System.out.println();
    System.out.println("------------------------------------------");
  }

Stack인 경우에는 pop으로 꺼내고
Queue인 경우에는 poll로 꺼내야 됨
Stack이든 Queue든 상관없이 일관된 방법으로 값을 꺼내기 위해서는 Iterator 인터페이스

ListIterator 만들기

package com.eomcs.oop.ex11.h.test;

// 컬렉션의 값을 조회하는 일을 수행한다.
//
public class ListIterator implements java.util.Iterator {
  
}

hasNext(), next() 2개의 메서드를 반드시 구현해야 한다.

int cursor;
한 개씩 꺼낸 다음에 내가 어디까지 꺼냈는지 기억해야 됨
0부터 시작

return list.get(cursor++)
cursor는 나중에 증가하도록 후위연산자 사용

package com.eomcs.oop.ex11.h.test;

// 컬렉션의 값을 조회하는 일을 수행한다.
//
public class ListIterator implements java.util.Iterator {

  LinkedList list;
  int cursor;

  public ListIterator(LinkedList list) {
    this.list = list;
  }
  
  @Override
  public boolean hasNext() {
    return cursor < list.size();
  }

  @Override
  public Object next() {
    return list.get(cursor++);
    //    int temp = cursor;
    //    cursor = cursor + 1;
    //    return list.get(temp);
  }
  
}

Iterator 만들기

package com.eomcs.oop.ex11.h.test;

// 컬렉션의 값을 조회하는 객체 사용법
//
public interface Iterator {
  boolean hasNext(); // 꺼낼 값이 있는지 알고 싶을 때
  Object next(); // 값을 꺼낼 때
}

ListIterator로 가서 java.util.Iterator 구현하지 않고
새로 만든 Iterator를 구현하는 걸로 바꾼다

public class ListIterator implements Iterator {

Test.java 가서
일관된 방법 쓰자
일관성 있는 데이터 조회 가능

  static void printList(LinkedList list) {
    // Iterator를 통해 목록에서 값 꺼내기
    Iterator iterator = new ListIterator(list);
    while (iterator.hasNext()) {
      System.out.print(iterator.next() + ",");
    }
    System.out.println();
    System.out.println("------------------------------------------");
  }

GoF의 Iterator 설계 패턴
Iterator 디자인 패턴 적용한 거
Stack이든 Queue든 ArrayList든 LinkedList든 상관없이
일관된 방법으로 데이터를 조회할 수 있다.

만약에 Iterator 디자인 패턴을 적용하지 않으면
List에서 조회할 때는 get 메서드를 써야 되고
Stack에서 값을 꺼낼 때는 pop 이라는 메서드를 호출해야 되고
Queue에서 값을 꺼낼 때는 poll이라는 메서드를 호출해야 한다.
즉 어떤 종류의 collection 객체인가에 따라서 데이터를 꺼내는 방법이 다르다
이걸 해결하고자 데이터를 꺼내는 일을 별도의 객체에 맡긴다
여기서는 ListIterator가 그 일을 하고 있다

linkedlist2.step3 - 중첩 클래스 활용

중첩 클래스 활용

Node 클래스는 LinkedList 안에서만 쓰인다
굳이 Node를 바깥에 빼놓을 이유가 없음
코드가 긴 것도 아니고

Node를 패키지 멤버(top level class)로 만들지 말고
어차피 LinkedList 안에서만 쓰이는 클래스라면
사용범위를 LinkedList 안으로 제한하자

Node를 LinkedList 안으로 옮긴다.

Node가 LinkedList(바깥 클래스)의 인스턴스 필드나 인스턴스 메서드를 쓰는가?
안 쓰면 static으로 만든다.

중첩 클래스가 바깥 클래스의 인스턴스 멤버를 사용하는가
안 쓴다면 static으로 만든다

자바에서도 java.util.LinkedList 안에 Node 클래스가 들어 있다.
Node를 바깥에서 쓸 일이 없어서 private으로 되어 있다.

우리도 똑같이 private로 바꾼다.
내부에서만 쓰니까 private로 바꾼다
private static class Node {

  // Static Nested Class 활용 예
  // => 특정 클래스 안에서만 사용되는 클래스일 때
  // => 바깥 클래스의 인스턴스 멤버를 사용하지 않을 때
  //
  private static class Node {
    Node prev;
    Object value;
    Node next;

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

Node.java 지우기

Test.java 실행해보기
아무런 문제 없음.

  static void printList(LinkedList list) {
    // Iterator를 통해 목록에서 값 꺼내기
    Iterator iterator = new ListIterator(list);
    while (iterator.hasNext()) {
      System.out.print(iterator.next() + ",");
    }
    System.out.println();
    System.out.println("------------------------------------------");
  }

Test에서 ListIterator를 쓴다
ListIterator를 LinkedList 안에 집어넣으면 안 되지 않습니까?
아니요.
Test에서 필요한 건 Iterator 일을 하는 객체이다.

ListIterator를 LinkedList에 집어넣는다.

  // Non-static Nested Class(= inner class) 활용 예
  // => 특정 클래스의 안에서만 사용될 때
  // => 바깥 클래스의 인스턴스 멤버를 사용할 때
  // 
  private class ListIterator implements Iterator {

ListIterator는 바깥 클래스의 인스턴스 멤버를 쓰는가?
쓴다.
그러면 객체 주소를 받기 위해서 생성자를 직접 만들지 말고
자동으로 만들어 달라고 한다
자동으로 만들어 달라고 할 때는 static을 붙이지 않는다

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

자동으로 만들어주면
바깥 클래스의 인스턴스 주소를 쓰고 싶을 때 어떻게 해야 되나
전에는 우리가 list 라는 인스턴스 변수를 만들어서
list.size() 이렇게 바깥 클래스의 인스턴스를 썼다

그런데 자동으로 만들어 주는 경우에는
바깥 클래스의 주소를 담고 있는 필드 이름이 뭔지 모름
모르니까 규칙을 정함
바깥 클래스의 인스턴스 주소를 사용하고 싶으면
바깥클래스명.this

ListIterator.java 지우기

지웠더니 Test.java에 빨간줄 뜸

LinkedList로 가서 iterator() 라는 메서드를 public으로 만든다.

  public Iterator iterator() {
     return new ListIterator();
  }

바깥 객체 주소를 파라미터로 전달해야 되는 거 아닙니까?
LinkedList 객체 주소를 전달해야 되는 거 아닙니까?
inner class인 경우는 바깥 클래스의 객체 주소를
자동으로 생성된 생성자를 인위적으로 호출할 수 없다
앞쪽으로 넘겨야 됨
파라미터로 넘기면 안 되고 앞쪽으로 넘겨야 됨
this.new ListIterator()
non-static nested class의 객체를 만들 때는
바깥 클래스의 객체 주소를 생성자 파라미터가 아닌 앞쪽에 넘겨야 한다.
그래야 내부적으로 this에 들어 있는 주소가 생성자를 통해서 전달되고
내부적으로 이렇게 바뀐다
new ListIterator(this)
그리고 이렇게 생성자에서 받은 this 값을
내부 히든 필드 this$0에 저장한다

그럼 Test는 어떡합니까?
Test는 Iterator 객체가 필요한 거지 ListIterator가 필요한 게 아님
Iterator 객체를 만들어 주는 게 iterator() 메서드
iterator() 메서드를 호출하면 된다
Iterator iterator = list.iterator();
Iterator 규칙에 따라서 만든 객체를 리턴한다.

인위적으로 호출할 수 없다
생성자 파라미터가 아닌 앞쪽에 넘겨야 한다

  public Iterator iterator() {
    return this.new ListIterator(); // new ListIterator(this)
  }

Test 에서 필요한 건 Iterator

static 붙이는 기준
중첩 클래스에서 바깥 클래스의 주소를 쓰지 않는다면 static으로
중첩 클래스에서 바깥 클래스의 주소를 쓴다면 static을 붙이지 않는다

중첩 클래스를 만들어야 하는 상황에서
어느 중첩 클래스를 non-static으로 만들어야 되는지

linkedlist2.step4 - 로컬 클래스

ListIterator 객체는 iterator() 메서드에서만 만든다
다른 메서드에서는 만들지 않는다
iterator() 메서드에서만 쓴다

Node 라는 중첩 클래스는 여러 메서드에서 쓴다

ListIterator를 iterator() 메서드 안으로 한정시킨다
ListIterator는 로컬 클래스가 된다

로컬 클래스에는 private 못 붙이니까 지우기
class ListIterator implements Iterator {

로컬 클래스에는 앞에 this 붙이면 안 됨. 아예 생략해야 됨.
return new ListIterator();

  public Iterator iterator() {
    // Local Class 활용 예
    // => 특정 메서드 안에서만 사용될 때
    // 
    class ListIterator implements Iterator {
      int cursor;
      @Override
      public boolean hasNext() {
        return cursor < LinkedList.this.size();
      }
      @Override
      public Object next() {
        return LinkedList.this.get(cursor++);
      }
    }

    return new ListIterator();
  }

중첩 클래스 Node는 여러 메서드에서 사용되므로 로컬 클래스로 못 만든다.

linkedlist2.step5 - 익명 클래스

로컬 클래스는 객체를 여러 개 만들 수 있다.
이름을 가지고 있으면 객체를 여러 개 만들 수 있다.
여러 개의 인스턴스를 만들 수 있다.

  public Iterator iterator() {
    // Local Class 활용 예
    // => 특정 메서드 안에서만 사용될 때
    // 
    class ListIterator implements Iterator {
      int cursor;
      @Override
      public boolean hasNext() {
        return cursor < LinkedList.this.size();
      }
      @Override
      public Object next() {
        return LinkedList.this.get(cursor++);
      }
    }
    
    Iterator i1 = new ListIterator();
    Iterator i2 = new ListIterator();
    Iterator i3 = new ListIterator();

    return new ListIterator();
  }

그런데 iterator() 메서드 안에서 ListIterator는 딱 한 개 만든다.
딱 한 개를 만들 때는 익명 클래스(anonymous class)를 사용한다.

  public Iterator iterator() {
    // Anonymous Class 활용 예
    // => 오직 한 개의 인스턴스만 생성할 경우
    // 
    Iterator obj = new Iterator() {
      int cursor;
      @Override
      public boolean hasNext() {
        return cursor < LinkedList.this.size();
      }
      @Override
      public Object next() {
        return LinkedList.this.get(cursor++);
      }
    };

    return obj;
  }

익명 클래스는 인터페이스를 구현한 클래스를 만든 후에 즉시 객체를 생성한다.

호출할 수퍼 클래스 생성자 지정

리턴 값은 Iterator 구현체 주소니까 인터페이스 레퍼런스에 저장한다.

linkedlist2.step6 - 익명 클래스 활용 예 2

익명 클래스를 변수에 담아서 리턴하지 말고 return문에 바로 넣어버린다.

  public Iterator iterator() {
    // Anonymous Class 활용 예 2
    // => 오직 한 개의 인스턴스만 생성할 경우
    // => return 문, 할당문, 파라미터 전달하는 곳에 놓기 
    //
    return new Iterator() {
      int cursor;
      @Override
      public boolean hasNext() {
        return cursor < LinkedList.this.size();
      }
      @Override
      public Object next() {
        return LinkedList.this.get(cursor++);
      }
    };
  }

람다 문법은 불가능.
인터페이스에 추상 메서드가 1개만 있어야 가능한데 2개 있어서 불가능

일단 중첩 클래스에 집중

step별 차이에 집중해서 공부하기

com.eomcs.oop.ex12

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

추상 메서드가 한 개 있는 인터페이스를 "functional interface"라고 부른다.

인터페이스를 구현한 익명 클래스를 정의하고
즉시 인스턴스를 생성하고
그 주소를 인터페이스 레퍼런스에 담는다

람다 문법으로 인터페이스 구현하기
메서드 한 개짜리 인터페이스를 좀 더 간결하게 구현하기 위해 만든 문법이다.

java-lang/bin/main/com/eomcs/oop/ex12/Exam0110.class

기본 생성자가 없는데 자동으로 만들어진다.

public Exam0110();

Exam0110 에는 main() 메서드 밖에 없는데
lambda$0() 라는 static 메서드가 만들어진다.

옛날에는 익명 클래스로 바뀌었음

Player p2 = () -> System.out.println("익명 클래스");
컴파일러는 람다 코드를 스태틱 메서드로 바꾼다. => static void lambda$0()

p2.play();
람다 코드를 실행하는 문장은 자동 생성된 메서드(lambda$0())를 호출하는 문장으로 바꾼다.
예) Exam0110.lambda$0();

내부적으로 이렇게 바꿔서 호출되게 한다

Exam0110.Player

JVM 구현체마다 구현 방법이 다름
JVM 명세서에서는 람다를 메서드로 바꾸라고 언급이 안 되어 있음
지금 우리가 쓰는 건 그렇다는 거지
언급이 된 게 아닌 경우는 그냥 빼겠음

com.eomcs.oop.ex12.Exam0120.java
Lambda 문법 - 람다 body

🔹 functional interface : 추상 메서드가 한 개만 있는 인터페이스

한 문장일 때는 중괄호를 생략할 수 있다.

물론 중괄호를 명확하게 적어도 상관은 없다.

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

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

1) 파라미터는 괄호() 안에 선언한다.

2) 파라미터 타입을 생략할 수 있다.

3) 파라미터가 한 개일 때는 괄호도 생략할 수 있다.

셋 중에 어느 것을 쓰는 게 좋습니까?
람다 문법이 코드를 더 간략하게 하자고 만든 문법 아님?
그럼 당연히 세 번째 거

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

이번에는 파라미터를 2개 받는다.

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

1) 파라미터는 괄호() 안에 선언한다.

2) 파라미터 타입을 생략할 수 있다.

3) 파라미터가 여러 개일 때는 괄호를 생략할 수 없다.
   한 개일 때만 괄호를 생략할 수 있다.

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

익명 클래스는 functional interface만 가능하다.

스태틱 필드, 인스턴스 필드, 로컬 변수, 파라미터, 리턴 값, 리턴 문
⟹ 람다 가능

익명 클래스를 놓을 수 있는 곳에는 다 람다를 놓을 수 있다.

com.eomcs.oop.ex12.Exam0150.java
Lambda 문법 - 람다 리턴

2개의 정수 값을 받아서 int 리턴

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

파라미터가 2개이기 때문에 선언해야 됨
파라미터의 타입을 적어도 되고, 생략해도 된다.
단, 파라미터가 2개이기 때문에 괄호는 생략할 수 없다.

return 문장이 있을 때는 중괄호 생략 불가능

1) 리턴 값은 return 명령을 사용하여 처리한다.

괄호를 생략할 때 return 키워드도 생략해야 한다. 있으면 컴파일 오류!

Calculator c2 = (a, b) -> a + b;

↑ return 키워드가 생략된 거다.

return을 생략하고 싶지 않으면 정확하게 중괄호를

값을 리턴해야 하는데 람다 문장에서 값을 리턴하지 않으면 컴파일 오류!

int를 리턴해야 되는데 println은 void

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

com.eomcs.oop.ex12.Exam0210.java
Lambda 문법 - functional interface의 자격

람다 문법으로 인터페이스 구현하기
메서드 한 개짜리 인터페이스를 좀 더 간결하게 구현하기 위해 만든 문법이다.

추상 메서드를 한 개만 갖고 있는 인터페이스에 대해
람다 문법으로 익명 클래스를 만들 수 있다.

파라미터가 없는 경우는 파라미터 괄호 생략 불가능

com.eomcs.oop.ex12.Exam0220.java
Lambda 문법 - functional interface의 자격

추상 메서드가 두 개 이상인 경우 람다 문법을 사용할 수 없다.

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

인터페이스가 아닌 추상 클래스는 람다 구현의 대상이 아니다!

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

굳이 로컬 클래스로 만들 필요 없이 익명 클래스로 만들 수 있음

Exam0310.java 복사해서 연습하기
310번을 313번까지 만드는 연습하기

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

  static interface Calculator {
    int compute(int a, int b);
  }
    class MyCalculator implements Calculator {
      public int compute(int a, int b) {
        return a + b;
      }
    }
    
    MyCalculator obj = new MyCalculator();
    test(obj);
    class MyCalculator implements Calculator {
      public int compute(int a, int b) {
        return a + b;
      }
    }
    
    test(new MyCalculator());

수퍼 클래스를 지정하지 않으면 Object가 수퍼 클래스

0Object 생성자는 기본 생성자 하나밖에 없음

    Calculator obj = new Calculator() {
      public int compute(int a, int b) {
        return a + b;
      }
    };
    
    test(obj);
    test(new Calculator() {
      public int compute(int a, int b) {
        return a + b;
      }
    });

익명 클래스가 functional interface 라고 불리는
추상 메서드 1개 짜리 인터페이스를 구현한 것이라면
람다 문법으로 바꿀 수 있다.

    test((a, b) -> a + b);

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

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

안에 있는 문장이 여러 개이기 때문에 중괄호는 생략할 수 없다.

    test((a, b) -> {
      int sum = 0;
      for (int i = a; i <= b; i++) {
        sum += i;
      }
      return sum;
    });

안에 문장이 여러개
여러 문장을 실행하는 경우 블록 {}으로 감싸라!

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

팩토리 메서드에서 익명 클래스 활용하기

getInterest()는 Interest 인터페이스를 구현한 객체를 리턴한다.

이자를 계산하는 로컬 클래스 InterestImpl

0410 복사해서 0410x 만들기

com.eomcs.oop.ex12.Exam0410x.java

getInterest()는 이자 계산기를 만들어서 리턴

파라미터로 받은 이자를 InterestImpl 생성자에 이자를 넘겨준다

이자를 내부에 받아놨다가 compute()를 호출하면 받아놨던 이자로 계산해서 결과를 리턴한다.

로컬 클래스인 경우, 바깥 메서드의 로컬 변수를 쓰는 경우는
직접 로컬 변수 값을 받을 생성자를 준비해도 되지만

      double rate;

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

이걸 준비하지 않아도
알아서 이 코드로 바뀐다.

로컬 클래스는 로컬 변수를 사용할 수 있다.

컴파일러는
이자율을 받는 생성자를 만든다
생성자에서 받은 값을 저장할 인스턴스 필드 변수까지 만든다

rate는 파라미터를 가리키는 게 아니다
rate는 내부 변수를 가리킨다
파라미터 변수 값을 생성자 파라미터에 넘기고

com.eomcs.oop.ex12.Exam0510.java
메서드 레퍼런스 - 스태틱 메서드 레퍼런스

  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);
  }

3) 기존에 작성한 클래스의 스태틱 메서드를 재활용하기
인터페이스의 메서드 규격과 일치하는 메서드가 있다면,
그 메서드를 람다 구현체로 대체할 수 있다.
⟹ 새로 코드를 작성할 필요가 없어 매우 편리하다.
⟹ 규격? 메서드의 파라미터 타입/개수/순서, 리턴 타입
⟹ 문법 : 클래스명::메서드명

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

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

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

스태틱 메서드 레퍼런스로 Calculator 구현체를 만드는 방법

Calculator c1 = MyCalculator::plus;

위의 코드는 내부적으로 다음과 같다.
내부적으로 Calculator 인터페이스를 구현한 익명 클래스를 만들고
그 메서드 안에서 return 하면서 호출하는 코드로 바뀐다.

인터페이스에 정의된 메서드(예: compute())가 호출되었을 때,
그 파라미터 값은 메서드 레퍼런스로 지정된 스태틱 메서드(예: plus())에게 전달될 것이다.
그래서 스태틱 메서드의 파라미터는 항상 인터페이스 메서드에 정의된 파라미터 값을 받을 수 있어야 한다.

스태틱 메서드의 리턴 값은 인터페이스 메서드에 정의된 대로 리턴할 수 있어야 한다.
그래서 스태틱 메서드의 리턴 타입은 인터페이스 메서드의 리턴 타입과 일치하거나
그 타입으로 바꿀 수 있어야 한다.

com.eomcs.oop.ex12.Exam0530.java
메서드 레퍼런스 - 스태틱 메서드 레퍼런스 - 리턴 타입

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

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

  // 리턴 타입 int ===> void
  Calculator4 c4 = MyCalculator::plus; // OK!
  
  c4.compute(100, 200); // plus() 메서드의 리턴 값은 무시한다.

버리면 되기 때문에 아무런 문제 없다.

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

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

  // 리턴 타입 int ===> Object
  Calculator4 c5 = MyCalculator::plus; // OK!
  
  c5.compute(100, 200);

Object를 리턴하는데
int를 Integer 객체로 만들어서 리턴한다.
plus()가 리턴한 int 값이 오토박싱 되기 때문이다.

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

com.eomcs.oop.ex12.Exam0540.java
메서드 레퍼런스 - 스태틱 메서드 레퍼런스 - 파라미터 타입

내부적으로 plus를 호출하는데 plus는 int 파라미터를 받는다.

byte, short, character > int > long > float > double

Object에 뭐가 올 줄 알고
Integer 객체가 오면 오토 언박싱이라도 가능한데
나머지는 불가능

Integer 객체는 int로 오토 언박싱 된다

3개 받아서 3개 넘기려고 하면 안 됨

compute() 메서드 규칙에 따라서 메서드를 호출했을 때
plus() 메서드를 사용할 수 없으면 안 된다.

com.eomcs.oop.ex12.Exam0610.java
메서드 레퍼런스 - 인스턴스 메서드 레퍼런스

인스턴스 객체 주소로 호출한다.

문법 : 인스턴스::메서드명

Exam0630 ~ 754 넘어 가기

620번까지 이해가 안 된 상태에서 보면 안 됨

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

1) 로컬 클래스로 인터페이스 구현체를 만든다.
2) 익명 클래스로 인터페이스 구현체를 만든다.
3) 람다로 인터페이스 구현체를 만든다.
4) 기존에 존재하는 메서드를 인터페이스 구현체로 사용한다.
5) 기존 클래스의 생성자를 인터페이스 구현체로 사용한다.
생성자도 메서드이기 때문에 메서드 레퍼런스를 사용할 수 있다.

Exam0910 ~ 923 넘어 가기

com.eomcs.algorithm.data_structure.linkedlist2.step1 ~ step6 복습

com.eomcs.oop.ex12.Exam0110 ~ Exam0620, Exam0810 복습

메서드 레퍼런스
기존에 만든 메서드를 재활용하자...
익명 클래스로 만드는 것조차도 낭비라고 보는 거

0개의 댓글