Показаны сообщения с ярлыком java. Показать все сообщения
Показаны сообщения с ярлыком java. Показать все сообщения

четверг, 12 ноября 2015 г.

15 вопросов к техническому интервью Java программиста с 5-6 летним опытом работы

Источник - 15-вопросов-к-техническому-интервью-Java-программиста-с-5-6-летним-опытом-работы
С ростом опыта программиста от уровня начинающего разработчика (от 2 до 4 лет опыта работы) до уровня старшего разработчика( от 5 до 7 лет) меняются и вопросы на интервью по Core Java.
Конечно такие основы как структуры данных, алгоритмы И объектно-ориентированное программирование остаются, но вопросы и ответы становятся более подробными. Я часто получаю вопросы о том какие вопросы по Core Java задают старшим разработчикам, или каких вопросов стоит ожидать на собеседовании на позицию старшего разработчика Java. Это меня на некоторое время озадачивает, ведь как только вы станете старшими разработчиками, вы автоматически начнете принимать участие в собеседованиях и вам надо иметь представление о том, чего ожидать от собеседования, но в то же самое время, я понимаю, что зная вопросы собеседовании, вам будет легче к нему подготовиться. Конечно, вы не получите вопросов которые задают программистам с 2-3 годами опыта работы с Java, хотя в начале собеседования это не исключено. Я не вижу отличий между телефонными интервью и собеседованиями с глазу на глаз. Какие то вопросы остаются теми же, какие то вопросы требуют более подробных ответов. В этой статье я поделюсь 15 техническими вопросами по Core Java, которые задавались при собеседованиях старших разработчиков во время телефонного интервью. Я не публикую все ответы, но вы можете найти их в текущем блоге или в блогеJavarevisited.

15 вопросов по Core Java для программистов с 5-6 опытом разработки.
Все вопросы были получены от работающих старших разработчиков, имеющих более чем 5 летний опыт работы. Разработчики встречались с этими вопросами на разных этапах трудоустройства, включая телефонные интервью и собеседования с глазу на глаз.

1) Как работает метод HashMap в Java?
Да, это по-прежнему один из самых популярных вопросов для старшего разработчика, вы должны ожидать его на телефонном интервью, после него возможно будет много смежных вопросов, посмотрите ответы на нихздесь.

2) Какие 2 метода должен реализовать объект ключа HashMap ? equals и hashcode

3) Почему объекты, используемые в качестве ключа должны быть иммутабельными(неизменяемыми) ? что бы хэш-код всегда возвращал одно и то же значение

4) Благодаря чему в ConcurrentHashMap достигается масштабируемость ? Иногда этот вопрос звучит на интервью как: разница между ConcurrentHashMap и Hashtable в Java, поищите ответы здесь.

5) Как сделать объект общим для разных потоков? Или как передать объект от одного потока к другому ? Есть много путей реализации этого, такие как очереди, обменники, но блокируемые очереди с шаблоном проектирования Producer/Consumer это самый простой способ, чтобы передать объект из одного потока в другой.

6) Как узнать что в вашей программе есть взаимная блокировка?( Получив дамп потока, используя kill -3, применяя JConsole или VisualVM). Я предлагаю подготовиться к этому вопросу на интервью очень тщательно, так как интервьюеры любят детали, они спрашивают бывали ли в вашем проекте подобные проблемы и как вам удалось их решить. 

7) Как избежать взаимных блокировок при кодировании ? Что бы разобраться с блокировками и получить полную информацию о теме, прочтите это.

8) Что такое ожидание занятости(«Busy spinning»)? Почему вы должны использовать его ? Один из интереснейших вопросов многопоточности для старшего разработчика Java, Busy spinning это стратегия ожидания, когда поток выполняет ожидание в цикле, при этом не используя ресурсы процессора и как бызасыпая. Эту стратегию используют когда время ожидания очень мало, при этом не нагружается процессор и не останавливается поток, сохраняются все данные, которые могут быть потеряны при запуске потока на другом ядре процессора. Этот вопрос популярен у программистов создающих высоко нагруженные проекты, где программисты 
добиваются чрезвычайно низких задержек в работе, в диапазоне микро- и мили- секунд.

