Статьи

Kotlin From Scratch: пакеты и основные функции

Kotlin — это современный язык программирования, который компилируется в байт-код Java. Он бесплатный и с открытым исходным кодом , и обещает сделать кодирование для Android еще более увлекательным.

В предыдущей статье вы узнали о коллекциях и коллекциях в Котлине. В этом руководстве мы продолжим изучать язык, рассмотрев, как организовать код с помощью пакетов, а затем перейдем к введению в функции в Kotlin.

Если вы знакомы с Java, вы знаете, что Java использует пакеты для группировки связанных классов; например, пакет java.util имеет ряд полезных служебных классов. Пакеты объявляются с помощью ключевого слова package , и любой файл Kotlin с объявлением package в начале может содержать объявления классов, функций или интерфейсов.

Глядя на код ниже, мы объявили пакет com.chikekotlin.projectx используя ключевое слово package . Кроме того, мы объявили класс MyClass (мы обсудим классы в Kotlin в будущих публикациях) внутри этого пакета.

1
2
3
package com.chikekotlin.projectx
 
class MyClass

Теперь полное имя класса MyClasscom.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()
}

Обратите внимание, что временное имя используется только внутри файла, которому оно было назначено.

Функция группирует серию операторов кода, которые выполняют задачу. Детали реализации функции скрыты от звонящего.

В 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 не поддерживает значения параметров по умолчанию в методах, вам нужно будет явно указать все значения параметров при вызове функции 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 в качестве аргумента ей вызовет:

Результат выполнения кода IntelliJ IDEA
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 SDK
    Java против Kotlin: стоит ли использовать Kotlin для разработки под Android?
    Джессика Торнсби
  • Android SDK
    Совет: пишите более чистый код с помощью Kotlin SAM Conversions
    Ашраф Хатхибелагал
  • Android SDK
    Android O: проверка номера телефона с помощью SMS-токенов