Refactoring a nested foreach - java

private List getEnumFromType(List vars, List enums) {
List enumList = new ArrayList<>();
for (Bean.Var var : vars) {
String typeWithoutTypeIdentifierPrefix = var.getType().substring(1,var.getType().length());
for (Enum enumVal : enums) {
if (typeWithoutTypeIdentifierPrefix.equals(enumVal.getName())) {
if (!enumList.contains(enumVal)) {
enumList.add(enumVal);
}
}
}
}
return enumList;
}

You have chained two terminal stream operators.
.forEach() returns void, hence the second .forEach() complains that it can't find a stream to work with.
You may want to read some of the Java 8 Stream documentation before continuing.

Don't do this.
Don't get the idea that the Java 8 Stream API should be used every time you are looping through a collection. It's not a wildcard that you can use to replace all enhanced for loops, especially nested ones.
Your error occurs because you are trying to call forEach on the return value of forEach. Since your for loops are nested, the calls to forEach should also be nested in the stream version. The second for loop should be put in a place like this:
.forEach(countries -> countries.getFromZone().getCountries().stream().filter(country ->country.getCode().equals(selectedCountry).forEach(...))
But seriously, Don't do this.
Your code is very messy in the stream version. It is far less readable than the for loops, mainly because you have a nested for loop. Instead of trying to rewrite your code using streams, you should try to abstract out the logic of your current code. Extract some methods for example:
for (Rate rate : product.getrates()) {
if (rateMatches(value)) { // I tried to guess what you are trying to do here. If you have better names please use yours
for (Countrys countrys : rate.getFromCountry().getCountries()) {
if (countrys.getCode().equals(selectedCountry)) {
updateDisplay(value);
break;
}
}
}
}
This way it's much more clearer.

Don't complicate too much, think of it on simple terms. Keep in mind streams are also about making easier to follow code:
find all Rate/Countrys pairs that match your criteria
For each of them, update value accordingly.
Java streams approach (there are more alternatives):
public void yourMethod() {
X product = ...;
Y value = ...;
Z selectedCountry = ...;
if (product.getRates() == null || product.getRates().isEmpty()) {
return;
}
product.getRates().stream()
.filter(r -> matchesValueRate(r, value))
.flatMap(this::rateCountrysPairStream)
.filter(p -> matchesSelectedCountry(p, selectedCountry))
.forEach(p -> updateValue(p, v));
}
public boolean matchesValueRate(Rate candidate, Y value) {
return value.getAtrribute().getRateType().getCode().equalsIgnoreCase(candidate.getRateType().getCode()) && ...; // add your tzone filter also
}
public Stream<Pair<Rate, Countrys>> rateCountrysPairStream(Rate rate) {
return rate.getFromCountry().getCountries().stream().map(c -> Pair.of(rate, c));
}
public boolean matchesSelectedCountry(Pair<Rate, Countrys> candidate, Z selectedCountry) {
return selectedCountry.equals(candidate.second().getCode());
}
public void updateValue(Pair<Rate, Countrys> rateCountry, Y value) {
Rate rate = rateCountry.first();
Countrys country = rateCountry.second();
// do your display stuff here
}
public static class Pair<K, V> {
private final K first;
private final V second;
private Pair(K first, V second) {
this.first = first;
this.second = second;
}
public static <K, V> Pair<K, V> of(K first, V second) {
return new Pair<>(first, second);
}
public K first() {
return first;
}
public V second() {
return second;
}
}

Related

Nested for loop to lambda - Java

I'm trying to replace java for loop with a lambda.
I have at first a class Arrs:
public class Arrs {
private boolean isX;
public boolean isX() {
return isX;
}
public void setX(boolean x) {
isX = x;
}
}
Then I have a class called Example:
public class Example {
private Arrs[] arrs;
public Arrs[] getArrs() {
return arrs;
}
public void setArrs(Arrs[] arrs) {
this.arrs = arrs;
}
}
Then in my main I want to count the total times of true value of isX variable.
Using pure Java for-loop the code is:
int count = 0;
for(Example anExample : exampleList) {
for(Arrs anArray : anExample.getArrs()) {
if(anArray.isX()) {
count++;
}
}
}
With lambda I tried the following:
Stream<Object> x = a.map(anArray -> {
return Arrays.stream(anArray).filter(array -> array.isX()).count();
});
But it does not return the correct number of element.
Well, you could just use flatMap and filter:
long count = exampleList.stream()
.flatMap(example -> Arrays.stream(example.getArrs()))
.filter(Arrs::isX) // Arrs::isX == arr -> arr.isX()
.count();
First, we're streaming over the list of examples, and flat map each element to getArrs(). Flat mapping makes sure that all elements of all getArrs() are present in a single stream.
Then we just filter by the predicate arr.isX(). At last, we call count() on the stream, which returns the number of (remaining) elements.
same flatmap, but with reduce instead of filter+count:
exampleList.stream()
.flatMap(example -> Arrays.stream(example.getArrs()))
.reduce(0, (total, arr) -> arr.isX() ? total+1 : 0);
reduce is a nice little function that goes over a stream and does something to each element while "carrying over" a value.
more on the subject Here
You are not getting objects from exampleList. Here is a piece of code that you are trying to achieve. :)
long x = exampleList.stream().map(example-> {
return Arrays
.stream(example.getArrs())
.filter(array -> array.isX())
.count();
}).count();

