Java 8 람다, Function.identity () 또는 t-> t


240

Function.identity()방법 의 사용법에 관한 질문이 있습니다 .

다음 코드를 상상해보십시오.

Arrays.asList("a", "b", "c")
          .stream()
          .map(Function.identity()) // <- This,
          .map(str -> str)          // <- is the same as this.
          .collect(Collectors.toMap(
                       Function.identity(), // <-- And this,
                       str -> str));        // <-- is the same as this.

당신이 사용해야하는 이유 어떤 이유가 Function.identity()대신 str->str(또는 그 반대). 나는 두 번째 옵션이 더 읽기 쉽다고 생각합니다 (물론 맛). 그러나 선호해야하는 "진정한"이유가 있습니까?


6
궁극적으로, 아니요, 차이가 없습니다.
fge

50
어느 쪽이든 괜찮습니다. 더 읽기 쉬운 것으로 생각하십시오. (걱정 마세요, 행복하세요)
Brian Goetz

3
t -> t더 간결하기 때문에 단순히 선호 합니다.
David Conrad

3
약간 관련이없는 질문이지만 언어 디자이너가 T 유형의 매개 변수를 사용하지 않고 반환하는 대신 identity ()가 Function 인스턴스를 반환하는 이유를 아는 사람이 있습니까?
Kirill Rakhman

함수형 프로그래밍의 다른 영역에서 중요한 의미를 갖기 때문에 "identity"라는 단어와 대화하는 데 쓸모가 있다고 주장합니다.
orbfish 2016 년

답변:


312

현재 JRE 구현 시점에서 Function.identity()항상 동일한 인스턴스를 반환하지만 각 발생은 identifier -> identifier자체 인스턴스를 생성 할뿐만 아니라 고유 한 구현 클래스를 갖습니다. 자세한 내용은 여기를 참조 하십시오 .

그 이유는 컴파일러 (의 경우, 람다 식의 사소한 본체 들고 합성법 생성한다는 것이다 x->x같음, return identifier;)이 메소드를 호출하는 기능 인터페이스의 구현을 생성하는 런타임 말한다. 따라서 런타임은 다른 대상 메소드 만 볼 수 있으며 현재 구현에서는 메소드를 분석하여 특정 메소드가 동일한 지 여부를 확인하지 않습니다.

따라서 Function.identity()대신에 사용 x -> x하면 메모리가 절약 될 수 있지만 실제로 x -> x보다 읽기 쉽다고 생각하면 결정을 내릴 수 없습니다 Function.identity().

디버그 정보가 활성화 된 상태에서 컴파일 할 때 합성 메서드에는 람다식이 포함 된 소스 코드 라인을 가리키는 라인 디버그 속성이 있으므로 Function디버깅하는 동안 특정 인스턴스 의 소스를 찾을 수 있습니다 . 반대로, Function.identity()작업을 디버깅하는 동안 반환 된 인스턴스가 발생하면 누가 해당 메서드를 호출하고 인스턴스를 작업에 전달했는지 알 수 없습니다.


5
좋은 대답입니다. 디버깅에 대한 의구심이 있습니다. 어떻게 유용 할 수 있습니까? x -> x프레임 과 관련된 예외 스택 추적을 얻을 가능성은 거의 없습니다 . 중단 점을이 람다로 설정 하시겠습니까? 보통은 (Eclipse에서 적어도) 단일 표현 람다에 중단 점을 넣어 그렇게 쉬운 일이 아닙니다
Tagir Valeev 보낸

14
@Tagir Valeev : 임의의 함수를받는 코드를 디버깅하고 해당 함수의 apply 메소드로 들어갈 수 있습니다. 그런 다음 람다 식의 소스 코드로 끝날 수 있습니다. 명시적인 람다 식의 경우 함수의 위치를 ​​파악하고 식별 함수를 통해 통과 결정을 내릴 위치를 인식 할 수 있습니다. Function.identity()해당 정보를 사용할 때 손실됩니다. 그러면 콜 체인은 간단한 경우에 도움이 될 수 있지만 원래 개시자가 스택 추적에없는 다중 스레드 평가를 생각할 수 있습니다.
Holger


13
@Wim Deblauwe : 흥미롭지 만 항상 다른 방법으로 볼 것입니다. 팩토리 메소드가 문서에서 명시 적으로 명시하지 않으면 매번 호출 할 때마다 새 인스턴스를 반환한다고 명시 할 수 없습니다. 그렇지 않다면 놀라운 일이 아닙니다. 결국, 대신 팩토리 메소드를 사용하는 한 가지 큰 이유입니다 new. new Foo(…)정확한 유형의 새로운 인스턴스를 생성하기위한 보증 Foo, 반면, Foo.getInstance(…)기존의 인스턴스 (하위 유형)를 반환 할 수 있습니다 Foo.
Holger

