Parsing Joker 2018



    Aloha!

    That ended one of the most hardcore conferences in the world of Java - Joker 2018, which is traditionally held in St. Petersburg at Expoforum. This year a record number of participants took part in the conference. Odnoklassniki traditionally offered to help our developers solve non-trivial tasks that arise when creating one of the most heavily loaded Java projects.

    Those who answered the questions well received prizes, and we offer you a brief analysis of our problems. We hid the correct answers under the spoiler, mind you, to open only after they themselves thought of the solution ;-)

    Let's go!

    Deduplicator


    Cyril wants to save memory by deduplicating objects equal in equals(). Help him implement the thread-safe dedup method by analogy with String.intern, but not only for strings.

    publicstaticObject dedup(Object obj) {
    }

    Decision
    Scratching the back of his head first, Cyril was able to come up with several options for solving this problem, but they were all somehow wrong. Then he scratched his nose and the dock pro java.util.concurrent, remembered a wonderful method computeIfAbsent. This method will perform the lambda passed to it in the parameter only if there is no key in Map, write its result and return it. If there is already such a key, the lambda will not be calculated, and the current value associated with the key will be returned. In addition, Kirill remembered, for ConcurrentHashMapthis method works atomically, which makes it very elegant to solve the problem. Satisfied Cyril wrote this code:

    privatestatic final ConcurrentHashMap map = new ConcurrentHashMap();
    publicstatic Object dedup(Object obj){
        returnmap.computeIfAbsent(obj, o -> o);
    }

    and gladly scratched my nose again.

    IP address


    Dima is developing a new network protocol. Correct the error in his method to translate the IPv4 address represented as a byte array into the string.

    String ipToString(byte[] ip) {
        return ip[0] + '.' +
               ip[1] + '.' +
               ip[2] + '.' +
               ip[3];
    }

    Decision
    Первую ошибку сразу же показала IDE, не дав Диме даже дописать метод до конца. Символ '.', имеющий тип char, складывается с байтом как целочисленный тип. Заменив '.' на ".", Дима так обрадовался успешно скомпилированному коду, что сразу запустил его без тестирования. «Ай-ай-ай, Дима», подумала JVM и выдала вместо IP-адреса какую-то ерунду. В отличие от Димы, JVM точно знала, что в Java тип byte служит для хранения чисел со знаком, то есть все адреса, имеющие октеты больше 127, будут представлены в Java отрицательными числами. По правилам же приведения этих чисел в int, отрицательный знак числа такой же, как и в оригинальном байте. Эх, Дмитрий, надо же было принять дополнительные меры для того, чтобы отбросить знаковую часть, например так:

    return (ip[0] & 255) + "." +
           (ip[1] & 255) + "." +
           (ip[2] & 255) + "." +
           (ip[3] & 255);


    Agile mixer


    Marina needs to mix the elements of the list in a random order. Why is this option not suitable, and how would you fix it?

    Random random = ThreadLocalRandom.current();
    list.sort((o1, o2) -> {
        returnrandom.nextBoolean() ? +1 : -1;
    });

    Decision
    Марина, очевидно, забыла, что контракт Comparator требует стабильности: при сравнении двух одинаковых значений результат сравнения должен быть одинаков. А в реализации Марины результат для каждой пары строго случаен, что запросто может привести к исключению java.lang.IllegalArgumentException: Comparison method violates its general contract! Если бы Марина читала вечерами документацию, она бы знала, что в данном случае лучше всего воспользоваться методом Collections.shuffle().

    Ответ: Нарушен контракт компаратора. Сортировка может выкинуть исключение. Лучше воспользоваться методом Collections.shuffle().

    Den of the functionary


    Egor loves to write in a functional style, without worrying about the effectiveness of the code. Estimate how many objects each call to this method creates, if it is transferred ArrayListfrom 10 lines?

    Predicate<String> equalsAny(List<String> list) {
        Predicate<String> p = s -> false;
        for (String s : list) {
            p = p.or(s::contains);
        }
        return p;
    }

    Decision
    В отличие от Егора, педантичная Алина не любит всё писать в функциональном стиле, потому что умеет считать накладные расходы. Единственная строка

    p = p.or(s::contains);

    создаст сразу два объекта: один как результат вызова p.or(), а второй для создания предиката s::contains. Последний не может быть закэширован, так как захватывает в контекст переменную s. Умножив на количество итераций, получим 20 объектов. А ведь еще и скрытый Iterator может быть создан, если JIT его не оптимизирует. «20 или даже 21 объект, если не повезет, грешновато», подумала Алина.

    Ответ: 10 предикатов or + 10 предикатов contains + 1 Iterator в зависимости от JIT-оптимизаций.

    Maxim turns on the maximum


    Maxim calculates the maximum in a multi-threaded program, but wants to do without locks. Help him fix the error.

    AtomicLong max = new AtomicLong();
    voidaddValue(long v) {
        if (v > max.get()) {
            max.set(v);
        }
    }

    Decision
    Эх, Максим! Само по себе использование AtomicLong ещё не делает программу потокобезопасной. Для этого есть атомарная операция AtomicLong.compareAndSwap. А начиная с Java 8 и вовсе не обязательно писать CAS-цикл самому, ведь появился замечательный атомарный метод accumulateAndGet. И тут удобно использовать как раз его:

    voidaddValue(long v){
        max.accumulateAndGet(v, Math::max);
    }
    


    Also popular now: