Основные принципы программирования: интроспекция и рефлексия
На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.
Часто во время работы программы нам бывает нужна информация о данных — например, какой у них тип или являются ли они экземпляром класса (в ООП). Опираясь на эти знания, нам нужно проводить над данными некоторые операции, или даже изменять их — но необходимого вида данных у нас может и не быть! Если вы ничего не поняли, не расстраивайтесь — мы подробно во всём разберёмся. Всё, что я здесь описал — это иллюстрация целей двух возможностей, присутствующих почти в каждом современном языке программирования: интроспекции и рефлексии.
Интроспекция
Интроспекция — это способность программы исследовать тип или свойства объекта во время работы программы. Как мы уже упоминали, вы можете поинтересоваться, каков тип объекта, является ли он экземпляром класса. Некоторые языки даже позволяют узнать иерархию наследования объекта. Возможность интроспекции есть в таких языках, как Ruby, Java, PHP, Python, C++ и других. В целом, инстроспекция — это очень простое и очень мощное явление. Вот несколько примеров использования инстроспекции:
// Java if(obj instanceof Person)
//PHP if ($obj instanceof Person) < // делаем что угодно >
В Python самой распространённой формой интроспекции является использование метода dir для вывода списка атрибутов объекта:
# Python class foo(object): def __init__(self, val): self.x = val def bar(self): return self.x . dir(foo(5)) => ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', 'bar', 'x']
В Ruby интроспекция очень полезна — в частности из-за того, как устроен сам язык. В нём всё является объектами — даже класс — и это приводит к интересным возможностям в плане наследования и рефлексии (об этом ниже). Если вы хотите узнать об этом больше, советую прочитать мини-цикл Metaprogramming in Ruby.
Прим. перев. Также не будет лишним прочитать нашу статью, посвящённую интроспекции в Ruby.
Вот несколько простых примеров интроспекции с использованием IRB (Interactive Ruby Shell):
# Ruby $ irb irb(main):001:0> A=Class.new => A irb(main):002:0> B=Class.new A => B irb(main):003:0> a=A.new => # irb(main):004:0> b=B.new => # irb(main):005:0> a.instance_of? A => true irb(main):006:0> b.instance_of? A => false irb(main):007:0> b.kind_of? A => true
Вы также можете узнать у объекта, экземпляром какого класса он является, и даже “сравнить” классы.
# Ruby irb(main):008:0> A.instance_of? Class => true irb(main):009:0> a.class => A irb(main):010:0> a.class.class => Class irb(main):011:0> A > B => true irb(main):012:0> B true
Однако интроспекция — это не рефлексия; рефлексия позволяет нам использовать ключевые принципы интроспекции и делать действительно мощные вещи с нашим кодом.
Рефлексия
Интроспекция позволяет вам изучать атрибуты объекта во время выполнения программы, а рефлексия — манипулировать ими. Рефлексия — это способность компьютерной программы изучать и модифицировать свою структуру и поведение (значения, мета-данные, свойства и функции) во время выполнения. Простым языком: она позволяет вам вызывать методы объектов, создавать новые объекты, модифицировать их, даже не зная имён интерфейсов, полей, методов во время компиляции. Из-за такой природы рефлексии её труднее реализовать в статически типизированных языках, поскольку ошибки типизации возникают во время компиляции, а не исполнения программы (подробнее об этом здесь). Тем не менее, она возможна, ведь такие языки, как Java, C# и другие допускают использование как интроспекции, так и рефлексии (но не C++, он позволяет использовать лишь интроспекцию).
По той же причине рефлексию проще реализовать в интерпретируемых языках, поскольку когда функции, объекты и другие структуры данных создаются и вызываются во время работы программы, используется какая-то система распределения памяти. Интерпретируемые языки обычно предоставляют такую систему по умолчанию, а для компилируемых понадобится дополнительный компилятор и интерпретатор, который следит за корректностью рефлексии.
Мне кажется, что мы сказали много об определении рефлексии, но смысла это пока несёт мало. Давайте взглянем на примеры кода ниже (с рефлексией и без), каждый из которых создаёт объект класса Foo и вызывает метод hello.
// ECMAScript - как JavaScript // Без рефлексии new Foo().hello() // С рефлексией // предполагаем, что Foo принадлежит this new this['Foo']()['hello']() // или не предполагаем new (eval('Foo'))()['hello']() // или вообще не заморачиваемся eval('new Foo().hello()')
// Java // Без рефлексии Foo foo = new Foo(); foo.hello(); // С рефлексией Object foo = Class.forName("complete.classpath.and.Foo").newInstance(); // Альтернатива: Object foo = Foo.class.newInstance(); Method m = foo.getClass().getDeclaredMethod("hello", new Class[0]); m.invoke(foo);
# Python # Без рефлексии obj = Foo() obj.hello() # С рефлексией class_name = "Foo" method = "hello" obj = globals()[class_name]() getattr(obj, method)() # С eval eval("Foo().hello()")
# Ruby # Без рефлексии obj = Foo.new obj.hello # С рефлексией class_name = "Foo" method = :hello obj = Kernel.const_get(class_name).new obj.send method # С eval eval "Foo.new.hello"
Этот список отнюдь не исчерпывает возможности рефлексии. Это очень мощный принцип, который к тому же является обычной практикой в метапрограммировании. Тем не менее, при использовании рефлексии нужно быть очень внимательным. Хотя у неё и есть свои преимущества, код, использующий рефлексию, значительно менее читаем, он затрудняет отладку, а также открывает двери по-настоящему плохим вещами, например, инъекции кода через выражения eval.
Eval-выражения
Некоторые рефлективные языки предоставляют возможность использования eval-выражений — выражений, которые распознают значение (обычно строку) как выражение. Такие утверждения — это самый мощный принцип рефлексии и даже метапрограммирования, но также и самый опасный, поскольку они представляют собой угрозу безопасности.
Рассмотрим следующий пример кода на Python, который принимает данные из стороннего источника в Сети (это одна из причин, по которой люди пользуются eval-выражениями):
session['authenticated'] = False data = get_data() foo = eval(data)
Защита программы будет нарушена, если кто-то передаст в метод get_data() такую строку:
"session.update(authenticated=True)"
Для безопасного использования eval-утверждений нужно сильно ограничивать формат входных данных — и обычно это лишь занимает лишнее время.
Заключение
Интроспекция и рефлексия — это очень мощные инструменты современных языков, и их понимание может позволить вам писать по-настоящему крутой код. Ещё раз отметим: интроспекция — это изучение атрибутов объекта, а рефлексия — это манипуляция ими. Будьте внимательны при использовании рефлексии, поскольку она может сделать ваш код нечитаемым и уязвимым. Чем больше сила, тем больше и ответственность — вот девиз всего, что связано с метапрограммированием.
Рефлексия
Рефлексия представляет собой процесс выявления типов во время выполнения приложения. Каждое приложение содержит набор используемых классов, интерфейсов, а также их методов, свойств и прочих кирпичиков, из которых складывается приложение. И рефлексия как раз и позволяет определить все эти составные элементы приложения. То есть основная задача рефлексии — это исследование типов.
Основной функционал рефлексии сосредоточен в пространстве имен System.Reflection . В нем мы можем выделить следующие основные классы:
- Assembly : класс, представляющий сборку и позволяющий манипулировать этой сборкой
- AssemblyName : класс, хранящий информацию о сборке
- MemberInfo : базовый абстрактный класс, определяющий общий функционал для классов EventInfo, FieldInfo, MethodInfo и PropertyInfo
- EventInfo : класс, хранящий информацию о событии
- FieldInfo : хранит информацию об определенном поле типа
- MethodInfo : хранит информацию об определенном методе
- PropertyInfo : хранит информацию о свойстве
- ConstructorInfo : класс, представляющий конструктор
- Module : класс, позволяющий получить доступ к определенному модулю внутри сборки
- ParameterInfo : класс, хранящий информацию о параметре метода
Эти классы представляют составные блоки типа и приложения: методы, свойства и т.д. Но чтобы получить информацию о членах типа, нам надо воспользоваться классом System.Type .
Класс Type представляет изучаемый тип, инкапсулируя всю информацию о нем. С помощью его свойств и методов можно получить эту информацию. Некоторые из его свойств и методов:
- Метод FindMembers() возвращает массив объектов MemberInfo данного типа
- Метод GetConstructors() возвращает все конструкторы данного типа в виде набора объектов ConstructorInfo
- Метод GetEvents() возвращает все события данного типа в виде массива объектов EventInfo
- Метод GetFields() возвращает все поля данного типа в виде массива объектов FieldInfo
- Метод GetInterfaces() получает все реализуемые данным типом интерфейсы в виде массива объектов Type
- Метод GetMembers() возвращает все члены типа в виде массива объектов MemberInfo
- Метод GetMethods() получает все методы типа в виде массива объектов MethodInfo
- Метод GetProperties() получает все свойства в виде массива объектов PropertyInfo
- Свойство Name возвращает имя типа
- Свойство Assembly возвращает название сборки, где определен тип
- Свойство Namespace возвращает название пространства имен, где определен тип
- Свойство IsArray возвращает true, если тип является массивом
- Свойство IsClass возвращает true, если тип представляет класс
- Свойство IsEnum возвращает true, если тип является перечислением
- Свойство IsInterface возвращает true, если тип представляет интерфейс
Получение типа
Чтобы управлять типом и получать всю информацию о нем, нам надо сперва получить данный тип. Это можно сделать тремя способами: с помощью оператора typeof , с помощью метода GetType() класса Object и применяя статический метод Type.GetType() .
Получение типа через typeof :
Type myType = typeof(Person); Console.WriteLine(myType); // Person public class Person < public string Name < get;>public Person(string name) => Name = name; >
Здесь определен класс Person с некоторой функциональностью. И чтобы получить его тип, используется выражение Type myType = typeof(Person);
Получение типа с помощью метода GetType , унаследованного от класса Object :
Person tom = new Person("Tom"); Type myType = tom.GetType();
В отличие от предыдущего примера здесь, чтобы получить тип, надо создавать объект класса.
И третий способ получения типа — статический метод Type.GetType() :
Type? myType = Type.GetType("Person", false, true);
Первый параметр указывает на полное имя класса с пространством имен. Второй параметр указывает, будет ли генерироваться исключение, если класс не удастся найти. В данном случае значение false означает, что исключение не будет генерироваться. И третий параметр указывает, надо ли учитывать регистр символов в первом параметре. Значение true означает, что регистр игнорируется. Поскольку указанный тип может отсутствовать, то метод возвращает объект nullable-типа
В данном случае класс основной программы и класс Person находятся в глобальном пространстве имен. Однако если тип располагается в другом пространстве имен, то его также надо указать:
Type? myType = Type.GetType("PeopleTypes.Person", false, true); Console.WriteLine(myType); // PeopleTypes.Person namespace PeopleTypes < public class Person < public string Name < get;>public Person(string name) => Name = name; > >
В качестве альтернативы можно применять оператор typeof, передавая в него имя типа с указанием пространства имен:
Type myType = typeof(PeopleTypes.Person);
Если нужный нам тип находится в другой сборке dll, то после полного имени класса через запятую указывается имя сборки:
Type myType = Type.GetType("PeopleTypes.Person, MyLibrary", false, true);
Теперь исследуем тип и получим некоторую информацию о нем.
Type myType = typeof(PeopleTypes.Person); Console.WriteLine($"Name: "); // получаем краткое имя типа Console.WriteLine($"Full Name: "); // получаем полное имя типа Console.WriteLine($"Namespace: "); // получаем пространство имен типа Console.WriteLine($"Is struct: "); // является ли тип структурой Console.WriteLine($"Is class: "); // является ли тип классом namespace PeopleTypes < class Person < public string Name < get; >public Person(string name) => Name = name; > >
Name: Person Full Name: PeopleTypes.Person Namespace: PeopleTypes Is struct: False Is class: True
Поиск реализованных интерфейсов
Чтобы получить все реализованные типом интерфейсы, надо использовать метод GetInterfaces() , который возвращает массив объектов Type:
Type myType = typeof(Person); Console.WriteLine(«Реализованные интерфейсы:»); foreach (Type i in myType.GetInterfaces()) < Console.WriteLine(i.Name); >public class Person : IEater, IMovable < public string Name < get;>public Person(string name) => Name = name; public void Eat() => Console.WriteLine($» eats»); public void Move()=> Console.WriteLine($» moves»); > interface IEater < void Eat(); >interface IMovable
Так как каждый интерфейс представляет объект Type, то для каждого полученного интерфейса можно также применить выше рассмотренные методы для извлечения информации о свойствах и методах
Но пока все примеры выше никак не использовали рефлексию. В следующих темах рассмотрим, как можно с помощью рефлексии получать компоненты типа и обращаться к ним, например, изменять значения приватных полей класса.
Java Reflection API
Java Reflection API — это программный интерфейс в языке Java, который позволяет приложениям анализировать свои компоненты и программное окружение, изменять собственное поведение и структуру. Позволяет исследовать информацию о полях, методах и конструкторах классов.

