📜 제목으로 보기

리스트

  1. 테스트부터 만든다.

    • test>java에서 class를 앞에 패키지.를 붙혀 만들면 패키지 + class를 동시에 만들 수 있다.
      • list.ListTest
     public class ListTest {
         @Test
         void name() {
             new ArrayList<>();
         }
     }
    

ArrayList vs LinkedList

ArrayList

  1. arrayList는 내부에 단순 배열을 가지고 있다. 배열을 사용하기 좋게 감싼 일급컬렉션

     transient Object[] elementData; // non-private to simplify nested class access
    
    • 배열을 그냥 쓰면, 배열크기가 고정되어있지만, 일급으로 만들어서 크기를 유동적으로 증가 감소, 복사해서 대체해주기 등을 한다.
     private Object[] grow(int minCapacity) {
         return elementData = Arrays.copyOf(elementData,
                                            newCapacity(minCapacity));
     }
    

LinkedList

  1. 배열이 아니라 시작과 끝노드를 가지고 있으며 연결되어있다.

     transient Node<E> first;
        
     /**
          * Pointer to last node.
          */
     transient Node<E> last;
    
  2. 연결되어있으므로 배열 공간에 미리할당할 필요없이 하나씩 node를 추가하면 된다.

저장 -> 접근 방식

image-20220302090332448

  1. 배열의 arrayList: 인덱스로 한번에 접근가능하다.
    • 인덱스로 한번에 중간원소 접근가능 -> 빠르다.
      • 메모리 공간을 미리 할당해야하므로, 메모리 딸리는 상황에서는 데이터 추가시 느릴 수 있다.
    • 중간원소 삭제인덱스를 한칸식 다 땡겨줘야하므로 시간이 오래 걸린다.
    • 중복처리 방식
      • 리스트 안에 리스트를 넣는방식 이용
      • 크기가 커지면 리스트 안에 트리를 넣는 방식 이용
      • 사이즈에 맞게 다르게 사용되어서.. api를 사용하는게 좋다.
  2. 노드의 LinkedList: 통해서 가야하는 줄줄이 노드.
    • 타고가야하는 줄줄이 노드는 중간원소 접근 -> 느리다.
    • 중간원소 삭제시 편하게 삭제

하드디스크의 물리적으로 순차적인 접근

  • 옛날 하드디스크 = 물리적으로 순차적으로 메모리 배열 -> arraylist와 마찬가지 방식 -> 유리했었음
    • 요즘은 linkedList가 메모리 파편화된 node들의 집합이어도 별 상관없어졌다.

제네릭

  • list처럼 다양한type을 관리하는 경우, 특정type으로 고정시킬 수 있다.
    • 제네릭이 멀까?
      • 컴파일 타임(핵심)에 처리가 되서, 그 때 버그를 잡아낼 수 있다.
      • 클래스를 변수로 만들기?

제네릭은 컴파일 타임에 처리된다.

  1. 1을 arrayList에 넣고 출력하는 코드를 작성하고 실행시켜 컴파일

     @Test
     void name() {
         final ArrayList<Integer> integers = new ArrayList<>();
         integers.add(1);
        
         System.out.println(integers);
     }
    
  2. out(build)부터 시작되는 컴파일코드로 가서 shift2번 > show bytecode를 쳐서 내부적으로 실행된 코드를 확인해보자.

    image-20220302093655182

     // access flags 0x0
       name()V
       @Lorg/junit/jupiter/api/Test;()
        L0
         LINENUMBER 9 L0
         NEW java/util/ArrayList
         DUP
            // ArrayList 생성할 때, 제네릭정보는 없다.
         INVOKESPECIAL java/util/ArrayList.<init> ()V
         ASTORE 1
        L1
         LINENUMBER 10 L1
         ALOAD 1
         ICONST_1
         INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
             // integer가 들어갈 때, Object로 들어간다. -> 컴파일 될 때 알아서 Type으로 떨어진다. 컴파일 타임까지만 Type실수하지않게 도와주는 것. 런타임 때는 안도와준다. 
         INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
         POP
        L2
         LINENUMBER 12 L2
         GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
         ALOAD 1
         INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
        L3
         LINENUMBER 13 L3
         RETURN
        L4
         LOCALVARIABLE this Lgeneric/GenericTest; L0 L4 0
         LOCALVARIABLE integers Ljava/util/ArrayList; L1 L4 1
         // signature Ljava/util/ArrayList<Ljava/lang/Integer;>;
         // declaration: integers extends java.util.ArrayList<java.lang.Integer>
         MAXSTACK = 2
         MAXLOCALS = 2
    
  3. 결과적으로 외부에서 타입을 지정할 수 있게하는 기술이다.

    • 생성시에는 타입없이 내부에서 생성
    • 외부인 변수에 할당시 타입지정
     ArrayList<Integer> integers = new ArrayList();
    
    • 외부에서 타입을 맘대로(나 인티져 넣을거야, 나 스트링 넣을 거야~)지정해서 편하게 사용한다.
     final ArrayList<Integer> integers = new ArrayList<>();
     final ArrayList<String> strings = new ArrayList<>();
    

