alt.lang.jre: Присмотритесь к JRuby

Добавьте блеска в инструментарий Java

JRuby объединяет мощь объектно-ориентированного Smalltalk, выразительность Perl и гибкость библиотек классов Java в одной эффективной и быстрой интегрированной среде разработки для платформы Java. В этом третьем выпуске серии статей alt.lang.jre Майкл Скуиллейс и Барри Фейгенбаум представляют JRuby — замечательное дополнение к вашему инструментарию разработки программ на языке Java.

Майкл Скуиллейс, программист, IBM China

Майкл СкуиллейсМайкл Скуиллейс работает в центре IBM Worldwide Accessibility Center, где занимается разработкой средств, повышающих доступность программных продуктов IBM. Имеет степень доктора философии и в настоящее время учится в Техасском университете по специальности «информатика». Профессиональный пианист. Незрячий. Связаться с доктором Скуиллейсом можно по адресу masquill@us.ibm.com.



Барри Фейгенбаум, старший консультант по ИТ-архитектуре, IBM China

Барри ФейгенбаумБарри Фейгенбаум работает в центре IBM Worldwide Accessibility Center, где входит в состав группы, которая стремится сделать продукты IBM доступными для людей с ограниченными возможностями. Он опубликовал несколько книг и статей, владеет несколькими патентами, а также выступал с докладами на отраслевых конференциях, таких как JavaOne. Имеет пост адъюнкт-ассистент-профессора вычислительных наук в Техасском университете, г. Остин.



03.07.2012

JRuby представляет собой альтернативный язык для платформы Java. Он опирается на Ruby — язык программирования, созданный Юкихиро Мацумото (Yukihiro "Matz" Matsumoto). Как утверждается на домашней странице RubyCentral (см. Ресурсы), Ruby — это язык программирования, "сочетающий чистую мощь классического объектно-ориентированного языка Smalltalk с выразительностью и удобством языка сценариев Perl". Это относительно зрелый язык, известный своими лаконичными, интуитивно понятными синтаксисом и семантикой и прозрачной, дружественной к разработчикам моделью программирования.

JRuby представляет собой реализацию интерпретатора Ruby, выполненную исключительно на Java. Подобно большинству языков, обсуждаемых в этой серии статей, JRuby эффективен и в то же время прост для изучения. Он сочетает в себе богатство функций для работы с текстом языка Perl, итераторы и замыкания, знакомые разработчикам Groovy, и возможности быстрой разработки, свойственные Jython и другим языкам, обсуждаемым в этой серии статей. Кроме того, JRuby является интерпретируемым языком, поэтому его можно запускать из командной строки или использовать для оперативной проверки простых выражений и блоков кода.

И хотя JRuby не пытается стать заменой всему и для всех, он определенно обладает некоторыми ключевыми преимуществами, позаимствованными у предшественников, и воплощает их в форме мощного и в то же время синтаксически простого языка. В отличие от многих других языков, обсуждаемых в этой серии статей, JRuby объединяет большинство своих возможностей в одном пакете. Например, JRuby объединяет итераторы с функциями обработки текста, что позволяет сравнительно просто писать эффективные и в то же время интуитивно понятные анализаторы. Кроме того, он позволяет использовать функции в качестве типов данных первого класса, которые можно комбинировать с блоками или замыканиями для расширения классов, позволяющего выполнять большую часть рутинной работы, обычно выполняемую операторами цикла и более традиционными классами итераторов (такими как классы системы Java Collections).

Об этой серии

Хотя большинство читателей раздела технологий Java знакомы с языком Java и знают, как он работает на кросс-платформенной виртуальной машине, немногим известно, что в Java Runtime Environment (JRE) можно исполнять не только Java, но и другие языки. Эта серия статей описывает многие другие альтернативные языки для JRE. Большинство из рассматриваемых здесь языков имеют открытый исходный код и могут использоваться бесплатно, но некоторые являются коммерческими продуктами и приобретаются за деньги. Все языки, представленные в серии статей alt.lang.jre, поддерживаются в JRE и, по мнению авторов, улучшают динамичность и гибкость платформы Java.

