Разбор задач JPoint 2019

image

Всем привет!

Закончилась одна из самых хардкорных конференций по Java — JPoint 2019, она проходила в седьмой раз и как всегда побила рекорд по посещаемости, в этот раз мероприятие привлекло более 1700 специалистов в области Java-разработки.

«Одноклассники» принимали участие во всех конференциях JPoint. Начиная с 2013 мы активно поддерживаем JPoint и на своих стендах устраиваем для участников различные активности по проверке знаний Java. В этом году у нас были знаменитые «нерешаемые» задачи от ведущих разработчиков OK.ru. Участники конференции, правильно ответившие на вопросы, получили призы.

Справедливости ради надо сказать, что из 600 листочков с задачами, которые мы раздали, обратно было получено менее 100, средний балл равен примерно 0.25.

Лучшим оказалось решение, набравшее 4 балла из 5 возможных.

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

Битам быть


Эту задачу решили 40%, сдавших ответы.

Михаил создаёт потокобезопасный аналог BitSet. Допишите реализацию метода setBit().

Для простоты можно считать размер BitSet постоянным.

public class ConcurrentBitSet {
    private final AtomicLongArray bits;
 
    public ConcurrentBitSet(int size) {
        assert size >= 0;
        int words = (size + 63) / 64;
        bits = new AtomicLongArray(words);
    }
 
    public void setBit(int index) {
        // TODO: Implement me!
    }
}


Решение
Реализация с помощью updateAndGet()/getAndUpdate(), доступных с Java 8, может выглядеть так:
public void setBit(int index) {
    int word = index >> 6;
    long mask = 1L << index;
    bits.updateAndGet(word, value -> value | mask);
}


Аналогично выглядит реализация на старом-добром compareAndSet():
public void setBit(int index) {
    int word = index >> 6;
    long mask = 1L << index;
    long oldValue;
    long newValue;
    do {
        oldValue = bits.get(word);
        newValue = oldValue | mask;
    } while (!bits.compareAndSet(word, oldValue, newValue));
}



Enum уже не тот


Эту задачу решили 45%, сдавших ответы.

Татьяна хочет проверить, являются ли два объекта константами одного и того же enum. Что она не учла?

boolean sameEnum(Object o1, Object o2) {
    return o1.getClass().isEnum() &&
            o1.getClass() == o2.getClass();
}


Решение
Подсказка кроется в документации к методу Enum.getDeclaringClass (), который используется, например, в Enum.compareTo():
public final Class getDeclaringClass() {
    Class clazz = getClass();
    Class zuper = clazz.getSuperclass();
    return (zuper == Enum.class) ? (Class)clazz : (Class)zuper;
}


Для enum-констант с непустыми телами создаются промежуточные классы, поэтому правильный ответ может выглядеть так:
boolean sameEnum(Object o1, Object o2) {
    return o1 instanceof Enum &&
            o2 instanceof Enum &&
            ((Enum) o1).getDeclaringClass() == ((Enum) o2).getDeclaringClass();
}



Некомпилируемые связи


Эту задачу решили 42%, сдавших ответы.

Имеется следующий интерфейс:

interface Link {
    T next();
}


Измените сигнатуру (но не тело) метода getTail(), чтобы код компилировался без ошибок и предупреждений.

Link getTail(Link head) {
    if (head.next() == null) {
        return head;
    }
    return getTail(head.next());
}


Решение
Правильных минимальных ответов всего три:
> Link getTail(Link head)
> Link getTail(T head)
> T getTail(T head)


Как это ни парадоксально выглядит, такая сигнатура не по зубам компилятору Java:
> T getTail(Link head)



Мессенджер


Эту задачу решили 14%, сдавших ответы.

Костя разрабатывает приложение для обмена сообщениями. Укажите ошибки в методе для отправки сообщения по сети.

void send(SocketChannel ch, String message) throws IOException {
    byte[] bytes = message.getBytes();
 
    ByteBuffer header = ByteBuffer.allocate(4);
 
    header.putInt(bytes.length);
    ch.write(header);
    ch.write(ByteBuffer.wrap(bytes));
}


Решение
В этом коде имеются как минимум три ошибки:
Так может выглядеть исправленная версия:
void send(SocketChannel ch, String message) throws IOException {
    byte[] bytes = message.getBytes(StandardCharsets.UTF_8);

    ByteBuffer header = ByteBuffer.allocate(4);
    header.putInt(bytes.length);
    header.flip();

    while (header.hasRemaining()) {
        ch.write(header);
    }

    ByteBuffer body = ByteBuffer.wrap(bytes);
    while (body.hasRemaining()) {
        ch.write(body);
    }
}



Java в контейнере


Эту задачу решили 7.5%, сдавших ответы.

Какие параметры JVM следует прописать Алексею, чтобы не позволить ОС Linux убить Java-процесс из-за превышения лимита памяти, отведённого на контейнер?

  • -Xmx
  • -XX:MaxMetaspaceSize
  • -XX:ReservedCodeCacheSize
  • -XX:+UseContainerSupport
  • -XX:MaxRAMPercentage
  • Память JVM нельзя ограничить


Решение
Память, потребляемая Java-процессом, далеко не ограничивается только хипом, Metaspace и Code Cache. Многие другие структуры JVM также занимают память, причём не все из них регулируются настройками. Помимо виртуальной Java машины нативную память выделяет Java Class Library и пользовательский код посредством Direct ByteBuffers и Mapped ByteBuffers.

Параметр UseContainerSupport совместно с MaxRAMPercentage влияет лишь на размер хипа. Таким образом, нет гарантированного способа избежать превышения лимита только с помощью флагов JVM, и правильным ответом будет последний. Подробнее об использовании памяти Java процессом можно посмотреть в докладе Андрея Паньгина на Joker 2018 «Память Java процесса по полочкам».

© Habrahabr.ru