장점

  1. (사용시 특정타입으로 제한하여) 타입안정성을 얻을 수 있다.
  2. 캐스팅해서 사용하던 것을 자동으로 처리되도록 됨.

my) [여러 타입가능한 자료구조]사용시 get하면 object로 내려와 (1) 타입확인 (2) 다운캐스팅 해서 사용했어야 했는데 사라지게 된다.

  • jdk 5부터 추가되었다.
  • 다른type, 다른형을 막아주기 때문에 특별한 이유가 없으면 여러타입가능 자료구조는 무조건 generic을 사용해라
@Test
void name() {
    final List integersNoGeneric = new ArrayList();
    integersNoGeneric.add(1);

    // 여러타입 가능한 것에 대해 제네릭으로 타입을 안정해준다면,
    // 1. <받아올 때> Object형으로 내려온다. -> <받아 사용하는 쪽에서> 다운캐스팅 해야 쓸 수 있다.
    final Object o = integersNoGeneric.get(0);

    // -> [넣은 것과 동일한 형으로 직접 다운 캐스팅]해야줘야   다시 해당 type으로 사용할 수 있다.
    final Integer integer = (Integer) integersNoGeneric.get(0);

    // 2. 다른 Type을 넣어버릴 수 도 있어서 type불안정하다.
    integersNoGeneric.add("1");
    // -> <사용하는 쪽, 타입까지 직접확인해줘야>해서 -> 사용할 때 [넣은 것과 동일한지 type확인도 미리] 다운캐스팅전에 해줬어야했다.
    final Object o1 = integersNoGeneric.get(0);
    if (o1 instanceof Integer) {
        final Integer o11 = (Integer) o1;
    }

}

Type과 제네릭은 영역 좁히는게 반대다?

ArrayList는 구현체 List가 인터페이스(추상체)

  • type을 받아줄 변수는 인터페이스(추상체)로 받아야, 메서드들이 분기를 대신하는 구상체(전략객체)를 받을 수 있는 추상체(인터페이스) 파라미터로 정의되어있음을 인지해야한다.

my) Type의 경우) 정의하는 변수와 파라미터전체 분기들을 내포되도록 추상체로 받고, 사용하는 할당과 메소드호출 인자에는 분기가 결정된 구상체를 넣어주자.

  1. 파라미터를 분기결정된 구상체로 받는다면?

    • 다른 구상체들을 못받는다.
     void 파라미터를_분기결정된_구상체로_넣는경우(ArrayList<Integer> value) {
         System.out.println(value);
     }
    
     // 1. default로 변수추출시 ArrayList 풀로 형이 정해지는데
     final ArrayList<Integer> integersArrayList = new ArrayList<>();
        
     // 2. vs 정의하는 변수와 파라미터는 전체분기들을 내포하는 추상체로 받아라 -> 할당과 메소드호출인자에는 분기결정한 구상체를 넣어주자.
     final List<Integer> integersList = new ArrayList<>();
        
     //3. 정의하는 메소드파라미터를 분기결정된 구상체 -> 해당 구상체밖에 못들어온다.
     파라미터를_분기결정된_구상체로_넣는경우(integersArrayList); // 에러안남.
     파라미터를_분기결정된_구상체로_넣는경우(integersList); // 에러남 (구상체 정의 -> 추상체를 넣음)
     }
    
  2. 파라미터를 분기내포하는 추상체(인터페이스)로 받는다면?

    • 파라미터를 추상체로 받는다? -> 분기들을 내포하여 구상체들을 다 받을 수 있는 상황
    • 인터페이스로 분리해서 내가 어떤이점을 얻고 있지? 잘활용하고 있나 고민할 것
      • 전략패턴에서는, 파라미터를 전략인터페이스로 정의후
      • 호출시점에 전략객체.전략메서드() 호출시켜서 실시간성 부여
      • 사용하는 메인에서 메서드호출시 new 구상체를 던져 분기를 정해줌
     void 파라미터를_분기내포하는_추상체로_넣는경우(List<Integer> value) {
             System.out.println(value);
         }
    
     파라미터를_분기내포하는_추상체로_넣는경우(integersArrayList); // 에러안남.
     파라미터를_분기내포하는_추상체로_넣는경우(integersList); // 이것도 에러 안남.
        
     //메소드 호출시, 여러분기들의 구상체를 다 받을 수 있다.
    

