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

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

OK, а в чём альтернативность-то? :) Про inconsistent hashing в разборе написано. А assert'ы лишь для того, чтобы показать ограничения на входные данные, не удлиняя сильно условие задачи.
На assert явным образом есть отсылка в авторском разборе:
Вадим же человек аккуратный (вы посмотрите, сколько assertов!), значит, комментарий к методу он тоже написал аккуратно, а там указано, что метод вернет число в интервале от 0 до partitions исключительно.

У читателей может сложиться ложное впечатление, что это best practice, не так ли?

А вот тут
Другая, более серьезная проблема Вадима гораздо менее очевидна. Способ, который он выбрал, чтобы распределять данные по серверам, неконсистентен: при изменении количества партиций (например, при добавлении серверов) все треки перераспределяются по кластеру практически полностью.

ничего нет про поведение Object.hashCode(), а именно про его невоспроизводимость. Последнее, опять же на мой взгляд, куда большая проблема, чем изменение количество шардов, т.к. их количество может измениться либо вследствие сбоев, либо намеренного изменения (см. отсылки к Elasticsearch или MongoDB), в то время как hashCode на разных серверах для одних и тех же объектов уже будет давать разные результаты. Это важно.
hashCode у Integer и String — это не деталь реализации, а часть спецификации. Алгоритм явно прописан в Javadoc.
Строго говоря, ни одна из спецификаций (JLS, JVMS) это не гарантирует. Соглашусь с тем, что на практике проблема скорее всего надуманная.
Это гарантирует спецификация платформы Java SE 8/9/10.
JLS, JVMS, Javadoc,… — это части такой спецификации.
habrahabr.ru/company/mailru/blog/321306/#comment_10068620
Наверное, я некорректно выразился: нет гарантии совместимости Java 8 SE API и Java X SE API, т.е. описанное в javadoc поведение может поменяться в будущем. Или есть?
Точно так же, как и нет гарантий совместимости JVMS 8 и JVMS X. Могут, к примеру, пару байткодов удалить (как уже было с jsr/ret). Гарантии Javadoc в этом смысле не слабее и не сильнее гарантий других спецификаций.
Могут, к примеру, пару байткодов удалить (как уже было с jsr/ret).

Их не удалили, а всего лишь запретили использовать начиная с определённой версии class-файла:


If the class file version number is 51.0 or above, then neither the jsr opcode or the jsr_w opcode may appear in the code array

JVMS9 §4.9.1

И в JVMS9 они по-прежнему на месте (§jsr§ret), и в class-файлах версии 50.0 и ниже они всё так же могут быть использованы.
Пример без проблем был запущен под OpenJDK 64-Bit Server VM 18.3 (build 10+46, mixed mode).

Спасибо, я в курсе, что HotSpot их поддерживает для старых классов (хотя и не обязан). Суть в том, что с API ровно та же история. Его теоретически могут менять в новых версиях, но на практике предусматривают решения для обратной совместимости. В некоторых случаях даже баги оставляют для поддержки предыдущих версий (см. свойства sun.nio.ch.bugLevel и sun.nio.cs.bugLevel).
HotSpot их поддерживает для старых классов (хотя и не обязан).

Не смог с ходу найти ответ: реализация JVM, не поддерживающая старые версии class-файлов сможет пройти сертификацию?

Сорри, насчёт "не обязан" погорячился. В спеке чётко прописано, что Java SE 10-совместимая реализация JVM должна поддерживать все .class файлы, начиная с версии 45.

Отлично, мы убедились, что результат воспроизводим между перезапусками приложений. Проблема же в том, что мы полагаемся на детали реализации. Нет никаких гарантий, что поведение останется неизменным в будущих версиях Java. Даже несмотря на однозначность javadoc'ов, которым мы доверяем, ведь они касаются только текущей реализации.

Этот javadoc документирует данную реализацию, поэтому код вполне может полагаться на неё.
В случае с java.lang.String реализация hashCode() не может взять и поменяться — от неё зависит, например, switch по строкам в уже скомпилированном коде.

Верное замечание, спасибо.

Такой исходный код:
        switch(s) {
            case "s":
                System.out.println("S");
                break;
            case "b":
                System.out.println("B");
                break;
        }

компилируется в
        int var_ = -1;
        switch(s.hashCode()) {
        case 98:
            if(s.equals("b")) {
                var_ = 1;
            }
            break;
        case 115:
            if(s.equals("s")) {
                var_ = 0;
            }
        }

        switch(var_) {
        case 0:
            System.out.println("S");
            break;
        case 1:
            System.out.println("B");
        }
Кстати, вместо варианта из оригинальной статьи:
return Math.abs( track.hashCode() % partitions );

я бы предложил такой вариант с обнулением бита, отвечающего за знак:
return (track.hashCode() & 0x7FFFFFFF) % partitions;

Для отрицательных значений hashCode результат, конечно, будет отличаться:
(-100 & 0x7FFFFFFF) % 7 // 0
Math.abs(-100 % 7) // 2

тем не менее, получим такое же распределение на [0; partitions).

В Java 8+ лучше использовать java.lang.Integer#remainderUnsigned. И биты не теряем, и проблем с отрицательными значениями нет.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.