Java:从字符串中删除注释

6

我想要做一个函数,它可以获取一个字符串,并在其中发现内联注释时将其删除。我知道这听起来很简单,但我想确保我做得对,例如:

private String filterString(String code) {
  // lets say code = "some code //comment inside"

  // return the string "some code" (without the comment)
}

我想到了两种方法:请随意提出其他建议

  1. 遍历字符串,查找双重内联括号并使用子字符串方法。
  2. 正则表达式的方式...(我不太确定)

您能告诉我哪种方法最好,并向我展示如何完成吗?(请不要提供过于高级的解决方案)

编辑:是否可以通过Scanner对象以某种方式完成此操作?(无论如何,我都在使用此对象)

9个回答

10

如果您想要一个更高效的正则表达式来匹配所有类型的注释,请使用以下内容:

replaceAll("(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)","");

来源: http://ostermiller.org/findcomment.html

编辑:

如果您不确定是否使用正则表达式,另一种解决方案是设计一个如下的小自动机:

public static String removeComments(String code){
    final int outsideComment=0;
    final int insideLineComment=1;
    final int insideblockComment=2;
    final int insideblockComment_noNewLineYet=3; // we want to have at least one new line in the result if the block is not inline.
    
    int currentState=outsideComment;
    String endResult="";
    Scanner s= new Scanner(code);
    s.useDelimiter("");
    while(s.hasNext()){
        String c=s.next();
        switch(currentState){
            case outsideComment: 
                if(c.equals("/") && s.hasNext()){
                    String c2=s.next();
                    if(c2.equals("/"))
                        currentState=insideLineComment;
                    else if(c2.equals("*")){
                        currentState=insideblockComment_noNewLineYet;
                    }
                    else 
                        endResult+=c+c2;
                }
                else
                    endResult+=c;
                break;
            case insideLineComment:
                if(c.equals("\n")){
                    currentState=outsideComment;
                    endResult+="\n";
                }
            break;
            case insideblockComment_noNewLineYet:
                if(c.equals("\n")){
                    endResult+="\n";
                    currentState=insideblockComment;
                }
            case insideblockComment:
                while(c.equals("*") && s.hasNext()){
                    String c2=s.next();
                    if(c2.equals("/")){
                        currentState=outsideComment;
                        break;
                    }
                    
                }
                
        }
    }
    s.close();
    return endResult;   
}

2
正则表达式解决方案和您提供的解决方案将破坏包含字符串字面量内部的注释起始字符序列的源代码。 - Christian Hujer
没错,谢谢你注意到了。当我遇到这个问题并发布这个答案时,那些情况对我来说并不重要,所以我没有太关注它们。不过,针对字符串声明保留注释的解决方案的改编应该不难实现,特别是对于第二种解决方案。 - Loïc Gammaitoni

5

最好的方法是使用正则表达式。 首先找到/**/注释,然后删除所有//注释。例如:

private String filterString(String code) {
  String partialFiltered = code.replaceAll("/\\*.*\\*/", "");
  String fullFiltered = partialFiltered.replaceAll("//.*(?=\\n)", "")
}

1
这会破坏包含注释起始字符序列的字符串字面值的源代码。 - Christian Hujer

3

只需使用String类中的replaceAll方法,结合简单的正则表达式即可。下面是具体操作:

import java.util.*;
import java.lang.*;

class Main
{
        public static void main (String[] args) throws java.lang.Exception
        {
                String s = "private String filterString(String code) {\n" +
"  // lets say code = \"some code //comment inside\"\n" +
"  // return the string \"some code\" (without the comment)\n}";

                s = s.replaceAll("//.*?\n","\n");
                System.out.println("s=" + s);

        }
}

关键是这一行代码:
s = s.replaceAll("//.*?\n","\n");

正则表达式//.*?\n匹配以//开始直到行尾的字符串。
如果您想看到此代码的效果,请访问这里:http://www.ideone.com/e26Ve 希望对您有所帮助!

你能解释一下这个正则表达式吗?我只需要删除“//some text”,但它似乎影响了更多的字符,比如“\n”...应该使用什么精确的正则表达式呢? - Popokoko
这行代码应该是 s = s.replaceAll("//.*?\n","\n"); 我会编辑帖子并更正它。你“选择”的解决方案在多行字符串上无法正常工作,就像你给出的示例一样。 - user361676
1
正则表达式解决方案和您提供的解决方案将破坏包含字符串字面量内部的注释起始字符序列的源代码。 - Christian Hujer