Разумеется, JRuby также тесно интегрирован в платформу Java и наследует удобство использования и гибкость библиотеки классов Java. Благодаря поддержке модулей и классов он хорошо подходит для разработки масштабируемых и надежных приложений. JRuby можно использовать для написания скриптов для классов Java, а также можно встраивать (через интерпретатор) непосредственно в приложения Java. Как Ruby, так и JRuby являются языками с открытым исходным кодом, которые доступны бесплатно для разработки и развертывания.

Эта третья часть серии статей alt.lang.jre посвящена JRuby. Мы начнем с самого начала – с некоторых базовых рекомендаций по установке, а затем поговорим о том, что делает JRuby ценным дополнением к вашему инструментарию разработки на языке Java. Остальная часть статьи будет посвящена изучению примеров программ, сложность которых будет возрастать по мере вашего знакомства с языком. Чтобы загрузить примеры исходного кода для этой статьи, щелкните на значке Код, расположенном в верхней или нижней части этой страницы.

Где взять JRuby

На момент написания этой статьи JRuby v0.7.0 можно загрузить с SourceForge. После загрузки архива распакуйте его в любую выбранную вами директорию (заметьте, что директория верхнего уровня jruby-0.7.0 является частью архива). В среде Windows интерпретатор запускается пакетным файлом, а в среде Unix — файлом скрипта оболочки Bash. Оба файла расположены в поддиректории bin директории JRuby. Чтобы запустить JRuby и проверить интерпретатор, просто введите в командной строке команду jruby.

После этого вы увидите примерно следующее:

using JRUBY_BASE ...
using JRUBY_HOME ...
using JAVA_HOME ...
using CLASSPATH ...
using ARGS ...

Нажмите Ctrl-C, чтобы выйти из интерпретатора, не исполняя кода. Нажмите Ctrl-Z, чтобы выйти из интерпретатора и запустить введенный вами код.


В чем секрет JRuby

Подобно многим другим языкам, описанным в этой серии статей, JRuby поддерживает функции как типы данных первого класса. Это значит, что функции можно присваивать переменным, помещать в коллекции, передавать в качестве аргументов другим функциям и получать в качестве возвращаемых значений. Кроме того, поскольку JRuby является интерпретируемым языком с динамической типизацией, он превосходно подходит для динамической обработки. Некоторые удобные возможности JRuby:

  • Простой синтаксис: JRuby не содержит символа, означающего конец выражения (подобного точке с запятой в языке Java), и позволяет опускать скобки в вызовах функций, не имеющих аргументов.
  • Нетипизированные переменные исключают потребность в объявлениях.
  • Атрибуты членов класса могут указываться как “читаемый” и/или “записываемый”, и к ним можно обращаться по имени (то есть без применения методов получения и установки свойств getter и setter).
  • Блоки и итераторы упрощают выполнение повторяющихся операций, особенно с членами коллекции.
  • Расширенный набор функций для работы со строками и регулярными выражениями существенно упрощает обработку текста.

Все эти и другие возможности, подкрепляемые мощью платформы Java, делают JRuby превосходным альтернативным языком для Java-разработчиков.


Hello World!

Оставшуюся часть этой статьи мы посвятим изучению и обсуждению примеров кода. В качестве первого примера давайте посмотрим, как JRuby справится с приведенной ниже канонической программой Hello World:

print "Hello, World!\n"

JRuby позволяет обработать эту простую программу одним из двух способов. Можно запустить интерпретатор JRuby и ввести эту программу в командную оболочку, завершив ввод нажатием Ctrl-Z, обозначающим конец файла, или можно поместить эту программу в файл JRuby (например, HelloWorld.rb), и затем исполнить его следующей командой:

jruby HelloWorld.rb

Как можно ожидать, в обоих случаях результат выглядит одинаково:

Hello, World!

Теперь, когда мы всех поприветствовали — “Hello, World!” — можно перейти к более сложной программе, показанной в листинге 1:

Листинг 1. Отображение моих публичных методов
x = 3
for m in x.methods
  print "#{m} " if not methods.index(m)
end

Об этом коде

