java中的匿名代码块

mspsb9vt  于 2021-06-29  发布在  Java
关注(0)|答案(9)|浏览(389)

在java中匿名代码块有什么实际用途吗?

public static void main(String[] args) {
    // in
    {
        // out
    }
}

请注意,这与命名块无关,即。

name: { 
     if ( /* something */ ) 
         break name;
}

.

wqsoz72f

wqsoz72f1#

您可以将其用作匿名内部类的构造函数。
这样地:

这样您就可以初始化您的对象,因为空闲块是在对象构造期间执行的。
它不局限于匿名内部类,它也适用于常规类。

public class SomeClass {
    public List data;{
        data = new ArrayList();
        data.add(1);
        data.add(1);
        data.add(1);
    }
}
efzxgjgh

efzxgjgh2#

我认为你和/或其他答案混淆了两个不同的句法结构;即示例初始化器和块(顺便说一下,“命名块”实际上是一个带标签的语句,其中语句恰好是一个块。)
示例初始值设定项用于类成员的语法级别;例如

public class Test {
    final int foo;

    {
         // Some complicated initialization sequence; e.g.
         int tmp;
         if (...) {
             ...
             tmp = ...
         } else {
             ...
             tmp = ...
         }
         foo = tmp;
    }
}

根据@dfa的例子,初始化器构造最常用于匿名类。另一个用例是对“final”属性进行复杂的初始化;e、 g.参见上述示例(但是,更常见的做法是使用常规构造函数。上面的模式更常用于静态初始值设定项。)
另一个构造是普通块,出现在诸如method之类的代码块中;例如

public void test() {
    int i = 1;
    {
       int j = 2;
       ...
    }
    {
       int j = 3;
       ...
    }
}

块最常用作控制语句的一部分,用于对语句序列进行分组。但是当您在上面使用它们时,它们(只是)允许您限制声明的可见性;例如 j 在上面。
这通常表示您需要重构代码,但这并不总是明确的。例如,您有时会在用java编写的解释器中看到这种情况。开关臂中的语句可以分解成不同的方法,但这可能会对解释器的“内部循环”造成严重的性能影响;例如

switch (op) {
    case OP1: {
             int tmp = ...;
             // do something
             break;
         }
    case OP2: {
             int tmp = ...;
             // do something else
             break;
         }
    ...
    };
xj3cbfub

xj3cbfub3#

通常最好使局部变量的范围尽可能小。匿名代码块可以帮助解决这个问题。
我觉得这对我来说特别有用 switch 声明。考虑以下示例,没有匿名代码块:

public String manipulate(Mode mode) {
    switch(mode) {
    case FOO: 
        String result = foo();
        tweak(result);
        return result;
    case BAR: 
        String result = bar();  // Compiler error
        twiddle(result);
        return result;
    case BAZ: 
        String rsult = bar();   // Whoops, typo!
        twang(result);  // No compiler error
        return result;
    }
}

对于匿名代码块:

public String manipulate(Mode mode) {
    switch(mode) {
        case FOO: {
            String result = foo();
            tweak(result);
            return result;
        }
        case BAR: {
            String result = bar();  // No compiler error
            twiddle(result);
            return result;
        }
        case BAZ: {
            String rsult = bar();   // Whoops, typo!
            twang(result);  // Compiler error
            return result;
        }
    }
}

我认为第二个版本更简洁易读。而且,它将在开关中声明的变量的范围缩小到了它们被声明的情况,根据我的经验,99%的时间都是这样。
但是,请注意,它不会改变case fall-through的行为-您仍然需要记住包含 break 或者 return 为了防止它!

c7rzv4ha

c7rzv4ha4#

@davidseiler的回答是正确的,但我认为代码块非常有用,应该经常使用,并不一定表示需要将其分解为方法。我发现它们对于构建swing组件树特别有用,例如:

JPanel mainPanel = new JPanel(new BorderLayout());
{
    JLabel centerLabel = new JLabel();
    centerLabel.setText("Hello World");
    mainPanel.add(centerLabel, BorderLayout.CENTER);
}
{
    JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0,0));
    {
        JLabel label1 = new JLabel();
        label1.setText("Hello");
        southPanel.add(label1);
    }
    {
        JLabel label2 = new JLabel();
        label2.setText("World");
        southPanel.add(label2);
    }
    mainPanel.add(southPanel, BorderLayout.SOUTH);
}

代码块不仅尽可能严格地限制变量的范围(这总是好的,特别是在处理可变状态和非最终变量时),而且它们还以xml/html的方式说明组件层次结构,使代码更易于阅读、编写和维护。
我把每个组件示例化分解成一个方法的问题是
该方法只会在公开给更广泛的受众时使用,即使它是一个私有示例方法。
更难阅读,想象一个更深更复杂的组件树,你必须深入找到你感兴趣的代码,然后松散的视觉上下文。
在这个swing示例中,我发现当复杂性确实超出了可管理性时,它表明是时候将树的一个分支分解成一个新类,而不是一堆小方法了。

zed5wv10

zed5wv105#

示例初始值设定项块:

class Test {
    // this line of code is executed whenever a new instance of Test is created
    { System.out.println("Instance created!"); }

    public static void main() {
        new Test(); // prints "Instance created!"
        new Test(); // prints "Instance created!"
    }
}