9) Что такое блокировка Чтения и Записи (ReadWriteLock)? Использует ли ConcurrentHashMap ReadWrite блокировки ? Блокировка Чтения и Записи это такая реализация блокировки, когда несколько разных потоков пытаются произвести операцию чтения и записи с одним объектом. сама по себе операция чтения не изменяет объект, что допускает многопоточные операции чтения без блокировок. Java предоставляет реализацию ReadWriteLock с которой стоит ознакомиться. Например ConcurrentHashMap не применяет ReadWriteLock, вместо этого он делит Hash на отдельные части и блокирует их отдельно, таким образом в любой момент времени заблокирована только часть хеша, а не он весь. Этот вопрос популярен у опытных разработчиков, обычно просят ответить подробнее, спрашивают разные реализации ReadWriteLock для разных случаев.

10) Как сделать объект иммутабельным(не изменяемым) в Java? Зачем делать объект иммутабельным ? Иммутабельность предполагает несколько преимуществ включая потокобезопасность, возможность кеширования, а так же делает многопоточный код более читаемым.
Посмотрите это и научитесь делать объекты иммутабельными. На собеседовании по этому вопросу возможны дополнительные уточнения, в зависимости от полноты вашего ответа. Например когда вы говорите, что Spring иммутабельный, будьте готовы ответить почему строки так же иммутабельны в Java.

11) Какие шаблоны проектирования в используете ?
Всегда ожидайте вопрос о шаблонах проектирования на собеседовании к вакансии старшего разработчика. Лучше отметить любой GOF шаблон, а не Singleton или MVC который использует каждый второй Java программист. Лучший ответ может быть шаблон Декоратор или шаблон Внедрение зависимости, которые довольно популярны в Spring Framework. Так же хорошо если называемые вами шаблоны проектирования вы действительно использовали и знаете к какому компромиссу ведет их применение. Как только вы произнесете название шаблона проектирования, например: «Фабрика», интервьюер тут же спросит: «Вы использовали это в своих проектах ?» Поэтому будьте готовы привести примеры и рассказать почему вы выбрали в своем проекте именно этот шаблон.

12) Знаете ли вы о принципе Открытости/Закрытости или Принцип подстановки Барбары Лисков? 
Шаблоны проектирования основаны на принципах объектно-ориентированного проектирования.
Настоятельно рекомендую посмотреть мою статью 10 принципов объектно ориентированного дизайна, которые должен знать программист Java, по крайней мере иметь представление о том как эти принципы помогут вам писать объектно ориентированный код. Если у вас нет ответа на этот вопрос, вы можете вежливо ответить «нет», никто не ожидает от вас знания ответов на все вопросы. Однако зная ответ на вопрос, который вызывает затруднения у большинства разработчиков, вы делаете свою кандидатуру на собеседовании очень сильной.

13) Какой шаблон проектирования будете использовать, что бы защитить ваш код от сторонней библиотеки, которая будет заменена через пару лет ? 
Это всего лишь один из возможных вариаций вопроса о шаблонах проектирования, которые могут быть заданы вам в зависимости от того о чем вы говорите сейчас на собеседовании. Один из способов как оградить свой код от сторонней библиотеки, это зависимость от интерфейса, а не от реализации и использование зависимостей что бы обеспечить конкретную реализацию. Подобные вопросы часто задают старшим разработчикам с 5-7 годами опыта работы.

14) Как предотвратить SQL инъекцию в коде на Java ?
Этот вопрос чаще задают Java EE разработчикам, чем обычным Java программистам, тем не менее это отличный повод узнать о PreparedStatement. PreparedStatement — это объект, который представляет предварительно скомпилированный SQL-оператор. PreparedStatement не только обеспечивает более высокую производительность, но и защищает от SQL инъекций. Если вы работаете по большей части с Java EE или J2EE, вы должны быть знакомы с некоторыми вопросами безопасности, в том числе «атака Фиксации Сессии» или «межсайтовый скриптинг», так же вам нужно уметь избегать этих атак.

15) Расскажите об отличиях типов ссылок в Java, таких как WeakReference, SoftReference иPhantomReference? И почему вы должны их использовать ?
По сути, различие между всеми типами ссылок только одно — поведение Java Garbage Collector с объектами, на которые они ссылаются. В Java объекты, создаваемые через оператор new создаются по strong ссылке. Сборщик мусора (garbage collector) уничтожает такие объекты только тогда, когда на них больше не остается сильных (strong) ссылок. В пакете java.lang.ref есть 3 класса, которые описывают 3 типа ссылок, соответственно SoftReference, WeakReference, PhantomReference. Объекты, созданные через SoftReference, будут собраны в случае, если JVM требует память. То есть имеется гарантия, что все SoftReference объекты будут собраны перед тем, как JVM выбросит исключение OutOfMemoryError. SoftReference часто используется для кешей, потребляющих большое количество памяти. WeakReference не спасает объект от финализации, даже если имеется достаточное количество свободной памяти. Как только на объект не останется strong и soft ссылок, он может быть финализирован. Используется для кешей и для создания цепей связанных между собой объектов. Объекты, созданные через PhantomReference, уничтожаются тогда, когда GC определяют, что ссылаемые объекты могут быть освобождены. Этот тип ссылок используется как альтернатива финализации (для более гибкого освобождения ресурсов).

