Java - 将文件扫描仪与用户输入扫描仪结合起来

标签 java java-8 refactoring java.util.scanner read-eval-print-loop

Edit2:请参阅下面的一些示例代码,这是因为人们确实以原始形式回复了这个问题。

<小时/>

这是我和我的计算机科学教授的综合:

我们有一项作业,要求学生为修改后的 SQL 子集编写基本命令接口(interface)。其实并不重要,但提供了上下文。

要求规定命令可以位于文件中或在命令提示符下输入。扫描仪对此似乎很自然,相当明显。

因此,对于这个问题,我将引用我正在进行的学生项目的此提交: https://github.com/greysondn/fallcorp/tree/fe5f2a317ff3f3206e7dd318cb50f9f67519b02b

两个相关的类是 net.darkglass.Appmanagernet.darkglass.arasql.command.ExecuteCommand .

问题是由 AppManager 中的 ln 51 转发和 ExecuteCommand 中的 ln 56 转发的组合引起的。用于管理用户输入的扫描仪循环和用于管理逐行读取文件的扫描仪循环不兼容;因此,我和我的教授都无法想出一种方法将 Scanner 的两种情况结合到一种方法中。

换句话说,这两种结构归根结底是极其相似的,而且可能应该是一模一样的,但我们不可能发现这不比目前的情况更糟糕。

有没有一种方法可以编写一个扫描程序,使其既可以用于用户输入,也可以用于用户输入的文件输入?

一些快速观察:

  • 我在我的代码中注意到事情开始变得有点臭;也就是说,直觉上感觉事情是错误的。这发生在 ExecuteCommand 中,因为它是要写入的两个命令中的第二个。

  • 此代码大致遵循 Interpreter design pattern 。我的母语更偏向 Python 和/或 C++。一些习语和处事方式无疑会体现这一点。

  • 我的教授很清楚我至少打算提出这个问题,并且和我一样好奇和激动。当他解决该项目以确保其可行以及需要多长时间时,他遇到了同样的绊脚石,无法找到令他满意的解决方案。

  • 编辑:上下文很重要;请在指定的位置弹出这两个文件并检查一下它们。目前最终发生的情况是,这两种扫描仪几乎相同,但一种仅适用于文件,另一种仅适用于用户输入,因为这两种类型的 I/O 在 Java 中的工作方式不同。从表面上看,我突然意识到这个问题听起来可能比实际情况要复杂得多。 (是的,任何使用扫描器的方法都可以解析字符串,无论源如何,但这里的情况更多的是在两个不同的源上使用相同的扫描器,主要是由于它的使用方式)。

<小时/>

Edit2:经过一些评论后,这里是演示核心问题的某种形式的代码。

public void doFile()
{
    // set scanner up against some URI; this is messy but it's a
    // "point of the matter" thing
    Scanner cin = new Scanner(aFile);

    // read over file
    while (cin.hasNextLine())
    {
        // this is actually a lot more complicated, but ultimately we're
        // just doing whatever the next line says
        doWhatItSays(cin.nextLine());
    }
}

public void doREPL()
{
    // set scanner up against user input - this is the actual line
    Scanner cin = new Scanner(System.in);


    Boolean continueRunning = true;

    while(continueRunning)
    {
        // pretty print prompt
        System.out.println("");
        System.out.print("$> ");

        // This, like before, is a lot more complicated, but ultimately
        // we just do whatever it says. (One of the things it may say
        // to do is to set continueRunning to false.)
        doWhatItSays(cin.nextLine());
    }
}

它们都只是简单地扫描输入并按照输入的内容执行操作;将这两种方法合并为一种方法需要什么? (是的,它很快而且很困惑;它至少表达了要点和基本评论。)

最佳答案

看来您对Scanner想得太多,使问题变得过于复杂。 ,这与问题无关。如果您的代码匹配度达到 99%,那么直接的解决方案是将通用代码移至其自己的方法中,并保留两个小的专用方法:

public void doFile() {
    try(Scanner cin = new Scanner(aFile)) {
        // read over file
        while (cin.hasNextLine()) {
            commonLoopBody(cin);
        }
    }
}

