Java 8에서는 메소드 참조 표현식 또는 기능 인터페이스의 구현을 리턴하는 메소드를 사용하는 것이 문체 적으로 더 나은가?


11

Java 8은 기능적 인터페이스 개념과 기능적 인터페이스 를 취하도록 설계된 수많은 새로운 메소드를 추가했습니다. 이러한 인터페이스의 인스턴스는 메소드 참조 표현식 (예 :)SomeClass::someMethod람다 표현식 (예 :)을 사용하여 간결하게 작성할 수 있습니다 (x, y) -> x + y.

동료와 나는 어떤 형태를 사용하는 것이 가장 좋은지에 대해 다른 의견을 가지고 있습니다 (이 경우 "최상의"는 기본적으로 그렇지 않기 때문에 "가장 읽기 쉬운"상태로, "가장 표준적인 사례와 일치"합니다) 동등한). 특히 다음과 같은 경우에 해당합니다.

  • 해당 함수는 단일 범위 밖에서 사용되지 않습니다
  • 인스턴스에 이름을 지정하면 가독성에 도움이됩니다 (예를 들어 로직이 한 눈에 발생하는 것을 볼 수있을 정도로 단순함)
  • 한 형태가 다른 형태보다 선호되는 다른 프로그래밍 이유는 없습니다.

이 문제에 대한 나의 의견은 개인 메소드를 추가하고 메소드 참조로 참조하는 것이 더 나은 접근법이라는 것입니다. 이것이 기능이 사용되도록 설계된 방식과 같은 느낌이 들며 메소드 이름과 서명을 통해 진행중인 내용을 쉽게 전달할 수 있습니다 (예 : "boolean isResultInFuture (Result result)"). 또한 클래스에 대한 향후 개선이 동일한 검사를 사용하려고하지만 기능적 인터페이스 랩퍼가 필요하지 않은 경우 개인 메소드를 더 재사용 가능하게 만듭니다.

동료의 선호는 인터페이스의 인스턴스를 반환하는 메소드 (예 : "Predicate resultInFuture ()")를 갖는 것입니다. 나에게 이것은 기능이 의도 된 방식이 아니고 약간 어색한 느낌이 들며 실제로 이름을 통해 의도를 전달하는 것이 더 어려워 보입니다.

이 예제를 구체적으로 만들기 위해 다른 스타일로 작성된 동일한 코드가 있습니다.

public class ResultProcessor {
  public void doSomethingImportant(List<Result> results) {
    results.filter(this::isResultInFuture).forEach({ result ->
      // Do something important with each future result line
    });
  }

  private boolean isResultInFuture(Result result) {
    someOtherService.getResultDateFromDatabase(result).after(new Date());
  }
}

vs.

public class ResultProcessor {
  public void doSomethingImportant(List<Result> results) {
    results.filter(resultInFuture()).forEach({ result ->
      // Do something important with each future result line
    });
  }

  private Predicate<Result> resultInFuture() {
    return result -> someOtherService.getResultDateFromDatabase(result).after(new Date());
  }
}

vs.

public class ResultProcessor {
  public void doSomethingImportant(List<Result> results) {
    Predicate<Result> resultInFuture = result -> someOtherService.getResultDateFromDatabase(result).after(new Date());

    results.filter(resultInFuture).forEach({ result ->
      // Do something important with each future result line
    });
  }
}

한 가지 접근 방식이 더 선호되는지, 언어 설계자의 의도에 더 부합하는지 또는 더 읽기 쉬운 지에 대한 공식 또는 준공식 문서 또는 의견이 있습니까? 공식 소스를 제외하고 더 나은 접근 방식이 필요한 명확한 이유가 있습니까?


1
람다는 이유로 언어에 추가되었으며 Java를 악화시키지 않았습니다.

@Snowman 말도없이갑니다. 따라서 귀하의 의견과 제 질문 사이에 이해가 잘 안되는 관계가 있다고 가정합니다. 당신이하려는 요점은 무엇입니까?
M. Justin

2
나는 그가하고있는 요점은 람다가 현 상태를 개선하지 않았다면, 그것들을 소개하려고 귀찮게하지 않았을 것이라고 가정한다.
Robert Harvey