Это весь список вопросов для интервью старшего Java разработчика. Я не включил в него многие важные вопросы об обработке исключений, сборщике мусора, настройке JVM, которые очень популярны у старших разработчиков, возможно я включу их в следующей части. Но если вы не находите ответов на какой либо из вопросов, дайте мне знать и я обновлю данный пост для моих постоянных читателей.

Оригинальная статья: 15 Technical Core Java Questions Answers for 5 to 6 Years Experienced Programmer 
Перевел dio

среда, 28 января 2015 г.

Как будучи Java-программистом перестать сомневаться и перейти на Scala, а также о применимости Scala для Android (в соавторстве с Алексеем Пышненко) (http://eax.me/scala-doubts/)

Нам нередко приходилось сталкиваться с сомнениями касательно перехода на Scala людей, которые в настоящее время пишут на Java. И в самом деле, насколько вообще перспективно переучиваться с простой, понятной и всем известной Java на какую-то там странную и никому неизвестную Scala? Кроме того, ситуацию осложняет тот факт, что поверхностное гугление находит помимо положительных отзывов о Scala и весьма негативные.
Примечание: Заметка написана в соавторстве с Алексеем Пышненко.
Действительно, как и в случае с любой другой технологией, существуют как положительные, так отрицательные отзывы. Но отрицательный отзывы обычно появляются из-за относительной (например, по сравнению с Java или PHP) высотой порога вхождения в язык. Такие элементы функционального программирования, как неизменяемые переменные, строгая статическая типизация или сопоставление с образцом, многим программистам непонятны, потому что это не то, чему их учили в школе на примере Pascal. В то же время эти программисты чувствуют себя довольно комфортно в своих интерпрайзах, поэтому не испытывают особой мотивации разбираться. Отсюда и появляются отзывы типа «Scala сложная, как C++», хотя это вовсе не так.
По существу, единственный реальный недостаток Scala — это относительно медленная компиляция. Но на современном железе (типа MacBook Pro и сравнимых машинах) это уже и не является проблемой. Что же касается высокого порога вхождения в язык, наш опыт показывает, что заинтересованный программист, написавший пару hello world’ов на Scala, уже где-то через полтора месяца работы над реальным проектом пишет на языке совершенно свободно. При этом те же программисты отмечают существенно бОльшую скорость разработки на Scala по сравнению с Java, а также существенно меньшее количество ошибок, особенно связанных с многопоточностью — за счет неизменяемости переменных и модели акторов (библиотека Akka).
Scala используется в Twitter (у них практически все, ну кроме БД, написано на Scala), Netflix (обработка HTTP, и множество бизнес-логики на Scala), Foursquare(написан и работает на Scala, веб-интерфейс на Scala), LinkedIn (веб-интерфейс написан на Scala, возможно и внутри что-то тоже), The Guardian, Sony, Siemens, Xerox, Novell, а также в Яндексе и многих других российских, украинских и белорусских компаниях (можете проверить по HeadHunter). Ежегодно выходит множество книг, посвященных Scala. Существует довольно большое, активное и дружелюбное сообщество программистов, ведущих блоги, выступающих на конференциях, пишущих подкасты и общающихся в списках рассылок. Посмотрите хотя бы рассылки scala-user@akka-user@ и play-framework@ в группах Google.
Не стоит недооценивать важность комьюнити. Оно постоянно двигает язык вперед, и делает его все более мощным. На подходе Scala 3, которая сейчас называетсяDotty. Это язык с dependent types, которые являются мечтой любого знакомого с ними программиста, желающего писать корректные программы. Помимо ученых, которые, надо сказать, работают далеко не на голом энтузиазме, языком занимается компания Typesafe, в которую не так давно было вложено немало инвестиций, самые значимые из которых составили 14 миллионов долларов. И они оправдывают эти инвестиции — популярность языка Scala растет с каждым днем, постоянно появляются все новые и новые вводные материалы для людей, заинтересованных, но не знакомых с языком, читаются доклады по теории типов на различных конференциях.
Что же касается опасения в отношении новых технологий, то его трудно не понять. Но, по всей видимости, в данном случае программист абсолютно ничего не теряет. Программируя на Scala, вы пишите под ту же JVM. Код на Scala даже может быть использован в коде на Java и наоборот. Притом несложно понять, как код на Scala транслируется в Java-классы, то есть вы просто пишите на Java с более приятным синтаксисом (кортежи, автоматический вывод типов). Те же Play, Akka и другие библиотеки успешно применяются программистами на Java. Так что, вы получаете универсальный опыт, применимый как для Scala, так и для Java.
Следует также отметить возможность разработки на Scala под Android.
Если вы когда-нибудь слышали про RxJava, то там представлен ровно тот подход, вокруг которого построена Scala — reactive programming. Это такой подход к event-driven архитектуре приложения, нацеленный на асинхронность во всем. В итоге все ваше приложение выглядит как совершенно прозрачная цепочка событий, где отлично видно причину и последствия любого действия пользователя. В итоге при какой-либо ошибке в бизнес-логике почти сразу же ясно, что пошло не так: отладка почти не отнимает времени. Ко всему прочему, код становится намного более компактным и выразительным, количество возможностей для обобщения логики разительно возрастает по сравнению с Java, при этом не теряя ни единой возможности, которую предоставляет Java.
Вот что говорят наши Android-программисты о достаточно скором для них переходе на Scala.
Очень заметно, как скорость разработки растет. Несмотря на то, что мы только вникаем в Scala, и не всегда все делаем правильно, а также тратим часть времени на обучение, скорость создания отдельного компонента / сущности / экрана заметно возросла, и продолжает расти.
В Scaloid, которым пользуется теперь наша команда, много очень удобных оберток над стандартными компонентами (SButton, SListView, и тд), которые сильно упрощают жизнь. Контекст теперь передается имплицитно, обработчики различных событий зачастую указываются прямо в конструкторе (имеется ввиду метод apply у companion object). Работа с коллекциями, несмотря на множество отзывов об их сложности со стороны опытных скалистов, стала намного приятнее. Теперь то, что в Java нужно было долго и упорно описывать, делается в несколько строк — map, flatMap, reverse, fold, что еще нужно?
Future сильно упростили код. AsyncTask’и хоть и вполне легковесны для Java, но ни в какое сравнение с легкостью и читабельностью Future они не идут. Код, обернутый во Future, отлично вписывается в код, работающий с UI. При чтении никакого переключения контекста не происходит, чего нельзя сказать о выделении собственного потока или использования AsyncTask.
Вот как можно сократить Java-код (взято отсюда):
private class LongOperation extends AsyncTask<StringVoid, String> {
    @Override
    protected String doInBackground(String... params) {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.interrupted();
            }
         }
         return "Executed";
     }

    @Override
    protected void onPostExecute(String result) {
        System.out.println("OnPostExecute");
    }

    @Override
    protected void onPreExecute() {
        System.out.println("OnPostExecute");
    }

    @Override
    protected void onProgressUpdate(Void... values) {}
}
… переписав его на Scala:
Future {
    println("OnPreExecute")
    for (<1 to 5) {
        Thread.sleep(1000)
    }
    "Executed"
}.map(_ => "OnPostExecute")
 .recover { case e => println(s"Error $e"); "OnError"}
