初始化内部类的最终实例变量

3

你好,我希望在内部类的构造函数中初始化最终变量,但编译器强制我在声明时初始化。为什么?

我该如何处理这种情况?

public MainActivity extends Activity {
    private class AcceptThread extends Thread {
            private final BluetoothServerSocket mBluetoothServerSocket;
            public AcceptThread() {
                try {
                    mBluetoothServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("BT_SERVER", UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666"));
                } catch (IOException e) { }
            }

            // Here methods
    }
}

看这里,谁回答了我在这里看起来很奇怪的问题

输入图像描述

输入图像描述

输入图像描述


第三个不起作用是因为您尝试对final属性进行多个赋值(对null的赋值仍然是一种赋值)。请尝试A Boschman建议的方法,并将BluetoothServerSocket作为参数传递给AcceptThread构造函数。 - JonK
是的,我知道我分享了所有可能性,但对我来说都没有起作用,这很奇怪。 - Mick
1
我认为这是因为编译器无法百分之百确定您的变量在try块内没有被赋值,因此必须假设在赋值发生后可能会触发catch - JonK
@JonK 是的,我明白了,很好 +1 - Mick
@ghik 我看到了,好答案。 - Mick
显示剩余5条评论
3个回答

6
问题不在于你试图在构造函数中初始化字段,而在于你没有在 catch 块中初始化它。编译器必须确保在退出构造函数之前该字段将始终得到初始化。
不幸的是,在 catch 块中简单地分配一个备用值是行不通的,因为当编译器看到这样的代码时:
try {
    ...
    someFinalField = ...
    ...
} catch {
    ...
    someFinalField = ...
    ...
}

然后它发现该字段可能会被初始化两次(一次在try块中,第二次在catch块中),这对于final字段是非法的。在您的简单情况下,我们清楚地看到这是不可能的,因为异常将始终在try块中初始化字段之前抛出,但不幸的是编译器并不聪明到足以理解这一点。

您可以通过在catch块中抛出异常来满足编译器要求,这可能是您的首选选项:

catch (IOException e) {
    throw new RuntimeException(e);
}

或者,使用非常流行的 guava库
catch (IOException e) {
    throw Throwables.propagate(e);
}

如果你真的想要吞掉这个异常,那么为了确保该字段仅被初始化一次,你需要将初始化移至 try-catch 块之外:
private class AcceptThread extends Thread {
    private final BluetoothServerSocket mBluetoothServerSocket;
    public AcceptThread() {
        BluetoothServerSocket localBluetoothServerSocket;
        try {
            localBluetoothServerSocket = ...
        } catch (IOException e) { 
            localBluetoothServerSocket = ...
        }
        mBluetoothServerSocket = localBluetoothServerSocket;
    }
}

如果遇到不应该或者无法从其恢复的情况,您也可以使用 throw new InstantiationException("Error initialising socket"); 这样的语句来处理。 - JonK
不好意思,你能再看一下我的问题吗?现在它看起来很奇怪。我不能使用你所说的方法,因为它在Android上不可用。 - Mick
@Mick 你是指Guava吗?你仍然可以简单地抛出RuntimeException,不需要使用库。此外,请参阅我后来关于在构造函数中使用局部变量的建议。 - ghik
为什么我的第一种方法没起作用,在catch中初始化了最终变量,见截图。 - Mick
@Mick 因为对于编译器来说,看起来该字段可能会在“try”块中初始化,然后可能会抛出异常并且该字段将在“catch”块中第二次赋值,这对于final字段是非法的。我们知道在您的情况下这是不可能的,但编译器并不那么聪明。这就是为什么我建议使用局部变量,所以请阅读我的整个答案。 - ghik
显示剩余3条评论

2

这是因为它位于try块内,因此在某些情况下未实例化最终变量。

修改后的答案:将对象作为参数请求。总的来说,在构造函数中进行操作通常不是一个好主意。


不,Android文档说的和我做的是一样的。http://developer.android.com/guide/topics/connectivity/bluetooth.html#ConnectingDevices - Mick
+1 - 将套接字作为参数传递给构造函数是处理这个问题的最佳方式。 - JonK

1
你的代码不能保证初始化 final 字段。如果发生异常,你捕获并忽略它,那么你还没有给最终字段分配任何值。请将你的代码更改为:
public AcceptThread() throws IOException {
    mBluetoothServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("BT_SERVER", UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666"));
}

更改了答案 - 确保您分配了 mBluetoothServerSocket 或抛出异常就足够了。您也可以捕获异常并将其更改为另一个异常。 - Erwin Bolwidt
1
+1 很好,谢谢!但是我们可以在私有内部类中拥有公共构造函数。 - Mick

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