【问题标题】:Java 8, how can I implement a switch statement using streams?Java 8,如何使用流实现 switch 语句?
【发布时间】:2016-09-25 13:00:15
【问题描述】:

我有一个文本文件imgui.ini,其中包含:

[Debug]
Pos=7,79
Size=507,392
Collapsed=0

[ImGui Demo]
Pos=320,5
Size=550,680
Collapsed=0

对于每个“元素”,我总是有PosSizeCollapsed,我需要阅读它们。

如果可能,我想使用 java 8 流。

是否可以模拟 switch 语句的行为?

    try (Stream<String> stream = Files.lines(Paths.get(context.io.iniFilename))) {

        ...
/*
    switch(string) {

        case "Pos":
            settings.pos = value;
            break;

        case "Size":
            settings.size = value;
            break;

        case "Collapsed":
            settings.collapsed = value;
            break;
    }
*/

    } catch (IOException e) {
    }
}

【问题讨论】:

  • 您的问题不清楚 - 您要打开什么?听起来您可能想要首先编写一个读取行并返回Map&lt;String, ConfigurationBlock&gt; 的方法(其中ConfigurationBlock 是您自己的类型,或者可能使用Properties)。
  • 首先展示你想要在没有 Stream 的情况下使用普通 switch 语句做什么
  • 我认为当输入的顺序无关紧要时,流是理想的。但是,在您的 ini 示例中,行的顺序至关重要。因此,顶级流的元素不应该是文本行,而是配置部分。每个部分都由一系列不区分顺序的文本行组成。
  • 不幸的是,@MaartenBodewes 看起来是这样。我有点希望有一种优雅的方法

标签: java java-8 switch-statement java-stream


【解决方案1】:

专注于“有没有办法模拟 switch 语句行为”这个问题,我认为答案是你可以,只需付出一点努力。几年前我问过自己,并做了以下练习(然后再也没有使用过):

private static <T> Predicate<T> testAndConsume(Predicate<T> pred, Consumer<T> cons) {
    return t -> {
        boolean result = pred.test(t);
        if (result) cons.accept(t);
        return result;
    };
}

public static class SwitchConsumer<T> {
    Predicate<T> conditionalConsumer;
    private SwitchConsumer(Predicate<T> pred) {
        conditionalConsumer = pred;
    }

    public static <C> SwitchConsumer<C> inCase(Predicate<C> pred, Consumer<C> cons) {
        return new SwitchConsumer<>(testAndConsume(pred, cons));
    }

    public SwitchConsumer<T> elseIf(Predicate<T> pred, Consumer<T> cons) {
        return new SwitchConsumer<>(conditionalConsumer.or(testAndConsume(pred,cons)));
    }

    public Consumer<T> elseDefault(Consumer<T> cons) {
        return testAndConsume(conditionalConsumer.negate(),cons)::test;   // ::test converts Predicate to Consumer
    }
}

testAndConsume 组合了一个Predicate 和一个Consumer,创建了一个返回相同值的Predicate,但如果该值为真,则调用Consumer 作为副作用。这成为“开关”中每个“案例”的基础。每个“案例”都由Predicate.or() 串在一起,这提供了开关的短路“else-if”特性。最后,通过将::test添加到Predicate,将组合的Predicate变成Consumer

将它应用到你的代码 sn-p,它看起来像这样:

    Stream.of("Pos=320,5", "Size=550,680", "Collapsed=0")
            .map(s -> s.split("="))
            .forEach(SwitchConsumer.<String[]>
                    inCase(arr -> "Pos".equals(arr[0]), arr -> settings.pos = arr[1])
                    .elseIf(arr -> "Size".equals(arr[0]), arr -> settings.size = arr[1])
                    .elseIf(arr -> "Collapsed".equals(arr[0]), arr -> settings.collapsed = arr[1])
                    .elseDefault(arr -> {}));

这与在 Consumer 正文中没有实际开关的情况下一样多。

