ITHub

Scala: miért lesz a Java utódja, és miért érdemes neked is megtanulnod?

Scala: miért lesz a Java utódja, és miért érdemes neked is megtanulnod?
Farkas Gábor
Farkas Gábor
| ~6 perc olvasás

Az elmúlt tizenöt-húsz évben a Java elképesztően népszerű programozási nyelvvé vált, azonban az akkor lefektetett alapok nem minden esetben felelnek már meg a mai követelményeknek, így számos fejlesztő modernebb nyelvek után néz. Ez a cikk nem egy Scala tutorial, hanem inkább egy rövid összehasonlítás, ismertető azokról a nyelvi elemekről, amelyek a Javához képest gyorsabbá és produktívabbá tehetik a mindennapi munkát.

Scala: miért lesz a Java utódja, és miért érdemes neked is megtanulnod?

A Scala egyik óriási előnye — és elterjedésének valószínűleg elég jelentős kulcsa —, hogy Java bájtkódra fordul, így az évek során felhalmozódott óriási Java könyvtárak mind hozzáférhetők Scala-s programjainkban is. Az utóbbit használva azonban számos előnyre tehetünk szert: eltűnt az eddig kötelező boilerplate kód (getterek és setterek), kifejezőbb, kompaktabb nyelvi konstrukciók (tuple, case class, option), amik kevés kóddal valósítanak meg hasznos funkciókat, ill. erőteljesebb kód-újrafelhasználás — az újrafelhasználható elemek kisebbek a Scalaban, mint a Javaban, ill. egyszeri öröklés helyett traitek és függvények vannak. A továbbiakban ezeket az előnyöket ismertetjük néhány egyszerű példán keresztül.

Nincsenek felesleges getterek és setterek

Nézzünk egy egyszerű Java osztályt, ami egy autót reprezentál:

public class Car {
	private String modelName = null;
	private int maxSpeed = 0;

	public void setModelName(String modelName) {
		this.modelName = modelName;
	}

	public String getModelName() {
		return modelName;
	}

	public void setMaxSpeed(int maxSpeed) {
		this.maxSpeed = maxSpeed;
	}

	public void getMaxSpeed() {
		return maxSpeed;
	}
}

Scalaban ugyanez az osztály valahogy így nézne ki:

public class Car {
	var modelName = ""
	var maxSpeed = 0
}

Ebben az osztályban a változók publikusak, ami Javás agyunkban azonnal megkongatja a vészharangokat. Mi van, ha később szükségünk lesz getterekre és setterekre, mert például plusz logikát szeretnénk bevinni, refaktorálnunk kell az egészet? Nyilván nem. Ez annak köszönhető, hogy a Scala nagyon rugalmas metódusdefiníciókat enged meg, így írhatunk egy metódust, ami pont úgy néz ki, mintha a változók direkt elérése lenne. Alakítsuk át az előző példát úgy, hogy csak 100-nál nagyobb maximális sebességet tudjunk beállítani:

public class Car {
	var modelName = ""
	private var theMaxSpeed = 0

	def maxSpeed = theMaxSpeed

	def maxSpeed_= (newMaxSpeed : Integer) : Unit = {
		if(newMaxSpeed > 100) theMaxSpeed = newMaxSpeed
	}
}

Nézzük meg, mi történt. Definiáltunk egy új privát változót theMaxSpeed néven, amihez írtunk egy maxSpeed nevű gettert, ami ugyanúgy használható, mint az előző példában, amikor még egy publikus változó volt (a Scalaban nem szükséges sem a return, sem a kapcsos zárójelek (ha a függvény egyetlen kifejezésből áll)). A setter metódus már nem teljesen triviális, itt azt fontos látni, hogy a metódus neve maxSpeed = (az egyenlőségjel is része a metódus nevének!), ahol a _ karakternek speciális jelentése van: ezzel definiálhatunk olyan metódusnevet, amiben szóköz van. Így amikor az alábbi kódot írjuk:

val c = new Car()
c.maxSpeed = 30

valójában a setter metódust hívjuk meg.

Case class, pattern matching

A case class egy olyan speciális osztály, ami egyszerű adathordozást valósít meg, és belső állapota kizárólag a konstruktortól függ. Definiáljunk egy ilyen osztályt:

case class Person(firstName : String, lastName : String)

