Java - 将Scanner for file与Scanner结合使用以进行用户输入 [英] Java - combine Scanner for file with Scanner for user input

查看:165
本文介绍了Java - 将Scanner for file与Scanner结合使用以进行用户输入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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






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



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



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



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



'p>在两个相关的类是 net.darkglass.Appmanager net.darkglass.arasql.command.ExecuteCommand



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



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



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



一些快速观察:




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


  • 此代码松散地遵循翻译设计模式。我的本地人更像是Pythonic和/或C ++。一些成语和处理事物的方式无疑会反映出这一点。


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


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







编辑2:经过一些评论后,这里有一些代码来演示核心问题。

  public void doFile()
{
//针对某些URI设置扫描程序;这是一个混乱,但它是一个
//问题点的事情
Scanner cin = new Scanner(aFile);

//读取文件
而(cin.hasNextLine())
{
//这实际上要复杂得多,但最终我们是
//正在做下一行所说的
doWhatItSays(cin.nextLine());
}
}

public void doREPL()
{
//根据用户输入设置扫描器 - 这是实际行
Scanner cin = new Scanner(System.in);


布尔值continueRunning = true;

while(continueRunning)
{
//漂亮的打印提示
System.out.println();
System.out.print($>);

//这和以前一样复杂得多,但最终
//我们只是按照它说的做。 (它可能会说
//要做的事情之一就是将continueRunning设置为false。)
doWhatItSays(cin.nextLine());
}
}

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

解决方案

看起来你好像是通过过多地考虑 Scanner 来解决问题,这与问题无关。如果您的代码匹配到99%,那么直接的解决方案就是将公共代码单独移动到一个方法中,并且剩下两个小的专用方法:

  public void doFile(){
try(Scanner cin = new Scanner(aFile)){
//读取文件
while(cin.hasNextLine( )){
commonLoopBody(cin);
}
}
}

public void doREPL(){
//根据用户输入设置扫描程序 - 这是实际行
扫描仪cin =新扫描仪(System.in);
boolean continueRunning = true;

while(continueRunning){
//漂亮的打印提示
System.out.printf(%n $>);
commonLoopBody(cin);
}
}

private void commonLoopBody(Scanner cin){
//这实际上要复杂得多,但最终我们只需
/ /只是做下一行所说的
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 $>));
}

私人无效commonLoop(扫描仪CIN,BooleanSupplier runCondition,可运行beforeCommand){
而(runCondition.getAsBoolean()){
beforeCommand.run();
//这实际上要复杂得多,但最终我们只需要
//做下一行所说的
doWhatItSays(cin.nextLine());
}
}

将循环语句移入这个具体例子中的公共代码,但有些情况下,框架中的循环维护提供了优势, Stream API只是一个例子......






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



但最后,如果你不再忽略<$ c的功能,这些操作已经过时了$ C>扫描仪。这个类不仅是提供现有 BufferedReader.readLine()功能的另一种方式,它是一种模式匹配工具,允许在流输入上使用正则表达式引擎。



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

  static String EXAMPLE_INPUT = 
单行命令; \ n
+ - 独立评论\ n
+a multi\\\

+line \ n
+ - 嵌入式评论\ n
+command; \ n
+multi - line\\\

+带双减号的命令; \ n
+只是最后一个命令; ;

公共静态无效的主要(字串[] args){
扫描器S =新扫描仪(EXAMPLE_INPUT).useDelimiter( ;(\\R | \\Z) );
while(s.hasNext()){
String command = s.next()。replaceAll((?m:^ - 。*)?+(\\\\ | \ \ Z),);
System.out.println(命令:+命令);
}
}

将打印

 命令:单行命令
命令:多行命令
命令:多行命令double减
命令:只是最后一个命令

如果你想保留分号结果,您可以将分隔符从

;(\\\\ | | \\Z)更改为(?< =;)(\\\\ | | \\Z)


Edit2: See below for some example code, this is left because people did reply to this question in its original form.


Here's one from myself and my CS professor combined:

We have an assignment where students are being asked to write a basic command interface for a modified subset of SQL. Doesn't really matter, but provides context.

The requirements state that commands can be in a file or entered at a command prompt. Scanner seems natural for this, fairly obviously.

So for this question, I'll be referencing this commit of my ongoing student project: https://github.com/greysondn/fallcorp/tree/fe5f2a317ff3f3206e7dd318cb50f9f67519b02b

The two relevant classes are net.darkglass.Appmanager and net.darkglass.arasql.command.ExecuteCommand.

The issue arises around a combination of ln 51 forwards in AppManager and ln 56 forwards in ExecuteCommand. Loops for Scanner to manage user input and loops for Scanner to manage reading a file line by line aren't compatible; as a consequence, neither I nor my professor can fathom a way to combine the two cases of Scanner into one method.

In other words, these two structures are extremely similar at the end of the day, and should probably be one and the same, but there is no way we could find that wasn't worse than the current state of affairs.

Is there a way to write a Scanner such that it works both for user input and for file input of what a user would have input?

Some quick observations:

  • I have noted in my code where things start to reek a bit; that is to say, where things feel intuitively wrong. This happens in ExecuteCommand as it was the second of the two to be written.

  • This code loosely follows the Interpreter design pattern. My native is more Pythonic and/or C++. Some idioms and ways of handling things will undoubtedly reflect this.

  • My professor is well aware that I at least intended to post this question and is just as curious and agitated as I am. When he solved the project to make sure it was doable and how long it would take, he hit the same stumbling block and couldn't find a solution that he was satisfied with.

  • EDIT: Context is important; please do pop open those two files at the indicated points and examine them a bit. What ultimately happens as it stands right now is that the two scanners are almost the same, but one only works for files and one only works for user input due to the way the two types of I/O work in Java. On it's face, I suddenly realize the question probably sounds a lot denser than it actually is. (Yes, any method using a Scanner can parse a string regardless of source, but the case here is more about using the same Scanner on two different sources due - mostly - to how it is used).


Edit2: After some commentary, here is some form of code that demonstrates the core problem.

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());
    }
}

They both simply scan over an input and do what it says; what would it take to consolidate these two methods into one? (Yes, it's quick and messy; it's at least got the point across and basic commentary.)

解决方案

It seems like you are over-complicating the problem by thinking too much about Scanner, which is irrelevant to the problem. If you have code that matches to 99%, the straight-forward solution is to move the common code into a method on its own and have two small specialized methods remaining:

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());
}

The specialized methods still contain the loop statement, but there’s nothing wrong with that, as the loops are different.

Still, there is the alternative of moving the difference out of the original code, rather than the common code, e.g.

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());
    }
}

There is no advantage of moving the loop statement into the common code in this specific example, but there are scenarios where having the loop maintenance in a framework provides advantages, the Stream API is just one example…


That said, looking at your specific code, there seems to be a fundamental misconception. In Java, String object are immutable and calling concat on a String creates a new instance. So a variable declaration with an initializer like String test = ""; does not "pre-reserve a space for the thing", it wastes resources by initializing the variable with a reference to an empty string, which gets overwritten by the subsequent test = cin.nextLine(); anyway. Also, test = test.concat(" "); test = test.concat(cin.nextLine()); needlessly creates intermediate string instances where the much simpler test = test + " " + cin.nextLine(); compiles to code using a builder—for free.

But in the end, these operations are obsolete if you stop ignoring the power of the Scanner. This class is not just another way of delivering the already existing BufferedReader.readLine() functionality, it’s a pattern matching tool allowing to use the regex engine on a stream input.

If you want to separate commands by a semicolon, use a semicolon as delimiter instead of reading lines that have to be concatenated manually. Replacing newlines of multi-line commands and removing comments can be done by a single pattern replace operation too. For example

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);
    }
}

will print

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

If you want to preserve the semicolons in the result, you can change the delimiter from
";(\\R|\\Z)" to "(?<=;)(\\R|\\Z)".

这篇关于Java - 将Scanner for file与Scanner结合使用以进行用户输入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