What is the purpose of the :: symbol in Java?

Question:

I implemented a method that sums all the numbers of a list of type List<Integer> as follows:

int soma = 0;

for (Integer num : numeros)
    soma += num;
return soma;

However, the NetBeans IDE suggested that I replace the above routine with the following routine below:

int soma = 0;
return numeros.stream().map((numero) -> numero).reduce(soma, Integer::sum);

I know that the above routine uses new features of Java 8, however, I was in doubt about the symbol :: used in the reduce() method, I don't know if it is some kind of operator, I would like to know what is its purpose?

Answer:

The :: operator was added to Java 8 and is part of expressions that reference methods ( Method Reference Expressions ) .

Method references ( Method References ) work as a complement to lambdas.

A lambda is like a piece of code that you can pass as an argument to a method. But sometimes this piece of code repeats itself or is already implemented in some method, so you can simply reference any method.

According to the documentation, there are basically four types of method references:

1. Static method

public static void medirTempo(Runnable rotinaDemorada, Supplier<Long> timestampSupplier) {
    long inicio = timestampSupplier.get();
    rotinaDemorada.run();
    System.out.format("Tempo: %d ms", timestampSupplier.get() - inicio);
}

Runnable is a functional interface that abstracts a task to be performed. It does not receive or return value. Supplier is a functional interface whose execution returns a value of a certain type, Long in this case.

Given a routine that takes time:

Runnable rotinaDemorada = () -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
};

We can call the method like this:

medirTempo(rotinaDemorada, System::currentTimeMillis);

I put the lambda in a separate variable for didactic purposes, but we could pass it directly as a parameter.

So, we pass a lambda and then the static method currentTimeMillis of class System as arguments. When the measurement routine runs, timestampSupplier.get() is exactly the same as System.currentTimeMillis() . The static method is called twice, at the beginning and at the end, where the time difference is then printed to the console.

Note that System.currentTimeMillis() takes no parameters and returns a long number, so it is compatible with the functional interface. The same goes for the lambda that is assigned to the variable Runnable , that is, it has no parameters and does not return a value.

2. Method of a specific instance

Example:

public int tamanho(Supplier<Integer> s) {
    return s.get();
}

Supplier is a functional interface whose execution returns a value of a certain type, Integer in this case.

We can call the method like this:

String s = "Hello, Instance Method!";
int t = tamanho(s::length);

Here we pass the object s length method s as an argument. When the routine executes, s.get() is exactly the same as s.length() . The length of the String is returned.

Note that s.length() takes no parameters and returns an integer, so it is compatible with the functional interface.

Another interesting point is that you could pass List.size () to the routine with the functional interface. Therefore, the interface compatibility mechanism allows for another level of generic programming.

3. Method of a class applied to any instance

Example:

public int tamanho(Function<String, Integer> f) {
    return f.apply("Hello, Class Method!");
}

Function is a functional interface that takes a value of type T ( String ) and returns a value of type R ( Integer ).

We can call the method like this:

int t = tamanho(String::length);

Here we pass the length method of the String class as an argument. When the routine executes, f.apply("...") is exactly the same as calling the "...".length() , that is, on the String passed as an argument to apply .

Note that in the previous example, the functional interface does not know which object the method belongs to. In this example, we can call the method on any instance of the type.

4. Reference to a builder

Example:

public String newHello(Function<String, String> f) {
    return f.apply("Hello, Constructor!");
}

Function is a functional interface that takes a value of type T ( String ) and returns a value of type R (also String ).

We can call the method like this:

String s = newHello(String::new);

Here we pass the String(String) as an argument. When the routine executes, f.apply("...") is exactly the same as instantiating the String like this: new String("...") .

In practice, there isn't much difference between this approach and the previous one, except that using constructors makes it easier to use immutable objects like String , BigDecimal , Integer and others when using the functional API.

About the NetBeans Transformation

The code generated by the IDE is not good:

int soma = 0;
return numeros.stream().map((numero) -> numero).reduce(soma, Integer::sum);

Unnecessary mapping

I don't know why the IDE generated a call to the map method. It is basically useful when you want to transform all the values ​​of a stream into something else, generating a new stream . In this case it is totally unnecessary.

The code below generates the same result:

int soma = 0;
return numeros.stream().reduce(soma, Integer::sum);

Unnecessary variable

The variable soma is not necessary as it only defines an initial value.

The code below generates the same result:

return numeros.stream().reduce(0, Integer::sum);

Alternative: IntStream.sum()

The Java 8 Streams API makes it possible to perform a certain operation on a sequence of values ​​and aggregate the result in some way. This is analogous to SQL aggregation operations like SUM and AVG .

One of the examples is the reduce method that has the parameters:

  1. Initial value
  2. aggregation method

However, there are shortcuts to common operations on numbers such as the IntStream class that implements sum , min , max , average and count .

In the documentation for sum , it reads that it is equivalent to reduce(0, Integer::sum) . So we could rewrite the code like this:

return numeros.stream().mapToInt(Integer::intValue).sum();

Alternative: array of primitives

The matpToInt in the previous example is only necessary because the initial stream was not of type IntStream .

This would not be necessary if instead of List<Integer> you had a primitive array int[] .

Example:

int[] numerosArray = new int[] { 1, 2, 3 };

So the code to use sum would be just:

return IntStream.of(numerosArray).sum();
Scroll to Top