В общем и целом, Scala сильно упростила разработку под Android. Больше нет ощущения борьбы с языком, которое иногда возникало в Java. Все просто идет своим чередом, одно вытекает из другого, все логично и красиво, и это здорово.

Мы надеемся, что данная заметка помогла вам развеять хотя бы часть сомнений по поводу Scala. Будем рады любым вашим комментариям в отношении написанного, а также ответным постам.
P.S. И кстати, даже Bruce Eckel любит Scala :)

Scala. Введение (http://habrahabr.ru/post/77750/)

Не так давно я заинтересовался одним из многочисленных ныне языков под JVM — Scala. Причин тому много, основная — всё нарастающее со временем чувство неудобства при работе с cpp-подобными языками. Взгляд мой попеременно падал на Ruby, Groovy, Python, но все они оставляли впечатление инструментов, не совсем подходящих для моего обычного круга рабочих задач (Python-таки хорош, но у нетипизированных языков есть свои ограничения). Scala же, напротив, показалась вполне годным языком. Так как поиск по хабру никаких статей о ней не выловил (было несколько, но мягко говоря не вводных), я решил написать маленький обзор и поделиться им с массами.

Немного философии языка в вольном изложении


Какие основные цели преследовали создатели языка? Согласно моим мироощущениям они такие:
Во-первых, совместимость. Среди своих задач разработчики ставили поддержание совместимости с Java-языком и тысячами примеров говнокода разработок на ней для решения самых разнообразных задач.
Во-вторых, интенсивное насыщение языка функциональными фичами, которые, в основном, (но далеко не полностью) составляют его отличия от Java.
В-третьих, облегчение нашего с вами труда. Действительно, компилятор Scala понимает программиста с полуслова, писать код специально, чтобы втолковывать ему, что я не верблюд, мне не довелось пока.
В-четвёртых, поддержка и стимулирование написания модульных, слабосвязанных программных компонентов в сочетании с широкими возможностями адаптации уже существующих. Цели не то, чтобы совсем противоположные, но порождающие известные трудности для одновременного достижения. Что ж, посмотрим что получится.
В-пятых, это поддержка параллелизма. К сожалению у меня руки и голова до этой области не дошли (надеюсь пока), но акцент на этом моменте делается постоянно на всех ресурсах по языку.

Для экспериментов с языком достаточно поставить соответствующий плагин на любимую IDE отсюда.

Итак, давайте посмотрим на сам язык…

Общие идеи языка, примеры синтаксиса


Самое, пожалуй, важное, — это «унифицированная модель объектов». Этот термин расшифровывается авторами так: «каждое значение — объект, каждая операция — вызов метода». Это, конечно, не «всё — объект», но сущностей в сравнении с Java убыло, а чем меньше сущностей — тем легче жизнь :) В прикладном плане это означает, что числа и символы сделались неизменяемым объектами, обитающими в общей куче, все операции приобретают ссылочную семантику. Например, код 5 + 5 вполне валиден, и породит новый объект в куче, который оперативненько оприходует сборщик мусора (на самом деле, я тихо надеюсь, что компилятор поймёт глубину замысла и порождать он ничего не будет :) ).

