Bevált gyakorlatok az „Effective Java (3rd Edition)” című könyvből
Részlet Joshua Bloch „Effective Java (3rd Edition) ” című könyvéből
1. Üres collectiont vagy tömböt adj vissza, ne nullt
Ha a metódus egy Setet ad vissza, üres eredménynél ne return null; legyen – helyette return Set.of();.
2. Fontold meg a statikus factory metódusokat a konstruktorok helyett
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
Nem mindig jobbak, de bizonyos felhasználási esetekben számos előnyt kínálnak:
- A statikus factory metódusok egyik előnye, hogy a konstruktorokkal ellentétben van nevük.
- Nem kell minden hívásnál új objektumot létrehozniuk.
- A visszatérési típusuk bármely altípusának objektumát visszaadhatják.
- A visszaadott objektum osztálya hívásról hívásra változhat a bemeneti paraméterek függvényében.
- A visszaadott objektum osztályának nem kell léteznie akkor, amikor a metódust tartalmazó osztályt megírják.
Hátrányok:
- Ha csak statikus factory metódusokat kínálunk, a fő korlát az, hogy a publikus vagy protected konstruktor nélküli osztályokból nem lehet leszármaztatni.
- A statikus factory metódusok második gyengesége, hogy a programozók nehezen találják meg őket.
3. Fontold meg a buildert, ha sok konstruktorparamétered van
A statikus factoryknak és a konstruktoroknak van egy közös korlátjuk: nem skálázódnak jól, ha sok az opcionális paraméter. Ez néhány projektet, amelyeken dolgoztam, nagyon keményen érintett.
4. A singleton tulajdonságot privát konstruktorral vagy enum típussal kényszerítsd ki
Mindent, aminek nincs szüksége több példányra, csak egyszer kellene példányosítani – akár @ApplicationScoped-del, akár egy másik singleton mintával.
5. A nem példányosíthatóságot privát konstruktorral kényszerítsd ki
Időnként olyan osztályt akarsz írni, amely csupán statikus metódusok és statikus mezők csoportja. Az ilyen osztályok rossz hírnévre tettek szert, mert egyesek visszaélnek velük, hogy ne kelljen objektumokban gondolkodniuk, de vannak jogos felhasználási területeik is.
private MyStaticClass() {
// private constructor to prevent instantiation
}
6. Kerüld a felesleges objektumok létrehozását
String s = new String("bikini"); // DON'T DO THIS!
Vagy:
// Hideously slow! Can you spot the object creation?
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
Megoldás: Ez a program helyes eredményt ad, de sokkal lassabb a kelleténél egy egykarakteres elírás miatt. A sum változó Longként van deklarálva long helyett, ami azt jelenti, hogy a program körülbelül 2^31 felesleges Long példányt hoz létre (nagyjából egyet minden alkalommal, amikor a long i hozzáadódik a Long sumhoz).
7. Kerüld a finalizereket és a cleanereket
A finalizerek kiszámíthatatlanok, gyakran veszélyesek, és általában szükségtelenek. Használatuk szabálytalan viselkedést, gyenge teljesítményt és hordozhatósági problémákat okozhat.
8. A try-with-resources jobb, mint a try-finally
// try-with-resources - the best way to close resources!
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
9. Fontold meg a Comparable implementálását
A Comparable implementálásával lehetővé teszed, hogy az osztályod együttműködjön az összes olyan generikus algoritmussal és collection-implementációval, amely erre az interfészre épül.
10. Minimalizáld az osztályok és tagok láthatóságát
A legfontosabb tényező, amely megkülönbözteti a jól megtervezett komponenst a rosszul megtervezettől, az, hogy a komponens milyen mértékben rejti el a belső adatait és egyéb implementációs részleteit más komponensek elől. Egy jól megtervezett komponens minden implementációs részletét elrejti, tisztán elválasztva az API-ját az implementációjától. A komponensek ezután csak az API-jukon keresztül kommunikálnak, és nem tudnak egymás belső működéséről. Ez a koncepció, amelyet információrejtésnek vagy egységbezárásnak neveznek, a szoftvertervezés egyik alapelve.
11. Publikus osztályokban hozzáférő metódusokat használj, ne publikus mezőket
Időnként kísértést érezhetsz arra, hogy degenerált osztályokat írj, amelyeknek nincs más céljuk, mint példánymezők csoportosítása:
// Degenerate classes like this should not be public!
class Point {
public double x;
public double y;
}
Mivel az ilyen osztályok adatmezőit közvetlenül érik el, ezek az osztályok nem nyújtják az egységbezárás előnyeit. Nem változtathatod meg a reprezentációt az API megváltoztatása nélkül, nem kényszeríthetsz ki invariánsokat, és nem hajthatsz végre kiegészítő műveletet, amikor egy mezőt elérnek. A keményvonalas objektumorientált programozók szerint az ilyen osztályok átkozottak, és mindig olyan osztályokra kell cserélni őket, amelyeknek privát mezőik és publikus hozzáférő metódusaik (getterek), illetve – módosítható osztályoknál – módosító metódusaik (setterek) vannak:
// Encapsulation of data by accessor methods and mutators
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() { return x; }
public double getY() { return y; }
public void setX(double x) { this.x = x; }
public void setY(double y) { this.y = y; }
}
A keményvonalasoknak a publikus osztályok esetében kétségtelenül igazuk van: ha egy osztály a csomagján kívülről is elérhető, adj hozzáférő metódusokat, hogy megőrizd a rugalmasságot az osztály belső reprezentációjának megváltoztatásához. Ha egy publikus osztály felfedi az adatmezőit, minden remény elvész a reprezentációja megváltoztatására, mert a kliensoldali kód messze szétszóródhat.
Ha viszont egy osztály package-private vagy privát beágyazott osztály, nincs eredendően semmi rossz abban, hogy felfedi az adatmezőit – feltéve, hogy azok megfelelően írják le az osztály által nyújtott absztrakciót.
12. Minimalizáld a módosíthatóságot
Egy immutable osztály egyszerűen olyan osztály, amelynek példányai nem módosíthatók. Az egyes példányokban tárolt összes információ az objektum teljes élettartama alatt rögzített, így soha semmilyen változás nem figyelhető meg. A Java platform könyvtárai sok immutable osztályt tartalmaznak, köztük a Stringet, a boxed primitív osztályokat, valamint a BigIntegert és a BigDecimalt. Ennek számos jó oka van: az immutable osztályokat könnyebb megtervezni, implementálni és használni, mint a módosíthatókat. Kevésbé hajlamosak a hibákra, és biztonságosabbak. Ahhoz, hogy egy osztály immutable legyen, kövesd ezt az öt szabályt:
- Ne adj olyan metódusokat, amelyek módosítják az objektum állapotát (ezeket mutátoroknak nevezik).
-
Gondoskodj róla, hogy az osztályt ne lehessen kiterjeszteni. Ez megakadályozza, hogy hanyag vagy rosszindulatú alosztályok veszélyeztessék az osztály immutable viselkedését azzal, hogy úgy viselkednek, mintha az objektum
állapota megváltozott volna. A leszármaztatást általában például úgy akadályozzuk meg, hogy az osztályt
final-lé tesszük. - Minden mező legyen final. Ez világosan, a rendszer által kikényszerített módon fejezi ki a szándékodat. Emellett szükséges a helyes működés biztosításához is, ha egy újonnan létrehozott példányra mutató referenciát szinkronizáció nélkül adunk át egyik szálból a másikba, ahogy azt a memóriamodell leírja.
- Minden mező legyen privát. Ez megakadályozza, hogy a kliensek hozzáférjenek a mezők által hivatkozott módosítható objektumokhoz, és közvetlenül módosítsák őket. Bár technikailag megengedett, hogy egy immutable osztálynak primitív értékeket vagy immutable objektumokra mutató referenciákat tartalmazó public final mezői legyenek, ez nem ajánlott, mert kizárja a belső reprezentáció megváltoztatását egy későbbi kiadásban.
-
Biztosíts kizárólagos hozzáférést minden módosítható komponenshez. Ha az osztályodnak vannak módosítható objektumokra hivatkozó mezői, gondoskodj róla, hogy az osztály kliensei ne szerezhessenek
referenciát ezekre az objektumokra. Soha ne inicializálj ilyen mezőt a kliens által adott objektumreferenciával, és ne add vissza a mezőt egy hozzáférő metódusból. Készíts defenzív másolatokat a konstruktorokban, a hozzáférő metódusokban és a
readObjectmetódusokban.
13. Részesítsd előnyben a kompozíciót az öröklődéssel szemben
Az öröklődés hatékony módja a kód újrahasznosításának, de nem mindig ez a legjobb eszköz a feladatra. Helytelenül használva törékeny szoftverhez vezet. Az öröklődés biztonságosan használható egy csomagon belül, ahol az alosztály és az ősosztály implementációja ugyanazon programozók kezében van. Ugyancsak biztonságos az öröklődés, ha kifejezetten kiterjesztésre tervezett és dokumentált osztályokat terjesztesz ki. A közönséges konkrét osztályokból való öröklés csomaghatárokon át viszont veszélyes.
A metódushívással ellentétben az öröklődés megsérti az egységbezárást. Azt gondolhatod, hogy biztonságos kiterjeszteni egy osztályt, ha csak új metódusokat adsz hozzá, és nem írod felül a meglévő metódusokat. Bár ez a fajta kiterjesztés sokkal biztonságosabb, nem kockázatmentes. Ha az ősosztály egy későbbi kiadásban új metódust kap, és neked az a balszerencséd, hogy az alosztálynak már adtál egy ugyanilyen szignatúrájú, de eltérő visszatérési típusú metódust, az alosztályod többé nem fog lefordulni. Ha az alosztálynak az új ősosztálybeli metódussal azonos szignatúrájú és visszatérési típusú metódust adtál, akkor most felülírod azt, így ki vagy téve a korábban leírt problémáknak. Ráadásul kétséges, hogy a metódusod teljesíti-e az új ősosztálybeli metódus szerződését, hiszen az a szerződés még nem létezett, amikor az alosztály metódusát megírtad.
Szerencsére van mód az összes fent leírt probléma elkerülésére. Ahelyett, hogy kiterjesztenél egy meglévő osztályt, adj az új osztályodnak egy privát mezőt, amely a meglévő osztály egy példányára hivatkozik. Ezt a tervezést kompozíciónak nevezzük, mert a meglévő osztály az új osztály egyik komponensévé válik. Az új osztály minden példánymetódusa meghívja a meglévő osztály tartalmazott példányának megfelelő metódusát, és visszaadja az eredményt. Ezt továbbításnak (forwarding) nevezzük, az új osztály metódusait pedig továbbító metódusoknak. Az eredményül kapott osztály sziklaszilárd lesz, és nem függ a meglévő osztály implementációs részleteitől. Még ha új metódusokat adnak is a meglévő osztályhoz, az sem lesz hatással az új osztályra.
14. Részesítsd előnyben az interfészeket az absztrakt osztályokkal szemben
A Javának két mechanizmusa van olyan típus definiálására, amely több implementációt is megenged: az interfészek és az absztrakt osztályok. Amióta a Java 8 bevezette az interfészek default metódusait, mindkét mechanizmus lehetővé teszi, hogy bizonyos példánymetódusokhoz implementációt adj.
Fontos különbség, hogy egy absztrakt osztály által definiált típus implementálásához az osztálynak az absztrakt osztály alosztályának kell lennie. Mivel a Java csak egyszeres öröklődést enged meg, az absztrakt osztályok e korlátozása erősen behatárolja típusdefinícióként való használatukat. Bármely osztály, amely definiálja az összes szükséges metódust és betartja az általános szerződést, implementálhat egy interfészt, függetlenül attól, hol helyezkedik el az osztályhierarchiában. A meglévő osztályok könnyen utólag alkalmassá tehetők egy új interfész implementálására.
- Az interfészek ideálisak mixinek definiálására.
- Az interfészek lehetővé teszik nem hierarchikus típusrendszerek felépítését.
- Az interfészek biztonságos, hatékony funkcióbővítést tesznek lehetővé a wrapper osztály idiómán keresztül. Ha absztrakt osztályokkal definiálsz típusokat, annak a programozónak, aki funkciót akar hozzáadni, nem hagysz más lehetőséget, mint az öröklődést.
15. Generikusok: ne használj nyers (raw) típusokat
A hibát csak futásidőben fedezed fel, jóval azután, hogy bekövetkezett, és olyan kódban, amely messze lehet a hibát tartalmazó kódtól. Amint meglátod a ClassCastExceptiont, végig kell keresned a kódbázist azt a metódushívást keresve, amely az érmét a bélyeggyűjteménybe tette. A fordító nem tud segíteni.
16. Generikusok: szüntesd meg az unchecked figyelmeztetéseket
Ha generikusokkal programozol, sok fordítói figyelmeztetést fogsz látni:
- unchecked cast figyelmeztetések
- unchecked method invocation figyelmeztetések
- unchecked parameterized vararg type figyelmeztetések
- unchecked conversion figyelmeztetések.
Minél több tapasztalatot szerzel a generikusokkal, annál kevesebb figyelmeztetést kapsz, de ne várd, hogy az újonnan írt kód tisztán lefordul. Sok unchecked figyelmeztetést könnyű megszüntetni. Tegyük fel például, hogy véletlenül ezt a deklarációt írod:
Set<Lark> exaltation = new HashSet();
Változtasd meg erre:
Set<Lark> exaltation = new HashSet<>();
Néhány figyelmeztetést sokkal nehezebb lesz megszüntetni. Ha olyan figyelmeztetéseket kapsz, amelyek némi gondolkodást igényelnek, tarts ki! Szüntess meg minden unchecked figyelmeztetést, amelyet csak tudsz.
Ha minden figyelmeztetést megszüntetsz, biztos lehetsz benne, hogy a kódod típusbiztos, ami nagyon jó dolog. Ez azt jelenti, hogy futásidőben nem kapsz ClassCastExceptiont, és növeli a bizalmadat abban,
hogy a programod úgy fog viselkedni, ahogy szeretted volna. Ha egy figyelmeztetést nem tudsz megszüntetni, de be tudod bizonyítani, hogy a figyelmeztetést kiváltó kód típusbiztos, akkor (és csak akkor) nyomd el
a figyelmeztetést egy @SuppressWarnings("unchecked") annotációval. Ha anélkül nyomod el a figyelmeztetéseket, hogy előbb bebizonyítanád a kód típusbiztonságát, hamis biztonságérzetet
keltesz magadban.
A kód lefordulhat egyetlen figyelmeztetés nélkül is, de futásidőben ettől még dobhat ClassCastExceptiont.
Minden alkalommal, amikor @SuppressWarnings("unchecked") annotációt használsz, írj mellé egy kommentet arról, miért biztonságos ez.
17. Generikusok: amikor van értelmük
Jaj – egy osztály, amelyre nagyon ráférnének a generikusok:
Az ilyesminek legkésőbb a merge requestnél fenn kell akadnia!
18. Generikusok: így ne
Bocs, ehhez nem kell szó. Ezt kérlek, SOHA ne mergeld:
public abstract class JpaLearningCardBaseContentService<LC extends LearningCardBaseContent<L, LC, AC>, J extends JpaLearningCardBaseContent<L, LC, AC>, L extends
LearningCardBase<LC, A>, A extends AnswerOptionBase<L, AC>, AC extends AnswerOptionBaseContent<LC, AC, A>, AS extends AnswerOptionBaseService<A, AC, L, LC>, ACS extends
AnswerOptionBaseContentService<AC, A, L, LC>, LS extends LearningCardBaseService<L>>
extends JpaLightPersistenceService<LC, J, Long> implements LearningCardBaseContentService<LC, L, A, AC> {
// ...
}
Egy puszta osztálydeklaráció megértése soha nem tarthat 5 percig.
19. A listák jobbak a tömböknél
A tömbök két fontos dologban különböznek a generikus típusoktól. Először is a tömbök kovariánsak. Ez az ijesztően hangzó szó egyszerűen azt jelenti, hogy ha a Sub a Super altípusa, akkor
a Sub[] tömbtípus a Super[] tömbtípus altípusa. A generikusok ezzel szemben invariánsak: bármely két különböző Type1 és Type2 típus esetén
a List<Type1> se nem altípusa, se nem őstípusa a List<Type2>-nek.
20. A lambdák jobbak a névtelen osztályoknál
// Anonymous class instance as a function object - obsolete!
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
Ehelyett csináld így:
// Lambda expression as function object (replaces anonymous class)
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
21. Minimalizáld a lokális változók hatókörét
A lokális változó hatókörének minimalizálására a leghatékonyabb technika az, ha ott deklarálod, ahol először használod...
22. A for-each ciklusok jobbak a hagyományos for ciklusoknál
// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
... // Do something with e
}
23. Ismerd és használd a könyvtárakat
Tegyük fel, hogy véletlen egész számokat szeretnél generálni nulla és egy felső korlát között. Ezzel a gyakori feladattal szembesülve sok programozó írna egy kis metódust, amely nagyjából így néz ki:
// Common but deeply flawed!
static Random rnd = new Random();
static int random(int n) {
return Math.abs(rnd.nextInt()) % n;
}
Ez a metódus jól nézhet ki, de három hibája van. Az első, hogy ha n kettő egy kis hatványa, a véletlenszámok sorozata elég rövid idő után ismétlődni kezd. A második hiba, hogy ha n nem kettő hatványa, egyes számokat átlagosan gyakrabban ad vissza, mint másokat. Ha n nagy, ez a hatás elég erős lehet.
A random metódus harmadik hibája, hogy ritkán katasztrofálisan elromolhat, és a megadott tartományon kívüli számot adhat vissza. Ez azért van, mert a
metódus a Math.abs meghívásával próbálja az rnd.nextInt() által visszaadott értéket nemnegatív int-té alakítani. Ha a nextInt()
Integer.MIN_VALUE-t ad vissza, akkor a Math.abs is Integer.MIN_VALUE-t ad vissza, és a maradékos osztás operátora (%) negatív számot ad, feltéve, hogy
n nem kettő hatványa. Ez szinte biztosan a program hibás működéséhez vezet, és a hibát nehéz lehet reprodukálni.
24. Kerüld a floatot és a double-t, ha pontos eredményre van szükség
Pénzösszegekhez BigDecimal-t használj. A float és a double kerekítési hibákat okoz.
Tegyük fel például, hogy 1,03 dollár van a zsebedben, és elköltesz 42 centet. Mennyi pénzed maradt? Íme egy naiv programrészlet, amely megpróbál válaszolni erre a kérdésre:
System.out.println(1.03 - 0.42);
Sajnos ez 0.6100000000000001-et ír ki. És ez nem egyedi eset. Tegyük fel, hogy van egy dollár a zsebedben, és veszel kilenc alátétet darabonként tíz centért. Mennyi
visszajárót kapsz?
System.out.println(1.00 - 9 * 0.10);
E programrészlet szerint $0.09999999999999998-t kapsz vissza.
Azt gondolhatnád, hogy a probléma egyszerűen megoldható az eredmények kiírás előtti kerekítésével, de ez sajnos nem mindig működik. Tegyük fel például, hogy van egy dollár a zsebedben, és meglátsz egy polcot, rajta egy sor finom cukorkával 10, 20, 30 centért és így tovább, egészen egy dollárig. Mindegyikből veszel egyet, a 10 centessel kezdve, amíg már nem futja a polcon következő cukorkára.
25. A primitív típusok jobbak a boxed primitíveknél
// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
26. Az objektumokra az interfészükön keresztül hivatkozz
Jó példa:
Set<Son> sonSet = new LinkedHashSet<>();
Rossz példa:
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
27. Az interfészek jobbak a reflectionnél
Ha mégsem:
- Elveszíted a fordítási idejű típusellenőrzés minden előnyét, beleértve a kivételek ellenőrzését is. Ha egy program reflectionnel próbál meghívni egy nem létező vagy elérhetetlen metódust, futásidőben el fog bukni, hacsak nem tettél különleges óvintézkedéseket.
- A reflection alapú hozzáféréshez szükséges kód nehézkes és bőbeszédű. Fárasztó megírni és nehéz olvasni.
- A teljesítmény romlik. A reflection alapú metódushívás sokkal lassabb a normál metódushívásnál.
28. Tartsd be az általánosan elfogadott elnevezési konvenciókat
A Java platformnak jól bevált elnevezési konvenciói vannak, amelyek közül sokat a The Java Language Specification [JLS, 6.1] tartalmaz.
29. A kivételeket csak kivételes helyzetekre használd
// Horrible abuse of exceptions. Don't ever do this!
try {
int i = 0;
while (true) {
range[i++].climb();
}
} catch (ArrayIndexOutOfBoundsException e) {
// catch it
}
Mit csinál ez a kód? Ránézésre egyáltalán nem nyilvánvaló, és ez már önmagában elég ok arra, hogy ne használd. Kiderül, hogy egy borzasztóan rosszul kitalált idióma egy tömb elemeinek
bejárására. A végtelen ciklus úgy ér véget, hogy dob, elkap és figyelmen kívül hagy egy ArrayIndexOutOfBoundsException-t, amikor
megpróbálja elérni a tömb határain kívül eső első elemet. Elvileg egyenértékű a tömbbejárás szabványos idiómájával, amelyet
bármely Java-programozó azonnal felismer:
for (Mountain m : range) {
m.climb();
}
A kivételeket, ahogy a nevük is mutatja, csak kivételes helyzetekben szabad használni; a szokásos vezérlési folyamatra soha.
30. Helyreállítható helyzetekre checked kivételeket, programozási hibákra runtime kivételeket használj
Checked kivételeket olyan helyzetekre használj, amelyekből ésszerűen elvárható, hogy a hívó helyreálljon. Azzal, hogy checked kivétellel szembesíti a felhasználót, az API tervezője megköveteli, hogy kezelje a helyzetet. A felhasználó figyelmen kívül hagyhatja ezt az elvárást, ha elkapja és figyelmen kívül hagyja a kivételt, de ez általában rossz ötlet.
Runtime kivételekkel jelezd a programozási hibákat. A runtime kivételek túlnyomó többsége előfeltétel megsértését jelzi. Az előfeltétel megsértése egyszerűen azt jelenti, hogy
egy API kliense nem tartotta be az API specifikációjában rögzített szerződést. Például a tömbhozzáférés szerződése előírja, hogy a tömbindexnek nulla és a tömb
hossza mínusz egy között kell lennie, a határokat is beleértve. Az ArrayIndexOutOfBoundsException azt jelzi, hogy ezt az előfeltételt megsértették.
31. Kerüld a checked kivételek szükségtelen használatát
Összefoglalva: mértékkel használva a checked kivételek növelhetik a programok megbízhatóságát; túlzásba víve kínszenvedéssé teszik az API-k használatát.
Pair<Boolean, LearningCardGroup> answerLearningCard(@NotNull LearningCardGroup learningCardGroup,
LearningCardAssignment learningCardAssignment,
@NotNull Collection<AnswerOption> answers)
throws InvalidTrainingStateException, PersistenceException, LearningCardGroupAlreadyCompletedException,
ConsecutiveCorrectCountExceededException,
PreviousLearningCardGroupUncompletedException, LearningCardGroupCooldownException,
LearningCardGroupHasUncompletedLearningCardsException, InvalidParticipantException, TrainingNotSignedException; // but why ???
// this is a mistake seen a lot with junior devs
// exceptions are exceptionally slow and should be avoided for mere validation
// the performance gets even worse if a stacktrace is printed, since logging and printing is one of the slowest possible actions
32. Részesítsd előnyben a szabványos kivételeket
A szakértő programozókat a kevésbé tapasztaltaktól megkülönböztető egyik tulajdonság, hogy a szakértők a kód magas fokú újrahasznosítására törekszenek, és általában el is érik.
A kivételek sem kivételek az alól a szabály alól, hogy a kód újrahasznosítása jó dolog. A Java-könyvtárak olyan kivételkészletet kínálnak, amely a legtöbb API kivételdobási igényeinek nagy részét lefedi. A szabványos kivételek újrahasznosításának több előnye is van. Ezek közül a legfontosabb, hogy az API-d könnyebben tanulható és használható lesz, mert illeszkedik a programozók által már ismert, bevett konvenciókhoz. Szorosan a második, hogy az API-dat használó programok könnyebben olvashatók, mert nincsenek telezsúfolva ismeretlen kivételekkel. Végül (és a legkevésbé fontosként) a kevesebb kivételosztály kisebb memóriaigényt és kevesebb osztálybetöltési időt jelent.
A leggyakrabban újrahasznosított kivételtípus az IllegalArgumentException. Általában ezt a kivételt kell dobni, ha a hívó nem megfelelő értékű argumentumot ad át. Például
ezt a kivételt kellene dobni, ha a hívó negatív számot adna át egy olyan paraméterben, amely azt adja meg, hányszor kell megismételni egy műveletet. Egy másik gyakran újrahasznosított
kivétel az IllegalStateException. Általában ezt a kivételt kell dobni, ha a hívás a fogadó objektum állapota miatt szabálytalan. Például ezt
a kivételt kellene dobni, ha a hívó megpróbálna használni egy objektumot, mielőtt azt megfelelően inicializálták volna.
Vitathatóan minden hibás metódushívás szabálytalan argumentumra vagy állapotra vezethető vissza, de bizonyos fajta szabálytalan argumentumokra és állapotokra szabványosan más kivételeket használnak. Ha a hívó
null-t ad át egy olyan paraméterben, amelyben a null érték tiltott, a konvenció szerint NullPointerException-t kell dobni, nem IllegalArgumentException-t.
Hasonlóképpen, ha a hívó tartományon kívüli értéket ad át egy sorozatbeli indexet jelölő paraméterben, IndexOutOfBoundsException-t kell dobni, nem IllegalArgumentException-t.
Ne használd újra közvetlenül az Exception, RuntimeException, Throwable vagy Error osztályt. Kezeld ezeket az osztályokat úgy, mintha absztraktak lennének. A RuntimeException azért használható, ha
egy checked kivételt runtime kivétellé akarsz alakítani, és tényleg tudod, mit csinálsz.
33. Ne hagyd figyelmen kívül a kivételeket
// Empty catch block ignores exception - Highly suspect!
try {
...
} catch (SomeException e) {
}
Az üres catch blokk meghiúsítja a kivételek célját, amely az, hogy rákényszerítsen a kivételes helyzetek kezelésére. Egy kivétel figyelmen kívül hagyása olyan, mintha figyelmen kívül hagynál egy tűzjelzőt – és ki is kapcsolnád, hogy senki más ne nézhesse meg, valóban ég-e valahol.