Статьи

Продуктивное программирование в Groovy

Единственное постоянное в этом мире — это перемены! В 1990-х годах Java потрясла компьютерный мир, предоставив управляемые среды выполнения (виртуальные машины) для массового программирования. Сегодня появляется много новых языков, которые заменяют популярные сегодня. Для JavaScript некоторые попытки заменить их — это TypeScript (Microsoft) и Dart (Google). Для C / C ++ попытки заменить их включают язык D и Go (Google). Интересно, что для Java основным конкурентом являются языки, построенные на основе самой среды Java! 

Знаете ли вы, что существует более 200 языков для платформы Java? Такие языки, как Scala, Clojure, Groovy, JRuby и Jython — это несколько, но важных языков, ориентированных на платформу Java. Из них нас интересует Groovy, поскольку в последние несколько лет он привлекает к себе всеобщее внимание и становится все более популярным.

Почему стоит выбрать Groovy? 

Я начал работать в Groovy совершенно случайно. Я пытался написать код для своего любимого проекта и понял, что Java замедляет меня. Я исследовал использование альтернативных языков, но не смог. Зачем? Существует множество причин, которые также будут вам интересны, когда вы начнете использовать Groovy, поэтому я подробно их расскажу здесь. Мое сравнение в основном с Python и Ruby, так как они были популярными альтернативными языками, которые я рассматривал. 

Во-первых, у меня был значительный опыт программирования на Java и использования библиотек и фреймворков Java. Когда я перехожу на такие языки, как Python или Ruby, мне приходится покидать родную страну и изучать язык и экосистему. Основным преимуществом Java является обширный набор библиотек и фреймворков (большинство из них бесплатные и с открытым исходным кодом), и я, скорее всего, потеряю их при таком шаге. Я подумал об использовании Jython и JRuby — но я предпочел Groovy, потому что я мог легко переключаться между Java и Groovy (так как Groovy является более или менее расширенным Java). 

Во-вторых, у меня было мало свободного времени на изучение нового языка с нуля. Groovy имеет Java-подобный синтаксис и, следовательно, кривая обучения не крутая. После того, как я установил Groovy, я сразу мог начать программировать на Groovy, так как я программирую в основном на Java и постепенно перехожу к более «Groovyish» коду. 

В-третьих, я должен был рассмотреть и аспекты развертывания. Хотя Python и Ruby также установлены на миллионах машин, среда выполнения Java повсеместна. Это означало, что я мог просто отправить Groovy jar-файл вместе с моим приложением и заставить программу работать на любом компьютере. 

Короче говоря, переезд с Явы в Groovy походил на переезд моего дома из Индии в Австралию — это знакомая местность, но мне все еще нужно знать много вещей, чтобы выжить там. Для меня переход с Java на Ruby или Python — это как переход из Индии в Антарктику — это совершенно другая местность — да, я игра для приключений, но не сейчас, когда у меня есть немедленная работа, чтобы закончить!  

Признаюсь, четвертая причина не является технической: мне понравилась отличная книга Венката Субраманиама о Groovy [2] и его презентации о Groovy. Будучи единомышленником, я решил попробовать, выучить и начать использовать Groovy.

Что такое Groovy? 

Groovy — это динамический язык, который динамически компилирует код для платформы Java. Он популярен как язык сценариев, а также широко используется для модульного тестирования кода Java. Groovy имеет открытый исходный код (лицензия Apache v2) и поддерживается VMware. Джеймс Страхан задумал идею языка Groovy и начал разработку в 2003 году. Текущая (по состоянию на ноябрь 2013 года) стабильная версия — 2.1. Вы можете использовать Groovy в большинстве распространенных операционных систем — Linux, Windows и Mac. 

Groovy также достаточно популярен. Он попал в заголовки новостей, когда Groovy вошел в топ-20 в широко цитируемом индексе популярности языков TIOBE в октябре 2013 года (он был на 53-м месте в 2012 году!). В 2012 году было загружено 1,7 миллиона Groovy. Мы должны взять эти цифры (особенно в отношении популярности) с долей соли; Тем не менее, можно с уверенностью сказать, что Groovy набирает постоянную популярность в последние несколько лет.  

Можете ли вы показать, как Groovy «Лучшая Java»?

Пример Hello World

Давайте начнем с классического примера «Привет, мир»: 

class Hello {
	public static void main(String []args) {
		System.out.println("hello world");
	}
}

Код, помеченный как зачеркнутый, должен показать, что эти части Java-кода не нужны в Groovy — это простое и приятное println сделает:   

println("hello world")

