简体   繁体   中英

How to return value by ternary condition in a stream?

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?

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) .

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

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM