[英]Java 8, how can I implement a switch statement using streams?
我有一個文本文件imgui.ini
包含:
[Debug]
Pos=7,79
Size=507,392
Collapsed=0
[ImGui Demo]
Pos=320,5
Size=550,680
Collapsed=0
對於每個“元素”,我總是有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) {
}
}
解析這樣一個文件(不使用專用的第三方庫)的最佳方法是通過正則表達式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));
});
嘗試:
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();
}
關注“有沒有辦法模擬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
作為副作用。 這成為“開關”中每個“案例”的基礎。 每個“case”由Predicate.or()
串聯在一起,它提供了交換機的“else-if”短路的短路。 最后,由Predicate
變成一個Consumer
加入::test
的Predicate
。
將它應用於您的代碼段,它看起來像這樣:
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
體內的實際開關就可以獲得。
另一種讀取配置文件的方法:
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'}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.