public void doREPL() {
    // set scanner up against user input - this is the actual line
    Scanner cin = new Scanner(System.in);
    boolean continueRunning = true;

    while(continueRunning) {
        // pretty print prompt
        System.out.printf("%n$> ");
        commonLoopBody(cin);
    }
}

private void commonLoopBody(Scanner cin) {
    // this is actually a lot more complicated, but ultimately we're
    // just doing whatever the next line says
    doWhatItSays(cin.nextLine());
}

专用方法仍然包含循环语句,但这没有什么问题,因为循环不同。

仍然可以选择将差异移出原始代码,而不是通用代码,例如

public void doFile() {
    try(Scanner cin = new Scanner(aFile)) {
        commonLoop(cin, cin::hasNextLine, ()->{});
    }
}

public void doREPL() {
    boolean continueRunning = true;
    commonLoop(new Scanner(System.in),()->continueRunning,()->System.out.printf("%n$> "));
}

private void commonLoop(Scanner cin, BooleanSupplier runCondition, Runnable beforeCommand){
    while(runCondition.getAsBoolean()) {
        beforeCommand.run();
        // this is actually a lot more complicated, but ultimately we're
        // just doing whatever the next line says
        doWhatItSays(cin.nextLine());
    }
}

在此特定示例中,将循环语句移至公共(public)代码中没有任何优势,但在某些情况下,在框架中进行循环维护会带来优势,Stream API 只是一个例子......

<小时/>

也就是说,看看你的具体代码,似乎存在一个根本性的误解。在 Java 中,String对象是不可变的并且调用 concatString上创建一个新实例。因此,带有初始化器的变量声明如 String test = ""; “为事物预先保留空间”,它通过引用空字符串初始化变量来浪费资源,该空字符串会被后续的 test = cin.nextLine(); 覆盖。反正。另外,test = test.concat(" "); test = test.concat(cin.nextLine());不必要地创建中间字符串实例,其中更简单 test = test + " " + cin.nextLine();使用构建器免费编译为代码。

但最终,如果您不再忽视Scanner的力量,这些操作就会过时。 。此类不仅仅是提供现有 BufferedReader.readLine() 的另一种方式。功能,它是一个模式匹配工具,允许在流输入上使用正则表达式引擎。

如果您想用分号分隔命令,请使用分号作为分隔符,而不是读取必须手动连接的行。替换多行命令的换行符和删除注释也可以通过单个模式替换操作来完成。例如

static String EXAMPLE_INPUT =
   "a single line command;\n"
 + "-- a standalone comment\n"
 + "a multi\n"
 + "line\n"
 + "-- embedded comment\n"
 + "command;\n"
 + "multi -- line\n"
 + "command with double minus;\n"
 + "and just a last command;";

public static void main(String[] args) {
    Scanner s = new Scanner(EXAMPLE_INPUT).useDelimiter(";(\\R|\\Z)");
    while(s.hasNext()) {
        String command = s.next().replaceAll("(?m:^--.*)?+(\\R|\\Z)", " ");
        System.out.println("Command: "+command);
    }
}

将打印

Command: a single line command 
Command:  a multi line  command 
Command: multi -- line command with double minus 
Command: and just a last command 

如果您想保留结果中的分号,您可以更改分隔符
";(\\R|\\Z)""(?<=;)(\\R|\\Z)" .

关于Java - 将文件扫描仪与用户输入扫描仪结合起来,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46123566/

相关文章:

java - Stream.collect(groupingBy(identity(), counting()) 然后按值对结果进行排序

java - Spring引导JPA java.lang.IndexOutOfBoundsException : Index: 1, 大小: 1 at java. util.ArrayList.rangeCheck

c - 尝试一起编译多个 CUDA 文件时出现链接错误 LNK2005

c# - 如何轻松地从类中创建接口(interface)?

java - 生命游戏振荡器和宇宙飞船不工作

java - Jasper 报告空白页

java - 需要调用一个数组并在不同的类中打印它

JavaFx 列表中带有奇怪的边框

ruby-on-rails - rails 和重构,针对 vim 用户的建议工具和技术?

java - 计时器的日期不断重复