[java] :: Java 8의 (이중 콜론) 연산자

Java 8 소스를 탐색 하고이 코드의 특정 부분이 매우 놀랍습니다.

//defined in IntPipeline.java
@Override
public final OptionalInt reduce(IntBinaryOperator op) {
    return evaluate(ReduceOps.makeInt(op));
}

@Override
public final OptionalInt max() {
    return reduce(Math::max); //this is the gotcha line
}

//defined in Math.java
public static int max(int a, int b) {
    return (a >= b) ? a : b;
}

인가 Math::max하는 방법 포인터처럼 뭔가? 일반적인 static방법은 어떻게 변환 IntBinaryOperator됩니까?



답변

일반적 으로 다음을 reduce사용 하여 메소드를 호출합니다 Math.max(int, int).

reduce(new IntBinaryOperator() {
    int applyAsInt(int left, int right) {
        return Math.max(left, right);
    }
});

호출하기 위해서는 많은 구문이 필요합니다 Math.max. 람다 표현이 시작됩니다. Java 8부터는 훨씬 짧은 방식으로 동일한 작업을 수행 할 수 있습니다.

reduce((int left, int right) -> Math.max(left, right));

어떻게 작동합니까? Java 컴파일러는 “감지” int합니다 int. 두 개의을 허용 하고 하나를 리턴 하는 메소드를 구현하려고합니다 . 인터페이스의 유일한 유일한 메소드 IntBinaryOperator( reduce호출하려는 메소드의 매개 변수)의 공식 매개 변수와 같습니다 . 따라서 컴파일러는 나머지 작업을 수행 IntBinaryOperator합니다. 구현하려는 것으로 가정합니다 .

그러나 Math.max(int, int)자체의 공식 요구 사항을 충족하므로 IntBinaryOperator직접 사용할 수 있습니다. Java 7에는 메소드 자체를 인수로 전달할 수있는 구문이 없으므로 (메소드 결과 만 전달할 수 있지만 메소드 참조는 전달할 수 없음) ::구문이 Java 8에서 도입되어 메소드를 참조합니다.

reduce(Math::max);

이것은 런타임에 JVM이 아니라 컴파일러에 의해 해석됩니다! 세 가지 코드 스 니펫에 대해 서로 다른 바이트 코드를 생성하지만 의미 적으로 동일하므로 마지막 두 코드는 IntBinaryOperator위 의 구현 의 짧은 (그리고 아마도 더 효율적인) 버전으로 간주 될 수 있습니다 !

( 람다 식 번역 참조 )


답변

::메소드 참조라고합니다. 기본적으로 단일 방법에 대한 참조입니다. 즉, 이름으로 기존 방법을 나타냅니다.

간단한 설명 :
아래는 정적 메소드에 대한 참조 예입니다.

class Hey {
     public static double square(double num){
        return Math.pow(num, 2);
    }
}

Function<Double, Double> square = Hey::square;
double ans = square.apply(23d);

square객체 참조처럼 전달되고 필요할 때 트리거 될 수 있습니다. 실제로, 그것은 객체의 “정상적인”방법에 대한 참조처럼 쉽게 사용될 수 있습니다 static. 예를 들면 다음과 같습니다.

class Hey {
    public double square(double num) {
        return Math.pow(num, 2);
    }
}

Hey hey = new Hey();
Function<Double, Double> square = hey::square;
double ans = square.apply(23d);

Function위의 기능 인터페이스 입니다. 완전히 이해하려면 ::기능적 인터페이스도 이해하는 것이 중요합니다. 분명히, 기능적 인터페이스 는 단 하나의 추상 메소드를 가진 인터페이스입니다.

기능적인 인터페이스의 예는 Runnable, Callable, 및 ActionListener.

Function위의 방법은 하나만있는 기능적인 인터페이스입니다 apply. 하나의 인수를 사용하여 결과를 생성합니다.


::s가 멋진 이유 는 다음 같습니다.

메소드 참조는 람다 표현식 (…)과 동일한 처리를하는 표현식이지만 메소드 본문을 제공하는 대신 기존 메소드를 이름으로 참조합니다.

예를 들어 람다 본문을 쓰는 대신

Function<Double, Double> square = (Double x) -> x * x;

당신은 단순히 할 수 있습니다

Function<Double, Double> square = Hey::square;

런타임 square시이 두 메소드는 서로 정확히 동일하게 작동합니다. 바이트 코드는 동일하거나 동일하지 않을 수 있습니다 (위의 경우 동일한 바이트 코드가 생성됩니다. 위의 코드를 컴파일하고로 확인하십시오 javap -c).

충족시키는 유일한 주요 기준은 다음 같습니다. 제공하는 메소드는 객체 참조로 사용하는 기능 인터페이스의 메소드와 유사한 서명을 가져야합니다 .

