Scala kontra Java: Dlaczego powinienem uczyć się Scali?
Opublikowany: 2022-03-11Wprawdzie jest trochę prawdy w stwierdzeniu, że „Scala jest trudna”, ale krzywa uczenia się jest warta inwestycji. Niektóre z bardziej złożonych funkcji języka (krotki, funkcje, makra, żeby wymienić tylko kilka) ostatecznie ułatwiają programiście pisanie lepszego kodu i zwiększanie wydajności poprzez programowanie w Scali. Szczerze mówiąc, jesteśmy programistami i jeśli nie jesteśmy wystarczająco sprytni, aby nauczyć się języka, który jest nieco złożony, to jesteśmy w złym biznesie.
Scala to bezpieczny dla typów język JVM, który łączy zarówno programowanie obiektowe, jak i funkcjonalne w niezwykle zwięzły, logiczny i niezwykle potężny język. Niektórzy mogą być zaskoczeni, wiedząc, że Scala nie jest tak nowa, jak im się wydawało, po raz pierwszy wprowadzona w 2003 roku. Jednak szczególnie w ciągu ostatnich kilku lat Scala zaczęła zdobywać znaczące grono fanów. Co nasuwa pytanie „Dlaczego Scala?”.
W tym artykule omówiono zalety Scali, zwłaszcza w porównaniu z Javą (ponieważ Scala jest napisana do uruchamiania w JVM). Scala to nie jedyna próba stworzenia „lepszej Javy”. Alternatywy, takie jak Kotlin i Ceylon, również poszły tą drogą, ale podjęły fundamentalną decyzję, aby pozostać bardzo blisko składni samego języka Java, aby zminimalizować krzywą uczenia się. Może się to wydawać świetnym pomysłem, ale ostatecznie jest trochę autodestrukcyjne, ponieważ zmusza cię do pozostania w obrębie tych samych paradygmatów Javy, które były powodem, dla którego chciałeś stworzyć „lepszą Javę” w pierwszej kolejności.
W przeciwieństwie do tego, Scala została stworzona specjalnie z myślą o byciu lepszym językiem , porzucając te aspekty Javy, które uważała za restrykcyjne, nadmiernie nużące lub frustrujące dla programisty. W rezultacie rzeczywiście istnieją rozróżnienia w kodzie i zmiany paradygmatów, które mogą nieco utrudnić wczesną naukę programowania w Scali, ale rezultatem jest znacznie czystszy i dobrze zorganizowany język, który jest ostatecznie łatwiejszy w użyciu i zwiększa produktywność.
Scala kontra Java: co jest naprawdę bardziej złożone?
Chociaż prostota języka Java była częścią jego sukcesu, jak na ironię, przyczyniła się również do jego złożoności. Jasne, w Javie można napisać prawie wszystko, ale wymagane do tego wiersze kodu mogą być zniechęcające. Z kolei programowanie w Scali ma nieco bardziej złożoną strukturę. Ale jeśli możesz napisać nieco bardziej złożoną pojedynczą linię kodu, która zastępuje 20 „prostszych” linii Javy, która z nich jest naprawdę bardziej złożona?
Prawda jest taka, że Java jest często zbyt gadatliwa. W Scali kompilator jest niewiarygodnie inteligentny, dzięki czemu programista nie musi wyraźnie określać tych rzeczy, które kompilator może wywnioskować. Porównaj na przykład to proste „Witaj świecie!” program w Javie vs. Scala:
Witaj świecie w Javie:
public class HelloJava { public static void main(String[] args) { System.out.println("Hello World!"); } }Witaj świecie w Scali:
object HelloScala { def main(args: Array[String]): Unit = { println("Hello World!") } }Chociaż nie ma dużej różnicy między tymi dwoma językami, Scala jest mniej gadatliwa, nawet w tym prostym przykładzie.
Aby uzyskać bardziej praktyczny przykład, spójrzmy na tworzenie prostej listy ciągów:
Jawa:
List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); list.add("3");Scala:
val list = List("1", "2", "3")Z pewnością w Javie jest kilka sztuczek, aby nieco skrócić kod, ale nie w standardowym użyciu.
Rozważmy teraz przypadek, w którym mamy listę ciągów, które są liczbami, ale chcemy przekonwertować tę listę na listę liczb całkowitych:
Jawa:
List<Integer> ints = new ArrayList<Integer>(); for (String s : list) { ints.add(Integer.parseInt(s)); }Scala:
val ints = list.map(s => s.toInt)Dzięki funkcjonalnym właściwościom Scali ta konwersja staje się niezwykle prosta.
Przykład klasy: Java kontra Scala
Pójdźmy o krok dalej i porównajmy tworzenie standardowego ziarna / zwykłego starego obiektu Java (POJO) w Javie i Scali.
Po pierwsze, wersja Java:
public class User { private String name; private List<Order> orders; public User() { orders = new ArrayList<Order>(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Order> getOrders() { return orders; } public void setOrders(List<Order> orders) { this.orders = orders; } } public class Order { private int id; private List<Product> products; public Order() { products = new ArrayList<Product>(); } public int getId() { return id; } public void setId(int id) { this.id = id; } public List<Product> getProducts() { return products; } public void setProducts(List<Product> products) { this.products = products; } } public class Product { private int id; private String category; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } }Uff. Kod Lotty.
Teraz wersja Scala:
class User { var name: String = _ var orders: List[Order] = Nil } class Order { var id: Int = _ var products: List[Product] = Nil } class Product { var id: Int = _ var category: String = _ }Który język powiedzieliśmy, że jest bardziej skomplikowany?!

Czy jesteśmy uczciwi?
Jeśli dotarłeś tak daleko i jesteś programistą Java, możesz w tym momencie pomyśleć, że robię niesprawiedliwe porównanie kodu. W końcu nic nie powstrzymuje mnie przed tworzeniem zmiennych publicznych w Javie, a następnie pozbyciem się funkcji pobierających i ustawiających.
Jeśli jednak przypomnisz sobie rozumowanie za programami pobierającymi i ustawiającymi w Javie, jest to szczególnie przydatne w przyszłości. Oznacza to, że jeśli później będziesz musiał dodaćtrochęlogiki do pobierania lub ustawiania zmiennych, będziesz musiał ponownie napisaćte zmienne publiczne, aby zamiast tego używały metod (dlatego zachęca się do używania metod pobierających i ustawiających na początek w Javie ). Jednak w programowaniu Scala tak nie jest. Ze względu na projekt języka abstrakcja pozostaje nienaruszona bez konieczności pobierania i ustawiania. Rozważmy na przykład tę zmodyfikowaną klasę User w Scali, która zgłasza NullPointerException , jeśli spróbujesz ustawić nazwę na null:
class User { private var _name: String = _ var orders: List[Order] = Nil def name = _name def name_=(name: String) = { if (name == null) { throw new NullPointerException("User.name cannot be null!") } _name = name }I nadal możesz ustawić nazwę w ten sposób:
user.name = "John Doe"Zauważ, że całkowicie eliminuje to potrzebę wstępnego konfigurowania akcesorów metod.
Co więcej, ponieważ Scala preferuje niezmienność, mogę napisać to w Scali jeszcze bardziej zwięźle za pomocą klas przypadków:
case class User(name: String, orders: List[Order]) case class Order(id: Int, products: List[Product]) case class Product(id: Int, category: String)Dość szalone, o ile mniej kodu muszę napisać.
Biorąc przykład nieco dalej
Rozważmy teraz scenariusz z powyższymi klasami, w którym chcę dodać fajną małą metodę w klasie User , która zwraca listę wszystkich Products zamówionych przez User :
W gadatliwym świecie Javy:
public List<Product> getProducts() { List<Product> products = new ArrayList<Product>(); for (Order order : orders) { products.addAll(order.getProducts()); } return products; } Na szczęście java.util.List ma metodę addAll , inaczej getProducts() byłaby jeszcze dłuższa w Javie.
Z kolei w Scali wszystko czego potrzebujemy to:
def products = orders.flatMap(o => o.products)Możesz zobaczyć, o ile mniejsza jest implementacja języka Scala. Tak, nowicjuszom Scala może wydawać się to bardziej skomplikowane, ale kiedy już w pełni zrozumiesz koncepcje, które się za nim kryją, kod Scali będzie wyglądał na znacznie bardziej uproszczony niż kod Java.
Skomplikujmy się jeszcze bardziej. Co zrobić, jeśli chcemy otrzymać tylko Products z określonej Category ?
W tym przypadku nie jesteśmy w stanie skorzystać z metody addAll w java.util.List , więc w Javie sytuacja robi się brzydsza :
public List<Product> getProductsByCategory(String category) { List<Product> products = new ArrayList<Product>(); for (Order order : orders) { for (Product product : order.getProducts()) { if (category.equals(product.getCategory())) { products.add(product); } } } return products; } Jednak w Scali kod pozostaje dość prosty. Po prostu używamy flatMap , aby połączyć listy produktów z każdego Order spłaszczone w jedną listę, a następnie filtrujemy, aby uwzględnić tylko te, które pasują do kategorii:
def productsByCategory(category: String) = orders.flatMap(o => o.products).filter(p => p.category == category)Dynamiczny a statyczny
Z pewnością nie brakowało nowych języków w ciągu ostatnich kilku lat, ale podczas gdy prawie wszystkie inne, które pojawiły się ostatnio, są dynamiczne, Scala jest typowana statycznie.
Jako profesjonalny programista – choć znam i używam wielu języków dynamicznych – uważam, że sprawdzanie w czasie kompilacji jest niezwykle ważne dla napisania solidnego kodu. W dynamicznym języku nigdy nie możesz być pewien, że Twój kod jest wystarczająco wolny od błędów i niezawodny, dopóki nie uruchomisz go w szerokim zakresie scenariuszy. Może to prowadzić do potencjalnie poważnych defektów w kodzie, które nigdy nie zostaną zrealizowane, dopóki kod nie znajdzie się w produkcji.
Zakończyć
Mamy nadzieję, że ten artykuł zestawia Javę i Scala na tyle, aby dać wstępne poczucie potęgi i możliwości Scali oraz zaostrzy apetyt na naukę języka. Jest to nie tylko świetny język, który może sprawić, że programowanie będzie mniej nudne i przyjemniejsze, ale jest również używany przez niektóre z największych firm na świecie (LinkedIn, Twitter, FourSquare, The Guardian, żeby wymienić tylko kilka).
Popularność i wykorzystanie Scali szybko rośnie, o czym świadczy stale rosnąca liczba wolnych stanowisk dla programistów Scali. Jeśli jeszcze tego nie zrobiłeś, teraz jest dobry moment, aby zacząć jeździć na fali i przestać pytać „Po co uczyć się Scali?”
