处理构造函数中捕获的 Java 异常,以及最终成员

2022-09-03 18:26:18

我有一些丑陋的代码,想重构它:

public class UdpTransport extends AbstractLayer<byte[]> {
    private final DatagramSocket socket;
    private final InetAddress address;
    private final int port;
    /* boolean dead is provided by superclass */

    public UdpTransport(String host, int port) {
        this.port = port;
        InetAddress tmp_address = null;
        try {
            tmp_address = InetAddress.getByName(host);
        } catch (UnknownHostException e) {
            e.printStackTrace();
            dead = true;
            socket = null;
            address = null;
            return;
        }
        address = tmp_address;
        DatagramSocket tmp_socket = null;
        try {
            tmp_socket = new DatagramSocket();
        } catch (SocketException e) {
            e.printStackTrace();
            dead = true;
            socket = null;
            return;
        }
        socket = tmp_socket;
    }
    ...

导致丑陋的问题是成员之间的相互作用和捕获的异常。如果可能的话,我想保留这些成员。finalfinal

我想按如下方式形成代码,但是Java编译器无法分析控制流 - 没有办法第二次分配,因为第一次尝试的赋值必须已经抛出控件才能到达子句。addresscatch

public UdpTransport(String host, int port) {
    this.port = port;
    try {
        address = InetAddress.getByName(host);
    } catch (UnknownHostException e) {
        e.printStackTrace();
        dead = true;
        address = null; // can only have reached here if exception was thrown 
        socket = null;
        return;
    }
    ...

Error:(27, 13) error: variable address might already have been assigned

有什么建议吗?

附言:我有一个约束,那就是构造函数不会抛出 - 否则这一切都很容易。


答案 1

让构造函数引发异常。如果构造函数未正常终止,则保持未赋值是可以的,因为在这种情况下不返回任何对象。final

代码中最丑陋的部分是捕获构造函数中的异常,然后返回现有但无效的实例。


答案 2

如果可以自由地使用私有构造函数,则可以将构造函数隐藏在公共静态工厂方法后面,该方法可以返回类的不同实例。比方说:UdpTransport

public final class UdpTransport
        extends AbstractLayer<byte[]> {

    private final DatagramSocket socket;
    private final InetAddress address;
    private final int port;
    /* boolean dead is provided by superclass */

    private UdpTransport(final boolean dead, final DatagramSocket socket, final InetAddress address, final int port) {
        super(dead);
        this.socket = socket;
        this.address = address;
        this.port = port;
    }

    public static UdpTransport createUdpTransport(final String host, final int port) {
        try {
            return new UdpTransport(false, new DatagramSocket(), getByName(host), port);
        } catch ( final SocketException | UnknownHostException ex ) {
            ex.printStackTrace();
            return new UdpTransport(true, null, null, port);
        }
    }

}

上面的解决方案可能有以下注意事项:

  • 它只有一个构造函数,该构造函数仅将参数分配给字段。因此,您可以轻松拥有字段。这与我记得的C#中称为主构造函数以及Scala非常相似。final
  • 静态工厂方法隐藏了实例化的复杂性。UdpTransport
  • 为简单起见,静态工厂方法返回一个实例,其中两者都设置为真实实例,或者将它们设置为指示无效状态。因此,此实例状态可能与问题中的代码生成的状态略有不同。socketaddressnull
  • 这样的工厂方法允许您隐藏它们返回的实例的实际实现,因此以下声明是完全有效的:(请注意返回类型)。这种方法的强大之处在于,如果可能的话,您可以根据需要将返回值替换为任何子类,除非您使用特定于公共接口。public static AbstractLayer<byte[]> createUdpTransport(final String host, final int port)UdpTransport
  • 另外,如果你对无效的状态对象很好,我猜,那么无效的状态实例不应该包含一个真正的端口值,允许你做出以下决定(假设可以指示一个无效的端口值,或者如果你可以自由地更改你的类的字段,甚至可以为空,而原始包装器对你来说不是一个限制):-1Integer
private static final AbstractLayer<byte[]> deadUdpTransport = new UdpTransport(true, null, null, -1);
...
public static AbstractLayer<byte[]> createUdpTransport(final String host, final int port) {
    try {
        return new UdpTransport(false, new DatagramSocket(), getByName(host), port);
    } catch ( final SocketException | UnknownHostException ex ) {
        ex.printStackTrace();
        return deadUdpTransport; // it's safe unless UdpTransport is immutable
    }
  • 最后,恕我直言,用这种方法打印堆栈跟踪不是一个好主意。