Best way to go through a Hashset

Asked

Viewed 1,091 times

4

I’m used to programming in PHP and now I’m studying Java, and the Collections API is something new and very interesting for me.

I searched the internet and easily found several ways to go through a Hashset, such as:

  • Java 8 foreach and lambda Expression.
  • iterator().
  • iterator() and Java 8 forEachRemaining method().
  • simple for-each loop.

But, what is the best way to go? Or what is the difference between the various options and what should I use in different cases?

  • 1

    It depends on a lot of things. Often better a simple for-each, but there are cases where you need iterator. The method .forEach i don’t see much need to use in the general case, but it gets a more functional look to be. If you receive a Consumer to make the iteration, then the method .forEach is the best. I don’t know the .forEachRemaining

1 answer

4


Normally I would recommend going by the loop for-each traditional. It solves much of what you normally want when you go through a collection.

Of course, there are some exceptions. And there are also convention reasons to treat it differently (I’ll get there).

for-each

The most trivial cases of iterating a collection (whatever) is simply treating each element of the collection, without altering the collection itself. And usually you save the result of the evaluation in a variable external to the loop, to work with this result later. For these cases, the greater simplicity of the for-each is enough.

In addition, you have all the advantages of flow control of a conventional loop:

  • continue to avoid unnecessary processing (and extra levels of indentation in blocks if)
  • break to stop the execution of the loop
  • return when you already have the desired result and there is no reason to continue processing

Iterator

The tie through the Iterator is only strictly necessary if you want to change the collection during iteration. Usually, in a Set, I only saw being used to remove elements from the collection. In a List I’ve seen being used to replace elements.

Like the noose for-each uses the Iterator underneath, but hiding all this code framework in a sweet and fluffy syntax, if you’re not going to use the proper methods of Iterator, then you’d better use the for-each.

Even so, even though Iterator the most suitable for these cases, the team here at work prefers to circumvent its use. So (in the era of Java 7 + Retrolambda, without the right to Streams of Java 8), to remove elements, we detect all elements that need to be removed and store in a list of actions what we wanted:

Set<Elemento> conjunto = ...;
ArrayList<Runnable> acoesRemocaoPostuma = new ArrayList<>();

for (Elemento e: conjunto) {
  if (devoRemover(e)) {
    acoesRemocaoPostuma.add(() -> conjunto.remove(e));
  }
}
for (Runnable acao: acoesRemocaoPostuma) {
  acao.run();
}

In addition, you have all the advantages of flow control of a conventional loop:

  • continue to avoid unnecessary processing (and extra levels of indentation in blocks if)
  • break to stop the execution of the loop
  • return when you already have the desired result

Iterator.forEachRemaining

I never used and I don’t know totally its use, I am obliged to update this answer in the future.

Method .forEach

Normally I only see practical advantage in using this method in 2 scenarios:

  1. you get a Consumer as a parameter
  2. you only need to call a function to evaluate the elements

For example, run all actions in a list of Runnable would look like this:

List<Runnable> listaAcoes = ...;
listaAcoes.forEach(Runnable::run);

If I need, for each element of the set, call a function called sincroniza passing as parameter a string representing the destination:

String destino = ...;
Set<Elemento> conjunto = ...;

conjunto.forEach(e -> sincroniza(destino, e));

The equivalent via loop of this code is a little more verbose:

String destino = ...;
Set<Elemento> conjunto = ...;

for (Elemento e: conjunto) {
  sincroniza(destino, e);
}

In addition, it also has an advantage of convention by iterating like this. In a team formed by young people and without much experience, the use of ties "encourages" change values of local variables, but external to the specific iteration. And, well... in a legacy code that wasn’t properly structured as it grew, it can generate unwanted side effects and hide bugs. Forcing to always use lambda functions, these changes do not occur, are prohibited by the language.

One disadvantage of using this structure to iterate is that it does not support loop control. You cannot directly ignore elements with continue, or also stop processing with a break let alone use a return to warn the caller that he has found what is desired.

using Streams to replace ties

Another way to iterate on elements is to use one stream of Java 8. And this has been seen with very good eyes by my team, but it is not unanimous. Use stream implies a less imperative/structured and more declarative/functional thinking, and functional programming has both fans (type I) and detractors, being people on the fence rare specimens that need to be studied.

With the construction of stream, you can simulate much of the flow control that you did with the loop.

Only count strings that start with "marm"? Filter and count:

conjuntoDeStringa.stream().filter(s -> s.startsWith("marm")).count();

Return some Elemento that is active? Returning null if you don’t find?

conjunto.stream().filter(Elemento::estahAtivo).findAny().orElse(null);

Concatenate all objects, converting them with toString and separating them with ;?

conjunto.stream().map(Object::toString).collect(Collectors.joining(";"));

Find out which of the elements Elemento returns the largest integer in valorResgate? Returning -1 in the case of empty set?

conjunto.stream().mapToInt(Elemento::valorResgate).max().orElse(-1);

Here where I work we work a lot with BigDecimal, and it is very easy for the programmer to make mistakes such as adding up the elements of a list. So we prefer to use stream to resolve this and avoid that pass the review these bugs:

itens.stream().map(Item::getVrItem).collect(BigDecimalUtils.coletorSoma());

Due to the idiosyncrasies of our system running at the same time in Java, Javascript (transformed from Java by GWT) and Totalcross, we need to use a wrapper of BigDecimal proper, since Totalcross did not support at the time with fullness java.math.BigDecimal.

In general, experience here at work indicates that the for-each is easier, but use streams proved less problematic and required a higher level of team abstraction, in a way making them think more and better what they want to do.

  • Excellent reply and very well detailed, thank you very much!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.