A hup.hu oldalon ismét megérkeztek a szakértők, és csodás érveikkel lesöpörték a tudatlan M$ huszárokat. Lássuk mennyire igazuk van, hogy miért jobb a java!
A eredeti érvek
Default parameters
default parameter: ez nekem kifejezetten nem hiányzik. Az elmúlt ~1-1,5 évben nem emlékszem 10 esetre, amikor használni szerettem volna. Ellenben, tapasztalataim szerint, ebből születnek a 10-15 paraméteres metódusok, átláthatatlan fejléccel.
Akár igaz is lehet. Ellenben nem kell egy konstruktorból/metódusból opcionális paraméterek száma a négyzeten overloadot csinálni. Szerintem pedig az sem mókás. Főleg ha a default változik, és valahol nem frissíti az ember, mert nem volt elég előrelátó, hogy konstansokba szervezze ezeket.
A Java mint a gondoskodó állam, megvéd a rossz döntéseidtől! Első felvonás, függöny le.
Operator overloading
operator overloading: jajj, ne. Inkább legyen egy myVector.crossProduct(Myvector lhs), s egy myVector.dotProduct(MyVector lhs) függvényem, minthogy a * az egyik, a % a másik. (ez utóbbi egyetemen jött szembe, és isten mentsen tőle, minden híváskor fel kell tekernem a dokumentációig). Leírni nem fáj a hosszabb metódusnevet (az IDE úgyis kiegészíti), ellenben borzasztóan olvashatatlan.
Milyen igaz, sokkal jobban olvasható, és legalább tudjuk, hogy mi történik! (A szarul választott metódusnevektől is megvéd ha nincs operator overloading! Csak operatort lehet nem idiomatikusan overloadolni!)
És milyen olvasható is a metódusos módon felírva egy vektorokkal számolás!
Pl: Az alábbi kódokban a, b, c vektorok, lambda skalár.
Java
Vector x = a.crossProduct(b.add(c)).scale(a.scalarProduct(b) * lambda);
Olvashatóbb, mint a C# változat, ahol a % a keresztszorzat, mint az említett példában. (Pedig a % hasonlít is a keresztre. Ezt nem megjegyezni némi rövidtávú memória problémákra utal):
C#
var x = (a * b * lambda) * a % (b + c);
Emellett az, hogy csak a primitív típusokra értelmezett a legtöbb operátor, azért is jó, mert pl. mindig tudhatja az ember, hogy mikor hív függvényt, és mikor csak natív CPU műveletet! Így pl. öröm ez annak aki BigInteger vagy BigDecimal osztályokkal dolgozik, mert mindig eszében tarthatja, ha pl. összead vagy kivon két értéket, hogy azok bizony potenciálisan nagyon költséges függvényhívással megvalósított műveletek! Ki akarná a +
és -
operátorokat használni skaláris számokat ábrázoló típusokra, ha azok nem a stacken vannak? Sokkal jobb a .add(BigInteger other)
és a .subtract(BigInteger other)
!
Ezért is jó, hogy a String osztályra, bár az nem primitív típus, ott van a +
operátor, hisz az immutable stringek összefűzésével létrehozott új String készítés egy CPU által natívan megvalósított olcsó művelet. Jó ez az egységes tervezői szemlélet ami az egész Java nyelvet végig kíséri!
Amúgy az, hogy nincs operator overloading, a indexerek hiányánál is elég jó dolog! Mindig tudod, hogy List, vagy Map leszármazottal dolgozol, mert nem mindegy, hogy .add(int index, T value)
vagy .put(K key,V value)
hívást használsz!
Honnan tudnád pl C#-ban, hogy a list[3] = something
és dict[SomeKeyTypeInstance] = somevalue
közül melyik mibe teszi bele az értéket? Hisz sosem tudhassa (sic!) az ember, hogy épp milyen objektumot manipulál, ha a metódusnév nem segít!
Amúgy az indexereknél az is jó még, hogy míg a C#-ban a foreach
az Enumerable<T>
interfészt megvalósító objektumokra működik, amit persze az Array
is implementál. Javában az egységes, az egész nyelven végig vonuló logika mentén ez csak az Iterable<T>
interfészt megvalósító objektumokra igaz. Na meg az Array-re, de az nem valósítja meg az interface-t. Így persze tömböt nem tudsz beadni Iterable<T>
-t váró metódusba. Persze egy Utility method mindig van kéznél midenre. Az sokkal tisztább mint az például az Extension method! (lásd alább) Ez a utility method amúgy az gyűjtemény inicializálásnál is vissza-visszaköszön néha (szintén lásd alább).
Ott van persze még az ==
operátor kérdése. Milyen jó is, hogy a referenciák azonosságát vizsgálja! Hisz erre gyakrabban van szükség, mint a szemantikai egyenlőségre (ami magában foglalja a referencia azonosságot is). Leggyakrabban persze csak a referencia azonosság érdekel minket. Ja, nem…
Mindemellett elismerem, hogy az operator overloading túl/rosszul használata problémás, de ennyi erővel a rossz sofőrök miatt az autózást is betilthatnánk. (Amúgy ez a szemlélet az egész Java nyelvre igaz). A Scala nyelvben például kifejezetten zavaró a sok infix operátor számomra, de ott bármilyen szimbólum lehet operátor, amit a gyűjteményosztályok szerintem eléggé túltoltak.
Property
property: ne. Egy szabványos @Getter
/@Setter
annotációt (ala Lombok) elfogadnék (hiányzik), de ami függvényhívás, annak legyen függvényhívás szintaktikája.
Milyen igaz! Látszódjék, ha valami függvényhívás! Ezért is jók a primitív típusok, amik nem objektumok, és metódusaik sincsenek!
Egy Java templateben, vagy HQL queryben, vagy egy adatkötésnél a mezőt leíró mágikus stringben nyilvánvaló számodra, hogy az obj.bla
az valójában azt jelenti, hogy: obj.getBla()
. Vagy azt, hogy obj.isBla()
. Esetleg azt, hogy obj.bla
, vagy egyes template engineknél talán azt, hogy obj.hasBla()
, netán obj.bla()
. Egyértelmű. Ez a konvenció. A fenti esetek részhalmazai különféle sorrendben librarytól függően. Világos, mint a vakablak, hogy ez egy mezőhozzáférés, vagy egy metódushívás, és az is tök nyilvánvaló, hogy melyik metódusé! Persze mondhatjuk erre, hogy miért van többféle a konvenciónak megfelelő metódus egy osztályban például. Mindig más tartja rosszul. Ez így jó!
Eközben a C#-ban ha van egy ilyen obj.Bla
, akkor honnan tudnád, hogy ez property (azaz metódusvívás)? Talán abból, hogy ez a konvenció! Property neve nagybetűvel kezdődik, és mezőhozzáférésnek néz ki. Mező neve kisbetűvel kezdődik és mezőhozzáférésnek néz ki. A templateben, LINQ-ban, mindenhol az obj.Bla
az az obj.Bla
propertyt jelenti, mert nincs dupla-, tripla-, többesgondolos konvencióra szükség. Nyelvi elem. Mindkét esetben egy konvencióra hivatkoztam, de az egyik kevésbé kitekertnem tűnik valahogyan…
Ráadásul az IDE is tud segíteni, hogy valami mező, vagy property. Persze, tudom, vim-ben fejlesztünk Javát is…
Amúgy mennyivel jobbak az anonym osztályok is a lambdáknál. Egyből látszik, hogy milyen típusokkal dolgozik, nem a fordító következteti ki abból, amit amúgy a programozó is lát. Ja nem, ez 10 évvel ezelőttől tavalyig volt a mantra, amíg a C#-ban volt ilyen feature, Javában nem. Majd ha a néhány év(tized) múltán a Java-ba is bekerül a property mint feature, akkor a puristák átállnak a Go-ra, a többiek meg törlik az idevonatkozó hozzászólásaikat.
Mellesleg amíg a Lombok a bytecodeot szerkesztgeti valamint nem dokumentált API-kat használ, addig a .Net 4.6 óta a fordítási folyamat gyakorlatilag pluginezhető, egyes lépcsői hookolhatóak a Roslyn segítségével. Úgy vélem, hogy az eddigi trendek alapján 2030 körül a Java is tudni fogja ezt.
Amúgy épp a minap olvastam azt, hogy a “Java a bürokrácia programkódként való manifesztációja”, és csak egyetérteni tudok vele néhány évnyi koca Java fejlesztés után.
Extension methods
extension methods: ezt jól sejtem, hogy olyasmi, hogy meglévő osztályokhoz tudok saját metódusukat „beleinjektálni”? ne. Erre Verhás Péter blogját tudnám hozni - a megszokott, mindenki által ismert osztályokat ne bővítgesse senki, mert nagyon csúnya dolgok születhetnek belőle.
Ha csak sejti a kollégaúr, hogy mi ez, akkor már az több mint elég, hogy leszólhassa!
Igaza van, tényleg ne bővítgesse senki a mindenki által ismert osztályokat! Mi van, ha a Pistike csinál egy hibás bővítményt a String-re, a libraryjét behúzom, és a stringjeim megbolondulnak! A hülye M$ termékei is biztos ezért bugosak!
Még véletlen sem lehetséges, hogy amelyik fileban használni akarom, ott excplicit be kell using
-olni a bővítéseket.
Verhás Tamás blogjában is tökre igaza van Tamásnak. Majdnem. A Lobok esetében is explicit kell importálni egy osztályba az extension methodokat tartalmazó osztályokat, tehát explicit tudható, hogy milyen extensionök lesznek használatban. Csak abban az osztályban. Globálisan semmilyen mellékhatása nincs ennek.
Partial class
partial class: itt amit az MSDN hoz két példát, hogy mikor érdemes használni, de egyiket sem tudom elfogadni. A több ember/forráskódot a veziókezelő oldja meg, a generált kódot pedig én kiváltanám szabványos annotációkkal. Ettől függetlenül lehet, hogy hasznos, de nem érzem a hiányát.
Ó, azok a szabványos annotációk! A JavaEE legszebb vonásai. Persze generált kód csak getter/setter lehet, egy GUI designer véletlen sem generál kódot? Szerencsére az is könnyen leírható szabványos annotációkkal!
public class MyDialog extends Dialog {
@Taborder(2)
@Text("ok")
@Position(Anchor.BOTTOM | Anchor.RIGHT, 20, 20)
@Size(60,30)
@OnClick(ButtonOkOnClick.class)
Button ok;
public class ButtonOkOnClick implements ButtonClickHandler {
void onClick(Button source, ClickEvent event){
// ...
}
}
//...
}
Nos, ez így elnézve egyszerűbb dolgokra akár működhetne is… Megkövetem Vilmost.
Onnantól úgyis kézzel kell az egészet csinálni, és az annotációkat is, meg a generált kódot is sokszor a hajunkra lehet kenni.
Másfelől nem ritka, hogy generált kódra van szükség, és segít azt nem eltörni egy változásnál az, hogy félve lehet szedni a generált és az azt kiegészítő kézzel írt részt.
Decimal type
Decimal type: hm? ez ez: MSDN? Ez mégis mire jó? Ebben a 0.3-0.2 tényleg 0.1?
Igen ennyire. Bankokban használ(hat)ják például. Bármilyen meglepő, de Java-ban is van: BigDecimal.
Persze ez (A Decimal) értéktípus, nem tetszőlegesen nagy értékek reprezentálására való. Mint ilyen stacken tárolt. Nem írok primitív típust, mert ez is egy objektum. Ami ugye a Java-ban nem lehetne szegénykém.
Dynamic type
dynamic type: ez mire jó? ezt találtam: MSDN - de nem értem, miért jó nekem, ha kikerülöm a compile time checket. El tudom fogadni, csak nem látom a szituációt, amikor ez hasznos lehet.
Ez dinamikus nyelvekkel interfészelésénél lehet jó. Például Python vagy JavaScript motorral interfészelheted a kódod, ami pl. pluginezhetővé teheti az alkalmazásodat, kódot injektálhat a user (ennek minden előnyével és hátrányával), akár egy szövegdobozba irkálással. COM interopnál is jó. Template motorok használják előszeretettel még, ott is vannak előnyei.
További eltérések:
Oké, mostanra láthatóan elfogyott a vitriolom, komolyra fordítom a szót!
Egységes típusrendszer
Igen, a C# alatt nincs duplagondol. Az 1 is egy objektum, ahogy egy byte is az, ahogy egy string is az, vagy egy ablak. Mindegyiken elérhetőek az Object
-ből örökült metódusok, például a .ToString()
.
Az értéktípusnak vannak persze sajátosságai, pl. mindig sealed
, de ez a stacken allokált fix helyből és a Liskov helyettesíthetőségi elvbből könnyen belátható tervezői döntés.
unsigned
Bizony, néha kell. Biztos erre is lehet valami lózungot találni, hogy megideologizáljuk, hogy miért jobb, ha nincs. Nekem nem megy.
Várjunk, csak, van egy ötletem! Próbálkozzunk a védjük meg a programozót/indiait valamitől, amit esetleg nem értene módszerrel!
Egy indiai/pakisztáni kecskepásztor csak a természetes számokat érti, tehát legyen csak unsigned a nyelvben! Húha, ez nem jó, ezzel nehéz lesz való életbeli problémákat megoldani, úgyhogy legyen csak előjeles, nehogy a típuskonverziókat félreértse! Persze így kell egy extra operátor: >>>
, de sebaj, olyan ez mint az análszex, majd megtanulják szeretni…
Mondjuk akkor lehet, hogy az M$ ezért csinálta ezt, hogy sok olcsó munkaerőt tudjon alkalmazni, mert így nem kell a negatív számokra kiképezni a “resource”-öket, ugye?
Amúgy a Java 8 egyik újítása az unsigned értékkel dolgozást segítő új API. Innováció a Java világában!
Értéktípusok cím szerinti átadása: ref, in, out
Java alatt nincs. Néha jó ha van. Natív kóddal interopnál is, de amúgy is. Java alatt erre az egy elemű tömb átadást szoktam látni, ami iszonyatos hack.
C# alatt a ref
az a sima “pointer” (persze erősen típusos), míg az in
az a hívott metódusban nem változtatható, kint kötelezően inicializált ref
-ek részhalmaza, az out
pedig hívásban kötelezően inicializálandó ref
részhalmaz. Jött már jól, bár tény, hogy nem mindennap használja az ember.
Értelmes enumok
Java alatt is van enum
, de nagyon nyakatekert.
A C#-os enum tisztább, szárazabb érzés. Megadható a tárolási (érték)típus.
Mindenféle okosságot tud (pl. FlagsAttribute), gyors, és nem tud túl sok, sosem használt dolgot.
Nem tudom, hogy a Java enumok a nehézkességük miatt, vagy mert túl későn érkeztek, esetleg mivel referencia típusúak, ezért lassabbak, de pl. az SWT-ben egyáltalán nincsenek használatban, helyette mindenféle misztikus intek vannak, amik közül valahonnan tudnod, érezned kellene, hogy mikor mit használhatsz, mivel az osztály ami definiálja csomó ilyet tartalmaz, néha prefixxelve, néha nem.
Példaként javasolnám egy MessageDialog kirakásának összehasonlítását C#/WindowsForms és Java/SWT alatt. Mindkettő enum-intenzív lenne, de láthatóan csak a C#-os van rendesen megcsinálva.
Persze a flags jellegű enumokat Java alatt operator overloading nélkül legyártani elég gagyin néz ki, plusz az elemek explicit felsorolása, és az értékek kombinálása az .or()
metódusban és a mögöttes implementáció is lassú felesleges boilerplate/generált kód fenyegetést sugall.
Példa hasból:
var v = Visibility.Fullscreen | Visibility.AlwaysOnTop;
Ezt Java enumokkal csak így lehet megcsinálni:
Visbility v = Visibility.Fullscreen.or(Visibility.AlwaysOnTop);
Ez a példa csak most jutott eszembe, lehet, hogy meg lehet szépen oldalni. Régen használtam enum-ot Java alatt, de arra emlékszem, hogy nem volt igazán jó érzés.
Anonim class
Ez a Java alatt teljesen mást jelent, és amit jelent, az nem is rossz dolog. Ez pl. néha, amikor csak összedobtam valamit, még úgy is éreztem, hogy hiányzott C# alatt is!
Másfelől a C# anonym class funkcionalitása néha jó lenne Java alatt is:
var model = new { Name = "John Doe", Age = 12 };
var text = templateEngine.Render(model);
Oké, kissé erőltetett, de nem értem ott miért ne lenne ez jó. Megvannak ennek a korlátjai, C# alatt sem fog szembe jönni ilyen akárhol, legfeljebb dynamic
formájában.
Gyűjtemény inicializálás
Java alatt fáj. Egyszerűen iszonyat.
Ami C# alatt így néz ki:
var list = new List<int>(){1, 2, 3, 4, 5};
var dict = new Dictionary<int, string>(){ {1, "egy"}, {2, "kettő"}, {3, "három"} };
Az Java alatt ez:
List<Integer>list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
Map<Integer,String>dict = new TreeMap<>();
dict.put(1,"egy");
dict.put(2,"kettő");
dict.put(3,"három");
vagy
List<Integer>list = new ArrayList<>(){{
add(1);
add(2);
add(3);
add(4);
add(5);
}};
Map<Integer,String>dict = new TreeMap<>(){{
put(1,"egy");
// dict.put(2,"három"); // látom megfogtad a lényeget! http://hup.hu/node/144487?comments_per_page=9999#comment-1938278
// De neked legalább egy értelmes találat is volt a kommentjeid közt... Bár ott sem lehetett feltűnő, hogy copy paste hiba. ;)
put(2,"kettő");
put(3,"három");
}};
viszont ha ezt sokat használod, akkor végtelen sok típust generál neked a fordító, amit nem akarsz. Ezért szokás tömb inicializátort használni, majd egy utility classal azt gyűjteménnyé alakítani, ha az olvashatóság fontosabb mint a teljesítmény.
Lehet még builder patternt is használni. Az ám az igazán OOP (néha hallom, hogy a Java tisztább OOP nyelv, mint pl a C#), hogy külön incializálást segítő osztályt kell írni minden gyűjteménytípushoz! Persze ha valaki Guava-t használ, nem kell ezzel vacakolni, vannak kész builderek (az immutable típusokhoz), meg minden mi szem-szájnak ingere! Azt meg úgyis mindenki használja!
megjegyzés
ezt a szakaszt olvasói visszajelzés hatására kibővítettem, hogy az üzenet a felhozott példám egyszerűbb Javás megvalósítása ellenére is átjöjjön. Bár úgy vélem, hogy az olvasó ellenpélda a korábban is leírt példám pepitában.
Mellesleg az immutable az nem azonos a fixed-sizezal. Ezt azért illene tudni! Az alábbi példa talán segít megérteni a különbséget:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.set(1, 6);
System.out.println("oh, ez mutable!");
for(Integer i : list){
System.out.println(i.toString());
}
try{
list.add(7);
} catch (Exception e){
System.out.println("... de fixed size");
}
Ezt itt is megnézheted.
megjegyzés 2
Egy másik szemfüles olvasó mérsékelten építő kritikája nyomán (csak így tovább, én is így kezdtem! :)) tovább csiszoltam a bekezdést, amiben szőrnyű copy-paste hibákat ejtettem! Szerencsére a Java 9-ben persze ez is meg van oldva.
Hol is van még a Java 9? Ja, hogy csak a roadmapen!?!?
megjegyzés 3
Az első szemfüles olvasó sajnos a szalmabáb érvelési hibába esett.
Lecserélte állításom, majd ezen saját állításához ragaszkodva próbálja az én állításom tévességét bemutatni.
Állításával szemben nem ugyanazt adja az Arrays.asList(), mint a fenti példák, hiszen saját maga állítja, hogy az immutable gyűjteményt ad vissza. Fent azonban nem immutable gyűjteményeket használok. Ön önellentmondásba keveredett.
Q.E.D.
Mellesleg érdekes hozzáállás az, hogy egy ismeretterjesztő cikkben ha egy általa eddig nem látott (nem véletlen, de erről szól a cikk) megoldást lát, akkor azt törölni kéne, mert felesleges.
Diamond operátor
Minek?!?!
Javítsunk a sok redundancián a kódban. Félig. Erről a korszellemnek megfelelően egy Joda idézethez folyamodnék:
Tedd, vagy ne tedd, de ne próbáld!
Bezzeg a var
az hülyeség lenne… Kódra lásd a fenti példát. És nem, a var
az nem azonos az object
-el. Az az első értékadásnál a kifejezés balértékének típusa, statikusan. Csak nem kell kiírni, mert úgyis az =
jobb oldalán látod, hogy mi van! Csak lokális változó kontextusban használható.
LINQ
Nos, a Java8 Streams API-ja ugyan egy lépés a jó irányba, de…
A LINQ (mind a DSL, mint a extension methodokra alapuló megoldása) jobban tetszik nekem, és ami objektív érv: bővíthetőbb mint a java Streams API-ja, ami egy jó kezdeményezés, de a kivitelezésében olyan inkonzisztenciákat látok, hogy az egy külön posztot megérne. (Pl. az Iterable nem stream()
-elhető, csak elég nyakatekerten. Persze 3rd party librarykkel is orvosolható, vagy iszonyatos utility method hívásokkal. Mindenre kell egy 3rd party lib Java alatt.)
Verziózási problémák, modulrendszer hiánya
A C#-ban az assembly egy verziózott, erős identitású egység. Java alatt a jar nem ilyen. Egy jar-ból két függőség ha két inkompatibilis verziót akar használni, akkor vagy szívtad, vagy az OSGi (amúgy egész korrekt) megoldását használod. Ellenben akkor a függőségek egy részét kézzel csomagolhatod újra, mert a Maven Centralban publikált csomagok jelentős részél nem vették a készítők a fáradtságot, hogy ezt is megcsinálják. Talán a problémáról sem tudnak. Amúgy ez érthető, ez a fordító/csomagoló eszköz dolga lenne, például a Maven-é
A .Net tervezésének egyik motiváló oka a dependency hell megoldása volt, amit sikeresen meg is tettek. További információkét a CLR via C# könyv első fejezeteit javaslom, illetve a fenti problémába futást a Java fejlesztés során, illetve az OSGi használatát.
Persze erre is terveztek megoldást találni a Java 8-ra. Jelenleg az az álláspont, hogy talán a Java 9 majd megoldja. Persze a kvázi-szabvány OSGi helyett a Jigsaw nevű megoldással, hiszen miért is egy nyílt és piacvezető, elterjedt és bevált megoldást használnánk… Új és saját kell, ami talán interoperábilis lesz. Meglátjuk mi sül ki ebből.
Maven/NuGet
Iszonyú. XML copy-paste. Értelmes defaultok hiányoznak. Egyszerre akar build tool és csomagkezelő lenni. Mindkettőt megcsinálja, de kissé fáj használni. Néha meg nagyon fáj. Mindenütt már elavult plugin verziós példák hemzsegnek a neten. Nem szól, hogy frissíthetnél.
A NuGet élmény fényévekkel jobb. Az MSBuild-et mondjuk nem tudom dicsérni, de legalább a Visual Studio helyettem karbantartja a build filet. Ha ez nem elég, az is pilótavizsgássá változik. Azt állítják, hogy a Fake most a hip megoldás erre. Az új ASP.Net és DNX, coreclr kapcsán valamelyest átalakítottak a .Net-es csomagolást, azt még nem tudom véleményezni.
Furcsaságok, tervezési hibák az osztálykönyvtárban és a nyelvben
Ami a minap jött szembe és zavaró volt, hogy a clone()
az Object
-ben definiát. De azt, hogy az shallow vagy deep copy nem tudhatod, hacsak nem használsz külön interfészt a deep copy-ra. A .Net-ben ezért is külön interfész a Cloneable<T>
.
A switch(){}
ha null
-t kap, akkor NullPointerException
-t dob, mivel az equals()
példányszintű, és azt hívja. C#-ban az ==
operátort hívja, ami gyakorlatilag statikus metódus, így a null
-ba sem hal bele.
Ha már switch
, akkor az, hogy van fallthrough az nekem nem tetszik a Java-ban, de goto
bezzeg nincs. A C#-os goto
ritkán ugyan, de jól tud jönni (pl. állapotgépeknél). A Java alatti break to label is jött már jól, de a goto
-nál kevésbé intuitív szerintem, ráadásul C# alatt a goto-val a switch címkék közt is tudsz vezérlést átadni, mintha fallthrough lenne, de explicit le kell írni, hogy mit szeretnél. Ez viszont szubjektív érv, azt elismerem.
Iszonyatos hiányok az alap osztálykönyvtárban
Persze mindenféle 3rd party libek pótolgatják a rést. Az életben a Java projekt ami nem használja a Guava-t az kb. egy egyetemi beadandó szintjén mozog. Főleg ha valaki legacy okokból Java 8-nál korábbi verzión ragadt, ugyanis a Guava egész jó megoldást szállít erre az esetre.
megjegyzés: ezt a bekezdést kibővítettem
Mivel kérdés volt, hogy mik ezek a hiányok, íme pár példa, a teljesség igénye nélkül (de a cikk korábbi részét olvasva is látható pár példa).
URL, URI, UrlBuilder osztályok. Bugosak, hiányosak, kényelmetlenek.
Iterable interfész mutable
A Java runtimeban az Iterable interfész mutable, ugyanis az Iterator<T>
interfész tartalmaz egy a gyűjteményt mutáló metódust!. Ez ordas nagy tervezési hiba. Ezzel rokon hiba, hogy a java.util packageben szereplő gyűjtemény interfészek is mutable-ek, nincs read-only párjuk.
A .Net BCL-ben az IEnumerator<T>
immutable. Az IList<T>
egy IReadOnlyList<T>
interfészt is megvalósít, amin keresztül nem mutálható a gyűjtemény.
Mivel interfész őse lehet a contractjának részhalmaza, ezért be kellett volna szúrni “föléjük” ReadOnly prefixxel csak olvasható interfészeket, amit szintén (automatikusan) megvalósítanának a jelenlegi gyűjteményosztályok. Ez a visszafelé kompatibilitást sem törné el első pillantásra, viszont szigorúbb contractok definiálást tenné lehetővé, ami a statikus nyelvek legnagyobb erénye, hisz a fordító helyettünk foghatna hibákat!
Ennél jobban nem fejtem itt ki, de a java.util.ArrayList<T>
és a System.Collections.Generic.List<T>
osztályok összevetésével ebből már könnyen belátható a tervezési hiányosság.
A Guava szállít Immutable collection osztályokat. A .Net-ben is vannak ilyen interfészek és osztályok, igaz, hogy egy részük csak a 4.5.-el jelent meg,viszont szintén nagyon hasznosak! Ezeket a System.Collections.Immutable
névtérben találjuk.
Array nem Iterable
Ezt fentebb is írtam. Nem látom be, hogy miért nem.
Iterable.stream() nincs
Más leszármazott collectionökre viszont van. Máshol is említem ezt a példát.
Képezz szimmetrikus differenciát egy halmazból!
Adott két java.util.Set
példány. Azokat az elemeket kérem amik nem szerepelnek a metszetben.
Ebbe egyszer belefutottam, és nagyon nem tetszett, hogy kell egy clone()
, mivel a retainAll()
mutálja a gyűjteményt, és más mód akkor nem volt.
Most megnéztem, és C#-ban ezt a HashSet.SymmetricExceptWith()
metódussal megtehető ez egy lépésben, ami szintén mutálja az egyik halmazt, amit hasonlóan ocsmánynak találok.
C# alatt persze LINQ segítségével is megoldható, de az újabb Java alatt stream()-el is meg lehet csinálni. Amúgy ez a LINQ .Union()
és .Except()
metódusairól jutott eszembe, amikkel építhető olyan szűrő, ami a források mutálása nélkül számítja ezt ki (feltehetően sokkal lassabban).
Ez döntetlen.
Érdekesség, hogy a .Net-ben valamiért a Set típusok mostohagyermekek. Az IEnumerable<T>
-ben van .ToList<T>()
, de nincs .ToHashSet<T>()
. Persze extension methodot bármikor írhatok rá…
Primitive collections
Mivel a generics olyan, amilyen (lásd lentebb), ezért ilyen is van.
Nice to have
Ezekre a .Net BCL-ben sincs kulcsrakész megoldás, de ha másért már úgyis behúzom a Guava-t, akkor már használni fogom:
hashcode()
helperek amik tisztább érzést adnak mint az IDE generált hashcode-ok, bár látom, hogy ez az innováció Java 7 óta már alap. Ezt csak az Objects.equals-()-ról tudtam.
compareTo()
helperek
Strings.emptyToNull()
. Lehet, hogy csak nekem fontos/hasznos, de ezzel egy s == null
-ra tudom redukálni azt, amit C#-ban String.IsNullOrEmptyOrWhitespace(s)
-el csinálnék.
- Ordering helpers
Kovariáns visszatérési típus, generikus típus paraméter megkötések
Na ez nem hiba, csak fura. Hiányoltam C#-ban némi Java kódolás után, nem tudtam mire vélni a hibaüzenetet. Miről is van szó?
A Java nyelvben a metódus visszatérési typusa kovariáns, azaz a leszármazott típus egy felülírt metódusának visszatérési típusa lehet az ős visszatérési típusának leszármazottja.
Ez a C#-ban nincs így, Eric Lippert állítása szerint a CLR implementáció korlátai miatt. Eric könyvét minden haladó C#-osnak ajánlom átolvasásra.
A Java generics megvalósításában a ko- és kontravariancia, a típus-paraméter megkötések kifinomultabban szabályozhatóak a wildcardokkal mint a C#-ban. Általában a C# által adott eszközök is elegendőek.
Egyik nyelv sem támogatja a variadikus típusparaméteres típusokat. Valahol úgy olvastam, hogy azért, mert messze túl alacsony lenne a ROI, és túl komplex lenne a szintaxis. Ezt teljesen meg tudom érteni. Persze ez vezet olyan szörnyetegekhez, mint ez! :)
Ütővonal
Ok, tudom, a generikus típusok támogatására hivatkozni a Java esetében majdnem annyira nagy genyaság, mintha a Go esetében tenném ezt. Betegeket csúfolni nem ér, de nem bírom megállni:
Persze a valódi generikusok sem hiányoznak soha senkinek. Sokkal jobbak a minden generikus típus mellé rakott IntArrayList, DoubleArrayList, stb. gyűjtemények, ha az ember egy kis teljesítményt is akar és absztraháló erőt is szeretne, nem csak izzasztani a GC-t a sok autoboxinggal. (Amúgy ez egy jó előadás, javaslom megnézni, de amikor ezt előhozta, felnyögtem, hogy a Java még itt tart.)
De nem csak a primitívek miatt szar a Java generics, hanem a type erasure miatt pl egy Pair<A,B>
-t sem lehet runtime type check nélküli equals()
-al megírni. Persze type token is kell hozzá, hogy azt meg tudd csinálni…
Szóval egy int
-re senki sem akarna toString()
-et hívni… Jobb az Integer.toString(1)
mint az 1.ToString()
. Egységesebb. Objektumorientáltabb. Jóra tanítja majd a gyereket. Szokja csak a duplagondolt, az életben szüksége lesz rá.
Ennyiből tényleg jobb a Java, mert az életre nevel!
ui.: Sajnos elragadtattam magam kissé, és utólag néhány megjegyzéssel, bővítéssel éltem. Ezért néhol a cikkben a megjegyzések kissé kizökkenthetik az olvasót a gondolatmenetem ívéből. Ha ezt történt volna, úgy elnézést kell kérnem.