Неявные преобразования — это мощная функция Scala, позволяющая пользователям предоставлять аргумент одного типа, как если бы он был другого типа, чтобы избежать шаблонного преобразования.
Обратите внимание, что в Scala 2 неявные преобразования также использовались для предоставления дополнительных членов запечатанным классам (см. Неявные классы). В Scala 3 мы рекомендуем использовать эту функциональность, определяя методы расширения вместо неявных преобразований (хотя стандартная библиотека по-прежнему полагается на неявные преобразования по историческим причинам).
Пример
Рассмотрим, например, метод findUserById
, принимающий параметр типа Long
:
def findUserById(id: Long): Option[User]
Для краткости опустим определение типа User
- это не имеет значения для нашего примера.
В Scala есть возможность вызвать метод findUserById
с аргументом типа Int
вместо ожидаемого типа Long
,
потому что аргумент будет неявно преобразован в тип Long
:
val id: Int = 42
findUserById(id) // OK
Этот код не упадет с ошибкой компиляции “type mismatch: expected Long
, found Int
”,
потому что есть неявное преобразование, которое преобразует аргумент id
в значение типа Long
.
Детальное объяснение
В этом разделе описывается, как определять и использовать неявные преобразования.
Определение неявного преобразования
В Scala 2 неявное преобразование из типа S
в тип T
определяется неявным классом T
,
который принимает один параметр конструктора типа S
, неявное значение
типа функции S => T
или неявный метод, преобразуемый в значение этого типа.
Например, следующий код определяет неявное преобразование из Int
в Long
:
import scala.language.implicitConversions
implicit def int2long(x: Int): Long = x.toLong
Это неявный метод, преобразуемый в значение типа Int => Long
.
См. раздел “Остерегайтесь силы неявных преобразований” ниже для объяснения пункта import scala.language.implicitConversions
в начале.
В Scala 3 неявное преобразование типа S
в тип T
определяется given
экземпляром
типа scala.Conversion[S, T]
.
Для совместимости со Scala 2 его также можно определить неявным методом (подробнее читайте во вкладке Scala 2).
Например, этот код определяет неявное преобразование из Int
в Long
:
given int2long: Conversion[Int, Long] with
def apply(x: Int): Long = x.toLong
Как и другие given определения, неявные преобразования могут быть анонимными:
given Conversion[Int, Long] with
def apply(x: Int): Long = x.toLong
Используя псевдоним, это можно выразить более кратко:
given Conversion[Int, Long] = (x: Int) => x.toLong
Использование неявного преобразования
Неявные преобразования применяются в двух случаях:
- Если выражение
e
имеет типS
иS
не соответствует ожидаемому типу выраженияT
. - В выборе
e.m
сe
типаS
, гдеS
не определяетm
(для поддержки методов расширения в стиле Scala-2).
В первом случае ищется конверсия c
, применимая к e
и тип результата которой соответствует T
.
В примере выше, когда мы передаем аргумент id
типа Int
в метод findUserById
,
вставляется неявное преобразование int2long(id)
.
Во втором случае ищется преобразование c
, применимое к e
и результат которого содержит элемент с именем m
.
Примером является сравнение двух строк "foo" < "bar"
.
В этом случае String
не имеет члена <
, поэтому вставляется неявное преобразование Predef.augmentString("foo") < "bar"
(scala.Predef
автоматически импортируется во все программы Scala.).
Как неявные преобразования становятся доступными?
Когда компилятор ищет подходящие преобразования:
- во-первых, он смотрит в текущую лексическую область
- неявные преобразования, определенные в текущей области или во внешних областях
- импортированные неявные преобразования
- неявные преобразования, импортированные с помощью импорта подстановочных знаков (только в Scala 2)
- затем он просматривает сопутствующие объекты, связанные с типом аргумента
S
или ожидаемым типомT
. Сопутствующие объекты, связанные с типомX
:- сам объект-компаньон
X
- сопутствующие объекты, связанные с любым из унаследованных типов
X
- сопутствующие объекты, связанные с любым аргументом типа в
X
- если
X
- это внутренний класс, внешние объекты, в которые он встроен
- сам объект-компаньон
Например, рассмотрим неявное преобразование fromStringToUser
, определенное в объекте Conversions
:
import scala.language.implicitConversions
object Conversions {
implicit def fromStringToUser(name: String): User = (name: String) => User(name)
}
object Conversions:
given fromStringToUser: Conversion[String, User] = (name: String) => User(name)
Следующие операции импорта эквивалентно передают преобразование в область действия:
import Conversions.fromStringToUser
// или
import Conversions._
import Conversions.fromStringToUser
// или
import Conversions.given
// или
import Conversions.{given Conversion[String, User]}
Обратите внимание, что в Scala 3 импорт с подстановочными знаками (т.е. import Conversions.*
)
не импортирует given определения.
Во вводном примере преобразование из Int
в Long
не требует импорта, поскольку оно определено в объекте Int
,
который является сопутствующим объектом типа Int
.
Дополнительная литература: Где Scala ищет неявные значения? (в Stackoverflow).
Остерегайтесь силы неявных преобразований
Поскольку неявные преобразования могут иметь подводные камни, если используются без разбора, компилятор предупреждает при компиляции определения неявного преобразования.
Чтобы отключить предупреждения, выполните одно из следующих действий:
- Импорт
scala.language.implicitConversions
в область определения неявного преобразования - Вызвать компилятор с командой
-language:implicitConversions
Предупреждение не выдается, когда компилятор применяет преобразование.
Поскольку неявные преобразования могут иметь подводные камни, если они используются без разбора, компилятор выдает предупреждение в двух случаях:
- при компиляции определения неявного преобразования в стиле Scala 2.
- на стороне вызова, где given экземпляр
scala.Conversion
вставляется как конверсия.
Чтобы отключить предупреждения, выполните одно из следующих действий:
- Импортировать
scala.language.implicitConversions
в область:- определения неявного преобразования в стиле Scala 2
- стороны вызова, где given экземпляр
scala.Conversion
вставляется как конверсия.
- Вызвать компилятор с командой
-language:implicitConversions
Contributors to this page:
Contents
- Введение
- Возможности Scala
- Почему Scala 3?
- Почувствуй Scala
- Пример 'Hello, World!'
- REPL
- Переменные и типы данных
- Структуры управления
- Моделирование данных
- Методы
- Функции первого класса
- Одноэлементные объекты
- Коллекции
- Контекстные абстракции
- Верхнеуровневые определения
- Обзор
- Первый взгляд на типы
- Интерполяция строк
- Структуры управления
- Моделирование предметной области
- Инструменты
- Моделирование ООП
- Моделирование ФП
- Методы
- Особенности методов
- Main методы в Scala 3
- Обзор
- Функции
- Анонимные функции
- Параметры функции
- Eta расширение
- Функции высшего порядка
- Собственный map
- Создание метода, возвращающего функцию
- Обзор
- Пакеты и импорт
- Коллекции в Scala
- Типы коллекций
- Методы в коллекциях
- Обзор
- Функциональное программирование
- Что такое функциональное программирование?
- Неизменяемые значения
- Чистые функции
- Функции — это значения
- Функциональная обработка ошибок
- Обзор
- Типы и система типов
- Определение типов
- Параметризованные типы
- Пересечение типов
- Объединение типов
- Алгебраические типы данных
- Вариантность
- Непрозрачные типы
- Структурные типы
- Зависимые типы функций
- Другие типы
- Контекстные абстракции
- Методы расширения
- Параметры контекста
- Контекстные границы
- Given импорты
- Классы типов
- Многостороннее равенство
- Неявное преобразование типов
- Обзор
- Параллелизм
- Scala утилиты
- Сборка и тестирование проектов Scala с помощью Sbt
- Рабочие листы
- Взаимодействие с Java