使用最终成员处理构造函数中捕获的 Java 异常

Handling Java exceptions caught in constructors, with final members

我有一些难看的代码,想重构它:

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;
    }
    ...

导致丑陋的问题是 final 成员之间的交互和捕获的异常。如果可能的话,我想保留成员final

我想形成如下代码,但是 Java 编译器无法分析控制流 - 无法像第一次尝试那样对 address 进行第二次赋值必须抛出赋值才能使控制到达 catch 子句。

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

有什么建议吗?

P.S。我有一个约束,即构造函数不会抛出 - 否则这一切都会很容易。

构造函数的两个版本,其中您的属性保持最终状态。请注意,我一般不认为您的原始方法 'ugly'。不过它可以改进,因为 try-catch-block 对于这两个异常是相同的。这成为我的构造函数 #1.

public UdpTransport(String host, int port) {
    InetAddress byName;
    DatagramSocket datagramSocket;

    try {
        byName = InetAddress.getByName(host);
        datagramSocket = new DatagramSocket(); 
    } catch (UnknownHostException | SocketException e) {
        e.printStackTrace();
        dead = true;
        datagramSocket = null;
        byName = null;
    }
    this.port = port;
    this.socket = datagramSocket;
    this.address = byName;
}

public UdpTransport(int port, String host) {

    this.port = port;
    this.socket = createSocket();
    this.address = findAddress(host);
}

private DatagramSocket createSocket() {
    DatagramSocket result;
    try {
        result = new DatagramSocket(); 
    } catch (SocketException e) {
        e.printStackTrace();
        this.dead = true;
        result = null;
    }
    return result;
}

private InetAddress findAddress(String host) {
    InetAddress result;
    try {
        result = InetAddress.getByName(host);
    } catch (UnknownHostException e) {
        e.printStackTrace();
        this.dead = true;
        result = null;
    }
    return result;
}

让构造函数抛出异常。如果构造函数没有正常终止,则不分配 final 是可以的,因为在这种情况下没有返回任何对象。

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

此构造函数根本没有捕获异常的正当理由。充满 null 值的对象对应用程序没有任何用处。构造函数应该 抛出 该异常。没抓到。

如果您可以随意使用私有构造函数,则可以将构造函数隐藏在 public 静态工厂方法后面,这样可以 return 您的 UdpTransport [=44] 的不同实例=].比方说:

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);
        }
    }

}

上述解决方案可能有以下注释:

  • 它只有一个构造函数,仅将参数分配给字段。因此,您可以轻松拥有 final 个字段。这与我记得在 C# 和 Scala 中称为 primary constructors 非常相似。
  • 静态工厂方法隐藏了UdpTransport实例化的复杂性。
  • 为简单起见,静态工厂方法return是一个实例,其中socketaddress都设置为真实实例,或者它们设置为null表示无效状态。因此,此实例状态可能与您问题中的代码产生的状态略有不同。
  • 此类工厂方法允许您隐藏实例的真实实现 return,因此以下声明是完全有效的:public static AbstractLayer<byte[]> createUdpTransport(final String host, final int port)(注意 return 类型)。这种方法的强大之处在于,除非您使用 UdpTransport 特定的 public 接口,否则您可以根据需要用任何子 class 替换 returned 值。
  • 此外,如果您对无效的状态对象没有问题,我猜,那么无效的状态实例不应该包含一个真实的端口值,允许您进行以下操作(假设 -1 可以指示一个无效的端口值,甚至可以为空 Integer 如果您可以自由更改 class 的字段并且原始包装器对您没有限制):
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
    }
  • 最后,恕我直言,用这种方法打印堆栈跟踪不是一个好主意。

像这样的事情怎么样:

public class UdpTransport extends AbstractLayer<byte[]> {
    private final DatagramSocket socket;
    private final InetAddress address;
    private final int port;

    public static UdpTransport create(String host, int port) {
        InetAddress address = null;
        DatagramSocket socket = null;
        try {
            address = InetAddress.getByName(host);
            socket = new DatagramSocket();
        } catch (UnknownHostException | SocketException e) {
            e.printStackTrace();
        }
        return new UdpTransport(socket, address, port);
    }

    private UdpTransport(DatagramSocket socket, InetAddress address, int port) {
        this.port = port;
        this.address = address;
        this.socket = socket;
        if(address == null || socket == null) {
           dead = true;
        }
    }
    ...