πŸ“ Java8 Lambda, Stream API κ°•μ˜ 정리


ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ μž₯점 ?

κ΄€μ‹¬μ‚¬μ˜ 뢄리

κ΄€μ‹¬μ‚¬μ˜ λΆ„λ¦¬λž€ λ¬΄μ—‡μΌκΉŒ? 예제λ₯Ό λ“€μ–΄λ³΄μž.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); λ¦¬μŠ€νŠΈμ— μžˆλŠ” μ›μ†Œλ§ˆλ‹€ μ½œλ‘ μ„ μΆ”κ°€ν•˜λ € ν•œλ‹€.

μ΄λ•Œ forEach() λ₯Ό μ‚¬μš©ν•œλ‹€λ©΄ ?

    @Test
    public void ForEachλ₯Ό_ν™œμš©ν•˜μ—¬_μ½œλ‘ μ„_μΆ”κ°€ν•˜λŠ”_λ¬Έμžμ—΄_μž‘μ„±() {
        StringBuilder stringBuilder = new StringBuilder();

        for (Integer number : numbers) {
            stringBuilder.append(number).append(" : ");
        }

        if (stringBuilder.length() > 0) {
            stringBuilder.delete(stringBuilder.length() - 3, stringBuilder.length());
        }
    }

λ‚˜λŠ” μ›μ†Œλ§ˆλ‹€ 콜둠 μΆ”κ°€ν•˜λŠ” 것을 μ›ν•˜λŠ”λ°, forEach문을 μž‘μ„±ν•˜λ‹ˆ 이λ₯Ό μ–΄λ–»κ²Œ(How) κ΅¬ν˜„ν• μ§€μ— μ§‘μ€‘ν•˜κ³  μžˆλŠ” 것 κ°™λ‹€.

κ·Έλ ‡λ‹€λ©΄ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ 일쒅인 Stream을 μ‚¬μš©ν•œλ‹€λ©΄ ?

@Test
public void Stream을_ν™œμš©ν•˜μ—¬_μ½œλ‘ μ„_μΆ”κ°€ν•˜λŠ”_λ¬Έμžμ—΄_μž‘μ„±() {
    final String result = numbers.stream()
        .map(String::valueOf)
        .collect(joining(COLON_DELIMITER));
}

μš”μ†Œλ§ˆλ‹€ μ½œλ‘ μ„ κ²°ν•©ν•˜λŠ” 것을 λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•¨μœΌλ‘œμ„œ 개발자인 λ‚΄κ°€ 무엇을(What)을 μˆ˜ν–‰ν•  것인지에 집쀑할 수 μžˆλ‹€.

Side Effectκ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ”λ‹€.

  • Side Effectλž€ ? ν•¨μˆ˜ λ‚΄μ˜ μ‹€ν–‰μœΌλ‘œ 인해 ν•¨μˆ˜ μ™ΈλΆ€κ°€ 영ν–₯을 λ°›λŠ” 것

ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ νŠΉμ§•μ€ 지역 λ³€μˆ˜λ§Œμ„ λ³€κ²½ν•  수 있고, λ§€κ°œλ³€μˆ˜λ₯Ό λ³€κ²½ν•˜μ§€ μ•ŠλŠ”λ‹€.
즉, ν•¨μˆ˜λŠ” 같은 μΈμˆ˜κ°’μœΌλ‘œ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν–ˆμ„ λ•Œ 항상 같은 값을 λ°˜ν™˜ν•œλ‹€. (이 λ•Œ λ‹€λ₯Έ 값을 λ°˜ν™˜ν•˜λŠ” Random, Scanner 등은 ν•¨μˆ˜κ°€ μ•„λ‹ˆλ‹€).
λ§Œμ•½ ν•¨μˆ˜μ— μ°Έμ‘°ν•˜λŠ” 객체가 μžˆλ‹€λ©΄ κ·Έ κ°μ²΄λŠ” λΆˆλ³€μ΄μ–΄μ•Ό ν•˜λ©°, ν•΄λ‹Ή 객체의 λͺ¨λ“  μ°Έμ‘° ν•„λ“œλ„ λΆˆλ³€ 객체λ₯Ό 직접 μ°Έμ‘°ν•΄μ•Ό ν•œλ‹€.
ν•¨μˆ˜ λ‚΄μ—μ„œ μƒμ„±ν•œ 객체의 ν•„λ“œλŠ” κ°±μ‹ ν•  수 μžˆμ§€λ§Œ, μƒˆλ‘œ μƒμ„±ν•œ ν•„λ“œμ˜ 갱신은 외뢀에 λ…ΈμΆœλ˜λ©΄ μ•ˆλœλ‹€.
λ˜ν•œ λ‹€μŒμ— λ©”μ„œλ“œλ₯Ό λ‹€μ‹œ ν˜ΈμΆœν•œ 결과에 영ν–₯을 λ―ΈμΉ˜μ§€ μ•ŠμœΌλ©°, μ–΄λ– ν•œ μ˜ˆμ™Έλ„ μΌμœΌν‚€μ§€ μ•Šμ•„μ•Ό ν•œλ‹€.
값이 λ³€κ²½λ˜λŠ” 것을 ν—ˆμš©ν•œ 객체λ₯Ό λ©€ν‹° μŠ€λ ˆλ“œ ν”„λ‘œκ·Έλž¨μ—μ„œ μ ‘κ·Όν•œλ‹€λ©΄, 값이 μΌμ •ν•˜μ§€ μ•Šμ„ 것이닀.

