Kotlin — это современный язык программирования, который компилируется в байт-код Java. Он бесплатный и с открытым исходным кодом , и обещает сделать кодирование для Android еще более увлекательным.
В предыдущей статье вы узнали о коллекциях и коллекциях в Котлине. В этом руководстве мы продолжим изучать язык, рассмотрев, как организовать код с помощью пакетов, а затем перейдем к введению в функции в Kotlin.
1. Пакеты
Если вы знакомы с Java, вы знаете, что Java использует пакеты для группировки связанных классов; например, пакет java.util
имеет ряд полезных служебных классов. Пакеты объявляются с помощью ключевого слова package
, и любой файл Kotlin с объявлением package
в начале может содержать объявления классов, функций или интерфейсов.
декларация
Глядя на код ниже, мы объявили пакет com.chikekotlin.projectx
используя ключевое слово package
. Кроме того, мы объявили класс MyClass
(мы обсудим классы в Kotlin в будущих публикациях) внутри этого пакета.
1
2
3
|
package com.chikekotlin.projectx
class MyClass
|
Теперь полное имя класса MyClass
— com.chikekotlin.projectx.MyClass
.
1
2
3
4
5
|
package com.chikekotlin.projectx
fun saySomething(): String {
return «How far?»
}
|
В приведенном выше коде мы создали функцию верхнего уровня (мы скоро к этому вернемся). Так же, как и в MyClass
, полное имя функции saySomething()
— com.chikekotlin.projectx.saySomething
.
импорт
В Kotlin мы используем объявление import
чтобы компилятор мог найти классы, функции, интерфейсы или объекты для импорта. В Java, с другой стороны, мы не можем напрямую импортировать функции или методы — только классы или интерфейсы.
Мы используем import
для доступа к функции, интерфейсу, классу или объекту вне пакета, в котором он был объявлен.
1
2
3
4
5
|
import com.chikekotlin.projectx.saySomething
fun main(args: Array<String>) {
saySomething() // will print «How far?»
}
|
В приведенном выше фрагменте кода мы импортировали функцию saySomething()
из другого пакета, а затем выполнили эту функцию.
Kotlin также поддерживает импорт подстановочных знаков с помощью оператора *
. Это импортирует все классы, интерфейсы и функции, объявленные в пакете одновременно. Это, однако, не рекомендуется — обычно лучше сделать импорт явным.
1
|
import com.chikekotlin.projectx.*
|
Импорт псевдонимов
Если у вас есть библиотеки с конфликтующими именами классов или функций (например, каждая из них объявляет функцию с одинаковым именем), вы можете использовать ключевое слово as
чтобы присвоить этой импортированной сущности временное имя.
1
2
3
4
5
6
|
import com.chikekotlin.projectx.saySomething
import com.chikekotlin.projecty.saySomething as projectYSaySomething
fun main(args: Array<String>) {
projectYSaySomething()
}
|
Обратите внимание, что временное имя используется только внутри файла, которому оно было назначено.
2. Функции
Функция группирует серию операторов кода, которые выполняют задачу. Детали реализации функции скрыты от звонящего.
В Kotlin функции определяются с помощью ключевого слова fun
, как показано в следующем примере:
1
2
3
4
5
|
fun hello(name: String): String {
return «Hello $name»
}
val message = hello(«Chike»)
print(message) // will print «Hello Chike»
|
В приведенном выше коде мы определили простую функцию hello()
с одним name
параметра типа String
. Эта функция возвращает тип String
. Формат определения параметров для функций: name: type
, например, age: Int
, price: Double
, student: StudentClass
.
1
2
3
4
5
|
fun hello(name: String): Unit {
print(«Hello $name»)
}
hello(«Chike») // will print «Hello Chike»
|
Вышеприведенная функция похожа на предыдущую, но обратите внимание, что эта функция имеет тип возврата Unit
. Поскольку эта функция не возвращает нам никакого значимого значения — она просто печатает сообщение — по умолчанию ее типом возврата является Unit
. Unit
— это объект Kotlin (мы обсудим объекты Kotlin в последующих публикациях), который похож на типы Void
в Java и C.
1
2
3
|
public object Unit {
override fun toString() = «kotlin.Unit»
}
|
Обратите внимание, что если вы явно не объявляете тип возвращаемого значения как Unit
, тип выводится компилятором.
1
2
3
|
fun hello(name: String) { // will still compile
print(«Hello $name»)
}
|
Однолинейные функции
Однострочные или однострочные функции — это функции, которые являются просто выражениями. В этой функции мы избавляемся от фигурных скобок и используем символ =
перед выражением. Другими словами, мы избавляемся от функционального блока.
1
2
3
|
fun calCircumference(radius: Double): Double {
return (2 * Math.PI) * radius
}
|
Вышеприведенную функцию можно сократить до одной строки:
1
|
fun calCircumference(radius: Double) = (2 * Math.PI) * radius
|
Взглянув на обновленную функцию выше, вы можете увидеть, что мы сделали наш код более кратким, удалив фигурные скобки {}
, ключевое слово return
, а также тип возврата (который выводится компилятором).
Вы все еще можете включить тип возвращаемого значения, чтобы быть более явным, если хотите.
1
|
fun calCircumference(radius: Double): Double = (2 * Math.PI) * radius
|
Именованные параметры
Именованные параметры позволяют более читаемые функции, называя параметры, которые передаются в функцию при вызове.
В следующем примере мы создали функцию, которая печатает мое полное имя.
1
2
3
|
fun sayMyFullName(firstName: String, lastName: String, middleName: String): Unit {
print(«My full name is $firstName $middleName $lastName»);
}
|
Чтобы выполнить вышеописанную функцию, мы бы просто назвали ее так:
1
|
sayMyFullName(«Chike», «Nnamdi», «Mgbemena»)
|
Глядя на вызов функции выше, мы не знаем, какие аргументы типа String
соответствуют каким параметрам функции (хотя некоторые IDE, такие как IntelliJ IDEA, могут помочь нам). Пользователи функции должны будут заглянуть в сигнатуру функции (или исходный код) или документацию, чтобы узнать, что соответствует каждому параметру.
1
|
sayMyFullName(firstName = «Chike», middleName = «Nnamdi», lastName = «Mgbemena»)
|
Во втором вызове функции выше мы указали имена параметров перед значениями аргументов. Вы можете видеть, что этот вызов функции более четкий и более читаемый, чем предыдущий. Этот способ вызова функций помогает уменьшить вероятность ошибок, которые могут произойти, если аргументы одного типа поменялись местами по ошибке.
Вызывающий также может изменить порядок параметров, используя именованные параметры. Например:
1
|
sayMyFullName(lastName = «Mgbemena», middleName = «Nnamdi», firstName = «Chike») // will still compile
|
В приведенном выше коде мы поменяли позицию аргумента firstName
на lastName
. Порядок аргументов не имеет значения с именованными параметрами, потому что компилятор отобразит каждый из них на правильный параметр функции.
Параметры по умолчанию
В Kotlin мы можем дать функции значения по умолчанию для любого из ее параметров. Эти значения по умолчанию используются, если ничего не назначено аргументам во время вызова функции. Чтобы сделать это в Java, нам нужно создать разные перегруженные методы.
Здесь, в нашем calCircumference()
, мы изменили метод, добавив значение по умолчанию для параметра pi
Math.PI
, константу из пакета java.lang.Math
.
1
|
fun calCircumference(radius: Double, pi: Double = Math.PI): Double = (2 * pi) * radius
|
Когда мы вызываем эту функцию, мы можем либо передать наше приблизительное значение для pi
либо использовать значение по умолчанию.
1
2
|
print(calCircumference(24.0)) // used default value for PI and prints 150.79644737231007
print(calCircumference(24.0, 3.14)) // passed value for PI and prints 150.72
|
Давайте посмотрим на другой пример.
1
2
3
|
fun printName(firstName: String, middleName: String = «N/A», lastName: String) {
println(«first name: $firstName — middle name: $middleName — last name: $lastName»)
}
|
В следующем коде мы попытались вызвать функцию, но она не скомпилируется:
1
|
printName(«Chike», «Mgbemena») // won’t compile
|
В приведенном выше вызове функции я передаю свое имя и фамилию функции и надеюсь использовать значение по умолчанию для второго имени. Но это не скомпилируется, потому что компилятор запутался. Он не знает, для чего используется аргумент «Mgbemena» — для параметра middleName
или lastName
?
Чтобы решить эту проблему, мы можем объединить именованные параметры и параметры по умолчанию.
1
|
printName(«Chike», lastName = «Mgbemena») // will now compile
|
Совместимость с Java
Учитывая, что Java не поддерживает значения параметров по умолчанию в методах, вам нужно будет явно указать все значения параметров при вызове функции Kotlin из Java. Но Kotlin предоставляет нам функциональность, облегчающую вызов Java-вызовов, путем аннотирования функции Kotlin с помощью @JvmOverloads
. Эта аннотация даст команду компилятору Kotlin сгенерировать перегруженные функции Java для нас.
В следующем примере мы аннотировали calCirumference()
с помощью @JvmOverloads
.
1
2
|
@JvmOverloads
fun calCircumference(radius: Double, pi: Double = Math.PI): Double = (2 * pi) * radius
|
Следующий код был сгенерирован компилятором Kotlin, чтобы вызывающие Java могли затем выбрать, какой из них вызывать.
1
2
3
|
// Java
double calCircumference(double radius, double pi);
double calCircumference(double radius);
|
В последнем сгенерированном определении метода Java параметр pi
был опущен. Это означает, что метод будет использовать значение по умолчанию pi
.
Неограниченные аргументы
В Java мы можем создать метод для получения неопределенного числа аргументов, включив многоточие ( ...
) после типа в список параметров метода. Эта концепция также поддерживается функциями Kotlin с использованием модификатора vararg
, за которым следует имя параметра.
1
2
3
4
5
6
|
fun printInts(vararg ints: Int): Unit {
for (n in ints) {
print(«$n\t»)
}
}
printInts(1, 2, 3, 4, 5, 6) // will print 1 2 3 4 5 6
|
Модификатор vararg
позволяет вызывающим абонентам передавать список аргументов через запятую. За кулисами этот список аргументов будет заключен в массив.
Когда функция имеет несколько параметров, параметр vararg
обычно является последним. Также возможно иметь параметры после vararg
, но вам нужно будет использовать именованные параметры, чтобы указать их при вызове функции.
1
2
3
4
5
6
7
8
|
fun printNumbers(myDouble: Double, myFloat: Float, vararg ints: Int) {
println(myDouble)
println(myFloat)
for (n in ints) {
print(«$n\t»)
}
}
printNumbers(1.34, 4.4F, 2, 3, 4, 5, 6) // will compile
|
Например, в приведенном выше коде параметр с модификатором vararg
находится на последней позиции в списке с несколькими параметрами (это то, что мы обычно делаем). Но что, если мы не хотим этого в последней позиции? В следующем примере он находится на второй позиции.
01
02
03
04
05
06
07
08
09
10
11
|
fun printNumbers(myDouble: Double, vararg ints: Int, myFloat: Float) {
println(myDouble)
println(myFloat)
for (n in ints) {
print(«$n\t»)
}
}
printNumbers(1.34, 2, 3, 4, 5, 6, myFloat = 4.4F) // will compile
printNumbers(1.34, ints = 2, 3, 4, 5, 6, myFloat = 4.4F) // will not compile
printNumbers(myDouble = 1.34, ints = 2, 3, 4, 5, 6, myFloat = 4.4F) // will also not
compile
|
Как вы можете наблюдать в обновленном коде выше, мы использовали именованные аргументы в последнем параметре для решения этой проблемы.
Оператор распространения
Допустим, мы хотим передать массив целых чисел нашей функции printNumbers()
. Функция ожидает, что значения будут развернуты в список параметров. Если вы попытаетесь передать массив непосредственно в printNumbers()
, вы увидите, что он не скомпилируется.
1
2
|
val intsArray: IntArray = intArrayOf(1, 3, 4, 5)
printNumbers(1.34, intsArray, myFloat = 4.4F) // won’t compile
|
Чтобы решить эту проблему, нам нужно использовать оператор распространения *
. Этот оператор распакует массив и затем передаст отдельные элементы в качестве аргументов в функцию для нас.
1
2
|
val intsArray: IntArray = intArrayOf(1, 3, 4, 5)
printNumbers(1.34, *intsArray, myFloat = 4.4F) // will now compile
|
Вставляя оператор распространения *
перед intsArray
в списке аргументов функции, код теперь компилируется и выдает тот же результат, как если бы мы передали элементы intsArray
в виде списка аргументов через запятую.
Возврат нескольких значений
Иногда мы хотим вернуть несколько значений из функции. Одним из способов является использование типа Pair
в Kotlin для создания Pair
а затем ее возврата. Эта структура Pair
включает в себя два значения, к которым впоследствии можно получить доступ. Этот тип Kotlin может принимать любые типы, предоставленные вами его конструктору. И более того, эти два типа даже не должны быть одинаковыми.
01
02
03
04
05
06
07
08
09
10
|
fun getUserNameAndState(id: Int): Pair<String?, String?> {
require(id > 0, { «Error: id is less than 0» })
val userNames: Map<Int, String> = mapOf(101 to «Chike», 102 to «Segun», 104 to «Jane»)
val userStates: Map<Int, String> = mapOf(101 to «Lagos», 102 to «Imo», 104 to «Enugu»)
val userName = userNames[id]
val userState = userStates[id]
return Pair(userName, userState)
}
|
В приведенной выше функции мы userState
новую Pair
, передав переменные userName
и userState
в качестве первого и второго аргументов соответственно ее конструктору, а затем вернули эту Pair
вызывающей стороне.
Следует также отметить, что в функции getUserNameAndState()
мы использовали функцию require()
. Эта вспомогательная функция из стандартной библиотеки используется, чтобы дать нашим вызывающим функциям предварительное условие для выполнения, иначе будет IllegalArgumentException
исключение IllegalArgumentException
(мы обсудим исключения в Kotlin в следующем посте). Необязательный второй аргумент для require()
— это литерал функции, возвращающий сообщение, которое будет отображаться в случае возникновения исключения. Например, вызов функции getUserNameAndState()
и передача -1
в качестве аргумента ей вызовет:
Получение данных из Pair
1
2
3
|
val userNameAndStatePair: Pair<String?, String?> = getUserNameAndState(101)
println(userNameAndStatePair.first) // Chike
println(userNameAndStatePair.second) // Lagos
|
В приведенном выше коде мы получили доступ к первому и второму значениям из типа Pair
, используя его first
и second
свойства.
Тем не менее, есть лучший способ сделать это: деструктуризация.
1
2
3
|
val (name, state) = getUserNameAndState(101)
println(name) // Chike
println(state) // Lagos
|
То, что мы сделали в обновленном коде выше, — это непосредственное назначение первого и второго значений возвращенного типа Pair
переменным name
и state
соответственно. Эта функция называется декларацией о разрушении .
Тройные возвращаемые значения и не только
А что если вы хотите вернуть три значения одновременно? Kotlin предоставляет нам еще один полезный тип, называемый Triple
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
fun getUserNameStateAndAge(id: Int): Triple<String?, String?, Int> {
require(id > 0, { «id is less than 0» })
val userNames: Map<Int, String> = mapOf(101 to «Chike», 102 to «Segun», 104 to «Jane»)
val userStates: Map<Int, String> = mapOf(101 to «Lagos», 102 to «Imo», 104 to «Enugu»)
val userName = userNames[id]
val userState = userStates[id]
val userAge = 6
return Triple(userNames[id], userStates[id], userAge)
}
val (name, state, age) = getUserNameStateAndAge(101)
println(name) // Chike
println(state) // Lagos
println(age) // 6
|
Я уверен, что некоторые из вас задаются вопросом, что делать, если вы хотите вернуть более трех значений. Ответ для этого будет в следующем посте, когда мы обсудим классы данных Kotlin.
Вывод
В этом руководстве вы узнали о пакетах и основных функциях на языке программирования Kotlin. В следующем уроке из серии Kotlin From Scratch вы узнаете больше о функциях в Kotlin. До скорого!
Чтобы узнать больше о языке Kotlin, я рекомендую посетить документацию Kotlin . Или посмотрите некоторые другие наши посты о разработке приложений для Android здесь на Envato Tuts +!
-
Android SDKВведение в компоненты архитектуры Android
-
Машинное обучениеСоздание интеллектуальных чат-ботов на Android с IBM Watson
-
Android SDKJava против Kotlin: стоит ли использовать Kotlin для разработки под Android?
-
Android SDKСовет: пишите более чистый код с помощью Kotlin SAM Conversions
-
Android SDKAndroid O: проверка номера телефона с помощью SMS-токенов