93

당신의 예에서 사이에 큰 차이가 없다 str -> str그리고 Function.identity()그것은 단순히 내부 보낸 사람 t->t.

그러나 때로는 사용할 수 Function.identity없기 때문에 사용할 수 없습니다 Function. 여기를보세요 :

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);

이것은 잘 컴파일됩니다

int[] arrayOK = list.stream().mapToInt(i -> i).toArray();

하지만 컴파일하려고하면

int[] arrayProblem = list.stream().mapToInt(Function.identity()).toArray();

mapToInt예상치 ToIntFunction않은 컴파일 오류가 발생 하는데 이는 관련이 없습니다 Function. 또한 방법 ToIntFunction이 없습니다 identity().


3
참조 stackoverflow.com/q/38034982/14731 대체하는 또 다른 예를 들어 i -> iFunction.identity()컴파일러 오류가 발생합니다.
길리

19
나는 선호한다 mapToInt(Integer::intValue).
shmosel

4
@shmosel은 괜찮지 만 mapToInt(i -> i)단순화하기 때문에 두 솔루션이 비슷하게 작동한다는 점을 언급 할 가치가 있습니다 mapToInt( (Integer i) -> i.intValue()). 더 명확하다고 생각되는 버전을 사용하십시오 mapToInt(i -> i).이 코드의 의도를 더 잘 보여줍니다.
Pshemo

1
메서드 참조를 사용하면 성능상의 이점이있을 수 있지만 대부분 개인적인 취향 일뿐입니다. 나는이 i -> i기능이 아닌 ID 함수처럼 보이기 때문에 더 설명 적 입니다.
shmosel

@shmosel 성능 차이에 대해별로 말할 수 없으므로 귀하가 옳을 수도 있습니다. 그러나 성능이 문제가되지 않으면 i -> iInteger를 int ( mapToInt매우 훌륭하게 제안 함)에 명시 적으로 intValue()메서드를 호출하지 않도록 매핑하는 것이 목표이기 때문에 계속 유지됩니다 . 어떻게 이 매핑을 달성 할 것입니다 것은 정말 중요하지 않습니다. 따라서 동의하지 않기로 합의하지만 성능 차이를 지적 해 주셔서 감사합니다. 언젠가는 그 점을 자세히 살펴 봐야합니다.
Pshemo

44

로부터 JDK 소스 :

static <T> Function<T, T> identity() {
    return t -> t;
}

문법적으로 올바른 한, 아닙니다.


8
이것이 객체를 생성하는 람다와 관련하여 위의 답변을 무효화하는지 또는 이것이 특정 구현인지 궁금합니다.
orbfish

28
@ orbfish : 그것은 완벽하게 일치합니다. t->t소스 코드에서 모든 발생은 하나의 객체를 생성 할 수 있으며 구현 Function.identity()하나의 발생입니다. 따라서 호출하는 모든 호출 사이트 identity()는 하나의 객체를 공유하는 반면 람다 식을 명시 적으로 사용하는 모든 사이트 t->t는 자체 객체를 생성합니다. 방법은 Function.identity()당신이 일반적으로 사용되는 람다 식을 캡슐화 팩토리 메소드를 작성하고 람다 표현식을 반복하는 대신에 메소드를 호출 할 때마다, 어떤 방법으로 특별하지 않습니다, 당신은 어떤 메모리를 절약 할 수있다 현재 구현 주어진다 .
Holger

컴파일러 t->t가 메소드가 호출 될 때마다 새 객체 의 생성을 최적화 하고 메소드가 호출 될 때마다 동일한 객체를 재활용하기 때문이라고 생각합니다.
Daniel Gray

1
@DanielGray 런타임에 결정이 내려집니다. 컴파일러 invokedynamic는 람다 식의 경우에 위치한 소위 부트 스트랩 메소드를 실행하여 첫 번째 실행에서 링크 되는 명령을 삽입 합니다 LambdaMetafactory. 이 구현은 항상 동일한 객체를 반환하는 생성자, 팩토리 메서드 또는 코드에 대한 핸들을 반환하기로 결정합니다. 또한 이미 존재하지 않는 기존 핸들에 대한 링크를 반환하기로 결정할 수도 있습니다.
Holger

@Holger 당신은 정체성에 대한이 전화가 인라인되지 않았을 가능성이 있고 잠재적으로 단정화 (및 다시 인라인) 될 것이라고 확신합니까?
JasonN
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.