Практически Groovy: Хитрые операторы

Перегрузка операторов в платформе Java? Groovy делает это реальностью!

Язык программирования Java™ не позволял перегружать операторы, но тут явился Groovy. Узнайте то, чего вы не знали все это время. Эндрю Гловер познакомит вас с повседневным использованием трех категорий перегружаемых операторов в этом последнем выпуске цикла статей Практически Groovy.

Эндрю Гловер, президент компании, Stelligent Incorporated

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



04.09.2008

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

Об этой серии

Чтобы применять какой-либо инструмент в практике разработки, необходимо знать, как им пользоваться и когда его лучше отложить в сторону. Языки сценариев могут быть чрезвычайно полезным помощником, но только если применять их правильно и в подходящих ситуациях. В связи с этим в серии статей Практически Groovy мы рассматриваем практические применения Groovy и показываем, когда и как его можно успешно использовать.

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

Три группы хитрых операторов

Я разделяю перегружаемые операторы Groovy на три логические группы: операторы сравнения, математические операторы и операторы для массивов. Эти группы охватывают лишь некоторую часть операторов стандартного языка Java. Например, логические операторы & и ^ в настоящее время не поддерживаются Groovy.

В Таблице 1 приведены три вида перегружаемых операторов Groovy:

Таблица 1. Перегружаемые операторы Groovy
1.Операторы сравнения соответствуют операторам equals и compareTo в Java
2.Математические операторы Java, например +, - и *
3.Оператор доступа к массиву []

Операторы сравнения

Операторы сравнения соответствуют Java-операторам equals и compareTo и обычно используются для упрощения определения операций сортировки в коллекциях. В таблице 2 показаны два оператора сравнения.

Таблица 2. Операторы сравнения
ОператорМетод
a == ba.equals(b)
a <=> ba.compareTo(b)

В языке Java оператор == является сокращением записи о равенстве объектов, но не указателей на них. Другими словами, оператор Groovy ==, помещенный между двумя объектами, означает, что они равны, потому что одинаковы их свойства, хотя эти объекты имеют разные указатели.

В соответствии с документацией Java метод compareTo() возвращает отрицательное целое число, нуль, либо положительное целое число в случае, если объект меньше, равен или больше заданного. Поскольку этот метод может возвращать один их этих трех типов значений, Groovy расширил синтаксис операторов <=> четырьмя дополнительными значениями, которые приведены в таблице 3.

Таблица 3. Четыре дополнительных значения
Оператор Значение
a > bесли a.compareTo(b) возвращает значение, большее нуля, то это условие вернет true
a >= bесли a.compareTo(b) возвращает значение, равное либо большее нуля, то это условие вернет true
a < bесли a.compareTo(b) возвращает значение, меньшее нуля, то это условие вернет true
a <= bесли a.compareTo(b) возвращает значение, меньшее либо равное нулю, то это условие вернет true

В поисках лучшей покупки

Помните «диско-класс» LavaLamp, который я впервые определил в "Feeling Groovy"? Этот класс также служил примером перехода на новый синтаксис JSR в статье "Groovy's growth spurt". Я собираюсь вновь использовать этот класс для того, что бы наглядно показать несколько изящных приемов использования операторов сравнения.

В листинге 1 я расширил класс LavaLamp обычным Java-методом equals() и его соратником hashCode. К тому же я сделал так, чтобы LavaLamp поддерживал Java-интерфейс Comparable, и создал метод compareTo():

Листинг 1. Возвращение класса LavaLamp
package com.vanward.groovy

import org.apache.commons.lang.builder.CompareToBuilder
import org.apache.commons.lang.builder.EqualsBuilder
import org.apache.commons.lang.builder.HashCodeBuilder
import org.apache.commons.lang.builder.ToStringBuilder

class LavaLamp implements Comparable{
 @Property model
 @Property baseColor
 @Property liquidColor
 @Property lavaColor

  def String toString() {
    return new ToStringBuilder(this).
        append(this.model).
	    append(this.baseColor).
	    append(this.liquidColor).
	    append(this.lavaColor).
	    toString()
  } 

  def boolean equals(obj) {
      if (!(obj instanceof LavaLamp)) {
         return false
      }
      LavaLamp rhs = (LavaLamp) obj
         return new EqualsBuilder().
             append(this.model, rhs.model).
             append(this.baseColor, rhs.baseColor).
             append(this.liquidColor, rhs.liquidColor).
             append(this.lavaColor, rhs.lavaColor).  	    
             isEquals()
   }

   def int hashCode() {
      return new HashCodeBuilder(17, 37).  
           append(this.model).
           append(this.baseColor).
           append(this.liquidColor).          
           append(this.lavaColor).
           toHashCode()
   }

