📜 ⬆️ ⬇️

No way back: Why I switched from Java to Scala and I am not going to return

The debate about the advantages and disadvantages of Scala in front of Java reminds me of a debate about C versus C ++. Pros, of course, an order of magnitude more complex language with a huge number of ways to shoot yourself in the foot, drop the application or write a completely unreadable code. But, on the other hand, C ++ is simpler. It allows you to do simple things that would be difficult on bare C. In this article I will try to tell you about the side of Scala that made this language industrial — about what makes programming easier, and what the source code is clearer.

Further comparison between languages ​​comes from the fact that the reader is familiar with the following things:

- Java8. Without the support of lambda and nothing to talk about
- Lombok Short annotations instead of long sheets of getters, setters, constructors and builders
- Guava Immune Collections and Transformations
- Java Stream API
- A decent SQL framework, so multiline strings support is not needed
- flatMap - map, replacing the element with an arbitrary number (0, 1, n) of other elements.

Default immunity


Probably everyone already agrees that an immutable data structure is a Good Idea. Scala allows you to write immutable code without setting up `final`
')
Java
@Value class Model { String s; int i; } public void method(final String a, final int b) { final String c = a + b; } 


Scala
 case class Model(s: String, i: Int) def method(a: String, b: Int): Unit = { val c: String = a + b } 


Block of code, condition, switch are an expression, not an operator


Those. All of the above returns a value, allowing you to get rid of the return statement and greatly simplifying code that works with immutable data or more lambdas.

Java
 final String s; if (condition) { doSomething(); s = "yes"; } else { doSomethingElse(); s = "no" } 


Scala
 val s = if (condition) { doSomething(); "yes" } else { doSomethingElse(); "no" } 


Pattern matching, unapply () and sealed class hierarchies


Have you ever wanted to have a switch that works with arbitrary data types, issuing a warning when compiling if it doesn’t cover all possible cases and is able to make samples for complex conditions and not for the fields of an object? In Scala, he is!

Scala
  sealed trait Shape //sealed trait - ,          case class Dot(x: Int, y: Int) extends Shape case class Circle(x: Int, y: Int, radius: Int) extends Shape case class Square(x1: Int, y1: Int, x2: Int, y2: Int) extends Shape val shape: Shape = getSomeShape() //    Shape val description = shape match { //x  x    -    Dot case Dot(x, y) => "dot(" + x + ", " + y + ")" //Circle,     .       Scala case Circle(x, y, 0) => s"dot($x, $y)" //   10 case Circle(x, y, r) if r < 10 => s"smallCircle($x, $y, $r)" case Circle(x, y, radius) => s"circle($x, $y, $radius)" //       case sq: Square => "random square: " + sq.toString } //        ,    


Java
I will not even try to repeat it in Java.

A set of syntactic features to support composition


If the first three whales of the PLO are (we are speaking in chorus) encapsulation, polymorphism and inheritance, and the fourth is aggregation, then the fifth whale will undoubtedly be a composition of functions, lambdas and objects.

What is the problem with java? In round brackets. If you do not want to write one-liners, then when calling a method with a lambda, you will have to wrap it additionally in the parentheses of the method call.

Java
 //         map  flatMap.         . // collection     ,     ,    collection.flatMap(e -> { return getReplacementList(e).map(e -> { int a = calc1(e); int b = calc2(e); return a + b; }); }); withLogging("my operation {} {}", a, b, () -> { //do something }); 


Scala
 collection.flatMap { e => getReplacementList(e).map { e => val a = calc1(e) val b = calc2(e) a + b } } withLogging("my operation {} {}", a, b) { //do something } 


The difference may seem insignificant, but with the massive use of lambda, it becomes significant. Something like using lambda instead of inner classes. Of course, this requires the availability of appropriate libraries designed for mass use of lambda - but they, undoubtedly, are already there, or will be available soon.

Method Parameters: Named Parameters and Default Parameters


Scala allows you to explicitly specify the names of the arguments when calling methods, and also supports the default values ​​of the arguments. Have you ever written converters between domain models? This is how it looks in the rock:

Scala
 def convert(do: PersonDataObject): Person = { Person( firstName = do.name, lastName = do.surname, birthDate = do.birthDate, address = Address( city = do.address.cityShort, street = do.address.street ) ) 


The set of parameters and their types are controlled at the compilation stage, in runtime it is just a call to the constructor. In Java, one has to use either a call to the constructor / factory method (lack of control over the arguments, mixed up two string arguments and hello), or builders (almost well, but the fact that when designing an object all the necessary parameters were specified can be checked only in runtime ).

null and NullPointerException


Skalovsky Option is fundamentally no different from the optional java, but the features listed above make working with him easy and enjoyable, while in Java you have to make some effort. Programmers on the cliff do not need to force themselves to avoid nullable fields - the wrapper class is no less convenient than null.

Scala
 val value = optValue.getOrElse("no value") //   "no value" val value2 = optValue.getOrElse { //  exception throw new RuntimeException("value is missing") } val optValue2 = optValue.map(v => "The " + v) //Option("The " + value) val optValue3 = optValue.map("The " + _) //  ,   val sumOpt = opt1.flatMap(v1 => opt2.map(v2 => v1 + v2)) //Option       Option val valueStr = optValue match { //Option -   sealed trait   ! case Some(v) => // -   ,   log.info("we got value {}", v) "value.toString is " + v case None => // -   ,    log.info("we got no value") "no value" } 


Of course, this list is not complete. Moreover, each example may seem insignificant - well, what, in fact, the difference, how many brackets you have to write when you call a lambda? But the key advantage of the rock is the code that results from the combination of all of the above. So java5 from java8 is not very different in terms of syntax, but a set of small changes makes the development much easier, including opening up new possibilities in architectural terms.

Also, this article does not cover other powerful (and dangerous) features of the language, the Scala ecosystem and the FI as a whole. And nothing is said about the shortcomings (who do not have them ...). But I hope that the javists will get an answer to the question “Why this rock is needed,” and the rocky people will be able to better defend the honor of their language in online battles)

Source: https://habr.com/ru/post/315050/


All Articles