【讨论】:

    【解决方案2】:

    另一种读取配置文件的方法:

    public class Main {
        public static void main(String[] args) throws IOException {
            Path path = Paths.get("D:\\Development\\workspace\\Application\\src\\main\\resources\\init.txt");
            String content = new String(Files.readAllBytes(path));
    
            Map<String, Config> configMap = Stream.of(content.split("\\n\\r"))
                .map(config -> Arrays.asList(config.split("\\r")))
                .collect(HashMap<String, Config>::new, (map, list) -> {
                    String header = list.get(0);
                    String pos = list.get(1);
                    String size = list.get(2);
                    String collapsed = list.get(3);
                    map.put(header, new Config(pos.substring(pos.indexOf("=") + 1), size.substring(size.indexOf("=") + 1), collapsed.substring(collapsed.indexOf("=") + 1)));
                }, (m, u) -> {});
    
            System.out.println(configMap);
        }
    }
    
    class Config {
        public String pos;
        public String size;
        public String collapsed;
    
        public Config(String pos, String size, String collapsed) {
            this.pos = pos;
            this.size = size;
            this.collapsed = collapsed;
        }
    
        @Override
        public String toString() {
            return "Config{" +  "pos='" + pos + '\'' + ", size='" + size + '\'' + 
                   ", collapsed='" + collapsed + '\'' + '}';
        }
    }
    

    结果将是地图:

    {
        [Debug]=Config{pos='7,79', size='507,392', collapsed='0'}, 
        [ImGui Demo]=Config{pos='320,5', size='550,680', collapsed='0'}
    }
    

    【讨论】:

    • 为什么要硬编码行尾?只是为了确保它不能在 Mac 或 Linux 上运行?为什么在路径中使用反斜杠而不是正斜杠?只是为了让你逃脱他们?
    【解决方案3】:

    解析此类文件的最佳方法(不使用专用的第 3 方库)是通过正则表达式 API 及其前端类 Scanner。不幸的是,目前缺少通过 Stream API 实现它的最佳操作。即,Matcher.results()Scanner.findAll(…) 还不存在。因此,除非我们想等到 Java 9,否则我们必须为 Java 8 兼容的解决方案创建类似的方法:

    public static Stream<MatchResult> findAll(Scanner s, Pattern pattern) {
        return StreamSupport.stream(new Spliterators.AbstractSpliterator<MatchResult>(
                1000, Spliterator.ORDERED|Spliterator.NONNULL) {
            public boolean tryAdvance(Consumer<? super MatchResult> action) {
                if(s.findWithinHorizon(pattern, 0)!=null) {
                    action.accept(s.match());
                    return true;
                }
                else return false;
            }
        }, false);
    }
    public static Stream<MatchResult> results(Matcher m) {
        return StreamSupport.stream(new Spliterators.AbstractSpliterator<MatchResult>(
                m.regionEnd()-m.regionStart(), Spliterator.ORDERED|Spliterator.NONNULL) {
            public boolean tryAdvance(Consumer<? super MatchResult> action) {
                if(m.find()) {
                    action.accept(m.toMatchResult());
                    return true;
                }
                else return false;
            }
        }, false);
    }
    

    一旦 Java 9 发布并变得司空见惯,使用具有相似语义的方法允许我们用标准 API 方法替换它们的用法。

    使用这两个操作,您可以使用解析文件

    Pattern groupPattern=Pattern.compile("\\[(.*?)\\]([^\\[]*)");
    Pattern attrPattern=Pattern.compile("(.*?)=(.*)\\v");
    Map<String, Map<String, String>> m;
    try(Scanner s=new Scanner(Paths.get(context.io.iniFilename))) {
        m = findAll(s, groupPattern).collect(Collectors.toMap(
            gm -> gm.group(1),
            gm -> results(attrPattern.matcher(gm.group(2)))
                .collect(Collectors.toMap(am->am.group(1), am->am.group(2)))));
    }
    

    生成的映射 m 包含所有信息,从组名映射到另一个包含键/值对的映射,即您可以使用以下命令打印等效的 .ini 文件:

    m.forEach((group,attr)-> {
        System.out.println("["+group+"]");
        attr.forEach((key,value)->System.out.println(key+"="+value));
    });
    

    【讨论】:

    • 哦,是的,这比问题中的 switch 语句更具可读性和可维护性。
    【解决方案4】:

    尝试:

    try {
            Path file = Paths.get("G:\\tmp", "img.ini");
            Stream<String> lines = Files.lines(file);
    
            lines.filter(line->{
                if("pos".equalsIgnoreCase(line.split("=")[0])){
                    //process pos line here
                    System.out.println("pos"+line);
                    return false;
                }
                return true;
            }).filter(line->{
                System.out.println("2"+line);
                if("Collapsed".equalsIgnoreCase(line.split("=")[0])){
                    //process Collapsed line here
                    System.out.println("Collapsed"+line);
                    return false;
                }
                return true;
            }).filter(line->{
                System.out.println("3"+line);
                if("Size".equalsIgnoreCase(line.split("=")[0])){
                    //process Size line here
                    System.out.println("Size"+line);
                    return false;
                }
                return true;
            }).forEach(line->{
                //settings = new Settings();
            });;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-01
      • 2021-06-12
      • 2012-09-14
      • 1970-01-01
      • 1970-01-01
      • 2019-10-08
      • 2019-04-07
      • 2013-09-03
      相关资源
      最近更新 更多