Первое, на что нужно обратить внимание в этом коде — это метод methods, который возвращает все публичные методы экземпляра класса использующего его объекта (то есть "x") вместе с методами любого суперкласса класса этого объекта. Затем цикл for перебирает все методы, доступные классу JRuby Integer (экземпляром которого является число 3). Если имя метода присутствует в списке methods, возвращаемом для класса Object, оно не печатается. Как мы вскоре увидим, вызов любого метода без предшествующего экземпляра и оператора точки (.) вызывает этот метод для класса Object. Следовательно, метод methods в модификаторе if вызывается для класса Object.

Следует также отметить, что nil (пустое значение) в JRuby (как и null в языке Java) в условных выражениях эквивалентно значению false (ложь), поэтому здесь фактически выполняется проверка выражения methods.index(m) == nil. Мы использовали синтаксис #{...} в вызове функции print, чтобы напечатать результаты проверки выражения в скобках.

В результате отобразится список методов, примерно такой, как показано в листинге 2:

Листинг 2. Вывод перечня моих публичных методов
 to_str / ~ id2name ^ to_f + [] ** - >> 
 | to_i << & size * % integer? downto 
 succ next times upto step chr member? include? 
 sort each_with_index collect find_all inject sort_by 
 max entries all? select reject grep min any? partition 
 group_by detect map find abs -@ round zero? floor modulo 
 <=> ceil coerce +@ truncate remainder nonzero? divmod 
 > >= between? < <=

Имена некоторых методов, возвращенные этой программой, могут вас удивить. Например, один из методов, оператор [], возвращает бит в позиции, которая определена в индексе двоичного представления числа. Таким образом, мы получим x[0] == 1, x[1] == 1, x[2] == 0 и так далее. Кроме того, эта программа возвращает типичные арифметические операторы (такие как + и )и условные операторы (вроде < и >). (Очень скоро мы познакомимся и с другими методами этого списка, такими как downto и step.)


Чистая объективизация

Как и Groovy, JRuby считает все объектами, будь то число, строка, класс, модуль, функция или даже литерал. Даже вызывая интерпретатор, вы попадаете в объект с именем main. Это легко проверить, подав в командной строке следующую команду:

print inspect, "\n"

Интерпретатор ответит вам строкой main, которая является результатом вызова метода print для результата метода inspect.

Засилье методов

Методы print и inspect являются членами класса Object, свойства которого наследуют все объекты JRuby. (Заметьте, что это не эквивалентно классу java.lang.Object.) Поскольку все в JRuby наследует свойства класса Object, каждый экземпляр каждого объекта имеет доступ к большому набору методов. Таким образом, JRuby не имеет оператора print, но может показаться, что он его имеет, поскольку метод print доступен в любом месте программы. (Применение print в качестве метода, а не в качестве оператора, безусловно, знакомо программистам, пишущим на языке Java. В конце концов, в языке Java методы print и println являются членами объекта out класса System.)

В JRuby (как и в Smalltalk) многие методы предоставляют функциональность, которая в других языках обычно свойственна операторам (или ключевым словам). Примером является метод include_package, который используется для импорта в программу пакетов Java. Метод include_package является членом класса Module, поэтому вызов этого метода за пределами определения модуля приведет к ошибке. Поэтому не удивляйтесь, если встретите в приложении JRuby примерно такой код:

module Swing
  include_package 'javax.swing'
end

Литералы тоже являются объектами

Поскольку все (включая числа и строковые литералы) в JRuby является объектами, мы можем написать следующее выражение:

3.type
5.0.to_s   # to_s is like Java's toString()
"abcde".length 
[1, 2, 3].size

Такие выражения могут показаться необычными разработчикам на Java, но только не разработчикам на Smalltalk. Например, в языке Java класс Math определяет публичные статические члены, выполняющие различные математические операции. В JRuby в этом нет необходимости; вместо того чтобы вызывать Math.round(3.9)б как в языке Java, мы используем форму 3.9.round, которая должна быть знакома тем, кто знает Smalltalk.


Правила JRuby

В этом разделе мы рассмотрим правила именования переменных в JRuby, которые в чем-то напоминают правила языка Basic. Важно отметить разницу между соглашением об именовании (таким как соглашение в языке Java о том, что имя класса должно начинаться с заглавной буквы) и правилами синтаксиса. Правила именования переменных в JRuby подпадают под последнюю категорию в том смысле, что имя переменной непосредственно относится к ее роли или способу применения в программе JRuby. Показанное в таблице 1 неправильное применение правил именования приведет к возникновению ошибки.