Первый (простой) пример 

Как насчет написания кода для реализации команды «type» (или команды «cat»), но это ограничивается печатью имен файлов, передаваемых в качестве аргумента? Вот версия Java:

import java.io.*;

class Type {
	public static void main(String []files) {
		// process each file passed as argument 
		for(String file : files) {
			// try opening the file with FileReader 
			try (FileReader inputFile = new FileReader(file)) { 
				int ch = 0; 
				while( (ch = inputFile.read()) != -1) {
					// ch is of type int - convert it back to char
					System.out.print( (char)ch ); 
				}
			} catch (FileNotFoundException fnfe) {
				System.err.printf("Cannot open the given file %s ", file);
			}
			catch(IOException ioe) {
				System.err.printf("Error when processing file %s; skipping it", file);
			} 
			// try-with-resources will automatically release FileReader object  
		}
	}
}

Что мне не нравится в этом коде, так это то, что он слишком многословен. Кроме того, почему я должен беспокоиться о низкоуровневых деталях, таких как чтение символов как целых, преобразование целого числа обратно в символ, проверка с -1 для проверки конца файла и т. Д.? 

Как многие из вас знают, Java 7 представила NIO.2, которая значительно упростила обработку файлов и операций ввода-вывода. В Java 7 код выглядит проще по сравнению с более ранними версиями Java, но все еще выглядит многословно: 

import java.nio.file.*;
import java.io.IOException;

