【问题标题】:Handling Java exceptions caught in constructors, with final members使用 final 成员处理构造函数中捕获的 Java 异常
【发布时间】:2015-11-11 04:22:27
【问题描述】:

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

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

有什么建议吗?

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

【问题讨论】:

  • 有没有想过可以使用非final而不是使用final实例变量?如果您想确保类外的不变性,请在 getter 中返回防御性副本。
  • socket创建失败是否需要address == null
  • 为什么你不能有另一个tmp_address并在最后分配address = tmp_address
  • 构造函数通常不应捕获异常。如果无法正确构造对象,则对应用程序没有任何用处。
  • 从实践的角度来看,这与构造函数无关。如果您无法处理异常并修复它,那么让它冒泡。当然不要吞下它并打印堆栈跟踪。

标签: java constructor checked-exceptions


【解决方案1】:

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

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

【讨论】:

  • 谢谢,我会认真考虑的。
  • 如果我这样做 new Layer0(new Layer1(new Layer2(params...), params...), params...) 整个堆栈将失败,Layer1 将不知道它应该使用哪种类和构造函数参数来构建新的 Layer2。
  • 再想一想,根本原因是我需要为协议栈中的每一层提供一种构造其子层的方法,而不是给它一个已经构造好的子层。这很棘手......
【解决方案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);
        }
    }

}

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

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

【讨论】:

    【解决方案3】:

    构造函数的两个版本,您的属性保持最终状态。请注意,我不认为您的原始方法通常“丑陋”。不过可以改进它,因为两个异常的 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;
    }
    

    【讨论】:

      【解决方案4】:

      这个构造函数没有任何正当理由去捕捉异常。一个充满null 值的对象对应用程序毫无用处。构造函数应该抛出那个异常。抓不住。

      【讨论】:

        【解决方案5】:

        这样的事情怎么样:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-29
          • 2016-07-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多