Что такое модуль kotlin
Все используемые типы, а также компоненты типов (классы, объекты, интерфейсы, конструкторы, функции, свойства) имеют определеннй уровень видимости, определяемый модификатором видимости (модификатором доступа). Модификатор видимости определяет, где те или иные типы и их компоненты доступны и где их можно использовать. В Kotlin есть следующие модификаторы видимости:
- private : классы, объекты, интерфейсы, а также функции и свойства, определенные вне класса, с этим модификатором видны только в том файле, в котором они определены. Члены класса с этим модификатором видны только в рамках своего класса
- protected : члены класса с этим модификатором видны в классе, в котором они определены, и в классах-наследниках
- internal : классы, объекты, интерфейсы, функции, свойства, конструкторы с этим модификатором видны в любой части модуля, в котором они определены. Модуль представляет набор файлов Kotlin, скомпилированных вместе в одну структурную единицу. Это может быть модуль IntelliJ IDEA или проект Maven
- public : классы, функции, свойства, объекты, интерфейсы с этим модификатором видны в любой части программы. (При этом если функции или классы с этим модификатором определены в другом пакете их все равно нужно импортировать)
Для установки уровня видимости модификатор ставится перед ключевыми словами var/val/fun в самом начале определения свойства или функции.
Если модификатор видимости явным образом не указан, то применяется модификатор public . То есть следующий класс
class Person() < var name = "Undefined" var age = 18 fun printPerson()< println("Name: $name Age: $age") >>
Будет эквивалентен следующему определению класса:
class Person() < public var name = "Undefined" public var age = 18 public fun printPerson()< println("Name: $name Age: $age") >>
Если свойства объявляются через первичный конструктор и для них явным образом не указан модификатор видимости:
class Person(val name: String, val age: Int) < public fun printPerson()< println("Name: $name Age: $age") >>
То также к таким свойствам автоматически применяется public :
class Person(public val name: String, public val age: Int) < public fun printPerson()< println("Name: $name Age: $age") >>
Соответственно мы можем обращаться к подобным компонентам класса в любом месте программы:
fun main() < val tom = Person("Tom", 37) tom.printPerson() // Name: Tom Age: 37 println(tom.name) println(tom.age) >
private
Если же к свойствам и методам применяется модификатор private , то к ним нельзя будет обратиться извне — вне данного класса.
class Person(private val name:String, _age: Int) < private val age = _age fun printPerson()< printName() printAge() >private fun printName() < println("Name: $name") >private fun printAge() < println("Age: $age") >> fun main() < val tom = Person("Tom", 37) tom.printPerson() // println(tom.name) // Ошибка! - свойство name - private // tom.printAge() // Ошибка! - функция printAge - private >
В данном случае класс Person определяет два свойства name (имя человека) и age (возраст человека). Чтобы было более показательно, одно свойство определено через конструктор, а второе как переменная класса. И поскольку эти свойства определены с модификатором private , то мы можем к ним обращаться только внутри этого класса. Вне класса обращаться к ним нельзя:
println(tom.name) // Ошибка! - свойство name - private
Также в классе определены три функции printPerson() , printAge() и printName() . Последние две функции выводят значения свойств. А функция printPerson выводит информацию об объекте, вызывая предыдущие две функции.
Однако функции printAge() и printName() определены как приватные, поэтому их можно использовать только внутри класса:
tom.printAge() // Ошибка! - функция printAge - private
protected
Модификатор protected определяет свойства и функции, которые из вне класса видны только в классах-наследниках:
fun main() < val tom = Employee("Tom", 37) tom.printEmployee() // Name: Tom Age: 37 // println(tom.name) // Ошибка! - свойство name - protected // tom.printPerson() // Ошибка! - функция printPerson - protected >open class Person(protected val name:String, private val age: Int) < protected fun printPerson()< printName() printAge() >private fun printName() < println("Name: $name") >private fun printAge() < println("Age: $age") >> class Employee(name:String, age: Int) : Person(name, age) < fun printEmployee()< println("Employee $name. Full information:") printPerson() // printName() // нельзя - printName - private // println("Age: $age") // нельзя age - private >>
Здесь в классе Person свойство name определенно как protected , поэтому оно доступно в классе-наследнике Employee (однако вне базового и производного класса — например, в функции main оно недоступно). А вот свойство age — приватное, поэтому оно доступно только внутри класса Person.
Также в классе Employee будет доступна функция printPerson() , так как она имеет модификатор protected , а функции printAge() и printName() с модификатором private будут недоступны.
Модификаторы конструкторов
Конструкторы как первичные, так и вторичные также могут иметь модификаторы. Модификатор указывается перед ключевым словом constructor . По умолчанию они имеют модификатор public . Если для первичного конструктора необходимо явным образом установить модификатор доступа, то конструктор определяется с помощью ключевого слова constructor :
fun main() < // val bob = Person("Bob") // Так нельзя - конструктор private >open class Person private constructor(val name:String) < fun printPerson()< println("Name: $name") >> // class Employee(name:String) : Person(name) // так нельзя - конструктор в Person private
Стоит отметить, что в данном случае, поскольку конструктор приватный мы не можем его использовать вне класса ни для создания объекта класса в функции main, ни при наследовании. Но мы можем использовать такой конструктор в других конструкторах внутри класса:
fun main() < val tom = Employee("Tom", 37) tom.printPerson() >open class Person private constructor(val name:String) < var age: Int = 0 protected constructor(_name:String, _age: Int): this(_name)< // вызываем приватный конструктор age = _age >fun printPerson() < println("Name: $name Age: $age") >> class Employee(name:String, age: Int) : Person(name, age)
Здесь вторичный конструктор класса Person, который имеет модификатор protected (то есть доступен в текущем классе и классах-наследниках) вызывает первичный конструктор класса Person, который имеет модификатор private .
Модификаторы объектов и типов верхнего уровня
Классы, а также переменные и функции, которые определены вне других классов, также могут иметь модификаторы public, private и internal.
Допустим, у нас есть файл base.kt , который определяет одноименный пакет:
package base private val privateVal = 3 val publicVal = 5 private class PrivateClass(val name: String) class PublicClass(val name:String) private fun privateFun() < println("privateFn") println(privateVal) val privateClass= PrivateClass("Tom") >fun publicFun()
Внутри данного файла мы можем использовать его приватные переменные, функции классы. Однако при подключении этого пакета в другие файлы, приватные переменные, функции и классы будут недоступны:
import base.* fun main() < publicFun() val publicClass= PublicClass("Tom") println(publicVal) // privateFun() // функция недоступна // val privateClass= PrivateClass("Tom") // класс недоступен // println(privateVal) // переменная недоступна >
Однако даже внутри одного файла есть ограничения на использование приватных классов:
package email private class Message(val text: String) fun send(message: Message, address : String)< println("Message `$` has been sent to $address") >
Здесь мы столкнемся с ошибкой, так как публичная функция не может принимать параметр приватного класса. И в данном случае нам надо либо сделать класс Message публичным, либо функцию send приватной.
Модификаторы доступа
Классы, объекты, интерфейсы, конструкторы, функции, свойства и их сеттеры могут иметь модификаторы доступа. Геттеры всегда имеют тут же видимость, что и свойства, к которым они относятся.
В Kotlin предусмотрено четыре модификатора доступа: private , protected , internal и public . Если явно не использовать никакого модификатора, то по умолчанию применяется public .
На этой странице вы узнаете, как использовать модификаторы для всех областей видимости.
Пакеты
Функции, свойства, классы, объекты и интерфейсы могут быть объявлены на самом «высоком уровне» прямо внутри пакета.
// имя файла: example.kt package foo fun baz() < /*. */ >class Bar < /*. */ >
- Если модификатор доступа не указан, будет использован public . Это значит, что весь код данного объявления будет виден из космоса;
- Если вы пометите объявление словом private , оно будет иметь видимость только внутри файла, где было объявлено;
- Если вы используете internal , видимость будет распространяться на весь модуль;
- protected запрещено использовать в объявлениях «высокого уровня».
Чтобы использовать видимое объявление верхнего уровня из другого пакета, вы должны импортировать его.
// имя файла: 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 члены.
В Kotlin внешний класс не видит private члены своих вложенных классов.
Если вы переопределите protected или internal член и явно не укажете его видимость, переопределённый элемент будет иметь тот же модификатор, что и исходный.
open class Outer < private val a = 1 protected open val b = 2 internal open 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 override val c = 7 // c - internal >class Unrelated(o: Outer) < // o.a и o.b не видно // o.c и o.d видно (тот же модуль) // Outer.Nested не видно, и Nested::e также не видно >
Конструкторы
Для указания видимости основного конструктора класса используется следующий синтаксис:
You need to add an explicit `constructor` keyword. —>
Вам необходимо добавить ключевое слово constructor .
class C private constructor(a: Int)
В этом примере конструктор помечен private . По умолчанию все конструкторы имеют модификатор доступа public , то есть видны везде, где виден сам класс (а вот конструктор internal класса видно только в том же модуле).
Локальные объявления
Локальные переменные, функции и классы не могут иметь модификаторов доступа.
Модули
Модификатор доступа internal означает, что этот член видно в рамках его модуля. Модуль — это набор скомпилированных вместе Kotlin файлов, например:
- модуль IntelliJ IDEA;
- Maven проект;
- исходный набор Gradle (за исключением того, что исходный набор test может получить доступ к внутренним объявлениям main );
- набор скомпилированных вместе файлов с одним способом вызова задачи в Ant.
© 2015—2023 Open Source Community
Модификаторы доступа (access modifiers)
Существуют четыре модификатора доступа (или видимости):
Модификаторы ставятся перед классом, функцией или свойством и позволяют гибко настраивать степень доступности из разных участков кода.
Модификатор public
Модификатор public используется по умолчанию и его можно не указывать. Но иногда можно явно указать, чтобы лишний раз напомнить, что доступ к классу возможен отовсюду.
Члены класса и объявления верхнего уровня с данным модификатором также доступны повсюду.
Модификатор private
Если функция или свойство имеют модификатор private, то они доступны только внутри самого класса.
В одном файле можно объявить классы, а также функции топ-уровня и свойства топ-уровня с модификатором private. Тогда они могут взаимодействовать между собой внутри этого файла.
Рассмотрим пример. Создадим файл TopLevel.kt.
private var index = 0 // свойство топ-уровня private class Cat(val name: String) // класс топ-уровня private fun recordCat( // функция топ-уровня cat: Cat ) < println("Кот #$index: $") index++ > fun recordCats() < recordCat(Cat("Васька")) recordCat(Cat("Барсик")) >fun getCatsCount()
Мы объявили закрытое свойство index, однако оно доступно в закрытой функции recordCat() и открытой функции getCatsCount(), потому то все они находятся в одном файле.
Аналогично закрытый класс Cat доступен функции recordCat() и т.д.
Попробуем создать экземпляр класса Cat в MainActivity.kt. Студия не позволит вам сделать этого.
По той же причине мы не сможем вызвать функцию recordCat().
А вот две функции без модификаторов мы можем вызвать.
override fun onCreate(savedInstanceState: Bundle?)
Эти функции обращаются к закрытым классам и свойствам, но так как они объявлены в одном файле, то могут получить доступ к ним.
Управление доступом часто используют при создании класса, когда нужно разграничить область видимости у разных свойств и функций. Создадим снова класс Cat следующим образом.
class Cat(private var isReady: Boolean) < private fun scratch() = println("Цап-царап") public fun bite() = println("Я делаю кусь") fun hooligan() < isReady = true scratch() bite() >>
Свойство isReady не будет доступно из других мест, его можно поменять только внутри класса, в нашем случае в функции hooligan(). Также мы не сможем вызвать функцию scratch(). Остальные функции доступны. И если мы вызовем функцию hooligan(), то она в свою очередь под капотом может вызвать закрытую функцию scratch().
override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val cat = Cat(false) cat.bite() // Нельзя обратиться напрямую // cat.isReady // cat.scratch() cat.hooligan() >
Подобный подход удобен при разработке библиотек. Вы как разработчик можете создавать вспомогательные функции и свойства, которые доступны только внутри вашего класса. А программист, который будет пользоваться вашей библиотекой, сможет вызывать только разрешённые вами функции и свойства.
Модификатор internal
В Kotlin добавлен новый модификатор internal, обеспечивающий видимость в границах модуля. Модуль — это набор файлов, компилируемых вместе (модуль в IDEA, проект Eclipse, Maven, Gradle и т.д.). Члены класса и объявления верхнего уровня доступны в пределах модуля.
Модификатор protected
Члены класса с модификатором protected доступны в подклассах.
1. Основы Kotlin. Простые функции
В библиотеке Java определено большое количество математических функций, предназначенных для выполнения более сложных операций. Для примера их использования рассмотрим задачу решения квадратного уравнения ax 2 + bx + c = 0.
Напомним, что корни квадратного уравнения ищутся по формуле x1,2 = (-b ± √d) / (2a), где d — дискриминант квадратного уравнения — вычисляется как d = b 2 -4ac. Мы решим эту задачу в упрощённом виде — найти какой-нибудь из двух возможных корней, скажем, тот, в котором в числителе используется знак плюс.
Для начала напишем функцию для расчёта дискриминанта (она ещё пригодится нам в будущем). Для расчёта b 2 применим уже написанную выше функцию sqr(x: Double) .
fun discriminant(a: Double, b: Double, c: Double) = sqr(b) - 4 * a * c
В приведённой записи b является аргументом функции sqr . Запись вида sqr(b) называется вызовом функции sqr . Подчеркнём отличие параметра и аргумента — параметр определяется внутри функции и имеет определённое имя, в данном случае x , а аргумент передаётся в функцию снаружи и может являться как именем переменной, так и более сложным выражением.
Теперь напишем функцию для поиска корня квадратного уравнения. Для вычисления квадратного корня применим готовую математическую функцию sqrt(x: Double) из математической библиотеки Котлина.
fun quadraticEquationRoot(a: Double, b: Double, c: Double) = (-b + kotlin.math.sqrt(discriminant(a, b, c))) / (2 * a)
Здесь мы, в свою очередь, используем уже написанную функцию discriminant для поиска дискриминанта, и выражение discriminant(a, b, c) , то есть дискриминант уравнения, является аргументом функции sqrt . Это как раз тот случай, когда аргумент является сложным выражением.
Обратите внимание на нотацию kotlin.math. перед именем функции sqrt . Поскольку готовых функций существует очень много, они разбиты на так называемые пакеты и классы внутри пакетов. kotlin.math.sqrt является полнымименем функции вычисления квадратного корня, а sqrt — её коротким именем. Из-за неудобства работы с полными именами, чаще используется следующая запись:
// Разрешение использовать короткие имена для ВСЕХ функций из пакета kotlin.math import kotlin.math.* fun quadraticEquationRoot(a: Double, b: Double, c: Double) = (-b + sqrt(discriminant(a, b, c))) / (2 * a)
Здесь import — так называемая директива импорта имён, смысл её пояснён в комментарии.
Примеры других функций из kotlin.math :
- abs(x: Int) или abs(x: Double) — модуль;
- sqrt(x: Double) — квадратный корень;
- Double.pow(x: Double) — возведение в степень x y , используется как x.pow(y) ;
- sin(x: Double) — синус, cos(x: Double) — косинус, tan(x: Double) — тангенс, все три функции считают, что x задан в радианах;
- exp(x: Double) — экспонента e x ;
- log(x: Double), log10(x: Double) — соответственно натуральный и десятичный логарифм;
- min(x: Int, y: Int) или min(x: Double, y: Double) — минимум из двух чисел;
- max(x: Int, y: Int) или max(x: Double, y: Double) — максимум из двух чисел.
В том же пакете kotlin.math определены константы PI = 3.14… и E = 2.718…
Переменные в функциях
Выше мы рассмотрели примеры с функциями sqr , discriminant и sqRoot , вычисление результата в которых занимало одну строчку кода. Однако, в программировании это скорее редкий случай; гораздо чаще расчёт результата функции предполагает реализацию некоторой последовательности вычислений — алгоритма. Для сохранения результатов промежуточных вычислений программисты придумали переменные.
Рассмотрим, например, задачу вычисления произведения двух корней квадратного уравнения. Напомним, что корни квадратного уравнения вычисляются как (-b+√d)/(2a) и (-b-√d)/(2a) соответственно, где d — дискриминант квадратного уравнения. При вычислении произведения удобно вначале сохранить вычисленный корень из дискриминанта в переменной sd , так как он используется при вычислении обоих корней. Затем нужно вычислить оба корня x1 и x2 и уже потом рассчитать их произведение. На Котлине это записывается следующим образом:
fun quadraticRootProduct(a: Double, b: Double, c: Double): Double /* тип обязателен */ < // Тело функции в виде блока val sd = sqrt(discriminant(a, b, c)) val x1 = (-b + sd) / (2 * a) val x2 = (-b - sd) / (2 * a) return x1 * x2 // Результат >
В этом примере тело функции записано в виде блока в фигурных скобках, в противоположность телу в виде выражения — как в функциях sqr и discriminant выше. Знак равенства при этом убирается и обязательно указывается тип результата функции. В примере присутствуют три промежуточные переменные — d , x1 , x2 . Определение промежуточной переменной в Котлине начинается с ключевого слова val (сокращение от value — значение), за которым следует имя переменной и, после знака равенства — её значение. При желании можно также указать тип переменной, например:
// . val sd: Double = sqrt(discriminant(a, b, c))
Если тип переменной не указан, он определяется автоматически, например, в данном случае он совпадёт с типом результата функции sqrt .
Блок состоит из так называемых операторов (в примере их четыре), выполняющихся по порядку сверху вниз. Преждечем использовать какую-либо переменную, её следует определить. Например, такая запись привела бы к ошибке:
fun quadraticRootProduct(a: Double, b: Double, c: Double): Double < val x1 = (-b + sd) / (2 * a) // Unresolved reference: sd val x2 = (-b - sd) / (2 * a) // Unresolved reference: sd val sd = sqrt(discriminant(a, b, c)) return x1 * x2 // Результат >
Последний оператор функции, начинающийся с ключевого слова return, определяет значение её результата; returnпереводится с английского как вернуть (результат). Функция quadraticRootProduct в первую очередь вычислит значение переменной sd , используя другие функции discriminant и sqrt . Затем произойдёт вычисление переменных x1 и x2 и лишь в конце — вычисление результата в операторе return.
Для сравнения, приведём запись той же функции, не использующей переменные:
fun quadraticRootProduct(a: Double, b: Double, c: Double) = ((-b + sqrt(discriminant(a, b, c))) / (2 * a)) * ((-b - sqrt(discriminant(a, b, c))) / (2 * a))
Хотя и записанная в одну строчку, такая функция является гораздо менее понятной, при её написании легко запутаться при расстановке скобок. Кроме того, в ней происходит двухкратное вычисление корня из дискриминанта, чего следует избегать.
