Miért nem szerettem a Java-t?

Ez a cikk 2003 novemberében készült. Azóta a Java sokat fejlődött, emiatt a lenti megállapítások egy része már nem érvényes. Az elmúlt években a Java rengeteg hasznos újdonságot szedett fel. Említésre méltó dolgok például:

És még valami: lévén, hogy a nyelv maga elég primitív, a fordítás pedig gyors és jól van szeparálva a fileok között ezért igazán remek IDE-t lehet köré építeni. Így aztán sok lenti megállapítás sületlenségnek tűnik olyanok számára, akik nem ismerték a Java-t korábban. A cikket itt hagyom, mert a Java régi népszerűségét továbbra is a marketingesek világ feletti szörnyű befolyásának jeleként tartom számon ;-)

2007. október 29.


Manapság nagyon divatos és nagyon keresett nyelv a Java. Rengeteg könyvet írnak róla, nagyon sokan használják, sok Java programozót keresnek és egészen elképesztő, hogy mit meg nem írnak Java-ban. Egy hozzá nem értő számára úgy tűnhet, hogy a Java valójában egy jó programozási nyelv. Ezeknek a gondolatoknak valóban van jogalapjuk:

A Java előnynek tartott tulajdonságai véleményem szerint gyakran egyenesen hátrányosak. Minden bizonnyal vannak olyan programozók, akik számára a Java egy olyan programozási nyelv, melyen hatékonyan tudnak dolgozni.

Véleményem szerint azonban egy olyan programozó, aki dolgozott már liberálisabb és nagyobb kifejezőkészséggel megáldott programozási nyelven (pl. C++, Perl), gyakran súlyos szenvedésként éli meg, hogy bilincsbe vert kézzel kell programoznia, és folyamatosan a Java szegényes lehetőségeihez igazodnia.

A Java nyelvvel a legnagyobb probléma az, amiből minden más problémája is fakad. Ez nem más, mint a nyelv tervezőinek szemlélete. A Java alkotóit az a gondolat vezérelte, hogy a programozók szerencsétlen, fogalmatlan emberek, akik munkaidejük nagy részét azzal töltik, hogy hibát hibára halmoznak.

A programozói hibák kiküszöbölése

Miféle hibák? Nos, a programozók elírásokat ejtenek, olvashatatlan kódot készítenek, és órákig hezitálnak azon, hogy a programozási nyelvek által adott eszközök közül mit is válasszanak - és végül rosszul döntenek. Egyébként pedig úgysem értik meg egy tisztességes programozási nyelv lehetőségeit.

Ezért a Java tervezői azt gondolták, hogy minden problémát kiküszöbölnek azzal, ha a nyelvet olyan egyszerűvé teszik, amennyire csak lehet. Nem kell sok lehetőség, szerencsétlen programozók úgyis csak belezavarodnának. Minden elvégzendő feladathoz csak egyetlen út vezessen.

Mi lett az eredménye ennek a törekvésnek? Nos, a nyelv valóban egyszerű. A kód könnyen olvasható. A nyelvet egy analfabéta is képes gyorsan megtanulni. Mire jó tehát? Arra nagyon is alkalmas, hogy ha egy olyan csapatot rakunk össze, ahol a tervezők nagyon jók és nagyon alaposak (azaz nagyon mélyen megtervezik a rendszert), a programozók pedig kevésbé jók, akkor az implementáció folyamán a kódolók nem nagyon tudnak eltérni attól, amit a tervező kigondolt, mert a nyelv nem sok választási lehetőséget biztosít a számukra. A kreativitást félre kell tenni.

Ebben a helyzetben ez valóban előny, de ennek az előnynek nagyon nagy az ára: a programozói szabadság elvétele. A programozó ideje nagy részét kénytelen lesz azzal tölteni, hogy a nyelv követeléseit kielégítse, nem koncentrálhat az igazi problémára. Nos, van akinek az ilyesmit bírja az idegrendszere, van akié nem.

