Java 8,如何使用流实现 switch 语句?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/37479217/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java 8, how can I implement a switch statement using streams?
提问by elect
I have a text file imgui.ini
containing:
我有一个imgui.ini
包含以下内容的文本文件:
[Debug]
Pos=7,79
Size=507,392
Collapsed=0
[ImGui Demo]
Pos=320,5
Size=550,680
Collapsed=0
For each "element" I always have Pos
, Size
and Collapsed
and I need to read them.
对于每一个“要素”我总是有Pos
,Size
而且Collapsed
,我需要阅读。
I would like to use, if possible, java 8 streams.
如果可能,我想使用 java 8 流。
Is it possible to simulate a switch statement behaviour?
是否可以模拟 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) {
}
}
采纳答案by Holger
The best way to parse such a file (without using dedicated 3rd party libraries), is via the regex API, and its front-end class Scanner
. Unfortunately, the best operations to implement it via Stream API, are currently missing. Namely, Matcher.results()
and Scanner.findAll(…)
are not there yet. So unless we want to wait until Java?9, we have to create similar methods for a Java?8 compatible solution:
解析此类文件的最佳方法(不使用专用的第 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);
}
Using methods with a similar semantic allows us to replace their usage with the standard API methods, once Java?9 is released and becomes commonplace.
一旦 Java?9 发布并变得司空见惯,使用具有相似语义的方法允许我们用标准 API 方法替换它们的用法。
Using these two operations, you can parse your file using
使用这两个操作,您可以使用
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)))));
}
the resulting map m
holds all information, mapping from the group names to another map holding the key/value pairs, i.e. you can print an equivalent .ini
file using:
生成的映射m
包含所有信息,从组名映射到另一个包含键/值对的映射,即您可以.ini
使用以下方法打印等效文件:
m.forEach((group,attr)-> {
System.out.println("["+group+"]");
attr.forEach((key,value)->System.out.println(key+"="+value));
});
回答by Hank D
Focusing on the question "is there a way to simulate switch statement behavior", I think the answer is that you could, with a little effort. I asked myself that a couple of years ago, and did the following as an exercise (and then never used it again):
关注“有没有办法模拟 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
composes a Predicate
and a Consumer
, creating a Predicate
that returns the same value but calls the Consumer
as a side-effect if the value is true. That becomes the basis for each "case" in the "switch". Each "case" is strung together by Predicate.or()
, which provides the short-circuiting "else-if" nature of the switch. Finally, the composed Predicate
is turned into a Consumer
by adding ::test
to the Predicate
.
testAndConsume
组合 aPredicate
和 a Consumer
,创建一个Predicate
返回相同值的a ,但Consumer
如果值为 true 则调用 the作为副作用。这成为“开关”中每个“案例”的基础。每个“案例”都由 串在一起Predicate.or()
,它提供了开关的短路“else-if”特性。最后,由Predicate
变成一个Consumer
加入::test
的Predicate
。
Applying it to your code snippet, it looks like this:
将其应用于您的代码片段,它看起来像这样:
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 -> {}));
That's about as switch-ish as it can get without an actual switch in a Consumer
body.
这与在Consumer
身体中没有实际开关的情况下可以达到的开关一样。
回答by Vasco
Attempt:
试图:
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();
}
回答by Alexander Ivanov
Another way to read your config file:
另一种读取配置文件的方法:
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 + '\'' + '}';
}
}
Result will be map:
结果将是地图:
{
[Debug]=Config{pos='7,79', size='507,392', collapsed='0'},
[ImGui Demo]=Config{pos='320,5', size='550,680', collapsed='0'}
}