Reputation: 86757
I want to return a value of a stream based on a condition. Take the following as an example only, where I want to map any apple to Food.APPLE
:
public enum Food {
APPLE, APPLE2, APPLE3, BANANA, PINEAPPLE, CUCUMBER;
private static final Food[] APPLES = new Food[] {APPLE, APPLE2, APPLE3};
//java7
public Food fromValue(String value) {
for (Food type : Food.values()) {
if (type.name().equalsIgnoreCase(value)) {
return ArrayUtils.contains(APPLES, type) ? APPLE : type;
}
}
return null;
}
//java8: how to include the array check for APPLES?
public Food fromValue(String value) {
return Arrays.stream(Food.values()).
filter(type -> type.name().equalsIgnoreCase(value))
.findFirst()
.orElse(null);
}
}
How can I include the ternary condition in a stream?
Upvotes: 5
Views: 14226
Reputation: 982
According to previous answers, in particular from @Alexis I wrote some code to check booth approach (from Java 7 and Java 8). Maybe this can be useful for new users on Java 8.
So, I've made some changes in original answer. First of all, I put some unit test and I added two wrapping methods verifyNames() and contains(). Second, we can use a default behavior when unexpected action occurs, in this case, when the appleApproachTwo.fromValueJava8() was called with null or an not existing enum value.
Finally, the last change uses the potential uses for java.util.Optional objects. In this case, we can protect the environment to crash due to inconsistency to null objects. There are more discussion about Default Values, Optional and orElse() method at Default Values and Actions
public enum Food {
APPLE, APPLE2, APPLE3, BANANA, PINEAPPLE, CUCUMBER, NONE;
private static final Food[] APPLES = new Food[] {APPLE, APPLE2, APPLE3};
// approach one
// java7: conventional use
public Food fromValueJava7(String value) {
for (Food type : Food.values()) {
if (verifyNames(type, value)) {
return contains(Food.APPLES, type) ? Food.APPLE : type;
}
}
return null;
}
// approach two
// java8: how to include the array check for APPLES?
public Food fromValueJava8(String value) {
return Arrays.stream(Food.values())
.filter(type-> verifyNames(type, value))
.map(type -> contains(Food.APPLES, type) ? Food.APPLE : type)
.findFirst()
.orElse(Food.NONE);
}
private boolean contains(Food[] apples, Food type) {
return ArrayUtils.contains(apples, type);
}
private boolean verifyNames(Food type,String other) {
return type.name().equalsIgnoreCase(other);
}
}
// FoodTest
//
public class FoodTest {
@Test
public void foodTest(){
Food appleApproachOne = Food.APPLE;
// from approach one
assertEquals( appleApproachOne.fromValueJava7("APPLE"), Food.APPLE);
assertEquals( appleApproachOne.fromValueJava7("APPLE2"), Food.APPLE);
assertEquals( appleApproachOne.fromValueJava7("APPLE3"), Food.APPLE);
assertEquals( appleApproachOne.fromValueJava7("apple3"), Food.APPLE);
assertNull ( appleApproachOne.fromValueJava7("apple4") );
assertNull ( appleApproachOne.fromValueJava7(null) );
Food appleApproachTwo = Food.APPLE;
//from approach two
assertEquals( appleApproachTwo.fromValueJava8("APPLE"), Food.APPLE);
assertEquals( appleApproachTwo.fromValueJava8("APPLE2"), Food.APPLE);
assertEquals( appleApproachTwo.fromValueJava8("APPLE3"), Food.APPLE);
assertEquals( appleApproachTwo.fromValueJava8("apple3"), Food.APPLE);
assertEquals( appleApproachOne.fromValueJava8("apple4"), Food.NONE);
assertEquals( appleApproachTwo.fromValueJava8(null), Food.NONE);
}
}
Upvotes: 2
Reputation: 34460
As others have suggested, using a Map
would be better:
import java.util.EnumSet;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TernaryCondition {
public enum Food {
APPLE, APPLE2, APPLE3, BANANA, PINEAPPLE, CUCUMBER;
private static final EnumSet<Food> APPLES = EnumSet.of(APPLE, APPLE2, APPLE3);
private static final Map<String, Food> MAP = Stream.of(
Food.values()).collect(
Collectors.toMap(
f -> f.name().toLowerCase(),
f -> APPLES.contains(f) ? APPLE : f));
public static Food fromValue(String value) {
return MAP.get(value.toLowerCase());
}
}
public static void main(String[] args) {
Food f = Food.fromValue("apple2");
System.out.println(f); // APPLE
}
}
I'd also make fromValue()
method static
and APPLES
an EnumSet
. While I realise this answer is very similar to @Holger's, I just wanted to show another approach to build the map.
Upvotes: 0
Reputation: 93842
You could do it like this:
import static java.util.AbstractMap.SimpleImmutableEntry;
...
enum Food {
APPLE, APPLE2, APPLE3, BANANA, PINEAPPLE, CUCUMBER;
private static final Map<String, Food> MAP = Stream.concat(
Stream.of(APPLE, APPLE2, APPLE3).map(e -> new SimpleImmutableEntry<>(e.name().toLowerCase(), APPLE)),
Stream.of(BANANA, PINEAPPLE, CUCUMBER).map(e -> new SimpleImmutableEntry<>(e.name().toLowerCase(), e)))
.collect(toMap(SimpleImmutableEntry::getKey, SimpleImmutableEntry::getValue));
public static Food fromValue(String value) {
return MAP.get(value.toLowerCase());
}
}
The lookup in the map will be O(1)
.
Upvotes: 3
Reputation: 298233
There is nothing special to the ternary operator. So you can simply add this mapping operation to the Stream
public Food fromValue(String value) {
return Arrays.stream(Food.values())
.filter(type -> type.name().equalsIgnoreCase(value))
.map(type -> ArrayUtils.contains(APPLES, type)? APPLE: type)
.findFirst()
.orElse(null);
}
However, neither of these linear searches is really necessary. Use a Map
:
public enum Food {
APPLE, APPLE2, APPLE3, BANANA, PINEAPPLE, CUCUMBER;
private static final Map<String,Food> MAP
= new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
static {
EnumSet<Food> apples=EnumSet.of(APPLE, APPLE2, APPLE3);
apples.forEach(apple->MAP.put(apple.name(), APPLE));
EnumSet.complementOf(apples).forEach(e->MAP.put(e.name(), e));
}
public static Food fromValue(String value) {
return MAP.get(value);
}
}
It will perform a case insensitive lookup as desired and it is initialized to return the APPLE
substitute in the first place so no additional comparison is required.
Upvotes: 2
Reputation: 62555
As Alexis suggested, you can use a map operation
public Food fromValue_v8(String value) {
return Arrays.stream(Food.values())
.filter(type-> type.name().equalsIgnoreCase(value))
.map(type -> ArrayUtils.contains(APPLES, type) ? APPLE : type)
.findFirst()
.orElse(null);
}
Upvotes: 2