web-dev-qa-db-ja.com

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に変換されますか?

829
Narendra Pathai

通常、次のようにMath.max(int, int)を使ってreduceメソッドを呼び出します。

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コンパイラは、2つのintを受け入れて1つのintを返すメソッドを実装することを "検出"します。これは、インタフェースIntBinaryOperatorの唯一のメソッドの仮パラメータ(呼び出したいメソッドreduceのパラメータ)に相当します。そのため、コンパイラは残りをあなたのために行います - それはあなたがIntBinaryOperatorを実装したいと仮定しているだけです。

しかし、Math.max(int, int)自体がIntBinaryOperatorの正式な要件を満たしているので、直接使用することができます。 Java 7にはメソッド自体を引数として渡すことを許可する構文がないため(メソッドの結果のみを渡すことができますが、メソッドの参照はできません)、::構文がJava 8でメソッドの参照に導入されました。

reduce(Math::max);

これは実行時にJVMによってではなく、コンパイラによって解釈されることに注意してください。これは3つのコードスニペットすべてに対して異なるバイトコードを生成しますが、それらは意味的に等しいので、最後の2つは上記のIntBinaryOperator実装の短い(そしておそらくより効率的な)バージョンであると考えることができます!

ラムダ式の翻訳 も参照)

915
isnot2bad

::はメソッド参照と呼ばれます。基本的に、単一のメソッドへの参照です。つまり既存のメソッドを名前で参照します。

簡単な説明
以下は、静的メソッドへの参照の例です。

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は、機能的なインターフェースです::を完全に理解するには、機能的なインターフェースも理解することが重要です。簡単に言えば、 機能的インターフェース は抽象メソッドを1つだけ持つインターフェースです。

機能インターフェイスの例には、RunnableCallable、およびActionListenerが含まれます。

上のFunctionは、applyという1つのメソッドのみを持つ機能的なインターフェイスです。 1つの引数を取り、結果を生成します。


::sが素晴らしい理由は that

メソッド参照は、ラムダ式(...)と同じ処理を行う式ですが、メソッド本体を提供する代わりに、既存のメソッドを名前で参照します。

例えば。ラムダ本体を書く代わりに

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

簡単にできます

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

実行時には、これら2つのsquareメソッドは互いにまったく同じように動作します。バイトコードは同じでも異なっていてもかまいません(ただし、上記の場合、同じバイトコードが生成されます。上記をコンパイルして、javap -cで確認してください)。

満たすべき唯一の主要な基準は次のとおりです。提供するメソッドは、オブジェクト参照として使用する機能インターフェイスのメソッドと同様のシグネチャを持つ必要があります。

以下は違法です:

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

squareは引数を予期し、doubleを返します。 Suppliergetメソッドは値を返しますが、引数を取りません。したがって、これはエラーになります。

メソッド参照は、機能的インターフェースのメソッドを指します。(前述のように、機能的インターフェースはそれぞれ1つのメソッドのみを持つことができます)。

さらにいくつかの例: 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 を参照してください。

460
Jatin

はい、それは本当だ。 ::演算子は、メソッドの参照に使用されます。それで、それを使うことによってクラスからstaticメソッドを抽出することができますまたはオブジェクトからメソッド。コンストラクタにも同じ演算子を使用できます。ここで言及されているすべてのケースは、以下のコードサンプルに例示されています。

オラクルの公式文書は ここ にあります。

JDK 8の変更点についての概要は this articleにあります。 メソッド/コンストラクタ参照セクションでは、コード例も提供されています。

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);
   }
}
50
Olimpiu POP

::は、既存のクラスのメソッドを参照するために使用される、Java 8に含まれる新しい演算子です。クラスの静的メソッドと非静的メソッドを参照できます。

静的メソッドを参照するための構文は次のとおりです。

ClassName :: methodName 

非静的メソッドを参照するための構文は次のとおりです。

objRef :: methodName

そして

ClassName :: methodName

メソッドを参照するための唯一の前提条件は、そのメソッドが機能参照内に存在しなければならないということです。 

メソッド参照は、評価されると、機能インターフェースのインスタンスを作成します。 

に見つかりました: http://www.speakingcs.com/2014/08/method-references-in-Java-8.html

