I have some ugly code and want to refactor it:
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;
}
...
The issue causing the ugliness is the interaction between final
members and caught exceptions. I would like to keep the members final
if possible.
I would like to form the code as follows, but the Java compiler cannot analyse the control flow - there is no way that address
could be assigned a second time, as the first attempted assignment must have thrown for control to have reached the catch
clause.
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
Any advice?
P.S. I have a constraint which is that the constructors do not throw - otherwise this would all be easy.
The short answer to the question “can a constructor throw an exception in Java” is yes! Of course, properly implementing exceptions in your constructors is essential to getting the best results and optimizing your code.
When throwing an exception in a constructor, the memory for the object itself has already been allocated by the time the constructor is called. So, the compiler will automatically deallocate the memory occupied by the object after the exception is thrown.
Yes, constructors are allowed to throw an exception in Java. A Constructor is a special type of a method that is used to initialize the object and it is used to create an object of a class using the new keyword, where an object is also known as an Instance of a class.
Let the constructor to throw the exceptions. Leaving final
unassigned is OK if the constructor does not terminate normally, as in this case no object is returned.
The most ugly part of your code is catching exceptions in constructor and then returning the existing, but invalid instance.
If you're free to use private constructors, you can hide your constructor behind a public static factory method, that can return different instances of your UdpTransport
class. Let's say:
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);
}
}
}
The solution above may have the following notes:
final
fields. This is very similar to what I remember is called primary constructors in C# and probably Scala.UdpTransport
instantiation.socket
and address
set to real instances, or them set to null
indicating invalid state. So this instance state may slightly differ from the state produced by the code in your question.public static AbstractLayer<byte[]> createUdpTransport(final String host, final int port)
(note the return type). The power of such approach is that you can substitute the returned value by any subclass depending on your needs if it's possible unless you use UdpTransport
-specific public interface.-1
can indicate an invalid port value, or even nullable Integer
if you're free to change the fields of your class and primitive wrappers are not a restriction for you):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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With