ν•˜μ§€λ§Œ λ‹¨μˆœνžˆ ꡬ쑰만으둜 μˆœμˆ˜μ„±μ΄ 보μž₯λ˜μ§€λŠ” μ•Šκ³ , μž…λ ₯에 참쑰값이 μ˜€λŠ” κ²½μš°λŠ” Side-Effectκ°€ 생긴닀. 이에 λŒ€ν•œ λ‚΄μš©μ€ μ°Έμ‘° 투λͺ…성을 μ‚΄νŽ΄λ³΄μž.

μ°Έμ‘° 투λͺ…μ„±

μ°Έμ‘° 투λͺ…성은 ν•¨μˆ˜κ°€ ν•¨μˆ˜ μ™ΈλΆ€μ˜ 영ν–₯을 받지 μ•ŠλŠ” 것을 μ˜λ―Έν•œλ‹€.
또, ν•¨μˆ˜μ˜ κ²°κ³ΌλŠ” μž…λ ₯ νŒŒλΌλ―Έν„°μ—λ§Œ μ˜μ‘΄ν•˜κ³ , ν•¨μˆ˜ μ™ΈλΆ€ 세계(μž…λ ₯ μ½˜μ†”, 파일, 데이터 베이슀 λ“±)μ—μ„œ 데이터λ₯Ό 읽지 μ•ŠλŠ”λ‹€.

public static int add(int a, int b) {
  if (b > 0) {
    a++;
  }
  return a;
}

μœ„ 예제의 경우 μ›μ‹œ νƒ€μž…μΈ a의 값을 λ°”κΎΈμ—ˆλŠ”λ°, μžλ°”μ˜ μ›μ‹œνƒ€μž…μ˜ λ§€κ°œλ³€μˆ˜λŠ” call by value (λ©”μ„œλ“œ 호좜 μ‹œ κΈ°λ³Έ μžλ£Œν˜•μ˜ 값을 인자둜 μ „λ‹¬ν•˜λŠ” 방식) ν˜•νƒœλ‘œ 전달이 λ˜μ–΄, ν•¨μˆ˜λ₯Ό λ²—μ–΄λ‚˜λ„ aμ—λŠ” 영ν–₯이 μ—†λ‹€.
μ—¬κΈ°μ„œ 인자둜 μ°Έμ‘° λ³€μˆ˜λ₯Ό λ„£μ–΄μ£Όλ©΄ μ–΄λ–€ 일이 λ°œμƒν•  수 μžˆμ„κΉŒ?

public static int position(Position a, Position b) {
  a.setX(b.getX())
  return a;
}

μ΄λ ‡κ²Œ μ°Έμ‘° λ³€μˆ˜λ₯Ό λ„˜κ²¨μ£Όλ©΄ ν•¨μˆ˜ μ•ˆμ—μ„œ μ°Έμ‘° λ³€μˆ˜μ˜ 값을 λ°”κΏ€ μˆ˜λ„ 있기 λ•Œλ¬Έμ— side effectκ°€ λ°œμƒν•  수 μžˆλ‹€.
ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ€ 이런 Side Effectκ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ”, μ°Έμ‘° 투λͺ…성이어야 ν•œλ‹€.

일급 객체

ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ€ ν•¨μˆ˜μΈ λ©”μ„œλ“œκ°€ 일급 κ°μ²΄μž„μ„ λ§ν•˜λŠ”λ°, 일급 κ°μ²΄λŠ” λ‹€μŒκ³Ό 같은 νŠΉμ„±μ„ 가진닀.

  • λ³€μˆ˜λ‚˜ 데이터에 ν• λ‹Ήν•  수 μžˆμ–΄μ•Ό ν•œλ‹€.
  • 객체의 λ§€κ°œλ³€μˆ˜λ‘œ λ„˜κΈΈ 수 μžˆμ–΄μ•Ό ν•œλ‹€.
  • 객체의 λ°˜ν™˜ κ°’μœΌλ‘œ 리턴할 수 μžˆμ–΄μ•Ό ν•œλ‹€.