Таблица 1. Правила JRuby: именование переменных
toptoptoptoptoptoptop
ПрефиксОбозначенная рольПример(ы)
$ глобальные переменные $total, $count, $_
: символ (т.е. относится к самому имени переменной) :foo, :size
@ член экземпляра класса @size, @foo
@@ переменная члена класса (как статический член в Java) @@refCount, @@NORTH
заглавная буква константа (должна использоваться во всех именах классов и модулей) MyClass, MyModule
строчная буква или подчеркивание имя локальной переменной или метода в зависимости от контекста k, _id, name, getName

В приведенных ниже примерах вы сможете увидеть, как работают эти правила.


Блоки и итераторы

Возможно, вы заметили некоторые незнакомые (или малознакомые) имена методов в результатах работы программы, показанных в листинге 2, такие как times, downto и step. В JRuby эти методы известны как итераторы. Итераторами называются методы, которые вызывают данный блок кода для последовательности значений. Последовательность значений определяется реализацией итератора и типом объекта, для которого он определен. (Обратите внимание, что здесь итератор является своего рода методом класса, а не самим классом, как в C++ или Java.)

В качестве простого примера давайте рассмотрим итератор each, который реализован для любой коллекции JRuby. Итератор each просто вызывает данный блок кода для каждого члена коллекции. Таким образом, мы можем переписать пример, показанный в листинге 1, так, чтобы использовать код, специфичный для класса Integer, как показано ниже:

3.methods.each { |m| print "#{m} " if not methods.index(m) }

Очевидно, этот код более лаконичен, чем предыдущий вариант, в котором использовалась конструкция for. Более того, он явно менее громоздок, чем эквивалентный код Java. Простота кода позволяет сосредоточиться на выполняемой работе, вместо того чтобы вникать в детали списка и вызывать операции для каждого члена этого списка.

Цикла for больше нет...

Продолжая рассматривать методы класса Integer, мы видим, что итератор upto передает в блок кода последовательные целые значения начиная с исходного целого значения и заканчивая значением, переданным в качестве аргумента функции. В результате , чтобы получить таблицу с квадратами чисел от 1 до 5 включительно, мы можем использовать следующую команду:

1.upto(5) { |n| print "#{n}\t{n**2}\n" }

Итератор step во многом похож на upto (или downto), за исключением того, что он имеет параметр step, показывающий, на сколько нужно увеличить (или уменьшить) текущее значение для получения следующего значения. Познакомившись с такими методами, можно легко понять, почему отпадает потребность в конструкции for. (На самом деле оператор for в языке JRuby реализован через итератор each класса Integer.)

Определяемые пользователем итераторы

Доступные в разных классах итераторы зависят от назначения этих классов и от определенных для них операций. Кроме того, JRuby позволяет нам определять наши собственные итераторы для наших собственных классов (или переопределять итераторы в библиотеке классов JRuby) с помощью выражения yield. Выражение yield передает управление от функции (или метода) блоку, передаваемому этой функции. Например, реализацию итератора upto для объекта Integer можно написать следующим образом:

Class Integer
:
  def upto (endNum)
    for val in self.to_i..endNum
      yield val
    end
  end
:
end

Используемый для ссылки на текущий экземпляр специальный идентификатор self работает точно так же, как ссылка this в языке Java. И так же, как и ссылка this, ссылка self подразумевается неявно, если отсутствует.

Выражение self.to_i..endNum приводит к созданию в JRuby экземпляра класса Range. Объект Range имеет первое значение, последнее значение и метод succ, который определяет способ формирования последующих значений вслед за первым. Конструкция for использует метод succ типа Integer для формирования диапазона значений от значения, представленного ссылкой self, до значения endNum. Здесь мы используем включающий объект Range, обозначенный двумя последовательными точками (..), вместо исключающего объекта Range, представленного тремя последовательными точками (...).

Долой циклы!

В определении метода upto присутствуют два параметра. Первый из них —endNum, что ясно видно из определения метода, в то время как второй параметр — блок — является неявным формальным параметром метода. Доступ к нему осуществляется автоматически (если он присутствует) с помощью оператора yield. Для разработчика, пишущего на языке JRuby, это значит, что можно вообще не использовать циклы. Пользуетесь ли вы обширной библиотекой JRuby или переопределяете соответствующие методы в собственных классах (например, succ, each), — вы можете использовать итераторы для выполнения большей части тяжелой работы.


