使用最终成员处理构造函数中捕获的 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是一个实例,其中
socket
和address
都设置为真实实例,或者它们设置为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;
}
}
...
我有一些难看的代码,想重构它:
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是一个实例,其中
socket
和address
都设置为真实实例,或者它们设置为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;
}
}
...