匿名初始值设定项块:

class Test {

    class Main {
        public void method() {
            System.out.println("Test method");
        }
    }

    public static void main(String[] args) {
        new Test().new Main() {
            {
                method(); // prints "Test method"
            }
        };

        {
            //=========================================================================
            // which means you can even create a List using double brace
            List<String> list = new ArrayList<>() {
                {
                    add("el1");
                    add("el2");
                }
            };
            System.out.println(list); // prints [el1, el2]
        }

        {
            //==========================================================================
            // you can even create your own methods for your anonymous class and use them
            List<String> list = new ArrayList<String>() {
                private void myCustomMethod(String s1, String s2) {
                    add(s1);
                    add(s2);
                }

                {
                    myCustomMethod("el3", "el4");
                }
            };

            System.out.println(list); // prints [el3, el4]
        }
    }
}

变量范围限制:

class Test {
    public static void main() {
        { int i = 20; }
        System.out.println(i); // error
    }
}
bqf10yzr

bqf10yzr6#

它们限制了变量的作用域。

public void foo()
{
    {
        int i = 10;
    }
    System.out.println(i); // Won't compile.
}

不过,在实践中,如果您发现自己使用了这样的代码块,那么这可能是您希望将该代码块重构为方法的标志。

vyu0f0g1

vyu0f0g17#

描述一个任务,可以是注解,也可以是由于代码的结构和所选的标识符而固有的,然后使用代码块在那里创建一个层次关系,而语言本身并不强制这样做。例如:

public void sendAdminMessage(String msg) throws IOException {
    MessageService service; {
        String senderKey = properties.get("admin-message-server");
        service = MessageService.of(senderKey);
        if (!ms.available()) {
          throw new MessageServiceException("Not available: " + senderKey);
        }
    }

    /* workaround for issue 1298: Stop sending passwords. */ {
        final Pattern p = Pattern.compile("^(.*?)\"pass\":.*(\"stamp\".*)$");
        Matcher m = p.matcher(msg);
        if (m.matches()) msg = m.group(1) + m.group(2);
    }
    ...
}

以上只是一些示例代码来解释这个概念。第一个块由它前面的块“记录”:该块用于初始化 service 变量。第二个块由注解记录。在这两种情况下,块都为注解/变量声明提供了“作用域”:它们解释了特定进程的结束位置。这是一种更常见的风格的替代品:

public void sendAdminMessage(String msg) throws IOException {
    // START: initialize service
    String senderKey = properties.get("admin-message-server");
    MessageService service = MessageService.of(senderKey);
    if (!ms.available()) {
      throw new MessageServiceException("Not available: " + senderKey);
    }
    // END: initialize service

    // START: workaround for issue 1298: Stop sending passwords.
    final Pattern p = Pattern.compile("^(.*?)\"pass\":.*(\"stamp\".*)$");
    Matcher m = p.matcher(msg);
    if (m.matches()) msg = m.group(1) + m.group(2);
    // END: workaround for issue 1298: Stop sending passwords.

    ...
}

但是,这些块更好:它们允许您使用编辑器工具更有效地导航(“转到块的末尾”),它们限定块中使用的局部变量的范围,以便它们无法转义,最重要的是,它们将包含的概念对齐:作为java程序员,您已经熟悉包含的概念:对于块,if块、方法块:它们都是代码流中层次结构的表达式。出于文件原因而非技术原因对代码的遏制仍然是遏制。为什么使用不同的机制?一致性很有用。减少精神负担。
注意:最好的设计很可能是将messageservice对象的初始化隔离到一个单独的方法。然而,这确实会导致分裂:在某种程度上,将一个简单且易于理解的任务隔离到一个方法中会使对方法结构的推理变得更加困难:通过隔离它,您已经将初始化messageservice的工作变成了一个黑盒(至少,在您查看helper方法之前),为了按照代码的流动顺序完整地阅读代码,您需要在源文件中到处转转。这通常是更好的选择(另一种选择是很长的方法,很难测试,或者重用其中的一部分),但有时情况并非如此。例如,如果块包含对大量局部变量的引用:如果创建一个helper方法,则必须传递所有这些变量。方法也不是控制流和局部变量透明的(助手方法不能从主方法中跳出循环,助手方法不能从主方法中看到或修改局部变量)。有时候这是个障碍。

1sbrub3j

1sbrub3j8#

匿名块对于限制变量的作用域以及双大括号初始化非常有用。
比较

Set<String> validCodes = new HashSet<String>();
validCodes.add("XZ13s");
validCodes.add("AB21/X");
validCodes.add("YYLEX");
validCodes.add("AR2D");

具有

Set<String> validCodes = new HashSet<String>() {{
  add("XZ13s");
  add("AB21/X");
  add("YYLEX");
  add("AR5E");
}};
neskvpey

neskvpey9#

可以使用块从父作用域初始化最终变量。这是一个很好的方法来限制一些变量的作用域,这些变量只用于初始化单个变量。

public void test(final int x) {
    final ClassA a;
    final ClassB b;
    {
        final ClassC parmC = getC(x);
        a = parmC.getA();
        b = parmC.getB();
    }
    //... a and b are initialized
}

一般来说,最好将块移动到方法中,但是这种语法对于需要返回多个变量并且您不想创建 Package 类的一次性情况是很好的。

相关问题