Operador::(dois-pontos duplos) no Java 8




method as parameter java 8 (13)

Eu estava explorando a fonte do Java 8 e achei essa parte específica do código muito surpreendente:

//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 algo como um ponteiro de método? Como um método static normal é convertido em IntBinaryOperator ?


As respostas anteriores são bem completas em relação à referência :: method. Resumindo, ele fornece uma maneira de se referir a um método (ou construtor) sem executá-lo e, quando avaliado, cria uma instância da interface funcional que fornece o contexto do tipo de destino.

Abaixo estão dois exemplos para encontrar um objeto com o valor máximo em uma referência de método ArrayList WITH e WITHOUT of :: . Explicações estão nos comentários abaixo.

SEM o uso de ::

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

COM o uso de ::

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

Em tempo de execução, eles se comportam exatamente da mesma maneira. O bytecode pode ser diferente (para acima do Incase, ele gera o mesmo bytecode (complie acima e verifique javaap -c;))

No tempo de execução, eles se comportam exatamente da mesma forma. (Método: math :: max) ;, gera a mesma matemática (complie acima e verifique o javap -c;))


:: O operador foi introduzido no java 8 para referências de métodos. Uma referência de método é a sintaxe abreviada de uma expressão lambda que executa apenas UM método. Aqui está a sintaxe geral de uma referência de método:

Object :: methodName

Sabemos que podemos usar expressões lambda em vez de usar uma classe anônima. Mas às vezes, a expressão lambda é realmente apenas uma chamada para algum método, por exemplo:

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

Para tornar o código mais claro, você pode transformar essa expressão lambda em uma referência de método:

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

Eu achei essa fonte muito interessante.

Na verdade, é o Lambda que se transforma em um duplo cólon . O duplo cólon é mais legível. Nós seguimos esses passos:

PASSO 1:

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

PASSO 2:

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

ETAPA 3:

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

:: é chamado Referência de Método. É basicamente uma referência a um único método. Ou seja, refere-se a um método existente pelo nome.

Breve explicação :
Abaixo está um exemplo de uma referência a um método estático:

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 pode ser passado como referências de objetos e acionado quando necessário. Na verdade, ele pode ser usado tão facilmente quanto uma referência a métodos "normais" de objetos como os static . Por exemplo:

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 acima é uma interface funcional . Para entender completamente :: , é importante entender as interfaces funcionais também. Obviamente, uma interface funcional é uma interface com apenas um método abstrato.

Exemplos de interfaces funcionais incluem Runnable , Callable e ActionListener .

Function acima é uma interface funcional com apenas um método: apply . Leva um argumento e produz um resultado.

A razão pela qual :: s é incrível é that :

As referências de método são expressões que têm o mesmo tratamento que as expressões lambda (...), mas em vez de fornecer um corpo de método, elas referem um método existente por nome.

Por exemplo, em vez de escrever o corpo lambda

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

Você pode simplesmente fazer

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

Em tempo de execução, esses dois métodos square se comportam exatamente da mesma forma que os outros. O bytecode pode ou não ser o mesmo (embora, para o caso acima, o mesmo bytecode seja gerado; compile o acima e verifique com javap -c ).

O único critério principal a ser satisfeito é: o método que você fornece deve ter uma assinatura semelhante ao método da interface funcional que você usa como referência de objeto .

O abaixo é ilegal:

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

square espera um argumento e retorna um double . O método get no Supplier espera um argumento, mas não retorna nada. Assim, isso resulta em um erro.

Uma referência de método refere-se ao método de uma interface funcional. (Como mencionado, as interfaces funcionais podem ter apenas um método cada).

Mais alguns exemplos: o método accept no Consumer recebe uma entrada, mas não retorna nada.

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

Acima, getRandom não recebe argumentos e retorna um double . Portanto, qualquer interface funcional que satisfaça os critérios de: sem argumento e retorno double pode ser usada.

Outro exemplo:

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

No caso de tipos parametrizados :

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;

Referências de método podem ter estilos diferentes, mas fundamentalmente todas elas significam a mesma coisa e podem simplesmente ser visualizadas como lambdas:

  1. Um método estático ( ClassName::methName )
  2. Um método de instância de um objeto específico ( instanceRef::methName )
  3. Um super método de um objeto em particular ( super::methName )
  4. Um método de instância de um objeto arbitrário de um tipo específico ( ClassName::methName )
  5. Uma referência de construtor de classe ( ClassName::new )
  6. Uma referência de construtor de matriz ( TypeName[]::new )

Para mais referências, consulte that .


return reduce(Math::max); NÃO É EQUAL para return reduce(max());

Mas isso significa algo assim:

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

Você pode apenas salvar 47 teclas digitadas se você escrever assim

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

:: é um novo operador incluído no Java 8 que é usado para referenciar um método de uma classe existente. Você pode consultar métodos estáticos e métodos não estáticos de uma classe.

Para referenciar métodos estáticos, a sintaxe é:

ClassName :: methodName 

Para referir métodos não estáticos, a sintaxe é

objRef :: methodName

E

ClassName :: methodName