class Type {
	public static void main(String []files) {	
		// process each file passed as argument 
		for(String file : files) {
			try {
				Files.copy(Paths.get(file), System.out);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

Сравните это с Groovy-версией, которая очень приятна: 

args.each { println new File(it).getText() }

Обход структуры данных в Groovy прост — вы можете использовать «замыкания» вместо простых циклов и использовать метод each (), как в этом случае. Groovy помогает вам работать на более высоких уровнях абстракции (как и большинство других языков сценариев). Поскольку обработка файлов является распространенной задачей в языках сценариев, Groovy предоставляет высокоуровневую абстракцию для этой функциональности, и, следовательно, код намного проще. 

Второй (более подробный) пример

Теперь давайте возьмем более сложную задачу: взять пары ключ-значение, поместить их в карту, отсортировать их по значению и по ключу и распечатать результаты. Давайте сделаем это шаг за шагом. Сначала давайте возьмем пары ключ-значение и поместим их в карту: 

Map<String, String> inglishWords = new HashMap<>();
inglishWords.put("Shampoo", "'Chapmo' (Hindi)"); 
inglishWords.put("Sugar", "'Sarkkarai' (Tamil)");  
inglishWords.put("Copra", "'Koppara' (Malayalam)"); 
inglishWords.put("Jute", "'Jhuto' (Bengali)"); 
inglishWords.put("Cot", "'Khatva' (Sanskrit)"); 

for(Map.Entry<String, String> word : inglishWords.entrySet()) {                        System.out.printf("%s => %s \n", word.getKey(), word.getValue());  
}

В этом коде мы взяли такие слова, как «Шампунь и сахар» на английском языке, и сопоставили их со словами индийского происхождения. Вот Groovy эквивалент:

def inglishWords =    [ "Shampoo" : "'Chapmo' (Hindi)", 
                        "Sugar" : "'Sarkkarai' (Tamil)",   
                        "Copra" : "'Koppara' (Malayalam)",  
                        "Jute" : "'Jhuto' (Bengali)",  
                        "Cot" : "'Khatva' (Sanskrit)" ]  

inglishWords.each { 
    key, value ->    
            println "$key => $value"   
}

В этом случае Groovy предоставляет «синтаксический сахар» в виде синтаксиса в виде массива [] для структуры данных Map. При закрытии метода each () мы называли значения индекса на карте («ключ» и «значение»). Как видите, вам нужно изучить некоторый новый синтаксис, например, как использовать $ вместо% s в printf или println при печати значений; но наш опыт показывает, что вы можете постепенно изучать новый синтаксис, когда вы пробуете больше программ и становитесь более сложными. Давайте теперь рассортируем карту и распечатаем содержимое. Вот версия Java: 

System.out.println("The (key-based) sorted map is: ");

Map<String, String> keySortedMap = new TreeMap<>(inglishWords); 
for(Map.Entry<String, String> word : keySortedMap.entrySet()) {                     System.out.printf("%s => %s \n", word.getKey(), word.getValue());  
}

Вот Groovy эквивалент: 

println "The (key-based) sorted map is: " 
def keySortedMap = inglishWords.sort() 
keySortedMap.each { 
        key, value ->    
            println "$key => $value"   
}

Но теперь у вас может возникнуть такой вопрос: «Является ли Groovy простым синтаксическим сахаром над Java?» Ответ НЕТ — Groovy предоставляет мощные функции, которые помогут вам стать более продуктивным. Это станет очевидным, когда мы попробуем нашу следующую задачу — отсортировать карту по значению. 

Несмотря на то, что я опытный программист на Java, я обнаружил, что попытка отсортировать карту по значению не проста, поэтому я быстро проверил stackoverflow.com на быстрое решение! Было 38 ответов на этот вопрос с большим количеством дебатов на каждый ответ; это показывает (а) есть довольно много программистов, которые пытаются это сделать, и (б) ответ нетривиален. Вот так: 

class MapUtil {
    // this code segment from http://stackoverflow.com/questions/109383/how-to-sort-a-mapkey-value-on-the-values-in-java 
    public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue( Map<K, V> map ) {
        List<Map.Entry<K, V>> list =
            new LinkedList<Map.Entry<K, V>>( map.entrySet() );
        Collections.sort( list, new Comparator<Map.Entry<K, V>>() {
            public int compare( Map.Entry<K, V> o1, Map.Entry<K, V> o2 ) {
                return (o1.getValue()).compareTo( o2.getValue() );
            }
        } );

        Map<K, V> result = new LinkedHashMap<K, V>();
        for (Map.Entry<K, V> entry : list) {
            result.put( entry.getKey(), entry.getValue() );
        }
        return result;
    }
}

Теперь код, который делает использование этой служебной функции: 

System.out.println("The (value-based) sorted map is: ");
Map<String, String> valueSortedMap = MapUtil.sortByValue(inglishWords); 
for(Map.Entry<String, String> word : valueSortedMap.entrySet()) {                  System.out.printf("%s => %s \n", word.getKey(), word.getValue());  
}  

Теперь сравните это с Groovy-версией: 

println "The (value-based) sorted map is: " 

def valueSortedMap = inglishWords.sort { it.value }  
valueSortedMap.each { 
    key, value ->    
            println "$key => $value"   
}

В этом случае мы просто передали значение it.value (которое соответствует значению текущего итератора) методу сортировки. Благодаря возможности замыканий код стал простым, простым и читаемым. 

Обратите внимание, что в Java 8 представлены замыкания (также известные как «лямбда-функции»), что является одной из важных функций Groovy, которые мы здесь обсуждали. Однако маловероятно, что добавление замыканий в Java 8 будет сдерживать рост популярности таких языков, как Groovy. Существует множество других функций, которые делают Groovy привлекательным, например, его возможности метапрограммирования, которые делают язык привлекательным для создания DSL. 

Последний пример (генерация HTML и XML)

Одна из общих задач, которые мы выполняем как разработчики Java, — генерировать выходные данные в некотором структурированном формате. Давайте создадим простые файлы HTML и XML с помощью Java и Groovy. Вот версия Java без использования библиотек или шаблонов (например, векторных шаблонов) для генерации HTML-файла: 

try  (PrintWriter pw = new PrintWriter(new FileWriter("./index.java.html"))) {
	pw.println("<html> <head> <title>Words from Indian origin in English</title> </head> <body>"); 
	pw.println("<h1>Words from Indian origin in English</h1>"); 
	pw.println("<table border='1'> <tr> <th>Inglish word</th> <th>Origin</th></tr>"); 

   	for(Map.Entry<String, String> word : inglishWords.entrySet()) {              		pw.printf("<tr> <td> %s </td> <td> %s </td> </tr>", word.getKey(), word.getValue());  
    	}

	pw.println("</table> <p style='font-style:italic;font-size:small;float:right'>Results obtained at " + new Date() + "</p> </body> </html>"); 
}

Groovy поддерживает сборщики (на основе шаблона проектирования Gang-of-четыре Builder) для создания файлов HTML и XML. При этом код выглядит так:

def writer = new StringWriter()
def doc = new MarkupBuilder(writer)
doc.html() {
    head {
        title("Words from Indian origin in English") 
    }
    body {
    h1("Words from Indian origin in English")
       table(border:1) {
           tr {
                th("Inglish word")
                th("Origin")
            }
            inglishWords.each { word, root ->
            tr {
                td("$word")
                td("$root")
            }
        }
  }
  p(style:'font-style:italic;font-size:small;float:right', "Results obtained at ${new Date().dateTimeString}")
  }
}

В отличие от более ранних примеров, Groovy-код не короче версии Java, но, безусловно, более  читабелен, чем версия Java. Обе эти программы генерируют одинаковый HTML-  вывод:

HTML вывод сгенерированный из Java / Groovy

Для того же вывода давайте создадим вывод XML в Java:

try  (PrintWriter pw = new PrintWriter(new FileWriter("./words.xml"))) { 
	pw.println("<inglishwords>"); 
	for(Map.Entry<String, String> word : inglishWords.entrySet()) {               	pw.printf("\t<language word='%s'> \n \t\t <origin> 
'%s'</origin> \n \t </language> \n", word.getKey(), word.getValue());  
    	}
	pw.println("</inglishwords>"); 
}

Он генерирует следующий вывод XML: 

<inglishwords>

<language word='Sugar'> 
  <origin> ''Sarkkarai' (Tamil)'</origin> 
</language> 

<language word='Copra'> 
  <origin> ''Koppara' (Malayalam)'</origin> 
</language> 

<language word='Shampoo'> 
  <origin> ''Chapmo' (Hindi)'</origin> 
</language> 

<language word='Jute'> 
  <origin> ''Jhuto' (Bengali)'</origin> 
</language> 

<language word='Cot'> 
  <origin> ''Khatva' (Sanskrit)'</origin> 
</language> 

</inglishwords>

Это версия Groovy, которая выполняет ту же работу с легкостью:

xmlbuilder = new groovy.xml.MarkupBuilder()
xmlbuilder.inglishwords {
        words.each { 
              key, value ->
                  language(word:key) { origin(value) }
        }
 }

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

Куда пойти отсюда? 

Мы лишь слегка коснулись того, что вы можете сделать с помощью Groovy. Мы не охватили много тем. Приглашаем вас изучить улучшения в программировании (по сравнению с Java), такие как автоматическая проверка нулей, вспомогательные методы, встроенные выражения в строках и перегрузка операторов. Конечно, не забудьте изучить его метапрограммирование и возможности обработки регулярных выражений. Его создатели могут упростить такие задачи, как обработка JSON. Если вы серьезный веб-разработчик, вам будет интересен веб-фреймворк Grails и Groovy Server Pages (GSP). Если вы администратор, вам может пригодиться среда автоматизации сборки Gradle.   

Если вы опытный Java-программист и хотите развить свои навыки, попробуйте Groovy. Вы обнаружите, что обычные задачи, такие как использование структур данных, создание вывода XML / HTML, создание графических интерфейсов, значительно упрощаются при использовании Groovy. Платформа Grails (которая похожа на Ruby on Rails) построена на основе мощных и популярных сред, таких как Spring и Hibernate; если вы серьезный веб-программист, попробуйте Grails. Как и другие динамические языки, Groovy является отличным кандидатом для создания DSL (доменных языков) — если вам это нужно, вы можете рассмотреть возможность использования Groovy для создания вашего следующего DSL.   

Итог: если вы используете Groovy вместо Java для обычной работы, вы наверняка увидите, что ваша производительность почти удвоится. Но предупреждение — как и при изучении любого нового языка или инструмента, всегда есть начальное падение производительности, когда вы изучаете веревки; но как только вы станете опытным, ваша производительность увеличится. Я бы порекомендовал вам перейти к программированию в Groovy не только из-за производительности, но и из-за проблем со здоровьем — мне больно писать код на Java, и теперь его нет после того, как я начал программировать в Groovy. Итак, чего же вы ждете — станьте круче и веселитесь! 

Ресурсы

[1] Домашняя страница Groovy: http://groovy.codehaus.org (перейдите на эту страницу, чтобы загрузить Groovy или узнать больше о Groovy) 

[2] «Программирование Groovy 2: динамическая производительность для разработчика Java», Venkat Subramaniam, Pragmatic Bookshelf, 2013. (Если вы хотите изучить последнюю версию Groovy, это лучшая книга для начала) 

[3] «Groovy в действии», Дирк Кениг, Эндрю Гловер, Пол Кинг, Гийом Лафорж, Джон Скит, Manning Publications, 2007. (Ранние книги, которые сделали Groovy популярным и окончательным указателем на язык; второе издание, посвященное функциям Groovy 2 выйдет в 2014 году). 

[4] «Groovy для доменно-ориентированных языков», Fergal Dearle, Packt, 2010. (Если вы планируете разрабатывать DSL с использованием Groovy, это отличный ресурс для начала работы). 

[5] «Полное руководство по Grails 2», Джефф Скотт Браун, Грэм Роше, Apress, 2013. (Это недавняя книга о последней версии фреймворка Grails).