Az ilyen típusú osztály egy egyszerűsített szintaxissal inicializálható: Person("John", "Doe"), a toString és az egyenlőség operátor implicit módon definiálva van rajta, és ami a legfontosabb: használható a Scala egyik legsokoldalúbb konstrukciójában, a pattern matchingben. Ez önmagában egy elég nagy témakör, így érdemes utánaolvasni, ha még nem hallottál róla, de a case class és a pattern matching egyik legerősebb kombinációját, a constructor patternt az alábbi példával illusztráljuk.

Tegyük fel, hogy hasonló Person objektummal reprezentált személyekről szeretnénk két konkrét emberkét felismerni, és speciális üzenettel köszönteni őket (legyenek ők "Alice Foo" és "Bob Bar"), a többi esetben pedig csak kiírni egy általános üzenetet. Ez Javaban valahogy így nézne ki:

if(obj instanceof Person) {
	Person p = (Person)obj;

	if (p.getFirstName() == "Alice" && p.getLastName() == "Foo") {
		System.out.println("Szia Alice! Jó újra látni téged!");
	} else if (p.getFirstName() == "Bob" && p.getLastName() == "Bar") {
		System.out.println("Hello Bob! Remélem jól telik a napod.");
	} else {
		System.out.println("Üdv, idegen!");
	}
}

Ez Scalaban (felhasználva a korábban definiált case classt):

obj match {
	case Person("Alice", "Foo") => println("Szia Alice! Jó újra látni téged!")
	case Person("Bob", "Bar") => println("Hello Bob! Remélem jól telik a napod.")
	case Person(firstName, lastName) => println("Üdv, idegen!")
}

Option

Javában gyakran visszatérő elem, amikor != null vizsgálatot kell végeznünk valamin, amikor az valóban lehet null. Például:

items = cart.getItems();

if (items != null) {
	for (Item i : items) {
		// ...
	}
} else {
	System.out.println("A bevásárlókosár üres.");
}

A Scala standard library tartalmaz erre egy Option nevű ősosztályt, amelynek két leszármazottja van, a Some és a None. Az alap minta az, hogy azok a metódusok, amik a Javában nullt is visszaadhatnak, itt vagy Some-ot, vagy None-t adnak. Így pattern matchinggel kombinálva az előző példa Scalaban így nézne ki:

items match {
	case Some(items) => items.foreach( ... )
	case None => println("A bevásárlókosár üres.")
}

Itt nem történhet meg, hogy a vizsgálatot elfelejtve futásidőben esetleg NullPointerException-t kapunk.

Tuple

Szintén gyakran adódik, hogy olyan metódust kell írnunk, ami több értékkel tér vissza. Ilyenkor általában definiálunk egy egyszerű osztályt, ami összefogja ezeket az értékeket, azonban ez nem a legjobb gyakorlat, és sok felesleges kódot generál. A Scalaban erre egy jó konstrukció a tuple. Ez egyszerűen kapcsos zárójelek között az értékek listája, például az 5-ös szám és a "Hello" string egy tuple-ben így nézne ki_

{5, "Hello"}

Ha több értéket szeretnénk egy metódusból visszaadni, egyszerűen csak adjunk vissza egy tuplet.

Trait

A C++-ban olyan sok félreértés és probléma forrása volt anno a többszörös öröklés, hogy a Javában teljesen kiiktatták. A Scala ezt a problémát az ún. traitek segítségével hidalja át, amik leginkább a C++ absztrakt osztályaihoz hasonlítanak, és több is lehet belőlük:

class Employee extends Human with Taxable with Payable

A klasszikus gyémánt problémát a C++ a virtual kulcsszóval hidalta át — a Scala ehelyett egy előre meghatározott algoritmussal dönti el, hogy az azonos szignatúrájú metódusok közül melyik fog lefutni.

Összegzés

Ez az egész persze nagyon felületes, és csak a jéghegy csúcsát jelenti, de ha eddig még nem találkoztál a Scalaval, talán ez alapján a poszt alapján kedvet kaptál hozzá, hogy közelebbről is megvizsgáld. Bár igényel némi tanulást az új konstrukciók megismerése, azonban láttuk, hogy ezek a modern elemek mennyire megkönnyítik gyakran visszatérő feladatok megoldását. Egy átfogó képet a nyelvről a Programming in Scala című könyv első kiadása ad, ami a linken ingyenes elérhető.