Java中是否存在匿名代码块的实际用途?
public static void main(String[] args) {
// in
{
// out
}
}
请注意,这不是关于命名的块,即
name: {
if ( /* something */ )
break name;
}
答案 0 :(得分:120)
他们限制变量范围。
public void foo()
{
{
int i = 10;
}
System.out.println(i); // Won't compile.
}
但实际上,如果你发现自己使用了这样一个代码块,这可能是你想要将该块重构为方法的标志。
答案 1 :(得分:43)
@David Seiler的回答是正确的,但我认为代码块非常有用,应该经常使用,并不一定表明需要考虑到方法。我发现它们对构造Swing Component树特别有用,例如:
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示例中,我发现当复杂性确实超出可管理性时,它表明是时候将树的一个分支分解为一个新类而不是一堆小方法。
答案 2 :(得分:23)
通常最好make the scope of local variables as small as possible。匿名代码块可以帮助解决这个问题。
我发现这对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%的时间。
但请注意,不会改变案例失败的行为 - 您仍然需要记住包含break
或return
来阻止它!
答案 3 :(得分:16)
我认为你和/或其他答案混淆了两种不同的句法结构;即实例初始化器和块。 (顺便说一句,“命名块”实际上是一个标记语句,其中语句恰好是一个块。)
Instance Initializer用于类成员的语法级别; e.g。
public class Test {
final int foo;
{
// Some complicated initialization sequence; e.g.
int tmp;
if (...) {
...
tmp = ...
} else {
...
tmp = ...
}
foo = tmp;
}
}
根据@dfa的例子,Initializer构造最常用于匿名类。另一个用例是对'final'属性进行复杂的初始化;例如看到上面的例子。 (但是,使用常规构造函数更常见。上面的模式更常用于静态初始化器。)
另一个构造是一个普通的块,出现在代码块中,如方法; e.g。
public void test() {
int i = 1;
{
int j = 2;
...
}
{
int j = 3;
...
}
}
块最常用作控制语句的一部分,用于对一系列语句进行分组。但是当你在上面使用它们时,它们(只是)允许你限制声明的可见性;例如上面的j
。
这通常表明您需要重构代码,但并不总是很明确。例如,您有时会在使用Java编码的解释器中看到这种情况。交换机臂中的语句可以分解为单独的方法,但这可能会导致解释器的“内环”出现重大性能损失; e.g。
switch (op) {
case OP1: {
int tmp = ...;
// do something
break;
}
case OP2: {
int tmp = ...;
// do something else
break;
}
...
};
答案 4 :(得分:11)
您可以将它用作匿名内部类的构造函数。
像这样:
这样就可以初始化对象,因为在对象构造过程中会执行空闲块。
它不仅限于匿名内部类,它也适用于常规类。
public class SomeClass {
public List data;{
data = new ArrayList();
data.add(1);
data.add(1);
data.add(1);
}
}
答案 5 :(得分:6)
匿名块对于限制变量范围以及double brace initialization非常有用。
比较
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");
}};
答案 6 :(得分:2)
实例初始化程序块:
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
}
}
答案 7 :(得分:0)
您可以使用块来初始化父作用域中的最终变量。这是一种限制某些变量范围的好方法,仅用于初始化单个变量。
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
}
一般情况下,最好将块移动到一个方法中,但是当需要返回多个变量并且您不想创建包装类时,这种语法对于一次性情况很有用。
答案 8 :(得分:-1)
使用注释或由于代码结构和所选择的标识符而固有地描述任务,然后使用代码块在其中创建语言本身不强制执行的层次关系。例如:
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 程序员,您已经熟悉包含的概念:for 块、if 块、方法块:它们都是代码流中层次结构的表达。出于文件原因而不是技术原因对代码的遏制仍然是遏制。为什么要使用不同的机制?一致性很有用。减少精神负担。
注意:最有可能的最佳设计是将 MessageService 对象的初始化隔离到单独的方法。然而,这确实导致了意大利面化:在某些时候,将一个简单且易于理解的任务隔离到一个方法中会使推理方法结构变得更加困难:通过隔离它,您已经将初始化消息服务的工作变成了一个黑匣子(在至少,在您查看辅助方法之前),并且要按照代码的流动顺序完全阅读代码,您需要遍历所有源文件。这通常是更好的选择(替代方法是非常长的方法,难以测试或重用部分方法),但有时并非如此。例如,如果您的块包含对大量局部变量的引用:如果您创建一个辅助方法,则必须传递所有这些变量。一个方法也不是控制流和局部变量透明的(辅助方法不能从主方法跳出循环,辅助方法不能从主方法看到或修改局部变量)。有时这是一个障碍。