Обработка исключений Java, обнаруженных в конструкторах, с конечными членами
У меня есть уродливый код и хочу его реорганизовать:
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. У меня есть ограничение, которое конструкторы не бросают - иначе все это будет легко.
Ответы
Ответ 1
Позвольте конструктору выбрасывать исключения. Оставляя final
unassigned в порядке, если конструктор не заканчивается нормально, так как в этом случае объект не возвращается.
Самая уродливая часть вашего кода - это перехват исключений в конструкторе, а затем возврат существующего, но недопустимого экземпляра.
Ответ 2
Если вы можете использовать частные конструкторы, вы можете скрыть свой конструктор за общедоступным статическим методом factory, который может возвращать разные экземпляры вашего класса 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
. Это очень похоже на то, что я помню, называется основными конструкторами в С# и, вероятно, Scala.
- Статический метод factory скрывает сложность создания
UdpTransport
.
- Для простоты статический метод factory возвращает экземпляр с установленными как для
socket
, так и address
для реальных экземпляров, либо для них установлено значение null
, указывающее недопустимое состояние. Таким образом, это состояние экземпляра может немного отличаться от состояния, созданного кодом в вашем вопросе.
- Такие методы factory позволяют скрыть реальную реализацию экземпляра, который они возвращают, таким образом, следующее объявление полностью допустимо:
public static AbstractLayer<byte[]> createUdpTransport(final String host, final int port)
(обратите внимание на тип возврата). Сила такого подхода заключается в том, что вы можете подставить возвращаемое значение любым подклассом в зависимости от ваших потребностей, если это возможно, если вы не используете UdpTransport
-специальный публичный интерфейс.
- Также, если вы в порядке с недопустимыми объектами состояния, я думаю, тогда недопустимый экземпляр состояния не должен содержать действительное значение порта, позволяющее сделать следующее (предполагается, что
-1
может указывать недопустимое значение порта или даже значение NULL 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 safe unless UdpTransport is immutable
}
- Наконец, IMHO, печать трассировки стека в таких методах не является хорошей идеей.
Ответ 3
Две версии конструктора, где ваши атрибуты остаются окончательными. Обратите внимание, что я не считаю ваш оригинальный подход вообще "уродливым". Его можно улучшить, поскольку блок try-catch идентичен для обоих исключений. Это становится моим конструктором # 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;
}
}
...