Miért nem szeretem a java-t

Az utóbbi napokban egy házin dolgoztam, amit java-ban kell megírni, és ezáltal újra rájöttem, hogy tulajdonképpen nem szeretem a java-t.

Nyilván az általános ellenérveket senkinek nem kell bemutatnom (pl. default argument hiánya, nem tudom java7-ben van-e már), ezért írok pár olyat, ami nem a nyelv, hanem a "library" hiányossága.

A háziban háromféle collection-t használtam, mivel a feladat szempontjából így volt a legcélszerűbb. Viszont mind a hárommal van valamilyen probléma, amit ugyan könnyű megkerülni, de nem elegáns.

A Stack iterátora nemes egyszerűséggel elölről járja be a listát, hasonlóan a Vector iterátorához, viszont pont azért használtam Stack-et, mert fordított bejárásra volt szükségem. Nem én vagyok az első, akinek ez feltűnt: http://www.tigraine.at/2010/05/12/never-assume-stack-iterators-in-java/

A TreeMap-nek viszont egyáltalán nincs iterátora, ami akár érthető is lehetne, hiszen a map-eknek általában véve nincs iterátora. Viszont mivel tree, egy inorder bejárással szépen visszakapnánk az elemek key szerint növekvő sorrendjét. Ehelyett van keySet() metódusa, ami visszaad egy Set-et a key-ekből, ami már iterálható. Ha pedig minden iterációban átadjuk az aktuális key-t a get() metódusnak, azzal szépen lekérhetjük a (piros-fekete) fa elemeit növekvő sorrendben. Ez szép és jó, de ha jól számolom, a teljes algoritmus O(n*log2(n)), míg egy korrekt inordert alkalmazó iterátorral O(n) lenne.

Szerk: oké, ezt benéztem, van entrySet() is. Ettől még az fenn áll, hogy szerintem a TreeMap-nek lehetne iterátora.

Ez egy kicsit PEBKAC, de megspórolt volna pár perc debuggingot, ha az ArrayList iterátora egy speciális esetben konzisztens marad annak ellenére, hogy a dokumentáció szerint ez nem garantált. Ugyanis az történt, hogy lekértem egy üres ArrayList iterátorát, majd hozzáadtam a listához pár elemet, majd használtam az iterátort. Ez utóbbi hasNext() metódusa ugyan true-t adott vissza, viszont a next() metódus hívásával szépen elszállt a program. Ha a megfelelő exceptiont dobja a next(), vagy ha a hasNext() false-t ad vissza, nem töltöttem volna perceket azzal, hogy kiderítsem, mégis mit jelent ez a hiba: "ArrayList$Itr.next() line: not available [local variables unavailable]", és hogy kaphattam. (Utólag elég egyértelmű, de egyszerűbb lett volna exceptionből.)

Hozzászólások

Tehát azért nem szereted a Java-t, mert meg kell tanulni (el kell olvasni) a hozzá tartozó doksit?

Borzalmas problemak. :)
Ha van egy TreeMap-ed es a values() metodust hasznalod, akkor az ertekeket a key-ek alapjan rendezett sorrendben kapod vissza.
A Stack-et meg azert hasznaljuk, mert a legfelso elemet akarjuk piszkalni. Ha csak forditott bejarasra van szukseged, akkor hasznalj valamilyen listat es a listIterator(list.size()) altal visszaadott iteratort + hasPrevious(), previous().
Nem erzem, hogy ezek a csunya hack kategoriaba tartoznanak.

RTFM, de tényleg. A JDK API docs leír mindent.

RTFM, kedves Bat :)

A Stack -ről annyit, hogy a Deque -s kollekciók sokkal teljesebben megvalósítják a LIFO -t, ez csak a push és a pop miatt hívódik így (ezt írja is a doksi).

És nézd meg a Google Guava -t, egy csomó hasznos kiegészítést ad a Java -s gyűjtemény keretrendszerhez.

--
http://neurogadget.com/

Ideillik egy Sussman idézet arról, hogy miért dolgozák át az MIT-n a SICP-es kurzusukat. Elég találó szerintem.

"engineering in 1980 was not what it was in the mid-90s or in 2000. In 1980, good programmers spent a lot of time thinking, and then produced spare code that they thought should work. Code ran close to the metal, even Scheme — it was understandable all the way down. [...] 6.001 had been conceived to teach engineers how to take small parts that they understood entirely and use simple techniques to compose them into larger things that do what you want.
But programming now isn’t so much like that. Nowadays you muck around with incomprehensible or nonexistent man pages for software you don’t know who wrote. You have to do basic science on your libraries to see how they work, trying out different inputs and seeing how the code reacts. This is a fundamentally different job, and it needed a different course."

(A Java dokumentációját egyébként nem tartom rossznak, mielőtt még valaki direktbe így fordítaná le magának ezt az idézetet. Annak szántam ami, egy viszonylag jól megragadott összképnek amibe persze beleférnek mindenféle konkrét kivételek. Azaz azt igaznak tartom, hogy mai fejlesztőnek nem csak abban kell kompetensnek lennie, hogy a könyvtárak működéséből következtessen a programja működésére, hanem gyakran a fordított irányban is, úgyhogy nem baj ha az ember edz rá - még ha "véletlenül" is, mert mondjuk éppen nem vesz észre valamit a dokumentációban ami pedig ott van... ;)

default argument hiánya <-- ez azért annyira nem vészes... kötelez arra, hogy tisztább kódot írj!:)

Nem szebb, de tényleg kényelmesebb, ha pl

def x(a, b, c=None):
....if c == None: # vagy c=c if c not None else b
........c = b

kódot írsz, de azért a megszokott - javában is használt - verzió sem olyan rossz..

Az csak egy példa, egyébként a házimban pont nem is volt rá szükségem.

Egyébként nem hiszem, hogy a java-s megoldás szebb kódot eredményezne, mert az input validálást mindenképpen el kell végezni. Írok egy példát (picit sarkított, de ez van):

Default argument nélkül:


public void setValue(int v)
{
	if(v<0)
	{
		throw new IllegalArgumentException();
	}

	value=v;
}

public void setValue()	//reset
{
	setValue(0);
}

Default argumenttel:


public void setValue(int v=0)
{
	if(v<0)
	{
		throw new IllegalArgumentException();
	}

	value=v;
}

--
Don't be an Ubuntard!

Mivel majdnem mindenki ugyanazt írta, egyszerre válaszolok:

1: RTFM: természetesen elolvastam a manualt, miután tapasztaltam, hogy nem tudom úgy megírni a programot, ahogyan én azt elképzeltem. Tudom, nem szabad programozás közben feltételezésekkel élni, de egy jó keretrendszert szerintem többek között az tesz jóvá, hogy manual olvasás nélkül is könnyedén lehet benne dolgozni, azaz a programozó az elképzeléseit meg tudja benne valósítani. Ilyen elképzelés az, hogy a Stack iterátora felülről járja be a Stack-et, meg ilyen elképzelés az, hogy a TreeMap-nek van normális iterátora.

2: Stack helyett más lista: felvetődik a kérdés, hogy ha a Stack nem a legjobb Stack implementáció, akkor mégis mire van? Ez is pont az a probléma, mint az első pontban: van egy elképzelésem, hogy hogyan működik egy stack, és annak különböző szolgáltatásai, nyilván ezért választom a Stack-et egy stack megvalósításához. Nem olvasom el a manualt, mert számomra triviális, hogy egy jó stack implementáció pont úgy működik, ahogyan én azt elképzeltem. Ezután azt kell tapasztalnom, hogy a Stack egyáltalán nem úgy működik, ahogyan az általam elképzelt ideális stack. Ekkor persze kénytelen vagyok elolvasni a manualt, amiben persze nincs javaslat arra, hogy melyik osztályt használjam a Stack helyett, ha nekem az általam elképzelt szemantikára van szükségem.

Egyébként a linkelt blogbejegyzés alatt van egy link a bugreportra, számomra egyáltalán nem úgy tűnik, hogy én vagyok a hülye, amiért feltételeztem, hogy az iterátora felülről járja be a Stacket.

--
Don't be an Ubuntard!

1. Te a Stack iterátorát felülről indulónak képzeled el. Más meg nem. TreeMap normális iterátor alatt mit értesz? Mindenki mást. A Stack eredeti implementációja by-design ilyen, ezen nem fognak verzióról verzióra változtatni. Ami egyszer bekerült az API-ba, az úgy is marad. A Java platformon nem lehet csak úgy visszafelé nem kompatibilis módosítást csinálni, ez nem PHP. Milyen szép lenne, ha az új libc nem lenne viszsafelé kompatibilis, ugye?

