Java8에서 void (void가 아닌) 메소드에 함수 유형을 지정하는 방법은 무엇입니까?


143

일급 시민으로서 어떻게 기능하는지 알아보기 위해 Java 8을 가지고 놀고 있습니다. 다음 스 니펫이 있습니다.

package test;

import java.util.*;
import java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

내가하려고하는 것은 메소드 참조를 사용하여 메소드 displayInt를 메소드에 전달하는 것 myForEach입니다. 컴파일러에서 다음과 같은 오류가 발생합니다.

src/test/Test.java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

컴파일러는 그것을 불평합니다 void cannot be converted to Void. myForEach코드가 컴파일되도록 서명에서 함수 인터페이스 유형을 지정하는 방법을 모르겠습니다 . 나는 단지의 반환 형식을 변경할 수 있습니다 알고 displayIntVoid다음 반환 null. 그러나 다른 곳으로 전달하려는 방법을 변경할 수없는 상황이있을 수 있습니다. 그대로 재사용하는 쉬운 방법 displayInt이 있습니까?

답변:


244

잘못된 인터페이스 유형을 사용하려고합니다. 이 경우 함수 유형 은 매개 변수를 수신하고 리턴 값을 가지므로 적합하지 않습니다. 대신 컨슈머 (이전 명칭 Block) 를 사용해야합니다

함수 타입은

interface Function<T,R> {
  R apply(T t);
}

그러나 소비자 유형은 다음과 같은 제품과 호환됩니다.

interface Consumer<T> {
   void accept(T t);
}

따라서 Consumer는 T를 받고 아무것도 반환하지 않는 (void) 메소드와 호환됩니다. 그리고 이것이 당신이 원하는 것입니다.

예를 들어, 목록에 모든 요소를 ​​표시하려면 간단히 람다 식을 사용하여 소비자를 만들 수 있습니다.

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

위의 경우 람다 식은 매개 변수를 받고 반환 값이 없음을 알 수 있습니다.

이제이 유형의 소비를 만들기 위해 람다 식 대신 메소드 참조를 사용하려면 String을 수신하고 void를 반환하는 메소드가 필요합니다.

나는 방법 참조의 다른 유형을 사용하지만,이 경우 사용하여 객체 메소드 참조의의 포획을 이용 할 수 println의 방법을 System.out다음과 같이 객체 :

Consumer<String> block = System.out::println

아니면 단순히 할 수 있습니다

allJedi.forEach(System.out::println);

println메소드는 acceptConsumer 의 메소드 와 마찬가지로 값을 수신하고 리턴 유형이 void이므로 적합합니다 .

따라서 코드에서 메소드 서명을 다음과 같이 변경해야합니다.

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

그런 다음 정적 메소드 참조를 사용하여 다음과 같이 소비자를 작성할 수 있습니다.

myForEach(theList, Test::displayInt);

궁극적으로 myForEach메소드를 완전히 제거 하고 간단하게 수행 할 수도 있습니다.

theList.forEach(Test::displayInt);

일등 시민으로서의 기능에 대하여

사실, 구조적 기능 유형이 언어에 추가되지 않기 때문에 Java 8은 일급 시민으로서 기능을하지 않을 것입니다. Java는 단순히 람다 식과 메소드 참조에서 기능적 인터페이스의 구현을 작성하는 대체 방법을 제공합니다. 궁극적으로 람다 식과 메서드 참조는 개체 참조에 바인딩되므로 우리가 가진 모든 것은 일류 시민으로서의 개체입니다. 중요한 것은 객체가 매개 변수로 전달되어 변수 참조에 바인딩되어 다른 메소드의 값으로 반환 될 수 있기 때문에 기능이 있다는 것입니다.


1
그건 그렇고, JDK 8 API의 최신 릴리스에서 Block변경되었습니다Consumer
Edwin Dalorzo

4
마지막 단락은 몇 가지 중요한 사실을 강조합니다. 람다 식과 메서드 참조는 구문 이상의 의미가 있습니다. JVM 자체는 익명 클래스보다 가장 효율적으로 메소드 참조를 처리 할 수있는 새로운 유형을 제공하며 MethodHandle,이를 사용하여 Java 컴파일러는 정적 메소드로 닫히지 않고 람다를 구현하여보다 효율적인 코드를 생성 할 수 있습니다. 추가 수업.
Feuermurmel

7
그리고 올바른 버전은 Function<Void, Void>입니다 Runnable.
OrangeDog

2
@OrangeDog 이것은 사실이 아닙니다. 의견 RunnableCallable인터페이스에서 쓰레드 와 함께 사용되어야한다고 쓰여 있습니다 . 그 결과 귀찮은 결과는 Sonar와 같은 일부 정적 분석 도구가 run()메소드를 직접 호출하면 불평 할 것 입니다.
데니스

순수하게 Java 배경에서 온다면 Java8 이후 기능 인터페이스와 람다가 도입 된 후에도 Java가 함수를 일류 시민으로 취급 할 수없는 경우가 무엇인지 궁금합니다.
Vivek Sethi

21

인수를 취하지 않고 결과를 반환하지 않는 인수로 함수를 수락해야 할 때, 내 의견으로는 여전히 다음과 같은 것이 가장 좋습니다.

  public interface Thunk { void apply(); }

코드 어딘가에. 함수형 프로그래밍 과정에서 'thunk'라는 단어는 이러한 기능을 설명하는 데 사용되었습니다. java.util.function에없는 이유는 내 이해를 초월합니다.

다른 경우에는 java.util.function에 원하는 서명과 일치하는 것이 있더라도 인터페이스의 이름이 내 코드의 함수 사용과 일치하지 않을 때 항상 올바른 느낌이 들지 않습니다. Thread 클래스와 관련된 용어 인 'Runnable'과 관련하여 여기에서 비슷한 점이 있다고 생각합니다. 따라서 서명이 필요할 수 있지만 독자를 혼란스럽게 할 가능성이 있습니다.


특히 Runnable, 확인 된 예외를 허용하지 않으므로 많은 응용 프로그램에 유용 하지 않습니다. Callable<Void>유용하지 않기 때문에 자신의 것으로 정의해야합니다. 추한.
Jesse Glick 17 년

확인 된 예외 문제와 관련하여 Java의 새로운 기능은 일반적으로 그와 싸우고 있습니다. 그것은 Stream API와 같은 것들에 대한 거대한 차단제입니다. 그들의 디자인이 매우 열악합니다.
user2223059

0

로 설정 반환 형식 Void대신 void하고return null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}

또는

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});

-2

소비자 인터페이스 대신 소비자 인터페이스를 사용해야한다고 생각합니다. Function<T, R> .

소비자는 기본적으로 값을 받아들이고 아무것도 반환하지 않도록 설계된 기능적인 인터페이스입니다 (예 : void).

귀하의 경우 다음과 같이 코드의 다른 곳에 소비자를 만들 수 있습니다.

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

그런 다음 myForEach코드를 아래 스 니펫으로 바꿀 수 있습니다 .

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

myFunction을 일급 객체로 취급합니다.


2
이것이 기존 답변 외에 무엇을 추가합니까?
Matthew 읽기
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.