애매한 잡학사전

[자바8] 람다 표현식 - 2장 본문

DEV/JAVA

[자바8] 람다 표현식 - 2장

거대한 개발자 2022. 2. 15. 21:55
반응형
'카이 호스트만의 코어 자바 8'을 기준으로 정리하였습니다.

 

 

[자바8] 람다 표현식 - 1장

'카이 호스트만의 코어 자바 8'을 기준으로 정리하였습니다. 1. 람다 표현식 - 1 ~ n번 실행할 수 있게 전달하는 코드 블록 - 자바에는 함수 타입이 없기 때문에 객체로 표현 - 파라미터 변수가 있는

dev-gabriel.tistory.com

 

6.  자신만의 함수형 인터페이스 구현

    - 표준 함수형 인터페이스가 적합하지 않을 경우 직접 구현해야 한다.

/* 함수형 인터페이스 */
@FunctionalInterface
public interface PixelFunction {
    Color apply(int x, int y);
}

/* 메서드 구현 */
BufferImage createImage(int width, int height, PixelFunction f) {
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < hegiht; y++) {
            Color color = f.apply(x, y);
            image.setRGB(x, y, color.getRGB());
        }
    }
    return image;
}

/* 람다 표현식 */
BufferedImage frenchFlag = createImage(150, 100, 
    (x, y) -> x < 50 ? color.BLUE : x < 100 ? Color.WHITE : Color.RED);

    - 함수형 인터페이스에는 @FunctionalInterface 어노테이션을 붙여야 한다. 

    - 컴파일러가 어노테이션이 붙은 엔티티를 검사하는데 추상 메서드가 하나만 있는 인터페이스인지 검사한다.

    - 자바독 페이지에 해당 인터페이스가 함수형 인터페이스라는 문장을 둔다.

 

7. 람다 표현식과 변수 유효 범위

    7-1. 람다 표현식의 유효 범위

        - 람다 표현식의 구현부는 유효 범위가 중첩 블록과 같다. 

        - 이름 충돌(name conflicts) 규칙과 이름 가리기(shadowing) 규칙이 적용된다.

        - 람다 안에 지역 변수와 이름이 같은 파라미터나 지역 변수와 이름이 같은 지역 변수를 선언하는 일은 규칙에 어긋난다.

int first = 0;
Comparator<String> comp = (first, second) -> first.length() - second.length();

        - first 를 이미 정의 했기 때문에 람다 내부에 파라미터 first 는 사용할 수 없다.

        - 메서드 안에는 이름이 같은 두 지역 변수를 둘 수 없기 때문에 람다 표현식에도 이처럼 이름이 같은 지역 변수를 도입할 수 없다.

public class Application() {
    public void doWork() {
        Runnable runner = () -> { ...; System.out.println(this.toString()); ... }
    }
}

        - 람다 표현식 안에 있는 this 키워드는 같은 유효 범위 규칙의 영향을 받는다. 

        - this 키워드는 람다 자체를 생성하는 메서드의 this 파라미터를 의미한다.

        - 표현식 this.toString()은 Runnable 인스턴스가 아니라 Application 객체의 toString 메서드를 호출한다.

        - 람다 표현식의 유효 범위는 doWork 메서드 안에 중첩되고, this는 이 메서드 내부 어디서든 의미가 같다.

    7-2. 바깥쪽 유효 범위에 속한 변수 접근

public static void repeatMessage(String text, int count) {
    Runnable r = () -> {
        for (int i = 0; i < count; i++) {
            System.out.println(text);
        }
    };
    new Thread(r).start();
}



repeatMessage("Hello", 1000);    // 별도의 스레드에서 Hello를 1,000번 출력

        - 람다 표현식은 3가지로 구성된다.

          1) 코드 블록

          2) 파라미터

          3) 자유 변수들의 값 ( 자유 변수는 파라미터 변수도 아니고 코드 내부에서 선언한 변수도 아니다 )

        - 위 예제에서 람다 표현식은 text와 count라는 자유 변수 두 개를 이용한다.

        - 람다 표현식을 표현하는 자료 구조는 반드시 이 변수들의 값을 저장해야 한다.

        - 이를 람다 표현식이 이 값들을 캡쳐했다고 말한다.

          : 자유 변수 값을 사용하는 코드 블록을 클로저(closure)라고 한다. 자바에서는 람다 표현식이 클로저다.

        - 람다 표현식은 자신을 감싸고 있는 유효 범위(enclosing scope)에 속한 변수의 값을 캡쳐할 수 있다.

        - 람다 표현식에서는 값이 변하지 않는 변수만 참조할 수 있다.