Освойте профессию «Java-разработчик»
С помощью механизма рефлексии можно обрабатывать типы, которые отсутствовали при компиляции, но появились во время выполнения программы. Рефлексия и наличие логически целостной модели выдачи информации об ошибках позволяют создавать корректный динамический код.
Возможности
Помимо самомодификации, API способен проводить самопроверку и самоклонирование. Чаще всего рефлексию Java используют:

- для получения информации о классах, интерфейсах, функциях, конструкторах, методах и модулях;
- изменения имен функций и классов во время выполнения программы;
- создания новых экземпляров классов;
- анализа и исполнения кода, поступающего из программного окружения;
- преобразования классов из одного типа в другой;
- создания массивов данных и манипуляций с ними;
- установления значений полей объектов по именам;
- получения доступа к переменным и методам, включая приватные, и к внешним классам;
- вызова методов объектов по именам.
Профессия / 14 месяцев
Java-разработчик
Освойте востребованный язык

Особенности рефлексии в Java
Снижение производительности программы
Рефлексия работает медленнее, чем обычные приемы по обработке классов, методов и переменных. Это связано с тем, что во время динамического определения многих типов оптимизация производительности становится недоступной. Поэтому не следует применять Reflection API во фрагментах кода, которые часто используются приложением, в особенности если скорость выполнения программы — приоритет разработчика.
Блокировка диспетчером безопасности
Для запуска рефлексии в программировании требуется разрешение на выполнение, которое, как правило, не выдается при работе программного компонента под управлением Security Manager (диспетчера безопасности).

