Ballerina 1.1.0 была выпущена в январе 2020 года, а среда выполнения (jBallerina) работает поверх JVM. Таким образом, ballerina обеспечивает бесшовное взаимодействие с Java, что позволяет разработчикам использовать возможности Java через ballerina. В этой статье подробно объясняется, как можно просто создавать объекты Java и вызывать методы Java через ballerina без особых хлопот.

Я создам простой стек в балерине с использованием реализации стека java. Здесь я предположил, что знаком с базовым синтаксисом и семантикой балерины. Прежде чем мы перейдем к реализации, давайте получим общее представление о том, что поддерживает язык Ballerina, когда речь идет о совместимости java.

Что поддерживается?

Мост, соединяющий миры балерины и java, - это «внешняя» функция. Внешняя функция - это функция, у которой нет тела, но есть ключевое слово external, которое говорит о том, что реализация исходит из внешнего мира. Чтобы указать, из какого мира происходит реализация, мы можем использовать аннотацию. Например, мы могли бы импортировать ballerinax/java package и использовать аннотации @java:XXX, чтобы сказать, что реализация исходит от Java.

Используя аннотации @java:XXX, мы можем сопоставить функцию балерины статическим методам Java, методам экземпляра Java и статическим полям Java. Прямое сопоставление Java-объектов с объектами-балеринами пока не поддерживается. Но это будет поддерживаться в будущих выпусках.

Реализация стека

Давайте начнем с нашей реализации стека. Сначала давайте создадим хороший API для нашего стека, определив объект-балерину. Этот объект Stack будет действовать как оболочка для экземпляра стека java, чтобы мы могли предоставить более чистый API.

Создайте конструктор

Теперь мы хотим создать экземпляр java.util.Stack и сохранить его как поле в объекте. Однако, поскольку функции, присоединенные к объекту, не могут быть записаны как внешняя функция, нам нужно создать обычную функцию, которая является внешней функцией, и вызвать ее в конструкторе объекта Stack (__init()).

function createJavaStack() returns handle = @java:Constructor {
    class: "java/util/Stack"
} external;

Здесь, чтобы отметить, что эта функция отображается в конструктор, мы используем аннотацию @java:Constructor{}. Поскольку возвращаемое значение этой функции является объектом Java, мы определяем тип возвращаемого значения как handle. Значение дескриптора представляет собой любое внешнее значение (в данном случае значение java) в мире балерин. При этом наш объект стека выглядит следующим образом:

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

Здесь, поскольку единственный конструктор не принимает параметров, мы просто определяем типы параметров как пустой массив в аннотации.

Реализуйте методы

Давайте реализуем методы стека push и pop. Как и ранее, нам нужно создать внешние функции верхнего уровня для обоих методов.

Если мы внимательно посмотрим на push() метод java/util/Stack, он имеет единственный параметр с универсальным типом и возвращаемый универсальный тип. Это эквивалентно функции балерины с параметром типа any и возвращением типа any. Кроме того, поскольку это метод экземпляра java, который мы собираемся вызвать, первым параметром нашей функции ballerina должно быть значение, для которого функция должна быть вызвана. Другими словами, первым параметром метода ballerina должен быть экземпляр стека java.

Здесь я указал paramType как java/lang/Object, потому что именно так java представляет общие типы внутри. Точно так же наш метод pop будет выглядеть так:

Теперь нам нужно вызвать эти методы взаимодействия из объекта Ballerina Stack, который мы определили ранее.

Давайте протестируем наш стек:

Это напечатает следующий вывод:

Mango
Orange
Apple

Хорошо, но что будет, если мы снова поднимем стопку? Стек должен быть пустым, и на уровне java будет java/util/EmptyStackException. Но как это распространится на балерину? Попробуем и увидим:

Mango
Orange
Apple
error: java.util.EmptyStackException
  at testMain:javaStackPop(testMain.bal:32)
    $value$Stack:pop(testMain.bal:17)
    testMain:main(testMain.bal:47)

Как видите, исключение java стало panic на уровне балерины. Однако в Ballerina panic - это ошибка, которую не следует обрабатывать. Но эта ошибка пустого стека является распространенной и известной ошибкой для метода stack-pop и должна обрабатываться пользователем. «Балерина» справляется с этой ситуацией, когда в сигнатуре функции возвращается ошибка. Таким образом, любой, кто использует метод pop (), сможет справиться с ошибкой. Таким образом, нам нужно trap эту ошибку и вернуть ее следующим образом. Обратите внимание на ключевое слово trap и тип возвращаемого значения union с ошибкой.

function pop() returns any|error {
   return trap javaStackPop(self.javaStack);
}

Конечно, это не лучший способ справиться со сценарием с пустым стеком. Мы должны проверить, пуста ли карта, и, если да, вернуть ошибку, вместо того, чтобы позволить ей паниковать и улавливать ошибку. Здесь я просто хотел показать, как исключения времени выполнения java распространяются на балерину и как обрабатывать их в коде балерины.