Класс JRuby в действии

Теперь мы приступим к изучению примеров классов в JRuby. Мы напишем два класса, первый из которых подчеркнет достоинства присущих JRuby эффективных функций обработки текста, а второй продемонстрирует его потенциальные возможности при объединении с библиотекой классов Java. Исходный код обоих классов можно получить, загрузив примеры, для чего щелкните на значке Код, расположенном в верхней или нижней части этой статьи.

Первым определяемым нами классом будет упрощенное представление элемента XML в следующем виде:

<tag>'</tag>

Несмотря на свою простоту, этот класс может многое, как видно из листинга 3.

Листинг 3. Извлечение класса из XmlElement.rb
# представляет хорошо сформированный элемент XML
# (т.е. имеющий открывающий и закрывающий теги) с вложенным классом 
XmlElement

    # член класса
    # соответствует строкам формы:
    # <tag>'</tag>
    @@xmlElement = /<([a-z]+)>((.|\s)*)<\/\1>/

    # атрибуты члена: сделаем их читаемыми/записываемыми
    attr_accessor :name, :children, :textContent
    
    def initialize (text, isRoot=true)
        # переменная:
        # список дочерних элементов
        @children = []
        # переменные:
        # имя элемента, полученное из имени тега
        # текстовое содержимое элемента (если есть)
        @name, @textContent = "DOC_ROOT", "" if isRoot
        
        # добавляем новые элементы, пока выполняется условие
        text.scan(@@xmlElement) { | name, content |
            newChild = XmlElement.new(content, false)
            newChild.name = name
            newChild.textContent = content if not content.include?('<')
            @children.push(newChild)
        }
    end # initialize
    
    def printTree (elem=self, indent="")
        print indent, elem.name
        print "[#{elem.textContent}]" if elem.textContent
        print "\n"
        indent += "  "
        elem.children.each { | elem | printTree(elem, indent) }
    end # printTree
end # XmlElement

Об этом коде

Этот код может многое, поэтому мы будем рассматривать наиболее важные элементы построчно. Определение класса начинается с ключевого слова class, за которым следует имя класса. Имена классов в JRuby являются константами, о чем свидетельствует заглавная буква в начале имени. Следующая строка содержит регулярное выражение, которое служит шаблоном для базового элемента XML. Обратите внимание, что это выражение является членом класса XmlElement, о чем свидетельствует префикс "@@". Этот член класса имеет тип Regexp. Чтобы создать его, можно прибегнуть к оператору new класса Regexp, но вместо этого мы воспользуемся более традиционным синтаксисом, знакомым разработчикам, пишущим на языке Perl.

Далее мы вводим три символа, которые будут обозначать экземпляры этого класса: name, children и textContent. Перед каждым из них мы ставим двоеточие(:), создавая объект типа Symbol, который необходим методу attr_accessor класса Module. Применение этого метода заставляет члены вести себя так, будто они соответствуют методам getter и setter, хотя для чтения или присвоения нам достаточно использовать только имя атрибута. В теле метода класса мы снабжаем атрибут префиксом "@", который показывает, что он является экземпляром.

Метод initialize всегда вызывается первым в любом классе JRuby при создании его экземпляра. В данном случае ему передается текст элемента XML для синтаксического анализа. После инициализации списка @children (списка, который будет содержать элементы child), мы начинаем рекурсивно определять каждый из этих элементов. (Обратите внимание, что мы создаем стандартный элемент root, символизирующий вершину документа XML. Стандартное значение isRoot устанавливается равным true для создания более удобного интерфейса.)

Основную работу по синтаксическому анализу выполняет для нас итератор scan в классе String. Простейшая форма функции scan берет регулярное выражение и возвращает массив строк внутри объекта string, вызывая scan (то есть текст), удовлетворяющий условию выражения. Другая версия scan (которую мы и применяем) вызывает блок кода при совпадениях. Самое интересное в scan то, что он обрабатывает совпадения в подвыражениях. Предположим, что в нашем примере @@xmlElement содержит два подвыражения: ([a-z]+) и ((.|\s)*). Они представляют имя и остаток содержимого элемента соответственно. Каждый элемент массива, возвращенный методом scan, представляет собой двухэлементный массив, содержащий эти два результата. Таким образом, наш блок принимает два аргумента: name и content.