μžλ°”8 μ΄μ „κΉŒμ§€, λ©”μ„œλ“œλŠ” 일급 객체가 μ•„λ‹ˆμ—ˆμ§€λ§Œ, μžλ°”8의 읡λͺ… ν•¨μˆ˜μ˜ λ“±μž₯으둜 λ©”μ„œλ“œλ„ 일급 객체둜 λ‹€λ£° 수 있게 λ˜μ—ˆλ‹€.
또 이 읡λͺ… ν•¨μˆ˜λ₯Ό μ’€ 더 λ‹¨μˆœν™” ν•œ 것이 λ°”λ‘œ λžŒλ‹€ ν‘œν˜„μ‹(lambda expression)이닀.

Boxingκ³Ό UnBoxing

μ›μ‹œ νƒ€μž…μ΄ 래퍼 클래슀둜 λ³€ν™˜ν•˜λŠ” 것을 Boxing이라고 ν•˜λ©°,
래퍼 클래슀λ₯Ό μ›μ‹œ νƒ€μž…μœΌλ‘œ ν˜•λ³€ν™˜ ν•˜λŠ” 것을 UnBoxing이라고 ν•œλ‹€.
JDK1.5λΆ€ν„°λŠ” 래퍼 ν΄λž˜μŠ€μ™€ κΈ°λ³Έ μžλ£Œν˜• μ‚¬μ΄μ˜ λ³€ν™˜μ„ μžλ™μœΌλ‘œ ν•΄μ£ΌλŠ” Auto Boxingκ³Ό Auto UnBoxing κΈ°λŠ₯을 μ§€μ›ν•œλ‹€.

int i = 10
Integer integer = i; // Auto Boxing
Integer integer1 = new Integer(777); // λͺ…μ‹œμ  Boxing

int primitive = integer; // Auto UnBoxing

#### Auto Boxing이 μΌμ–΄λ‚˜λŠ” 예

μžλ°”μ—μ„œλŠ” 래퍼 ν΄λž˜μŠ€μ— λŒ€ν•œ 연산이 μ‹œλ„λ  λ•Œ, 연산을 ν•˜λ €λŠ” 두 객체λ₯Ό Auto Unboxing을 ν•˜μ—¬ μ›μ‹œνƒ€μž…μœΌλ‘œ λ³€ν™˜ ν›„,
연산을 μˆ˜ν–‰ν•˜κ²Œ λœλ‹€. 래퍼 ν΄λž˜μŠ€μ™€ μ›μ‹œ νƒ€μž… κ°„ 연산도 λ™μΌν•˜λ‹€.

final Integer integer127 = 127;
System.out.println(
  Stream.of(1, 2, 3, 4, 127)
  .filter(i -> i == integer127)
  .findFirst()
);

System.out.println(
  Stream.of(1, 2, 3, 4, 128)
  .filter(i -> i.equals(integer127))
  .findFirst()
);

두 슀트림 μ—°μ‚°μ—μ„œ μ›μ‹œ νƒ€μž… int 인 κ°’λ“€μ˜ Stream을 λ§Œλ“€κ³  각각 래퍼 클래슀 Integer 와 비ꡐλ₯Ό ν•  λ•Œ Auto Boxing이 μΌμ–΄λ‚œλ‹€.
μ΄λ•Œ, findFirst().get() 을 ν•˜κ²Œ λœλ‹€λ©΄ 래퍼 ν΄λž˜μŠ€κ°€ λ°˜ν™˜λœλ‹€.
μ΄λ ‡κ²Œ 되면 각각 μ›μ‹œ νƒ€μž…μ— Auto Boxing이 μΌμ–΄λ‚˜λ‹ˆ,
μ›μ‹œ νƒ€μž…μ— λŒ€ν•œ μŠ€νŠΈλ¦Όμ€ κΈ°λ³Έν˜• νŠΉν™” 슀트림 (IntStream, LongStream, DoubleStream)을 μ‚¬μš©ν•˜λŠ” 것이 μ„±λŠ₯상 μ’‹λ‹€.

βž• μˆ˜μ—… μ˜ˆμ œμ—μ„œ 이 뢀뢄이 μ™œ 127κ³Ό 128둜 λ‚˜λˆ„μ—ˆμ„κΉŒ ν¬λ£¨λ“€μ΄λž‘ 이야기λ₯Ό ν–ˆλ‹€.
Integer 래퍼 ν΄λž˜μŠ€λŠ” 127κΉŒμ§€ μΈμŠ€ν„΄μŠ€λ₯Ό 미리 생성해 두기 λ•Œλ¬Έμ— == μ—°μ‚°μžλ‘œλ„ 비ꡐ가 κ°€λŠ₯ν•˜μ§€λ§Œ (μ£Όμ†Œκ°’μ„ λΉ„κ΅ν•˜λŠ” 것이닀),
128λΆ€ν„°λŠ” μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜κΈ° λ•Œλ¬Έμ— 동일성 검사인 equals() λ₯Ό μ‚¬μš©ν•΄ λ°˜ν™˜ν•œλ‹€.

참고 자료