Hasonló gondolatok közepette jutottak az alkotók arra a zseniális gondolatra, hogy az operátortúlterhelés túl bonyolult, ráadásul felesleges (a programozók pedig természetesen fogalmatlanok), ezért ezt az eszközt teljesen kihagyták a nyelvből. Közben azért rájöttek, hogy néha azért nagyon jól jön, ezért a String osztálynál használhatjuk az infix + operátort sztringek összefűzésére.

Az igazság valójában az, hogy a nyelv tervezőjének meg kell bíznia a programozóban. Igenis sok lehetőséget kell a kezébe adnia, hogy a programozó kiválaszthassa a feladathoz megfelelőt. Igaz, hogy valóban tévedhet, és biztosan fog is. Mindez azonban olcsó ár azért, hogy a programozó nagyobb lelkesedéssel, gyorsabban fejleszthessen.

Alaptípusok - osztályok

A Java tervezői a túlterhelhetetlen operátorok mellett egy másik általános, az egész nyelvre jellemző koncepciót fektettek le: Java-ban minden osztály, ami pedig nem, az objektum. Akárcsak az előző esetben, itt is eszükbe jutott azonban egy zseniális gondolat: az Integer, Byte, Double, Float... osztályok nagyon szépek, de mi lenne, ha lennének helytakarékossági okokból int, byte, double, float, stb. típusok is? Ezzel ráadásul gyorsabb kódhoz is juthatnánk. De jó!

Mi lett ennek az eredménye? Kín, kín, kín...
Mivel minden alaptípushoz úgyis van a típust magába foglaló osztály, ezért nem gond, ha minden tárolóban csak Object-et tárolhatunk, gondolták. Ennek eredményeképpen számokat kezelni Java-ban katasztrófa. Ugyanis ha egy számot szeretnék egy ilyen tárolóban eltárolni, akkor mindenképpen példányosítani kell osztályt. Az osztályban viszont példányosítás után az értéket módosítani nem tudjuk, ezért ha a tároló egy számlálót tartalmaz, akkor a példányból ki kell szedni az alaptípus értékét, elvégezni vele valamilyen műveletet (például növelni), majd egy újabb példányosítással az objektumot a tárolóban el kell helyezni. Az osztállyal semmiféle műveletet nem végezhetünk, ami az értéket módosítaná. Ez nagyon kényelmetlenné teszi a használatát, az alaptípusok használatától remélt takarékossági eredmények pedig teljesen elmaradnak, a kód zavaros és nehezen olvasható lesz (és ezzel egy újabb megsértett alapkoncepcióval találkozunk).