Сам по себе блок не содержит особых хитростей. Мы создаем новый XmlElement, передавая ему содержимое, найденное в совпадении. Мы устанавливаем имя и текстовое содержимое вновь сформированного XmlElement и используем метод include? класса String, чтобы узнать, может ли этот блок выступать в роли текстового содержимого. (Предполагается, что этот текст достаточно прост.) И, наконец, мы вставляем вновь созданный элемент child в список потомков этого элемента.

Обработка документа XML

Файл XmlElement.rb, доступный в исходном коде для загрузки, демонстрирует поддержку в JRuby ввода длинных (многострочных) строк. Здесь мы рассмотрим только один подход. Если ввести следующий текст непосредственно в интерпретатор, начнется анализ документа XML:

load "XmlElement.rb"   # access the XmlElement class
text = IO.readlines("test.xml").join
xmlDoc = XmlElement.new(text)
xmlDoc.printTree

Метод printTree распечатывает обнаруженные в документе элементы вместе с их текстовым содержимым (если таковое имеется). Дочерние элементы выводятся с отступом для повышения удобочитаемости.


Построение графического интерфейса пользователя

Наш второй пример класса будет значительно сложнее, поскольку мы собираемся построить калькулятор с графическим интерфейсом. В этом примере будут представлены два новых класса JRuby, Hash и Proc, и будет показано, как использовать классы Java в приложении JRuby. Кроме того, этот пример продемонстрирует, как быстро и просто написать сложное приложение в JRuby.

Чтобы запустить калькулятор из исходного кода, введите:

jruby Calculator.rb

Доступ к классам Java

Как и в предыдущем примере, мы будем использовать метод include_package класса Module для импорта пакетов Java в программу JRuby. Этот метод предлагается библиотекой классов Java в JRuby вместе с другими классами и функциями. Таким образом, типичное приложение JRuby, использующее классы Java, начинается примерно так:

require  'java'
module <name>
include_package '<name_of_Java_package>'
end

Заметьте, что мы используем метод require для подключения библиотеки Java и для определения модуля, представляющего пространство имен, в котором будет находиться пакет Java.

Интерфейс калькулятора

Интерфейс калькулятора будет состоять из трех компонентов:

  • Текстовое поле для ввода выражения, которое будет обрабатываться
  • Группа кнопок 4 x 4, содержащая цифры от 0 до 9, основные арифметические операции, десятичную точку и знак равенства (=)
  • Раздел функций, содержащий группу кнопок, выполняющих стандартные операции, таких как sin, cos, квадратный корень и т.п.

Похоже, работы предстоит немало, не так ли? К счастью для нас, одна из особенностей этого примера как раз и заключается в том, чтобы показать, насколько просто JRuby может справиться с этой работой!

Начнем с интегрированной системы

Для построения графического интерфейса мы будем использовать классы Swing платформы Java. Сам калькулятор можно реализовать как подкласс JFrame. Следовательно, мы можем начать примерно с такого кода, который показан в листинге 4:

Листинг 4. Создание калькулятора
require  'java'
module Swing
include_package 'java.awt'
include_package 'javax.swing'
end
module AwtEvent
include_package 'java.awt.event'
end

$calculator = Swing::JFrame.new
class << $calculator

    def init
    end
    
end

$calculator.init
$calculator.setSize(400, 400)
$calculator.setVisible(true)

Метод require, который загружает в интерпретатор библиотеку Java языка JRuby, необходим всем приложениям, использующим классы Java. Далее мы определяем два модуля, каждый из которых представляет пространство имен для набора пакетов Java. Имя модуля, как и имя класса, должно быть константой, поэтому оно начинается с заглавной буквы. Для импорта пакета Java в модуль мы используем метод include_package класса Module. Теперь для доступа к классам этого пакета мы будем обращаться к имени модуля, за которым следуют оператор разрешения пространства имен (::) и нужное имя класса Java.