Compare String in ENUM

I want to implement storing of enabled or disabled features into database row. When some String value is received from them the network I would like to compare it into ENUM.
ENUM:
public enum TerminalConfigurationFeatureBitString {
Authorize("authorize", 0), // index 0 in bit string
Authorize3d("authorize3d", 1), // index 1 in bit String
Sale("sale", 2), // index 2 in bit String
Sale3d("sale3d", 3), // index 3 in bit String
}
Map<TerminalConfigurationFeatureBitString, Boolean> featureMaps =
config.initFromDatabaseValue(optsFromDatabase);
featureMaps.get(transaction.transactionType);
The best way is to use featureMaps.get(TerminalConfigurationFeatureBitString.Sale);
But I don't know the incoming string what would be.
Now I get warning Unlikely argument type String for get(Object) on a Map<TerminalConfigurationFeatureBitString,Boolean>
Is there any other way to make a query into the ENUM without knowing the key?
In cases like these, I often find myself adding a static method getByX which does a lookup based upon a property of the enum:
public enum BitString {
//...
public static Optional<BitString> getByTransactionType(String transactionType)
{
return Arrays.stream(values())
.filter(x -> x.transactionType.equals(transactionType))
.findFirst();
}
}
Usage:
enum TransactionStatus
{
ENABLED, NOT_ENABLED, NOT_SUPPORTED
}
TransactionStatus status = BitString.getBygetByTransactionType(transaction.transactionType)
.map(bitString -> featureMaps.get(bitString))
.map(enabled -> enabled ? TransactionStatus.ENABLED : TransactionStatus.NOT_ENABLED)
.orElse(TransactionStatus.NOT_SUPPORTED);
Similar to #Michael's answer, you can just generate a static lookup map inside your enum which maps an enums transaction type to the actual enum:
private static final Map<String, TerminalConfigurationFeatureBitString> TRANSACTION_TYPE_TO_ENUM =
Arrays.stream(values()).collect(Collectors.toMap(
TerminalConfigurationFeatureBitString::getTransactionType,
Function.identity()
);
And then have a lookup method, also inside the enum:
public static TerminalConfigurationFeatureBitString getByTransactionType(String transactionType) {
TerminalConfigurationFeatureBitString bitString = TRANSACTION_TYPE_TO_ENUM.get(transactionType);
if(bitString == null) throw new NoSuchElementException(transactionType);
return bitString;
}
This in a way more performant than the mentioned answer, because the Map is created the first time the enum is loaded (So when it is the first time referenced). And thus the iteration happens only once. Also Maps have a rather fast lookup time so you could say that getting an enum this way works O(1) (when ignoring the initial computation time of O(n))
You can extend your enum with extra static method which will try to convert given String on enum item:
enum TerminalConfigurationFeatureBitString {
Authorize("authorize", 0), // index 0 in bit string
Authorize3d("authorize3d", 1), // index 1 in bit String
Sale("sale", 2), // index 2 in bit String
Sale3d("sale3d", 3); // index 3 in bit String
private final String value;
private final int index;
TerminalConfigurationFeatureBitString(String value, int index) {
this.value = value;
this.index = index;
}
public String getValue() {
return value;
}
public int getIndex() {
return index;
}
public static Optional<TerminalConfigurationFeatureBitString> fromValue(String value) {
for (TerminalConfigurationFeatureBitString item : values()) {
if (item.value.equals(value)) {
return Optional.of(item);
}
}
return Optional.empty();
}
}
In case option is not found, return Optional.empty(). If feature is not present it means String representation does not represent any feature. Usage:
public void test() {
EnumMap<TerminalConfigurationFeatureBitString, Boolean> featureMaps = new EnumMap<>(
TerminalConfigurationFeatureBitString.class);
Optional<TerminalConfigurationFeatureBitString> feature = TerminalConfigurationFeatureBitString.fromValue("authorize");
if (!feature.isPresent()) {
System.out.println("Feature is not foudn!");
} else {
Boolean authorize = featureMaps.get(feature.get());
if (authorize != null && authorize) {
System.out.println("Feature is enabled!");
} else {
System.out.println("Feature is disabled!");
}
}
}

using java streams in parallel with collect(supplier, accumulator, combiner) not giving expected results

I'm trying to find number of words in given string. Below is sequential algorithm for it which works fine.
public int getWordcount() {
boolean lastSpace = true;
int result = 0;
for(char c : str.toCharArray()){
if(Character.isWhitespace(c)){
lastSpace = true;
}else{
if(lastSpace){
lastSpace = false;
++result;
}
}
}
return result;
}
But, when i tried to 'parallelize' this with Stream.collect(supplier, accumulator, combiner) method, i am getting wordCount = 0. I am using an immutable class (WordCountState) just to maintain the state of word count.
Code :
public class WordCounter {
private final String str = "Java8 parallelism helps if you know how to use it properly.";
public int getWordCountInParallel() {
Stream<Character> charStream = IntStream.range(0, str.length())
.mapToObj(i -> str.charAt(i));
WordCountState finalState = charStream.parallel()
.collect(WordCountState::new,
WordCountState::accumulate,
WordCountState::combine);
return finalState.getCounter();
}
}
public class WordCountState {
private final boolean lastSpace;
private final int counter;
private static int numberOfInstances = 0;
public WordCountState(){
this.lastSpace = true;
this.counter = 0;
//numberOfInstances++;
}
public WordCountState(boolean lastSpace, int counter){
this.lastSpace = lastSpace;
this.counter = counter;
//numberOfInstances++;
}
//accumulator
public WordCountState accumulate(Character c) {
if(Character.isWhitespace(c)){
return lastSpace ? this : new WordCountState(true, counter);
}else{
return lastSpace ? new WordCountState(false, counter + 1) : this;
}
}
//combiner
public WordCountState combine(WordCountState wordCountState) {
//System.out.println("Returning new obj with count : " + (counter + wordCountState.getCounter()));
return new WordCountState(this.isLastSpace(),
(counter + wordCountState.getCounter()));
}
I've observed two issues with above code :
1. Number of objects (WordCountState) created are greater than number of characters in the string.
2. Result is always 0.
3. As per accumulator/consumer documentation, shouldn't the accumulator return void? Even though my accumulator method is returning an object, compiler doesn't complain.
Any clue where i might have gone off track?
UPDATE :
Used solution as below -
public int getWordCountInParallel() {
Stream<Character> charStream = IntStream.range(0, str.length())
.mapToObj(i -> str.charAt(i));
WordCountState finalState = charStream.parallel()
.reduce(new WordCountState(),
WordCountState::accumulate,
WordCountState::combine);
return finalState.getCounter();
}
You can always invoke a method and ignore its return value, so it’s logical to allow the same when using method references. Therefore, it’s no problem creating a method reference to a non-void method when a consumer is required, as long as the parameters match.
What you have created with your immutable WordCountState class, is a reduction operation, i.e. it would support a use case like
Stream<Character> charStream = IntStream.range(0, str.length())
.mapToObj(i -> str.charAt(i));
WordCountState finalState = charStream.parallel()
.map(ch -> new WordCountState().accumulate(ch))
.reduce(new WordCountState(), WordCountState::combine);
whereas the collect method supports the mutable reduction, where a container instance (may be identical to the result) gets modified.
There is still a logical error in your solution as each WordCountState instance starts with assuming to have a preceding space character, without knowing the actual situation and no attempt to fix this in the combiner.
A way to fix and simplify this, still using reduction, would be:
public int getWordCountInParallel() {
return str.codePoints().parallel()
.mapToObj(WordCountState::new)
.reduce(WordCountState::new)
.map(WordCountState::getResult).orElse(0);
}
public class WordCountState {
private final boolean firstSpace, lastSpace;
private final int counter;
public WordCountState(int character){
firstSpace = lastSpace = Character.isWhitespace(character);
this.counter = 0;
}
public WordCountState(WordCountState a, WordCountState b) {
this.firstSpace = a.firstSpace;
this.lastSpace = b.lastSpace;
this.counter = a.counter + b.counter + (a.lastSpace && !b.firstSpace? 1: 0);
}
public int getResult() {
return counter+(firstSpace? 0: 1);
}
}
If you are worrying about the number of WordCountState instances, note how many Character instances this solution does not create, compared to your initial approach.
However, this task is indeed suitable for mutable reduction, if you rewrite your WordCountState to a mutable result container:
public int getWordCountInParallel() {
return str.codePoints().parallel()
.collect(WordCountState::new, WordCountState::accumulate, WordCountState::combine)
.getResult();
}
public class WordCountState {
private boolean firstSpace, lastSpace=true, initial=true;
private int counter;
public void accumulate(int character) {
boolean white=Character.isWhitespace(character);
if(lastSpace && !white) counter++;
lastSpace=white;
if(initial) {
firstSpace=white;
initial=false;
}
}
public void combine(WordCountState b) {
if(initial) {
this.initial=b.initial;
this.counter=b.counter;
this.firstSpace=b.firstSpace;
this.lastSpace=b.lastSpace;
}
else if(!b.initial) {
this.counter += b.counter;
if(!lastSpace && !b.firstSpace) counter--;
this.lastSpace = b.lastSpace;
}
}
public int getResult() {
return counter;
}
}
Note how using int to represent unicode characters consistently, allows to use the codePoint() stream of a CharSequence, which is not only simpler, but also handles characters outside the Basic Multilingual Plane and is potentially more efficient, as it doesn’t need boxing to Character instances.
When you implemented stream().collect(supplier, accumulator, combiner) they do return void (combiner and accumulator). The problem is that this:
collect(WordCountState::new,
WordCountState::accumulate,
WordCountState::combine)
In your case actually means (just the accumulator, but same goes for the combiner):
(wordCounter, character) -> {
WordCountState state = wc.accumulate(c);
return;
}
And this is not trivial to get indeed. Let's say we have two methods:
public void accumulate(Character c) {
if (!Character.isWhitespace(c)) {
counter++;
}
}
public WordCountState accumulate2(Character c) {
if (Character.isWhitespace(c)) {
return lastSpace ? this : new WordCountState(true, counter);
} else {
return lastSpace ? new WordCountState(false, counter + 1) : this;
}
}
For the them the below code will work just fine, BUT only for a method reference, not for lambda expressions.
BiConsumer<WordCountState, Character> cons = WordCountState::accumulate;
BiConsumer<WordCountState, Character> cons2 = WordCountState::accumulate2;
You can imagine it slightly different, via an class that implementes BiConsumer for example:
BiConsumer<WordCountState, Character> clazz = new BiConsumer<WordCountState, Character>() {
#Override
public void accept(WordCountState state, Character character) {
WordCountState newState = state.accumulate2(character);
return;
}
};
As such your combine and accumulate methods needs to change to:
public void combine(WordCountState wordCountState) {
counter = counter + wordCountState.getCounter();
}
public void accumulate(Character c) {
if (!Character.isWhitespace(c)) {
counter++;
}
}
First of all, would it not be easier to just use something like input.split("\\s+").length to get the word count?
In case this is an exercise in streams and collectors, let's discuss your implementation. The biggest mistake was pointed out by you already: Your accumulator and combiner should not return new instances. The signature of collect tells you that it expects BiConsumer, which do not return anything. Because you create new object in the accumulator, you never increase the count of the WordCountState objects your collector actually uses. And by creating a new object in the combiner you would discard any progress you would have made. This is also why you create more objects than characters in your input: one per character, and then some for the return values.
See this adapted implementation:
public static class WordCountState
{
private boolean lastSpace = true;
private int counter = 0;
public void accumulate(Character character)
{
if (!Character.isWhitespace(character))
{
if (lastSpace)
{
counter++;
}
lastSpace = false;
}
else
{
lastSpace = true;
}
}
public void combine(WordCountState wordCountState)
{
counter += wordCountState.counter;
}
}
Here, we do not create new objects in every step, but change the state of the ones we have. I think you tried to create new objects because your Elvis operators forced you to return something and/or you couldn't change the instance fields as they are final. They do not need to be final, though, and you can easily change them.
Running this adapted implementation sequentially now works fine, as we nicely look at the chars one by one and end up with 11 words.
In parallel, though, it fails. It seems it creates a new WordCountState for every char, but does not count all of them, and ends up at 29 (at least for me). This shows a basic flaw with your algorithm: Splitting on every character doesn't work in parallel. Imagine the input abc abc, which should result in 2. If you do it in parallel and do not specify how to split the input, you might end up with these chunks: ab, c a, bc, which would add up to 4.
The problem is that by parallelizing between characters (i.e. in the middle of words), you make your separate WordCountStates dependent on each other (because they would need to know which one come before them and whether it ended with a whitespace char). This defeats the parallelism and results in errors.
Aside from all that, it might be easier to implement the Collector interface instead of providing the three methods:
public static class WordCountCollector
implements Collector<Character, SimpleEntry<AtomicInteger, Boolean>, Integer>
{
#Override
public Supplier<SimpleEntry<AtomicInteger, Boolean>> supplier()
{
return () -> new SimpleEntry<>(new AtomicInteger(0), true);
}
#Override
public BiConsumer<SimpleEntry<AtomicInteger, Boolean>, Character> accumulator()
{
return (count, character) -> {
if (!Character.isWhitespace(character))
{
if (count.getValue())
{
String before = count.getKey().get() + " -> ";
count.getKey().incrementAndGet();
System.out.println(before + count.getKey().get());
}
count.setValue(false);
}
else
{
count.setValue(true);
}
};
}
#Override
public BinaryOperator<SimpleEntry<AtomicInteger, Boolean>> combiner()
{
return (c1, c2) -> new SimpleEntry<>(new AtomicInteger(c1.getKey().get() + c2.getKey().get()), false);
}
#Override
public Function<SimpleEntry<AtomicInteger, Boolean>, Integer> finisher()
{
return count -> count.getKey().get();
}
#Override
public Set<java.util.stream.Collector.Characteristics> characteristics()
{
return new HashSet<>(Arrays.asList(Characteristics.CONCURRENT, Characteristics.UNORDERED));
}
}
We use a pair (SimpleEntry) to keep the count and the knowledge about the last space. This way, we do not need to implement the state in the collector itself or write a param object for it. You can use this collector like this:
return charStream.parallel().collect(new WordCountCollector());
This collector parallelizes nicer than the initial implementation, but still varies in results (mostly between 14 and 16) because of the mentioned weaknesses in your approach.

Collect HashSet / Java 8 / Regex Pattern / Stream API

Recently I change version of the JDK 8 instead 7 of my project and now I overwrite some code snippets using new features that came with Java 8.
final Matcher mtr = Pattern.compile(regex).matcher(input);
HashSet<String> set = new HashSet<String>() {{
while (mtr.find()) add(mtr.group().toLowerCase());
}};
How I can write this code using Stream API ?
A Matcher-based spliterator implementation can be quite simple if you reuse the JDK-provided Spliterators.AbstractSpliterator:
public class MatcherSpliterator extends AbstractSpliterator<String[]>
{
private final Matcher m;
public MatcherSpliterator(Matcher m) {
super(Long.MAX_VALUE, ORDERED | NONNULL | IMMUTABLE);
this.m = m;
}
#Override public boolean tryAdvance(Consumer<? super String[]> action) {
if (!m.find()) return false;
final String[] groups = new String[m.groupCount()+1];
for (int i = 0; i <= m.groupCount(); i++) groups[i] = m.group(i);
action.accept(groups);
return true;
}
}
Note that the spliterator provides all matcher groups, not just the full match. Also note that this spliterator supports parallelism because AbstractSpliterator implements a splitting policy.
Typically you will use a convenience stream factory:
public static Stream<String[]> matcherStream(Matcher m) {
return StreamSupport.stream(new MatcherSpliterator(m), false);
}
This gives you a powerful basis to concisely write all kinds of complex regex-oriented logic, for example:
private static final Pattern emailRegex = Pattern.compile("([^,]+?)#([^,]+)");
public static void main(String[] args) {
final String emails = "kid#gmail.com, stray#yahoo.com, miks#tijuana.com";
System.out.println("User has e-mail accounts on these domains: " +
matcherStream(emailRegex.matcher(emails))
.map(gs->gs[2])
.collect(joining(", ")));
}
Which prints
User has e-mail accounts on these domains: gmail.com, yahoo.com, tijuana.com
For completeness, your code will be rewritten as
Set<String> set = matcherStream(mtr).map(gs->gs[0].toLowerCase()).collect(toSet());
Marko's answer demonstrates how to get matches into a stream using a Spliterator. Well done, give that man a big +1! Seriously, make sure you upvote his answer before you even consider upvoting this one, since this one is entirely derivative of his.
I have only a small bit to add to Marko's answer, which is that instead of representing the matches as an array of strings (with each array element representing a match group), the matches are better represented as a MatchResult which is a type invented for this purpose. Thus the result would be a Stream<MatchResult> instead of Stream<String[]>. The code gets a little simpler, too. The tryAdvance code would be
if (m.find()) {
action.accept(m.toMatchResult());
return true;
} else {
return false;
}
The map call in his email-matching example would change to
.map(mr -> mr.group(2))
and the OP's example would be rewritten as
Set<String> set = matcherStream(mtr)
.map(mr -> mr.group(0).toLowerCase())
.collect(toSet());
Using MatchResult gives a bit more flexibility in that it also provides offsets of match groups within the string, which could be useful for certain applications.
I don't think you can turn this into a Stream without writing your own Spliterator, but, I don't know why you would want to.
Matcher.find() is a state changing operation on the Matcher object so running each find() in a parallel stream would produce inconsistent results. Running the stream in serial wouldn't have better performance that the Java 7 equivalent and would be harder to understand.
What about Pattern.splitAsStream ?
Stream<String> stream = Pattern.compile(regex).splitAsStream(input);
and then a collector to get a set.
Set<String> set = stream.map(String::toLowerCase).collect(Collectors.toSet());
What about
public class MakeItSimple {
public static void main(String[] args) throws FileNotFoundException {
Scanner s = new Scanner(new File("C:\\Users\\Admin\\Desktop\\TextFiles\\Emails.txt"));
HashSet<String> set = new HashSet<>();
while ( s.hasNext()) {
String r = s.next();
if (r.matches("([^,]+?)#([^,]+)")) {
set.add(r);
}
}
set.stream().map( x -> x.toUpperCase()).forEach(x -> print(x));
s.close();
}
}
Here is the implementation using Spliterator interface.
// To get the required set
Set<String> result = (StreamSupport.stream(new MatcherGroupIterator(pattern,input ),false))
.map( s -> s.toLowerCase() )
.collect(Collectors.toSet());
...
private static class MatcherGroupIterator implements Spliterator<String> {
private final Matcher matcher;
public MatcherGroupIterator(Pattern p, String s) {
matcher = p.matcher(s);
}
#Override
public boolean tryAdvance(Consumer<? super String> action) {
if (!matcher.find()){
return false;
}
action.accept(matcher.group());
return true;
}
#Override
public Spliterator<String> trySplit() {
return null;
}
#Override
public long estimateSize() {
return Long.MAX_VALUE;
}
#Override
public int characteristics() {
return Spliterator.NONNULL;
}
}

What's the best way to implement `next` and `previous` on an enum type?

Suppose I have an enum:
enum E {
A, B, C;
}
As shown in this answer by lucasmo, enum values are stored in a static array in the order that they are initialized, and you can later retrieve (a clone of) this array with E.values().
Now suppose I want to implement E#getNext and E#getPrevious such that all of the following expressions evaluate to true:
E.A.getNext() == E.B
E.B.getNext() == E.C
E.C.getNext() == E.A
E.A.getPrevious() == E.C
E.B.getPrevious() == E.A
E.C.getPrevious() == E.B
My current implementation for getNext is the following:
public E getNext() {
E[] e = E.values();
int i = 0;
for (; e[i] != this; i++)
;
i++;
i %= e.length;
return e[i];
}
and a similar method for getPrevious.
However, this code seems cumbersome at best (e.g., "empty" for loop, arguable abuse of a counter variable, and potentially erroneous at worst (thinking reflection, possibly).
What would be the best way to implement getNext and getPrevious methods for enum types in Java 7?
NOTE: I do not intend this question to be subjective. My request for the "best" implementation is shorthand for asking for the implementation that is the fastest, cleanest, and most maintainable.
Try this:
public enum A {
X, Y, Z;
private static final A[] vals = values();
public A next() {
return vals[(this.ordinal() + 1) % vals.length];
}
}
Implementation of previous() is left as an exercise, but recall that in Java, the modulo a % b can return a negative number.
EDIT: As suggested, make a private static copy of the values() array to avoid array copying each time next() or previous() is called.
Alternatively, one can go somehow along the lines of the following idea:
public enum SomeEnum {
A, B, C;
public Optional<SomeEnum> next() {
switch (this) {
case A: return Optional.of(B);
case B: return Optional.of(C);
// any other case can NOT be mapped!
default: return Optional.empty();
}
}
Notes:
In contrast to the other answer, this way does some implicit mapping; instead of relying on ordinal(). Of course that means more code; but it also forces the author to consider what it means to add new constants or remove existing ones. When relying on ordinal, your implicit assumption is that the order is based on the order used for the enum constant declaration. So when somebody comes back 6 months later and has to add a new constant, he has to understand that the new constant Y needs X, Y, Z ... instead of just appending X, Z, Y!
There might be situations where it doesn't make any sense for the "last" enum constant to have the "first" as successor. Think of T-Shirt sizes for examples. XXL.next() is for sure not XS. For such situations, using Optional is the more appropriate answer.
public enum Three
{
One, Two, Three;
static
public final Three[] values = values();
public Three prev() {
return values[(ordinal() - 1 + values.length) % values.length];
}
public Three next() {
return values[(ordinal() + 1) % values.length];
}
}
Here's another take at the problem:
public enum Planet {
MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE;
private Planet prevPlanet = null;
private Planet nextPlanet = null;
static {
for (int i = 1; i <= values.length; i++) {
Planet current = values[i % values.length];
current.prevPlanet = values[i - 1];
current.nextPlanet = values[(i + 1) % values.length];
}
}
public Planet prev() {
return prevPlanet;
}
public Planet next() {
return nextPlanet;
}
}
With this approach, all calculations are done during static initialization and the actual methods directly return the result from a member variable.
However, I would argue that for this enum (and for most enums in general), wrapping around doesn't make sense, so I would rather do it this way:
import java.util.Optional;
public enum Planet {
MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE;
private Planet prevPlanet = null;
private Planet nextPlanet = null;
static {
Planet[] values = Planet.values();
for (int i = 1; i < values.length; i++) {
values[i].prevPlanet = values[i - 1];
}
for (int i = 0; i < values.length - 1; i++) {
values[i].nextPlanet = values[i + 1];
}
}
public Optional<Planet> prev() {
return Optional.ofNullable(prevPlanet);
}
public Optional<Planet> next() {
return Optional.ofNullable(nextPlanet);
}
}
Here, the first planet does not have a previous one and the last one does not have a next one. Optional is used to make it even more explicit that callers of the code need to be prepared that not every planet has a next/previous one. Whether you want to use Optional is up to you, the code works just as well with the getters of the first implementation, in which case a null would be returned directly instead of as an empty Optional.
Another thing to consider is that the desired ordering may not match the enumeration of the values. There could also be special values in the enum that do not fit in the ordering. Or you could just want to make the specification of the ordering explicit so that one can not accidentally break the logic by adding a new value to the enum out of order. Then you can do this:
import java.util.Optional;
public enum Planet {
MERCURY, VENUS(MERCURY), EARTH(VENUS), MARS(EARTH), JUPITER(MARS),
SATURN(JUPITER), URANUS(SATURN), NEPTUNE(URANUS);
private Planet prevPlanet = null;
private Planet nextPlanet = null;
Planet() {}
Planet(Planet prev) {
this.prevPlanet = prev;
prev.nextPlanet = this;
}
public Optional<Planet> prev() {
return Optional.ofNullable(prevPlanet);
}
public Optional<Planet> next() {
return Optional.ofNullable(nextPlanet);
}
}
This doesn't allow for wraparounds, but you could use this as a low-impact way to check adjacency:
enum Phase {
ONE, TWO, THREE;
public final Phase previous;
Phase() {
previous = Data.last;
Data.last = this
}
private static class Data {
private static Phase last = null;
}
}
class Example {
Phase currentPhase = Phase.ONE;
void advanceToPhase(Phase nextPhase) {
if (nextPhase.previous == currentPhase)
currentPhase = nextPhase;
}
}
It has to use an auxiliary static class to store a variable for the static initializer, but it has the advantage of being extremely low-cost at startup.
Same approach as #Zoltan but without optional :
public enum Planet {
MERCURY, VENUS(MERCURY), EARTH(VENUS), MARS(EARTH), JUPITER(MARS),
SATURN(JUPITER), URANUS(SATURN), NEPTUNE(URANUS);
private Planet prevPlanet = null;
private Planet nextPlanet = null;
Planet() {
// required for Mercury
}
Planet(Planet prev) {
prevPlanet = prev;
prev.nextPlanet = this;
}
public Planet prev() {
return this == MERCURY ? this : prevPlanet;
}
public Planet next() {
return this == NEPTUNE ? this : nextPlanet;
}
}
TreeSet-based implementation
We can implement an enum capable of retrieving next and previous members via instance methods next() and previous() by using TreeSet, which maintains a Red-black tree under the hood, and offers methods for traversing the tree like higher() and lower().
The implementation below would in a way similar to a circular list, retrieving the fist enum-constant when next() is invoked on the very last constant, and vice versa returning the last when previous() is called on the very first constant.
Instead of hard-coding the fist and the last enum-members inside the next() and previous() when we hit edge-cases, we can use methods first() and last(). By doing so, we're eliminating the possibility of introducing a bug, if someone would decide to add a few more constants or reorder them.
public enum Enum {
A, B, C, D, E, F, G;
private static final NavigableSet<Enum> set =
new TreeSet<>(EnumSet.allOf(Enum.class)); // EnumSet.allOf() generates a set of enum-constants of the specified type
public Enum next() {
return Objects.requireNonNullElseGet(
set.higher(this), set::first
);
}
public Enum previous() {
return Objects.requireNonNullElseGet(
set.lower(this), set::last
);
}
}
Note: both higher() and lower() would return null if the requested element doesn't exist. To dial with the edge-cases I've used Java 11 utility method Objects.requireNonNullElseGet() which expects a nullable value and a Supplier that would be used only if provided value is null (reminder: Java 8 functions are lazy).
main()
public static void main(String[] args) {
EnumSet.allOf(Enum.class).forEach(e ->
System.out.println(e + " -> " + " next: " + e.next() + " prev: " + e.previous())
);
}
Output:
A -> next: B prev: G
B -> next: C prev: A
C -> next: D prev: B
D -> next: E prev: C
E -> next: F prev: D
F -> next: G prev: E
G -> next: A prev: F
Performance Note
All the methods of the TreeSet used above have logarithmic time complexity O(log n), which in most of the real life scenarios would result in only a couple of steps through the tree, since the waste majority of enums have fewer than ten constants. Therefore, we can call it acceptable, but it can't beat constant time the performance of the straightforward solution provided in the answer by Jim Garrison.
That said, the code above is meant to serve educational purposes by illustrating how a TreeSet can be utilized.

Categories