1135 Stimmen

:: (Doppelpunkt Doppelpunkt) Operator in Java 8

Ich habe den Java 8-Quellcode erkundet und diesen bestimmten Codeteil als sehr überraschend gefunden:

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

@Override
public final OptionalInt max() {
    return reduce(Math::max); // Dies ist die überraschende Zeile
}

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

Ist Math::max ähnlich wie ein Methodenzeiger?
Wie wird eine normale static-Methode in einen IntBinaryOperator umgewandelt?

1151voto

isnot2bad Punkte 24135

Normalerweise würde man die Methode reduce mit Math.max(int, int) folgendermaßen aufrufen:

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

Das erfordert viel Syntax, um einfach Math.max aufzurufen. Hier kommen Lambda-Ausdrücke ins Spiel. Seit Java 8 ist es möglich, das Gleiche auf eine viel kürzere Weise zu tun:

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

Wie funktioniert das? Der Java-Compiler "erkennt", dass du eine Methode implementieren willst, die zwei int-Werte akzeptiert und einen int-Wert zurückgibt. Dies entspricht den formalen Parametern der einzigen Methode des Interface IntBinaryOperator (dem Parameter der Methode reduce, die du aufrufen möchtest). Der Compiler übernimmt also den Rest für dich - er geht einfach davon aus, dass du IntBinaryOperator implementieren möchtest.

Aber da Math.max(int, int) selbst die formalen Anforderungen von IntBinaryOperator erfüllt, kann es direkt verwendet werden. Da Java 7 keine Syntax hat, die es erlaubt, eine Methode selbst als Argument zu übergeben (man kann nur Methodenergebnisse übergeben, aber nie Methodenreferenzen), wurde in Java 8 die ::-Syntax eingeführt, um auf Methoden zu verweisen:

reduce(Math::max);

Beachte, dass dies vom Compiler interpretiert wird, nicht zur Laufzeit von der JVM! Obwohl alle drei Code-Schnipsel unterschiedliche Bytecodes erzeugen, sind sie semantisch gleich, sodass die letzten beiden als kurze (und wahrscheinlich effizientere) Versionen der obigen IntBinaryOperator-Implementierung angesehen werden können!

(Siehe auch Übersetzung von Lambda-Ausdrücken)

625voto

Jatin Punkte 29670

:: wird als Methodenreferenz bezeichnet. Es handelt sich im Grunde um einen Verweis auf eine einzelne Methode, d. h. es bezieht sich auf eine vorhandene Methode nach ihrem Namen.

Kurze Erklärung:

Hier ist ein Beispiel für einen Verweis auf eine statische Methode:

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

Function quadrat = Hey::quadrat;
double antwort = quadrat.apply(23d);

quadrat kann genauso wie Objektverweise weitergereicht und bei Bedarf ausgelöst werden. Tatsächlich kann es genauso einfach verwendet werden wie ein Verweis auf "normale" Methoden von Objekten wie auf statische. Zum Beispiel:

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

Hey hey = new Hey();
Function quadrat = hey::quadrat;
double antwort = quadrat.apply(23d);

Function oben ist eine funktionale Schnittstelle. Um :: vollständig zu verstehen, ist es wichtig, funktionale Schnittstellen ebenfalls zu verstehen. Einfach ausgedrückt ist eine funktionale Schnittstelle eine Schnittstelle mit nur einer abstrakten Methode.

Beispiele für funktionale Schnittstellen sind Runnable, Callable und ActionListener.

Function oben ist eine funktionale Schnittstelle mit nur einer Methode: apply. Sie nimmt ein Argument entgegen und liefert ein Ergebnis.


Der Grund, warum :: so großartig sind, ist dies:

Methodenreferenzen sind Ausdrücke, welche genauso behandelt werden wie Lambda-Ausdrücke (...), jedoch anstelle eines Methodenrumpfes bereits existierende Methoden beim Namen nennen.

Z. B. anstatt den Lambda-Körper zu schreiben

Function quadrat = (Double x) -> x * x;

Kannst du einfach das tun

Function quadrat = Hey::quadrat;

Zur Laufzeit verhalten sich diese beiden quadrat Methoden genau gleich. Der Bytecode kann der gleiche sein oder auch nicht (obwohl, für den obigen Fall, der gleiche Bytecode erzeugt wird; kompiliere das obige und überprüfe mit javap -c).

Das einzige Hauptkriterium, das erfüllt werden muss, ist: Die von dir bereitgestellte Methode sollte eine ähnliche Signatur wie die Methode der funktionalen Schnittstelle haben, die du als Objektverweis verwendest.

Folgendes ist illegal:

Supplier p = Hey::quadrat; // illegal

quadrat erwartet ein Argument und gibt eine double zurück. Die Methode get in Supplier gibt einen Wert zurück, nimmt aber kein Argument an. Daher ergibt dies einen Fehler.

Ein Methodenverweis bezieht sich auf die Methode einer funktionalen Schnittstelle. (Wie erwähnt, können funktionale Schnittstellen jeweils nur eine Methode haben.)

Weitere Beispiele: Die Methode accept in Consumer nimmt eine Eingabe entgegen, gibt aber nichts zurück.

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

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

Callable call = hey::getRandom;
Supplier call2 = hey::getRandom;
DoubleSupplier sup = hey::getRandom;
// Supplier ist eine funktionale Schnittstelle, die kein Argument annimmt und ein Ergebnis liefert

Oben nimmt getRandom kein Argument an und gibt eine double zurück. Daher kann jede funktionale Schnittstelle, die die Kriterien erfüllt: kein Argument entgegennehmen und double zurückgeben, verwendet werden.

