需要最终变量的Java内部类的复杂性?

时间:2015-07-01 14:10:58

标签: java closures

我理解Java不支持闭包的整个想法,但后来我遇到了这种行为:

    class myClass
    {
        static List<ServerSocket> servers;
        //assume the list contains a bunch of items

        void myFunc()
        {
            //This looks fine - no errors  
            for (ServerSocket server : servers)
            {                
               new Thread(new Runnable())
               {
                  public void run()
                  {
                        server.accept();
                  }
               }
            }

            //This results in an error
            for (int i=0; i < servers.size(); i++)
            {                
               new Thread(new Runnable())
               {
                  public void run()
                  {
                     //Error here - i is not final
                     servers.get(i).accept();
                  }
               }
            }
        }//end myFunc
}

我的同事也在第一次执行时遇到错误,但我没有?我正在运行JRE 1.8.0_45,他正在运行1.8.0_31

基本上,我想要的是确保所有套接字都在监听客户端。在我做任何事情之前,我实际上都在等待所有套接字连接,因此变量不是final的事实并不是真正相关的。

2 个答案:

答案 0 :(得分:1)

在第一种情况下,可以在Java 8中捕获server,因为它是有效的最后一个&#34; - 初始化后没有任何东西可以分配给它。基本上,在for-each循环中,迭代变量在本地范围内限定为每次迭代。每次迭代,您都会得到 new server变量。因此,即使server在不同的迭代中不同,它也不是变量值的变化,而是变量的变化。

在第二种情况下,i无法捕获,因为它不是&#34;有效的最终&#34; - i++表达式在初始化后分配给变量i。这与第一种情况的区别在于这是一个正常的for循环。在正常for循环中声明的变量将作用于整个循环。您只需获得一个变量,该变量在迭代中保留(并且通常更新)。您可以通过递增来在迭代之间更新i。但也许你可以在迭代之间做一些更奇特的更新操作。 for循环的完全灵活性使得通常无法将变量范围限定在每个循环的内部,即使在这种情况下,您实际上每次迭代都有一个值。

另一方面,如果您将i的值赋给循环内的局部变量,那么该变量的范围将限定在迭代的内部,因为您不会分配给随后,它将是“有效的最终”#34;并且可以被捕获:

        for (int i=0; i < servers.size(); i++)
        {
           int j = i; // j is effectively final
           new Thread(new Runnable())
           {
              public void run()
              {
                 servers.get(j).accept();
              }
           }
        }

关于第一种情况的错误,听起来像Java-8之前的情况。在Java-8之前,必须显式声明捕获的变量final。由于server未明确声明为final,因此无法在Java 7及之前的本地或匿名类中捕获它(即使它是&#34;实际上是最终的&#34;,意味着它如果声明final,则会编译。 Java 8放宽了这个有效的最终版本#34;现在,你说你的同事也在使用Java 8.我只能猜测这是错误的信息或他的编译器版本中的错误。

答案 1 :(得分:0)

您的问题是i超出了范围并且更改了server引用实际上是最终的。

此处已修复

static List<ServerSocket> servers;
//assume the list contains a bunch of items

void myFunc() {
    //This looks fine - no errors
    for (ServerSocket server : servers) {
        Thread t = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    server.accept();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        });
    }

    //This is now ok too.
    for (int i = 0; i < servers.size(); i++) {
        final ServerSocket socket = servers.get(i);
        Thread t = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    //Error here - i is not final
                    socket.accept();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        });
    }
}//end myFunc

请注意,您无法执行以下操作:

    for (int i = 0; i < servers.size(); i++) {
        Thread t = new Thread(new Runnable() {
            final ServerSocket socket = servers.get(i);

            @Override
            public void run() {
                try {
                    //Error here - i is not final
                    socket.accept();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        });
    }

所有这一切背后的原因是lambda不是对象,它实际上只是足够的指令和环境,让JVM在感觉到它时创建对象。如果你访问lambda中的局部变量,必须是最终的