Java - 将Scanner for file与Scanner结合用于用户输入

时间:2017-09-08 19:42:09

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

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

这是我自己和我的CS教授的结合:

我们有一项任务,要求学生为修改的SQL子集编写基本命令界面。并不重要,但提供了背景。

要求声明命令可以在文件中或在命令提示符下输入。扫描仪看起来很自然,相当明显。

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

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

问题出现在AppManager中的ln 51前锋和ExecuteCommand中的ln 56前锋的组合中。扫描程序用于管理用户输入和循环的循环,以便扫描程序逐行管理读取文件不兼容;因此,我和我的教授都无法理解将扫描仪的两种情况合并为一种方法的方法。

换句话说,这两个结构在一天结束时非常相似,应该是同一个,但我们无法找到并不比当前状况更糟糕。< / p>

有没有办法编写扫描仪,以便它既可以用于用户输入,也可以用于用户输入的文件输入?

一些快速观察:

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

  • 此代码松散地跟随Interpreter design pattern。我的本地人更喜欢Pythonic和/或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());
    }
}

他们都只是扫描一个输入并按照它说的做;将这两种方法合并为一种方法需要什么? (是的,它快速而混乱;它至少得到了重点和基本评论。)

3 个答案:

答案 0 :(得分:1)

我认为this will solve your problem。这基本上是来自普林斯顿的 Algorithms,第4版由Robert Sedgewick和Kevin Wayne提出的StdIn课程。我在他们的Coursera MOOC 算法第一部分中使用了这个类来从文件或cmd中读取输入。

修改
结合上面提到的类with In class from the same repository

编辑2
In类中,您有两种方法:

/**
     * Initializes an input stream from a file.
     *
     * @param  file the file
     * @throws IllegalArgumentException if cannot open {@code file}
     * @throws IllegalArgumentException if {@code file} is {@code null}
     */
    public In(File file) {
        if (file == null) throw new IllegalArgumentException("file argument is null");
        try {
            // for consistency with StdIn, wrap with BufferedInputStream instead of use
            // file as argument to Scanner
            FileInputStream fis = new FileInputStream(file);
            scanner = new Scanner(new BufferedInputStream(fis), CHARSET_NAME);
            scanner.useLocale(LOCALE);
        }
        catch (IOException ioe) {
            throw new IllegalArgumentException("Could not open " + file, ioe);
        }
    }

/**
     * Initializes an input stream from a given {@link Scanner} source; use with 
     * {@code new Scanner(String)} to read from a string.
     * <p>
     * Note that this does not create a defensive copy, so the
     * scanner will be mutated as you read on. 
     *
     * @param  scanner the scanner
     * @throws IllegalArgumentException if {@code scanner} is {@code null}
     */
    public In(Scanner scanner) {
        if (scanner == null) throw new IllegalArgumentException("scanner argument is null");
        this.scanner = scanner;
    }

你根据用户输入调用正确的一个(比如你检查第一个参数是否是一个文件,这很容易)然后你有很多其他方法,如hasNextLine或{{1} }。这一切都属于同一类。无论您使用哪种构造函数,都可以调用相同的方法。

答案 1 :(得分:1)

我会从主代码中分离输入,因此您有一个接口Source,您可以轮询命令,然后有两个具体的实现。

  • FileSource只是从文件中读取
  • InteractiveSource向用户显示提示并返回他们返回的文本

您可以将其中一个传递给您的类,无论从哪里获取命令,它都可以使用它。

这样做的好处是,如果您想添加NetworkSourceUISource,则只需编写新的Source实现。

这种方式还允许您干净地关闭系统,因为每个Source都可以有一个方法keepGoing(),您可以在命令之间调用以查看系统是否应该关闭,对于文件,当没有时更多行,输入后可以输入exit

答案 2 :(得分:1)

通过过多地考虑与问题无关的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());
    }
}

在此特定示例中将循环语句移动到公共代码中没有任何优势,但是在某些情况下,在框架中进行循环维护可以提供优势,Stream API只是一个示例......

那说,看看你的具体代码,似乎存在一个根本的误解。在Java中,String对象是不可变的,并且在concat上调用String会创建一个新实例。因此,使用像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)"