   def int compareTo(obj) {
      LavaLamp lmp = (LavaLamp)obj
         return new CompareToBuilder().
             append(lmp.model, this.model).          
             append(lmp.lavaColor, this.lavaColor).
             append(lmp.baseColor, this.baseColor).
             append(lmp.liquidColor, this.liquidColor).
             toComparison()
   }	
}

Замечание: Поскольку я являюсь большим поклонником повторного использования объектов, я широко использую Jakarta's Commons Lang project (даже для реализации метода toString()). Если Вы все еще разрабатываете свои собственные методы equals(), возможно, вам будет интересно выделить минуту для знакомства с этой библиотекой. (Смотрите Ресурсы.)

В листинге 2 можно видеть, как работает перегрузка операторов, которую я определил в листинге 1. Я создал пять экземпляров класса LavaLamp (гулять так гулять!) и использовал операторы сравнения Groovy для того, чтобы отличить их друг от друга:

Листинг 2. Операторы сравнения в действии
lamp1 = new LavaLamp(model:"1341", baseColor:"Black", 
         liquidColor:"Clear", lavaColor:"Red")
lamp2 = new LavaLamp(model:"1341", baseColor:"Blue", 
         liquidColor:"Clear", lavaColor:"Red")
lamp3 = new LavaLamp(model:"1341", baseColor:"Black", 
         liquidColor:"Clear", lavaColor:"Blue")
lamp4 = new LavaLamp(model:"1342", baseColor:"Blue", 
         liquidColor:"Clear", lavaColor:"DarkGreen")
lamp5 = new LavaLamp(model:"1342", baseColor:"Blue", 
         liquidColor:"Clear", lavaColor:"DarkGreen")

println lamp1 <=> lamp2  //  1
println lamp1 <=> lamp3  // -1
println lamp1 < lamp3    //  true
println lamp4 <=> lamp5  //  0

assert lamp4 == lamp5
assert lamp3 != lamp4

Обратите внимание на то, что lamp4 и lamp5 одинаковы, а остальные классы немного отличаются друг от друга. Поскольку baseColor экземпляра lamp1 равен Black, а baseColorlamp2 равен Blue, оператор <=> возвратил 1 (символ «a» в «black» предшествует символу «u» в «blue»). Аналогично, lavaColor экземпляра lamp3 равен Blue в отличие от значения этого свойства, равного Red для экземпляра lamp1. Поскольку сравнение lamp1 <=> lamp3 возвращает -1, выражение lamp1 < lamp3 возвращает true. lamp4 и lamp5 равны, поэтому, оператор <=> возвращает 0.

Еще один момент. Вы заметили, как оператор == работает для одинаковых объектов. lamp4 и lamp5 равны. Конечно, я бы мог использовать выражение assert lamp4.equals(lamp5), чтобы определить этот факт, но существует более быстрый путь – использование оператора ==!

Проверка равенства указателей

Теперь давайте рассмотрим ситуацию, в которой мне понадобилось проверить равенство указателей на объект. Очевидно, я не могу использовать для этого оператор ==, но специально для этих целей Groovy предоставляет метод is(), как показано в листинге 3.

Листинг 3. Вот как работает метод is()
lamp6 = new LavaLamp(model:"1344", baseColor:"Black", 
             liquidColor:"Clear", lavaColor:"Purple")

lamp7 = lamp6
assert lamp7.is(lamp6)

Как показано в листинге 3, и lamp6, и lamp7 имеют одинаковые указатели, поэтому метод is возвращает true.

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


Математические операторы

Groovy поддерживает перегрузку следующих математических операторов:

Таблица 3. Математические операторы Groovy
ОператорМетод
a + b a.plus(b)
a - b a.minus(b)
a * b a.multiply(b)
a / b a.divide(b)
a++ or ++a a.next()
a-- или --a a.previous()
a << b a.leftShift(b)

Вы, должно быть, заметили, что оператор + в Groovy уже перегружался в нескольких других случаях, особенно, в тех, когда дело касалось коллекций. Вы когда-нибудь интересовались тем, как это происходит или, хотя бы, думали об осуществлении подобного со своими собственными классами? Теперь пора узнать все это.

Складываем операторы

Помните класс Song из "Stir some Groovy into your Java apps"? Давайте посмотрим, что получится, если создать объект jukebox (музыкальный автомат) для воспроизведения Song. В листинге 4 я отброшу малозначимые для нас подробности воспроизведения формата MP3 и уделю внимание операциям добавления и удаления мелодий Song в/из JukeBox:

Листинг 4. Музыкальный автомат
package com.vanward.groovy

import com.vanward.groovy.Song


class JukeBox {

  def songs  

  JukeBox(){   
    songs = []      
  }

  def plus(song){
    this.songs << song
  }

  def minus(song){    
    def val = this.songs.lastIndexOf(song)
    this.songs.remove(val)
  }
  def printPlayList(){     songs.each{ song -> println "${song.getTitle()}" }
  }
}