아래는 불법입니다 :

Supplier<Boolean> p = Hey::square; // illegal

square인수를 기대하고를 반환합니다 double. 공급 업체get메소드 는 값을 반환하지만 인수를받지 않습니다. 따라서 오류가 발생합니다.

메소드 참조는 기능 인터페이스의 메소드를 나타냅니다. 언급했듯이 기능 인터페이스에는 각각 하나의 방법 만있을 수 있습니다.

더 많은 예 : Consumeraccept메소드 는 입력을 받지만 아무것도 반환하지 않습니다.

Consumer<Integer> b1 = System::exit;   // void exit(int status)
Consumer<String[]> b2 = Arrays::sort;  // void sort(Object[] a)
Consumer<String> b3 = MyProgram::main; // void main(String... args)

class Hey {
    public double getRandom() {
        return Math.random();
    }
}

Callable<Double> call = hey::getRandom;
Supplier<Double> call2 = hey::getRandom;
DoubleSupplier sup = hey::getRandom;
// Supplier is functional interface that takes no argument and gives a result

위의 getRandom인수를 취하지 않고를 반환합니다 double. 따라서 다음과 같은 기준을 만족하는 모든 기능 인터페이스를 사용할 수 있습니다. 인수를double 사용 하지 않고 리턴 할 수 있습니다.

또 다른 예:

Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));
Predicate<String> pred = set::contains;
boolean exists = pred.test("leo");

매개 변수화 된 유형의 경우 :

class Param<T> {
    T elem;
    public T get() {
        return elem;
    }

    public void set(T elem) {
        this.elem = elem;
    }

    public static <E> E returnSame(E elem) {
        return elem;
    }
}

Supplier<Param<Integer>> obj = Param<Integer>::new;
Param<Integer> param = obj.get();
Consumer<Integer> c = param::set;
Supplier<Integer> s = param::get;

Function<String, String> func = Param::<String>returnSame;

메소드 참조는 다른 스타일을 가질 수 있지만 기본적으로 모두 동일한 것을 의미하며 간단히 람다로 시각화 할 수 있습니다.

  1. 정적 방법 ( ClassName::methName)
  2. 특정 객체의 인스턴스 메소드 ( instanceRef::methName)
  3. 특정 객체의 슈퍼 방법 ( super::methName)
  4. 특정 유형의 임의 객체의 인스턴스 메소드 ( ClassName::methName)
  5. 클래스 생성자 참조 ( ClassName::new)
  6. 배열 생성자 참조 ( TypeName[]::new)

추가 참조는 http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html을 참조 하십시오 .


답변

응 그것은 사실이야. ::연산자 방법 – 참조에 사용된다. 따라서 클래스의 정적 메소드 또는 객체의 메소드를 사용하여 정적 메소드를 추출 할 수 있습니다 . 생성자에도 동일한 연산자를 사용할 수 있습니다. 여기에 언급 된 모든 사례는 아래 코드 샘플에 예시되어 있습니다.

오라클의 공식 문서는 여기 에서 찾을 수 있습니다 .

기사 에서 JDK 8 변경 사항을 더 잘 이해할 수 있습니다 . 에서 방법 / 생성자 참조 섹션 코드 예가 또한 제공된다 :

interface ConstructorReference {
    T constructor();
}

interface  MethodReference {
   void anotherMethod(String input);
}

public class ConstructorClass {
    String value;

   public ConstructorClass() {
       value = "default";
   }

   public static void method(String input) {
      System.out.println(input);
   }

   public void nextMethod(String input) {
       // operations
   }

   public static void main(String... args) {
       // constructor reference
       ConstructorReference reference = ConstructorClass::new;
       ConstructorClass cc = reference.constructor();

       // static method reference
       MethodReference mr = cc::method;

       // object method reference
       MethodReference mr2 = cc::nextMethod;

       System.out.println(cc.value);
   }
}


답변

그것은 조금 늦게 보이지만 여기에 내 2 센트가 있습니다. 람다 표현식은 익명 메소드를 만드는 데 사용됩니다. 기존 메소드를 호출하는 것 외에는 이름을 사용하지 않고 메소드를 직접 참조하는 것이 더 명확합니다. 그리고 메소드 참조 는 메소드 참조 연산자를 사용하여 그렇게 할 수 있습니다.:: .

각 직원의 이름과 등급이있는 다음과 같은 간단한 수업을 고려하십시오.

public class Employee {
    private String name;
    private String grade;

    public Employee(String name, String grade) {
        this.name = name;
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGrade() {
        return grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }
}

어떤 방법으로 반환 된 직원 목록이 있고 직원을 등급별로 정렬하려고한다고 가정합니다. 익명 클래스 를 다음과 같이 사용할 수 있습니다 .