선언시, 변수 생략된T <> 형 + 파라미터, 클래스 <T>형의 제네릭에서의 추상체 <—> 호출시구상체 <특정형> 을 지정해서 사용한다.

my) intellij의 생성자 파라미터를 T로 지정하여 Generic을 생성한 선T -> 호구지 (선언시 T형 -> 호출시 구상형을 지정해서 사용)

  • 호출부에서 T형을 집어넣을 수 없으니 -> 일단 아무형이나 넣어서 생성자에 넣고 내부에서 T로 바꿔서 정의한다.
  • T형으로 파라미터를 받아도, T는 빨간줄이다. 빨간줄 [Create type T paramter] 를 통해 class 전체를 선택해서 클래스자체도 Class<T>형으로 지정해준다.
    • 필드가 아닌, 파라미터 속 T에 대해서만 해당 T형 파라미터 자동생성이 활성화된다.

제네릭 추상체 <T>형으로 box클래스 정의하기

  • 외부에서 생성자를 통해 빨간줄로 생성할 땐, 아무형이나 집어넣고 -> 내부에서 T형으로 정의후 -> Create T type parameter를 통해 class에도 <T>로 선언되도록 해야한다. (선T호구지)
  1. 제네릭 추상체 <T>형 클래스 만들기

     class Box<T> {
        
         private final T value; // 1. T형 변수을 감싸는 클래스 box를 만든다. T형이란 미정이므로 -> 2. 클래스 정의시 <T> 제네릭을 달아줘야한다.
        
         //3. T형변수를 변수를 생성자를 통해 초기화하고
         public Box(final T value) {
             this.value = value;
         }
        
         //4. T형 변수를 getter로 응답한다.
         public T getValue() {
             return value;
         }
     }
    

my) 제네릭은 Type과 반대로, 변수 및 생성자에서는 제네릭 구상체<특정형> 변수에 , 할당(사용)생략된T형<>을 넣어줘, 변수와 생성자에서 좁힌다.

  1. 제네릭 추상체 생략된T형 <>변수, 제네릭 구상체 <특정형> 변수 각각 생성자 대입해보기

    • 변수가 제네릭 구상체이라면, 할당도 해당하는 형밖에 못넣는다.특정형>
     @Test
     void name2() {
         final Box<String> stringBox = new Box<>("");
         //final Box<String> stringBox = new Box<>(1); // 제네릭은 변수와 생성자에서 좁힌다. 할당시 제네릭추상체-생략된T<>형으로 넓게준다.
     }
    

할당(사용)시 생략된T형<>을 넣지만, 좁히는 변수와생성자 자리에 들어간<특정형> 변수가 추상체인 인터페이스라면, 넓히는 할당 자리에 <특정형 인터페이스의 구상체형들> 들도 할당가능하다

넓은 할당자리에 특정형을 집어넣으면 해당 클래스만 사용가능해진다.
  1. 무엇이든 다 들어갈 수 있는 제네릭 추상체 Box<T>에 대해서 제네릭 구상체 Box<특정형>을 지정해줬지만, 그 특정형이 구상체들을 가진다면 우변에서 구상체들의 할당이 가능하다

     // 8.제네릭에서는 생략된T형인 Box<>에 비해 Box<특정형>은 제네릭구상체다. -> 할당이 <특정형>의 자체 및 그 구상체들만 가능해진다.
     // -> Animal이 제네릭 특정형으로 들어가 그 제네릭 구상체가 되었더라도, 그것의 구상체들인 Dog, Cat은 할당될 수 있다.
     new Box<Animal>();
     final Box<Animal> animalBox = new Box<Animal>();
    

