使用 final 成员处理构造函数中捕获的 Java 异常
Posted
技术标签:
【中文标题】使用 final 成员处理构造函数中捕获的 Java 异常【英文标题】:Handling Java exceptions caught in constructors, with final members 【发布时间】: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
?
构造函数通常不应捕获异常。如果无法正确构造对象,则对应用程序没有任何用处。
从实践的角度来看,这与构造函数无关。如果您无法处理异常并修复它,那么让它冒泡。当然不要吞下它并打印堆栈跟踪。
【参考方案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
实例化的复杂性。
为简单起见,静态工厂方法返回一个实例,其中socket
和address
设置为真实实例,或者它们设置为null
指示无效状态。因此,此实例状态可能与您问题中的代码生成的状态略有不同。
此类工厂方法允许您隐藏它们返回的实例的真正实现,因此以下声明完全有效:public static AbstractLayer<byte[]> 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;
...
【讨论】:
以上是关于使用 final 成员处理构造函数中捕获的 Java 异常的主要内容,如果未能解决你的问题,请参考以下文章