3

@Christian Hujer一直正确指出,如果注释位于字符串内,则发布的许多或所有解决方案都会失败。

@Loïc Gammaitoni建议他的自动机方法可以很容易地扩展到处理这种情况。以下是该扩展。

enum State { outsideComment, insideLineComment, insideblockComment, insideblockComment_noNewLineYet, insideString };

public static String removeComments(String code) {
  State state = State.outsideComment;
  StringBuilder result = new StringBuilder();
  Scanner s = new Scanner(code);
  s.useDelimiter("");
  while (s.hasNext()) {
    String c = s.next();
    switch (state) {
      case outsideComment:
        if (c.equals("/") && s.hasNext()) {
          String c2 = s.next();
          if (c2.equals("/"))
            state = State.insideLineComment;
          else if (c2.equals("*")) {
            state = State.insideblockComment_noNewLineYet;
          } else {
            result.append(c).append(c2);
          }
        } else {
          result.append(c);
          if (c.equals("\"")) {
            state = State.insideString;
          }
        }
        break;
      case insideString:
        result.append(c);
        if (c.equals("\"")) {
          state = State.outsideComment;
        } else if (c.equals("\\") && s.hasNext()) {
          result.append(s.next());
        }
        break;
      case insideLineComment:
        if (c.equals("\n")) {
          state = State.outsideComment;
          result.append("\n");
        }
        break;
      case insideblockComment_noNewLineYet:
        if (c.equals("\n")) {
          result.append("\n");
          state = State.insideblockComment;
        }
      case insideblockComment:
        while (c.equals("*") && s.hasNext()) {
          String c2 = s.next();
          if (c2.equals("/")) {
            state = State.outsideComment;
            break;
          }
        }
    }
  }
  s.close();
  return result.toString();
}

2

使用正则表达式替换来查找常量子字符串之前的子字符串有点复杂。

你可以使用indexOf()来检查注释开始的位置,然后使用substring()获取第一部分,类似于:

String code = "some code // comment";
int    offset = code.indexOf("//");

if (-1 != offset) {
    code = code.substring(0, offset);
}

这对于你自己的代码不起作用,它将删除字符串中的“//注释”。 - Desmond Zhou
我不需要处理 /** comments :) 我已经检查过这个解决方案,它工作正常! - Popokoko
4
过于简略了——会弄乱类似这样的代码:String url="http://www.google.com"; - jwl
我正在寻找一种方法来删除字符串中的所有注释行。对于 /* */ 和 // 样式的注释,请查看此答案,它帮助了我:https://dev59.com/GHE85IYBdhLWcg3wyGp_#2613945 - Utku Özdemir
这将会破坏包含字符串字面量中注释起始字符序列的源代码。 - Christian Hujer

1
我制作了一个开源的库(在GitHub上),它叫做CommentRemover,你可以用它来删除单行和多行Java注释。
它支持删除或不删除TODO。
同时,它也支持JavaScript、HTML、CSS、Properties、JSP和XML注释。
下面是如何使用它的一小段代码片段(有两种用法):
第一种方式是InternalPath:
 public static void main(String[] args) throws CommentRemoverException {

 // root dir is: /Users/user/Projects/MyProject
 // example for startInternalPath

 CommentRemover commentRemover = new CommentRemover.CommentRemoverBuilder()
        .removeJava(true) // Remove Java file Comments....
        .removeJavaScript(true) // Remove JavaScript file Comments....
        .removeJSP(true) // etc.. goes like that
        .removeTodos(false) //  Do Not Touch Todos (leave them alone)
        .removeSingleLines(true) // Remove single line type comments
        .removeMultiLines(true) // Remove multiple type comments
        .startInternalPath("src.main.app") // Starts from {rootDir}/src/main/app , leave it empty string when you want to start from root dir
        .setExcludePackages(new String[]{"src.main.java.app.pattern"}) // Refers to {rootDir}/src/main/java/app/pattern and skips this directory
        .build();

 CommentProcessor commentProcessor = new CommentProcessor(commentRemover);
                  commentProcessor.start();        
  }