Следующий фрагмент кода может показаться необычным тем, кто привык программировать на Java. Сначала мы определяем глобальную переменную для главного окна калькулятора, предваряя идентификатор знаком доллара ($). Такое определение класса называется в JRuby анонимным классом. В сущности мы хотим создать класс, основанный на объекте (или наследующий его свойства), который мы только что создали. (В связи с ограничениями механизма наследования в JRuby мы не можем определить класс, непосредственно наследующий свойства класса Java). Определяя наш новый подкласс, мы создаем метод init (поскольку метод initialize уже вызывался в процессе создания объекта $calculator). Нам нужно будет вызвать этот метод после определения класса. (До некоторой степени это похоже на вызов super() в конструкторе классов Java и последующую инициализацию этого класса).

Добавляем поле выражения

Это очень просто. Нам просто нужно добавить надпись и поле, в котором пользователь сможет вводить вычисляемое выражение. В листинге 5 показан новый метод init:

Листинг 5. Инициализация поля выражения
    def init
        cp = getContentPane
        
        # field for entering expression
        exprPanel = Swing::JPanel.new(Swing::BorderLayout.new)
        exprBox = Swing::Box.new(
            Swing::BoxLayout::X_AXIS)
        exprBox.add(Swing::Box::createHorizontalGlue)
        exprBox.add(
            Swing::JLabel.new("Enter an expression: "))
        @exprField = Swing::JTextField.new(20)
        exprBox.add(Swing::Box::createHorizontalStrut(4))
        exprBox.add(@exprField)
        exprBox.add(Swing::Box::createHorizontalGlue)
        exprPanel.add(Swing::BorderLayout::NORTH, exprBox)
        cp.add(Swing::BorderLayout::NORTH, exprPanel)
    end

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

    def setExpression (expr)
        @exprField.setText(expr)
    end
    def getExpression
        @exprField.getText
    end

Добавляем цифровую клавиатуру

Здесь мы в полной мере начнем понимать, что же делает JRuby столь замечательным. Следующее, что нам нужно создать, - это группа кнопок 4 x 4, которая будет выглядеть как традиционная цифровая клавиатура. Как вы помните, стандартная клавиатура калькулятора содержит цифры от 0 до 9, четыре основных арифметических действия (сложение, вычитание, деление и умножение) и кнопку равенства. Для простоты все кнопки (кроме кнопки равенства) будут при нажатии выводить соответствующий символ в самую правую позицию поля выражения. Кнопка равенства будет выполнять вычисление.

В листинге 6 представлен соответствующий код из метода init.

Листинг 6. Инициализация цифровой клавиатуры
# формируем и добавляем цифровую клавиатуру
buttons = [
    '7', '8', '9', '/',
    '4', '5', '6', '*',
    '1', '2', '3', '-',
    '0', '.', '=', '+'
]
numPadPanel = Swing::JPanel.new(
    Swing::GridLayout.new(4, 4))
buttons.each { |symbol|
    numPadButton = Swing::JButton.new(symbol)
    numPadButton.setActionCommand(symbol)
    numPadButton.addActionListener(<to_be_written>)
    numPadPanel.add(numPadButton)
}
cp.add(numPadPanel, Swing::BorderLayout::CENTER)

Мы перебираем символы, для которых хотим создать кнопки. Для каждого символа, который должен присутствовать на цифровой клавиатуре калькулятора, мы создаем сначала новый JButton с текстом, содержащим этот символ, и добавляем процедуру, прослушивающую эту кнопку (хотя ее еще предстоит написать). И, наконец, мы добавляем вновь созданную кнопку в numPadPanel. Завершив эту итерацию, мы прикрепляем numPadPanel к главному окну. Представьте, как бы вы сделали это на языке Java, и вы поймете, почему столько разработчиков предпочитают работать с JRuby!

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

Листинг 7. Определение прослушивающей процедуры
$numListener = AwtEvent::ActionListener.new
def $numListener.actionPerformed (event)
    comm = event.getActionCommand
    $calculator.setExpression(
        $calculator.getExpression + comm) if
            comm != "="
    $calculator.setExpression(
        eval($calculator.getExpression).to_s) if
            comm == "="
    end
end

