Подтвердить что ты не робот

Обработка исключений 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. У меня есть ограничение, которое конструкторы не бросают - иначе все это будет легко.

4b9b3361

Ответ 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;
        }
    }
    ...