24
sreenath

これはJava 8でのメソッドリファレンスです。Oracleのドキュメントは here です。

ドキュメントに記載されているように...

メソッド参照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)です。

19
david99world

それは少し遅いようですが、ここに私の2セントです。 ラムダ式 は、無名メソッドを作成するために使用されます。既存のメソッドを呼び出す以外のことは何もしませんが、メソッドをその名前で直接参照する方が明確です。そして method reference は、メソッド参照演算子::を使ってそれを可能にします。

各従業員に名前とグレードがある次のような単純なクラスを考えてみましょう。

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("Farhan", "F"),
                new Employee("Brian", "B"),
                new Employee("Donald", "D"),
                new Employee("Adam", "A"),
                new Employee("Evan", "E")
                );
    }

Comparator は機能的なインタフェースであることがわかりました。 関数型インタフェース は、厳密に1つの抽象メソッドを持つものです(ただし、1つ以上のデフォルトメソッドまたは静的メソッドを含むことはできます)。ラムダ式は@FunctionalInterfaceの実装を提供するので、関数型インタフェースは1つの抽象メソッドしか持てません。ラムダ式は次のように使用できます。

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

docs によると、4種類のメソッド参照があります。

+----+-------------------------------------------------------+--------------------------------------+
|    | 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                       |
+------------------------------------------------------------+--------------------------------------+
18
i_am_zero

:: Operator は、メソッド参照用にJava 8で導入されました。メソッド参照は、1つのメソッドのみを実行するラムダ式の短縮構文です。メソッド参照の一般的な構文は次のとおりです。

Object :: methodName

無名クラスを使う代わりに ラムダ式 を使うことができることを私たちは知っています。しかし時々、ラムダ式は実際には単にあるメソッドへの呼び出しにすぎません、例えば:

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

コードをわかりやすくするために、そのラムダ式をメソッド参照に変えることができます。

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

::はメソッド参照として知られています。 PurchaseクラスのcalculatePriceメソッドを呼び出したいとしましょう。それから我々はそれを書くことができます:

Purchase::calculatePrice

メソッド参照はラムダ式に変換されるため、ラムダ式を書くことの短い形式としても見られます。

3
Sonu

実行時には、それらはまったく同じ振る舞いをします。バイトコードは同じでも異なってもかまいません(上記のIncaseの場合は、同じバイトコードを生成します(上記を順守してjavaap -c;をチェックしてください))

実行時に、それらはまったく同じ.method(math :: max);のように振る舞い、それは同じ数学を生成します(上記を順守し、javap -c;をチェックしてください)。

2
Alfa khatoon

return reduce(Math::max); NOT EQUAL / return reduce(max());です

しかし、これは、次のようなことを意味します。

IntBinaryOperator myLambda = (a, b)->{(a >= b) ? a : b};//56 keystrokes I had to type -_-
return reduce(myLambda);

47キーストロークを節約する あなたがこのように書くのであれば

return reduce(Math::max);//Only 9 keystrokes ^_^
2
Jude Niroshan

Java-8 Streams Reducerでは、単純な作業では、2つの値を入力として受け取り、計算後に結果を返す関数です。この結果は次の繰り返しで与えられます。

math:max関数の場合、methodは渡された2つの値の最大値を返し続け、最終的には最大の数が得られます。

2
Pramod

古いバージョンのJavaでは、 "::"やlambdの代わりに、

public interface Action {
    void execute();
}

public class ActionImpl implements Action {

    @Override
    public void execute() {
        System.out.println("execute with ActionImpl");
    }

}

public static void main(String[] args) {
    Action action = new Action() {
        @Override
        public void execute() {
            System.out.println("execute with anonymous class");
        }
    };
    action.execute();

    //or

    Action actionImpl = new ActionImpl();
    actionImpl.execute();
}

またはメソッドに渡す:

public static void doSomething(Action action) {
    action.execute();
}

::メソッド参照が何をするのかに関しては、これまでの回答はかなり完全です。まとめると、メソッド(またはコンストラクター)を実行せずに参照する方法を提供し、評価時には、ターゲット型コンテキストを提供する機能インターフェイスのインスタンスを作成します。

以下は、ArrayList WITHおよび最大::メソッド参照を使用せずに最大値を持つオブジェクトを見つける2つの例です。説明は以下のコメントにあります。


::を使わずに

import Java.util.*;

class MyClass {
    private int val;
    MyClass (int v) { val = v; }
    int getVal() { return val; }
}

class ByVal implements Comparator<MyClass> {
    // no need to create this class when using method reference
    public int compare(MyClass source, MyClass ref) {
        return source.getVal() - ref.getVal();
    }
}

public class FindMaxInCol {
    public static void main(String args[]) {
        ArrayList<MyClass> myClassList = new ArrayList<MyClass>();
        myClassList.add(new MyClass(1));
        myClassList.add(new MyClass(0));
        myClassList.add(new MyClass(3));
        myClassList.add(new MyClass(6));

        MyClass maxValObj = Collections.max(myClassList, new ByVal());
    }
}

::を使って

import Java.util.*;

class MyClass {
    private int val;
    MyClass (int v) { val = v; }
    int getVal() { return val; }
}

public class FindMaxInCol {
    static int compareMyClass(MyClass source, MyClass ref) {
        // This static method is compatible with the compare() method defined by Comparator. 
        // So there's no need to explicitly implement and create an instance of Comparator like the first example.
        return source.getVal() - ref.getVal();
    }

    public static void main(String args[]) {
        ArrayList<MyClass> myClassList = new ArrayList<MyClass>();
        myClassList.add(new MyClass(1));
        myClassList.add(new MyClass(0));
        myClassList.add(new MyClass(3));
        myClassList.add(new MyClass(6));

        MyClass maxValObj = Collections.max(myClassList, FindMaxInCol::compareMyClass);
    }
}
1
Liutong Chen

だから 私はここで率直に言って複雑すぎる答えをたくさん見ています、そしてそれは控えめな表現です。  

答えは非常に簡単です。 ::それはメソッドリファレンスと呼ばれます https://docs.Oracle.com/javase/tutorial/Java/javaOO/methodreferences.html

だから私はリンクの上にコピー&ペーストしない、あなたがテーブルまでスクロールすればあなたはすべての情報を見つけることができる。


それでは、メソッド参照とは何かを簡単に見てみましょう。

A :: B ややは、次のインラインラムダ式を置き換えます。 (params ...) - > AB(params ...)

これをあなたの質問と関連付けるためには、Javaのラムダ式を理解する必要があります。難しいことではありません。

インラインラムダ式は、defined 関数型インタフェース(メソッドが1つ以上、1つ以上ないインタフェース) に似ています。 私が言っていることを簡単に見てみましょう。

InterfaceX f = (x) -> x*x; 

InterfaceXは機能的なインターフェイスでなければなりません。どんな機能的なインターフェースでも、そのコンパイラにとってInterfaceXに関して重要なのはあなたがフォーマットを定義することだけです。

InterfaceXは次のいずれかです。

interface InterfaceX
{
    public Integer callMe(Integer x);
}

またはこれ

interface InterfaceX
{
    public Double callMe(Integer x);
}

より一般的な:

interface InterfaceX<T,U>
{
    public T callMe(U x);
}

最初に提示したケースと、前に定義したインラインラムダ式を見てみましょう。 

Java 8より前のバージョンでは、これと同じように定義できました。

 InterfaceX o = new InterfaceX(){
                     public int callMe (int x, int y) 
                       {
                        return x*x;
                       } };

機能的には同じです。違いは、コンパイラーがこれをどのように認識するかという点です。 

インラインラムダ式を見てきたので、メソッド参照(::)に戻りましょう。このようなクラスがあるとしましょう。

class Q {
        public static int anyFunction(int x)
             {
                 return x+5;
             } 
        }

メソッドanyFunctionsはInterfaceX callMeと同じ型を持つので、それら2つをメソッドリファレンスで等価にすることができます。

このように書くことができます。

InterfaceX o =  Q::anyFunction; 

これはこれと同じです。

InterfaceX o = (x) -> Q.anyFunction(x);

メソッド参照の優れた点と利点は、最初は変数に代入するまで型がないことです。そのため、それらを同等の外観を持つ(同じ定義型を持つ)機能インターフェースにパラメーターとして渡すことができます。これはまさにあなたの場合に起こることです