A Stack osztályban le is van írva, hogyan működik:
"The Stack class represents a last-in-first-out (LIFO) stack of objects. It extends class Vector with five operations that allow a vector to be treated as a stack."
Ez tehát egy Vector, ami fel van ruházva Stack működéssel. Mivel a Vector gyerekosztálya, ezért úgy kell viselkednie az iterátorának is, mint a Vector iterátorának (Liskov substitution principle).

2. Stack azért van még mindig a JDK-ban, hogy a visszafelé kompatibilitás megmaradjon. Stack egy JDK 1.0 óta meglévő class, 16 éves kód. Azóta csináltak jobbat, ők sem tökéletesek.

Nem várom el, hogy utólag módosítsanak rajta (sőt), de akkor a manualban emeljék ki, hogy máshogyan működik, mint ahogy elvárnád, és ha ez zavar, használj mondjuk LinkedList-et helyette. Akár deprecated-dé is tehetnék, de ezt se várom el.

Attól hogy a Vector gyerekosztálya, még override-olhatták volna az Iterable metódusait.

A TreeMap egy tree, aminek jellemzően pre-, in-, vagy postorder bejárása szokott lenni, nyilván itt a pre- és postordernek nincs túl sok értelme, az inordernek viszont van.

--
Don't be an Ubuntard!

"de akkor a manualban emeljék ki, hogy máshogyan működik, mint ahogy elvárnád"
Igen, írják, hogy ez az osztály nem más, mint a Vector gyerekosztálya 5 új metódussal. Így az iterátora úgy működik, mint a Vector iterátora.

"Attól hogy a Vector gyerekosztálya, még override-olhatták volna az Iterable metódusait."
Nem, mert akkor nem lenne funkcionálisan ekvivalens.
Ha P szülőosztály és C gyerekosztály, akkor minden olyan helyne, ahol P használt, azt bármikor helyettesíteni lehet C egy példányával. Ha az Iterable úgy lenne Override-olva, hogy a Stack iterátora más kimenetet ad, mint egy ugyanolyan módon feltöltött Vector iterátora, akkor ez a helyettesítési elv sérül. Ez borítja a unit testinget is.
Gondolj bele, van egy kódod, amely a következőt csinálja: egy Vectorhoz hozzáad egy elemet. Azt, hogy valóban a végére került-e az elem, úgy lehet tesztelni, hogy végigiterálunk a Vectoron, és megnézzük, hogy tényleg az utolsó helyre került-e be az eleme. Ha itt az általad javasolt Stack implementáció lenne (és nyugodtan lehetne, hiszen a Stack egy Vector), akkor a unit test elbukna.

Gondolkodj el azon, milyen problémákat tudna okozni, ha egy Vector v = valami; értékadás érvényes, de a valami nem úgy viselkedik, mint egy Vector. Főleg, ha a valami egy külső paraméter, amiről nem tudod, milyen implementációjú.
Röviden összefoglalva: egy gyerekosztály nem módosíthatja a szülőosztály specifikációját egyik override-olt metódussal kapcsolatban sem, nem szűkíthetik a működést.

"A TreeMap egy tree, aminek jellemzően pre-, in-, vagy postorder bejárása szokott lenni, nyilván itt a pre- és postordernek nincs túl sok értelme, az inordernek viszont van."
Mindenféle bejárásnak van értelme, feladattól függően. Az egy dolog, hogy egy bináris keresőfa (nem minden fa bináris és keresőfa), inorder bejárása pont szerencsés, de ez nem azért van, mert maga az inorder bejárás értelmes csak, hanem azért, mert bináris keresőfán használod. De attól még a fa, mint adatstruktúra nem jelent semmit.
Preorder bejárás például nagyon jó akkor, ha egy fát akarsz (pl. könyvtárhierarchiát) indentálva kiírni. Tipikus preorder feladat.

Ha a Stack push, pop és egyéb stack-re jellemző metódusait máshogy ("fordítva") implementálják, akkor ez nem lenne probléma. De az igazi probléma az, hogy a te érvelésed szerint már az hiba, hogy a Stack egy Vector. (Egyébként ez a fent linkelt bugreportban is le van írva.)

TreeMap-ről annyit, hogy én most kifejezetten keresőfára gondoltam tree alatt, bár tény, hogy ezt nem írtam explicit le (gondoltam a threadből öröklődik). :)

--
Don't be an Ubuntard!