Евгений Колесников 4 жил өмнө
parent
commit
8421e15ac7

+ 405 - 0
articles/android_studio.md

@@ -0,0 +1,405 @@
+# Первый проект в Android Studio
+
+>Основано на [этих лекциях](https://metanit.com/java/android/1.2.php) (адаптировано под Kotlin)
+
+## Создание первого приложения, структура проекта
+
+Cоздадим первое приложение в среде Android Studio для операционной системы Android. Откроем Android Studio и на начальном экране выберем пункт New Project (либо, если какой-то проект уже открыт, через главное меню File - New Project)
+
+При создании проекта Android Studio вначале предложит нам выбрать шаблон проекта:
+
+>Внешний вид может немного отличаться, т.к. я пишу лекции под Linux
+
+![](../img/04010.png)
+
+Android Studio предоставляет ряд шаблонов для различных ситуаций. Выберем в этом списке шаблон Empty Activity, который предосавляет самый простейший фукционал, необходимый для начала, и нажмем на кнопку Next.
+
+После этого отобразится окно настроек нового проекта:
+
+![](../img/04011.png)
+
+В окне создания нового проекта мы можем установить его начальные настройки:
+
+* В поле **Name** вводится название приложения.
+
+* В поле **Package Name** указывается имя пакета, где будет размещаться главный класс приложения. В для тестовых проектов это значение не играет ольшого значения.
+
+* В поле **Save Location** установливается расположение файлов проекта на жестком диске. Можно оставить значение по умолчанию.
+
+* В поле **Language** указывается используемый язык программирования, по умолчанию в этом поле стоит Kotlin (поддерживается и Java).
+
+* В поле **Minimum SDK** указывается самая минимальная поддерживаемая версия SDK. Оставим значение по умолчанию - API 21: Android 5.0 (Lollipop), которая означает, что наше приложение можно будет запустить начиная с Android 5.0, а это 94% устройств. На более старых устройствах запустить будет нельзя.
+
+Стоит учитывать, что чем выше версия SDK, тем меньше диапазон поддерживаемых устройств.
+
+Далее нажмем на кнопку Finish, и Android Studio создаст новый проект (процесс создания достаточно долгий):
+
+![](../img/04012.png)
+
+Вначале вкратце рассмотрим структуру проекта, что он уже имеет по умолчанию
+
+![](../img/04013.png)
+
+Проект Android может состоять из различных модулей. По умолчанию, когда мы создаем проект, создается один модуль - **app**. Модуль имеет три подпапки:
+
+* **manifests**: хранит файл манифеста `AndroidManifest.xml`, который описывает конфигурацию приложения и определяет каждый из компонентов данного приложения.
+
+* **java**: хранит файлы кода (всегда называется Java, хотя мы и пишем на Kotlin), которые структурированы по отдельным пакетам. Так, в папке ru.yotc.myapplication (название которого было указано на этапе создания проекта) имеется по умолчанию файл `MainActivity.kt` с кодом на языке Kotlin, который представляет класс MainActivity, запускаемый по умолчанию при старте приложения
+
+* **res**: содержит используемые в приложении ресурсы. Все ресурсы разбиты на подпапки.
+
+    * папка **drawable** предназначена для хранения изображений, используемых в приложении
+
+    * папка **layout** предназначена для хранения файлов, определяющих графический интерфейс. По умолчанию здесь есть файл `activity_main.xml`, который определяет интерфейс для класса MainActivity в виде xml
+
+    * папки **mipmap** содержат файлы изображений, которые предназначены для создания иконки приложения при различных разрешениях экрана.
+
+    * папка **values** хранит различные xml-файлы, содержащие коллекции ресурсов - различных данных, которые применяются в приложении. По умолчанию здесь есть два файла и одна папка:
+
+        * файл **colors.xml** хранит описание цветов, используемых в приложении
+
+        * файл **strings.xml** содержит строковые ресурсы, используемые в приложении
+
+        * папки **themes** хранит две темы приложения - для светлую (дневную) и темную (ночную)
+
+Отдельный элемент **Gradle Scripts** содержит ряд скриптов, которые используются при построении приложения.
+
+Во всей этой структуре следует выделить файл **MainActivity.kt**, который открыт в Android Studio и который содержит логику приложения и собственно с него начинается выполнение приложения. И также выделим файл **activity_main.xml**, который определяет графический интерфейс - по сути то, что увидит пользователь на своем устройстве после загрузки приложения.
+
+## Запуск проекта
+
+Созданный выше проект уже содержит некоторый примитивный функционал. Правда, этот функционал почти ничего не делает, только выводит на экран строку "Hello world!". Тем не менее это уже фактически приложение, которое мы можем запустить.
+
+Для запуска и тестирования приложения мы можем использовать эмуляторы или реальные устройства. Но в идеале лучше тестировать на реальных устройствах. К тому же эмуляторы требуют больших аппаратных ресурсов, и не каждый компьютер может потянуть требования эмуляторов. А для использования мобильного устройства для тестирования может потребоваться разве что установить необходимый драйвер.
+
+Запустим проект, нажав на зеленую стрелочку на панели инструментов.
+
+![](../img/04014.png)
+
+И после запуска мы увидим наше приложение на экране, которое просто выводит на экран строку Hello World.
+
+![](../img/04015.png)
+
+Но почему у нас выводится именно эта строка? Почему у нас вообще создается именно такой визуальный интерфейс?
+
+Выполнение приложения Android по умолчанию начинается с класса **MainActivity**, который по умолчанию открыт в Android Studio:
+
+```kt
+package ru.yotc.myapplication
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+
+class MainActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+    }
+}
+```
+
+Каждый отдельный экран или страница в приложении описывается таким понятием как **activity**. В литературе могут использоваться различные термины: экран, страница, активность. В данном случае я буду использовать понятие "activity". Так вот, если мы запустим приложение, то на экране мы по сути увидим определенную activity, которая предсталяет данный интерфейс.
+
+Класс **MainActivity** по сути представляет обычный класс, в начале которого идет определение пакета данного класса:
+
+```kt
+package ru.yotc.myapplication
+```
+
+Далее идет импорт классов из других пакетов, функциональность которых используется в MainActivity:
+
+```kt
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+```
+
+По умолчанию **MainActivity** наследуется от класса **AppCompatActivity**, который выше подключен с помощью директивы импорта. Класс **AppCompatActivity** по сути представляет отдельный экран (страницу) приложения или его визуальный интерфейс. И **MainActivity** наследует весь этот функционал.
+
+По умолчанию **MainActivity** содержит только один метод *onCreate()*, в котором фактически и создается весь интерфейс приложения:
+
+```kt
+override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    setContentView(R.layout.activity_main)
+}
+```
+
+В метод *setContentView()* передается ресурс разметки графического интерфейса:
+
+```kt
+setContentView(R.layout.activity_main)
+```
+
+Именно здесь и решается, какой именно визуальный интерфейс будет иметь **MainActivity**. Но что в данном случае представляет ресурс **R.layout.activity_main**? Это файл `activity_main.xml` из папки `res/layout` (в принципе можно заметить, что название ресурса соответствует названию файла), который также по умолчанию открыт в Android Studio:
+
+![](../img/04016.png)
+
+## Файл activity_main.xml
+
+Android Studio позволяет работать с визуальным интерфейсом как в режиме кода, так и в графическом режиме. Так, по умолчанию файл открыт в графическом режиме, и мы наглядно можем увидеть, как у нас примерно будет выглядеть экран приложения. И даже набросать с панели инструментов какие-нибудь элементы управления, например, кнопки или текстовые поля.
+
+Но также мы можем работать с файлом в режиме кода, поскольку `activity_main.xml` - это обычный текстовый файл с разметкой xml. Для переключения к коду нажмём на кнопку **Code** над графическим представлением. (Дополнительно с помощью кнопки **Split** можно переключиться на комбинированное представление код + графический дизайнер)
+
+Здесь мы увидим, что на уровне кода файл **activity_main.xml** содержит следующую разметку:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Hello World!"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+```
+
+Весь интерфейс представлен элементом-контейнером **androidx.constraintlayout.widget.ConstraintLayout**. ConstraintLayout позволяет расположить вложенные элементы в определенных местах экрана. 
+
+Атрибут **android:layout_width** определяет ширину контейнера.
+
+Значением атрибута android:layout_width является "match_parent". Это значит, что элемент (ConstraintLayout) будет растягиваться по всей ширине контейнера (экрана устройства).
+
+Атрибут **android:layout_height="match_parent"** определяет высоту контейнера. Значение "match_parent" указывает, что ConstraintLayout будет растягивается по всей высоте контейнера (экрана устройства).
+
+Атрибут **tools:context** определяет, какой класс activity (экрана приложения) связан с текущим определением интерфейса. В данном случае это класс **MainActivity**. Это позволяет использовать в Android Studio различные возможности в режиме дизайнера, которые зависят от класса activity.
+
+Внутри контейнера **ConstraintLayout** расположен текстовый блок:
+
+**TextView**
+
+* **android:layout_width** устанавливает ширину виджета. Значение wrap_content задает для виджета величину, достаточную для отображения в контейнере.
+
+* **android:layout_height** устанавливает высоту виджета. Значение wrap_content аналогично установке ширины задает для виджета высоту, достаточную для отображения в контейнере
+
+* **android:text** устанавливает текст, который будет выводиться в TextView (в данном случае это строка "Hello World!")
+
+* **app:layout_constraintLeft_toLeftOf="parent"**: указывает, что левая граница элемента будет выравниваться по левой стороне контейнера **ConstraintLayout**
+
+* **app:layout_constraintTop_toTopOf="parent"**: указывает, что верхняя граница элемента будет выравниваться по верхней стороне контейнера **ConstraintLayout**
+
+* **app:layout_constraintRight_toRightOf="parent"**: указывает, что правая граница элемента будет выравниваться по правой стороне контейнера ConstraintLayout
+
+* **app:layout_constraintBottom_toBottomOf="parent"**: указывает, что нижняя граница элемента будет выравниваться по нижней стороне контейнера ConstraintLayout
+
+Стоит отметить, что последние четыре атрибута вместе будут приводить к расположению **TextView** по центру экрана.
+
+Таким образом, при запуске приложения сначала запускается класс **MainActivity**, который в качестве графического интерфейса устанавливает разметку из файла `activity_main.xml`. И поскольку в этой разметке прописан элемент **TextView**, который представляет некоторый текст, то мы и увидим его текст на экране.
+
+## Определение интерфейса в файле XML. Файлы layout
+
+Как правило, для определения визуального интерфейса в проектах под Android используются специальные файлы xml. Эти файлы являются ресурсами разметки и хранят определение визуального интерфейса в виде кода XML. Подобный подход напоминает создание веб-сайтов, когда интерфейс определяется в файлах html, а логика приложения - в коде javascript.
+
+Объявление пользовательского интерфейса в файлах XML позволяет отделить интерфейс приложения от кода. Что означает, что мы можем изменять определение интерфейса без изменения кода. Например, в приложении могут быть определены разметки в файлах XML для различных ориентаций монитора, различных размеров устройств, различных языков и т.д. Кроме того, объявление разметки в XML позволяет легче визуализировать структуру интерфейса и облегчает отладку.
+
+Файлы разметки графического интерфейса располагаются в проекте в каталоге `res/layout`. По умолчанию при создании проекта с пустой activity уже есть один файл ресурсов разметки **activity_main.xml**
+
+В файле определяются все графические элементы и их атрибуты, которые составляют интерфейс. При создании разметки в XML следует соблюдать некоторые правила: каждый файл разметки должен содержать один корневой элемент, который должен представлять объект **View** или **ViewGroup**.
+
+В нашем случае корневым элементом является элемент **ConstraintLayout**, который содержит элемент **TextView**.
+
+При компиляции каждый XML-файл разметки компилируется в ресурс View. Загрузка ресурса разметки осуществляется в методе Activity.onCreate. Чтобы установить разметку для текущего объекта activity, надо в метод setContentView() в качестве параметра передать ссылку на ресурс разметки.
+
+```kt
+setContentView(R.layout.activity_main)
+```
+
+Для получения ссылки на ресурс в коде необходимо использовать выражение `R.layout.[название_ресурса]`. Название ресурса layout будет совпадать с именем файла.
+
+Но у нас может быть и несколько различных ресурсов layout. Как правило, каждый отдельный класс Activity использует свой файл layout. Либо для одного класса Activity может использоваться сразу несколько различных файлов layout.
+
+### Добавление файла layout
+
+К примеру, добавим в проект новый файл разметки интерфейса. Для этого нажмем на папку `res/layout` правой кнопкой мыши и в появившемся меню выберем пункт **New -> Layout Resource File**
+
+После этого в специальном окошке будет предложено указать имя и корневой элемент для файла layout:
+
+![](../img/04017.png)
+
+В качестве названия укажем *second_layout*. Все остальные настройки оставим по умолчанию:
+
+* в поле **Root element** указывается корневой элемент. По умолчанию это androidx.constraintlayout.widget.ConstraintLayout.
+
+* поле **Source set** указывает, куда помещать новый файл. По умолчанию это main - область проекта, с которой мы собственно работаем при разаботке приложения.
+
+* поле **Directory name** указывает папку в рамках каталога, выбранного в предыдущей опции, в который собственно помещается новый файл. По умолчанию для файлов с разметкой интерфейса это layout.
+
+После этого в папку `res/layout` будет добавлен новый файл **second_layout.xml**, с которым мы можем работать точно также, как и с **activity_main.xml**.
+
+При создании он содержит только корневой элемент:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+```
+
+Добавим в него текстовое поле:
+
+```xml
+<TextView
+    android:id="@+id/header"
+    android:text="Welcome to Android"
+    android:textSize="26sp"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
+```
+
+Здесь определено текстовое поле **TextView**, которое имеет следующие атрибуты:
+
+* **android:id** - идентификатор элемента, через который мы сможем ссылаться на него в коде. В записи *android:id="@+id/header"* символ @ указывает XML-парсеру использовать оставшуюся часть строки атрибута как идентификатор. А знак + означает, что если для элемента не определен id со значением header, то его следует определить.
+
+* **android:text** - текст элемента - на экран будет выводиться строка "Welcome to Android".
+
+* **android:textSize** - высота шрифта (здесь 26 единиц)
+
+* **android:layout_width** - ширина элемента. Значение "match_parent" указывает, что элемент будет растягиваться по всей ширине контейнера ConstraintLayout
+
+* **android:layout_height** - высота элемента. Значение "match_parent" указывает, что элемент будет растягиваться по всей высоте контейнера ConstraintLayout
+
+Применим этот файл в качестве определения графического интерфейса в классе MainActivity:
+
+```kt
+override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    // setContentView(R.layout.activity_main)
+    setContentView(R.layout.second_layout)
+}
+```
+
+Файл интерфейса называется `second_layout.xml`, поэтому по умолчанию для него будет создаваться ресурс **R.layout.second_layout**. Соответственно, чтобы его использовать, мы передаем его в метода setContentView. В итоге мы увидим на экране следующее:
+
+![](../img/04018.png)
+
+### Получение и управлене визуальными элементами в коде
+
+Выше определенный элемент **TextView** имеет один очень важный атрибут - **id** или **идентификатор элемента**. Этот идентификатор позволяет обращаться к элементу, который определен в файле xml, из кода. Например, перейдем к классу MainActivity и изменим его код:
+
+```kt
+...
+setContentView(R.layout.second_layout)
+
+val textView = findViewById<TextView>(R.id.header)
+textView.text = "Hello from Kotlin!"
+```
+
+Для получения элементов по **id** класс Activity имеет метод *findViewById()*. В этот метод передается идентификатор ресурса в виде `R.id.[идентификатор_элемента]`. Этот метод возвращает объект **View** - объект базового класса для всех элементов, поэтому результат метода еще необходимо привести к типу **TextView**.
+
+Выше мы приводили тип указывая нужный в угорвых скобках метода, но можно явно определить тип переменной:
+
+```kt
+val textView: TextView = findViewById(R.id.header)
+```
+
+Далее мы можем что-то сделать с этим элементом, в данном случае изменяем его текст.
+
+Причем что важно, получение элемента происходит после того, как в методе setContentView была установлена разметка, в которой этот визуальный элемент был определен.
+
+И если мы запустим проект, то увидим, что TextView выводит новый текст:
+
+![](../img/04019.png)
+
+### Определение размеров
+
+При разработке приложений под Android мы можем использовать различные типы измерений:
+
+* px: пиксели текущего экрана. Однако эта единица измерения не рекомендуется, так как реальное представление внешнего вида может изменяться в зависимости от устройства; каждое устройство имеет определенный набор пикселей на дюйм, поэтому количество пикселей на экране может также меняться
+
+* dp: (device-independent pixels) независимые от плотности экрана пиксели. Абстрактная единица измерения, основанная на физической плотности экрана с разрешением 160 dpi (точек на дюйм). В этом случае 1dp = 1px. Если размер экрана больше или меньше, чем 160dpi, количество пикселей, которые применяются для отрисовки 1dp соответственно увеличивается или уменьшается. Например, на экране с 240 dpi 1dp=1,5px, а на экране с 320dpi 1dp=2px. Общая формула для получения количества физических пикселей из dp: px = dp * (dpi / 160)
+
+* sp: (scale-independent pixels) независимые от масштабирования пиксели. Допускают настройку размеров, производимую пользователем. Рекомендуются для работы со шрифтами.
+
+* pt: 1/72 дюйма, базируются на физических размерах экрана
+
+* mm: миллиметры
+
+* in: дюймы
+
+Предпочтительными единицами для использования являются **dp**. Это связано с тем, что мир мобильных устройств на Android сильно фрагментирован в плане разрешения и размеров экрана.
+
+### Ширина и высота элементов
+
+Все визуальные элеметы, которые мы используем в приложении, как правило, упорядочиваются на экране с помощью контейнеров. В Android подобными контейнерами служат такие классы как **RelativeLayout**, **LinearLayout**, **GridLayout**, **TableLayout**, **ConstraintLayout**, **FrameLayout**. Все они по разному располагают элементы и управляют ими, но есть некоторые общие моменты при компоновке визуальных компонентов, которые мы сейчас рассмотрим.
+
+Для организации элементов внутри контейнера используются параметры разметки. Для их задания в файле xml используются атрибуты, которые начинаются с префикса layout_. В частности, к таким параметрам относятся атрибуты **layout_height** и **layout_width**, которые используются для установки размеров и могут использовать одну из следующих опций:
+
+* Растяжение по всей ширине или высоте контейнера с помощью значения **match_parent** (для всех контейнеров кроме ConstraintLayout) или 0dp (для ConstraintLayout)
+
+* Растяжение элемента до тех границ, которые достаточны, чтобы вместить все его содержимое с помощью значения **wrap_content**
+
+* Точные размеры элемента, например 96 dp
+
+**match_parent**
+
+Установка значения **match_parent** позволяет растянуть элемент по всей ширине или высоте контейнера. Стоит отметить, что данное значение применяется ко всем контейнерам, кроме ConstraintLayout. Например, рястянем элемент TextView по всей ширине и высоте контейнера LinearLayout:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+ 
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:text="Hello World!"
+        android:textSize="30sp"
+        android:background="#e0e0e0" />
+     
+</LinearLayout>
+```
+
+Контейнер самого верхнего уровня, в качестве которого в данном случае выступает **LinearLayout**, для высоты и ширины имеет значение **match_parent**, то есть он будет заполнять всю область для activity - как правило, весь экран.
+
+И **TextView** также принимает подобные атрибуты. Значение **android:layout_width="match_parent"** обеспечивает растяжение по ширине, а **android:layout_height="match_parent"** - по вертикали. Для наглядности в **TextView** применяет атрибут **android:background**, который задает фон элемента и в данном случае окрашивает фон в цвет "#e0e0e0", благодаря чему мы можем увидеть занимаемую им область.
+
+![](../img/04020.png)
+
+**wrap_content**
+
+Значение **wrap_content** устанавливает те значения для ширины или высоты, которые необходимы, чтобы разместить на экране содержимое элемента:
+
+![](../img/04021.png)
+
+## Внутренние и внешние отступы
+
+Параметры разметки позволяют задать отступы как от внешних границ элемента до границ контейнера, так и внутри самого элемента между его границами и содержимым.
+
+**Padding**
+
+Для установки внутренних отступов применяется атрибут **android:padding**. Он устанавливает отступы контента от всех четырех сторон контейнера. Можно устанавливать отступы только от одной стороны контейнера, применяя следующие атрибуты: **android:paddingLeft**, **android:paddingRight**, **android:paddingTop** и **android:paddingBottom**.
+
+Стоит отметить, что вместо атрибутов **android:paddingLeft** и **android:paddingRight** можно применять атрибуты **android:paddingStart** и **android:paddingEnd**, которые разработаны специально адаптации приложения для работы как для языков с левосторонней ориентацией, так и правосторонней ориентацией (арабский, фарси).
+
+**Margin**
+
+Для установки внешних отступов используется атрибут **layout_margin**. Данный атрибут имеет модификации, которые позволяют задать отступ только от одной стороны: **android:layout_marginBottom**, **android:layout_marginTop**, **android:layout_marginLeft** и **android:layout_marginRight**.
+
+Дальше ссылки на несколько оригинальных лекций
+
+## [ConstraintLayout](https://metanit.com/java/android/3.8.php)
+
+## [Размеры элементов в ConstraintLayout](https://metanit.com/java/android/3.7.php)
+
+## [Цепочки элементов в ConstraintLayout](https://metanit.com/java/android/3.15.php)
+
+## [LinearLayout](https://metanit.com/java/android/3.2.php)
+
+На этом пока с теорией закончим.
+
+Практическое задание:
+
+Используя **LinearLayout** нарисовать разметку для калькулятора (за основу взять калькулятор Windows)

+ 8 - 1
articles/cs_edit_product.md