Станьте Java-разработчиком
и создавайте сложные сервисы
на востребованном языке
Уязвимость
При неверном использовании API способен нарушать один из главных принципов объектно-ориентированного программирования — инкапсуляцию данных. Это может привести к появлению потенциальных уязвимостей в веб-приложениях. В период с 2013 по 2016 год в библиотеке Reflection существовала брешь, которая позволяла хакерам обходить «песочницу» (изолированную зону для выполнения программ).
Нарушение переносимости программы
Поскольку Reflection API позволяет коду выполнять операции, которые обычно находятся под запретом, например получать доступ к закрытым полям и методам, использование рефлексии может сделать код неработоспособным и нарушить переносимость с одной операционной системы на другую. Кроме того, рефлексивный код нарушает абстракции, поэтому может изменить поведение программы при обновлении платформы.
Пример работы Reflection API в Java
Чтобы использовать Java Reflection API, не нужно подключать сторонние библиотеки. Все расположено в пакете java.lang.reflect.
Продемонстрируем некоторые методы рефлексии в программировании на конкретных примерах.
// Демонстрация работы Рефлексии в Java
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
// Создание объектов для класса Sample
// Создаем приватное поле private
// Создаем публичный конструктор
// Создаем публичный метод без параметров
public void method1() System.out.println(«Информация в строке — » + s); >
// Создаем публичный метод с целым числом в качестве параметра
public void method2(int x) System.out.println(«Целое число — » + x);
>
// Создаем приватный метод
private void method3() System.out.println(«Вызов приватного метода»);
>
>
class Exercise
public static void main(String args[]) throws Exception
// Создаем объект для последующей проверки свойств
Sample obj = new Sample();
// Создаем новый объект класса из другого объекта
Class cls = obj.getClass();
System.out.println(«Имя класса — » +
cls.getName());
// Получаем имя конструктора класса с помощью объекта
Constructor constructor = cls.getConstructor();
System.out.println(«Имя конструктора — » +
constructor.getName());
System.out.println(«Это публичные методы классов: «);
// Получаем методы классов с помощью объектов
Method[] methods = cls.getMethods();
// Выводим имена методов
for (Method method:methods)
System.out.println(method.getName());
// Создаем объект нужного метода с помощью имени метода и параметра класса
Method methodcall1 = cls.getDeclaredMethod(«method2», int.class);
// Вызов метода во время исполнения
// Создаем объект нужного поля с помощью имени поля
Field field = cls.getDeclaredField(«s»);
// Открываем доступ к полю независимо от используемого в нем спецификатора доступа
// Устанавливаем новое значение поля
// Создаем объект метода с помощью имени метода
Method methodcall2 = cls.getDeclaredMethod(«method1»);
// Вызов метода во время исполнения
// Создаем третий объект метода с помощью имени метода
Method methodcall3 = cls.getDeclaredMethod(«method3»);
// Изменяем настройки доступа
// Вызов метода во время исполнения
Рефлексия — мощный инструмент, для правильного использования которого требуются высокая квалификация и взвешенный подход.
Java-разработчик
Java уже 20 лет в мировом топе языков программирования. На нем создают сложные финансовые сервисы, стриминги и маркетплейсы. Освойте технологии, которые нужны для backend-разработки, за 14 месяцев.
Рефлексия
Рефлексия — это набор возможностей языка и библиотек, который позволяет интроспектировать программу (обращаться к её структуре) во время её исполнения. В Kotlin функции и свойства первичны, и поэтому их интроспекция (например, получение имени или типа во время исполнения) сильно переплетена с использованием функциональной или реактивной парадигмы.
Kotlin/JS provides limited support for reflection features. [Learn more about reflection in Kotlin/JS](js-reflection.md). —>
JVM зависимость
На платформе JVM дистрибутив компилятора Kotlin включает в себя компонент среды выполнения, необходимый для использования рефлексии в качестве отдельного артефакта, kotlin-reflect.jar . Это сделано для уменьшения требуемого размера runtime-библиотеки для приложений, которые не используют рефлексию.
Чтобы использовать рефлексию в проекте Gradle или Maven, добавьте зависимость kotlin-reflect :
// Kotlin dependencies
// Groovy dependencies
- В Maven:
org.jetbrains.kotlin kotlin-reflect
Если вы не используете Gradle или Maven, убедитесь, что kotlin-reflect.jar добавлен в classpath вашего проекта. В других поддерживаемых случаях (проекты IntelliJ IDEA, использующие компилятор командной строки или Ant), он добавляется по умолчанию. В компиляторе командной строки и Ant вы можете использовать опцию компилятора -no-reflect , чтобы исключить kotlin-reflect.jar из classpath.
Ссылки на классы
Самая базовая возможность рефлексии — это получение ссылки на Kotlin класс. Чтобы получить ссылку на статический Kotlin класс, используйте синтаксис литерала класса.
val c = MyClass::class
Ссылка на класс имеет тип KClass.
On JVM: a Kotlin class reference is not the same as a Java class reference. To obtain a Java class reference, >use the `.java` property on a `KClass` instance. —>
Ссылка на Kotlin класс это не то же самое, что и ссылка на Java класс. Для получения ссылки на Java класс, используйте свойство .java экземпляра KClass .
Ссылки на привязанные классы
Вы можете получить ссылку на класс определённого объекта с помощью уже известного вам синтаксиса, вызвав ::class у нужного объекта.
val widget: Widget = . assert(widget is GoodWidget) < "Bad widget: $" >
Вы получите ссылку на точный класс объекта, например GoodWidget или BadWidget , несмотря на тип объекта, участвующего в выражении ( Widget ).
Вызываемые ссылки
Ссылки на функции, свойства и конструкторы также могут вызываться или использоваться в качестве экземпляров функциональных типов.
`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-callable/index.html), where `R` is the return value type. It is the property type for properties, and the constructed type for constructors. —>
Ссылки на функции
Когда у нас есть именованная функция, как объявленная ниже, вы можете вызвать её напрямую ( isOdd(5) ).
fun isOdd(x: Int) = x % 2 != 0
В качестве альтернативы вы можете передать её как значение, например в другую функцию. Чтобы сделать это, используйте оператор :: .
val numbers = listOf(1, 2, 3) println(numbers.filter(::isOdd)) // выведет [1, 3]
Здесь, ::isOdd — значение функционального типа (Int) -> Boolean .
`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-function/index.html) subtypes, depending on the parameter count. For instance, `KFunction3 `. —>
Оператор :: может быть использован с перегруженными функциями, когда тип используемой функции известен из контекста. Например:
fun isOdd(x: Int) = x % 2 != 0 fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove" val numbers = listOf(1, 2, 3) println(numbers.filter(::isOdd)) // ссылается на isOdd(x: Int)
Также вместо этого вы можете указать нужный контекст путём сохранения ссылки на функцию в переменной, тип которой задан явно.
val predicate: (String) -> Boolean = ::isOdd // ссылается на isOdd(x: String)
Если вы хотите использовать член класса или функцию-расширение, вам нужно обозначить это явным образом: String::toCharArray .
Даже если вы инициализируете переменную ссылкой на функцию-расширение, у предполагаемого типа функции не будет получателя, но у него будет дополнительный параметр, принимающий объект-получатель. Чтобы вместо этого иметь тип функции с приемником, укажите тип явно.
val isEmptyStringList: List.() -> Boolean = List::isEmpty
Пример: композиция функций
Рассмотрим следующую функцию:
fun compose(f: (B) -> C, g: (A) -> B): (A) -> C < return < x ->f(g(x)) > >
Она возвращает композицию двух функций, переданных ей: compose(f, g) = f(g(*)) . Вы можете применять её к ссылкам на функции.
fun length(s: String) = s.length val oddLength = compose(::isOdd, ::length) val strings = listOf("a", "ab", "abc") println(strings.filter(oddLength)) // выведет "[a, abc]"
Ссылки на свойства
Для доступа к свойствам как первичным объектам в Kotlin используйте оператор :: .
val x = 1 fun main()
` type property object. You can read its value using `get()` or retrieve the property name using the `name` property. For more information, see the [docs on the `KProperty` class](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-property/index.html). —>
Выражение ::x возвращает объект свойства типа KProperty , который позволяет вам читать его значение с помощью get() или получать имя свойства при помощи обращения к свойству name . Для получения более подробной информации обратитесь к документации класса KProperty .
`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-mutable-property/index.html) type which has a `set()` method: —>
Для изменяемых свойств, например var y = 1 , ::y возвращает значение типа KMutableProperty , который имеет метод set() .
var y = 1 fun main() < ::y.set(2) println(y) // выведет 2 >
Ссылка на свойство может быть использована там, где ожидается функция с одним обобщённым параметром.
val strs = listOf("a", "bc", "def") println(strs.map(String::length)) // выведет [1, 2, 3]
Для доступа к свойству, которое является членом класса, укажите класс.
class A(val p: Int) fun main() < val prop = A::p println(prop.get(A(1))) // выведет "1" >
val String.lastChar: Char get() = this[length - 1] fun main() < println(String::lastChar.get("abc")) // выведет "c" >
Взаимодействие с рефлексией Java
На платформе JVM стандартная библиотека Kotlin содержит расширения, которые сопоставляют расширяемые ими объекты рефлексии Kotlin с объектами рефлексии Java (см. пакет kotlin.reflect.jvm ). К примеру, для нахождения поля или Java метода, который служит геттером для Kotlin-свойства, вы можете написать что-то вроде этого:
import kotlin.reflect.jvm.* class A(val p: Int) fun main() < println(A::p.javaGetter) // выведет "public final int A.getP()" println(A::p.javaField) // выведет "private final int A.p" >
Для получения класса Kotlin, соответствующего классу Java, используйте свойство-расширение .kotlin .
fun getKClass(o: Any): KClass = o.javaClass.kotlin
Ссылки на конструктор
К конструкторам можно обратиться так же, как и к методам или свойствам. Они могут быть использованы везде, где ожидается объект функционального типа, который принимает те же параметры, что и конструктор, и возвращает объект соответствующего типа. Обращение к конструкторам происходит с помощью оператора :: и имени класса. Рассмотрим функцию, которая принимает функциональный параметр без параметров и возвращает Foo :
class Foo fun function(factory: () -> Foo)
Используя ::Foo , конструктор класса Foo без аргументов, вы можете просто вызывать функцию таким образом:
function(::Foo)
`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-function/index.html) subtypes depending on the parameter count. —>
Вызываемые ссылки на конструкторы вводятся как один из подтипов KFunction в зависимости от количества параметров.
Привязанные функции и свойства
Вы можете сослаться на метод экземпляра конкретного объекта.
val numberRegex = "\\d+".toRegex() println(numberRegex.matches("29")) // выведет "true" val isNumber = numberRegex::matches println(isNumber("29")) // выведет "true"
Вместо вызова метода matches напрямую, в примере используется ссылка на него. Такие ссылки привязаны к объектам, к которым относятся. Их можно вызывать напрямую (как в примере выше) или использовать всякий раз, когда ожидается выражение функционального типа.
val numberRegex = "\\d+".toRegex() val strings = listOf("abc", "124", "a70") println(strings.filter(numberRegex::matches)) // выведет "[124]"
Сравним типы привязанных и соответствующих непривязанных ссылок. Объект-приёмник «прикреплён» к привязанной ссылке, поэтому тип приёмника больше не является параметром.
val isNumber: (CharSequence) -> Boolean = numberRegex::matches val matches: (Regex, CharSequence) -> Boolean = Regex::matches
Ссылка на свойство может быть также привязанной.
val prop = "abc"::length println(prop.get()) // выведет "3"
Вам не нужно указывать this в качестве приёмника: this::foo и ::foo эквивалентны.
Ссылки на привязанные конструкторы
Привязанная ссылка на конструктор внутреннего класса может быть получена путем предоставления экземпляра внешнего класса.
class Outer < inner class Inner >val o = Outer() val boundInnerCtor = o::Inner
© 2015—2024 Open Source Community