Nézzünk meg egy példát ezzel kapcsolatban. Nagyon egyszerűen szavakat számlálunk egy Hashtable segítségével, a szó pedig minden karaktert tartalmaz, ami nem fehér szóköz (hogy ne kelljen a ,'., stb. karakterekkel bajlódni).

import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Enumeration;

class Test {
	public static void main(String[] argv) {
		String test = "Let's count the words in this text. "
			+ "The text contains some words more than once, "
			+ "so their count will be more than one.";

		Hashtable words = new Hashtable();

		StringTokenizer st = new StringTokenizer(test);
		while ( st.hasMoreTokens() ) {
			String token = st.nextToken();
			if ( words.containsKey(token) ) {
				// a kód legborzalmasabb része: egy Integer objektum
				// megszerzése, ronda explicit típuskonverzió,
				Integer value = (Integer) words.get(token);

				// a value objektum-tól az érték megszerzése, az int
				// típusú érték növelése, majd példányosítás ismét...
				words.put(token, new Integer(value.intValue() + 1));
			}
			else {
				words.put(token, new Integer(1));
			}
		}

		Enumeration keys = words.keys();
		while ( keys.hasMoreElements() ) {
			String token = (String) keys.nextElement();
			System.out.println(token + ": " + words.get(token));
		}
	}
}

Az összehasonlítás kedvéért megnézhetjük, hogy az objektum orientált Ruby nyelven mindezt hogy írhatnánk meg:

String test = "Let's count the words in this text. "
	+ "The text contains some words more than once, so their count will be more than one."

words = Hash.new(0);
test.split.each { |word| words[word] += 1 }
words.each { |key,value| puts "#{key}: #{value}" }

Kellemetlen továbbá, hogy Java-ban a char típust leszámítva minden egész típus előjeles. Emiatt ha byte-okat kell kezelnem és tárolnom, akkor választhatok: vagy használom az int típust, hiszen az értékkészletének része a 0..255 intervallum, és akkor minden egész értékem 4 byte-on lesz tárolva, vagy azt választom, hogy én kezelem az intervallum eltolódását, ami kényelmetlen, és rontja az olvashatóságot.

Platformfüggetlenség

A Java-hívek egyik fő érve a platformfüggetlenség. Mit jelent ez? Azt, hogy a lefordított byte-kód teljesen hordozható! Bármilyen gépen lefuttathatom, ugyanazt az eredményt fogja adni.
Szép történet.

De mit is jelent ez, ha jobban belegondolunk? Azt, hogy minden gépen futtatható? Korántsem. Azt jelenti, hogy minden gépen futtatható, ahol van JVM! Ezek száma pedig kevesebb, mint ahány platformra például Perl interpretert találunk.

Arról nem is beszélve, hogy ha valamely platform specifikus dolgait használjuk, akkor a programunk erőteljesen platformfüggő lesz. Ha pedig nem használjuk, akkor egy modern scriptnyelven (Perl, Ruby, Python, stb.) megírt program ugyanúgy jogosan használhatja a platformfüggetlen jelzőt.

Sebesség

A legkevésbé fájó hibája a Java-nak a sebessége. Ezt róják fel a legtöbbször, és meg kell hagyni, valóban nem egy olajozott villám. Véleményem szerint azonban a sebesség nem a legfontosabb szempont. Nem kell rettentően optimálisnak lennie, a processzoridő jelentős részét igenis oda kell áldozni a futtatáshoz szükséges adminisztrációnak, ha ezzel jelentős előnyöket érhetünk el, amit például a Java esetében a hordozható byte-kód testesít meg.

A Java azonban egy kicsit itt is elveti a sulykot. Lévén, hogy még a String osztály is Java-ban lett megírva, ezért egy kód futtatása valóban nagyon lassú. Idéznék valakit ezzel kapcsolatban: Nem az a baj a Java-val, hogy lassú. Hanem az, hogy ***** lassú!. Ha további izgalmas technológiákat vetünk be, akkor további látványos sebességcsökkenés érhető el. Hadd idézzem EPP kollégánkat ez EJB-vel kapcsolatban: Valóban nagyon szép az EJB. Az osztályaimat szétpakolhatom tíz szerverre, és teljesen transzparensen kezelhetem őket anélkül, hogy tudnom kellene, hogy melyik hol van. Na de könyörgöm, ha nem lenne az EJB, nem kellene az a tíz szerver!.

Összefoglalás

A Java programozási nyelv a gyakorlatban bizonyos helyzetekben hasznos és alkalmas eszköz lehet. Ez nem vitatható, és némi létjogosultságot is ad neki. A fent felvázolt problémák azonban más nyelvekben nincsenek meg (kissé cinikusan azt mondhatnánk, hogy más nyelvekben más problémákkal vannak helyettesítve), így valóban érdemes egy alternatív lehetőséget választani, például Perl, Python, Ruby. Bár ezek dinamikus típusrendszerű nyelvek, mégis alkalmasak minden feladat elkészítésére, melyek Java-ban elkészíthetők.

Nyissátok fel a szememet! Ne hagyjatok tévedésekben élni! Várok minden megjegyzést a padre@elte.hu címre.