O único pré-requisito para referenciar um método é que o método existe em uma interface funcional, que deve ser compatível com a referência do método.

Referências de métodos, quando avaliadas, criam uma instância da interface funcional.

Encontrado em: http://www.speakingcs.com/2014/08/method-references-in-java-8.html


Nas versões mais antigas do Java, em vez de "::" ou lambd, você pode usar:

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

Ou passando para o método:

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

O :: é conhecido como referências de método. Vamos dizer que queremos chamar um método calculatePrice da classe Purchase. Então podemos escrever como:

Purchase::calculatePrice

Também pode ser visto como forma abreviada de escrever a expressão lambda Como as referências de método são convertidas em expressões lambda.


No java-8 Streams Reducer em trabalhos simples é uma função que toma dois valores como entrada e retorna o resultado após algum cálculo. esse resultado é alimentado na próxima iteração.

no caso da função Math: max, o método continua retornando no máximo dois valores passados ​​e no final você tem o maior número em mãos.


Sim, isso é verdade. O operador :: é usado para referência de método. Assim, pode-se extrair métodos estáticos de classes usando-os ou métodos de objetos. O mesmo operador pode ser usado até mesmo para construtores. Todos os casos mencionados aqui são exemplificados no exemplo de código abaixo.

A documentação oficial da Oracle pode ser encontrada here .

Você pode ter uma visão geral melhor das alterações do JDK 8 this artigo. Na seção de referência Método / Construtor , um exemplo de código também é fornecido:

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

Parece um pouco tarde, mas aqui estão meus dois centavos. Uma expressão lambda é usada para criar métodos anônimos. Não faz nada além de chamar um método existente, mas é mais claro se referir ao método diretamente pelo seu nome. E a referência de método nos permite fazer isso usando o operador de referência de método :: .

Considere a seguinte classe simples em que cada funcionário tem um nome e uma nota.

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

Suponha que tenhamos uma lista de funcionários retornados por algum método e queremos ordenar os funcionários por sua nota. Sabemos que podemos usar classes anônimas como:

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

onde getDummyEmployee () é algum método como:

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

Agora sabemos que o Comparator é uma interface funcional. Uma Interface Funcional é aquela com exatamente um método abstrato (embora possa conter um ou mais métodos padrão ou estáticos). Então podemos usar a expressão lambda como:

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

Parece tudo bem, mas e se a classe Employee também fornece um método similar:

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

Neste caso, usar o nome do método em si será mais claro. Portanto, podemos nos referir diretamente ao método usando a referência de método como:

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

De acordo com os documentos, existem quatro tipos de referências de métodos:

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

Como muitas respostas aqui explicaram bem :: comportamento, adicionalmente gostaria de esclarecer que :: operador não precisa ter exatamente a mesma assinatura da Interface Funcional de referência, se for usada para variáveis ​​de instância . Vamos supor que precisamos de um BinaryOperator que tenha o tipo de TestObject . De maneira tradicional é implementado assim:

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

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

            return t;
        }
    };

Como você vê na implementação anônima, ele requer dois argumentos TestObject e também retorna um objeto TestObject. Para satisfazer essa condição usando :: operator, podemos começar com um método estático:

public class TestObject {


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

e depois ligue:

BinaryOperator<TestObject> binary = TestObject::testStatic;

Ok, compilou bem. E se precisarmos de um método de instância? Vamos atualizar o TestObject com o método da instância:

public class TestObject {

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

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

Agora podemos acessar a instância como abaixo:

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

Este código compila bem, mas abaixo de um não:

BinaryOperator<TestObject> binary = TestObject::testInstance;

Meu eclipse me diz "Não é possível fazer uma referência estática ao método não estático testInstance (TestObject, TestObject) do tipo TestObject ..."

É justo que seja um método de instância, mas se sobrecarregarmos testInstance como abaixo:

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

E ligue:

BinaryOperator<TestObject> binary = TestObject::testInstance;

O código só vai compilar bem. Porque ele chamará testInstance com um único parâmetro em vez de um duplo. Ok então o que aconteceu com nossos dois parâmetros? Vamos imprimir e ver:

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

Qual será a saída:

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

Ok, então a JVM é inteligente o suficiente para chamar param1.testInstance (param2). Podemos usar testInstance de outro recurso, mas não TestObject, ou seja:

public class TestUtil {

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

E ligue:

BinaryOperator<TestObject> binary = TestUtil::testInstance;

Apenas não compilará e o compilador dirá: "O tipo TestUtil não define testInstance (TestObject, TestObject)" . Então, o compilador irá procurar por uma referência estática se ela não for do mesmo tipo. Ok, e o polimorfismo? Se removermos os modificadores finais e adicionarmos nossa classe SubTestObject :

public class SubTestObject extends TestObject {

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

}

E ligue:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

Ele não compilará também, o compilador ainda procurará por referências estáticas. Mas abaixo código irá compilar bem, uma vez que está passando é um teste:

public class TestObject {

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

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

* Eu estou apenas estudando, então eu descobri, tente e veja, sinta-se livre para me corrigir se eu estiver errado





java-8