Java 8,如何使用流实现switch语句?

ele*_*ect 10 java switch-statement java-8 java-stream

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

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

[ImGui Demo]
Pos=320,5
Size=550,680
Collapsed=0
Run Code Online (Sandbox Code Playgroud)

对于每一个"要素"我总是有Pos,Size而且Collapsed,我需要阅读.

我想尽可能使用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) {
    }
}
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 11

解析此类文件(不使用专用的第三方库)的最佳方法是通过正则表达式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);
}
Run Code Online (Sandbox Code Playgroud)

一旦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)))));
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 哦,是的,比问题中的switch语句更具可读性和可维护性。 (2认同)

Han*_*k D 6

着眼于“是否有一种模拟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
    }
}
Run Code Online (Sandbox Code Playgroud)

testAndConsume组成一个Predicate和一个Consumer,创建一个Predicate返回相同值的Consumera,如果该值为true,则将其称为副作用。这成为“开关”中每个“案例”的基础。每个“外壳”由串在一起Predicate.or(),提供了开关的短路“否则-如果”性质。最后,由Predicate变成一个Consumer加入::testPredicate

将其应用于您的代码段,如下所示:

    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 -> {}));
Run Code Online (Sandbox Code Playgroud)

如果没有Consumer身体上的实际开关,那将是尽可能接近开关的。


Vas*_*sco 5

尝试:

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();
    }
Run Code Online (Sandbox Code Playgroud)