Как стать автором
Обновить

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

НЛО прилетело и опубликовало эту надпись здесь
Боюсь, что найдутся извращенцы, которые начнут навешивать аннотации Ломбока на записи…
Java всё больше становится похожей на Kotlin, однако.
Предлагаю для следующей статьи делать ставки, под каким номером будет коммент про то, что Java становится похожей на Kotlin.
Ой, да ладно уж вам, не надо делать сообщество вокруг языка токсичным ;)
Но он ведь прав.
В этой правоте столько же правды и смысла, сколько в «java похожа на basic потому что в ней сложение тоже через '+' делается».
Groovy от этого умер.
Один больной вопрос, который меня интересует: можно ли создать несколько публичных записей в одном файле?
Подскажите, в чем проблема оборачивать в класс, Records.java к примеру?

Интересно, а можно ли использовать рекорды в качестве параметров аннотаций? А то ограничение только скалярами, строками и классами нередко оказывается слишком неудобным.

Наверняка нет, т. к. это позволило бы положить что угодно в аннотацию, обернув в рекорд с одним полем. Да и зачем? Аннотации сами по себе очень похожи на рекорды, особенно с простой реализацией нужных методов (equals/hashCode), и они могут быть использованы параметрами других аннотаций.
Из интересного, на мой взгляд, можно отметить еще и шаблоны (дженерики)
public class Records {
    public static void main(String[] args) {
        new Point<Integer>(1, 2).print();
        new Point<String>("11", 2).print();
    }

}

record Point<T>(T x, int y) {
    void print() {
        System.out.println("" + x + " - " + y);
    }
}

Вопрос к разработчикам: почему внезапно аксессоры компонентов назвали также как и поля, тогда как в джаве стандарт — это getXXX()? Много фреймворков и библиотек настроены работать именно с геттерами...

Потому что тогда бы пришлось делать toUpperCase для первой буквы имени поля, но toUpperCase зависит от локали: youtu.be/qurG_J81_Cs?t=1625

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


Что такое Record? Скорее всего это просто класс со всем его поведением, но вводится новое ключевое слово record и теперь у нас есть и class и record, но пишем мы point.getClass()… Разве не лучше было бы record class хотя бы? Когда-нибудь дойдет до структур и получим class, record и struct, угадай что тебе надо.


Хороший вопрос про геттеры и сеттеры, да и многие примеры вызывают один и тот же вопрос, почему сделан такой выбор?

Спасибо за исследование! Это небольшое различие в значениях хеша лишило меня всякого сна и покоя, пришлось лезть разбираться, в чем же тут дело. И разобралось следующее.


Objects.hashCode опирается на алгоритм, приведенный в описании метода java.util.List.hashCode:


int hashCode = 1;
for (E e : list)
    hashCode = 31*hashCode + (e==null ? 0 : e.hashCode())

Для записей метод расчета хеша генерируется тут: java.lang.runtime.ObjectMethods.makeHashCode(Class<?>, List).

Я выделил самые, на мой взгляд, важные части:



private static int hashCombiner(int x, int y) {
    return x*31 + y;
}

private static MethodHandle makeHashCode(Class<?> receiverClass,
                                        List<MethodHandle> getters) {
    //метод, всегда возвращающий 0
    MethodHandle accumulator = MethodHandles.dropArguments(ZERO, 0, receiverClass);

    for (MethodHandle getter : getters) {
        //для примитивов hashCode их класса-обертки (Integer, Float и т.д.)
        //а для объектов Objects.hashCode
        MethodHandle hasher = hasher(getter.type().returnType());
        MethodHandle hashThisField = MethodHandles.filterArguments(hasher, 0, getter);
        //HASH_COMBINER это ссылка на метод hashCombiner выше
        MethodHandle combineHashes = MethodHandles.filterArguments(
            HASH_COMBINER, 0, accumulator, hashThisField);
        accumulator = MethodHandles.permuteArguments(
            combineHashes, accumulator.type(), 0, 0);
    }
    return accumulator;
}

Если не вдаваться к детали, то получается, что для записей алгоритм расчета хеша идентичен java.util.List.hashCode (тоже хеш элементов считает да на 31 умножает, ага) за исключением инициализации счетчика нулем, а не единицей. Могли бы ради единообразия подхода и тут с единицы считать начинать. Я бы тогда поспал :)

Интересно, будет ли реализации equals(), созданная вызовом invokedynamic при исполнении, менять порядок сравнения, чтобы сначала сравнивались примитивные типы, строки и простые объекты?

В текущей реализации всё отдано на откуп компилятору.
В каком порядке он геттеры в статические параметры invokedynamic загонит, в таком и будут проверяться:


/**
 * Generates a method handle for the {@code equals} method for a given data class
 * @param receiverClass   the data class
 * @param getters         the list of getters
 * @return the method handle
 */
private static MethodHandle makeEquals(Class<?> receiverClass,
                                      List<MethodHandle> getters) {
...
    for (MethodHandle getter : getters) {
        MethodHandle equalator =
            equalator(getter.type().returnType()); // (TT)Z
        MethodHandle thisFieldEqual =
            MethodHandles.filterArguments(equalator, 0, getter, getter); // (RR)Z
        accumulator =
            MethodHandles.guardWithTest(
                thisFieldEqual, accumulator, instanceFalse.asType(rr)
            );
    }

    return MethodHandles.guardWithTest(
        isSameObject,
        instanceTrue,
        MethodHandles.guardWithTest(isInstance, accumulator.asType(ro), instanceFalse)
    );
}

Скорее всего проверки пойдут в порядке декларации полей.
Программист захочет — сам поля в записи переупорядочит.

Программист захочет — сам поля в записи переупорядочит.

Я так и думал ))

Хороший вопрос для рассылки OpenJDK :)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации