Почему компонент записи переменной арности не выводится с помощью пользовательского конструктора?

#java #arity #java-record #java-16

#java #arity #java-запись #java-16

Вопрос:

Пробуем некоторый код с record компонентами и записываем. Я использовал компоненты переменной редкости и был поражен ошибкой времени компиляции из-за пользовательского конструктора.

 public record Break<R extends Record>(R record, String... notifications) {

    public Break(R record, String... notifications) {
        System.out.println("record: "   record   " and notifications: "   Arrays.toString(notifications));
        this.record = record;
        this.notifications = notifications;
    }

    // compile error: non canonical record constructor must delegate to another costructor
    public Break(R record) { 
        System.out.println("record: "   record);
        this.record = record;
    }

    public Break() {
        this(null); // this works 
        // actually intelliJ suggests it uses the constructor that is not compiling
    }


    public static void main(String[] args) {
        new Break<>(new Break<>());
    }
}
  

Что мне интересно понять, так это то, как выводится аналогичный конструктор при вызове через другой пользовательский конструктор без каких-либо компонентов, предоставленных для инициализации.

Комментарии:

1. Я понимаю, что неканонические конструкторы НЕ обязаны делегировать только каноническому конструктору. Java все еще может разрешать цепочку и обнаруживать циклы. Итак, предполагая, что вы Break(R record) делегированы каноническому конструктору, вызов Break() все равно приведет к успешной инициализации всех компонентов.

Ответ №1:

Это не имеет ничего общего с переменной арностью. Все цепочки конструкторов записей должны быть «снизу» в каноническом конструкторе (который может быть указан с использованием компактной формы), независимо от того, является ли переменная арностью или нет. Это проявляется как требование, чтобы каждый неканонический конструктор должен делегировать другому конструктору; в конечном итоге это означает, что цепочки находятся внизу в каноническом.

Вы можете исправить свой плохой конструктор с помощью:

 public Break(R record) {
    this(record);
}
  

Но вам даже не нужно объявлять этот конструктор all, поскольку канонический конструктор уже может обрабатывать вызовы формы new Break(r) .

Комментарии:

1. Иногда я хотел бы иметь частные конструкторы, которые обходят канонический конструктор. Причины включают в себя отказ от создания защитной копии массива, если я знаю, что переданный массив не будет изменен в будущем. Может быть, путем явного вызова super() , чтобы показать, что это не случайность? Текущий обходной путь использует ThreadLocals для установки флага, но обычно это не стоит таких усилий.

2. @JohannesKuhn Я думаю, вы обнаружите, что такие игры в основном просто перемещают проблему. Ваши средства доступа выполняют защитные копии? Вы обновляете Object::equals , чтобы отразить это? Обратите внимание, что существует уточненный контракт, Record::equals который, вероятно, подрывается подобными трюками.

3. Я всегда спрашиваю себя: что, если java.lang.String бы это была запись? Имеет смысл из нескольких POV. Чего я стараюсь избегать, так это двойного копирования массива, поэтому я хочу реализовать такой конструктор. Глядя на эволюцию String, становится ясно, что также должна быть история о том, как преобразовать запись в обычный класс. Проблема в отражающей поддержке — Class.getRecordComponents больше не работает.

4. @JohannesKuhn Может быть, способ сделать это — написать класс? Помните, что записи не являются синтаксической функцией (хотя их синтаксис хорош), они являются семантической функцией — номинальными кортежами. Все (почти), что могут делать записи, могут делать классы (кроме реализации java.lang.Record .)

5. @JohannesKuhn Я согласен, что текущее определение может исключать некоторые (согласованные: редкие) случаи, которые все равно будут семантически кандидатами. И в еще более редких случаях разница в производительности может иметь значение (но я подозреваю, что это очень редко.) Но одного этого все еще недостаточно, чтобы сделать вывод, что мы выбрали неправильный набор правил; выбор правил требует балансировки множества различных соображений, и это только одно из них. Дизайн предполагает компромиссы; здесь мы исключаем некоторые возможные хорошие случаи, чтобы исключить больше плохих случаев. Спросите меня через 5 лет, правильно ли мы это поняли…