Результаты Java Deathmatch — мини-игра-головоломка для разработчиков
Несколько месяцев назад мы выпустили наш новый сторонний проект с мини-сайтом под названием Java Deathmatch , и с тех пор более 20 000 разработчиков попробовали его. На сайте представлено 20 вопросов с несколькими вариантами ответов по Java, и сегодня, после того как мы собрали статистику по всем играм, в которые мы играли, мы будем рады поделиться некоторыми результатами и решениями с вами.
В целом мы собрали 61 872 ответа, что дает нам около 3094 ответов на каждый из 20 вопросов. Каждый сеанс Java deathmatch случайным образом выбирает 5 вопросов и дает вам 90 секунд на решение каждого из них. На каждый вопрос есть 4 возможных ответа. Нас критиковали за то, что вопросы слишком сложные, но, ну, это не называется смертельным матчем без причины! Используя эту статистику, мы смогли определить, какие вопросы были самыми сложными, а какие — самыми простыми. В этом посте мы хотели бы поделиться 5 сложнейшими вопросами из этого эксперимента и решить их вместе.
1. Самый сложный вопрос Java Deathmatch
Давайте начнем с самого прочного ореха, который мы получили от Александра-Константина Бледеа из Бухареста. И это настоящая головоломка. Только 20% участников смогли решить эти вопросы. Это означает, что если бы вы выбрали случайный ответ — у вас, вероятно, был бы лучший шанс выбрать правильный ответ. У дженериков Java есть это качество.
Хорошо, так что у нас здесь? У нас есть дженерики с удалением типов и несколько исключений. Несколько вещей, чтобы помнить здесь:
- RuntimeException и SQLException оба наследуются от Exception, в то время как RuntimeException не проверено, а SQLException является проверенным исключением.
- Обобщение 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% правильных ответов, следующий вопрос занял второе место в жесткой шкале.
Это на самом деле намного проще, просто взглянув на строку 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% ответили правильно, так же, как и случайный выбор ответа.
Так что мы видим здесь? У нас есть метод, который возвращает набор, содержащий «клику» лучших друзей человека. Мы видим, что есть цикл, который проверяет, есть ли у человека лучший друг, и добавляет их в набор результатов. Если у человека действительно есть лучший друг, он повторяет этот процесс для него, поэтому у нас в конечном итоге будет набор лучших друзей, пока мы не найдем человека, у которого нет лучшего друга ИЛИ того, что его лучший друг уже есть в наборе. Эта последняя часть может быть немного хитрой — мы не можем добавить человека, который уже находится в наборе, поэтому нет никакого потенциала для бесконечного цикла.
Проблема здесь в том, что мы рискуем исключить нехватку памяти. На съемочной площадке нет никаких ограничений, поэтому мы можем продолжать добавлять и добавлять людей, пока у нас не закончится память.
Кстати, если вы в Google Guava, прочитайте этот пост, в котором мы писали о некоторых из менее известных, но полезных функций .
4. Двойная инициализация скобки, лол ваут ?!
Этот вопрос был одним из самых коротких, но этого было достаточно, чтобы большинство разработчиков запутались. Только 26% поняли это правильно.
Не многие разработчики знают об этом синтаксисе, который пригодится, когда вам нужно инициализировать константу, хотя некоторые побочные эффекты включены . На самом деле, это отсутствие популярности может быть хорошей вещью. Так когда же WAT ?! эффект исчезает, вы видите, что мы добавляем элемент в список, а затем пытаемся распечатать его. Обычно вы ожидаете, что он распечатает [Джон], но инициализация двойной скобки имеет в виду другие планы. Здесь мы видим анонимный класс, который используется для инициализации списка. Когда он пытается распечатать NAMES, он фактически становится нулевым. Поскольку инициализатор еще не использовался и список пуст.
Вы можете прочитать больше об инициализации двойной скобки прямо здесь .
5. Любопытный случай карты во время выполнения
Это еще один вопрос сообщества, полученный от Барака Яиша из Израиля. Только 27% участников смогли решить этот вопрос.
Хорошо, вычисление ищет значение на карте. Если он нулевой, он добавляет его и возвращает его значение. Поскольку список пуст, «foo» не существует, v равен нулю, и мы сопоставляем «foo» с новым ArrayList <Object> () . ArrayList пуст, поэтому он печатает [] .
Для второй строки «foo» существует на карте, поэтому мы оцениваем выражение справа. ArrayList успешно приводится к списку, и к нему добавляется «ber». add возвращает true, и это то, что он печатает.
Правильный ответ [] верно . Еще раз спасибо, Барак, что поделился этим вопросом с нами!
Бонус: И самый простой вопрос …
На этот раз у нас есть вопрос от Питера Лоури из OpenHFT, который также ведет блог о Vanilla Java . Питер входит в топ-50 списка StackOverflow, и на этот раз он перешел на другую сторону и задал вопрос, который 76% из вас получили право.
Ответ C проще, чем A, B & D не компилируется.
Вывод
Время от времени нам действительно нравится играть в подобные головоломки, чтобы улучшить наши знания Java, но если вы когда-нибудь будете тратить слишком много времени на эти головоломки в своей собственной кодовой базе, это, вероятно, будет далеко не идеальным. Особенно, если кто-то звонит посреди ночи, чтобы исправить критическую производственную ошибку. Для такой ситуации мы создали Takipi для Java . Takipi — это Java-агент, который знает, как отслеживать необработанные исключения, перехваченные исключения и регистрировать ошибки на рабочих серверах. Он позволяет вам видеть значения переменных, которые вызывают ошибки, по всему стеку и накладывать их на ваш код.
Ссылка: | 4 из 5 разработчиков Java не смогли решить этот вопрос от нашего партнера по JCG Алекса Житницкого из блога Takipi . |