После столь возвышенного введения можно глянуть на решение классической задачи:
object Main {
 def main(args:Array[String]) :Unit = {
  print("Hello, " + args(0) + "!")
 }
}

В нём мы видим следующее:
  • Можно объявлять отдельные объекты. Ничего необычного в этом нет, подобная возможность имеется, например в Groovy. Ведут себя такие объекты так же как написанные на Java реализации шаблона Singelton.
  • Объявление функции выглядит непривычно, но вполне читабельно: [ключевое слово def] [имя]([список параметров]):[возвращаемый тип] = [блок кода].
  • В качестве типа, не несущего информационной нагрузки, выступает тип Unit. Он вполне аналогичен void в C-подобных языках.
  • Объявление параметра функции (а на самом деле и локальной переменной тоже) выглядит как [имя]:[тип].
  • Для параметризации типа используется не привычные нам <>, а казалось бы, навсегда закреплённые за массивами [].
  • Для обращения к элементам массива(экое непотребство) используются ().
  • Имеется какие-то встроенные функции, доступные в коде по умолчанию без всяких дополнительных импортов.

В дополнение, давайте взглянем на ещё один короткий пример:
println( ("Hello, " + args(0) + "!").toUpperCase )
println( "Hello, " + args(0) + "!" toUpperCase )

Как из него следует, использование оператора . совершенно не необходимо. Синтаксис языка вполне допускает использование вместо него пробела (также аргументы метода можно писать без скобок и запятых сразу после имени метода). И как мы видим, это оказывается вполне полезно: в первой строке высокоприоритетный оператор . заставляет нас писать ненужные, засоряющие код скобки, во второй получается более краткая и наглядная форма записи.

В качестве подспорья разработчику Scala поддерживает также интерактивный режим. То есть, можно запустить интерпретатор и по одной вводить команды. Интерпретатор, встроенный в IDE, как-то нерегулярно работает, его отдельный вариант есть в репозиториях Убунты, думаю у остальных дистрибутивов тоже всё хорошо, счастливым обладателям Windows как всегда придётся помучаться :) Интерпретатор запускается самым необычным способом:
$ scala
Welcome to Scala version 2.7.3final (Java HotSpot(TM) Server VM, Java 1.6.0_16).
Type in expressions to have them evaluated.
Type :help for more information.
scala>

Совсем маленький пример:
scala> 1 to 10
res0: Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Тут мы видим пример вызова метода с параметром. Если кто не догадался, у объекта класса Int 1 вызывается метод to с параметром того же типа 10, результат — диапазон значений.

Попробуем-ка мы теперь написать ещё одну функцию. Пусть она нам считает сумму чисел в заданном диапазоне, итак:
scala> def sum(a: Int, b: Int): Int = {
| var result = 0
| for (i <- a to b) result += i
| result
| }
sum: (Int,Int)Int

scala> sum(1, 5)
res3: Int = 15

Здесь видны ещё три важных момента:
  • При помощи ключевого слова var мы можем объявлять локальные переменные
  • Результатом вычисления блока является последнее выражение в нём
  • В нашем распоряжении имеется цикл for, который может выполнять вычисления для значений в заданном диапазоне (на самом деле для объектов в любом объекте — контейнере)

Операции над функциями


Что же мы такого можем с ними тут делать? Да что угодно =) Функции являются полноценными объектами программы. Их можно хранить как свойства объектов, передавать как параметры и возвращаемые значения и собственно создавать во время выполнения. Данные свойства позволяют строить так называемые функции высокого порядка, оперирующие себе подобными.

Для иллюстрации рассмотрим ставший классическим пример вычисления суммы:
scala> def sum(f: Int => Int, a: Int, b: Int): Int =
| if (a > b) 0 else f(a) + sum(f, a + 1, b) sum: ((Int) => Int,Int,Int)Int

В данном примере определяется функция sum, представляющая знакомый, надеюсь, всем оператор суммы. Параметры имеют следующий смысл:
f — функция преобразования целого числа из пределов суммирования в элемент суммы. Обратите внимание на объявление типа параметра: знак => означает, что параметр — функция, типы принимаемых значений перечисляются слева от него в круглых скобках (если параметр один, как в данном примере, их допустимо опустить), тип возвращаемого результата справа.
Работает она тривиально: вычисляет значение функции в нижней границе диапазона и складывает его с результатом вычисления себя самой в диапазоне на 1 меньшем.
Также в этом примере видна ещё одна особенность языка — if является выражением, имеющим значение (кстати, использованный ранее for — тоже выражение, его результат типа Unit). Если условие истина, то его результат первый вариант, иначе — второй.
a и b — пределы суммирования.

Ещё пара функций id и square, они равны своему параметру и его квадрату соответственно.
scala> def id(x: Int): Int = x
id: (Int)Int
scala> def square(x: Int): Int = x * x
square: (Int)Int

Тут надо сделать ещё одно лирическое отступление: функции в Scala имеют декларативный стиль объявления. Они описывают не как получить результат, а чему он равен. Но если требуется организовать последовательные вычисления в теле функции, нет проблем — у нас есть блоки.

Теперь можно воспользоваться тем, что мы написали ранее.
scala> sum(id, 1, 5)
res1: Int = 15
scala> sum(square, 1, 5)
res2: Int = 55

Здесь происходит кульминация этой части — мы берём и передаём функцию в другую функцию. Никаких интерфейсов, анонимных классов, делегатов: вот оно — маленькое счастье.

Я здесь намеренно не стал приводить примеры вложенных и анонимных функций, карринга. Всё это Scala умеет, но всё нельзя включить в небольшой обзор. Думаю, приведёный пример достаточен для понимания важности и, главное, — удобства функций высокого порядка как инструмента программирования. Напоследок могу посоветовать почитать эту главу замечательной книги по программированию.

Особенности классов


Давайте опишем несложный класс. Пусть это будет комплексное число. Создадим следующий код:
class Complex(r: Double, i: Double) {
 def real = r
 def image = i
 def magnitude = Math.sqrt(r*r + i*i)
 def angle = Math.atan2(i, r)

 def + (that: Complex) = new Complex(this.real + that.real, this.image + that.image)

 override def toString = real+" + i*"+image+" | "+magnitude+"*e^(i*"+angle+"))"
}

object Main {
 def main(args:Array[String]) :Unit = {
  val first = new Complex(1, 5)
  val second = new Complex(2, 4)
  val sum = first + second
  println(first)
  println(second)
  println(sum)
 }
}

Во-первых, класс объявлен с какими-то параметрами. Как несложно догадаться по продолжению, это параметры конструктора, которые доступны всё время жизни объекта.
Во-вторых, в классе объявлено несколько методов — селекторов. Одно семейство для декартового представления и одно для полярного. Как видим оба они используют параметры конструктора.
В-третьих, в классе объявлен оператор сложения. Объявлен он как обычный метод, принимает также Complex и возвращает его же.
Ну и наконец, для этого класса переопределена, без сомнения, знакомая всем Java-программистам функция toString. Важно отметить что на переопределение методов в Scala всегда необходимо явно указывать при помощи ключевого словаoverride.

Несмотря на огромную практическую ценность данный класс обладает рядом недостатков, а именно:
  • Занимает неоправданно много для своей функциональности места на экране
  • Не умеет сравнивать себя с себе подобными