@@ -3,7 +3,7 @@
 </a></td><td style="width: 20%;">
 <a href="../readme.md">Содержание
 </a></td><td style="width: 40%;">
-<a href="../articles/cs_edit_product.md">Создание, изменение продукции
+<a href="../articles/kotlin.md">Основы языка Kotlin
 </a></td><tr></table>
 
 # Добавление/редактирование продукции
@@ -393,3 +393,10 @@
 
         Тут по идее надо делать запрос к базе, но у нас есть метод получения списка продукции и мы можем искать в нём используя LINQ-запросы
 
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/cs_coloring.md">Подсветка элементов по условию. Дополнительные выборки.Массовая смена цены продукции.
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/kotlin.md">Основы языка Kotlin
+</a></td><tr></table>

+ 4034 - 0
articles/kotlin.md

@@ -0,0 +1,4034 @@
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/cs_edit_product.md">Создание, изменение продукции
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/kotlin.md">Основы языка Kotlin
+</a></td><tr></table>
+
+# Основы языка Kotlin
+
+## Переменные
+
+Для хранения данных в программе в **Kotlin**, как и в других языках программирования, применяются переменные. 
+
+Каждая переменная характеризуется определенным именем, типом данных и значением. Имя переменной представляет поизвольный идентификатор, который может содержать алфавитно-цифровые символы или символ подчеркивания и должен начинаться либо с алфавитного символа, либо со знака подчеркивания. Для определения переменной можно использовать либо ключевое слово **val**, либо ключевое слово **var**.
+
+Например, определим переменную *age*:
+
+```kt
+val age: Int
+```
+
+Вначале идет слово **val** или **var**, затем имя переменной и через двоеточие тип переменной. То есть в данном случае объявлена переменная *age*, которая имеет тип **Int**. Тип **Int** говорит о том, что переменная будет содержать целочисленные значения.
+
+После определения переменной ей можно присвоить значение:
+
+```kt
+val age: Int
+age = 23
+println(age)
+```
+
+Для присвоения значения переменной используется знак равно. Затем мы можем производить с переменной различные операции. Например, в данном случае с помощью функции *println* значение переменной выводится на консоль. И при запуске этой программы на консоль будет выведено число 23.
+
+Присвоение значения переменной должно производиться только после ее объявления. И также мы можем сразу присвоить переменной начальное значение при ее объявлении. Такой прием называется инициализацией. При инициализации можно опустить указание типа, тип переменной будет задан по типу присваимого значения:
+
+```kt
+val age: Int = 23 
+println(age)
+```
+
+### Изменяемые и неизменяемые переменные
+
+Выше было сказано, что переменные могут объявляться как с помощью слова **val**, так и с помощью слова **var**. В чем же разница между двумя этими способами?
+
+С помощью ключевого слова **val** определяется неизменяемая переменная (immutable variable). То есть мы можем присвоить значение такой переменной только один раз, но изменить его после первого присвоения мы уже не сможем. Например, в следующем случае мы получим ошибку:
+
+```kt
+val age: Int
+age = 23        // здесь норм - первое присвоение
+age = 56        // здесь ошибка - переопределить значение переменной нельзя
+println(age)
+```
+
+В этом плане подобные переменные похожи на константы в других языках программирования.
+
+А у переменной, которая определена с помощью ключевого слова **var** мы можем многократно менять значения (mutable variable):
+
+```kt
+var age: Int
+age = 23
+println(age)
+age = 56
+println(age)
+```
+
+Поэтому если не планируется изменять значение переменной в программе, то лучше определять ее с ключевым словом **val**.
+
+## Типы данных
+
+В **Kotlin** каждая переменная имеет определенный тип. Тип данных определяет, какие операции можно производить с данными этого типа.
+
+В отличие от языка **Java** и ряда других языков **Kotlin** не имеет встроенных примитивных типов. Все типы представляют определенные классы.
+
+### Числовые типы
+
+* **Byte**: хранит целое число от -128 до 127 и занимает 1 байт
+* **Short**: хранит целое число от -32768 до 32767 и занимает 2 байта
+* **Int**: хранит целое число от -2147483648 до 2147483647 и занимает 4 байта
+* **Long**: хранит целое число от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 и занимает 8 байт
+* **Float**: хранит число с плавающей точкой от -3.4*1038 до 3.4*1038 и занимает 4 байта
+* **Double**: хранит число с плавающей точкой от ±5.0*10-324 до ±1.7*10308 и занимает 8 байта.
+
+### Литералы
+
+Литералы представляют неизменяемые значения (иногда их еще называют константами). Литералы можно передавать переменным в качестве значения. Литералы бывают логическими, целочисленными, вещественными, символьными и строчными.
+
+Любые литералы, которые представляют целые числа, воспринимаются как данные типа **Int**.
+
+```kt
+// переменная age будет иметь тип Int
+val age: Int = 45
+```
+
+Литерал `45` представляет целое число и является значением типа **Int**. Если же мы хотим явно указать, что число представляет значение типа **Long**, то следует использовать суффикс "L":
+
+```kt
+// тут Котлин автоматически приведет к объявленному типу
+val age: Long = 45  
+
+// а вот тут мы используем суффикс, чтобы явно указать тип
+val age = 45L       
+```
+
+Аналогично все числа с плавающей точкой (которые содержат точку в качестве разделителя целой и дробной части) рассматриваются как числа типа **Double**:
+
+```kt
+val weight: Double = 68.71
+```
+
+Если мы хотим указать, что данные будут представлять тип Float, то необходимо использовать суффикс F:
+
+```kt
+val weight: Float = 68.71F
+```
+
+Кроме чисел в десятичной системе мы можем определять числа в двоичной и шестнадцатеричной системах.
+
+Шестнадцатеричная запись числа начинается с `0x`, затем идет набор символов от `0` до `F`, которые представляют число:
+
+```kt
+val age: Int = 0x0A1    // 161
+```
+
+Двоичная запись числа предваряется символами 0b, после которых идет последовательность из нулей и единиц:
+
+```kt
+val a: Int = 0b0101    // 5
+val b: Int = 0b1011     // 11
+```
+
+### Нижние подчеркивания в числовых литералах (начиная с версии 1.1)
+
+Вы можете использовать нижние подчеркивания, чтобы сделать числовые константы более читаемыми:
+
+```kt
+val oneMillion = 1_000_000
+val creditCardNumber = 1234_5678_9012_3456L
+val socialSecurityNumber = 999_99_9999L
+val hexBytes = 0xFF_EC_DE_5E
+val bytes = 0b11010010_01101001_10010100_10010010
+```
+
+### Выведение типа
+
+**Kotlin** позволяет выводить тип переменной на основании данных, которыми переменная инициализируется. Поэтому при инициализации переменной тип можно опустить:
+
+```kt
+val age = 5
+```
+
+В данном случае компилятор увидит, что переменной присваивается значение типа **Int**, поэтому переменная age будет представлять тип **Int**.
+
+Соответственно если мы присваиваем переменной строку, то такая переменная будет иметь тип **String**.
+
+```kt
+val name = "Tom"
+```
+
+Однако при этом нам обязательно надо инициализировать переменную некоторым значением. То есть нельзя сначала объявить переменную, а потом где-то в программе присвоить ей какое-то значение:
+
+```kt
+val age     // Ошибка, переменная не инициализирована
+age = 5 
+```
+
+### Логический тип Boolean
+
+Тип **Boolean** может хранить одно из двух значений: **true** (истина) или **false** (ложь).
+
+```kt
+val a = true
+val b = false
+```
+
+### Символы
+
+Символьные данные представлены типом **Char**. Он представляет отдельный символ, который заключается в одинарные кавычки.
+
+```kt
+val a = 'A'
+val b = 'B'
+val c = 'T'
+```
+
+Также тип **Char** может представлять специальные последовательности, которые интерпретируются особым образом:
+
+* **\t**: табуляция
+* **\n**: перевод строки
+* **\r**: возврат каретки
+* **\'**: одинарная кавычка
+* **\"**: двойная кавычка
+* ``\\``: обратный слеш
+
+### Строки
+
+Строки представлены типом **String**. Строка представляет последовательность символов, заключенную в двойные кавычки, либо в тройные двойные кавычки.
+
+```kt
+val name = "Eugene"
+println(name)
+```
+
+Строка может содержать специальные символы или эскейп-последовательности. Например, если необходимо вставить в текст перевод на другую строку, можно использовать эскейп-последовательность `\n`:
+
+```kt
+val text: String = "SALT II was a series of talks between United States \n and Soviet negotiators from 1972 to 1979"
+```
+
+Для большего удобства при создании многострочного текста можно использовать тройные двойные кавычки:
+
+```kt
+val text: String = """SALT II was a series of talks between United States
+and Soviet negotiators from 1972 to 1979.
+It was a continuation of the SALT I talks."""
+println(text)
+```
+
+Строки состоят из символов, которые могут быть получены по порядковому номеру: s[i]. Проход по строке можно выполнить циклом for:
+
+```kt
+for (c in str) {
+    println(c)
+}
+```
+
+### Строковые шаблоны
+
+Строки могут содержать шаблонные выражения, т.е. участки кода, которые выполняются, а полученный результат встраивается в строку. Шаблон начинается со знака доллара ($) и состоит либо из простого имени (например, переменной):
+
+```kt
+val i = 10
+val s = "i = $i" // evaluates to "i = 10"
+```
+
+либо из произвольного выражения в фигурных скобках:
+
+```kt
+val s = "abc"
+val str = "$s.length is ${s.length}" // evaluates to "abc.length is 3"
+```
+
+Шаблоны поддерживаются как в обычных, так и в экранированных строках. При необходимости символ $ может быть представлен с помощью следующего синтаксиса:
+
+```kt
+val price = "${'$'}9.99"
+```
+
+### Тип Any
+
+Тип **Any** является базовым для всех остальных типов. Остальные базовые типы, такие как **Int** или **Double**, являются производными от **Any**. Соответственно мы можем присвоить переменной данного типа любое значение:
+
+```kt
+var name: Any = "Tom Smith"
+name = 6758
+```
+
+### Операторы is и !is
+
+Мы можем проверить принадлежит ли объект к какому-либо типу во время исполнения с помощью оператора **is** или его отрицания **!is**:
+
+```kt
+if (obj is String) {
+    print(obj.length)
+}
+
+if (obj !is String) { // то же самое, что и !(obj is String)
+    print("Not a String")
+}
+else {
+    print(obj.length)
+}
+```
+
+**Умные приведения**
+
+Во многих случаях в **Kotlin** вам не нужно использовать явные приведения, потому что компилятор следит за **is**-проверками для неизменяемых значений и вставляет приведения автоматически, там, где они нужны:
+
+```kt
+fun demo(x: Any) {
+    if (x is String) {
+        print(x.length) // x автоматически преобразовывается в String
+    }
+}
+```
+
+Компилятор достаточно умён для того, чтобы делать автоматические приведения в случаях, когда проверка на несоответствие типу (!is) приводит к выходу из функции:
+
+```kt
+if (x !is String) return
+
+// x автоматически преобразовывается в String
+print(x.length) 
+```
+
+или в случаях, когда приводимая переменная находится справа от оператора `&&` или `||`:
+
+```kt
+// x автоматически преобразовывается в String справа от ||
+if (x !is String || x.length == 0) return
+
+// x автоматически преобразовывается в String справа от &&
+if (x is String && x.length > 0) {
+    print(x.length) // x автоматически преобразовывается в String
+}
+```
+
+Заметьте, что умные приведения не работают, когда компилятор не может гарантировать, что переменная не изменится между проверкой и использованием. Более конкретно, умные приведения будут работать:
+
+* с локальными **val** переменными - всегда;
+* с **val** свойствами - если поле имеет модификатор доступа **private** или **internal**, или проверка происходит в том же модуле, в котором объявлено это свойство. Умные приведения неприменимы к публичным свойствам или свойствам, которые имеют переопределённые getter'ы;
+* с локальными **var** переменными - если переменная не изменяется между проверкой и использованием и не захватывается лямбдой, которая её модифицирует;
+* с **var** свойствами - никогда (потому что переменная может быть изменена в любое время другим кодом).
+
+**Оператор "небезопасного" приведения**
+
+Этот оператор приведения выбрасывает исключение, если приведение невозможно, поэтому мы называем его небезопасным. Небезопасное приведение в **Kotlin** выполняется с помощью инфиксного оператора **as**:
+
+```kt
+val x: String = y as String
+```
+
+Заметьте, что **null** не может быть приведен к **String**, так как **String** не является **nullable**, т.е. если y - **null**, код выше выбросит исключение. Чтобы соответствовать семантике приведений в **Java**, нам нужно указать **nullable** тип в правой части приведения:
+
+```kt
+val x: String? = y as String?
+```
+
+## Null безопасность
+
+### Nullable типы и Non-Null типы
+
+Система типов в языке **Kotlin** нацелена на то, чтобы искоренить опасность обращения к **null** значениям.
+
+Самым распространённым подводным камнем многих языков программирования, в том числе **Java**, является попытка произвести доступ к **null** значению. Это приводит к ошибке. В **Java** такая ошибка называется **NullPointerException** (сокр. "NPE").
+
+**Kotlin** призван исключить ошибки подобного рода из нашего кода. NPE могу возникать только в случае:
+
+* Явного указания `throw NullPointerException()`
+* Использования оператора `!!` (описано ниже)
+* Эту ошибку вызвал внешний **Java**-код
+* Есть какое-то несоответствие при инициализации данных (в конструкторе использована ссылка `this` на данные, которые не были ещё проинициализированы)
+
+Система типов **Kotlin** различает ссылки на те, которые могут иметь значение **null** (nullable ссылки) и те, которые таковыми быть не могут (non-null ссылки). К примеру, переменная часто используемого типа **String** не может быть **null**:
+
+```kt
+var a: String = "abc"
+a = null // ошибка компиляции
+```
+
+Для того, чтобы разрешить **null** значение, мы можем объявить эту строковую переменную как **String?**:
+
+```kt
+var b: String? = "abc"
+b = null // ok
+```
+
+Теперь, при вызове метода с использованием переменной `a`, исключены какие-либо NPE. Вы спокойно можете писать:
+
+```kt
+val l = a.length
+```
+
+Но в случае, если вы захотите получить доступ к значению `b`, это будет небезопасно. Компилятор предупредит об ошибке:
+
+```kt
+val l = b.length // ошибка: переменная `b` может быть null
+```
+
+Но нам по-прежнему надо получить доступ к этому свойству/значению, так? Есть несколько способов этого достичь.
+
+### Проверка на null
+
+Первый способ. Вы можете явно проверить `b` на **null** значение и обработать два варианта по отдельности:
+
+```kt
+val l = if (b != null) b.length else -1
+```
+
+Компилятор отслеживает информацию о проведённой вами проверке и позволяет вызывать *length* внутри блока **if**. Также поддерживаются более сложные конструкции:
+
+```kt
+if (b != null && b.length > 0) {
+    print("String of length ${b.length}")
+} else {
+    print("Empty string")
+}
+```
+
+Обратите внимание: это работает только в том случае, если `b` является неизменной переменной (ориг.: immutable). Например, если это локальная переменная, значение которой не изменяется в период между его проверкой и использованием. Также такой переменной может служить **val**. В противном случае может так оказаться, что переменная `b` изменила своё значение на **null** после проверки.
+
+### Безопасные вызовы
+
+Вторым способом является оператор безопасного вызова `?.`:
+
+```kt
+x = b?.length
+```
+
+Этот код возвращает `b.length` в том, случае, если `b` не имеет значение **null**. Иначе он возвращает **null**. Типом этого выражения будет **Int?**.
+
+Такие безопасные вызовы полезны в цепочках. К примеру, если Bob, Employee (работник), может быть прикреплён (или нет) к отделу Department, и у отдела может быть управляющий, другой Employee. Для того, чтобы обратиться к имени этого управляющего (если такой есть), напишем:
+
+```kt
+bob?.department?.head?.name
+```
+
+Такая цепочка вернёт **null** в случае, если одно из свойств имеет значение **null**.
+
+Для проведения каких-либо операций исключительно над non-null значениями вы можете использовать **let** оператор вместе с оператором безопасного вызова:
+
+```kt
+val listWithNulls: List<String?> = listOf("A", null)
+for (item in listWithNulls) {
+    item?.let { println(it) } // выводит A и игнорирует null
+}
+```
+
+### Элвис-оператор
+
+Если у нас есть nullable ссылка `r`, мы можем либо провести проверку этой ссылки и использовать её, либо использовать non-null значение `x`:
+
+```kt
+val l: Int = if (b != null) b.length else -1
+```
+
+Аналогом такому if-выражению является элвис-оператор **?:**:
+
+```kt
+val l = b?.length ?: -1
+```
+
+Если выражение, стоящее слева от Элвис-оператора, не является **null**, то элвис-оператор его вернёт. В противном случае, в качестве возвращаемого значения послужит то, что стоит справа. Обращаем ваше внимание на то, что часть кода, расположенная справа, выполняется ТОЛЬКО в случае, если слева получается **null**.
+
+Так как **throw** и **return** тоже являются выражениями в **Kotlin**, их также можно использовать справа от Элвис-оператора. Это может быть крайне полезным для проверки аргументов функции:
+
+```kt
+fun foo(node: Node): String? {
+    val parent = node.getParent() ?: return null
+    val name = node.getName() ?: throw IllegalArgumentException("name expected")
+    // ...
+}
+```
+
+### Оператор !!
+
+Для любителей NPE существует ещё один способ. Мы можем написать `b!!` и это вернёт нам либо non-null значение b (в нашем примере вернётся **String**), либо выкинет NPE:
+
+```kt
+val l = b!!.length
+```
+
+В случае, если вам нужен NPE, вы можете заполучить её только путём явного указания.
+
+### Безопасные приведения типов
+
+Обычное приведение типа может вызвать ClassCastException в случае, если объект имеет другой тип. Можно использовать безопасное приведение, которое вернёт **null**, если попытка не удалась:
+
+```kt
+val aInt: Int? = a as? Int
+```
+
+### Коллекции nullable типов
+
+Если у вас есть коллекция nullable элементов и вы хотите отфильтровать все non-null элементы, используйте функцию filterNotNull.
+
+```kt
+val nullableList: List<Int?> = listOf(1, 2, null, 4)
+val intList: List<Int> = nullableList.filterNotNull()
+```
+
+## Операции с числами
+
+### Арифметические операции
+
+**Kotlin** поддерживает базовые арифметические операции:
+
+**+** (сложение): возвращает сумму двух чисел.
+
+```kt
+val x = 5
+val y = 6
+val z = x + y
+println(z)      // z = 11
+```
+
+**-** (вычитание): возвращает разность двух чисел.
+
+```kt
+val x = 5
+val y = 6
+val z = x - y  // z = -1
+```
+
+***** (умножение): возвращает произведение двух чисел.
+
+```kt
+val x = 5
+val y = 6
+val z = x * y  // z = 30
+```
+
+**/** (деление): возвращает частное двух чисел.
+
+```kt
+val x = 60
+val y = 10
+val z = x / y  // z = 6
+```
+
+**%**: возвращает остаток от целочисленного деления двух чисел.
+
+```kt
+val x = 65
+val y = 10
+val z = x % y  // z = 5
+```
+
+**++** (инкремент): увеличивает значение на единицу.
+
+Префиксный инкремент возвращает увеличенное значение:
+
+```
+var x = 5
+val y = ++x
+println(x)      // x = 6
+println(y)      // y = 6
+```
+
+Постфиксный инкремент возвращает значение до увеличения на единицу:
+
+```kt
+var x = 5
+val y = x++
+println(x)      // x = 6
+println(y)      // y = 5
+```
+
+**--** (декремент): уменьшает значение на единицу.
+
+Префиксный декремент возвращает уменьшенное значение:
+
+```kt
+var x = 5
+val y = --x
+println(x)      // x = 4
+println(y)      // y = 4
+```
+
+Постфиксный декремент возвращает значение до уменьшения на единицу:
+
+```kt
+var x = 5
+val y = x--
+println(x)      // x = 4
+println(y)      // y = 5
+```
+
+Также есть ряд операций присвоения, которые сочетают арифметические операции и присвоение:
+
+**+=**: присваивание после сложения. Присваивает левому операнду сумму левого и правого операндов: A += B эквивалентно A = A + B
+
+**-=**: присваивание после вычитания. Присваивает левому операнду разность левого и правого операндов: A -= B эквивалентно A = A - B
+
+***=**: присваивание после умножения. Присваивает левому операнду произведение левого и правого операндов: A *= B эквивалентно A = A * B
+
+**/=**: присваивание после деления. Присваивает левому операнду частное левого и правого операндов: A /= B эквивалентно A = A / B
+
+**%=**: присваивание после деления по модулю. Присваивает левому операнду остаток от целочисленного деления левого операнда на правый: A %= B эквивалентно A = A % B
+
+### Побитовые операторы
+
+Ряд операций выполняется над двоичными разрядми числа. Здесь важно понимать, как выглядит двоичное представление тех или иных чисел. В частности, число 4 в двоичном виде - 100, а число 15 - 1111.
+
+Есть следующие побитовые операторы (они применяются только к данным типов Int и Long):
+
+**shl**: сдвиг битов числа со знаком влево
+
+```kt
+val z = 3 shl 2     // z = 11 << 2 = 1100 
+println(z)          // z = 12
+val d = 0b11 shl 2
+println(d)          // d = 12
+```
+
+В данном случае число сдвигается на два разряда влево, поэтому справа число в двоичном виде дополняется двумя нулями. То есть в двоичном виде 3 представляет 11. Сдвигаем на два разряда влево (дополняем справа двумя нулями) и получаем 1100, то есть в десятичной системе число 12.
+
+**shr**: сдвиг битов числа со знаком вправо
+
+```kt
+val z = 12 shr 2     // z = 1100 >> 2 = 11
+println(z)          // z = 3
+val d = 0b1100 shr 2
+println(d)          // d = 3
+```
+
+Число 12 сдвигается на два разряда вправо, то есть два числа справа факически отбрасываем и получаем число 11, то есть 3 в десятичой системе.
+
+**ushr**: сдвиг битов беззнакового числа вправо
+
+```kt
+val z = 12 ushr 2     // z = 1100 >> 2 = 11
+println(z)          // z = 3
+```
+
+**and**: побитовая операция AND (логическое умножение или конъюнкция). Эта операция сравнивает соответствующие разряды двух чисел и возвращает единицу, если эти разряды обоих чисел равны 1. Иначе возвращает 0.
+
+```kt
+val x = 5   // 101
+val y = 6   // 110
+val z = x and y     // z = 101 & 110 = 100
+println(z)          // z = 4
+ 
+val d = 0b101 and 0b110
+println(d)          // d = 4
+```
+
+**or**: побитовая операция OR (логическое сложение или дизъюнкция). Эта операция сравнивают два соответствуюших разряда обоих чисел и возвращает 1, если хотя бы один разряд равен 1. Если оба разряда равны 0, то возвращается 0.
+
+```kt
+val x = 5   // 101
+val y = 6   // 110
+val z = x or y     // z = 101 | 110 = 111
+println(z)         // z = 7
+ 
+val d = 0b101 or 0b110
+println(d)          // d = 7
+```
+
+**xor**: побитовая операция XOR. Сравнивает два разряда и возвращает 1, если один из разрядов равен 1, а другой равен 0. Если оба разряда равны, то возвращается 0.
+
+```kt
+val x = 5   // 101
+val y = 6   // 110
+val z = x xor y     // z = 101 ^ 110 = 011
+println(z)         // z = 3
+ 
+val d = 0b101 xor 0b110
+println(d)          // d = 3
+```
+
+**inv**: логическое отрицание или инверсия - инвертирует биты числа
+
+```kt
+val b = 11  // 1011
+val c = b.inv()
+println(c)      // -12
+```
+
+## Условные выражения
+
+Условные выражения представляют некоторое условие, которое возвращает значение типа **Boolean**: либо **true** (если условие истинно), либо **false** (если условие ложно).
+
+### Операции отношения
+**>** (больше чем): возвращает **true**, если первый операнд больше второго. Иначе возвращает **false**
+
+```kt
+val a = 11
+val b = 12
+val c : Boolean =  a > b
+println(c)      // false - a меньше чем b
+ 
+val d = 35 > 12
+println(d)      // true - 35 больше чем 12
+```
+
+**<** (меньше чем): возвращает **true**, если первый операнд меньше второго. Иначе возвращает **false**
+
+```kt
+val a = 11
+val b = 12
+val c =  a < b   // true
+ 
+val d = 35 < 12  // false
+```
+
+**>=** (больше чем или равно): возвращает **true**, если первый операнд больше или равен второму
+
+```kt
+val a = 11
+val b = 12
+val c = a >= b      // false
+val d = 11 >= a     // true
+```
+
+**<=** (меньше чем или равно): возвращает **true**, если первый операнд меньше или равен второму.
+
+```kt
+val a = 11
+val b = 12
+val c = a <= b      // true
+val d = 11 <= a     // false
+```
+
+**==** (равно): возвращает **true**, если оба операнда равны. Иначе возвращает **false**
+
+```kt
+val a = 11
+val b = 12
+val c = a == b      // false
+val d = b == 12     // true
+```
+
+**!=** (не равно): возвращает **true**, если оба операнда НЕ равны
+
+```kt
+val a = 11
+val b = 12
+val c = a != b      // true
+val d = b != 12     // false
+```
+
+### Логические операции
+
+Операндами в логических операциях являются два значения типа **Boolean**. Нередко логические операции объединяют несколько операций отношения:
+
+**and**: возвращает **true**, если оба операнда равны **true**.
+
+```kt
+val a = true
+val b = false
+val c = a and b                         // false
+val d = (11 >= 5) and (9 < 10)     // true
+println(c)
+println(d)
+```
+
+**or**: возвращает **true**, если хотя бы один из операндов равен **true**.
+
+```kt
+val a = true
+val b = false
+val c = a or b                          // true
+val d = (11 < 5) or (9 > 10)     // false
+```
+
+**xor**: возвращает **true**, если только один из операндов равен **true**. Если операнды равны возвращается **false**
+
+```kt
+val a = true
+val b = false
+val c = a xor b                 // true
+val d = a xor (90 > 10)      // false
+```
+
+**!**: возвращает **true**, если операнд равен **false**. И, наоборот, если операнд равен **false**, возвращает **true**.
+
+```kt
+val a = true
+val b = !a  // false
+val c = !b  // true
+```
+
+В качестве альтернативы оператору **!** можно использовать метод not():
+
+```kt
+val a = true
+val b = a.not()  // false
+val c = b.not()  // true
+```
+
+**in**: возвращает **true**, если операнд имеется в некоторой последовательности.
+
+```kt
+val a = 5
+val b = a in 1..6       // true
+```
+
+Выражение **1..6** создает последовательность чисел от 1 до 6. И в данном случае оператор in проверяет, есть ли значение переменной a в этой последовательности. Поскольку значение переменной a имеется в данной последовательности, то возвращается **true**.
+
+## Условные конструкции
+
+Условные конструкции позволяют направить выполнение программы по одному из путей в зависимости от условия.
+
+### if...else
+
+Конструкция **if** принимает условие, и если это условие истинно, то выполняется последующий блок инструкций.
+
+```kt
+val a = 10
+if(a == 10) {
+    println("a равно 10")
+}
+```
+
+В данном случае в конструкции **if** проверяется истинность выражения **a == 10**, если оно истинно, то выполняется последующий блок кода в фигурных скобках, и на консоль выводится сообщение "a равно 10". Если же выражение ложно, тогда блок кода не выполняется.
+
+Если необходимо задать альтернативный вариант, то можно добавить блок **else**:
+
+```kt
+val a = 10
+if(a == 10) {
+    println("a равно 10")
+}
+else{
+    println("a НЕ равно 10")
+}
+```
+
+Таким образом, если условное выражение после оператора **if** истинно, то выполняется блок после **if**, если ложно - выполняется блок после **else**.
+
+Если блок кода состоит из одного выражения, то в принципе фигурные скобки можно опустить:
+
+```kt
+val a = 10
+if(a == 10)
+    println("a равно 10")
+else
+    println("a НЕ равно 10")
+```
+
+Если необходимо проверить несколько альтернативных вариантов, то можно добавить выражения **else if**:
+
+```kt
+val a = 10
+if(a == 10) {
+    println("a равно 10")
+}
+else if(a == 9){
+    println("a равно 9")
+}
+else if(a == 8){
+    println("a равно 8")
+}
+else{
+    println("a имеет неопределенное значение")
+}
+```
+
+Стоит отметить, что конструкция **if** может возвращать значение. Например, найдем максимальное из двух чисел:
+
+```kt
+val a = 10
+val b = 20
+val c = if (a > b) a else b
+ 
+println(c)  // 20
+```
+
+Если при определении возвращаемого значения надо выполнить еще какие-нибудь действия, то можно заключить эти действия в блоки кода:
+
+```kt
+val a = 10
+val b = 20
+val c = if (a > b){
+    println("a = $a")
+    a
+} else {
+    println("b = $b")
+    b
+}
+```
+
+В конце каждого блока указывается возвращаемое значение.
+
+### Конструкция when
+
+Конструкция **when** проверяет значение некоторого объекта и в зависимости от его значения выполняет тот или иной код. Конструкция **when** аналогична конструкции **switch** в других языках.
+
+```kt
+val a = 10
+when(a){
+    10 -> println("a = 10")
+    20 -> println("a = 20")
+    else -> println("неопределенное значение")
+}
+```
+
+После ключевого слова **when** в скобках идет выражение. Затем идет блок кода, в котором определяются значения для сравнения. После каждого значения после стрелки **->** идет последовательность выполняемых инструкций:
+
+```kt
+10 -> println("a = 10")
+```
+
+То есть в данном случае если переменная a равна 10, то на консоль будет выводиться сообщение "a = 10".
+
+Если ни одно из значений в блоке **when** не соответствуют выражению, то выполняются инструкции из выражения **else**. Выражение **else** не обязательное, его можно не определять.
+
+Если надо, чтобы при совпадении значений выполнялось несколько инструкций, то для каждого значения можно определить блок кода:
+
+```kt
+var a = 10
+when(a){
+    10 -> {
+        println("a = 10")
+        a *= 2
+    }
+    20 -> {
+        println("a = 20")
+        a *= 5
+    }
+    else -> { println("неопределенное значение")}
+}
+println(a)
+```
+
+Можно определить одни и те же действия сразу для нескольких значений. В этом случае значения перечисляются через запятую:
+
+```kt
+val a = 10
+when(a){
+    10, 20 -> println("a = 10 или a = 20")
+    else -> println("неопределенное значение")
+}
+```
+
+Также можно сравнивать с целым диапазоном значений с помощью оператора **in**:
+
+```kt
+val a = 10
+when(a){
+    in 10..19 -> println("a в диапазоне от 10 до 19")
+    in 20..29 -> println("a в диапазоне от 20 до 29")
+    !in 10..20 -> println("a вне диапазона от 10 до 20")
+    else -> println("неопределенное значение")
+}
+```
+
+Если оператор **in** позволяет узнать, есть ли значение в определенном диапазоне, то связка операторов **!in** позволяет проверить отсутствие значения в определенной последовательности.
+
+#### Возвращение значения
+
+Как и **if** конструкция **when** может возвращать значение:
+
+```kt
+val sum = 1000
+ 
+val rate = when(sum){
+    in 100..999 -> 10
+    in 1000..9999 -> 15
+    else -> 20
+}
+println(rate)       // 15
+```
+
+Таким образом, если значение переменной sum располагается в определенном диапазоне, то возвращается то значение, которое идет после стрелки.
+
+Также вместе с **when**-выражениями работают умные приведения (**is**, **!is**)
+
+```kt
+when (x) {
+    is Int -> print(x + 1)
+    is String -> print(x.length + 1)
+    is IntArray -> print(x.sum())
+}
+```
+
+**when** удобно использовать вместо цепочки условий вида **if-else if**. При отстутствии аргумента, условия работают как простые логические выражения, а тело ветки выполняется при его истинности:
+
+```kt
+when {
+  x.isOdd() -> print("x is odd")
+  x.isEven() -> print("x is even")
+  else -> print("x is funny")
+}
+```
+
+## Циклы
+
+Циклы представляют вид управляющих конструкций, которые позволяют в зависимости от определенных условий выполнять некоторое действие множество раз.
+
+### For
+
+Цикл **for** пробегается по всем элементам коллекции. В этом плане цикл **for** в **Kotlin** эквивалентен циклу **for-each** в ряде других языков программирования. Его формальная форма выглядит следующим образом:
+
+```kt
+for(переменная in поледовательность){
+    выполняемые инструкции
+}
+```
+
+Например, выведем все квадраты чисел от 1 до 9, используя цикл for:
+
+```kt
+for(n in 1..9){
+    print("${n * n} \t")
+}
+```
+
+В данном случае перебирается последовательность чисел от 1 до 9. При каждом проходе цикла (итерации цикла) из этой последовательности будет извлекаться элемент и помещаться в переменную n. И через переменную n можно манипулировать значением элемента. То есть в данном случае мы получим следующий консольный вывод:
+
+```
+1 	4 	9 	16 	25 	36 	49 	64 	81
+```
+
+Циклы могут быть вложенными. Например, выведем таблицу умножения:
+
+```kt
+for(i in 1..9){
+    for(j in 1..9){
+        print("${i * j} \t")
+    }
+    println()
+}
+```
+
+В итоге на консоль будет выведена следующая таблица умножения:
+
+```
+1 	2 	3 	4 	5 	6 	7 	8 	9 	
+2 	4 	6 	8 	10 	12 	14 	16 	18 	
+3 	6 	9 	12 	15 	18 	21 	24 	27 	
+4 	8 	12 	16 	20 	24 	28 	32 	36 	
+5 	10 	15 	20 	25 	30 	35 	40 	45 	
+6 	12 	18 	24 	30 	36 	42 	48 	54 	
+7 	14 	21 	28 	35 	42 	49 	56 	63 	
+8 	16 	24 	32 	40 	48 	56 	64 	72 	
+9 	18 	27 	36 	45 	54 	63 	72 	81
+```
+
+### Цикл while
+
+Цикл **while** повторяет определенные действия пока истинно некоторое условие:
+
+```kt
+var i = 10
+while(i > 0){
+    println(i*i)
+    i--;
+}
+```
+
+Здесь пока переменная i больше 0, будет выполняться цикл, в котором на консоль будет выводиться квадрат значения i.
+
+В данном случае вначале проверяется условие (i > 0) и если оно истинно (то есть возвращает true), то выполняется цикл. И вполне может быть ситуация, когда к началу выполнения цикла условие не будет выполняться. Например, переменная i изначально меньше 0, тогда цикл вообще не будет выполняться.
+
+Но есть и другая форма цикла **while** - **do..while**:
+
+```kt
+var i = -1
+do{
+    println(i*i)
+    i--;
+}
+while(i > 0)
+```
+
+В данном случае вначале выполняется блок кода после ключевого слова **do**, а потом оценивается условие после **while**. Если условие истинно, то повторяется выполнение блока после **do**. То есть несмотря на то, что в данном случае переменная i меньше 0 и она не соответствует условию, тем не менее блок do выполнится хотя бы один раз.
+
+### Операторы continue и break
+
+Иногда при использовании цикла возникает необходимость при некоторых условиях не дожидаться выполнения всех инструкций в цикле, перейти к новой итерации. Для этого можно использовать оператор **continue**:
+
+```kt
+for(n in 1..8){
+    if(n == 5) continue;
+    println(n * n)
+}
+```
+
+В данном случае когда n будет равно 5, сработает оператор **continue**. И последующая инструкция, которая выводит на консоль квадрат числа, не будет выполняться. Цикл перейдет к обработке следующего элемента в массиве
+
+Бывает, что при некоторых условиях нам вовсе надо выйти из цикла, прекратить его выполнение. В этом случае применяется оператор **break**:
+
+```kt
+for(n in 1..5){
+    if(n == 5) break;
+    println(n * n)
+}
+```
+
+В данном случае когда n окажется равен 5, то с помощью оператора **break** будет выполнен выход из цикла. Цикл полностью завершится.
+
+## Последовательности
+
+Последовательность представляет набор значений или диапазон. Для создания последовательности применяется оператор **..**:
+
+```kt
+var range = 1..5    // последовательность [1, 2, 3, 4, 5]
+```
+
+Этот оператор принимает два значения - границы последовательности, и все элементы между этими значениями (включая их самих) составляют последовательность.
+
+Последовательность необязательно должна представлять числовые данные. Например, это могут быть строки:
+
+```kt
+var range =  "a".."d"
+```
+
+Оператор .. позволяет создать последовательность по нарастающей, где каждый следующий элемент будет больше предыдущего. С помощью специальной функции downTo можно построить последовательность в обратном порядке:
+
+```kt
+var range1 =  1..5      // 1 2 3 4 5
+var range2 =  5 downTo 1    // 5 4 3 2 1
+```
+
+Еще одна специальная функция step позволяет задать шаг, на который будут изменяться последующие элементы:
+
+```kt
+var range1 = 1..10 step 2           // 1 3 5 7 9
+var range2 = 10 downTo 1 step 3     // 10 7 4 1
+```
+
+Еще одна функция until позволяет не включать верхнюю границу в саму последовательность:
+
+``` kt
+var range1 = 1 until 9          // 1 2 3 4 5 6 7 8
+var range2 = 1 until 9 step 2   // 1 3 5 7
+```
+
+С помощью специальных операторов можно проверить наличие или отсутствие элементов в последовательности:
+
+**in**: возвращает **true**, если объект имеется в последовательности
+
+**!in**: возвращает **true**, если объект отсутствует в последовательности
+
+```kt
+fun main(args: Array<String>) {
+     var range = 1..5
+ 
+    var isInRange = 5 in range
+    println(isInRange)      // true
+ 
+    isInRange = 86 in range
+    println(isInRange)      // false
+ 
+    var isNotInRange = 6 !in range
+    println(isNotInRange)   // true
+ 
+    isNotInRange = 3 !in range
+    println(isNotInRange)   // false
+}
+```
+
+С помощью цикла **for** можно перебирать последовательность:
+
+```kt
+var range1 = 5 downTo 1 
+for(c in range1) print(c)   // 54321
+println()
+for(c in 1..9) print(c)     // 123456789
+println()
+for(c in 1 until 9) print(c)    // 12345678
+println()
+for(c in 1..9 step 2) print(c)  // 13579
+```
+
+## Массивы
+
+Массив представляет набор данных одного типа. В языке **Kotlin** массивы представлены типом **Array**.
+
+При определении массива после типа **Array** в угловых скобках необходимо указать, объекты какого типа могут храниться в массиве. Например, определим массив целых чисел:
+
+```kt
+val numbers: Array<Int>
+```
+
+С помощью встроенной функции *arrayOf()* можно передать набор значений, которые будут составлять массив:
+
+```kt
+val numbers: Array<Int> = arrayOf(1, 2, 3, 4, 5)
+```
+
+То есть в данном случае в массиве 5 чисел от 1 до 5.
+
+С помощью индексов мы можем обратиться к определенному элементу в массиве. Индексация начинается с нуля, то есть первый элемент буде иметь индекс 0. Индекс указывается в квадратных скобках:
+
+```kt
+val numbers: Array<Int> = arrayOf(1, 2, 3, 4, 5)
+val n = numbers[1]  // получаем второй элемент  n=2
+numbers[2] = 7      // переустанавливаем третий элемент
+```
+
+Также инициализировать массив значениями можно следующим способом:
+
+```kt
+val numbers = Array(3, {5}) // [5, 5, 5]
+```
+
+Здесь применяется конструктор класса **Array**. В этот конструктор передаются два параметра. Первый параметр указывает, сколько элементов будет в массиве. В данном случае 3 элемента. Второй параметр представляет выражение, которое генерирует элементы массива. Оно заключается в фигурные скобки. В данном случае в фигурных скобках стоит число 5, то есть все элементы массива будут представлять число 5. Таким образом, массив будет состоять из трех пятерок.
+
+Для упрощения создания массива в **Kotlin** определены дополнительные типы **BooleanArray**, **ByteArray**, **ShortArray**, **IntArray**, **LongArray**, **CharArray**, **FloatArray** и **DoubleArray**, которые позволяют создавать массивы для определенных типов. Например, тип **IntArray** позволяет определить массив объектов **Int**, а **DoubleArray** - массив объектов **Double**:
+
+```kt
+val numbers: IntArray = intArrayOf(1, 2, 3, 4, 5)
+val doubles: DoubleArray = doubleArrayOf(2.4, 4.5, 1.2)
+```
+
+Для определения данных для этих массивов можно применять функции, которые начинаются на название типа в нижнем регистре, например, **int**, и затем идет **ArrayOf**.
+
+Аналогично для инициализации подобных массивов также можно применять конструктор соответствуюшего класса:
+
+```kt
+val numbers = IntArray(3, {5})
+val doubles = DoubleArray(3, {1.5})
+```
+
+Как и в случае с последовательностью мы можем проверить наличие или отсутствие элементов в массиве с помощью операторов **in** и **!in**:
+
+```kt
+val numbers: Array<Int> = arrayOf(1, 2, 3, 4, 5)
+ 
+println(4 in numbers)       // true
+println(2 !in numbers)      // false
+```
+
+### Двухмерные массивы
+
+Выше рассматривались одномерные массивы, которые можно представить в виде ряда или строки значений. Но кроме того, мы можем использовать многомерные массивы. К примеру, возьмем двухмерный массив - то есть такой массив, каждый элемент которого в свою очередь сам является массивом. Двухмерный массив еще можно представить в виде таблицы, где каждая строка - это отдельный массив, а ячейки строки - это элементы вложенного массива.
+
+Определение двухмерных массивов менее интуитивно понятно и может вызывать сложности. Например, двухмерный массив чисел:
+
+```kt
+val table: Array<Array<Int>> = Array(3, { Array(5, {0}) })
+```
+
+В данном случае двухмерный массив будет иметь три элемента - три строки. Каждая строка будет иметь по пять элементов, каждый из которых равен 0.
+
+Используя индексы, можно обращаться к подмассивам в подобном массиве, в том числе переустанавливать их значения:
+
+```kt
+val table = Array(3, { Array(3, {0}) })
+table[0] = arrayOf(1, 2, 3)
+table[1] = arrayOf(4, 5, 6)
+table[2] = arrayOf(7, 8, 9)
+```
+
+Для обращения к элементам подмассивов двухмерного массива необходимы два индекса. По первому индексу идет получение строки, а по второму индексу - столбца в рамках этой строки:
+
+```kt
+val table = Array(3, { Array(3, {0}) })
+table[0][1] = 6  // второй элемент первой строки
+val n = table[0][1]     // n = 6
+```
+
+Перебор массивов
+Для перебора массивов применяется цикл **for**:
+
+```kt
+val phones: Array<String> = arrayOf("Galaxy S8", "iPhone X", "Motorola C350")
+for(phone in phones){
+    println(phone)
+}
+```
+
+В данном случае переменная phones представляет массив строк. При переборе этого массива в цикле каждый его элемент оказывается в переменной phone. Консольный вывод программы:
+
+```
+Galaxy S8
+iPhone X
+Motorola C350
+```
+
+Используя два цикла, можно перебирать двухмерные массивы:
+
+```kt
+fun main(args: Array<String>) {
+    val table: Array<Array<Int>> = Array(3, { Array(3, {0}) })
+    table[0] = arrayOf(1, 2, 3)
+    table[1] = arrayOf(4, 5, 6)
+    table[2] = arrayOf(7, 8, 9)
+    for(row in table){
+        for(cell in row){
+            print("$cell \t")
+        }
+        println()
+    }
+}
+```
+
+С помощью внешнего цикла **for(row in table)** пробегаемся по всем элементам двухмерного массива, то есть по строкам таблицы. Каждый из элементов двухмерного массива сам представляет массив, поэтому мы можем пробежаться по этому массиву и получить из него непосредственно те значения, которые в нем хранятся. В итоге на консоль будет выведено следующее:
+
+```
+1 	2 	3 	
+4 	5 	6 	
+7 	8 	9
+```
+
+## Функции и их параметры
+
+Одним из строительных блоков программы являются функции. Функция определяет некоторое действие. В **Kotlin** функция объявляется с помощью ключевого слова **fun**, после которого идет название функции. Затем после названия в скобках указывается список параметров. Если функция возвращает какое-либо значение, то после списка параметров через двоеточие можно указать тип возвращаемого значения. И далее в фигурных скобках идет тело функции.
+
+```
+fun имя_функции (параметры) : возвращаемый_тип 
+{
+    выполняемые инструкции
+}
+```
+
+Параметры необязательны.
+
+Например, определим и вызовем функцию, которая просто выводит некоторую строку на консоль:
+
+```kt
+fun main(args: Array<String>) {
+    hello() // вызов функции hello
+    hello() // вызов функции hello
+    hello() // вызов функции hello
+}
+
+// определение функции hello
+fun hello(){
+    println("Hello")
+}
+```
+
+Функции можно определять в файле вне других функций или классов, сами по себе, как например, определяется функция main. Такие функции еще называют функциями верхнего уровня (top-level functions).
+
+Здесь кроме главной функции **main** также определена функция **hello**, которая не принимает никаких параметров и ничего не возвращает. Она просто выводит строку на консоль.
+
+Функция **hello** (и любая другая определенная функция, кроме **main**) сама по себе не выполняется. Чтобы ее выполнить, ее надо вызвать. Для вызова функции указывается ее имя (в данном случае "hello"), после которого идут пустые скобки.
+
+Таким образом, если необходимо в разных частях программы выполнить одни и те же действия, то можно эти действия вынести в функцию, и затем вызывать эту функцию.
+
+### Предача параметров
+
+Через параметры функция может получать некоторые значения извне. Параметры указываются после имени функции в скобках через запятую в формате `имя_параметра : тип_параметра`. Например, определим функцию, которая вычисляет факториал числа:
+
+```kt
+fun main(args: Array<String>) {
+    factorial(4)
+    factorial(5)
+    factorial(6)
+}
+ 
+fun factorial(n: Int){
+ 
+    var result = 1;
+    for(d in 1..n){
+        result *= d
+    }
+    println("Factorial of $n is equal to $result")
+}
+```
+
+Функция **factorial** принимает один параметр типа **Int**. Поэтому при вызове функции в скобках необходимо передать значение для этого параметра: `factorial(4)`. Причем это значение должно представлять тип **Int**. Значения, которые передаются параметрам функции, еще назвают аргументами.
+
+Консольный вывод программы:
+
+```
+Factorial of 4 is equal to 24
+Factorial of 5 is equal to 120
+Factorial of 6 is equal to 720
+```
+
+Другой пример - функция, которая выводит данные о пользователе на консоль:
+
+```kt
+fun main(args: Array<String>) {
+    displayUser("Tom", 23)
+    displayUser("Alice", 19)
+    displayUser("Kate", 25)
+}
+
+fun displayUser(name: String, age: Int){
+    println("Name: $name   Age: $age")
+}
+```
+
+Функция displayUser() принимает два параметра - *name* и *age*. При вызове функции в скобках ей передаются значения для этих параметров. При этом значения передаются параметрам по позиции и должны соответствовать параметрам по типу. Так как вначале идет параметр типа **String**, а потом параметр типа **Int**, то при вызове функции в скобках вначале передается строка, а потом число.
+
+### Аргументы по умолчанию
+
+В примере выше при вызове функций **factorial** и **displayUser** мы обязательно должны предоставить для каждого их параметра какое-то определенное значение, которое соответствует типу параметра. Мы не можем, к примеру, вызвать функцию **displayUser**, не передав ей аргументы для параметров, это будет ошибка.
+
+Однако мы можем определить какие-то параметры функции как необязательные и установить для них значения по умолчанию:
+
+```kt
+fun displayUser(name: String, age: Int = 18, position: String="unemployed"){
+    println("Name: $name   Age: $age  Position: $position")
+}
+ 
+fun main(args: Array<String>) {
+    displayUser("Tom", 23, "Manager")
+    displayUser("Alice", 21)
+    displayUser("Kate")
+}
+```
+
+В данном случае функция **displayUser** имеет три параметра для передачи имени, возраста и должности. Для первого параметр *name* значение по умолчанию не установлено, поэтому для него по-прежнему обязательно передавать значение. Два последующих - *age* и *position* являются необязательными, и для них установлено значение по умолчанию. Если для этих параметров не передаются значения, тогда параметры используют значения по умолчанию. Поэтому для этих параметров в принципе нам необязательно передавать аргументы. Но если для какого-то параметра определено значение по умолчанию, то для всех последующих параметров тоже должно быть установлено значение по умолчанию.
+
+Консольный вывод программы
+
+```
+Name: Tom   Age: 23  Position: Manager
+Name: Alice   Age: 21  Position: unemployed
+Name: Kate   Age: 18  Position: unemployed
+```
+
+### Именованные аргументы
+
+По умолчанию значения передаются параметрам по позиции: первое значение - первому параметру, второе значение - второму параметру и так далее. Однако, используя именованные аргументы, мы можем переопределить порядок их передачи параметрам:
+
+```kt
+fun main(args: Array<String>) {
+    displayUser(name="Tom", position="Manager", age=28)
+    displayUser(age=21, name="Alice")
+    displayUser("Kate", position="Middle Developer")
+}
+```
+
+При вызове функции в скобках мы можем указать название параметра и с помощью знака равно передать ему нужное значение.
+
+При этом, как видно из последнего случае, необязательно все аргументы передавать по имени. Часть аргументов могут передаваться параметрам по позиции. Но если какой-то аргумент передан по имени, то остальные также должны передаваться по имени соответствующих параметров.
+
+### Переменное количество параметров. Vararg
+
+Функция может принимать переменное количество параметров одного типа. Для определения таких параметров применяется ключевое слово **vararg**. Например, нам необходимо передать в функцию несколько строк, но сколько именно строк, мы точно не знаем. Их может быть пять, шесть, семь и т.д.:
+
+```kt
+fun printStrings(vararg strings: String){
+    for(str in strings)
+        println(str)
+}
+
+fun main(args: Array<String>) {
+    printStrings("Tom", "Bob", "Sam")
+    printStrings("Kotlin", "JavaScript", "Java", "C#", "C++")
+}
+```
+
+Функция *printStrings* принимает неопределенное количество строк. В самой функции мы можем работать с параметром как с последовательностью строк, например, перебирать элементы последовательности в цикле и производить с ними некоторые действия.
+
+При вызове функции мы можем ей передать любое количество строк.
+
+Другой пример - подсчет суммы неопределенного количества чисел:
+
+```kt
+fun sum(vararg numbers: Int){
+    var result=0
+    for(n in numbers)
+        result += n
+    println("Сумма чисел равна $result")
+}
+
+fun main(args: Array<String>) {
+ 
+    sum(1, 2, 3, 4, 5)
+    sum(1, 2, 3, 4, 5, 6, 7, 8, 9)
+}
+```
+
+Если функция принимает несколько параметров, то обычно **vararg**-параметр является последним.
+
+```kt
+fun printUserGroup(count:Int, vararg users: String){
+    println("Count: $count")
+    for(user in users)
+        println(user)
+}
+ 
+fun main(args: Array<String>) {
+ 
+    printUserGroup(3, "Tom", "Bob", "Alice")
+}
+```
+
+Однако это необязательно, и если после **vararg**-параметра идут еще какие-нибудь параметры, то при вызове функции значения этим параметрам передаются через именованные аргументы:
+
+```kt
+fun printUserGroup(group: String, vararg users: String, count:Int){
+    println("Group: $group")
+    println("Count: $count")
+    for(user in users)
+        println(user)
+}
+fun main(args: Array<String>) {
+ 
+    printUserGroup("KT-091", "Tom", "Bob", "Alice", count=3)
+}
+```
+
+Здесь функция printUserGroup принимает три параметра. Значения параметрам до vararg-параметра передаются по позициям. То есть в данном случае "KT-091" будет представлять значение для параметра group. Последующие значения интерпретируются как значения для vararg-параметра вплоть до именнованных аргументов.
+
+### Оператор *
+
+Оператор ***** (spread operator) (не стоит путать со знаком умножения) позволяет передать параметру в качестве значения элементы из массива:
+
+```kt
+fun printUserGroup(group: String, vararg users: String, count:Int){
+    println("Count: $count")
+    for(user in users)
+        println(user)
+}
+
+fun main(args: Array<String>) {
+    val users = arrayOf("Tom", "Bob", "Alice")
+    printUserGroup("MO-011", *users, count=3)
+}
+```
+
+Обратите внимание на звездочку перед users при вызове функции: printUserGroup("MO-011", *users, count=3). Без применения данного оператора мы столкнулись бы с ошибкой, поскольку параметры функции представляют не массив, а неопределенное количество строк.
+
+### Возвращение результата. Оператор return
+
+Функция может возвращать некоторый результат. В этом случае после списка параметров через двоеточие указывается возвращаемый тип. А в теле функции применяется оператор **return**, после которого указывается возвращаемое значение.
+
+Например, определим функцию, которая возвращает факториал числа:
+
+```kt
+// функция возвращает значение типа Int
+fun factorial(n: Int) : Int 
+{ 
+    var result = 1;
+    for(d in 1..n){
+        result *= d
+    }
+    return result       //  возвращение значения
+}
+ 
+fun main(args: Array<String>) {
+ 
+    val a = factorial(4)
+    val b = factorial(5)
+    val c = factorial(6)
+    println("a=$a  b=$b  c=$c")
+}
+```
+
+В объявлении функции *factorial* после списка параметров через двоеточие указывается тип **Int**, который будет представлять тип возвращаемого значения.
+
+Так как функция возвращает значение, то при ее вызове это значение можно присвоить переменной:
+
+```kt
+val a = factorial(4)
+```
+
+### Тип Unit
+
+Если функция не возвращает какого-либо результата, то фактически неявно она возвращает значение типа **Unit**. Этот тип аналогичен типу **void** в ряде языков программирования, которое указывает, что функция ничего не возвращает. Например, следующая функция
+
+```kt
+fun hello()
+{
+    println("Hello")
+}
+```
+
+будет аналогична следующей:
+
+```kt
+fun hello() : Unit
+{
+    println("Hello")
+}
+```
+
+Формально мы даже можем присвоить результат такой функции переменной:
+
+```kt
+val d = hello()
+val e = hello()
+```
+
+Однако практического смысла это не имеет, так как возвращаемое значение представляет объект **Unit**, который больше никак не применяется.
+
+Если функция возвращает значение **Unit**, мы также можем использовать оператор **return** для возврата из функции:
+
+```kt
+fun factorial(n: Int){
+    if(n < 1){
+        println("Incorrect input parameter")
+        return
+    }
+    var result = 1;
+    for(d in 1..n){
+        result *= d
+    }
+    println("Factorial of $n is equal $result")
+}
+```
+
+В данном случае если значение параметра n меньше 1, то с помощью оператора **return** осуществляется выход из функции, и последующие инструкции не выполняются. При этом если функция возвращает значение Unit, то после оператора **return** можно не указывать никакого значения.
+
+### Однострочные функции
+
+Однострочные функции (single expression function) используют сокращенный синтаксис определения функции в виде одного выражения. Эта форма позволяет опустить возвращаемый тип и оператор **return**.
+
+```
+fun имя_функции (параметры_функции) = тело_функции
+```
+
+Функция также определяется с помощью ключевого слова **fun**, после которого идет имя функции и список параметров. Но после списка параметров не указывается возвращаемый тип. Возвращаемый тип будет выводится компилятором. Далее через оператор присвоения = определяется тело функции в виде одного выражения.
+
+Например, функция возведения числа в квадрат:
+
+```kt
+fun double(x: Int) = x * x
+
+fun main(args: Array<String>) {
+ 
+    val a = double(5)   // 25
+    val b = double(6)   // 36
+    println("a=$a  b=$b")
+}
+```
+
+В данном случае функция **double** возводит число в квадрат. Она состоит из одного выражения **x * x**. Значение этого выражения и будет возвращаться функцией. При этом оператор **return** не используется.
+
+Такие функции более лаконичны, более читабельны, но также опционально можно и указывать возвращаемый тип явно:
+
+```kt
+fun double(x: Int) : Int = x * x
+```
+
+### Область действия функций
+
+В **Kotlin** функции могут быть объявлены в самом начале файла. Подразумевается, что вам не обязательно создавать объект какого-либо класса, чтобы воспользоваться его функцией (как в Java, C# или Scala). В дополнение к этому, функции в языке Kotlin могут быть объявлены локально, как функции-члены (ориг. "member functions") и функции-расширения ("extension functions").
+
+### Локальные функции
+
+Одни функции могут быть определены внутри других функций. Внутренние или вложенные функции еще называют локальными.
+
+Локальные функции могут определять действия, которые используются только в рамках какой-то конкретной функции и нигде больше не применяются.
+
+Например, функция принимает на вход основание и высоту двух треугольников и должна вычислить, больше ли площадь первого треугольника, чем второго:
+
+```kt
+fun isFirstGreater(base1: Double, height1: Double, base2: Double, height2: Double): Boolean{
+ 
+    fun square(base: Double, height: Double) = base * height / 2
+     
+    return square(base1, height1) > square(base2, height2)
+}
+
+fun main(args: Array<String>) {
+    val a = isFirstGreater(10.0, 10.0, 20.0, 20.0)
+    val b = isFirstGreater(20.0, 20.0, 10.0, 10.0)
+    println("a=$a  b=$b")
+}
+```
+
+Для промежуточных вычислений - вычисления площади каждого отдельного треугольника в функции *isFirstGreater* определена вспомогательная функция *square*. Больше в программе эта функция нигде не используется, поэтому ее можно сделать локальной.
+
+При этом локальная может использоваться только в той функции, где она определена.
+
+### Перегрузка функций
+
+Перегрузка функций (function overloading) представляет определение нескольких функций с одним и тем же именем, но с различными параметрами. Параметры перегруженных функций могут отличаться по количеству, типу или по порядку в списке параметров.
+
+```kt
+fun add(a: Int, b: Int) : Int{
+    return a + b
+}
+fun add(a: Double, b: Double) : Double{
+    return a + b
+}
+fun add(a: Int, b: Int, c: Int) : Int{
+    return a + b + c
+}
+fun add(a: Int, b: Double) : Double{
+    return a + b
+}
+fun add(a: Double, b: Int) : Double{
+    return a + b
+}
+```
+
+В данном случае для одной функции **add** определено пять перегруженных версий. Каждая из версий отличается либо по типу, либо количеству, либо по порядку параметров. При вызове функции **add** компилятор в зависимости от типа и количества параметров сможет выбрать для выполнения нужную версию:
+
+```kt
+fun main(args: Array<String>) {
+ 
+    val a = add(1, 2)
+    val b = add(1.5, 2.5)
+    val c = add(1, 2, 3)
+    val d = add(2, 1.5)
+    val e = add(1.5, 2)
+}
+```
+
+При этом при перегрузке не учитывает возвращаемый результат функции. Например, пусть у нас будут две следующие версии функции **add**:
+
+```kt
+fun add(a: Double, b: Int) : Double{
+    return a + b
+}
+fun add(a: Double, b: Int) : String{
+    return "$a + $b"
+}
+```
+
+Они совпадают во всем за исключением возвращаемого типа. Однако в данном случае мы сталкивамся с ошибкой, так как перегруженные версии должны отличаться именно по типу, порядку или количеству параметров. Отличие в возвращаемом типе не имеют значения.
+
+### Лямбда-выражения
+
+Лямбда-выражения представляют небольшие кусочки кода, которые выполняют некоторые действия. Фактически лямбды преставляют сокращенную запись функций. При этом лямбды могут передаваться в качестве параметра в функции.
+
+Лямбда-выражения оборачиваются в фигурные скобки:
+
+```kt
+{println("hello")}
+```
+
+В данном случае лямбда-выражение выводит на консоль строку "hello".
+
+Лямбда-выражение можно сохранить в обычную переменную и затем вызывать через имя этой переменной как обычную функцию.
+
+```kt
+fun main(args: Array<String>) {
+ 
+    val hello = {println("hello")}
+    hello()
+    hello()
+}
+```
+
+В данном случае лямбда сохранена в переменную hello и через эту переменную вызывается два раза.
+
+Также лямбда-выражение можно выполнить сразу при определении с помощью оператора run:
+
+```kt
+fun main(args: Array<String>) {
+ 
+    run {println("hello")}
+}
+```
+
+### Передача параметров
+
+Лямбды как и функции могут принимать параметры. Для передачи параметров используется стрелка **->**. Параметры указываются слева от стрелки, а тело лямбда-выражения, то есть сами выполняемые действия, справа от стрелки.
+
+```kt
+fun main(args: Array<String>) {
+ 
+    val printer = {message: String -> println(message)}
+    printer("Hello")
+    printer("Good Bye")
+}
+```
+
+Здесь лямбда-выражение принимает один параметр типа **String**, значение которого выводится на консоль.
+
+Если параметров несколько, то они передаются слева от стрелки через запятую:
+
+```kt
+fun main(args: Array<String>) {
+ 
+    val sum = {x:Int, y:Int -> println(x + y)}
+    sum(2, 3)   // 5
+    sum(4, 5)   // 9
+}
+```
+
+Если в лямбда-выражении надо выполнить не одно, а несколько действий, то эти действия можно размещать на отдельных строках после стрелки:
+
+```kt
+val sum = {x:Int, y:Int ->
+    val result = x + y
+    println("$x + $y = $result")
+}
+```
+
+### Возвращение результата
+
+Выражение, стоящее после стрелки, определяет результат лямбда-выражения. И этот результат мы можем присвоить, например, переменной.
+
+Если лямбда-выражение формально не возвращает никакого результата, то фактически, как и в функциях, возвращается значение типа **Unit**:
+
+```kt
+val hello = { println("Hello")}
+val h = hello()             // h представляет тип Unit
+ 
+val printer = {message: String -> println(message)}
+val p = printer("Welcome")    // p представляет тип Unit
+```
+
+В обоих случаях используется функция println, которая формально не возвращает никакого значения (точнее возвращает объект типа Unit).
+
+Но также может возвращаться конкретное значение:
+
+```kt
+fun main(args: Array<String>) {
+ 
+    val sum = {x:Int, y:Int -> x + y}
+     
+    val a = sum(2, 3)   // 5
+    val b = sum(4, 5)   // 9
+    println("a=$a  b=$b")
+}
+```
+
+Здесь выражение справа от стрелки x + y продуцирует новое значение - сумму чисел, и при вызове лямбда-выражения это значение можно передать переменной.
+
+Если лямбда-выражение многострочное, состоит из нескольких инструкций, то возвращается то значение, которое генерируется последней инструкцией:
+
+```kt
+val sum = {x:Int, y:Int ->
+    val result = x + y
+    println("$x + $y = $result")
+    result
+}
+```
+
+Последнее выражение по сути представляет число - сумму чисел x и y и оно будет возвращаться в качестве результата лямбда-выражения.
+
+### Функции высокого порядка
+
+Функции высокого порядка (high order function) - это функции, которые либо принимают функцию в качестве параметра, либо возвращают функцию, либо и то, и другое.
+
+### Тип функции
+
+Для определения функций высокого порядка прежде всего необходимо представлять, что такое тип функции. Тип функции определяется следующим образом:
+
+```
+(типы_параметров) -> возвращаемый_тип
+```
+
+Например, возьмем следующее лямбда-выражение:
+
+```
+{mes:String-> println(mes)}
+```
+
+Это лямбда-выражение принимает в качестве параметра строку и формально ничего не возвращает (точнее возвращаемым типом является Unit). Поэтому тип этого выражения будет следующий:
+
+```
+(String)-> Unit
+```
+
+Другой пример: лямбда-выражение принимает два числа и возвращает их сумму:
+
+```
+x: Int, y: Int -> x+y
+```
+
+Это выражение будет иметь следующий тип:
+
+```
+(Int, Int) -> Int
+```
+
+Если лямбда-выражение не принимает никаких параметров, то указываются пустые скобки:
+
+```
+() -> Unit
+```
+
+К примеру, этому типу будет соответствовать лямбда-выражение {println("hello")}
+
+При определении лямбда-выражения и присвоении его переменной мы можем явным образом у этой переменной указать тип:
+
+```kt
+val sum: (Int, Int)-> Int = {x:Int, y: Int -> x+y}
+val printer: (String) -> Unit = {message: String -> println(message)}
+```
+
+Правда, в данном случае тип можно не указывать, так как компилятор может сам вывести тип переменной.
+
+### Передача лямбда-выражения в функцию
+
+Для передачи лямбда-выражения в функцию, необходимо определить у функции параметр, тип которого соответствует типу лямбда-выражения:
+
+```kt
+fun main(args: Array<String>) {
+ 
+    val add = {x:Int, y: Int -> x+y}
+    val multiply = {x:Int, y: Int -> x*y}
+ 
+    action(5, 3, add)
+    action(5, 3, multiply)
+    action(5, 3, {x: Int, y: Int -> x -y})
+}
+ 
+fun action (n1: Int, n2: Int, operation: (Int, Int)-> Int){
+    val result = operation(n1, n2)
+    println(result)
+}
+```
+
+В данном случае функция action определяет три параметра. Первый два параметра - числа, а третий параметр - некоторая операция, которая производится над этими числами. На момент определения функции можно не знать, что это будет за операция. Это может быть любое лямбда-выражение, которое принимает два объекта типа Int и возвращает также объект типа Int.
+
+В самой функции action вызываем эту операцию, передавая ей два числа, и полученный результат выводим на консоль.
+
+При вызове функции action мы можем передать для ее третьего параметра лямбда-выражение, которое соответствует этому параметру по типу:
+
+```kt
+action(5, 3, add)
+action(5, 3, multiply)
+action(5, 3, {x: Int, y: Int -> x -y})
+```
+
+### Возвращение функции из функции
+
+В более редких случаях может потребоваться возвратить функцию из другой функции. В этом случае для функции в качестве возвращаемого типа устанавливается тип другой функции. А в теле функции возвращается лямбда выражение. Например:
+
+```kt
+fun selectAction(key: Int): (Int, Int) -> Int{
+    // определение возвращаемого результата
+    when(key){
+        1 -> return {x:Int, y: Int -> x + y}
+        2 -> return {x:Int, y: Int -> x - y}
+        3 -> return {x:Int, y: Int -> x * y}
+        else -> return  {x:Int, y: Int -> 0}
+    }
+}
+```
+
+Здесь функция selectAction принимает один параметр - key, который представляет тип Int. В качестве возвращаемого типа у функции указан тип (Int, Int) -> Int. То есть selectAction будет возвращать некую функцию, которая принимает два параметра типа Int и возвращает объект типа Int.
+
+В теле функции selectAction в зависимости от значения параметра key возвращается определенное лямбда-выражение, которое соответствует типу (Int, Int) -> Int.
+
+Используем данную функцию:
+
+```kt
+fun main(args: Array<String>) {
+ 
+    var action = selectAction(1)
+    println(action(8,5))    // 13
+ 
+    action = selectAction(2)
+    println(action(8,5))    // 3
+ 
+}
+fun selectAction(key: Int): (Int, Int) -> Int{
+    // определение возвращаемого результата
+    when(key){
+        1 -> return {x:Int, y: Int -> x + y}
+        2 -> return {x:Int, y: Int -> x - y}
+        3 -> return {x:Int, y: Int -> x * y}
+        else -> return  {x:Int, y: Int -> 0}
+    }
+}
+```
+
+Здесь переменная action хранит результат функции selectAction. Так как selectAction возвращает лямбда-выражение, то и переменная action будет хранить определенное лямбда-выражение. Затем через переменную action можно вызвать это лямбда-выражение. Поскольку лямбда-выражение соответствует типу (Int, Int) -> Int, то при его вызове ему необходимо передать два числа и соответственно мы можем получить его результат и вывести его на консоль.
+
+### Анонимные функции
+
+Анонимные функции выглядят как обычные за тем исключением, что они не имеют имени. Анонимная функция может иметь одно выражение:
+
+```kt
+fun(x: Int, y: Int): Int = x + y
+```
+
+Либо может представлять блок кода:
+
+```kt
+fun(x: Int, y: Int): Int{ 
+    return x + y
+}
+```
+
+Анонимные функции используется только в качестве аргументов в других функциях. Например:
+
+```kt
+fun main(args: Array<String>) {
+ 
+    operation(9, 5, fun(x: Int, y: Int): Int { return x + y })   // 14
+    operation(9, 5, fun(x: Int, y: Int): Int = x - y)            // 4
+}
+fun operation(x: Int, y: Int, op: (Int, Int) ->Int){
+ 
+    val result = op(x, y)
+    println(result)
+}
+```
+
+Функция operation принимает три параметра. Первые два параметра - числа, а третий параметр - функция, которая выполняет некоторые действия над этими числами.
+
+При вызове функции operation для третьего параметра в качестве аргумента передается анонимная функция, которая соответствует этому параметру по типу: (Int, Int) ->Int. То есть анонимная функция должна принимать два парамтра типа Int и возвращать значение типа Int.
+
+Передача анонимной функции в данном случае аналогична передачи лямбда-выражения, в то же время лямбда-выражения представляют более лаконичный синтаксис, поэтому, как правило, в этой роли применяются лямбда-выражения.
+
+# Исключения
+
+В любой, особенно большой, программе могут возникать ошибки, приводящие к ее неработоспособности или к тому, что программа делает не то, что должна. Причин возникновения ошибок много.
+
+Программист может сделать ошибку в употреблении самого языка программирования. Другими словами, выразиться так, как выражаться не положено. Например, начать имя переменной с цифры или забыть поставить двоеточие в заголовке сложной инструкции. Подобные ошибки называют синтаксическими, они нарушают синтаксис и пунктуацию языка. IDE, встретив ошибочное выражение, не знает как его интерпретировать. Поэтому  выводит соответствующее сообщение, указав на место возникновения ошибки.
+
+Но случаются ошибки, которые происходят во время выполнения программы, например, деление на 0 или попытка открыть несуществующий файл. В таких случаях JVM "выбрасывает" исключение.
+
+На этот случай в языках программирования, в том числе Котлин, существует специальный оператор, позволяющий перехватывать возникающие исключения и обрабатывать их так, чтобы программа продолжала работать или корректно завершала свою работу.
+
+## Классы исключений
+
+Все исключения в Kotlin являются наследниками класса **Throwable**. У каждого исключения есть сообщение, трассировка стека, а также причина, по которой это исключение вероятно было вызвано.
+
+Обычно исключения вызываются системой (деление на 0, попытка открыть не существующий файл...), но можно и самому возбудить исключение явным образом, для этого используется оператор **throw**
+
+```kt
+throw MyException("Hi There!")
+```
+
+## try
+
+Оператор try позволяет перехватывать исключения
+
+```kt
+try {
+    // при возникновении исключения в этом блоке кода выполнение программы перейдет в блок catch
+}
+catch (e: SomeException) {
+    // handler
+}
+catch (e: AnotherException) {
+    // handler
+}
+finally {
+    // этот блок выполнится всегда
+}
+```
+
+В коде может быть любое количество блоков catch (такие блоки могут и вовсе отсутствовать). Блоки finally могут быть опущены. Однако, должен быть использован как минимум один блок catch или finally.
+
+## Try - это выражение
+
+Ключевое слово try является выражением, то есть оно может иметь возвращаемое значение.
+
+```kt
+val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
+```
+
+Возвращаемым значением будет либо последнее выражение в блоке try, либо последнее выражение в блоке catch (или блоках). Содержимое finally блока никак не повлияет на результат try-выражения.
+
+# Регулярные выражения в Котлине
+
+Для описания регулярных выражений в Котлине используется тип **Regex**. Для создания регулярного выражения следует вызвать его конструктор, например Regex("KotlinAsFirst"). Второй способ создания регулярного выражения — вызов функции toRegex() на строке-получателе, например "KotlinAsFirst".toRegex().
+
+При создании регулярных выражений вместо обычных строк в двойных кавычках рекомендуется использовать так называемые raw string literals (необработанные строки). Перед и после такого литерала должны стоять три двойных кавычки. Внутри необработанных строк не применяется экранирование, что позволяет применять специфичные для регулярных выражений символы без дополнительных ухищрений. Например: Regex("""x|+|-|\*|/|\(|\)|\d+?| +?""") — задаёт выражение x, или +, или -, или …​, или число, или любое количество пробелов. Без тройных кавычек нам пришлось бы дважды записать каждый из \.
+
+```kt
+fun main(args: Array<String>){
+    // регулярное выражение создано конструтором с одним флагом
+    val regex = Regex("""(\d+)""", RegexOption.IGNORE_CASE)
+    val res = regex.find("найдет только число 99, а число 22 не найдет")
+    if(res!=null)
+        for (r in res.groupValues)
+            println(r)
+}
+```
+
+Программа выдаст:
+
+```
+99
+99
+```
+
+Во втором варианте создадим регулярное выражение через метод строки:
+
+```kt
+fun main(args: Array<String>){
+    val regex = """(\d+)""".toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
+    val res = regex.findAll("найдет число 99, и число 22 тоже")
+    if(res!=null)
+        for (r in res)
+            for(r2 in r.groupValues.drop(1))
+                println(r2)
+}
+```
+
+Программа выдаст:
+
+```
+99
+22
+```
+
+Для анализа результата поиска применяется тип MatchResult, который можно получить, вызвав find на регулярном выражении-получатале: Regex("""…​""").find(string, startIndex). find ищет первое вхождение регулярного выражения в строку string, начиная с индекса startIndex (по умолчанию — 0). Если вхождений регулярного выражения не найдено, результат find равен null.
+
+Regex("""…​""").findAll(string, startIndex) ищет ВСЕ вхождения регулярного выражения, которые после этого можно перебрать с помощью цикла for.
+
+Тип MatchResult включает в себя следующие свойства:
+
+* result.value — подстрока исходной строки, с которой совпало регулярное выражение (совпадение)
+* result.range — интервал индексов символов, в котором было найдено совпадение
+* result.groupValues — список строк, 0-й элемент которого содержит всё регулярное выражение, а последующие содержат значения групп поиска из регулярного выражения (то есть размер списка равен числу групп поиска в выражении + 1)
+
+Некоторые другие полезные методы, связанные:
+
+* Regex("""…​""").replace("MyString", "Replacement") — находит в данной строке все вхождения регулярного выражения и заменяет их на `"Replacement"
+* "MyString".contains(Regex("""…​""")) — есть ли в данной строке хоть одно вхождение регулярного выражения
+* Regex("""…​""").containsMatchIn("MyString") — то же самое, но в другом порядке
+* "MyString".matches(Regex("""…​""")) — соответствует ли данная строка данному регулярному выражению
+* Regex("""…​""").matches("MyString") — то же самое, но в другом порядке
+* Regex("""…​""").split("MyString") — деление строки на части с использованием заданного регулярного выражения как разделителя
+
+Мини-пример:
+
+```kt
+fun timeStrToSeconds(str: String): Int {
+    val matchResult = Regex("""(\d\d):(\d\d):(\d\d)""").find(str)
+    if (matchResult == null) return -1
+    return matchResult.groupValues.drop(1).map { it.toInt() }.fold(0) {
+        previous, next -> previous * 60 + next
+    }
+}
+```
+
+Здесь мы разбираем исходную строку вида «12:34:56» с целью найти в ней три одинаковых группы поиска (\d\d). Каждая из групп поиска включает в себя две цифры. Убедившись с помощью проверки на null, что регулярное выражение успешно найдено, мы отбрасываем первый элемент groupValues с помощью функции drop(1), оставляя, таким образом, в списке только значения трёх групп поиска. Далее каждая из пар цифр конвертируется в число. Результат сворачивается в число секунд, прошедших с начала дня, с помощью функции высшего порядка fold
+
+# Асинхронное выполнение кода (Корутины)
+
+Создание слишком большого количества потоков может на самом деле сделать приложение неэффективным в некоторых ситуациях; потоки - это объекты, которые накладывают накладные расходы во время размещения объектов и сборки мусора.
+
+Чтобы преодолеть эти проблемы, **Kotlin** представил новый способ написания асинхронного неблокирующего кода: **Coroutine** (сопрограммы).
+
+Подобно потокам, сопрограммы могут работать одновременно, ожидать и общаться друг с другом, с той разницей, что их создание намного дешевле, чем потоков.
+
+Сопрограммы обеспечивают возможность избежать блокировки исполняющегося потока путём использования более дешёвой и управляемой операции: приостановки (suspend) сопрограммы.
+
+Сопрограммы упрощают асинхронное программирование, оставив все осложнения внутри библиотек. Логика программы может быть выражена последовательно в сопрограммах, а базовая библиотека будет её реализовывать асинхронно. Библиотека может обернуть соответствующие части кода пользователя в обратные вызовы (callbacks), подписывающиеся на соответствующие события, и диспетчировать исполнение на различные потоки (или даже на разные машины!). Код при этом останется столь же простой, как если бы исполнялся строго последовательно.
+
+Kotlin, как язык, предоставляет только минимальные низкоуровневые API в своей стандартной библиотеке, чтобы позволить различным другим библиотекам использовать сопрограммы. 
+
+Для работы с корутинами сделана библиотека kotlinx.coroutines. Она содержит ряд высокоуровневых примитивов с поддержкой корутин, которые рассматриваются ниже, включая запуск, асинхронность и другие.
+
+Для того, чтобы использовать библиотеку в своем проекте, необходимо добавить ее в зависимости:
+
+```json
+//build.graddle
+dependencies {
+    ...
+    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
+}
+```
+
+[дальше перевод официальной доки](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html)
+
+
+## Основы
+
+Здесь рассмотрим основные концепции.
+
+### Наша первая корутина
+
+Запустите следующий код:
+
+```kt
+import kotlinx.coroutines.*
+
+fun main(){
+    // запускает новую корутину в фоне и ПРОДОЛЖАЕТ выполнение
+    GlobalScope.launch {
+        // НЕ блокирующая задержака на 1 секунду
+        delay(1_000L) 
+        println("World!") 
+    }
+    // код в основном потоке продолжает выполняться
+    println("Hello,")
+    // БЛОКИРУЮЩАЯ задержка на 2 сек
+    Thread.sleep(2_000L)
+}
+```
+
+Увидим следующий результат:
+
+```
+Hello,
+World! //выведет через секунду
+```
+
+По существу, корутины - это легкие потоки. Они запускаются с помощью launch coroutine builder в контексте некоторой области видимости Coroutine. Здесь мы запускаем новую сопрограмму в глобальной области, что означает, что время жизни новой сопрограммы ограничено только временем жизни всего приложения.
+
+Аналогичный результат можно получить заменив *GlobalScope.launch { ... }* на *thread { ... }* и *delay(...)* на *Thread.sleep(...)*.
+
+Если попробовать использовать *delay(...)* в обычных потоках, то система выбросит исключение: Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function.
+
+Это происходит потому, что *delay* это специальная *suspending function*, которая не блокирует поток, но приостанавливает корутину.
+
+### Смешивание блокирующих и неблокирующих методов
+
+В первом примере мы смешивали блокирующие и не-блокирующие задержки. Чтобы не запутаться какой вызов блокирующий, а какой нет, можно явно указать блокирующий вызов используя *runBlocking*:
+
+```kt
+import kotlinx.coroutines.*
+
+fun main(){
+    // запускает новую корутину в фоне и ПРОДОЛЖАЕТ выполнение
+    GlobalScope.launch {
+        // НЕ блокирующая задержака на 1 секунду
+        delay(1_000L)
+        println("World!")
+    }
+    // код в основном потоке продолжает выполняться
+    println("Hello,")
+
+    // это выражение блокирует основной поток 
+    runBlocking {     
+        delay(2_000L)
+    }
+}
+```
+
+Этот пример можно переписать по-другому:
+
+```kt
+fun main() = runBlocking<Unit> {
+    GlobalScope.launch {
+        delay(1000L)
+        println("World!")
+    }
+    println("Hello,")
+    delay(2000L)
+}
+```
+
+Мы весь код функции пометили как блокирующий.
+
+### Ожидание выполнения работы
+
+Жесткое задание времени ожидания корутины плохая практика (нас извиняет что других способов мы пока не знаем).
+
+Можно явно дождаться (не блокируя) завершения фонового задания:
+
+```kt
+fun main() = runBlocking<Unit> {
+    val job = GlobalScope.launch {
+        delay(1000L)
+        println("World!")
+    }
+    println("Hello,")
+    // ждем, пока выполнится корутина
+    job.join()
+}
+```
+
+Теперь функция завершится сразу, как только завершится корутина.
+
+### Structured concurrency (упорядоченный параллелизм?)
+
+Как бы ни мало процессорного времени занимали корутины, но все-равно они потребляют ресурсы (основного потока!), особенно если их много, а тем более если какие-то корутины из-за внутренних ошибок работают неправильно. Хранить ссылки на все корутины муторно и может привести к ошибкам.
+
+Есть лучшее решение. Мы можем использовать *Structured concurrency* в нашем коде. Вместо того чтобы запускать сопрограммы в глобальной области, как мы обычно делаем с потоками (потоки всегда глобальны), мы можем запускать сопрограммы в конкретной области выполняемой нами операции.
+
+В этом примере мы запускаем функцию *main* в корутине используя runBlocking Coroutine Builder (хз как это правильно перевести, строитель сопрограмм звучит криво). Каждый билдер добавляет CoroutineScope в область блока кода. Мы можем выполнять корутину без вызова метода *join*, т.к. внешняя корутина (runBlocking в нашем случае) не завершится, пока полностью не выполнится корутина запущенная в области. Т.о. мы можем упростить код:
+
+```kt
+fun main() = runBlocking<Unit> {//this: CoroutineScope
+    launch {
+        delay(1000L)
+        println("World!")
+    }
+    println("Hello,")
+    // убрали join
+}
+```
+
+### Scope builder
+
+В дополниние к областям предоставленным разными билдерами, мы можем объявить свою область, используя билдер coroutineScope. Он создает область и не завершает ее, пока не завершатся все вложенные корутины (дети).
+
+*runBlocking* и *coroutineScope* выглядят похоже, т.к. обе ждут, пока не выполнится блок кода и вложенных детей. Основное отличие между ними в том, что *runBlocking* **блокирует** текущий поток, тогда как *coroutineScope* просто ожидает... 
+
+```kt
+fun main() = runBlocking<Unit> {
+    launch {
+        delay(200L)
+        println("[${Thread.currentThread().name}] Task from runBlocking")
+    }
+
+    coroutineScope { // Creates a coroutine scope
+        launch {
+            delay(500L)
+            println("[${Thread.currentThread().name}] Task from nested launch")
+        }
+
+        delay(100L)
+        println("[${Thread.currentThread().name}] Task from coroutine scope") // This line will be printed before the nested launch
+    }
+
+    println("[${Thread.currentThread().name}] Coroutine scope is over")
+}
+```
+
+```
+[main] Task from coroutine scope
+[main] Task from runBlocking
+[main] Task from nested launch
+[main] Coroutine scope is over
+```
+
+Обратите внимание, что сразу после сообщения "Task from coroutine scope", в ожидании вложенного запуска, выполняется и печатается "Task from runBlocking", хотя coroutineScope еще не завершен.
+
+### Оформим код в функцию
+
+Просто вынесем содержимое блока кода внутри *launch {...}* в отдельную функцию. Для этой функции указываем модификатор *suspend*. Suspending функция может быть использована внутри корутин как обычная функция, но в ней доступны дополнительные возможности, такие как вызов других *suspend* функций, например *delay*.
+
+```kt
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+    launch { doWorld() }
+    println("Hello,")
+}
+
+// это наша первая suspending функция
+suspend fun doWorld() {
+    delay(1000L)
+    println("World!")
+}
+```
+
+### Корутины ОЧЕНЬ легкие
+
+Предлагается выполнить следующий код:
+
+```kt
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+    repeat(100_000) { // launch a lot of coroutines
+        launch {
+            delay(1000L)
+            print(".")
+        }
+    }
+}
+```
+
+Выполнится за секунду. А если использовать обычные потоки, то скорее всего получим ошибку нехватки памяти.
+
+### Глобальные корутины похожи на "демонов"
+
+```kt
+GlobalScope.launch {
+    repeat(1000) { i ->
+        println("I'm sleeping $i ...")
+        delay(500L)
+    }
+}
+delay(1300L) // just quit after delay
+```
+
+На выходе мы получим только 3 строки.
+
+```
+I'm sleeping 0 ...
+I'm sleeping 1 ...
+I'm sleeping 2 ...
+```
+
+Корутины запущенные в *GlobalScope* не препятсствуют завершению процесса. Этим они и похожи на "демонов".
+
+## Прерывание работы и таймауты
+
+### Прерывание работы корутины
+
+Мы можем не дожидаться выполнения корутины и в любое время завершить ее вызвав методом *cancel()* (GlobalScope не указан, значит корутина не в режиме демона и не даст завершиться приложению, пока не доработает до конца)
+
+```kt
+fun main() = runBlocking<Unit> {
+    val job = launch {
+        repeat(1000) { i ->
+            println("job: I'm sleeping $i ...")
+            delay(500L)
+        }
+    }
+    delay(1300L) // delay a bit
+    println("main: I'm tired of waiting!")
+    job.cancel() // cancels the job
+    job.join() // waits for job's completion
+    println("main: Now I can quit.")
+}
+```
+
+```
+job: I'm sleeping 0 ...
+job: I'm sleeping 1 ...
+job: I'm sleeping 2 ...
+main: I'm tired of waiting!
+main: Now I can quit.
+```
+
+> У класса *Job* есть еще метод *cancelAndJoin*
+
+### Прерывания кооперативны
+
+Это означает что ЛЮБАЯ корутина может быть прервана, но для этого она должна проверять свое состояние. Ниже приведен пример корутины, которая несмотря на вызов *cancelAndJoin* прервана не будет:
+
+```kt
+val startTime = System.currentTimeMillis()
+val job = launch(Dispatchers.Default) {
+    var nextPrintTime = startTime
+    var i = 0
+    while (i < 5) { // computation loop, just wastes CPU
+        // print a message twice a second
+        if (System.currentTimeMillis() >= nextPrintTime) {
+            println("job: I'm sleeping ${i++} ...")
+            nextPrintTime += 500L
+        }
+    }
+}
+delay(1300L) // delay a bit
+println("main: I'm tired of waiting!")
+job.cancelAndJoin() // cancels the job and waits for its completion
+println("main: Now I can quit.")
+```
+
+### Делаем корутину прерываемой
+
+Существует два способа. Первый что-то с yield, но примера нет. Второй - периодически проверять состояние прерывания:
+
+Заменим проверку счетчика на проверку состояния (вообще это не хорошо, лучше бы обе проверки оставить...):
+
+```kt
+val startTime = System.currentTimeMillis()
+val job = launch(Dispatchers.Default) {
+    var nextPrintTime = startTime
+    var i = 0
+    while (isActive) { // cancellable computation loop
+        // print a message twice a second
+        if (System.currentTimeMillis() >= nextPrintTime) {
+            println("job: I'm sleeping ${i++} ...")
+            nextPrintTime += 500L
+        }
+    }
+}
+delay(1300L) // delay a bit
+println("main: I'm tired of waiting!")
+job.cancelAndJoin() // cancels the job and waits for its completion
+println("main: Now I can quit.")
+```
+
+Свойство *isAlive* видно внутри корутины через объект CoroutineScope.
+
+### Освобождайте ресурсы с помощью try .. finally
+
+При прерывании корутины возникает исключение *CancellationException*, поэтому если есть какие-то открытые ресурсы, то их нужно завернуть в блок try .. finally (в примере ниже открытых ресурсов нет)
+
+```kt
+val job = launch {
+    try {
+        repeat(1000) { i ->
+            println("job: I'm sleeping $i ...")
+            delay(500L)
+        }
+    } finally {
+        println("job: I'm running finally")
+    }
+}
+delay(1300L) // delay a bit
+println("main: I'm tired of waiting!")
+job.cancelAndJoin() // cancels the job and waits for its completion
+println("main: Now I can quit.")
+```
+
+И метод *join* и метод *cancelAndJoin* ждут завершения блока finally (в принципе это и так понятно - они ждут завершения ВСЕЙ корутины)
+
+### Выполнение не прерываемого блока
+
+Любая попытка использовать suspending функцию в блоке finally предыдущего примера вызывает исключение *CancellationException*, поскольку корутина, выполняющая этот код, в состоянии отмены. Как правило, это не проблема, так как все хорошо работающие операции закрытия (закрытие файла, отмена задания или закрытие любого канала связи) обычно не блокируются и не включают в себя какие-либо функции приостановки. Однако в редких случаях, когда вам нужно приостановить работу в отмененной корутине, вы можете обернуть соответствующий код в *withContext (Nonancellable) {...}* используя withContext функции и NonCancellable контекст, как показано в следующем примере:
+
+```kt
+val job = launch {
+    try {
+        repeat(1000) { i ->
+            println("job: I'm sleeping $i ...")
+            delay(500L)
+        }
+    } finally {
+        withContext(NonCancellable) {
+            println("job: I'm running finally")
+            delay(1000L)
+            println("job: And I've just delayed for 1 sec because I'm non-cancellable")
+        }
+    }
+}
+delay(1300L) // delay a bit
+println("main: I'm tired of waiting!")
+job.cancelAndJoin() // cancels the job and waits for its completion
+println("main: Now I can quit.")
+```
+
+### Таймаут
+
+Наиболее очевидная практическая причина отмены выполнения корутины заключается в том, что время ее выполнения превысило некоторый тайм-аут. В то время как вы можете вручную отслеживать ссылку на соответствующее задание и запускать отдельную корутину для отмены отслеживаемого после задержки, есть готовая к использованию функция withTimeout, которая делает это. Рассмотрим следующий пример:
+
+```kt
+withTimeout(1300L) {
+    repeat(1000) { i ->
+        println("I'm sleeping $i ...")
+        delay(500L)
+    }
+}
+```
+
+Программа выдаст:
+
+```
+I'm sleeping 0 ...
+I'm sleeping 1 ...
+I'm sleeping 2 ...
+Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
+```
+
+Исключение *TimeoutCancellationException*, создаваемое *withTimeout*, является подклассом исключения *CancellationException*. Внутри отмененной сопрограммы исключение CancellationException считается нормальной причиной завершения сопрограммы. Однако в этом примере мы использовали withTimeout прямо внутри основной функции.
+
+Поскольку отмена - это всего лишь исключение, все ресурсы закрываются обычным способом. Вы можете обернуть код с таймаутом в *try {...} catch (e: TimeoutCancellationException) {...}* блок, если необходимо выполнить какое-либо дополнительное действие специально для любого вида таймаута или использовать функцию withTimeoutOrNull, которая аналогична функции withTimeout, но возвращает значение null при таймауте, а не создает исключение:
+
+```kt
+val result = withTimeoutOrNull(1300L) {
+    repeat(1000) { i ->
+        println("I'm sleeping $i ...")
+        delay(500L)
+    }
+    "Done" // will get cancelled before it produces this result
+}
+println("Result is $result")
+```
+
+```
+I'm sleeping 0 ...
+I'm sleeping 1 ...
+I'm sleeping 2 ...
+Result is null
+```
+
+## Составление Suspending функций
+
+В этом разделе рассматриваются различные подходы к составлению *Suspending* функций.
+
+### Последовательность действий по-умолчанию
+
+Предположим, что у нас есть две *suspending* функции, которые делают что-то полезное, например, какой-то удаленный вызов службы или вычисление (в нашем примере каждая из них просто задерживается на секунду для примера):
+
+```kt
+suspend fun doSomethingUsefulOne(): Int {
+    delay(1000L) // pretend we are doing something useful here
+    return 13
+}
+
+suspend fun doSomethingUsefulTwo(): Int {
+    delay(1000L) // pretend we are doing something useful here, too
+    return 29
+}
+```
+
+Мы используем обычный последовательный вызов, т.к. код в корутине, как и в обычном коде по умолчанию является последовательным. В следующем примере измеряется время, затраченное на работу корутин:
+
+```kt
+val time = measureTimeMillis {
+    val one = doSomethingUsefulOne()
+    val two = doSomethingUsefulTwo()
+    println("The answer is ${one + two}")
+}
+println("Completed in $time ms")
+```
+
+Видно, что общее время работы примерно равно сумме времени работы каждой функции:
+
+```
+The answer is 42
+Completed in 2017 ms
+```
+
+### Одновременный асинхронный запуск
+
+Если выполнение функций не зависит друг от друга, то можно выполнить работу быстрее, запустив их одновременно. В этом нам поможет *async*.
+
+Концептуально *async* - это то же самое, что и *launch*. Он запускает отдельную корутину, работающей одновременно со всеми другими корутинами. Разница в том, что *launch* возвращает *Job* и не несет никакого результирующего значения, в то время как *async* возвращает *Deffered* — легкое неблокирующее будущее, которое представляет собой обещание предоставить результат позже. Вы можете использовать метод *.await()* на отложенное значение, чтобы получить его конечный результат, но *Deffered* также является *Job*, поэтому вы можете отменить его при необходимости.
+
+```kt
+val time = measureTimeMillis {
+    val one = async { doSomethingUsefulOne() }
+    val two = async { doSomethingUsefulTwo() }
+    println("The answer is ${one.await() + two.await()}")
+}
+println("Completed in $time ms")
+```
+По затраченному времени видно, что функции выполнялись параллельно и вся работа выполнена в два раза быстрее:
+
+```
+The answer is 42
+Completed in 1017 ms
+```
+
+### Отложенный асинхронный запуск
+
+При необходимости, мы можем создать асинхронный блок, но его реальный запуск отложить (задав параметр start = CoroutineStart.LAZY). В этом случае запуск производится методом *start*:
+
+```kt
+val time = measureTimeMillis {
+    val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
+    val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
+    // some computation
+    one.start() // start the first one
+    two.start() // start the second one
+    println("The answer is ${one.await() + two.await()}")
+}
+println("Completed in $time ms")
+```
+
+Таким образом, здесь две корутины созданы, но не выполняются. Программист сам решает когда именно начать выполнение, вызывая start.
+
+Обратите внимание, что если мы просто вызовем *await* в println без вызова start, это приведет к последовательному выполнению, так как await запускает выполнение корутины и ждет ее завершения.
+
+### Асинхронные функции
+
+Мы можем определить функции как асинхронные, используя async coroutine builder с явной ссылкой на GlobalScope. Мы называем такие функции с суффиксом "... Async", чтобы подчеркнуть тот факт, что они начинают асинхронные вычисления и нужно использовать полученное отложенное значение, чтобы получить результат.
+
+```kt
+// The result type of somethingUsefulOneAsync is Deferred<Int>
+fun somethingUsefulOneAsync() = GlobalScope.async {
+    doSomethingUsefulOne()
+}
+
+// The result type of somethingUsefulTwoAsync is Deferred<Int>
+fun somethingUsefulTwoAsync() = GlobalScope.async {
+    doSomethingUsefulTwo()
+}
+```
+
+Обратите внимание, что эти функции xxxAsync не являются *suspend* функциями. Они могут быть использованы из любого места. Однако их использование всегда подразумевает асинхронное (здесь имеется в виду параллельное) выполнение с вызывающим кодом.
+
+В следующем примере показано их использование за пределами корутин:
+
+```kt
+// note that we don't have `runBlocking` to the right of `main` in this example
+fun main() {
+    val time = measureTimeMillis {
+        // we can initiate async actions outside of a coroutine
+        val one = somethingUsefulOneAsync()
+        val two = somethingUsefulTwoAsync()
+        // but waiting for a result must involve either suspending or blocking.
+        // here we use `runBlocking { ... }` to block the main thread while waiting for the result
+        runBlocking {
+            println("The answer is ${one.await() + two.await()}")
+        }
+    }
+    println("Completed in $time ms")
+}
+```
+
+Этот стиль программирования с асинхронными функциями приведен здесь только для иллюстрации, потому что это популярный стиль в других языках программирования. Использование этого стиля в Kotlin категорически не рекомендуется по причинам, описанным ниже.
+
+Рассмотрим, что произойдет, если между строкой val one = somethingUsefulOneAsync() и выражением one.await() в коде возникает некоторая логическая ошибка, и программа выдает исключение, а операция, выполняемая программой, прерывается. Обычно глобальный обработчик ошибок может перехватывать это исключение, регистрировать и сообщать об ошибке разработчикам, но в противном случае программа может продолжать выполнять другие операции. Но здесь у нас есть somethingUsefulOneAsync, все еще работающее в фоновом режиме, хотя операция, которая его инициировала, была прервана. Эта проблема не возникает со структурированным параллелизмом, как показано в разделе ниже.
+
+### Асинхронный структурированный параллелизм
+
+Возьмем пример из [Одновременный асинхронный запуск](#Одновременный-асинхронный-запуск) и извлечем функцию, которая одновременно выполняет doSomethingUsefulOne и doSomethingUsefulTwo и возвращает сумму их результатов. Поскольку *async* coroutine builder определяется как расширение в CoroutineScope, нам необходимо иметь его в области видимости, и именно это обеспечивает функция coroutineScope:
+
+```kt
+suspend fun concurrentSum(): Int = coroutineScope {
+    val one = async { doSomethingUsefulOne() }
+    val two = async { doSomethingUsefulTwo() }
+    one.await() + two.await()
+}
+```
+
+Таким образом, если что-то пойдет не так в коде параллельной функции Sum и она выдаст исключение, все сопрограммы, запущенные в ее области видимости, будут отменены.
+
+```kt
+val time = measureTimeMillis {
+    println("The answer is ${concurrentSum()}")
+}
+println("Completed in $time ms")
+```
+
+Операции по прежнему выполняются асинхронно:
+
+```
+The answer is 42
+Completed in 1017 ms
+```
+
+Отмена всегда распространяется через иерархию корутин:
+
+```kt
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+    try {
+        failedConcurrentSum()
+    } catch(e: ArithmeticException) {
+        println("Computation failed with ArithmeticException")
+    }
+}
+
+suspend fun failedConcurrentSum(): Int = coroutineScope {
+    val one = async<Int> { 
+        try {
+            delay(Long.MAX_VALUE) // Emulates very long computation
+            42
+        } finally {
+            println("First child was cancelled")
+        }
+    }
+    val two = async<Int> { 
+        println("Second child throws an exception")
+        throw ArithmeticException()
+    }
+    one.await() + two.await()
+}
+```
+
+Обратите внимание, что и первый async (one) и ожидающий родитель (failedConcurrentSum) отменяются при сбое второго потомка:
+
+```
+Second child throws an exception
+First child was cancelled
+Computation failed with ArithmeticException
+```
+
+## Корутины: контекст и диспетчеры
+
+Корутины всегда выполняются в некотором контексте, представленном значением типа *CoroutineContext*, определенного в стандартной библиотеке Kotlin.
+
+Контекст корутины - это набор различных элементов. Основными элементами являются *Job*, которую мы видели ранее, и ее диспетчер, который рассматривается в этом разделе.
+
+### Деспетчеры и потоки
+
+Контекст корутины включает в себя [coroutine dispatcher](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html), который определяет, какой поток или потоки использует соответствующая корутина для своего выполнения. Диспетчер может ограничить выполнение корутины определенным потоком, отправить его в пул потоков или позволить ему работать неограниченно.
+
+Все конструкторы корутин (coroutine builders), такие как launch и async, принимают необязательный параметр контекста сопрограммы, который можно использовать для явного указания диспетчера для новой корутины и других элементов контекста.
+
+Попробуйте выполнить следующий пример:
+
+```kt
+import kotlinx.coroutines.*
+
+fun main() = runBlocking<Unit> {
+    launch { 
+        // контекст родителя, main runBlocking
+        println("main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
+    }
+    launch(Dispatchers.Unconfined) { 
+        // not confined - будет запущен в основном потоке
+        println("Unconfined            : I'm working in thread ${Thread.currentThread().name}")
+    }
+    launch(Dispatchers.Default) { 
+        // будет запущен в потоке DefaultDispatcher
+        println("Default               : I'm working in thread ${Thread.currentThread().name}")
+    }
+    launch(newSingleThreadContext("MyOwnThread")) { 
+        // запустся в созданном потоке
+        println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
+    }
+}
+```
+
+Получим примерно следующее (порядок может отличаться):
+
+```
+Unconfined            : I'm working in thread main
+Default               : I'm working in thread DefaultDispatcher-worker-1
+main runBlocking      : I'm working in thread main
+newSingleThreadContext: I'm working in thread MyOwnThread
+```
+
+При запуске корутины без параметров (launch { ... }) наследуется текущий контекст (и его диспетчер) от CoroutineScope. В нашем случае она наследует контекст корутины main runBlocking, которая выполняется в главном потоке.
+
+Dispatchers.Unconfined - это специальный диспетчер, который также работает в основном потоке, но на самом деле это другой механизм, который будет объяснен позже.
+
+Диспетчер по умолчанию, используемый при запуске корутины в GlobalScope, представлен Dispatchers.Default и использует общий фоновый пул потоков, поэтому ``launch(Dispatchers.Default) { ... }`` использует тот же диспетчер, что и ``GlobalScope.launch { ... }``.
+
+*newSingleThreadContext* создает поток для запуска сопрограммы. Выделенный поток - это очень дорогой ресурс. В реальном приложении он должен быть либо освобожден, когда больше не нужен, с помощью функции *close*, либо сохранен в переменной верхнего уровня и повторно использован в приложении.
+
+### Неограниченный (Unconfined) и ограниченный (confined) деспетчер
+
+Dispatchers.Unconfined запускает корутину в вызывающем потоке, но только до первой точки приостановки (suspend функции). После приостановки он возобновляет работу сопрограммы в потоке, который полностью определяется вызванной функцией приостановки. Неограниченный диспетчер подходит для корутин, которые не потребляют процессорное время и не обновляют общие данные (например, пользовательский интерфейс).
+
+С другой стороны, диспетчер наследуется от CoroutineScope по умолчанию. Диспетчер по умолчанию для runBlocking, в частности, ограничен потоком вызова, поэтому его наследование приводит к ограничению выполнения этим потоком с предсказуемым расписанием FIFO.
+
+```kt
+launch(Dispatchers.Unconfined) { 
+    // not confined -- will work with main thread
+    println("Unconfined      : I'm working in thread ${Thread.currentThread().name}")
+    delay(500)
+    println("Unconfined      : After delay in thread ${Thread.currentThread().name}")
+}
+launch { 
+    // context of the parent, main runBlocking coroutine
+    println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
+    delay(1000)
+    println("main runBlocking: After delay in thread ${Thread.currentThread().name}")
+}
+```
+
+Выведет следующее:
+
+```
+Unconfined      : I'm working in thread main
+main runBlocking: I'm working in thread main
+Unconfined      : After delay in thread kotlinx.coroutines.DefaultExecutor
+main runBlocking: After delay in thread main
+```
+
+Итак, корутина с контекстом, унаследованным от runBlocking {...} продолжает выполняться в основном потоке, в то время как неограниченная возобновляется в потоке по умолчанию, который использует функция *delay*.
+
+>Неограниченный диспетчер - это усовершенствованный механизм, который может быть полезен в некоторых случаях, ... . Неограниченный диспетчер не должен использоваться в общем коде.
+
+### Отладка курутин и потоков
+
+Корутины могут приостанавливаться в одном потоке и возобновляться в другом потоке. Даже с однопоточным диспетчером было бы трудно понять, что делает корутина, где и когда. Общий подход к отладке приложений с потоками заключается в печати имени потока в файле журнала для каждой записи. Эта функция повсеместно поддерживается фреймворками ведения журнала. При использовании корутин имя потока само по себе не дает большого контекста, поэтому kotlinx.coroutines включает средства отладки, чтобы сделать его проще.
+
+Запустите следующий код с JVM опцией ``-Dkotlinx.coroutines.debug``:
+
+```kt
+val a = async {
+    log("I'm computing a piece of the answer")
+    6
+}
+val b = async {
+    log("I'm computing another piece of the answer")
+    7
+}
+log("The answer is ${a.await() * b.await()}")
+```
+
+Здесь у нас три корутины. Основная корутина (#1) внутри runBlocking и две корутины, вычисляющие отложенные значения a (#2) и b (#3). Все они выполняются в контексте runBlocking и ограничены основным потоком.
+
+```
+[main @coroutine#2] I'm computing a piece of the answer
+[main @coroutine#3] I'm computing another piece of the answer
+[main @coroutine#1] The answer is 42
+```
+
+Функция log выводит имя потока в квадратных скобках, и вы можете видеть, что это основной поток с добавленным к нему идентификатором текущей выполняемой корутины. Этот идентификатор последовательно присваивается всем созданным корутинам, когда включен режим отладки.
+
+### Переключение между потоками
+
+Выполните следующий код с опцией JVM ``-Dkotlinx.coroutines.debug``
+
+```kt
+newSingleThreadContext("Ctx1").use { ctx1 ->
+    newSingleThreadContext("Ctx2").use { ctx2 ->
+        runBlocking(ctx1) {
+            log("Started in ctx1")
+            withContext(ctx2) {
+                log("Working in ctx2")
+            }
+            log("Back to ctx1")
+        }
+    }
+}
+```
+
+Он демонстрирует несколько новых методов. Один из них использует *runBlocking* с явно заданным контекстом, а другой использует функцию *withContext* для изменения контекста корутины, оставаясь при этом в той же самой корутине, как вы можете видеть ниже:
+
+```
+[Ctx1 @coroutine#1] Started in ctx1
+[Ctx2 @coroutine#1] Working in ctx2
+[Ctx1 @coroutine#1] Back to ctx1
+```
+
+Обратите внимание, что в этом примере используется функция *use* из стандартной библиотеки Kotlin для освобождения потоков, созданных с помощью *newSingleThreadContext*.
+
+### Job в контексте
+
+Объект *job* корутины является частью его контекста и может быть получен с помощью выражения ``coroutineContext[Job]``:
+
+```kt
+println("My job is ${coroutineContext[Job]}")
+```
+
+В отладочном режиме получим что-то типа:
+
+```
+My job is "coroutine#1":BlockingCoroutine{Active}@6d311334
+```
+
+### Потомки корутины
+
+Когда корутина запускается в CoroutineScope другой корутины, она наследует свой контекст от CoroutineScope.coroutineContext и новая корутина становится дочерней по отношению к родительской корутине. Когда родительская корутина отменяется, все ее дочерние элементы также рекурсивно отменяются.
+
+Однако если для запуска корутины используется GlobalScope, то для нее нет родителя. Поэтому корутина не привязана к области, из которой она была запущена, и работает независимо.
+
+```kt
+// launch a coroutine to process some kind of incoming request
+val request = launch {
+    // эта корутина будет запущена в GlobalScope
+    GlobalScope.launch {
+        println("job1: I run in GlobalScope and execute independently!")
+        delay(1000)
+        println("job1: I am not affected by cancellation of the request")
+    }
+    // а эта в контексте родителя
+    launch {
+        delay(100)
+        println("job2: I am a child of the request coroutine")
+        delay(1000)
+        println("job2: I will not execute this line if my parent request is cancelled")
+    }
+}
+delay(500)
+request.cancel() // cancel processing of the request
+delay(1000) // delay a second to see what happens
+println("main: Who has survived request cancellation?")
+```
+
+На выходе получим:
+
+```
+job1: I run in GlobalScope and execute independently!
+job2: I am a child of the request coroutine
+job1: I am not affected by cancellation of the request
+main: Who has survived request cancellation?
+```
+
+### Родительская ответственность
+
+Родительская корутина всегда ожидает завершения всех своих потомков. Родителю не нужно явно отслеживать всех детей, которых он запускает,и не нужно использовать Job.join, чтобы дождаться их завершения:
+
+```kt
+// launch a coroutine to process some kind of incoming request
+val request = launch {
+    repeat(3) { i -> // launch a few children jobs
+        launch  {
+            delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
+            println("Coroutine $i is done")
+        }
+    }
+    println("request: I'm done and I don't explicitly join my children that are still active")
+}
+request.join() // wait for completion of the request, including all its children
+println("Now processing of the request is complete")
+```
+
+На выходе получим:
+
+```
+request: I'm done and I don't explicitly join my children that are still active
+Coroutine 0 is done
+Coroutine 1 is done
+Coroutine 2 is done
+Now processing of the request is complete
+```
+
+### Присвоение имен корутинам для отладки
+
+Автоматически назначенные идентификаторы хороши, когда корутины часто регистрируются, и вам просто нужно сопоставить записи журнала, поступающие из одной и той же корутины. Однако, когда корутина связана с обработкой конкретного запроса или выполнением какой-то конкретной фоновой задачи, ее лучше называть явно для целей отладки. Элемент контекста CoroutineName  служит той же цели, что и имя потока. Он включается в имя потока, выполняющего эту корутину, когда включен режим отладки.
+
+```kt
+log("Started main coroutine")
+// run two background value computations
+val v1 = async(CoroutineName("v1coroutine")) {
+    delay(500)
+    log("Computing v1")
+    252
+}
+val v2 = async(CoroutineName("v2coroutine")) {
+    delay(1000)
+    log("Computing v2")
+    6
+}
+log("The answer for v1 / v2 = ${v1.await() / v2.await()}")
+```
+
+При запуске с JVM ключем -Dkotlinx.coroutines.debug выведет:
+
+```
+[main @main#1] Started main coroutine
+[main @v1coroutine#2] Computing v1
+[main @v2coroutine#3] Computing v2
+[main @main#1] The answer for v1 / v2 = 42
+```
+
+### Комбинирование элементов контекста
+
+Для определения нескольких элементов контекста мы можем использовать оператор "+". Например, мы можем запустить корутину с явно заданным диспетчером и явно заданным именем:
+
+```kt
+launch(Dispatchers.Default + CoroutineName("test")) {
+    println("I'm working in thread ${Thread.currentThread().name}")
+}
+```
+
+### Область видимости корутины
+
+Давайте объединим наши знания о контекстах, детях и *job*. Предположим, что приложение имеет объект, и этот объект не является корутиной. Например, мы пишем приложение для Android и запускаем различные корутины в контексте Android activity для выполнения асинхронных операций по извлечению и обновлению данных, анимации и т. д. Все эти корутины должны быть отменены при уничтожении активности, чтобы избежать утечки памяти. Мы, конечно, можем манипулировать контекстами и *job* вручную, чтобы связать activity и ее корутины, но kotlinx.coroutines предоставляет абстракцию, инкапсулирующую это: CoroutineScope. Вы должны быть уже знакомы с областью действия coroutine, так как все конструкторы корутин объявляются в ней как расширения.
+
+Мы управляем жизненными циклами наших корутин, создавая экземпляр CoroutineScope, привязанный к жизненному циклу нашей activity. Экземпляр CoroutineScope может быть создан с помощью фабричных функций CoroutineScope() или MainScope(). Первый создает область общего назначения, в то время как второй создает область для приложений пользовательского интерфейса и использует Dispatchers.Main диспетчером по умолчанию:
+
+```kt
+class Activity {
+    private val mainScope = MainScope()
+    
+    fun destroy() {
+        mainScope.cancel()
+    }
+    // to be continued ...
+```
+
+В качестве альтернативы мы можем реализовать интерфейс CoroutineScope в этом классе Activity. Лучший способ сделать это - использовать делегирование с фабричными функциями по умолчанию. Мы также можем объединить нужный диспетчер (мы использовали Dispatchers.Default в этом примере) с областью действия:
+
+```kt
+class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
+    // to be continued ...
+```
+
+Теперь мы можем запускать корутины в рамках этой activity без необходимости явно указывать их контекст. Для демонстрации мы запускаем десять корутин, которые задерживаются на разное время:
+
+```kt
+// class Activity continues
+    fun doSomething() {
+        // launch ten coroutines for a demo, each working for a different time
+        repeat(10) { i ->
+            launch {
+                delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
+                println("Coroutine $i is done")
+            }
+        }
+    }
+} // class Activity ends
+```
+
+В основной функции мы создаем activity, вызываем нашу тестовую функцию doSomething и через 500 мс уничтожаем activity. При уничтожении activity отменятся все корутины, которые были запущены из doSomething. 
+
+```kt
+val activity = Activity()
+activity.doSomething() // run test function
+println("Launched coroutines")
+delay(500L) // delay for half a second
+println("Destroying activity!")
+activity.destroy() // cancels all coroutines
+delay(1000) // visually confirm that they don't work
+```
+
+Это видно по логам:
+
+```
+Launched coroutines
+Coroutine 0 is done
+Coroutine 1 is done
+Destroying activity!
+```
+
+Видно, что только первые две корутины печатают сообщение, а остальные отменяются вызовом job.cancel() в Activity.destroy().
+
+### локальные данные потока (thread) - не понятно, переварить и переписать
+
+Иногда удобно иметь возможность передавать некоторые  локальные данные в корутины или между ними.
+
+В этом нам поможет функция asContextElement из ThreadLocal. Она создает дополнительный элемент контекста, который сохраняет значение данного ThreadLocal и восстанавливает его каждый раз, когда корутина переключает свой контекст.
+
+Легче показать на примере:
+
+```kt
+threadLocal.set("main")
+println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
+val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) {
+    println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
+    yield()
+    println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
+}
+job.join()
+println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
+```
+
+В этом примере мы запускаем новую корутину в фоновом пуле потоков с помощью Dispatchers.Default, поэтому он может работает в потоке, отличном от текущего, но все равно имеет значение локальной переменной потока, заданной с помощью threadLocal.asContextElement (value = "launch"). Таким образом, вывод (с включенной отладкой) будет:
+
+```
+Pre-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
+Launch start, current thread: Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main], thread local value: 'launch'
+After yield, current thread: Thread[DefaultDispatcher-worker-2 @coroutine#2,5,main], thread local value: 'launch'
+Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main'
+```
+
+Легко забыть установить соответствующий элемент контекста. Локальная переменная потока, доступная из корутины, может иметь неожиданное значение, если поток, выполняющий корутину, отличается. Чтобы избежать таких ситуаций, рекомендуется использовать метод *ensurePresent*.
+
+ThreadLocal может использоваться с любыми примитивами kotlinx.coroutines. Однако у него есть одно ключевое ограничение: при изменении локального потока новое значение не передается вызывающему объекту корутины (поскольку элемент контекста не может отслеживать все обращения к объекту ThreadLocal), и обновленное значение теряется при следующей приостановке. Используйте withContext для обновления значения threadlocal в корутине, см.  asContextElement для получения более подробной информации.
+
+Кроме того, значение может храниться в изменяемом поле типа ``class Counter(var i: Int)``, которое, в свою очередь, хранится в локальной переменной потока. Однако в этом случае вы несете полную ответственность за синхронизацию потенциально одновременных изменений переменной в этом изменяемом поле.
+
+Для расширенного использования, например для интеграции с журналированием MDC, транзакционными контекстами или любыми другими библиотеками, которые внутренне используют локальные потоки для передачи данных, см. документацию интерфейса ThreadContextElement, который должен быть реализован.
+
+## Асинхронные потоки
+
+Suspend функции асинхронно возвращают одно значение, но как мы можем вернуть несколько асинхронно вычисленных значений? Вот тут-то и появляются потоки Котлина.
+
+### Представление нескольких значений
+
+Несколько значений могут быть представлены с помощью коллекций. Например, у нас может быть функция foo(), которая возвращает список из трех чисел:
+
+```kt
+fun foo(): List<Int> = listOf(1, 2, 3)
+ 
+fun main() {
+    foo().forEach { value -> println(value) } 
+}
+```
+
+**Последовательности**
+
+Если мы вычисляем числа с некоторым блокирующим кодом, потребляющим процессор (каждое вычисление занимает 100 мс), то мы можем представить числа с помощью *Sequence*:
+
+```kt
+fun foo(): Sequence<Int> = sequence { // конструктор последовательностей
+    for (i in 1..3) {
+        Thread.sleep(100) // симулируем вычисления
+        yield(i) // yield next value
+    }
+}
+
+fun main() {
+    foo().forEach { value -> println(value) } 
+}
+```
+
+На выходе мы получим тот же результат, но перед выводом значений будет задержка 100 мс.
+
+// TODO: включить в лекции sequences и yield
+
+> Функцию [yield()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence-scope/yield.html) мы раньше не рассматривали. Она возвращает значение итератора и приостанавливает (suspend) работу до тех пор, пока не будет запрошено следующее значение.
+
+**Приостановка (Suspending) функций**
+
+Однако это вычисление блокирует основной поток, который выполняет код. Когда эти значения вычисляются асинхронным кодом, мы можем пометить функцию foo модификатором suspend, чтобы она могла выполнять свою работу без блокировки и возвращать результат в виде списка:
+
+```kt
+suspend fun foo(): List<Int> {
+    delay(1000) // pretend we are doing something asynchronous here
+    return listOf(1, 2, 3)
+}
+
+fun main() = runBlocking<Unit> {
+    foo().forEach { value -> println(value) } 
+}
+```
+
+### Потоки (Flows)
+
+Использование типа результата List<Int> означает, что мы можем возвращать только все значения сразу. Для представления потока значений, которые вычисляются асинхронно, мы можем использовать тип Flow<Int> так же, как и тип Sequence<Int> для синхронно вычисляемых значений:
+
+```kt
+fun foo(): Flow<Int> = flow { // конструктор потоков
+    for (i in 1..3) {
+        delay(100) // pretend we are doing something useful here
+        emit(i) // emit next value
+    }
+}
+
+fun main() = runBlocking<Unit> {
+    // Launch a concurrent coroutine to check if the main thread is blocked
+    launch {
+        for (k in 1..3) {
+            println("I'm not blocked $k")
+            delay(100)
+        }
+    }
+    // Collect the flow
+    foo().collect { value -> println(value) } 
+}
+```
+
+Этот код ожидает 100 мс перед печатью каждого числа, не блокируя основной поток. Это подтверждается печатью "I'm not blocked" каждые 100 мс из отдельной корутины, которая выполняется в основном потоке:
+
+```
+I'm not blocked 1
+1
+I'm not blocked 2
+2
+I'm not blocked 3
+3
+```
+
+Обратите внимание на следующие отличия кода с потоком от предыдущих примеров:
+
+- Конструктор корутин для типа Flow называется flow.
+- Код внутри блока flow { ... } можно приостановить.
+- Функция foo () больше не помечена модификатором suspend.
+- Значения испускаются (emitted) из потока с помощью функции emit.
+- Значения собираются из потока с помощью функции collect.
+
+**Потоки холодные (Flows are cold)**
+
+Flows - это холодные потоки, похожие на последовательности: код внутри конструктора потоков не запускается, пока поток не собран. Это становится ясно в следующем примере:
+
+```kt
+fun foo(): Flow<Int> = flow { 
+    println("Flow started")
+    for (i in 1..3) {
+        delay(100)
+        emit(i)
+    }
+}
+
+fun main() = runBlocking<Unit> {
+    println("Calling foo...")
+    val flow = foo()
+    println("Calling collect...")
+    flow.collect { value -> println(value) } 
+    println("Calling collect again...")
+    flow.collect { value -> println(value) } 
+}
+```
+
+```
+Calling foo...
+Calling collect...
+Flow started
+1
+2
+3
+Calling collect again...
+Flow started
+1
+2
+3
+```
+
+Это ключевая причина, по которой функция foo () (которая возвращает поток) не помечена модификатором suspend. Сам по себе foo() возвращается быстро и ничего не ждет. Поток запускается каждый раз, когда он собирается, поэтому мы видим, что "поток запущен", когда мы снова вызываем collect.
+
+**Отмена потока**
+
+Поток придерживается общей кооперативной отмены корутин. Однако инфраструктура потока не вводит дополнительных точек отмены. Он полностью прозрачен для отмены. Обычно, сбор потока может быть отменен, когда поток приостановлен в suspend функции приостановки (например, delay), и не может быть отменен иначе.
+
+В следующем примере показано, как поток отменяется по таймауту при выполнении в блоке withTimeoutOrNull:
+
+```kt
+fun foo(): Flow<Int> = flow { 
+    for (i in 1..3) {
+        delay(100)          
+        println("Emitting $i")
+        emit(i)
+    }
+}
+
+fun main() = runBlocking<Unit> {
+    withTimeoutOrNull(250) { // Timeout after 250ms 
+        foo().collect { value -> println(value) } 
+    }
+    println("Done")
+}
+```
+
+Обратите внимание, что поток успел сгенерировать только два числа:
+
+```
+Emitting 1
+1
+Emitting 2
+2
+Done
+```
+
+**Конструкторы потоков (Flow builders)**
+
+Конструктор ``flow { ... }``  из предыдущих примеров является самым базовым. Однако есть и другие конструкторы для облегчения декларирования потоков:
+
+- конструктор flowOf, определяющий поток, генерирующий фиксированный набор значений.
+- Различные коллекции и последовательности могут быть преобразованы в потоки с помощью функции asFlow().
+
+Пример, который выводит числа от 1 до 3 из потока, может быть записан как:
+
+```kt
+// Convert an integer range to a flow
+(1..3).asFlow().collect { value -> println(value) }
+```
+
+**Операторы промежуточного потока**
+
+Потоки могут быть преобразованы с помощью операторов, так же как и с помощью коллекций и последовательностей. Промежуточные операторы применяются к восходящему потоку и возвращают нисходящий поток. Эти операторы холодны, как и потоки. Вызов такого оператора сам по себе не является функцией приостановки. Он работает быстро, возвращая определение нового преобразованного потока.
+
+Основные операторы имеют знакомые названия: map и filter. Важным отличием последовательностей является то, что блоки кода внутри этих операторов могут вызывать функции приостановки.
+
+Например, поток входящих запросов может быть сопоставлен с результатами с помощью оператора map, даже если выполнение запроса является длительной операцией, которая реализуется suspend функцией:
+
+```kt
+suspend fun performRequest(request: Int): String {
+    delay(1000) // imitate long-running asynchronous work
+    return "response $request"
+}
+
+fun main() = runBlocking<Unit> {
+    (1..3).asFlow() // a flow of requests
+        .map { request -> performRequest(request) }
+        .collect { response -> println(response) }
+}
+```
+
+Этот пример выведет три строки, каждая строка появляется с задержкой в секунду:
+
+```kt
+response 1
+response 2
+response 3
+```
+
+**Оператор Transform**
+
+Среди операторов преобразования потока наиболее общим является так называемый transform. Он может использоваться для имитации простых преобразований, таких как map и filter, а также для реализации более сложных преобразований. Используя оператор transform, мы можем генерировать (emit) произвольные значения произвольное число раз.
+
+Например, с помощью transform мы можем выдать строку перед выполнением длительного асинхронного запроса и следовать за ней с ответом:
+
+```kt
+(1..3).asFlow() // a flow of requests
+    .transform { request ->
+        emit("Making request $request") 
+        emit(performRequest(request)) 
+    }
+    .collect { response -> println(response) }
+```
+
+```
+Making request 1
+response 1
+Making request 2
+response 2
+Making request 3
+response 3
+```
+
+**Операторы ограничения размера**
+
+Промежуточные операторы ограничения размера, такие как take, отменяют выполнение потока при достижении соответствующего предела. Отмена в корутинах всегда выполняется путем создания исключения, поэтому нужно использовать функции управления ресурсами (например, try { ... } finally { ... }):
+
+```kt
+fun numbers(): Flow<Int> = flow {
+    try {                          
+        emit(1)
+        emit(2) 
+        println("This line will not execute")
+        emit(3)    
+    } finally {
+        println("Finally in numbers")
+    }
+}
+
+fun main() = runBlocking<Unit> {
+    numbers() 
+        .take(2) // take only the first two
+        .collect { value -> println(value) }
+}        
+```
+
+В логах видно, что выполнение блока flow { ... } в функции numbers() остановилось после генерации второго числа:
+
+```
+1
+2
+Finally in numbers
+```
+
+# ООП
+
+## Класс и объект
+
+```kt
+// объявление класса с помощью ключевого слова class
+class Transformer(var x: Int){
+    // объявление метода run
+    fun run(){
+        // обращение к собственному атрибуту 
+        x += 1
+    }
+}
+
+// а теперь клиентский код:
+
+// создаем новый экземпляр трансформера с начальной позицией 0
+var optimus = Transformer(0)
+
+optimus.run() // приказываем Оптимусу бежать
+println( optimus.x ) // выведет 1
+optimus.run() // приказывает Оптимусу еще раз бежать
+print( optimus.x ) // выведет 2
+```
+
+Что мы видим из кода?
+
+1. Объект может обращаться из своих методов к собственным атрибутам (у нас аттрибут x). Обращаю внимание, что только к собственным, то бишь, когда трансформер вызывает свой метод, либо меняет собственное состояние. Если снаружи обращение будет выглядеть так: optimus.x, то изнутри, если Оптимус захочет сам обратиться к своему полю x, в его методе обращение будет звучать так: x, то есть "я (Оптимус) обращаюсь к своему атрибуту x". В большинстве языков для обращения к аттрибутам класса используются ключевые слова this или self, но в Котлине обходятся без них.
+
+2. constructor — это специальный метод, который автоматически вызывается при создании объекта. Конструктор может принимать любые аргументы, как и любой другой метод. В каждом языке конструктор обозначается своим именем. Где-то это специально зарезервированные имена типа ``__construct`` или ``__init__``, а где-то имя конструктора должно совпадать с именем класса. Назначение конструкторов — произвести первоначальную инициализацию объекта, заполнить нужные поля. Про конструкторы мы подробнее поговорим ниже, а в нашем примере конструктор присутствует сразу при объявлении класса - это круглые скобки с параметром, который, в свою очередь является аттрибутом класса.
+
+3. В котлине для создания экземпляра класса нужно вызвать конструктор класса (как фкнкцию). В этот момент создается объект и вызывается конструктор. В нашем примере, конструктору передается 0 в качестве стартовой позиции трансформера (это и есть вышеупомянутая инициализация).
+
+4. Методы constructor и run работают с внутренним состоянием, а во всем остальном не отличаются от обычных функций. Даже синтаксис объявления совпадает.
+
+## Основные принципы ООП.
+
+Ортодоксальная ООП-церковь проповедует нам фундаментальную троицу — *инкапсуляцию*, *полиморфизм* и *наследование*, на которых зиждется весь объектно-ориентированный подход. Разберем их по порядку.
+
+### Наследование
+
+**Наследование** — это механизм системы, который позволяет, как бы парадоксально это не звучало, наследовать одними классами свойства и поведение других классов для дальнейшего расширения или модификации.
+
+```kt
+class Transformer
+{ // базовый класс
+    fun run(){
+        // код, отвечающий за бег
+    }
+    fun fire(){
+        // код, отвечающий за стрельбу
+    }
+}
+
+
+class Autobot : Transformer()
+{ // дочерний класс, наследование от Transformer
+    fun transform(){
+        // код, отвечающий за трансформацию в автомобиль
+    }
+}
+
+class Decepticon : Transformer()
+{ // дочерний класс, наследование от Transformer
+    fun transform(){
+        // код, отвечающий за трансформацию в самолет
+    }
+}
+
+optimus = new Autobot()
+megatron = new Decepticon()
+```
+
+Этот пример наглядно иллюстрирует, как наследование становится одним из способов избежать дублирования кода с помощью родительского класса, и одновременно предоставляет возможности для мутации в классах-потомках.
+
+#### Перегрузка
+
+Если же в классе-потомке переопределить уже существующий в классе-родителе метод, то сработает перегрузка. Это позволяет не дополнять поведение родительского класса, а модифицировать. В момент вызова метода или обращения к полю объекта, поиск атрибута происходит от потомка к самому корню — родителю. То есть, если у автобота вызвать метод fire(), сначала поиск метода производится в классе-потомке — Autobot, а поскольку его там нет, поиск поднимается на ступень выше — в класс Transformer, где и будет обнаружен и вызван.
+
+### Полиморфизм
+
+**Полиморфизм** — свойство системы, позволяющее иметь множество реализаций одного интерфейса. Ничего непонятно. Обратимся к трансформерам.
+
+### Инкапсуляция
+
+**Инкапсуляция** — это контроль доступа к полям и методам объекта. Под контролем доступа подразумевается не только можно/неможно, но и различные валидации, подгрузки, вычисления и прочее динамическое поведение.
+
+Во многих языках частью инкапсуляции является сокрытие данных. Для этого существуют модификаторы доступа (опишем те, которые есть почти во всех ООП языках):
+
+publiс — к атрибуту может получить доступ любой желающий
+private — к атрибуту могут обращаться только методы данного класса
+protected — то же, что и private, только доступ получают и наследники класса в том числе
+
+```kt
+class Transformer {
+    public fun constructor(){ }
+
+    protected fun setup(){ }
+
+    private fun dance(){ }
+}
+```
+
+Как правильно выбрать модификатор доступа? В простейшем случае так: если метод должен быть доступен внешнему коду, выбираем public. В противном случае — private. Если есть наследование, то может потребоваться protected в случае, когда метод не должен вызываться снаружи, но должен вызываться потомками.
+
+## Абстрактные классы
+
+Кроме обычных классов в некоторых языках существуют абстрактные классы. От обычных классов они отличаются тем, что нельзя создать объект такого класса. Зачем же нужен такой класс, спросит читатель? Он нужен для того, чтобы от него могли наследоваться потомки — обычные классы, объекты которых уже можно создавать.
+
+Абстрактный класс наряду с обычными методами содержит в себе абстрактные методы без реализации (с названием, но без кода), которые обязан реализовать программист, задумавший создать класс-потомок. Абстрактные классы не обязательны, но они помогают установить контракт, обязующий реализовать определенный набор методов.
+
+## Интерфейсы
+
+Задача **интерфейса** — снизить уровень зависимости сущностей друг от друга, добавив больше абстракции.
+
+Выше мы рассматривали абстрактные классы, затрагивая тему контрактов, обязующих реализовать какие-то абстрактные методы. Так вот интерфейс очень смахивает на абстрактный класс, но является не классом, а просто пустышкой с перечислением абстрактных методов. 
+
+Интерфейсы в Kotlin, однако, могут содержать как абстрактные методы, тик и методы с реализацией. Главное отличие интерфейсов от абстрактных классов заключается в невозможности хранения переменных экземпляров. Они могут иметь свойства, но те должны быть либо абстрактными, либо предоставлять реализацию методов доступа.
+
+Обычно в языках, в которых есть интерфейсы, нет множественного наследования классов, но есть множественное наследование интерфейсов. Это позволяет классу перечислить интерфейсы, которые он обязуется реализовать.
+
+Классы с интерфейсами состоят в отношении «многие ко многим»: один класс может имплементировать множество интерфейсов, и каждый интерфейс, в свою очередь, может имплементироваться многими классами.
+
+// описания интерфейсов:
+
+```kt
+interface Weapon 
+{
+    fun fire() // декларация метода без имплементации. Ниже аналогично
+}
+
+interface EnergyGenerator
+{
+    // тут уже два метода, которые должны будут реализовать классы:
+    fun generate_energy() // первый
+    fun load_fuel()       // второй
+}
+
+interface Scanner
+{
+    fun scan()
+}
+
+// классы, реализующие интерфейсы:
+
+class RocketLauncher : Weapon 
+{
+    override fun fire(){
+        // имплементация запуска ракеты
+    }
+}
+
+class LaserGun : Weapon 
+{
+    override fun fire(){
+        // имплементация выстрела лазером
+    }
+}
+
+class NuclearReactor : EnergyGenerator 
+{
+    override fun generate_energy(){
+        // имплементация генерации энергии ядерным реактором
+    }
+	
+    override fun load_fuel(){
+        // имплементация загрузки урановых стержней
+    }
+}
+
+class RITEG : EnergyGenerator 
+{
+    override fun generate_energy(){
+        // имплементация генерации энергии РИТЭГ
+    }
+	
+    override fun load_fuel(){
+        // имплементация загрузки РИТЭГ-пеллет
+    }
+}
+
+class Radar : Scanner 
+{
+    override fun scan(){
+        // имплементация использования радиолокации
+    }	
+}
+
+class Lidar : Scanner 
+{
+    override fun scan(){
+        // имплементация использования оптической локации
+    }
+}
+
+// класс - потребитель:
+
+class Transformer {
+    // привет, композиция:
+    // Интерфейсы указаны в качестве типов данных.
+    // Они могут принимать любые объекты,
+    // которые имплементируют указанный интерфейс
+
+    private var slot_weapon: Weapon? = null   
+    private var slot_energy_generator: EnergyGenerator? = null
+    private var slot_scanner: Scanner? = null
+	
+    /*
+    в параметрах методов интерфейс тоже указан как тип данных,
+    метод может принимать объект любого класса,
+    имплементирующий данный интерфейс:
+    */
+    fun install_weapon(weapon: Weapon){ 
+        slot_weapon = weapon
+    }
+	
+    fun install_energy_generator(energy_generator: EnergyGenerator ){
+        slot_energy_generator = energy_generator
+    }
+	
+    function install_scanner(scanner: Scanner){
+        slot_scanner = scanner
+    }
+}
+
+// фабрика трансформеров
+
+class TransformerFactory {
+    fun build_some_transformer {
+       	transformer = Transformer()
+       	laser_gun = LaserGun()
+       	nuclear_reactor = NuclearReactor()
+       	radar = Radar()
+       	
+       	transformer.install_weapon(laser_gun)
+       	transformer.install_energy_generator(nuclear_reactor)
+       	transformer.install_scanner(radar)
+        	
+        return transformer
+    }
+}
+
+// использование
+
+transformer_factory = TransformerFactory()
+oleg = transformer_factory.build_some_transformer()
+```
+
+## От теории к практике
+
+Классы в Kotlin объявляются с помощью использования ключевого слова **class**:
+
+```kt
+class Transformer {}
+```
+
+Объявление класса состоит из ключевого слова *class*,  имени класса (помним, что в котлине названия типов данных начинаются с большой буквы), заголовка (указания типов его параметров, основного конструктора и т.п) и тела класса, заключённого в фигурные скобки. И заголовок, и тело класса являются необязательными составляющими: если у класса нет тела, фигурные скобки могут быть опущены.
+
+```kt
+class Empty
+```
+
+### Конструкторы
+
+Конструктор - это метод, который будет выполнен при создании экземпляра класса. В нем, обычно, производится инициализация данных.
+
+Класс в Kotlin может иметь основной конструктор (**primary constructor**) и дополнительные конструкторы (**secondary constructors**). Основной конструктор является частью заголовка класса, его объявление идёт сразу после имени класса (и необязательных параметров):
+
+```kt
+class Transformer constructor(firstName: String)
+```
+
+Если у конструктора нет аннотаций и модификаторов видимости, ключевое слово *constructor* может быть опущено:
+
+```kt
+class Transformer(firstName: String)
+```
+
+Основной конструктор **не может** содержать в себе исполняемого кода. Инициализирующий код может быть помещён в соответствующий блок (**initializers blocks**), который помечается словом init:
+
+```kt
+class Transformer(name: String) {
+    init {
+        logger.info("Transformer initialized with value ${name}")
+    }
+}
+```
+
+Обратите внимание, что параметры основного конструктора фактически являются свойствами класса и могут быть использованы в его методах и других свойствах:
+
+```kt
+class Transformer(name: String) {
+    val transformerAlias = name.toUpperCase()
+}
+```
+
+В действительности, для объявления и инициализации свойств основного конструктора в Kotlin есть лаконичное синтаксическое решение:
+
+```kt
+class Transformer(val firstName: String, val lastName: String, var age: Int) {
+  // ...
+}
+```
+
+### Дополнительные конструкторы
+
+В классах также могут быть объявлены дополнительные конструкторы (**secondary constructors**), перед которыми используется ключевое слово **constructor**:
+
+```kt
+class Transformer {
+    constructor(parent: Person) {
+        parent.children.add(this)
+    }
+}
+```
+
+Если у класса есть основной конструктор, каждый дополнительный конструктор должен прямо или косвенно ссылаться (через другой(ие) конструктор(ы)) на основной:
+
+```kt
+class Transformer(val name: String) {
+    constructor(name: String, parent: Person) : this(name) {
+        parent.children.add(this)
+    }
+}
+```
+
+### Создание экземпляров классов
+
+Для создания экземпляра класса конструктор вызывается так, как если бы он был обычной функцией:
+
+```kt
+val transformer = Transformer()
+
+val Transformer2 = Transformer("Joe Smith")
+```
+
+### Члены класса
+
+Классы могут содержать в себе:
+
+* Конструкторы и инициализирующие блоки
+* Функции
+* Свойства
+* Вложенные классы
+* Объявления объектов
+
+### Наследование
+
+Для всех классов в языке Koltin родительским суперклассом является класс Any. Он также является родительским классом для любого класса, в котором не указан какой-либо другой родительский класс:
+
+```kt
+class Example // Неявно наследуется от Any
+```
+
+Класс Any не является аналогом java.lang.Object. В частности, у него нет никаких членов кроме методов: equals(), hashCode(), и toString(). 
+
+Для явного объявления суперкласса (предка, от которого наследуемся) мы помещаем его имя за знаком двоеточия в оглавлении класса:
+
+```kt
+open class Transformer {...}
+
+class Autobot(p: Int) : Transformer()
+```
+
+Если у класса есть основной конструктор, базовый класс может (и должен) быть проинициализирован там же, с использованием параметров основного конструктора.
+
+Если у класса нет основного конструктора, тогда каждый последующий дополнительный конструктор должен включать в себя инициализацию базового класса с помощью ключевого слова **super** или давать отсылку на другой конструктор, который это делает. Примечательно, что любые дополнительные конструкторы могут ссылаться на разные конструкторы базового класса:
+
+```kt
+class MyView : View {
+    constructor(ctx: Context) : super(ctx) {
+    }
+
+    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
+    }
+}
+```
+
+Ключевое слово open является противоположностью слову final в Java: оно позволяет другим классам наследоваться от данного. По умолчанию, все классы в Kotlin имеют статус final.
+
+### Переопределение членов класса
+
+Как упоминалось ранее, мы придерживаемся идеи определённости и ясности в языке Kotlin. И, в отличие от Java, Kotlin требует чёткой аннотации и для членов, которые могут быть переопределены, и для самого переопределения:
+
+```kt
+open class Base {
+  open fun v() {}
+  fun nv() {}
+}
+
+class Derived() : Base() {
+  override fun v() {}
+}
+```
+
+Для метода Derived.v() необходима аннотация *override*. В случае её отсутствия компилятор выдаст ошибку. Если у метода Base.nv() нет аннотации *open*, объявление метода с такой же сигнатурой в производном классе невозможно, с override или без. В final классе (классе без аннотации open), запрещено использование аннотации open для его членов.
+
+Метод класса, помеченный override, является сам по себе open, т.е. он может быть переопределён в производных классах. Если вы хотите запретить возможность переопределения такого члена, используйте *final*:
+
+```kt
+open class AnotherDerived() : Base() {
+  final override fun v() {}
+}
+```
+
+### Правила переопределения
+
+В Kotlin правила наследования реализации определены следующим образом: если класс наследует многочисленные реализации одного и того метода от ближайших родительских классов, он должен переопределить этот член и обеспечить свою собственную реализацию (возможно, используя одну из унаследованных). Для того, чтобы отметить конкретный супертип (родительский класс), от которого мы наследуем данную реализацию, мы используем ключевое слово super. Для задания имени родительского супертипа используются треугольные скобки, например super<Base>:
+
+```kt
+open class A {
+  open fun f() { print("A") }
+  fun a() { print("a") }
+}
+
+interface B {
+  fun f() { print("B") } // члены интерфейса открыты ('open') по умолчанию
+  fun b() { print("b") }
+}
+
+class C() : A(), B {
+  // Компилятор требует, чтобы f() была переопределена:
+  override fun f() {
+    super<A>.f() // вызов A.f()
+    super<B>.f() // вызов B.f()
+  }
+}
+```
+
+Для метода f() у нас есть две реализации, унаследованные классом C, поэтому необходимо переопределить метод f() в классе C и обеспечить нашу собственную реализацию этого метода для устранения получившейся неоднозначности.
+
+### Абстрактные классы
+
+Класс и некоторые его члены могут быть объявлены как *abstract*. Абстрактный метод не имеет реализации в своём классе. Обратите внимание, что нам не надо аннотировать абстрактный класс или функцию словом open - это подразумевается и так.
+
+### Свойства и поля
+
+#### Объявление свойств
+
+Классы в Kotlin могут иметь свойства: изменяемые (mutable) и неизменяемые (read-only) — **var** и **val** соответственно (т.е. выглядят как обычные переменные).
+
+```kt
+public class Address {
+    public var name: String = ...
+    public var street: String = ...
+    public var city: String = ...
+    public var state: String? = ...
+    public var zip: String = ...
+}
+```
+
+Для того, чтобы воспользоваться свойством, мы просто обращаемся к его имени:
+
+```kt
+fun copyAddress(address: Address): Address {
+    val result = Address() // нет никакого слова `new`
+    result.name = address.name // получаем свойство класса
+    result.street = address.street
+    // ...
+    return result
+}
+```
+
+### Свойства с поздней инициализацией
+
+Обычно, свойства, объявленные non-null типом, должны быть проинициализированы в конструкторе. Однако, довольно часто это неосуществимо. К примеру, свойства могут быть инициализированы через внедрение зависимостей, в установочном методе (ориг.: "setup method") юнит-теста или в методе onCreate в Android. В таком случае вы не можете обеспечить non-null инициализацию в конструкторе, но всё равно хотите избежать проверок на null при обращении внутри тела класса к такому свойству.
+
+Для того, чтобы справиться с такой задачей, вы можете пометить свойство модификатором lateinit:
+
+```kt
+public class MyTest {
+    lateinit var subject: TestSubject
+
+    @SetUp fun setup() {
+        subject = TestSubject()
+    }
+
+    @Test fun test() {
+        subject.method()  // объект инициализирован, проверять на null не нужно
+    }
+}
+```
+
+Такой модификатор может быть использован только с var свойствами, объявленными внутри тела класса (не в основном конструкторе, и только тогда, когда свойство не имеет пользовательских геттеров и сеттеров) и, начиная с Kotlin 1.2, со свойствами, расположенными на верхнем уровне, и локальными переменными. Тип такого свойства должен быть non-null и не должен быть примитивным.
+
+Доступ к lateinit свойству до того, как оно проинициализировано, выбрасывает специальное исключение, которое чётко обозначает, что свойство не было определено.
+
+### Проверка инициализации lateinit var (начиная с версии 1.2)
+
+Чтобы проверить, было ли проинициализировано lateinit var свойство, используйте .isInitialized метод ссылки на это свойство:
+
+```kt
+if (foo::bar.isInitialized) {
+    println(foo.bar)
+}
+```
+
+### Реализация интерфейсов
+
+Класс или объект могут реализовать любое количество интерфейсов:
+
+```kt
+class Child : MyInterface {
+    override fun bar() {
+        // тело
+    }
+}
+```
+
+### Модификаторы доступа
+
+Классы, объекты, интерфейсы, конструкторы, функции, свойства и их сеттеры могут иметь модификаторы доступа (у геттеров всегда такая же видимость, как у свойств, к которым они относятся). В Kotlin предусмотрено четыре модификатора доступа: **private**, **protected**, **internal** и **public**. Если явно не используется никакого модификатора доступа, то по умолчанию применяется **public**.
+
+### Пакеты
+
+Функции, свойства, классы, объекты и интерфейсы могут быть объявлены на самом "высоком уровне" прямо внутри пакета:
+
+```kt
+// имя файла: example.kt
+package foo
+
+fun baz() {}
+class Bar {}
+```
+
+* Если вы не укажете никакого модификатора доступа, будет использован **public**. Это значит, что весь код данного объявления будет виден в глобальной области видимости;
+* Если вы пометите объявление словом **private**, оно будет видно только внутри файла, где было объявлено;
+* Если вы используете **internal**, видимость будет распространяться на весь модуль;
+* **protected** запрещено использовать в объявлениях "высокого уровня".
+
+Примеры:
+
+```kt
+// имя файла: example.kt
+package foo
+
+private fun foo() {} // имеет видимость внутри example.kt
+
+public var bar: Int = 5 // свойство видно со дна Марианской впадины
+    private set         // сеттер видно только внутри example.kt
+    
+internal val baz = 6    // имеет видимость внутри модуля
+```
+
+#### Классы и интерфейсы
+
+Для методов, объявленых в классе:
+
+* **private** означает видимость только внутри этого класса;
+* **protected** — то же самое, что и private + видимость в потомках;
+* **internal** — любой клиент внутри модуля, который видит объявленный класс, видит и его internal члены;
+* **public** — любой клиент, который видит объявленный класс, видит его public члены.
+
+Примеры:
+
+```kt
+open class Outer {
+    private val a = 1
+    protected open val b = 2
+    internal val c = 3
+    val d = 4  // public по умолчанию
+    
+    protected class Nested {
+        public val e: Int = 5
+    }
+}
+
+class Subclass : Outer() {
+    // a не видно
+    // b, c и d видно
+    // класс Nested и его свойство e видно
+
+    override val b = 5   // 'b' - protected
+}
+
+class Unrelated(o: Outer) {
+    // o.a, o.b не видно
+    // o.c и o.d видно (тот же модуль)
+    // Outer.Nested не видно, и Nested::e также не видно
+}
+```
+
+#### Конструкторы
+
+Для указания видимости основного конструктора класса используется следующий синтаксис:
+
+```kt
+class C private constructor(a: Int) { ... }
+```
+
+В этом примере конструктор является **private**. По умолчанию все конструкторы имеют модификатор доступа **public**, то есть видны везде, где виден сам класс (а вот конструктор internal класса видно только в том же модуле).
+
+### Расширения (extensions)
+
+Аналогично таким языкам программирования, как C# и Gosu, Kotlin позволяет расширять класс путём добавления нового функционала. Не наследуясь от такого класса и не используя паттерн "Декоратор". Это реализовано с помощью специальных выражений, называемых расширения. Kotlin поддерживает функции-расширения и свойства-расширения.
+
+#### Функции-расширения
+
+Для того, чтобы объявить функцию-расширение, нам нужно указать в качестве префикса расширяемый тип, то есть тип, который мы расширяем. Следующий пример добавляет функцию *swap* к MutableList<Int>:
+
+```kt
+fun MutableList<Int>.swap(index1: Int, index2: Int) {
+    val tmp = this[index1] // 'this' даёт ссылку на список
+    this[index1] = this[index2]
+    this[index2] = tmp
+}
+```
+
+Ключевое слово *this* внутри функции-расширения соотносится с объектом расширяемого типа. Теперь мы можем вызывать такую функцию в любом MutableList<Int>:
+
+```kt
+val l = mutableListOf(1, 2, 3)
+l.swap(0, 2) // 'this' внутри 'swap()' будет содержать значение 'l'
+```
+
+Разумеется, эта функция имеет смысл для любого MutableList<T>, и мы можем сделать её обобщённой:
+
+```kt
+fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
+    val tmp = this[index1] // 'this' относится к списку
+    this[index1] = this[index2]
+    this[index2] = tmp
+}
+```
+
+#### Расширение null-допустимых типов
+
+Обратите внимание, что расширения могут быть объявлены для null-допустимых типов. Такие расширения могут ссылаться на переменные объекта, даже если значение переменной равно null. В таком случае есть возможность провести проверку this == null внутри тела функции. Благодаря этому метод toString() в языке Koltin вызывается без проверки на null: она проходит внутри функции-расширения.
+
+```kt
+fun Any?.toString(): String {
+    if (this == null) return "null"
+    // после проверки на null, `this` автоматически приводится к не-null типу, 
+    // поэтому toString() обращается (ориг.: resolves) к функции-члену класса Any
+    return toString()
+}
+```
+
+#### Область видимости расширений
+
+Чаще всего мы объявляем расширения на самом верхнем уровне, то есть сразу под пакетами:
+
+```kt
+package foo.bar
+ 
+fun Baz.goo() { ... } 
+```
+
+Для того, чтобы использовать такое расширение вне пакета, в котором оно было объявлено, нам надо импортировать его на стороне вызова:
+
+```kt
+package com.example.usage
+
+import foo.bar.goo // импортировать все расширения за именем "goo"
+                   // или
+import foo.bar.*   // импортировать все из "foo.bar"
+
+fun usage(baz: Baz) {
+    baz.goo()
+)
+```
+
+### Классы данных
+
+Нередко мы создаём классы, единственным назначением которых является хранение данных. Функционал таких классов зависит от самих данных, которые в них хранятся. В Kotlin класс может быть отмечен словом data:
+
+```kt
+data class User(val name: String, val age: Int)
+```
+
+Такой класс называется классом данных. Компилятор автоматически формирует следующие члены данного класса из свойств, объявленных в основном конструкторе:
+
+* пару функций equals()/hashCode(),
+* функцию toString() в форме "User(name=John, age=42)",
+* компонентные функции componentN(), которые соответствуют свойствам, в соответствии с порядком их объявления,
+* функцию copy() (см. ниже)
+
+Если какая-либо из этих функций явно определена в теле класса (или унаследована от родительского класса), то генерироваться она не будет.
+
+Для обеспечения согласованности и осмысленного поведения сгенерированного кода классы данных должны удовлетворять следующим требованиям:
+
+* Основной конструктор должен иметь как минимум один параметр;
+* Все параметры основного конструктора должны быть отмечены, как val или var;
+* Классы данных не могут быть абстрактными, open, sealed или inner;
+* (до версии 1.1) Классы данных не могут наследоваться от других классов (но могут реализовывать интерфейсы).
+
+Дополнительно, генерация членов классов данных при наследовании подчиняется следующим правилам:
+
+* Если существуют явные реализации equals(), hashCode() или toString() в теле класса данных или конечные (final) реализации в суперклассе, то эти функции не генерируются, а используются существующие реализации;
+* Если суперкласс включает функции componentN(), которые являются открытыми и возвращают совместимые типы, соответствующие компонентные функции создаются для класса данных и переопределяют функции суперкласса. Если функции суперкласса не могут быть переопределены из-за несовместимости сигнатур или являются конечными (final), выдаётся сообщение об ошибке;
+* Наследование класса данных от типа, который уже имеет функцию copy(...) с совпадающей сигнатурой не рекомендуется в Kotlin 1.2 и запрещена в Kotlin 1.3;
+* Предоставление явных реализаций для функций componentN() и copy() не допускается.
+
+Для того, чтобы у сгенерированного в JVM класса был конструктор без параметров, значения всех свойств должны быть заданы по умолчанию
+
+```kt
+data class User(val name: String = "", val age: Int = 0)
+```
+
+#### Свойства, объявленные в теле класса
+
+Обратите внимание, что компилятор использует только свойства, определенные в основном конструкторе для автоматически созданных функций. Чтобы исключить свойство из автоматически созданной реализации, объявите его в теле класса:
+
+```kt
+data class Person(val name: String) {
+    var age: Int = 0
+}
+```
+
+Только свойство *name* будет учитываться в реализациях функций toString(), equals(), hashCode() и copy(), и будет создана только одна компонентная функция component1(). Даже если два объекта класса Person будут иметь разные значения свойств age, они будут считаться равными.
+
+```kt
+    val person1 = Person("John")
+    val person2 = Person("John")
+    person1.age = 10
+    person2.age = 20
+    println("${person1 == person2}") // выведет "true"
+```

BIN
img/04010.png


BIN
img/04011.png


BIN
img/04012.png


BIN
img/04013.png


BIN
img/04014.png


BIN
img/04015.png


BIN
img/04016.png


BIN
img/04017.png


BIN
img/04018.png


BIN
img/04019.png


BIN
img/04020.png


BIN
img/04021.png


+ 6 - 0
readme.md

@@ -128,6 +128,12 @@ http://sergeyteplyakov.blogspot.com/2014/01/microsoft-fakes-state-verification.h
 
 5. [Создание, изменение продукции](./articles/cs_edit_product.md)
 
+## Тема 5.1.4. Разработка мобильных приложений. Android Studio. Kotlin.
+
+1. [Основы языка Kotlin](./articles/kotlin.md)
+
+2. [Первый проект в Android Studio](./articles/android_studio.md)
+
 <!--
 
 https://office-menu.ru/uroki-sql Уроки SQL