:: (двойное двоеточие) оператор в 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?

18 ответов

Решение

Обычно можно назвать 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, Он принимает один аргумент и дает результат.


Причина по которой ::Это удивительно, что:

Ссылки на методы - это выражения, которые обрабатываются так же, как лямбда-выражения (...), но вместо предоставления тела метода они ссылаются на существующий метод по имени.

Например, вместо написания лямбда-тела

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 Метод в поставщике ожидает аргумент, но ничего не возвращает. Таким образом, это приводит к ошибке.

Ссылка на метод относится к методу функционального интерфейса. (Как уже упоминалось, функциональные интерфейсы могут иметь только один метод каждый).

Еще несколько примеров: 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;

Ссылки на методы могут иметь разные стили, но по сути они все означают одно и то же и могут просто отображаться как лямбды:

  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.

Да это правда. :: Оператор используется для ссылки на метод. Таким образом, можно извлечь статические методы из классов, используя его или методы из объектов. Один и тот же оператор может использоваться даже для конструкторов. Все случаи, упомянутые здесь, приведены в примере кода ниже.

Официальную документацию от 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);
   }
}

Кажется, уже немного поздно, но вот мои два цента. Лямбда-выражение используется для создания анонимных методов. Он ничего не делает, кроме как вызывает существующий метод, но яснее ссылаться на метод напрямую по его имени. И ссылка на метод позволяет нам сделать это с помощью оператора метода ссылки ::,

Рассмотрим следующий простой класс, где у каждого сотрудника есть имя и класс.

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 - это функциональный интерфейс. Функциональный интерфейс - это интерфейс с ровно одним абстрактным методом (хотя он может содержать один или несколько стандартных или статических методов). Таким образом, мы можем использовать лямбда-выражение как:

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. Документация оракула находится здесь.

Как указано в документации...

Ссылка на метод 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).

:: Оператор был введен в Java 8 для ссылок на методы. Ссылка на метод - это сокращенный синтаксис для лямбда-выражения, которое выполняет только ОДИН метод. Вот общий синтаксис ссылки на метод:

Object :: methodName

Мы знаем, что мы можем использовать лямбда-выражения вместо анонимного класса. Но иногда лямбда-выражение на самом деле является просто вызовом некоторого метода, например:

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

Чтобы сделать код более понятным, вы можете превратить это лямбда-выражение в ссылку на метод:

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

Так что я вижу здесь тонны ответов, которые откровенно слишком сложны, и это преуменьшение.

Ответ довольно прост: :: это называется метод Ссылки. 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);

Крутая вещь и преимущество ссылок на методы в том, что сначала, до тех пор, пока вы не назначите их переменным, они будут не иметь типов. Таким образом, вы можете передавать их в качестве параметров любому эквивалентному (имеющему одинаковые типы) функциональному интерфейсу. Что именно происходит в вашем случае

Я нашел этот источник очень интересным.

На самом деле, это лямбда, которая превращается в двойной двоеточие. Двойной двоеточие более читабельно. Мы следуем этим шагам:

ШАГ 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
Comparator c = Comparator.comparing(Person::getAge());

Оператор с двойным двоеточием (::), также известный как оператор ссылки на метод в Java, используется для вызова метода путем прямого обращения к нему с помощью его класса. Они ведут себя точно так же, как лямбда-выражения. Единственное отличие, которое он имеет от лямбда-выражений, состоит в том, что они используют прямую ссылку на метод по имени вместо предоставления делегата методу.

Синтаксис:

      <Class name>::<method name>

Этот символ можно использовать вместо ламда-выражений

Программа:

      // Java code to print the elements of Stream
// without using double colon operator
  
import java.util.stream.*;
  
class MyClass {
    public static void main(String[] args)
    {
  
        // Get the stream
        Stream<String> stream
            = Stream.of("Testing","Program");
  
        // Print the stream
        stream.forEach(s -> System.out.println(s));
    }
}

Выход:

      Testing
Program

Линия stream.forEach(s -> System.out.println(s)); Можно заменить на

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

Также при программировании чаще всего используется первый метод.

:: известен как ссылки на метод. Допустим, мы хотим вызвать метод Calculate для класса "Покупка". Тогда мы можем написать это как:

Purchase::calculatePrice

Это также можно рассматривать как краткую форму написания лямбда-выражения, поскольку ссылки на методы преобразуются в лямбда-выражения.

В java-8 Streams Reducer в простых работах представляет собой функцию, которая принимает два значения в качестве входных данных и возвращает результат после некоторого вычисления. этот результат подается в следующую итерацию.

в случае функции Math: max метод продолжает возвращать максимум из двух переданных значений, и в итоге у вас на руках наибольшее число.

Так как многие ответы здесь объяснены хорошо :: поведение, кроме того, я хотел бы уточнить, что :: оператор не должен иметь точно такую ​​же сигнатуру, что и ссылающийся функциональный интерфейс, если он используется для переменных экземпляра. Предположим, нам нужен 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;

Мое затмение говорит мне: "Невозможно сделать статическую ссылку на нестатический метод 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;

Он также не будет компилироваться, компилятор все равно будет искать статическую ссылку. Но приведенный ниже код прекрасно скомпилируется, поскольку он проходит тест is-a:

public class TestObject {

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

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

* Я только учусь, поэтому я понял, попробуй и посмотри, не стесняйся поправлять меня, если я ошибаюсь

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 ^_^

В более старых версиях 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();
}

Во время выполнения они ведут себя абсолютно одинаково. Байт-код может / не быть одинаковым (для вышеупомянутого Incase он генерирует тот же байт-код (соблюдайте выше и проверяйте javaap -c;))

Во время выполнения они ведут себя точно так же.method(math::max);, он генерирует ту же математику (соблюдайте выше и проверьте javap -c;))

Предыдущие ответы довольно полны относительно того, что :: ссылка на метод делает. Подводя итог, он предоставляет способ ссылки на метод (или конструктор) без его выполнения, а при оценке он создает экземпляр функционального интерфейса, который предоставляет целевой контекст типа.

Ниже приведены два примера, чтобы найти объект с максимальным значением в ArrayList С и без использования :: ссылка на метод. Пояснения в комментариях ниже.


БЕЗ использования ::

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

Двойное двоеточие ie:: operator введено в Java 8 как ссылка на метод. Ссылка на метод - это форма лямбда-выражения, которая используется для ссылки на существующий метод по его имени.

имя_класса:: имяМетода

например: -

  • stream.forEach (element -> System.out.println (element))

Используя Double Colon::

  • stream.forEach (System.out:: Println (элемент))
Другие вопросы по тегам