좌항 좁히는 변수<특정형>의 제네릭구상체라면 -> 마찬가지 좁히는 생성자에 들어갈 변수도 특정형으로 처리되어야함. 할당(사용, 특정분기)은 생략된T형(제네릭추상체)의 기본생성자 호출 불가

변수만 좁히고, 할당은 안좁혔을 때 -> 기본 생성자에 인자가 들어간다면 넓은 할당형에 쓰던 것이 생성자로 들어갈 예정이니 에러가난다 -> 인자를 안받는 생성자를 만들어줘서 생략된T형의 인자가 못들어가게 막아주자.
  • List(추상) = ArrayList(구상)는 내가 특정분기를 우항에서 할당하여 사용하는 것이다.

    • 하지만 현 상황에서는 제네릭 구상체인 Box<Animal> (구상) = new Box <> () (추상:생략된T형)을 넣는 것은 정반대로 주는 것
    • **좌변 변수정의에서 구상체 특정형으로 제한되어있다면, 뭔짓을 한들 생략된 T형 = 제네릭추상체 사용시, 생성자에 건네주는 것으로 class내부 T형 변수를 초기화해줄 수 없기 때문이다. **
      // 9. 좁히는 변수만 좁히고, 할당은 안좁혔을 때 -> 기본 생성자에 인자가 들어간다면 넓은 할당형에 쓰던 것이 생성자로 들어갈 예정이니 에러가난다 
      final Box<Animal> animalBox = new Box<>(); // 생성자쪽에서 에러
      // -> 인자를 안받는 생성자를 만들어줘서 생략된T형의 인자가 못들어가게 막아주자.
      // -> default생성자(파라미터T가 안되니, 파라미터를 아예 안받고, 내부 T형은 null로 초기화시켜주는)를 추가해주자.
        
      public Box() {
          this(null);
      }
    
      final Box<Animal> animalBox = new Box<>();
    
    • 마치 제네릭 구상체 변수제네릭추상체 할당이 가능해 보이지만, 생성자가 파라미터를 외부에서 아예 안받고 내부적으로 자체 처리해서 제네릭추상체를 감당하고 있기 때문에 그렇게 보이기만 할 뿐이다.
      • 특수처리되어서 에러는 사라졌으나 사용은 불가하다.?!

할당시 제네릭구상체<특정형>을 지정해주면, 내부의 모든 것이 T->특정형으로 바뀐다. 할당은 변수보다 넓어야하니 변수는 더 좁은 특정형or특정형의 구상체들로 받아줘야할 것이다.

// 11. 이미 좁힌 제네릭변수가 있을 때, 할당은 생략된T형 <>으로 넓게 줄 수 있다. (넓은 형으로 들어갈 생성자만 잘 처리되면)
final Box<Animal> animalBox2 = new Box<>();
// 12. 할당에서 특정형을 제네릭구상체로 지정해주면, 내부의 모든 로직이 해당 classType으로 바껴서
// 12-1. 동일형으로 좁힌 변수에는 잘들어간다.
final Box<Animal> animalBox3 = new Box<Animal>();
// 12-2. 만약 넓은 할당쪽이 더 좁아졌다면? (좁힌 변수보다 더 좁은) Type과 다르게 안들어가진다. -> 변수와 생성자가 더 좁아야한다.
final Box<Animal> animalBox4 = new Box<Dog>(); // 에러

좁았던, 제네릭 변수정의 - 생성자 인자의 관계에선, Type처럼 변수-추상체라면, 생성자인자-구상체의 관계를 가진다.

//13. 할당시 생략된T형<> -> 변수-생성자 인자 관계에서는, Type처럼 변수 추상체 - 생성자=메서드호출 구상체가 가능하다.
//13-1. 할당시 생략된 T<>으로 내부가 T로 구성된 데이터에, 좁은 생성자 인자를 넣어줄 때,  추상체에 구상체가 들어가므로 괜춘하다.
final Box<Animal> animalBox4 = new Box<>(new Dog());
//13-2. 할당이 넓은 상태라면,  변수-추상체 생성자-구상체로서 동일한 것도 생성자로 넣을 수 있다.
final Box<Dog> animalBox5 = new Box<>(new Dog());