    List<Employee> employeeList = getDummyEmployees();

    // Using anonymous class
    employeeList.sort(new Comparator<Employee>() {
           @Override
           public int compare(Employee e1, Employee e2) {
               return e1.getGrade().compareTo(e2.getGrade());
           }
    });

여기서 getDummyEmployee ()는 다음과 같은 메소드입니다.

private static List<Employee> getDummyEmployees() {
        return Arrays.asList(new Employee("Carrie", "C"),
                new Employee("Fanishwar", "F"),
                new Employee("Brian", "B"),
                new Employee("Donald", "D"),
                new Employee("Adam", "A"),
                new Employee("Evan", "E")
                );
    }

이제 우리는 비교기 가 기능적 인터페이스 라는 것을 알고 있습니다. 기능 인터페이스 (이 하나 이상의 기본 또는 정적 방법을 포함 할 수 있지만) 정확하게 하나의 추상 메소드를 사용하여 하나입니다. Lambda 표현식은 @FunctionalInterface기능 인터페이스가 하나의 추상 메소드 만 가질 수있는 구현을 제공합니다 . 람다 식을 다음과 같이 사용할 수 있습니다.

employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // lambda exp

그것은 모두 좋아 보이지만 클래스가 Employee비슷한 방법을 제공 한다면 어떨까요?

public class Employee {
    private String name;
    private String grade;
    // getter and setter
    public static int compareByGrade(Employee e1, Employee e2) {
        return e1.grade.compareTo(e2.grade);
    }
}

이 경우 메소드 이름 자체를 사용하는 것이 더 명확합니다. 따라서 메소드 참조를 다음과 같이 사용하여 메소드를 직접 참조 할 수 있습니다.

employeeList.sort(Employee::compareByGrade); // method reference

문서에 따라 네 가지 종류의 메소드 참조가 있습니다.

+----+-------------------------------------------------------+--------------------------------------+
|    | Kind                                                  | Example                              |
+----+-------------------------------------------------------+--------------------------------------+
| 1  | Reference to a static method                          | ContainingClass::staticMethodName    |
+----+-------------------------------------------------------+--------------------------------------+
| 2  |Reference to an instance method of a particular object | containingObject::instanceMethodName |
+----+-------------------------------------------------------+--------------------------------------+
| 3  | Reference to an instance method of an arbitrary object| ContainingType::methodName           |
|    | of a particular type                                  |                                      |
+----+-------------------------------------------------------+--------------------------------------+
| 4  |Reference to a constructor                             | ClassName::new                       |
+------------------------------------------------------------+--------------------------------------+


답변

::Java 8에 포함 된 새로운 연산자로 기존 클래스의 메소드를 참조하는 데 사용됩니다. 클래스의 정적 메서드와 비 정적 메서드를 참조 할 수 있습니다.

정적 메소드를 참조하기위한 구문은 다음과 같습니다.

ClassName :: methodName 

비 정적 메소드를 참조하기 위해 구문은 다음과 같습니다.

objRef :: methodName

ClassName :: methodName

메소드를 참조하기위한 유일한 전제 조건은 메소드가 기능 인터페이스에 존재하고 메소드 참조와 호환되어야한다는 것입니다.

메소드 참조는 평가 될 때 기능 인터페이스의 인스턴스를 작성합니다.

http://www.speakingcs.com/2014/08/method-references-in-java-8.html 에 있습니다.


답변

이것은 Java 8의 메소드 참조입니다. Oracle 문서는 여기에 있습니다 .

설명서에 명시된 바와 같이 …

메소드 참조 Person :: compareByAge는 정적 메소드에 대한 참조입니다.

다음은 특정 객체의 인스턴스 메소드에 대한 참조 예입니다.

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }

    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}

ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName); 

메소드 참조 myComparisonProvider :: compareByName은 오브젝트 myComparisonProvider의 일부인 compareByName 메소드를 호출합니다. JRE는이 경우 (Person, Person) 인 메소드 유형 인수를 유추합니다.


답변

:: 메소드 참조를 위해 연산자 가 java 8에 도입되었습니다. 메서드 참조는 ONE 메서드 만 실행하는 람다 식의 간단한 구문입니다. 메소드 참조의 일반적인 구문은 다음과 같습니다.

Object :: methodName

익명 클래스를 사용하는 대신 람다 식을 사용할 수 있다는 것을 알고 있습니다 . 그러나 때로는 람다 식은 실제로 일부 메서드를 호출하는 것입니다.

Consumer<String> c = s -> System.out.println(s);

코드를 명확하게하기 위해 해당 람다 식을 메서드 참조로 변환 할 수 있습니다.

Consumer<String> c = System.out::println;