Статьи

4 из 5 разработчиков Java не смогли решить этот вопрос

Результаты Java Deathmatch — мини-игра-головоломка для разработчиков

Несколько месяцев назад мы выпустили наш новый сторонний проект с мини-сайтом под названием Java Deathmatch , и с тех пор более 20 000 разработчиков попробовали его. На сайте представлено 20 вопросов с несколькими вариантами ответов по Java, и сегодня, после того как мы собрали статистику по всем играм, в которые мы играли, мы будем рады поделиться некоторыми результатами и решениями с вами.

В целом мы собрали 61 872 ответа, что дает нам около 3094 ответов на каждый из 20 вопросов. Каждый сеанс Java deathmatch случайным образом выбирает 5 вопросов и дает вам 90 секунд на решение каждого из них. На каждый вопрос есть 4 возможных ответа. Нас критиковали за то, что вопросы слишком сложные, но, ну, это не называется смертельным матчем без причины! Используя эту статистику, мы смогли определить, какие вопросы были самыми сложными, а какие — самыми простыми. В этом посте мы хотели бы поделиться 5 сложнейшими вопросами из этого эксперимента и решить их вместе.

В среднем 41% попыток ответов были правильными, что совсем неплохо. Живая статистика результатов и вопросы по индексу доступны прямо здесь. Статистика этого поста - снимок с 26 июля.

В среднем 41% попыток ответов были правильными, что совсем неплохо. Живая статистика результатов и вопросы по индексу доступны прямо здесь . Статистика этого поста — снимок с 26 июля.

1. Самый сложный вопрос Java Deathmatch

Давайте начнем с самого прочного ореха, который мы получили от Александра-Константина Бледеа из Бухареста. И это настоящая головоломка. Только 20% участников смогли решить эти вопросы. Это означает, что если бы вы выбрали случайный ответ — у вас, вероятно, был бы лучший шанс выбрать правильный ответ. У дженериков Java есть это качество.

toughestquestion

Хорошо, так что у нас здесь? У нас есть дженерики с удалением типов и несколько исключений. Несколько вещей, чтобы помнить здесь:

  1. RuntimeException и SQLException оба наследуются от Exception, в то время как RuntimeException не проверено, а SQLException является проверенным исключением.
  2. Обобщение Java не реализовано, что означает, что во время компиляции информация об обобщенном типе «теряется» и обрабатывается так, как если бы код заменялся связью типа или Object, если он не существует. Это то, что вы называете стиранием типа.

Наивно, мы ожидаем, что строка 7 вызовет ошибку компиляции, поскольку вы не можете привести SQLException к RuntimeException, но это не так. Что происходит, так это то, что T заменяется на Exception, поэтому мы имеем:

1
throw (Exception) t;  // t is also an Exception

Так как pleaseThrow ожидает исключение , а T заменяется на исключение , приведение исключается, как если бы оно не было записано. Мы можем видеть это в байт-коде:

01
02
03
04
05
06
07
08
09
10
11
12
private pleaseThrow(Ljava/lang/Exception;)V throws java/lang/Exception
L0
LINENUMBER 8 L0
ALOAD 1
ATHROW
L1
LOCALVARIABLE this LTemp; L0 L1 0
// signature LTemp<TT;>;
// declaration: Temp<T>
LOCALVARIABLE t Ljava/lang/Exception; L0 L1 1
MAXSTACK = 1
MAXLOCALS = 2

Ради интереса мы попытались увидеть, как будет выглядеть байт-код без использования обобщений, и приведение появилось прямо перед оператором ATHROW :

1
CHECKCAST java/lang/RuntimeException

Теперь, когда мы убеждены, что кастинг не задействован, мы можем вычеркнуть эти два ответа:

  • «Сбой компиляции, потому что мы не можем привести SQLException к RuntimeException»
  • «Выдает ClassCastException, потому что SQLException не является экземпляром RuntimeException»

В конце концов, мы бросаем SQLException, и вы ожидаете, что он будет захвачен блоком catch и получит трассировку его стека. Ну не совсем. Эта игра сфальсифицирована. Оказывается, что компилятор запутывается так же, как и мы, и код заставляет его думать, что блок catch недоступен. Для ничего не подозревающего свидетеля SQLException не существует . Правильный ответ заключается в том, что компиляция завершается неудачно, потому что компилятор не ожидает, что SQLException будет выброшено из блока try — когда на самом деле он генерируется!

Еще раз спасибо Александру за то, что поделились этим вопросом с нами!

2. toString () или не toString (), вот в чем вопрос

Имея только 24% правильных ответов, следующий вопрос занял второе место в жесткой шкале.

q9

