web/ru/basics2.textile (230 lines of code) (raw):
---
prev: basics.textile
next: collections.textile
title: Основы языка. Продолжение
layout: post
---
В этом уроке вы узнаете:
* "Метод apply":#apply
* "Объекты":#object
* "Функции, тоже являются Объектами":#fnobj
* "Пакеты":#package
* "Сопоставление с образцом":#match
* "Case классы":#caseclass
* "try-catch-finally":#exception
h2(#apply). Метод apply
Метод apply - это синтаксический сахар, который применяется для класса или объекта с единственной целью.
<pre>
object FooMaker {
def apply() = new Foo
}
scala> class Bar {
| def apply() = 0
| }
defined class Bar
scala> val bar = new Bar
bar: Bar = Bar@47711479
scala> bar()
res8: Int = 0
</pre>
Здесь наш экземпляр объекта выглядит так, будто мы просто вызываем метод, но это не так. Подробнее об этом позже!
h2(#object). Объекты
Объекты используются для хранения одного экземпляра класса. Чаще всего они используются с фабриками объектов.
<pre>
object Timer {
var count = 0
def currentCount(): Long = {
count += 1
count
}
}
</pre>
Как можно это использовать?
<pre>
scala> Timer.currentCount()
res0: Long = 1
</pre>
Классы и Объекты могут иметь похожие имена. В этом случае Объект называется 'Объект-спутник'(Companion Object). Чаще всего мы будем использовать Объекты-спутники с Фабриками объектов.
Далее представлен простой пример, который показывает, как можно использовать Объект-спутник, для того чтобы исключить необходимость в использовании ключевого слова 'new' для создания экземпляра объекта.
<pre>
class Bar(foo: String)
object Bar {
def apply(foo: String) = new Bar(foo)
}
</pre>
h2(#fnobj). Функции, тоже являются Объектами
В Scala, мы часто говорим об объектно-функциональном стиле. Что это значит? Чем на самом деле является Функция?
Функция - это набор трейтов. В частности, функция, которая принимает один аргумент является экземпляром трейта Function1. Этот трейт определяет метод <code>apply()</code>, который является синтаксическим сахаром, о нем мы узнали ранее, он позволяет вам вызывать объект, словно он является функцией.
<pre>
scala> object addOne extends Function1[Int, Int] {
| def apply(m: Int): Int = m + 1
| }
defined module addOne
scala> addOne(1)
res2: Int = 2
</pre>
Существует функция с 22 аргументами. Почему с 22? Это произвольное магическое число. Я никогда не нуждался в функции с более чем 22 аргументами.
Синтаксический сахар метода apply объединяет двойственность объектного и функционального стилей программирования. Вы можете передавать классы и использовать их в качестве функций, кроме этого функции могут быть просто экземплярами классов.
Означает ли это, что каждый раз, когда вы определяете метод в своем классе, вы фактически получаете экземпляр Function*? Нет, методы в классах - это просто методы. Методы-одиночки, определенные в REPL будут экземплярами Function*.
Классы могут расширять Function* и в этих случаях они могут быть вызваны при помощи ().
<pre>
scala> class AddOne extends Function1[Int, Int] {
| def apply(m: Int): Int = m + 1
| }
defined class AddOne
scala> val plusOne = new AddOne()
plusOne: AddOne = <function1>
scala> plusOne(1)
res0: Int = 2
</pre>
Запись <code>extends Function1[Int, Int]</code> мы можем переписать, используя <code>extends (Int => Int)</code>
<pre>
class AddOne extends (Int => Int) {
def apply(m: Int): Int = m + 1
}
</pre>
h2(#package). Пакеты
Вы можете организовывать ваш код, используя пакеты.
<pre>
package com.twitter.example
</pre>
В верхней части файла объявляется все, что будет в этом пакете.
Значения и функции не могут быть объявлены за пределами класса или объекта. Объекты представляют собой полезный инструмент для организации статических функций.
<pre>
package com.twitter.example
object colorHolder {
val BLUE = "Blue"
val RED = "Red"
}
</pre>
После этого у вас есть доступ к членам пакета напрямую
<pre>
println("the color is: " + com.twitter.example.colorHolder.BLUE)
</pre>
Обратите внимание, что Scala REPL говорит вам когда вы объявляете объект:
<pre>
scala> object colorHolder {
| val Blue = "Blue"
| val Red = "Red"
| }
defined module colorHolder
</pre>
Это дает вам небольшую подсказку, которую разработчики Scala используют для проектирования объектов, которые станут частью модульной системы Scala.
h2(#match). Сопоставление с образцом
Одна из самых часто используемых возможностей Scala.
Сопоставление значений
<pre>
val times = 1
times match {
case 1 => "one"
case 2 => "two"
case _ => "some other number"
}
</pre>
Сопоставление с использованием условий
<pre>
times match {
case i if i == 1 => "one"
case i if i == 2 => "two"
case _ => "some other number"
}
</pre>
Заметьте, как мы пытаемся поймать значение переменной 'i'.
Используемый знак <code>_</code> в последнем утверждении - это спецсимвол, он
гарантирует, что мы сможем отловить любое значение. В противном случае вы можете получить
ошибку времени выполнения, если вы попадаете в утверждение, которого не существует. Мы обсудим
это чуть позже.
*Смотрите также:* В Effective Scala описывается <a href="https://twitter.github.com/effectivescala/index-ru.html#Функциональное программирование-Сопоставление с образцом">когда использовать сопоставление с образцом </a> и <a href="https://twitter.github.com/effectivescala/index-ru.html#Форматирование-Сравнение с образцом">правила форматирования сопоставления с образцом</a>. В "Туре по Scala" также описывается <a href="https://www.scala-lang.org/node/120">Сопоставление с образцом</a>
h3. Сопоставление типов
Вы можете использовать <code>match</code>, чтобы управлять значениями типов различными способами.
<pre>
def bigger(o: Any): Any = {
o match {
case i: Int if i < 0 => i - 1
case i: Int => i + 1
case d: Double if d < 0.0 => d - 0.1
case d: Double => d + 0.1
case text: String => text + "s"
}
}
</pre>
h3. Сопоставление методов класса
Вспомните про наш калькулятор, который мы рассматривали ранее.
Давайте проведем классификацию по типам.
<pre>
def calcType(calc: Calculator) = calc match {
case calc.brand == "HP" && calc.model == "20B" => "financial"
case calc.brand == "HP" && calc.model == "48G" => "scientific"
case calc.brand == "HP" && calc.model == "30B" => "business"
case _ => "unknown"
}
</pre>
Ничего себе, как-то все слишком сложно. К счастью, Scala предоставляет некоторые полезные инструменты специально для этого случая.
h2(#caseclass). Case Классы
Case классы используются для удобного хранения и поиска соответствий по содержимому класса. Вы можете создавать их без использования 'new'.
<pre>
scala> case class Calculator(brand: String, model: String)
defined class Calculator
scala> val hp20b = Calculator("HP", "20b")
hp20b: Calculator = Calculator(hp,20b)
</pre>
У case классов есть метод ToString, работающий автоматически, и который опирается на аргументы конструктора.
<pre>
scala> val hp20b = Calculator("HP", "20b")
hp20b: Calculator = Calculator(hp,20b)
scala> val hp20B = Calculator("HP", "20b")
hp20B: Calculator = Calculator(hp,20b)
scala> hp20b == hp20B
res6: Boolean = true
</pre>
case классы могут иметь методы, как и обычные классы.
h6. Case Классы и сопоставление с образцом
case классы предназначены для использования вместе с сопоставлением с образцом. Давайте упростим наш классификатор из примера с калькулятором.
<pre>
val hp20b = Calculator("HP", "20B")
val hp30b = Calculator("HP", "30B")
def calcType(calc: Calculator) = calc match {
case Calculator("HP", "20B") => "financial"
case Calculator("HP", "48G") => "scientific"
case Calculator("HP", "30B") => "business"
case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
}
</pre>
А это другой способ для последнего сопоставления
<pre>
case Calculator(_, _) => "Calculator of unknown type"
</pre>
мы можем не объявлять, что это Calculator совсем.
<pre>
case _ => "Calculator of unknown type"
</pre>
Или мы можем связать найденное значение с другим именем
<pre>
case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)
</pre>
h2(#exception). Исключения
Исключения доступны в Scala при использовании синтаксиса try-catch-finally, который использует сопоставление с образцом.
<pre>
try {
remoteCalculatorService.add(1, 2)
} catch {
case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
} finally {
remoteCalculatorService.close()
}
</pre>
<code>try</code> тоже ориентирован на выражения
<pre>
val result: Int = try {
remoteCalculatorService.add(1, 2)
} catch {
case e: ServerIsDownException => {
log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
0
}
} finally {
remoteCalculatorService.close()
}
</pre>
Этот код не является примером прекрасного стиля программирования, а просто пример того, как try-catch-finally вычисляет выражения, подобно всему остальному в Scala.
Finally будет вызван после того, как исключение будет обработано.