for (int i = 0; i < n; i++) {
    new Thread(() -> System.out.println(i)).start();
}
/* i 변수의 유효 범위가 전체 루프이다. */

        - 람다 표현식은 i를 캡처하려고 하지만 i는 값이 변하는 변수이므로 규칙에 어긋난다.

        - 람다 표현식을 감싸고 있는 유효 범위에 속한 사실상 최종(effectively final)인 지역 변수에만 접근할 수 있다. 

        - 사실상 최종 변수는 절대로 수정되지 않기 때문이다.

          : 이미 final로 선언했거나 final로 선언이 가능하다.

for (String arg : args) {
    new Thread(() -> System.out.println(arg)).start();
}

        - 향사된 for 루프의 변수는 유효범위가 단일 반복(single iteration)이므로 사실상 최종이다.

        - 위 예에서는 각 반복마다 새로운 변수인 arg가 생성되고, arg 배열에 있는 다음 값을 할당 받는다.

public static void repeatMessage(String text, int count, int threads) {
    Runnable r = () -> {
        while (count > 0) {
            count--;	// 오류 - 캡처한 변수를 변경할 수 없다.
            System.out.println(text);
        }
    };
    for ( int i = 0; i < threads; i++) {
        new Thread(r).start();
    }
}

        - 사실상 최종(effectively final) 규칙 때문에 람다 표현식은 캡처한 변수를 어느 것도 변경할 수 없다.

         : 컴파일러가 모든 병행 접근 오류를 잡아내리라고 확신하면 안된다.

         : 캡처한 변수를 변경할 수 없는 것은 지역 변수에만 해당한다.

         : count가 외부 클래스의 인스턴스 변수나 정적 변수라면, 결과가 정의되지 않더라도 아무 오류도 보고하지 않는다.

 

8. 고차함수

    - 메서드에 숫자를 전달하고, 숫자를 생성하는 메서드를 만들 수 있는 것처럼 함수를 인자와 반환 값으로 사용할 수 있다.

    - 함수를 처리하거나 반환하는 함수를 고차 함수라고 한다.

    8-1. 함수를 반환하는 메서드

public static Comparator<String> compareInDirection(int direction) {
  return (x, y) -> direction * x.compareTo(y);
}

        - compareInDirection(1) 호출은 오름차순 비교자를 compareInDirection(-1) 호출은 내림차순 비교자를 반환한다.

Arrays.sort(friends, compareInDirection(-1));

        - 위의 예제처럼 비교자 인터페이스를 인자로 받는 메서드에 전달할 수 있다.

    8-2. 함수를 수정하는 메서드

public static Comparator<String> reverse(Comparator<String> comp) {
    return (x, y) -> comp.compare(x, y);
}

        - 위 예제는 함수를 인자로 받아서 수정된 함수를 반환 한다.

reverse(String::compareToIgnoreCase);

        - 대소문자를 구별하지 않는 내림차순 비교자를 얻으려면 위 예제처럼 호출해야 한다.

9. 지역클래스

    - 메서드 안에 정의한 클래스를 지역 클래스(로컬 클래스)라고 한다.

    - 보통은 그저 전술적으로 사용하는 클래스를 지역 클래스로 정의한다.

private static Random generator = new Random();

public static IntSequence randomInts(int low, int high) {
    class RandomSequence implements IntSequence {
        public int next() {
            return low + generator.nextInt(high - low + 1);
        }
        public boolean hasNext() {
            return true;
        }
    }
    
    return new RandomSequence();
}

    - 지역 클래스는 메서드 바깥에서 접근할 수 없으므로 public 이나 private으로 선언할 수 없다.

    - 클래스를 지역 클래스로 만들면 두 가지 이점이 있다.

    - 첫 번째 : 클래스 이름이 메서드의 유효 범위 안으로 숨는다.

    - 두 번째 : 람다 표현식의 변수와 마찬가지로 지역 클래스의 메서드에서 지역 클래스를 감싸고 있는 유효 범위에 속한 변수에 접근할 수         있다.

 

10. 익명클래스

    - 딱 한번만 사용할 목적으로 생성할 클래스는 익명 클래스로 만들 수 있다.

private static Random generator = new Random();

public static IntSequence randomIntsIint low, int high) {
    return new IntSequence() {
        public int next() {
            return low + generator.nextInt(high - low + 1);
        }
        public boolean hasNext() {
            return true;
        }
    }
}

 

    - 자바에 람다 표현식이 생기기 전에는 익명 내부 클래스가 러너블(runnable), 비교자(comparator), 함수 객체(functional object)를 제공하는 가장 간결한 문법이었다.

 

Comments