Это на самом деле намного проще, просто взглянув на строку 12, мы увидим, что этот код выводит m1 и m2, а не m1.name и m2.name. Сложно было вспомнить, что при печати класса Java использует метод toString. Поле «имя» было искусственно добавлено. Если вы пропустите это и правильно выполните остальную часть кода, вас могут обмануть, выбрав m1 и новое имя.

Эта строка устанавливает оба имени в «m1»:

1
m1.name = m2.name = "m1";

Затем callMe устанавливает имя m2 в новое имя, и все готово.

Но этот фрагмент фактически выведет что-то вроде этого, включая имя класса и хэш-код:

1
MyClass@3d0bc85 & MyClass@7d08c1b7

И правильный ответ будет «Ничего из вышеперечисленного».

3. Google Guava Sets

Этот вопрос не требовал особых знаний о наборах гуавы, но оставил большинство респондентов в замешательстве. Только 25% ответили правильно, так же, как и случайный выбор ответа.

Q6

Так что мы видим здесь? У нас есть метод, который возвращает набор, содержащий «клику» лучших друзей человека. Мы видим, что есть цикл, который проверяет, есть ли у человека лучший друг, и добавляет их в набор результатов. Если у человека действительно есть лучший друг, он повторяет этот процесс для него, поэтому у нас в конечном итоге будет набор лучших друзей, пока мы не найдем человека, у которого нет лучшего друга ИЛИ того, что его лучший друг уже есть в наборе. Эта последняя часть может быть немного хитрой — мы не можем добавить человека, который уже находится в наборе, поэтому нет никакого потенциала для бесконечного цикла.

Проблема здесь в том, что мы рискуем исключить нехватку памяти. На съемочной площадке нет никаких ограничений, поэтому мы можем продолжать добавлять и добавлять людей, пока у нас не закончится память.

Кстати, если вы в Google Guava, прочитайте этот пост, в котором мы писали о некоторых из менее известных, но полезных функций .

4. Двойная инициализация скобки, лол ваут ?!

Этот вопрос был одним из самых коротких, но этого было достаточно, чтобы большинство разработчиков запутались. Только 26% поняли это правильно.

q12

Не многие разработчики знают об этом синтаксисе, который пригодится, когда вам нужно инициализировать константу, хотя некоторые побочные эффекты включены . На самом деле, это отсутствие популярности может быть хорошей вещью. Так когда же WAT ?! эффект исчезает, вы видите, что мы добавляем элемент в список, а затем пытаемся распечатать его. Обычно вы ожидаете, что он распечатает [Джон], но инициализация двойной скобки имеет в виду другие планы. Здесь мы видим анонимный класс, который используется для инициализации списка. Когда он пытается распечатать NAMES, он фактически становится нулевым. Поскольку инициализатор еще не использовался и список пуст.

Вы можете прочитать больше об инициализации двойной скобки прямо здесь .

5. Любопытный случай карты во время выполнения

Это еще один вопрос сообщества, полученный от Барака Яиша из Израиля. Только 27% участников смогли решить этот вопрос.

q2

Хорошо, вычисление ищет значение на карте. Если он нулевой, он добавляет его и возвращает его значение. Поскольку список пуст, «foo» не существует, v равен нулю, и мы сопоставляем «foo» с новым ArrayList <Object> () . ArrayList пуст, поэтому он печатает [] .

Для второй строки «foo» существует на карте, поэтому мы оцениваем выражение справа. ArrayList успешно приводится к списку, и к нему добавляется «ber». add возвращает true, и это то, что он печатает.

Правильный ответ [] верно . Еще раз спасибо, Барак, что поделился этим вопросом с нами!

Бонус: И самый простой вопрос …

На этот раз у нас есть вопрос от Питера Лоури из OpenHFT, который также ведет блог о Vanilla Java . Питер входит в топ-50 списка StackOverflow, и на этот раз он перешел на другую сторону и задал вопрос, который 76% из вас получили право.

easiestquestion

Ответ C проще, чем A, B & D не компилируется.

Вывод

Время от времени нам действительно нравится играть в подобные головоломки, чтобы улучшить наши знания Java, но если вы когда-нибудь будете тратить слишком много времени на эти головоломки в своей собственной кодовой базе, это, вероятно, будет далеко не идеальным. Особенно, если кто-то звонит посреди ночи, чтобы исправить критическую производственную ошибку. Для такой ситуации мы создали Takipi для Java . Takipi — это Java-агент, который знает, как отслеживать необработанные исключения, перехваченные исключения и регистрировать ошибки на рабочих серверах. Он позволяет вам видеть значения переменных, которые вызывают ошибки, по всему стеку и накладывать их на ваш код.