Итак, я экспериментирую со Scala, потому что я хочу написать парсер, а API-интерфейс Scala Parsers кажется действительно подходящим. В конце концов, я могу реализовать синтаксический анализатор в Scala и обернуть его за интерфейс Java, поэтому кроме дополнительной зависимости во время выполнения не должно быть никаких проблем взаимодействия.
После нескольких дней, когда вы действительно привыкли к удивительному синтаксису Scala, вот 10 лучших вещей, которые мне больше всего не хватает при возвращении к написанию Java:
1. Многострочные строки
Это мой личный фаворит и действительно потрясающая функция, которая должна быть на любом языке. Даже в PHP это есть: многострочные строки. Так же просто, как писать:
1
2
3
4
5
6
7
|
println ( "" "Dear reader, If we had this feature in Java, wouldn't that be great? Yours Sincerely, Lukas" "" ) |
Где это полезно? С SQL, конечно! Вот как вы можете запустить простой SQL-оператор с jOOQ и Scala :
1
2
3
4
5
6
7
8
9
|
println( DSL.using(configuration) .fetch( "" " SELECT a.first_name, a.last_name, b.title FROM author a JOIN book b ON a.id = b.author_id ORDER BY a.id, b.id " "" ) ) |
И это не только хорошо для статических строк. С помощью интерполяции строк вы можете легко вставлять переменные в такие строки:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
val predicate = if (someCondition) "AND a.id = 1" else "" println( DSL.using(configuration) // Observe this little "s" .fetch(s "" " SELECT a.first_name, a.last_name, b.title FROM author a JOIN book b ON a.id = b.author_id -- This predicate is the referencing the -- above Scala local variable. Neat! WHERE 1 = 1 $predicate ORDER BY a.id, b.id " "" ) ) |
Это довольно круто, не правда ли? Для SQL в Scala большой потенциал.
2. Точки с запятой
Я искренне не пропустил их ни капли. То, как я структурирую код (и, вероятно, большинство людей структурирует код), похоже, что Scala вообще не нуждается в точках с запятой. В JavaScript я бы не сказал то же самое. Похоже, что интерпретируемая и нетипичная природа JavaScript указывает на то, что отказ от необязательных элементов синтаксиса является гарантией того, что вы будете в ногу. Но не со Скалой.
1
2
3
|
val a = thisIs.soMuchBetter() val b = no.semiColons() val c = at.theEndOfALine() |
Вероятно, это связано с безопасностью типов в Scala, которая заставляет компилятор жаловаться в одной из этих редких неоднозначных ситуаций, но это всего лишь обоснованное предположение.
3. Скобки
Это минное поле и во многих случаях удаление скобок кажется опасным. На самом деле, вы также можете оставить точки при вызове метода:
1
|
myObject method myArgument |
Из-за количества неоднозначностей, которые это может генерировать, особенно при объединении большего количества вызовов методов, я думаю, что лучше избегать этой техники. Но в некоторых ситуациях просто удобно «забыть» о паренсе. Например
1
|
val s = myObject.toString |
4. Тип вывода
Этот действительно раздражает в Java, и кажется, что многие другие языки сделали это правильно, тем временем. Java имеет только ограниченные возможности вывода типов, и все не так ярко, как могло бы быть .
В Scala я мог бы просто написать:
1
|
val s = myObject.toString |
… и не заботится о том, что s
имеет тип String. Иногда, но только иногда мне нравится явно указывать тип моей ссылки. В этом случае я все еще могу сделать это:
1
|
val s : String = myObject.toString |
5. Кейсы
Я думаю, что мне хотелось бы написать еще один POJO с 40 атрибутами, конструкторами, геттерами, сеттерами, равно, hashCode и toString
— Никто не сказал. Когда-либо
В Scala есть тематические классы. Простые неизменные pojos, написанные в однострочниках. Возьмем класс Case, например:
1
|
case class Person(firstName : String, lastName : String) |
Я должен записать атрибуты один раз, согласился. Но все остальное должно быть автоматическим.
А как вы создаете экземпляр такого класса case? Легко, вам даже не нужен new
оператор (на самом деле, он полностью ускользает от моего воображения, зачем вообще нужен new
оператор):
1
|
Person( "George" , "Orwell" ) |
Вот и все. Что еще вы хотите написать, чтобы соответствовать требованиям Enterprise?
Примечание
ОК, некоторые люди теперь будут спорить, чтобы использовать проект ломбок . Генерация кода на основе аннотаций — это бессмыслица, и ее лучше избегать. На самом деле, многие аннотации в экосистеме Java являются простым доказательством того факта, что язык Java очень ограничен в своих возможностях эволюции. Возьмите, например, @Override
. Это должно быть ключевое слово, а не аннотация. Вы можете подумать, что это косметическая разница, но я говорю, что Scala доказала, что аннотации — это почти всегда неправильный инструмент. Или вы недавно видели сильно аннотированный Scala-код?
6. Методы (функции!) Везде
Это, на мой взгляд, одна из самых полезных функций на любом языке. Почему мы всегда должны связывать метод с определенным классом? Почему мы не можем просто иметь методы на любом уровне контекста? Потому что мы можем с помощью Scala:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
// "Top-level", i.e. associated with the package def m 1 (i : Int) = i + 1 object Test { // "Static" method in the Test instance def m 2 (i : Int) = i + 2 def main(args : Array[String]) : Unit = { // Local method in the main method def m 3 (i : Int) = i + 3 println(m 1 ( 1 )) println(m 2 ( 1 )) println(m 3 ( 1 )) } } |
Правильно? Почему я не могу определить локальный метод в другом методе? Я могу сделать это с классами в Java:
1
2
3
4
5
|
public void method() { class LocalClass {} System.out.println( new LocalClass()); } |
Локальный класс — это внутренний класс, локальный для метода. Это вряд ли когда-либо полезно, но то, что действительно полезно, это локальные методы.
Они также поддерживаются в JavaScript или REPL . Это замечательно для тестирования небольшого алгоритма или концепции, выходящей за рамки вашего приложения.
В Java мы обычно склонны делать это:
01
02
03
04
05
06
07
08
09
10
|
public class SomeRandomClass { // [...] public static void main(String[] args) { System.out.println(SomeOtherClass.testMethod()); } // [...] } |
В Scala я бы просто написал это в REPL:
1
|
println(SomeOtherClass.testMethod) |
Обратите внимание также на всегда доступный метод println
. Чистое золото с точки зрения эффективной отладки.
8. Массивы НЕ (это большая часть) особый случай
В Java, кроме примитивных типов, есть и такие странные вещи, которые мы называем массивами. Массивы происходят из совершенно отдельной вселенной, где мы должны помнить причудливые правила, восходящие к эпохам капитана Кирка (или около того):
Да, правила как:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// Compiles but fails at runtime Object[] arrrrr = new String[ 1 ]; arrrrr[ 0 ] = new Object(); // This works Object[] arrrr2 = new Integer[ 1 ]; arrrr2[ 0 ] = 1 ; // Autoboxing // This doesn't work Object[] arrrr3 = new int []; // This works Object[] arr4[] = new Object[ 1 ][]; // So does this (initialisation): Object[][] arr5 = { { } }; // Or this (puzzle: Why does it work?): Object[][] arr6 = { { new int [ 1 ] } }; // But this doesn't work (assignment) arr5 = { { } }; |
Да, список можно продолжать. С Scala массивы — это не особый случай, если говорить синтаксически:
1
2
3
4
5
6
7
|
val a = new Array[String]( 3 ); a( 0 ) = "A" a( 1 ) = "B" a( 2 ) = "C" a.map(v = > v + ":" ) // output Array(A:, B:, C:) |
Как видите, массивы ведут себя так же, как и другие коллекции, включая все полезные методы, которые можно использовать с ними.
9. Символические имена методов
Теперь эта тема одна , которая является более спорной, так как он напоминает нам о опасностях перегрузки операторов . Но время от времени мы хотели бы иметь что-то подобное. То, что позволяет нам писать:
1
2
3
|
val x = BigDecimal( 3 ); val y = BigDecimal( 4 ); val z = x * y |
Очень интуитивно, значение z должно быть BigDecimal(12)
. Это не может быть слишком сложно, не так ли? Мне все равно, является ли реализация *
действительно методом с именем multiply()
или чем-то еще. При написании метода я хотел бы использовать то, что выглядит как очень распространенный оператор для умножения.
Кстати, я также хотел бы сделать это с SQL. Вот пример:
1
2
3
4
5
6
7
|
select ( AUTHOR.FIRST _ NAME || " " || AUTHOR.LAST _ NAME, AUTHOR.AGE - 10 ) from AUTHOR where AUTHOR.ID > 10 fetch |
Разве это не имеет смысла? Мы знаем, что ||
означает concat (в некоторых базах данных). Мы знаем, что означает -
(минус) и >
(больше чем). Почему бы просто не написать это?
Выше приведен пример компиляции jOOQ в Scala , кстати.
Внимание: будьте осторожны
Всегда есть обратная сторона, допускающая что-то вроде перегрузки операторов или символических имен методов. Это может (и будет) злоупотреблено. И библиотеками, и языком Scala .
10. Кортежи
Будучи человеком SQL, это опять-таки одна из особенностей, которые мне больше всего не хватает в других языках. В SQL все является либо TABLE, либо ROW. мало кто на самом деле знает это , и лишь немногие базы данных поддерживают такой способ мышления.
В Scala нет типов ROW (которые на самом деле являются записями), но, по крайней мере, существуют анонимные типы кортежей. Думайте о строках как о кортежах с именованными атрибутами, тогда как классы case будут именоваться строками
- Кортеж: анонимный тип с типизированными и индексированными элементами
- Строка: анонимный тип с типизированными, именованными и индексированными элементами
- case class: Именованный тип с типизированными и именованными элементами
В Scala я могу просто написать:
1
2
3
4
5
|
// A tuple with two values val t 1 = ( 1 , "A" ) // A nested tuple val t 2 = ( 1 , "A" , ( 2 , "B" )) |
В Java подобное можно сделать, но вам придется писать библиотеку самостоятельно, и у вас нет языковой поддержки:
1
2
3
4
5
6
7
|
class Tuple2<T1, T2> { // Lots of bloat, see missing case classes } class Tuple3<T1, T2, T3> { // Bloat bloat bloat } |
А потом:
1
2
3
4
5
6
|
// Yikes, no type inference... Tuple2<Integer, String> t1 = new Tuple2<>( 1 , "A" ); // OK, this will certainly not look nice Tuple3<Integer, String, Tuple2<Integer, String>> t2 = new Tuple3<>( 1 , "A" , new Tuple2<>( 2 , "B" )); |
jOOQ широко использует вышеописанную технику для перевода выражений значений строк SQL в Java, и, что удивительно, в большинстве случаев вы можете обойтись без вывода пропущенного типа, поскольку jOOQ — это свободный API, в котором вы никогда не назначаете значения локальным переменным… пример:
1
2
3
4
5
6
7
8
9
|
DSL.using(configuration) .select(T1.SOME_VALUE) .from(T1) .where( // This ROW constructor is completely type safe row(T1.COL1, T1.COL2) .in(select(T2.A, T2.B).from(T2)) ) .fetch(); |
Вывод
Это была, безусловно, про-Scala и слегка противоречащая Java-статья. Не пойми меня неправильно. Я ни в коем случае не хочу полностью мигрировать в Scala. Я думаю, что язык Scala выходит далеко за рамки разумного в любом полезном программном обеспечении. Есть много маленьких функций и уловок, которые, кажется, приятно иметь, но неизбежно взорвутся на вашем лице, такие как:
-
implicit
преобразование. Это не только очень сложно, но и ужасно замедляет компиляцию. Кроме того, вероятно, совершенно невозможно реализовать семантическое управление версиями разумно, используяimplicit
, поскольку, вероятно, невозможно предвидеть все возможные нарушения клиентского кода из-за случайной обратной несовместимости. - Поначалу локальный импорт кажется великолепным, но его мощь быстро делает код неразборчивым, когда люди начинают частично импортировать или переименовывать типы для локальной области видимости.
- символические имена методов чаще всего используются неправильно. Возьмите, например, API синтаксического анализатора, который содержит имена методов, такие как
^^
,^^^
,^?
или~!
Тем не менее, я думаю, что преимущества Scala над Java, перечисленные в этой статье, могут быть реализованы и на Java:
- с небольшим риском нарушения обратной совместимости
- с (вероятно) не слишком большими усилиями, с точки зрения JLS
- с огромным влиянием на производительность разработчиков
- с огромным влиянием на конкурентоспособность Java
В любом случае, Java 9 будет еще одним многообещающим выпуском с горячими темами, такими как типы значений, декларация сайта , специализация (очень интересно!) Или ClassDynamic.
Будем надеяться, что с этими огромными изменениями найдется место для каких-либо из перечисленных выше небольших улучшений, которые добавили бы более непосредственную ценность в повседневную работу.
Ссылка: | 10 самых досадных вещей, возвращающихся к Java после нескольких дней работы Scala от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и JOOQ . |