:: (двойной двоеточие) в 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
?
Ответы
Ответ 1
Как правило, можно было бы назвать 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
описанной выше!
(См. Также перевод лямбда-выражений)
Ответ 2
::
называется методом ссылки. Это в основном ссылка на один метод. Т.е. это относится к существующему методу по имени.
Краткое объяснение:
Ниже приведен пример ссылки на статический метод:
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
. Он принимает один аргумент и дает результат.
Причина, почему ::
удивительны в том, что:
Ссылки на методы - это выражения, которые обрабатываются так же, как лямбда-выражения (...), но вместо предоставления тела метода они ссылаются на существующий метод по имени.
Например, вместо написания лямбда-тела
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
в Supplier возвращает значение, но не принимает аргумент. Таким образом, это приводит к ошибке.
Ссылка на метод относится к методу функционального интерфейса. (Как уже упоминалось, функциональные интерфейсы могут иметь только один метод каждый).
Еще несколько примеров: метод accept
в Consumer принимает входные данные, но ничего не возвращает.
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;
Ссылки на методы могут иметь разные стили, но по сути все они означают одно и то же и могут просто отображаться как лямбды:
- Статический метод (
ClassName::methName
) - Метод экземпляра определенного объекта (
instanceRef::methName
) - Супер метод конкретного объекта (
super::methName
) - Метод экземпляра произвольного объекта определенного типа (
ClassName::methName
) - Ссылка на конструктор класса (
ClassName::new
) - Ссылка на конструктор массива (
TypeName[]::new
)
Для получения дополнительной информации см. Http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html.
Ответ 3
Да это правда. Оператор ::
используется для ссылки на метод. Таким образом, можно извлечь статические методы из классов, используя его или методы из объектов. Один и тот же оператор может быть использован даже для конструкторов. Все случаи, упомянутые здесь, приведены в примере кода ниже.
Официальную документацию от Oracle можно найти здесь.
Вы можете получить лучший обзор изменений 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);
}
}
Ответ 4
::
- это новый оператор, включенный в Java 8, который используется для ссылки на метод существующего класса. Вы можете ссылаться на статические методы и нестатические методы класса.
Для обращения к статическим методам синтаксис:
ClassName :: methodName
Для ссылки на нестатические методы синтаксис
objRef :: methodName
и
ClassName :: methodName
Единственным предварительным условием для обращения к методу является тот метод, который существует в функциональном интерфейсе, который должен быть совместим с ссылкой на метод.
Ссылки на методы, при оценке, создают экземпляр функционального интерфейса.
Найдено по: http://www.speakingcs.com/2014/08/method-references-in-java-8.html
Ответ 5
Кажется, уже немного поздно, но вот мои два цента. Лямбда-выражение используется для создания анонимных методов. Он не делает ничего, кроме вызова существующего метода, но более понятно ссылаться на метод напрямую по его имени. И ссылка на метод позволяет нам сделать это, используя метод-ссылку operator ::
.
Рассмотрим следующий простой класс, где у каждого сотрудника есть имя и класс.
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")
);
}
Теперь мы знаем, что Comparator - это функциональный интерфейс. Функциональный интерфейс - это интерфейс с ровно одним абстрактным методом (хотя он может содержать один или несколько стандартных или статических методов). Лямбда-выражение обеспечивает реализацию @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 |
+------------------------------------------------------------+--------------------------------------+
Ответ 6
Это ссылка на метод в Java 8. Документация оракула здесь.
Как указано в документации...
Ссылка метода 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 вызывает метод compareByName это часть объекта myComparisonProvider. JRE представляет аргументы типа метода, которые в этом случае являются (Person, Person).
Ответ 7
:: Оператор был введен в Java 8 для ссылок на методы. Ссылка на метод - это сокращенный синтаксис для лямбда-выражения, которое выполняет только ОДИН метод. Вот общий синтаксис ссылки на метод:
Object :: methodName
Мы знаем, что мы можем использовать лямбда-выражения вместо анонимного класса. Но иногда лямбда-выражение на самом деле является просто вызовом некоторого метода, например:
Consumer<String> c = s -> System.out.println(s);
Чтобы сделать код более понятным, вы можете превратить это лямбда-выражение в ссылку на метод:
Consumer<String> c = System.out::println;
Ответ 8
The:: известен как ссылки на методы. Допустим, мы хотим вызвать метод calculatePrice класса Purchase. Тогда мы можем записать его как:
Purchase::calculatePrice
Его также можно рассматривать как короткую форму записи выражения лямбда. Поскольку ссылки на методы преобразуются в лямбда-выражения.
Ответ 9
Во время выполнения они ведут себя точно так же. Байт-код может/не быть одинаковым (для выше Incase он генерирует один и тот же байт-код (см. выше и проверьте javaap -c;))
Во время выполнения они ведут себя точно так же .method(math:: max);, он генерирует одну и ту же математику (см. выше и проверить javap -c;))
Ответ 10
return reduce(Math::max);
НЕ РАВНО до 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 ^_^
Ответ 11
Поскольку многие ответы здесь объясняли хорошо поведение ::
, дополнительно я хотел бы пояснить, что оператор ::
не должен иметь точно такую же подпись, что и ссылочный функциональный интерфейс, если он используется для переменных экземпляра. Предположим, нам нужен BinaryOperator, который имеет тип TestObject. Традиционно он реализован следующим образом:
BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>() {
@Override
public TestObject apply(TestObject t, TestObject u) {
return t;
}
};
Как вы видите в анонимной реализации, он требует два аргумента 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;
My eclipse сказать мне "Невозможно сделать статическую ссылку на нестатический метод testInstance (TestObject, 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
с единственным параметром вместо двойного. Хорошо, так что случилось с нашими двумя параметрами? Вывод распечатки и просмотр:
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). Можем ли мы использовать testInstance
с другого ресурса, но не TestObject, то есть:
public class TestUtil {
public final TestObject testInstance(TestObject t){
return t;
}
}
И вызов:
BinaryOperator<TestObject> binary = TestUtil::testInstance;
Он просто не компилируется, и компилятор скажет: "Тип TestUtil не определяет testInstance (TestObject, TestObject)" . Поэтому компилятор будет искать статическую ссылку, если это не тот же тип. Хорошо, что о полиморфизме? Если мы удалим финальные модификаторы и добавим класс SubTestObject:
public class SubTestObject extends TestObject {
public final TestObject testInstance(TestObject t){
return t;
}
}
И вызов:
BinaryOperator<TestObject> binary = SubTestObject::testInstance;
Он не будет компилироваться, компилятор все равно будет искать статическую ссылку. Но ниже код будет компилироваться отлично, поскольку он проходит - это тест:
public class TestObject {
public SubTestObject testInstance(Object t){
return (SubTestObject) t;
}
}
BinaryOperator<TestObject> binary = TestObject::testInstance;
* Я просто изучаю, поэтому я понял, что попробуй и посмотри, не стесняйся меня исправить, если я ошибаюсь
Ответ 12
Я нашел этот источник очень интересным.
На самом деле, это лямбда, которая превращается в двойной двоеточие. Двойной двоеточие более читабельно. Мы следуем этим шагам:
ШАГ 1:
// We create a comparator of two persons
Comparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());
ШАГ 2:
// We use the interference
Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());
ШАГ 3:
// The magic using method reference
Comparator c = Comparator.comparing(Person::getAge);
Ответ 13
В java-8 Streams Reducer в простых произведениях есть функция, которая принимает два значения в качестве входных данных и возвращает результат после некоторых вычислений. этот результат подается на следующей итерации.
в случае функции Math: max, метод возвращает максимальное значение двух пройденных значений и, в конце концов, у вас наибольшее количество в руке.
Ответ 14
В более старых версиях 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();
}
Ответ 15
Так что я вижу здесь тонны ответов, которые, честно говоря, слишком сложны, и это занижение.
Ответ довольно прост: :: он называется "Метод ссылки" https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
Так что я не буду копировать-вставлять, по ссылке, вы можете найти всю информацию, если прокрутите вниз до таблицы.
Теперь давайте кратко рассмотрим, что такое "Ссылки на метод":
A :: B несколько заменяет следующее встроенное лямбда-выражение: (params...) → AB (params...)
Чтобы соотнести это с вашими вопросами, необходимо понимать лямбда-выражение java. Что не сложно.
Встроенное лямбда-выражение аналогично определенному функциональному интерфейсу (который является интерфейсом, который имеет не более и не менее 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;
} };
Функционально это одно и то же. Разница больше в том, как компилятор это воспринимает.
Теперь, когда мы взглянули на встроенное лямбда-выражение, вернемся к методам References (: :). Допустим, у вас есть такой класс:
class Q {
public static int anyFunction(int x)
{
return x+5;
}
}
Поскольку метод anyFunctions имеет те же типы, что и интерфейсный интерфейс callMe, мы можем сопоставить эти два с помощью метода Reference.
Мы можем написать это так:
InterfaceX o = Q::anyFunction;
и это эквивалентно этому:
InterfaceX o = (x) -> Q.anyFunction(x);
Крутая вещь и преимущество ссылок на методы в том, что сначала, до тех пор, пока вы не назначите их переменным, они будут не иметь типов. Таким образом, вы можете передавать их в качестве параметров любому эквивалентному (имеющему одинаковые типы) функциональному интерфейсу. Что именно происходит в вашем случае
Ответ 16
Предыдущие ответы вгляда относительно того, что ::
эталонного метода делает. Подводя итог, он предоставляет способ ссылки на метод (или конструктор) без его выполнения, а при оценке он создает экземпляр функционального интерфейса, который предоставляет целевой контекст типа.
Ниже приведены два примера поиска объекта с максимальным значением в ArrayList
WITH и БЕЗ использования ссылки на метод ::
. Пояснения в комментариях ниже.
БЕЗ использования ::
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 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);
}
}
Ответ 17
Двойное двоеточие, т.е. оператор ::
представлен в Java 8 как ссылка на метод. Ссылка на метод - это форма лямбда-выражения, которая используется для ссылки на существующий метод по его имени.
имя_класса :: имяМетода
например: -
stream.forEach(element -> System.out.println(element))
Используя двойное двоеточие ::
stream.forEach(System.out::println(element))