Реализовав методы plus() и minus(), я перегрузил операторы + и . Теперь их можно использовать в моих программах. В листинге 5 показано, как можно использовать эти операторы для добавления мелодий в список воспроизведения или удаления их оттуда:

Листинг 5. Проигрываем мелодии
sng1 = new Song("SpanishEyes.mp3")
sng2 = new Song("RaceWithDevilSpanishHighway.mp3")
sng3 = new Song("Nena.mp3")


jbox = new JukeBox()
jbox + sng1
jbox + sng2
jbox + sng3

jbox.printPlayList() //выводит Spanish Eyes, Race with the Devil.., Nena

jbox - sng2

jbox.printPlayList() //выводит Spanish Eyes, Nena

Перегрузка перегруженных перегрузчиков

Продолжим тему о перегрузке. В таблице 3 вы могли заметить один из перегружаемых математических операторов, <<, который также перегружается для коллекций Groovy. В этом случае оператор << перегружается таким образом, что он ведет себя как обычный Java-метод add(), а именно помещает значения в конец массива (что похоже также на ситуацию Ruby). В листинге 6 можно увидеть, что получится, если использовать такое поведение для того, чтобы пользователи JukeBox могли добавлять мелодии Song с помощью оператора << и оператора +:

Листинг 6. Сдвигаем список воспроизведения влево
def leftShift(song){
   this.plus(song)
}

В листинге 6 я определил метод leftShift, который вызывает метод plus для того, чтобы добавить мелодии Song в мой список воспроизведения. В листинге 7 показано как работает оператор <<:

Листинг 7. Поехали
jbox << sng2 //повторно добавляет Race with the Devil...

Как можно видеть, перегруженные математические операторы Groovy могут не только много на себя берут, но и быстро бегают!


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

Groovy позволяет перегружать стандартный оператор доступа к массиву [], как показано в таблице 4:

Таблица 4. Операторы для массивов
ОператорМетод
a[b] a.getAt(b)
a[b] = c a.putAt(b, c)

Оператор доступа к массиву хорошо подходит и для коллекций, поэтому я переписал класс JukeBox так, чтобы перегрузка работала для обоих случаев в листинге 8:

Листинг 8. Перегрузка класса
def getAt(position){    
  return songs[position]
}

def putAt(position, song){
  songs[position] = song
}

Теперь, когда реализованы и getAt, и putAt, я могу использовать оператор [], как показано в листинге 9:

Листинг 9. Можно ли быстрее?
println jbox[0] //выводит Spanish Eyes

jbox[0] = sng2  //размещает Race w/the Devil на первое место
println jbox[0] //выводит Race w/the Devil

JDK методы Groovy

Теперь, когда вы поняли принцип перегрузки операторов и то, как это сделать на Groovy, можно заметить, что множество обычных объектов Java уже расширены разработчиками Groovy.

Например, класс Character поддерживает compareTo(), как показано в листинге 10:

Листинг 10. Сравнение символов
def a = Character.valueOf('a' as char)
def b = Character.valueOf('b' as char)
def c = Character.valueOf('c' as char)
def g = Character.valueOf('g' as char)

println a < b  //выводит true
println g < c  //выводит false

Точно так же экземпляры класса StringBuffer можно соединять друг с другом с помощью оператора <<, как показано в листинге 11:

Листинг 11. Строки в буфере
def strbuf = new StringBuffer()
strbuf.append("Error message: ")
strbuf << "NullPointerException on line ..."

println strbuf.toString() //выводит Error message: NullPointerException on line ...

И, наконец, в листинге 12 показано, что экземплярами класса Date можно управлять с помощью операторов + и -.

Листинг 12. Какой сегодня день?
def today = new Date()

println today   //выводит Tue Oct 11 21:15:21 EDT 2005
println "tomorrow: " + (today + 1)  //Wed Oct 12 21:15:21 EDT 2005
println "yesterday: " + (today - 1) //Mon Oct 10 21:15:21 EDT 2005

Вывод

Как видно, настраиваемый полиморфизм операторов или, как я его называл, перегрузка операторов, может оказаться весьма полезным при правильном использовании и документировании. Однако не злоупотребляйте этой функциональностью. Если вы перегружаете оператор для каких-либо нестандартных целей, позаботьтесь о его подробном описании. Расширить классы Groovy для поддержки перегрузки очень просто. Аккуратность и подробное документирование при этом являются разумной платой за полученное удобство и быстроту написания кода.

Ресурсы

Научиться

Получить продукты и технологии

  • Web-страница проекта Groovy : загрузите Groovy.(EN)
  • Commons Lang: хранилище дополнительных методов, предназначенных для работы с основными классами платформы Java, включая методы управления классами String.(EN)

Обсудить

Комментарии

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=335608
ArticleTitle=Практически Groovy: Хитрые операторы
publish-date=09042008