실사용 와일드카드

실사용은 좁은 변수제네릭에 와일드카드를 지정해주고 넓은 할당은 생략된T형이 기본인 상태에서, 넣어줄 생성자에 변수의 좁은제네릭에 들어있는 구상체or상속or와일드카드 범위의 객체를 넣어 사용해줘야한다.

//14. 넓은 제네릭할당에 대해
new Box<>();
//14-1. 좁은 변수의 제네릭에 와일드카드(upper bound) 적용주자.
final Box<? extends Animal> extendsAnimalBox = new Box<>();
//14-1-1. 실사용은 변수에 대해 생성자에 넣어줄 놈은 더 구상체로 넣어주면 된다.
final Box<? extends Animal> extendsAnimalBox2 = new Box<>(new Animal(){});
final Box<? extends Animal> extendsAnimalBox3 = new Box<>(new Dog());

상속자 자리에 와일드카드변수(제네릭추상체) 구현체를 상속한 것들도 넣어줄 수 있다.

//15. [Animal의 구현체인 Dog]를 상속한 것들도 제네릭 상속자 자리에 넣어줄 수 있다.
// -> 술을 먹어서 개가 된 돌범(상속) 정의
class Dolbum extends Dog { }
//15-2. 제네릭 변수-상속자의 관계는 구상체 자식들 하위단계를 다 넣어줄 수 있다.
final Box<? extends Animal> extendsAnimalBox4 = new Box<>(new Dolbum()); 

넓은 할당쪽 제네릭은 사실, 생성자에 들어갈 객체의 범위가 default다.

  • 할당쪽 제네릭은 와일드카드 사용불가
  • 특정형 사용시, 생성자도 그걸로
  • 생성자를 넣어줄 시, 그거랑 동일한 것이 default

와일드카드에 대해, 상속자들어갈놈 -> 할당제네릭의 default가 안정해줄 경우, 어떤 Type으로 잡힐까?

  • 제일 defaultType은 제일 상단이 나온다.
    • Type입장에서는 열려있는 최고 부모를 변수type으로 받아줘야한다.
//16. 와일드카드에 대해, 상속자들어갈놈 -> 할당제네릭의 default가 안정해줄 경우, 어떤 Type으로 잡힐까?
final Box<? extends Animal> extendsAnimalBox5 = new Box<>();
//16-1. upperbound를 가진 extends 와일드카드의 경우, 제일 상단인 Animal이 나오고
final Animal value = extendsAnimalBox5.getValue();

final Box<? super Animal> superAnimalBox5 = new Box<>();
//16-2. lowerbound를 가진 super의 경우, 제일 하단인 Animal이 나올 것 같지만, 
//    -> 역시 제일 상단인 Object가 나온다.
//    --> 와일드카드의 default는 제일 위아래쪽 상관없이     상단 bound ~ Object까지 나오게 된다.
final Object value2 = superAnimalBox5.getValue();

제네릭의 클변 메파 정의/ 좁 변수(와) + 메파 제한자(와X) -> 생성자/ 넓 할당

  • Type은 정의하는 변수, 메서드 파라미터에는 추상체를 / 사용하는 할당, 메소드호출에는 구상체를 넣어서 분기처리를 밖으로 제거해줄 수 있다.

제네릭은 정의(class 속 변수, method 파라미터)는 T(E)로 / 변수(와일드카드)와 method 파라미터는 <T> (<E>) 좁게 + 생성자(좁힌 것의 구현체&자식들) + / 할당은 넓게(default로 두면 알아서 생성자에 사용되는 구현체에 맞게)

메소드 파라미터는 제네릭E로 정의해주지만 (클래스 변수 타입 T형처럼) /히는 작업이 접근제한자와 응답사이에 좁은 꺽쇠<E>로 정의 (좁은 변수Type처럼)