2
@RobertHarvey 물론입니다. 그러나 그 시점에서 람다와 메소드 참조가 동시에 추가되었으므로 이전에는 상태가 없었습니다. 이 특별한 경우가 없더라도 메소드 참조가 여전히 더 나은 경우가 있습니다. 예를 들어 기존의 공개 방법 (예 :) Object::toString. 그래서 내 질문은 내가 여기에 배치하고있는이 특정 유형의 인스턴스에서 더 나은지 여부에 대한 것입니다.
M. Justin

답변:


8

함수형 프로그래밍의 관점에서, 당신과 동료가 논의하고있는 것은 포인트 프리 스타일 ,보다 구체적으로 축소 입니다. 다음 두 가지 과제를 고려하십시오.

Predicate<Result> pred = result -> this.isResultInFuture(result);
Predicate<Result> pred = this::isResultInFuture;

이러한 운영상 동일, 첫 번째라고 pointful 두 번째 동안 스타일은 포인트 무료 스타일. "포인트"라는 용어는 후자의 경우에는 누락 된 명명 된 함수 인수 (토폴로지에서 유래)를 나타냅니다.

더 일반적으로, 모든 함수 F에 대해, 주어진 모든 인수를 취하여 F를 변경되지 않은 상태로 전달하는 래퍼 람다 인 F의 변경되지 않은 결과는 F 자체와 동일합니다. 람다를 제거하고 F를 직접 사용하는 것 (위의 뾰족한 스타일에서 뾰족한 스타일로 이동) 을 람다 미적분 에서 유래하는 용어 인 에타 감소 라고합니다 .

에타 축소에 의해 생성되지 않은 포인트 프리 표현식이 있으며, 가장 전형적인 예는 함수 구성 입니다. 유형 A에서 유형 B 로의 함수와 유형 B에서 유형 C 로의 함수가 주어지면 유형 A에서 유형 C까지의 함수로 함께 구성 할 수 있습니다.

public static Function<A, C> compose(Function<A, B> first, Function<B, C> second) {
  return (value) -> second(first(value));
}

이제 방법이 있다고 가정하자 Result deriveResult(Foo foo). 술어는 함수이므로 먼저 호출 deriveResult한 다음 파생 된 결과를 테스트 하는 새로운 술어를 구성 할 수 있습니다.

Predicate<Foo> isFoosResultInFuture =
    compose(this::deriveResult, this::isResultInFuture);

람다와 함께 뾰족한 스타일 을 사용 하여 구현compose 하는 경우isFoosResultInFuture 에도 인수를 언급 할 필요가 없으므로 compose를 사용 하여 정의 할 수 있습니다.

포인트 프리 프로그래밍은 암묵적 프로그래밍 이라고도하며 , 함수 정의에서 무의미한 (pun 의도 된) 세부 사항을 제거하여 가독성을 높이는 강력한 도구가 될 수 있습니다. Java 8은 Haskell과 같은 더 많은 기능 언어와 마찬가지로 포인트 프리 프로그래밍을 거의 지원하지 않지만 항상 에타 감소를 수행하는 것이 좋습니다. 랩핑하는 기능과 다른 동작을하지 않는 람다를 사용할 필요가 없습니다.


1
내 동료와 내가 논의하고있는 것은이 두 가지 과제에 더 가깝습니다 . Predicate<Result> pred = this::isResultInFuture;그리고 Predicate<Result> pred = resultInFuture();resultInFuture ()는 a를 반환합니다 Predicate<Result>. 따라서이 방법 참조 대 람다 식을 사용하는 경우에 대한 훌륭한 설명이지만이 세 번째 경우는 다루지 않습니다.
M. Justin

4
@ M.Justin : resultInFuture();과제 resultInFuture()는 인라인 방법을 제외하고 내가 제시 한 람다 과제와 동일합니다 . 따라서이 접근 방식에는 람다 구성 방법과 람다 자체 라는 두 가지 간접 계층이 있습니다 (아무 것도 수행하지 않음). 더 나쁜!
Jack

5

현재 답변 중 어느 것도 실제로 질문의 핵심을 다루지 않습니다. 이것은 클래스에 private boolean isResultInFuture(Result result)메소드 가 있어야하는지 여부 private Predicate<Result> resultInFuture()입니다. 그것들은 대체로 동등하지만 각각에 장단점이 있습니다.