Что же, попробуем исправить недочёты, средствами этого прекрасного языка.
class Complex(val real: Double, val image: Double) extends Ordered[Complex] {
 def magnitude = Math.sqrt(real*real + image*image)
 def angle = Math.atan2(image, real)
 def + (that: Complex) = new Complex(this.real + that.real, this.image + that.image)
 def compare(that: Complex): Int = this.magnitude compare that.magnitude
 override def toString = real+" + i*"+image+" | "+magnitude+"*e^(i*"+angle+"))"
}

object Main {
 def main(args:Array[String]) :Unit = {
  val first = new Complex(1, 5)
  val second = new Complex(2, 4)
  if (first > second )
   println("First greater")
  if (first < second )
   println("Second greater")
  if (first == second )
   println("They're equal")
 }
}

Итак, что появилось нового:
  • У параметров конструктора появилось ключевое слово val и исчезли соответствующие селекторы. Да, всё вполне очевидно, это разрешение компилятору создать селекторы для них автоматически.
  • Добавилось наследование от незнакомого нам класса (а точнее trait'а) Ordered. Да не простого, а параметризованного нашим классом. Как следует из названия, он должен помочь нам с упорядочиванием наших экземпляров.
  • Появился метод compare, который сравнивает два комплексных числа посредством сравнения их модулей.
  • В тестовом методе появились использования операторов >, <, ==. Их мы явно не определяли.

Пришло время сказать несколько слов об идее trait'а. Это особый тип класса, который не может иметь конструкторов, но может иметь любые методы и атрибуты. Обычно они устанавливают некоторый протокол для взаимодействия со своими возможными наследниками. Используя этот протокол они могут получить необходимую информацию от потомка и реализовать в нём некоторое поведение. Соответственно, любой класс (или объект) может наследоваться от произвольного количества trait'ов(и лишь от одного class'а). Например Ordered объявляет абстрактный метод compare и на его основании дополняет класс-наследник операторами <, <=, > и т.д. Здесь надо заметить, что по-хорошему стоит переопределить предоставленный нам оператор ==, так как он даёт истину и для неодинаковых объектов, да и методы equals с hashCode также стоит переопределять в таких случаях.
«Всё это хорошо» — скажет бывалый боец аутсорсного рынка, «но что делать если требуется банальный domain-класс, с богомерзкими модификаторами атрибутов?».
Решение у нас, естественно, есть :)
class User {
 private[thisvar _name: String = ""
 def name = _name toUpperCase
 def name_=(name: String) = {
  _name = if (name != null) name else ""
 }
}

  • Во-первых, этот класс использует уже знакомое нам ключевое слово var в своём теле, да не просто а с диковинным модификатором private[this]. Значение этого ключевого слова в теле класса абсолютно аналогично таковому в внутри блока(и даже, скажу по секрету, в конструктор его тоже можно запихать) и делает из имени после него изменяемый атрибут класса. Диковинный модификатор заявляет, что переменная должна быть доступна только данному объекту. Можно было написать, например, private[User] и она стала бы доступна другим, нам подобным, объектам, или указать имя пакета (что-то это мне напоминает).
  • Далее объявлена функция возвращающая наше поле в верхнем регистре.
  • И в заключение, странная функция name_=, получающая строку в виде параметра, проверяющая что она не null и записывающая её в наше поле.

Чтобы понять, как это всё использовать давайте взглянем на результат выполнения следующего кода(для краткости я не стал включать сюда описание объекта и main-метода):
val user = new User("Scala!!!")
println(user.name)
user.name = "M. Odersky"
println(user.name)

SCALA!!!
M. ODERSKY


Внимание, вывод: метод с именем <что-то>_= вызывается при использовании конструкции <объект>.<что-то> = <что-то другое>. Насколько я знаю в Scala это второй хак (первый — преобразование () в вызов метода apply), как Гвидо завещалc неявным преобразованием использования оператора в вызов метода.

Pattern-matching


Начать придётся немного издалека. В Scala есть так называемые сase classes(естественно и objects тоже). Они объявляются с ключевым словом case, после чего компилятор берёт на себя смелость сделать следующее:
  1. Создать функцию-конструктор с именем, совпадающим с клаcсом.
  2. Имплементировать в классе toString, equals, hashCode на основе аргументов конструктора.
  3. Создать селекторы для всех аргументов конструктора.

Вся эта магия открывает нам путь к использованию метода match. Давайте взглянем на пример:
abstract class User

case class KnownUser(val name: String) extends User

case class AnonymousUser() extends User

object Test {
 val users = List(KnownUser("Mark"), AnonymousUser(), KnownUser("Phil"))

 def register(user: User): Unit = user match {
   case KnownUser(name) => println("User " + name + " registered")
   case AnonymousUser()     => println("Anonymous user can't be registered")
 }

 def main(args: Array[String]) =
  users.foreach( register )
}

Итак, общая картина кода: есть абстрактный класс пользователя, есть два его казуальных потомка: известный и анонимный пользователи. Мы хотим зарегистрировать некий список пользователей на (здесь включаем фантазию) встречу. Для чего и используем pattern-matching, который позволяет нам определить разное поведение метода для разных типов объектов и обеспечивает выборку данных из этих объектов.

После столь жизненного примера можно немного теории о работе метода match. Для каждого выражения case он выполняет проверку на совпадения типа с классом шаблона и соответствия параметров конструктора шаблону. Шаблон в общем случае может включать в себя:
  1. Конструкторы других case-классов. Тут всё вполне рекурсивно, глубина вложенности шаблона ограничивается безумием программиста не ограничивается.
  2. Переменные шаблона. Они становятся доступны в теле функции вычисления результата.
  3. Символы _ обозначающие любое, неинтересующее нас значение.
  4. Литералы языка. Например 1 или "Hello".

Таким образом мы получаем инструмент, позволяющий описать получение некоторого значения из объекта на основе его структуры (класса) и/или хранимых в нём данных.

Люди знакомые с базовыми принципами ООП конечно сразу заметят, что проблема эта вполне решается использованием виртуальных функций (более того предлагаемый подход является не лучшей практикой). Однако их использование несёт в себе две трудности: во-первых усложняет поддержку кода при большом числе таких функций (нам ведь захочется регистрировать пользователей и на события, и в группы, и в блоги и т.п., что для каждого случая создавать виртуальный метод?), во-вторых не решает проблемы с тем, что объекты одного типа могут принципиально иметь разную структуру и не иметь возможности предоставлять некоторые данные.

На вторую проблему хочется обратить особое внимание. Как выглядил бы приведённый выше код в Java? Один класс, если пользователь анонимный выставляем в имени null и проверяем каждый раз (эстеты вроде меня заводят методы типаisAnonymous, состоящие из сравнения поля с тем же null). Проблемы налицо — неявно и небезопасно. Таких примеров великое множество, когда разные вариации структуры объектов объединяются в один класс, а неиспользуемые в конкретном случае забиваются null'ами, или того хуже придумывается значение по умолчанию. Scala позволяет явно описывать вариации структуры объектов, и предоставляет удобный механизм для работы с этими вариациями.

В заключение, пара мыслей насчёт того, когда данная техника может быть эффективно применена как замена виртуальным функциям:
  • У нас много функций. Да если у нас пара сотен операций, используемых по паре раз, зависящих от структуры и содержания объектов, система на основе case classes — pattern matching будет явно лучше поддерживаема.
  • У нас мало классов. match из пары выриантов всегда хорошо читаем.
  • У нас есть значительные вариации структуры объектов, которые однако надо хранить и обрабатывать единообразно.

Для всех пунктов действуют и обратные утвердения, например использование pattern matching для десятка классов не кажется мне хорошей идеей.

Вывод типов


Думаю, вы уже заметили, что в коде я указывал типы только при объявлении классов и методов. В блоках кода я их практически всегда опускал. Дело в том, что если программист не указывает тип явно, Scala пытается определить его из контекста. Например при инициализации значения константы в определении def s = "Scala" компилятор определит тип константы как строку. Всё это также работает на обобщённых типах, например фрагмент выше val users = List(KnownUser("Mark"), AnonymousUser(), KnownUser("Phil")), создаёт константу типа List[User], автоматически поднимаясь до подходящего уровня в иерархии наследования и используя его для параметризации типа-контейнера. На практике это означает, что можно значительно сэкономить на подобных объявлениях (для развлечения напишите делающий то же самое код на Java или C# :) ).

Заключение


Мда… К началу поста уже и скролить долго. Явно пора заканчивать. А сказать хотелось бы ещё про многое: про интереснейший механизм описания обобщённых классов, про неявные преобразования и то, что они на самом деле явные, ленивую инициализацию констант.

Мне и самому ещё только предстоит изучить модель многопоточности и своеобразный набор примитивов для её реаизации, разобраться с языковой поддержкой xml, поиграться с DSL-строением, посмотреть на их флагманский проект — Lift…

Однако всё равно осмелюсь сделать пару выводов:
  • Scala является весьма лаконичным и выразительным языком
  • Она предоставляет мощный инструментарий для создания простых и красивых программ