//1. T는 클래스의 변수 정의하느라 썼으니
//   -> 파라미터는 E로 받지만, 제한자 쪽에다가 꺽쇠붙인 <E>로 좁혀준다.
//   -> <E>는 별다르게 좁혀주지 않는 default 어느타입이든 가능임.
public <E> void some(E e) {

}
//2. 제네릭은 변수에 좁게 정의하고, 그 와일드카드에 해당 범위의 객체를 생성자에 넣어주니, 변수까지 정의해서 객체를 생성한다.
new Box<>();
final Box<? super Animal> superAnimalBox6 = new Box<>();

final Object value1 = superAnimalBox.getValue(); // 와일드카드에 구상체 객체를 않넣어주면, T형의 인스턴스변수를 Object로 잡아 반환한다.
final Box<?> value6 = superAnimalBox; //객체는 ? 와일드카드에 그냥 넣어줄 수 있다?

value6.some(""); // E는 기본적으로 Object부터 시작하여 -> 메서드호출시 어떤Type이든지 호출 가능해진다.
value6.some(1); // E는 기본적으로 Object부터 다 받을 수 있다.

좁은 변수처럼, 메소드 파라미터는 제한자 쪽에서 좁힌 Type <E>와일드카드 or 직접 꺽쇠에 제한을 걸수 있다.

  • 잘보면 응답Type void보다 앞쪽에서, 파라미터에 대한 와일드카드 제한을 거는 것이다.
//3. 메서드의 파라미터의 좁힘 제한은 응답Type앞쪽에서 꺽쇠<E>제네릭으로 걸 수 있다.
public <E extends String> void some(E e) {

}

//4. 
value6.some(""); // string 이하로 제한건 E에는.. string 및 string상속한 것만 들어간다.
value6.some(1); // 에러
참고) E는 와일드카드 범위내에서 사용된 Type이 추론끝나면 컴파일 타임에서 해당Type으로 대체되어 사라진다.
참고) 정의를 해주는 class옆 꺽쇠 + method파라미터용 앞쪽 꺽쇠에서는 와일드카드 사용이 안된다. 변수 제네릭에서만 와일드카드 가능
// 와일드카드 사용 좁힘시 에러
public <? extends String> void some(E e) {

}

// 와일드카드 사용시 에러
class Box2<? extends Animal> {

}
추가 메서드 파라미터 E를 좁힐 땐, 와일드카드가 아닌 꺾쇠<E extends >로 좁히며, super불가 + 와이드카드 불가다..

메서드에서 class와 동일한 타입추론 변수를 사용해도 된다. but…

public <T> void some(T t) {

}
메서드 파라미터의 변수는 지역변수라 … class쪽과 다르다.
//2. class내부에서 동일한 타입추론 변수T를 사용한 메서드는.. 파라미터로 T가 추론된 Dog형만 사용가능할까?
// -> class정의시에서는 T로 똑같은게 사용된 것 같지만, 메서드 파라미터의 변수 -> 지역변수 -> class 제네릭 타입변수 T랑은 별개다.
// -> 타입추론한 Dog와 별개로, 제한안걸린 T로서 모든 타입이 다들어간다.
dogBox.some(new Object());
dogBox.some(1);
dogBox.some("1");
  • E로 바꿔도 똑같이 지역변수로 적용되므로… 개별적 처리가 된다.
  • 되도록이면 이름 중복 안되게 하는게 좋다.

P-E C-S(제네릭와일드카드 사용처)

  • 생성관련된 메소드의 파라미터에 대해서는 extends 상속한 것들만 들어올 수 있게
    • ArrayList의 생성자 파라미터 정의시
    • ArrayList의 add메서드들의 파라미터 정의시
  • 소비하는 곳에서는 super를 써서 제네릭을 정의해라

ArrayList를 타고 들어가서 ? extends가 어디서 사용되는지 검색 해보자.

  • 생성자 파라미터에서 E의 상속한 자식들(extends)만 들어오도록 제한을 걸어준다.

    image-20220303003915949

  • add의 파라미터할때도 ? extends를 사용한다.

    image-20220303004054586

? super는 소비되는 곳에서 사용된다.

  • super는 어떤 값이든 들어와도 되며, 소비되는 곳에 사용된다.

    • 소비만 되므로, 실제 어떤 타입인지 중요하지 않다.

    image-20220303181203578

  • action은 람다 학습시 필요하다.

    • 람다로 넘겨줄 때, Consumer, Predicate 사용시 많이 이용된다.
       public void forEachRemaining(Consumer<? super E> action) {