Как избежать избыточности кода конструктора в Java?

У меня есть следующий класс:

class Pair
{
    String car;
    Integer cdr;

    public Pair () {}
    public Pair (String car) { this.car = car; }
    public Pair (Integer cdr) { this.cdr = cdr; }

    public Pair (String car, Integer cdr)
    {
        this(car);
        this(cdr);
    }
}

Класс содержит два необязательных значения, и я хотел бы предоставить все возможные перестановки конструктора. Первая версия ничего не инициализирует, вторая инициализирует только первое значение, а третье инициализирует только второе значение.

Последний конструктор представляет собой комбинацию второго и третьего. Но записать это невозможно, потому что сбой кода с.

constructor.java:13: call to this must be first statement in constructor
        this(cdr);
            ^
1 error

Можно ли написать последний конструктор без избыточности кода (также без вызова тех же методов setter)?

Ответы

Ответ 1

Как правило, конструкторы с меньшим количеством аргументов должны вызывать те, у которых больше.

public Pair() {}
public Pair(String car) { this(car, null); }
public Pair(Integer cdr) { this(null, cdr); }
public Pair(String car, Integer cdr) { this.car = car; this.cdr = cdr; }

Ответ 2

Цепь ваших конструкторов в противоположном направлении, причем наиболее конкретным является тот, который задает все поля:

public Pair() {
    this(null, null); // For consistency
}

public Pair(String car) {
    this(car, null);
}

public Pair(Integer cdr) {
    this(null, cdr);
}

public Pair (String car, Integer cdr)  {
    this.car = car;
    this.cdr = cdr;
}

Таким образом:

  • Только одно место устанавливает поля и устанавливает все поля
  • Из любого другого конструктора вы можете указать (и указать, когда вы читаете код) значения "по умолчанию" для других полей.

В стороне, я настоятельно рекомендую вам сделать поля частными (и, возможно, окончательными), и дать им более значимые имена.

Обратите внимание, что таким образом, если у вас есть (скажем) 5 параметров и один конструктор с 3, один с 4 и один с 5, вы можете выбрать цепочку из 3 → 4 → 5, или вы можете перейти прямо из 3 → 5.

Кроме того, вам может потребоваться полностью удалить однопараметрические конструкторы - было бы более читаемым вместо этого использовать статические методы, где вы можете указать значение в имени:

public static Pair fromCar(String car) {
    return new Pair(car, null);
}

public static Pair fromCdr(Integer cdr) {
    return new Pair(null, cdr);
}

Или в моем предпочтительном наименовании значений:

public static Pair fromFirst(String first) {
    return new Pair(first, null);
}

public static Pair fromSecond(Integer second) {
    return new Pair(null, second);
}

В этот момент вы можете сделать класс Pair общим, не беспокоясь о том, какой конструктор будет вызываться, если два аргумента типа одинаковы. Кроме того, любой, кто читает код, может понять, что будет создано, не проверяя тип аргумента.

Ответ 3

Вы, вероятно, ищете шаблон конструктора здесь.

Кроме всего прочего, этот шаблон позволяет вам не иметь конструкторов bazillion, охватывающих все случаи. Для вашей ситуации это может быть:

@Immutable // see JSR 305
public final class Pair
{
    private final String car;
    private final integer cdr;

    private Pair(final Builder builder)
    {
        car = builder.car;
        cdr = builder.cdr;
    }

    public static Builder newBuilder()
    {
        return new Builder();
    }

    // whatever other methods in Pair, including accessors for car and cdr, then:

    @NotThreadSafe // see JSR 305
    public final class Builder
    {
        private String car;
        private int cdr;

        private Builder()
        {
        }

        public Builder withCar(final String car)
        {
            this.car = car;
            return this;
        }

        public Builder withCdr(final int cdr)
        {
            this.cdr = cdr;
            return this;
        }

        public Pair build()
        {
            return new Pair(this);
        }
    }
}

Использование образца:

final Pair newPair = Pair.newBuilder.withCar("foo").withCdr(1).build();

Преимущество: Pair теперь неизменно!

Ответ 4

class Pair
{
    String car;
    Integer cdr;

    public Pair () {}
    public Pair (String car) { 
        this(car, null)
    }
    public Pair (Integer cdr) {
        this(null, cdr);
    }

    public Pair (String car, Integer cdr) {
        this.car = car;
        this.cdr = cdr;
    }
}