如何在流(Stream)中使用三目运算符返回值?

5
我希望根据条件返回流的值。以下仅作为示例,我想将任何苹果映射到 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);
    }
}

如何将三元条件包含在流中?


3
你可以使用一个带有条件的 map - Alexis C.
@AlexisC。好的,谢谢,你可能是指 .map(type -> ArrayUtils.contains(APPLES, type) ? APPLE : type)?如果是这样,并且这是最佳解决方案,你可以将其添加为答案。 - membersound
1
那么像这样的代码private static final HashSet<String> APPLES = new HashSet<>(Arrays.asList("apple", "apple2", "apple3"));public static Food fromValue(String value) { return APPLES.contains(value.toLowerCase()) ? APPLE : null; }怎么样?时间复杂度是常数级别,而且需要编写的代码更少。 - Alexis C.
1
啊,这并不完全相同。但是你的问题可以简化为这个:https://dev59.com/F14c5IYBdhLWcg3wia1P#27807324。我的建议是在那里使用一个映射表。 - Alexis C.
5个回答

3
你可以像这样做:
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());
    }
}

在这个映射中查找的时间复杂度为O(1)


2
你不需要使用 getOrDefault 来获取空值。你可以直接调用 get() 方法,因为普通的 Map.get 方法的契约是如果没有映射,则返回 null。顺便说一下,对于 ASCII 字符来说,使用小写可能足够了,但这并不是大小写不敏感比较的通用解决方案。 - Holger

2
正如Alexis建议的那样,您可以使用map操作。
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);
}

2

三元运算符并没有什么特别的。因此,您可以将此映射操作简单地添加到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);
}

然而,这两种线性搜索并不是必需的。可以使用 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);
    }
}

它将执行不区分大小写的查找,因此它被初始化为在第一位返回APPLE替代品,因此无需进行额外的比较。


2
根据之前的答案,特别是@Alexis的回答,我写了一些代码来检查两种方法(Java 7和Java 8)的效果。也许这对于新的Java 8用户很有用。
所以,我对原始答案做了一些修改。首先,我添加了一些单元测试,并添加了两个包装方法verifyNames()和contains()。其次,在意外情况下,我们可以使用默认行为,即在这种情况下,当调用appleApproachTwo.fromValueJava8()时,如果传入null或不存在的枚举值,则这两种情况都会处理。
最后,最后一个变化使用了java.util.Optional对象的潜在用途。在这种情况下,我们可以保护环境免于由于空对象的不一致而崩溃。有更多关于默认值、可选和orElse()方法的讨论,请访问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);
    }
}

0

正如其他人建议的那样,使用 Map 会更好:

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

我还会将fromValue()方法设为static,并将APPLES设置为EnumSet。虽然我意识到这个答案与@Holger的非常相似,但我只是想展示另一种构建映射的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接