1
Nertan Lucian

ここでは多くの回答が::の振る舞いについて詳しく説明しているので、さらに、:: 演算子がインスタンス変数 に使用されている場合は参照元の関数型インタフェースとまったく同じシグネチャを持つ必要はないことを明確にします。 TestObject の型を持つ BinaryOperator が必要だとしましょう。伝統的な方法でそれはこのように実装されています:

BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>() {

        @Override
        public TestObject apply(TestObject t, TestObject u) {

            return t;
        }
    };

匿名の実装に見られるように、それは2つのTestObject引数を必要とし、同様にTestObjectオブジェクトを返します。 ::演算子を使用してこの条件を満たすには、静的メソッドから始めます。

public class TestObject {


    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

それから、

BinaryOperator<TestObject> binary = TestObject::testStatic;

それでうまくコンパイルできました。インスタンスメソッドが必要な場合はどうなりますか?インスタンスメソッドでTestObjectを更新しましょう。

public class TestObject {

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

これで以下のようにインスタンスにアクセスできます。

TestObject testObject = new TestObject();
BinaryOperator<TestObject> binary = testObject::testInstance;

このコードはうまくコンパイルされますが、以下のコードではコンパイルされません。

BinaryOperator<TestObject> binary = TestObject::testInstance;

私のEclipseは私に言う "型TestObjectから非静的メソッドtestInstance(TestObject、TestObject)への静的参照を作成できません..."

そのインスタンスメソッドは十分に公平ですが、以下のようにtestInstanceをオーバーロードすると、

public class TestObject {

    public final TestObject testInstance(TestObject t){
        return t;
    }

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

そして電話:

BinaryOperator<TestObject> binary = TestObject::testInstance;

コードは正常にコンパイルされます。なぜなら、それは二重のパラメータではなく単一のパラメータでtestInstanceを呼び出すからです。 Okでは、この2つのパラメータはどうなりましたか?プリントアウトして見ることができます:

public class TestObject {

    public TestObject() {
        System.out.println(this.hashCode());
    }

    public final TestObject testInstance(TestObject t){
        System.out.println("Test instance called. this.hashCode:" 
    + this.hashCode());
        System.out.println("Given parameter hashCode:" + t.hashCode());
        return t;
    }

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

どれが出力されます:

 1418481495  
 303563356  
 Test instance called. this.hashCode:1418481495
 Given parameter hashCode:303563356

はい、JVMはparam1.testInstance(param2)を呼び出すのに十分スマートです。 TestObjectではなく、他のリソースからのtestInstanceを使用できますか。

public class TestUtil {

    public final TestObject testInstance(TestObject t){
        return t;
    }
}

そして電話:

BinaryOperator<TestObject> binary = TestUtil::testInstance;

"TestUtil型はtestInstance(TestObject、TestObject)を定義していません" 。そのため、コンパイラは静的参照が同じ型でない場合はそれを探します。 Ok多態性についてはどうですか?最後の修飾子を削除して SubTestObject classを追加すると、

public class SubTestObject extends TestObject {

    public final TestObject testInstance(TestObject t){
        return t;
    }

}

そして電話:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

それはうまくコンパイルされません、コンパイラはまだ静的参照を探します。しかし、以下のコードはis-testに合格しているのでうまくコンパイルできます。

public class TestObject {

    public SubTestObject testInstance(Object t){
        return (SubTestObject) t;
    }

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

*私は勉強しているだけなので試してみて理解しています。間違っている場合は遠慮なく訂正してください。

1
HRgiger

この出所 /非常におもしろい。

実際、 Lambda Double Colon に変わります。

STEP1:

// We create a comparator of two persons
Comparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());

STEP2:

// We use the interference
Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());

STEP3:

// The magic
Comparator c = Comparator.comparing(Person::getAge());
1
Husam Bdr

二重コロン、すなわち::演算子は、メソッド参照としてJava 8で導入されました。メソッド参照は、既存のメソッドを名前で参照するために使用されるラムダ式の形式です。

クラス名:: methodName

例: -

  • stream.forEach(要素 - > System.out.println(要素))

二重コロンを使用することによって::

  • stream.forEach(System.out :: println(element))
0