第二种方式 ExternalPath
 public static void main(String[] args) throws CommentRemoverException {

 // example for externalPath

 CommentRemover commentRemover = new CommentRemover.CommentRemoverBuilder()
        .removeJava(true) // Remove Java file Comments....
        .removeJavaScript(true) // Remove JavaScript file Comments....
        .removeJSP(true) // etc..
        .removeTodos(true) // Remove todos
        .removeSingleLines(false) // Do not remove single line type comments
        .removeMultiLines(true) // Remove multiple type comments
        .startExternalPath("/Users/user/Projects/MyOtherProject")// Give it full path for external directories
        .setExcludePackages(new String[]{"src.main.java.model"}) // Refers to /Users/user/Projects/MyOtherProject/src/main/java/model and skips this directory.
        .build();

 CommentProcessor commentProcessor = new CommentProcessor(commentRemover);
                  commentProcessor.start();        
  }

我该如何获取结果?它既没有返回,也没有写回源文件... - BullyWiiPlaza
@BullyWiiPlaza 如果你想获取一个已经删掉评论的类列表,那么没有这样的功能。但是,如果出现问题,库会显示无法删除的类的列表。 - Ertuğrul Çetin
这个非常好用。如果你只是想运行一个外部路径,甚至不需要添加 'setExcludePackages' setter。我克隆了它,并成功地在删除 'setExcludePackages' setter 后运行了外部路径示例,没有任何问题。 - Ryan Zakariudakis

0

简单的解决方案,不会删除额外的代码部分(如上面那些) // 对于任何读者都适用,您还可以迭代字符串列表

        String str="";
        String s;
        while ((s = reader.readLine()) != null)
        {
            s=s.replaceAll("//.*","\n");
            str+=s;
        }
        str=str.replaceAll("/\\*.*\\*/"," ");

0

对于扫描仪,请使用分隔符,

分隔符示例。

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;

public class MainClass {
  public static void main(String args[]) throws IOException {
FileWriter fout = new FileWriter("test.txt");
fout.write("2, 3.4,    5,6, 7.4, 9.1, 10.5, done");
fout.close();

FileReader fin = new FileReader("Test.txt");
Scanner src = new Scanner(fin);
// Set delimiters to space and comma.
// ", *" tells Scanner to match a comma and zero or more spaces as
// delimiters.

src.useDelimiter(", *");

// Read and sum numbers.
while (src.hasNext()) {
  if (src.hasNextDouble()) {
    System.out.println(src.nextDouble());
  } else {
    break;
  }
}
fin.close();
  }
}

使用分词器处理普通字符串

分词器:

// start with a String of space-separated words
String tags = "pizza pepperoni food cheese";

// convert each tag to a token
StringTokenizer st = new StringTokenizer(tags," ");

while ( st.hasMoreTokens() )
{
  String token = (String)st.nextToken();
  System.out.println(token);
}

http://www.devdaily.com/blog/post/java/java-faq-stringtokenizer-example

谢谢,但我不认为这与我的问题有关,在您的示例中,您没有考虑我提供的字符串示例。另外,很抱歉,但我尽量避免使用过于高级的解决方案。 - Popokoko
我看到你刚刚添加了建议的另一部分,谢谢,但这仍然没有回答我的问题,我想要一个干净的函数,我不知道它如何有帮助。 - Popokoko

0

如果代码能够分别处理单行注释和多行注释,那将更好。有什么建议吗?

    public class RemovingCommentsFromFile {

public static void main(String[] args) throws IOException {

    BufferedReader fin = new BufferedReader(new FileReader("/home/pathtofilewithcomments/File"));
    BufferedWriter fout = new BufferedWriter(new FileWriter("/home/result/File1"));


    boolean multilinecomment = false;
    boolean singlelinecomment = false;


    int len,j;
    String s = null;
    while ((s = fin.readLine()) != null) {

        StringBuilder obj = new StringBuilder(s);

        len = obj.length();

        for (int i = 0; i < len; i++) {
            for (j = i; j < len; j++) {
                if (obj.charAt(j) == '/' && obj.charAt(j + 1) == '*') {
                    j += 2;
                    multilinecomment = true;
                    continue;
                } else if (obj.charAt(j) == '/' && obj.charAt(j + 1) == '/') {
                    singlelinecomment = true;
                    j = len;
                    break;
                } else if (obj.charAt(j) == '*' && obj.charAt(j + 1) == '/') {
                    j += 2;
                    multilinecomment = false;
                    break;
                } else if (multilinecomment == true)
                    continue;
                else
                    break;
            }
            if (j == len)
            {
                singlelinecomment=false;
                break;
            }
            else
                i = j;

            System.out.print((char)obj.charAt(i));
            fout.write((char)obj.charAt(i));
        }
        System.out.println();
        fout.write((char)10);
    }
    fin.close();
    fout.close();

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接