Здесь мы применяем ту же стратегию, которую мы использовали, когда делали объект $calculator экземпляром JFrame; только здесь используется другой синтаксис. Разработчики, пишущие на Java, обнаружат, что этот синтаксис полностью соответствует тому, который применяется при определении анонимного внутреннего класса на языке Java.

Добавляем панель функций

Последнее, что нужно сделать, — это добавить панель с кнопками функций. Для этого мы используем JRuby Hash (который ведет себя как Java java.util.HashMap), который активируется именем функции, написанным на кнопке. Значения объекта Hash известны как объекты Proc (подобные замыканиям) и создаются с помощью метода Kernel класса proc. Последняя часть метода init выглядит так:

Листинг 8. Инициализация панели функций
        # формируем и добавляем панель функций
        @fnMap = {
            'sin'  => proc { |n| Lang::Math.sin(n) },
            'cos'  => proc { |n| Lang::Math.cos(n) },
            'ln'   => proc { |n| Lang::Math.log(n) },
            'sqrt' => proc { |n| Lang::Math.sqrt(n) }
        }
        fnPanel = Swing::JPanel.new(
            Swing::GridLayout.new(1, 4))
        @fnMap.each_key { | fnName |
            fnButton = Swing::JButton.new(fnName)
            fnButton.setActionCommand(fnName)
            # мы напишем $fnListener momentarily – который полностью 
            # соответствует только что описанному $numPadListener
            fnButton.addActionListener($fnListener)
            fnPanel.add(fnButton)
        }
        cp.add(fnPanel, Swing::BorderLayout::SOUTH)

Заметьте, что каждый объект Proc инкапсулирует блок кода, который просто вызывает некоторую математическую функцию с аргументом, переданным этому блоку. Передача аргумента происходит при вызове метода call для объекта Proc.

Мы сделали fnMap экземпляром, чтобы опять же можно было предоставить доступ к нему из прослушивающей процедуры. Это потребуется для функциональных кнопок. Для обеспечения соответствующего доступа мы можем добавить к классу следующий метод:

    def getFnForName (name)
        @fnMap[name]
    end

Процедура прослушивания определяется глобально и создается с помощью того же метода, который мы использовали для создания процедуры прослушивания кнопок для цифровой клавиатуры:

$fnListener = AwtEvent::ActionListener.new
def $fnListener.actionPerformed (event)
    val = $calculator.getExpression.to_f
    fn = $calculator.getFnForName(event.getActionCommand)
    $calculator.setExpression(fn.call(val).to_s)
end

После извлечения значения из поля выражения (предполагается, что результатом вычисления является одно значение), мы можем взять нужный нам объект Proc и вызвать метод call, передав ему значение, извлеченное из поля выражения. Затем мы преобразуем его обратно в строку с помощью метода to_s и очищаем поле выражения.

Вот и все!

Графический интерфейс, созданный этим приложением, показан на рисунке 1. (Неказист, зато работает!)

Рисунок 1. Пример графического интерфейса калькулятора
Sample of Calculator GUI showing the digit and function grid of buttons

Показанный здесь (и приведенный в Ресурсах) пример калькулятора полностью функционален; кроме того, его можно легко расширять. Для расширения функций калькулятора нужно просто добавить соответствующую запись для каждой новой функции в $fnMap.


Заключение

JRuby — эффективный и гибкий язык, предоставляющий богатую библиотеку и обширные возможности. Поддержка итераторов, удобные функции обработки текста и, конечно, доступ к огромному набору библиотек классов Java и API предоставляют превосходную среду, в которой можно быстро и просто создавать эффективно работающие приложения любых размеров.

В этом выпуске серии статей alt.lang.jre мы представили JRuby, используя простые и знакомые примеры для демонстрации возможностей этого стремительно развивающегося языка. Исходный код для этой статьи вы найдете в разделе Ресурсы. Как уже упоминалось, некоторые из примеров классов можно легко расширить в качестве дополнительного упражнения.

Чтобы узнать больше о JRuby и других альтернативных языках для платформы Java, загляните в Ресурсы. И не забудьте посетить нас в следующем месяце!


Загрузка

ОписаниеИмяРазмер
Образец кодаj-alj09074-source.zip2 КБ

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=824028
ArticleTitle=alt.lang.jre: Присмотритесь к JRuby
publish-date=07032012