첫 번째 방법은 메서드 참조를 만드는 것 외에도 메서드를 직접 호출하면 더 자연 스럽습니다. 예를 들어이 방법을 단위 테스트하면 첫 번째 방법을 사용하는 것이 더 쉬울 수 있습니다. 첫 번째 접근 방식의 또 다른 장점은이 메소드가 사용 방식에 신경 쓸 필요가 없으며 호출 사이트에서 분리하는 것입니다. 나중에 클래스를 변경하여 메소드를 직접 호출하면이 분리가 아주 작은 방법으로 보상합니다.

이 방법을 다른 기능적 인터페이스 또는 다른 인터페이스 조합에 적용하려는 경우 첫 번째 방법도 우수합니다. 예를 들어, 메소드 참조가 type이기를 원할 수 Predicate<Result> & Serializable있는데, 두 번째 방법은 유연성을 제공하지 않습니다.

반면, 술어에 대해 고차원 조작을 수행하려는 경우 두 번째 접근법이 더 자연 스럽습니다. 술어 에는 메소드 참조에서 직접 호출 할 수없는 여러 메소드가 있습니다. 이러한 방법을 사용하려는 경우 두 번째 방법이 우수합니다.

궁극적으로 결정은 주관적입니다. 그러나 Predicate에서 메소드를 호출하지 않으려면 첫 번째 접근법을 선호합니다.


메소드 참조를 캐스팅하여 술어에 대한 상위 순서 조작을 계속 사용할 수 있습니다.((Predicate<Resutl>) this::isResultInFuture).whatever(...)
Jack

4

더 이상 Java 코드를 작성하지 않지만 생활을위한 기능적 언어로 작성하고 기능적 프로그래밍을 배우고있는 다른 팀도 지원합니다. 람다는 가독성이 좋은 달콤한 장소를 가지고 있습니다. 일반적으로 받아 들여지는 스타일은 인라인 할 수있을 때 사용하는 스타일이지만 나중에 사용하기 위해 변수에 할당해야한다고 생각되면 메소드를 정의해야합니다.

예, 사람들은 항상 자습서의 변수에 람다를 할당합니다. 이것들은 단지 그들이 어떻게 작동하는지에 대한 철저한 이해를 돕기위한 튜토리얼입니다. 비교적 드문 상황 (if 표현식을 사용하여 두 개의 람다 중에서 선택)을 제외하고 실제로 실제로는 그렇게 사용되지 않습니다.

즉, @JimmyJames의 의견과 같이 인라인 후 쉽게 읽을 수있을 정도로 람다가 짧으면 다음과 같이하십시오.

people = sort(people, (a,b) -> b.age() - a.age());

예제 람다를 인라인하려고 할 때 이것을 가독성과 비교하십시오.

results.filter(result -> someOtherService.getResultDateFromDatabase(result).after(new Date()))...

귀하의 특정 예를 들어, 풀 요청에 개인적으로 플래그를 지정하고 길이 때문에 명명 된 메소드로 가져 오도록 요청합니다. Java는 성가신 장황한 언어이므로 시간이 지나면 자체 언어 별 관행이 다른 언어와 다를 수 있지만 그때까지는 람다를 짧고 인라인으로 유지하십시오.


2
Re : verbosity-새로운 기능 추가는 그 자체로 많은 verbosity를 줄이지 않지만 그렇게하는 방법을 허용합니다. 예를 들어 public static final <T> List<T> sort(List<T> list, Comparator<T> comparator), 명백한 구현으로 다음을 정의 할 수 있으며 다음과 같은 코드를 작성할 수 있습니다. people = sort(people, (a,b) -> b.age() - a.age());이는 크게 개선 된 IMO입니다.
JimmyJames

제가 @JimmyJames에 대해 이야기 한 훌륭한 예입니다. 람다는 짧고 인라인 될 수 있으면 크게 향상됩니다. 너무 길어서 분리하고 이름을 지정하려면 메소드를 정의하는 것이 좋습니다. 내 답변이 어떻게 잘못 해석되었는지 이해하도록 도와 주셔서 감사합니다.
Karl Bielefeldt

당신의 대답은 괜찮다고 생각합니다. 왜 투표에 실패했는지 잘 모르겠습니다.
JimmyJames
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.