Ein weiteres Beispiel:

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

Bei parametrisierten Typen:

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

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

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

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

Function func = Param::returnSame;

Methodenverweise können unterschiedliche Stile haben, aber grundsätzlich bedeuten sie alle dasselbe und können einfach als Lambdas visualisiert werden:

  1. Eine statische Methode (Klassenname::methName)
  2. Eine Instanzmethode eines bestimmten Objekts (instanceRef::methName)
  3. Eine Supermethode eines bestimmten Objekts (super::methName)
  4. Eine Instanzmethode eines beliebigen Objekts eines bestimmten Typs (Klassenname::methName)
  5. Ein Klassenkonstruktorverweis (Klassenname::new)
  6. Ein Array-Konstruktorverweis (TypNAME[]::new)

Zur weiteren Referenz siehe State of the Lambda.

62voto

Olimpiu POP Punkte 4836

Ja, das ist wahr. Der :: Operator wird zum Referenzieren von Methoden verwendet. Auf diese Weise können statische Methoden aus Klassen extrahiert werden oder auch Methoden aus Objekten. Der gleiche Operator kann sogar für Konstruktoren verwendet werden. Alle hier erwähnten Fälle sind im untenstehenden Code-Beispiel veranschaulicht.

Die offizielle Dokumentation von Oracle finden Sie hier.

Sie können einen besseren Überblick über die JDK 8-Änderungen in diesem Artikel erhalten. Im Abschnitt Methoden-/Konstruktorreferenzierung wird ebenfalls ein Codebeispiel bereitgestellt:

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) {
       // Operationen
   }

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

       // statische Methoden-Referenz
       MethodReference mr = cc::method;

       // Objekt-Methoden-Referenz
       MethodReference mr2 = cc::nextMethod;

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

35voto

akhil_mittal Punkte 20953

Ein Lambda-Ausdruck wird verwendet, um anonyme Methoden zu erstellen. Er tut nichts anderes als einen vorhandenen Methode aufrufen, aber es ist klarer, sich direkt auf die Methode mit ihrem Namen zu beziehen. Und Methodenreferenz ermöglicht es uns, dies mit dem Methodenreferenzoperator :: zu tun.

Betrachten Sie die folgende einfache Klasse, in der jeder Mitarbeiter einen Namen und eine Note hat.

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;
    }
}

Angenommen, wir haben eine Liste von Mitarbeitern, die von einer Methode zurückgegeben wird, und wir möchten die Mitarbeiter nach ihrer Note sortieren. Wir wissen, dass wir anonyme Klasse verwenden können:

    List employeeList = getDummyEmployees();

    // Verwendung einer anonymen Klasse
    employeeList.sort(new Comparator() {
           @Override
           public int compare(Employee e1, Employee e2) {
               return e1.getGrade().compareTo(e2.getGrade());
           }
    });

wo getDummyEmployee() eine Methode wie folgt ist:

private static List 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")
                );
    }

Jetzt wissen wir, dass ein Comparator ein funktionales Interface ist. Ein funktionales Interface ist dasjenige mit genau einer abstrakten Methode (obwohl es eine oder mehrere Standard- oder statische Methoden enthalten kann). Der Lambda-Ausdruck bietet eine Implementierung von @FunctionalInterface, sodass ein funktionales Interface nur eine abstrakte Methode haben kann. Wir können den Lambda-Ausdruck wie folgt verwenden:

employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // Lambda-Ausdruck

Es scheint alles gut zu sein, aber was ist, wenn die Klasse Employee auch eine ähnliche Methode bereitstellt?

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

In diesem Fall wird die Verwendung des Methodennamens selbst klarer sein. Daher können wir direkt auf die Methode verweisen, indem wir die Methodenreferenz verwenden: employeeList.sort(Employee::compareByGrade); // Methodenreferenz

Laut der Dokumentation gibt es vier Arten von Methodenreferenzen:

+----+-------------------------------------------------------+--------------------------------------+
|    | Art                                                   | Beispiel                              |
+----+-------------------------------------------------------+--------------------------------------+
| 1  | Referenz auf eine statische Methode                   | ContainingClass::staticMethodName    |
+----+-------------------------------------------------------+--------------------------------------+
| 2  | Referenz auf eine Instanzmethode eines bestimmten Objekts | containingObject::instanceMethodName |
+----+-------------------------------------------------------+--------------------------------------+
| 3  | Referenz auf eine Instanzmethode eines beliebigen Objekts| ContainingType::methodName           |
|    | eines bestimmten Typs                                 |                                      |
+----+-------------------------------------------------------+--------------------------------------+
| 4  | Referenz auf einen Konstruktor                        | ClassName::new                       |
+------------------------------------------------------------+--------------------------------------+

29voto

sreenath Punkte 489

:: ist ein neuer Operator, der in Java 8 enthalten ist und verwendet wird, um auf eine Methode einer vorhandenen Klasse zu verweisen. Sie können sowohl auf statische als auch nicht-statische Methoden einer Klasse verweisen.

Um auf statische Methoden zu verweisen, lautet die Syntax:

Klassenname :: Methodenname

Um auf nicht-statische Methoden zu verweisen, lautet die Syntax:

ObjektReferenz :: Methodenname

Und

Klassenname :: Methodenname

Die einzige Voraussetzung für das Verweisen auf eine Methode ist, dass die Methode in einem funktionalen Interface vorhanden ist, das mit dem Methodenverweis kompatibel sein muss.

Methodenverweise erstellen beim Auswerten eine Instanz des funktionalen Interfaces.

Dies wurde gefunden unter: http://www.speakingcs.com/2014/08/method-references-in-java-8.html

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X