Java

Kezdeti lépések

Filozófia

A technika részletek előtt némi filozófiával kezdem: mi is a Java. Őszintén bevallom, hogy kezdetben az esetemben "nem működött a kémia": nem értettem, miért van szükség még egy n+1-edik programozási nyelvre, mivel tud többet a Java, mint mondjuk a C++, vagy akár a Pascal. Ugyanakkor szembeötlő volt a hátránya: az akkori 16 bites 386-os számítógépemen, 6-os DOS-szal, 3.1-es Windows-zal nem működött, új számítógép és új operációs rendszer kellett tehát, ami jelentősen megterhelte a családi költségvetést. Majd jöttek a gyermekbetegségek: sehogy sem akart úgy működni, ahogy a nagykönyvben meg van írva, lassú volt, szóval még egyáltalán nem volt kiforrott, mondjuk a C++-szal ellentétben. És sajnos a környezetemben nem voltak olyanok, akik rendesen megválaszolták volna a kétségeimet. Pl. arra a kérdésre, hogy miért nem használható DOS-szal a Java, azt a választ kaptam, hogy a fájl kiterjesztése .java, míg a DOS csak 3 karakteres kiterjesztést tesz lehetővé. Ami persze nem válaszolja meg azt a kérdést, hogy miért ne lehetne mondjuk .JAV a kiterjesztés…

Ennek elfogadásához a Java mélyebb filozófiai megértése szükséges. Kezdjük a C++-szal! Egy fordító a forráskódot fordítja le gépi kódra, amit a processzor végrehajt. Noha elég sok processzor létezik, nagyjából hasonló, egyszerű műveleteket tudnak végrehajtani: bitek mozgatását regiszterek és a memória között, egyszerű matematikai műveleteket stb. Magát a fordítót tehát el kell készíteni minden architektúrára, a nyelvet viszont úgy kellett megalkotni, hogy alkalmas legyen arra, hogy minden architektúrára elkészülhessen a fordító. Jobban belegondolva, ez egy nagyon komoly megkötés! Lehet, hogy az egyik a processzor csak 16 bites egész számokat képes kezelni, és a 32 bites lebegőpontos ábrázolást szimulálni kell, míg a másik esetén ez natív módon működik, de olyan, ma már hétköznapi fogalmakat, mint mondjuk a többszálúság, vagy a grafikus felhasználói felület, processzor szinten nem létezik. A konzol is valójában határeset: a processzor ugyan nem ismeri ezt a fogalmat, viszont azt feltételezhetjük, hogy a program valamilyen operációs rendszeren fut, és a legtöbb esetben értelmezhető az a fogalom, hogy standard input és standard output, ami legtöbb esetben a konzolt jelenti.

A Java nagy újítása ezzel szemben az, hogy megalkotott egy köztes réteget, a Java virtuális gépet, amelynek az utasításkészletében már olyan fogalmak is szerepelnek, mint mondjuk a többszálúság vagy a grafikus felület. Ez viszont megkötéssel is jár: csak olyan operációs rendszeren lehet implementálni a Java virtuális gépet, amely ezeket a fogalmakat ismeri. Többszálúság (olyan értelemben, ahogy azt a Java megkívánta) nem létezett Windows 3.1-ben, ill. általában a 16 bites operációs rendszereken, így nagyon komoly megkötést jelentett volna ezeknek a támogatása. Egyébként is, általában igaz, hogy a technikai újítások fájdalommal járnak: a korábbi technológia használhatatlanná válik. Ugyanakkor érdemes meghozni az áldozatot: képzeljük csak el, hogy hogyan néznének ki az autópályák, ha mondjuk a lovaskocsi forgalom is megengedett lenne! Azóta viszont tovább fejlődött a világ, és az akkori, előre mutató döntések mára komoly megkötéssé váltak. Az újabb technológiák ismételten elvetik a régiekkel való kompatibilitást, cserében megszabadulnak a bizonyos megkötésektől, majd a világ tovább fejlődik, és idővel azok is elavulttá válnak. És így tovább, és így tovább…

Adott volt tehát egy új megközelítés, mely csak a legújabb hardveren működött, és mivel megjelent egy absztrakciós réteg a fizikai processzor és a programnyelv között, végeredményben az utasítások (a processzor szintjén) interpretáltak voltak, ami iszonyat lassúvá tette a futást. Ráadásul akkor új technológiáról beszélünk, annak minden gyermekbetegségével.

A kezdeti nehézségeket azonban bőven kompenzálják az előnyök. Leginkább a C++-szal érdemes összehasonlítani.

  • Széles körben elterjedt. Maga a bájt kód futtatható számos rendszeren, nem kell lefordítani mindenhova, mint a C++ programot (ami egyébként a legritkább esetben működik elsőre).
  • Az alaprendszernek része az, ami a C++-ban külső, platformfüggő könyvtárral volt elérhető. Néhány példa: többszálúság, GUI, hálózat elérés, fájlkezelés, collection (a Standard Template Library egész későn lett része a C++-nak, sokáig erre a célra is külső könyvtárt kellett használni; a collection kezdettől része a Java-nak) stb. Sőt, még nyelvi szinten is támogat bizonyos olyan dolgokat, amelyek a C++-ban csak külső könyvtárral érhetőek el, gondoljunk pl a synchronized kulcsszóra.
  • A Java külső könyvtárakkal való ellátottsága kiváló, sok közülük szinte szabványnak tekinthető. Gondoljunk csak pl. Log4J-re, a junit-ra vagy a jdbc-re; szinte úgy használjuk ezeket, mintha az alaprendszer részei lennének, pedig nem azok. A C++-ban még azok sem voltak tekinthetőek szabványnak, amelyek a Java-ban az alaprendszer részei, hát még a speciálisabb dolgok!
  • A Java az egyik legnépszerűbb programozási nyelv már hosszú ideje. Igen keresett szakma.

Ugyanakkor vannak hátulütői is:

  • Nagyon sok lépést meg kell tenni, mire eljutunk az első programunk futásáig. Mondjuk egy Python-t letöltjük, feltelepítjük Next → Next → … → Finish-sel, elindítjuk, és máris kiadhatjuk a print("Hello, vilag") parancsot, ezzel szemben már a letöltés sem egyértelmű, a telepítés is összetettebb a fentinél, majd külön osztályt kell létrehozni szigorú szintaxissal, le kell fordítani és csak utána tudjuk futtatni.
  • Hosszú a betanulási görbe. Ahhoz, hogy valaki elmondhassa magáról, tud Java-ban programozni, meg kell tanulnia a nyelvi elemek mellett a legfontosabb belső és külső könyvtárakat is. Ellenben pl. JavaScript-ben már lehet valaki produktív akkor, ha megtanulja az alapokat (ami amúgy egyszerűbb mint a Java), és egy könyvtárat, és azt használja.
  • A nyelv megalkotásánál utólag néhány döntés kissé elhibázottnak tekinthető, viszont a kompatibilitási kényszer miatt ezzel együtt kell élnünk.

A Java egyébként az Indonéziában található Jáva szigetről kapta a nevét, az ott termő finom kávéra utalva (a Java logója egy kávéscsésze). Ennélfogva a szokásos magyar kiejtése magyarul jáva, angolul dzsava (ami angol szövegkörnyezetben szinte megtévesztésig hasonlít arra, ahogy a nem magyar anyanyelvűek kiejtik azt, hogy Csaba).

A Java programozási nyelvről nagyon sok könyv született, többnyire ezer oldalas nagyságrendű oldalszámmal, és azok is inkább bevezető jellegűeknek tekinthetőek. Ez az oldal tehát még a bevezetőnek is épp, hogy a felszínét karcolja. Lássuk a legfontosabbakat!

Ezt az oldalt érdemes a programozás oldallal együtt tanulmányozni, mivel az általános tudnivalók ott találhatóak.

Telepítés

A Java esetén megkülönböztetünk két fogalmat:

  • JRE (Java Runtime Environment): a Java programok futtatásához szükséges. Erre lénygében mindenkinek szüksége van, mert a programok nagy része Java-ban készül, és csak ezzel tudjuk futtatni. Ez egyre több helyre alapból feltelepül. A legfontosabb komponense a Java futtató, melynek meglétét az alábbi paranccsal tudjuk ellenőrizni: java -version.
  • JDK (Java Development Kit): ez a Java fejlesztőeszköz, melyre a Java programozóknak van szükségük. A legfontosabb a Java fordító, melynek meglétét a következőképpen tudjuk ellenőrizni: javac -version.

A Java-t innen tudjuk letölteni: https://www.oracle.com/technetwork/java/javase/downloads/index.html. Ne ijedjünk meg a túl sok verziótól, válasszunk egy fő verziót, azon belül a legfrissebb alverziót, a megfelelő operációs rendszerhez tartozó, megfelelő bitszámú verziót (i586 végű a 32 bites és x64 a 64 bites), célszerűen .exe kiterjesztésűt, fogadjuk el a felhasználói feltételeket (rádiógomb felül), majd töltsük le és telepítsük fel. A telepítő sajnos nem állítja be a szükséges környezeti változókat, azt nekünk kell beállítanunk:

  • JAVA_HOME: a JDK telepítés gyökere (pl. c:\Program Files\Java\jdk-11.0.2).
  • PATH: egészítsük ki a JDK-n belüli bin könyvtárral, pl. c:\Program Files\Java\jdk-11.0.2\bin.

Helló, világ!

A "Helló, világ!" programoknak hívjuk azokat a programokat, amelyek elvégzik a legegyszerűbb értelmes feladatot az adott rendszeren. Ez a legtöbb esetben azt jelenti, hogy kiírja a standard kimenetre azt, hogy "Hello, world!". Persze nem minden esetben jelenti szó szerint ezt, pl. az Arduino esetén a LED villogtatása számít a Helló, világ!-nak, míg a Big Data területen a szószámolás.

Maradjunk most a Java-nál! Ezen a ponton elvileg van egy működő Java rendszerünk. Első körben vegyünk egy tetszőleges szövegszerkesztőt (én személy szerint a NotePad++t használom, de tényleg bármit használhatunk, ami képes formázatlan szövegfájlt létrehozni), hozzunk létre egy HelloWorld.java nevű fájlt (célszerűen egy külön könyvtárba, de ez sem kötelező), majd másoljuk be az alábbi programot:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

Nyissunk meg egy parancssor értelmezőt, angolul command promptot az adott könyvtárban. Windows operációs rendszer alatt ezt a cmd parancs segítségével tudjuk megtenni. Első lépésben le kell fordítani a fenti forrásfájlt, amit az alábbi paranccsal tudunk megtenni:

javac HelloWorld.java

Elég barátságtalan amúgy, mert ha sikeres, nem ír ki semmit. Viszont megjelenik a könyvtárban egy HelloWorld.class nevű bináris fájl, ez a fordítás eredménye. Ez tartalmazza azokat az utasításokat, amelyeket a processzor ugyan nem tud közvetlenül értelmezni, a Java Virtuális Gép viszont már igen. A következő paranccsal tudjuk lefuttatni:

java HelloWorld

Figyeljük meg azt, hogy noha a bináris fájl kiterjesztése az, hogy .class, ezt nem kell odaírni.

Ha mindent jól csináltunk, akkor megjelenik a konzolon a Hello, world! felirat.

Fejlesztő környezet

Elméletben a fent megadott módon is lehet fejleszteni: a forrásokat elkészítjük egy akármilyen szövegszerkesztővel, majd futtatjuk. Idővel ugyan meg kell tanulunk egyre több kapcsolót, ill. egy-két újabb programot (ami a JDK része), de elméletben semmi sem akadályozhat meg abban, hogy akár a legbonyolultabb programokat is ily módon elkészítsük.

A valóságban azonban elég gyorsan elérjük azt a szintet, amikor kényelmetlen felsorolni az összes forrásfájlt, vagy jó lenne, ha a szerkesztő jelezné a legdurvább hibákat, valamint valamelyest segítene a fejlesztésben. Ehhez célszerű valamilyen integrált fejlesztőeszközt használnunk.

Valamint, noha elvileg bármit meg tudunk valósítani, nagyon sok olyan általános célú programkönyvtár van, amit fel tudunk mi magunk is használni. És ezeket ugyan le tudjuk tölteni az internetről "kézzel", és megfelelő kapcsolókkal hozzá tudjuk szerkeszteni a mi programunkhoz (amit egyébként fordítás és futás során is meg kell tennünk), ez két-három könyvtáron túl eléggé kényelmetlenné válik. Ezekhez alkották meg az ún. build rendszereket.

Ezen a ponton máris a fejlesztőeszközök végtelennek tűnő területére tévedtünk, emiatt nem lesz szó erről túl részletesen, de most első körben telepítsünk fel két dolgot:

  • Maven: töltsük le a https://maven.apache.org/ oldalról, csomagoljuk ki, majd a bin könyvtárát adjuk hozzá a PATH környezeti változóhoz. Ellenőrizzük a következő paranccsal: mvn -version
  • Eclipse: töltsük le a https://www.eclipse.org/ oldalról és telepítsük fel (vagy csomagoljuk ki), majd indítsuk el. (Az Eclipse-nek sajnos van pár "rigolyája". Ebben a projektben még együtt tudunk élni vele, de hosszabb távon érdemes megfelelően bekonfigurálni, ahogy az a fejlesztőeszközök oldalon le van írva.)

A példában létrehozunk Eclipse-ben egy Maven projektet. A példa egy összeadó lesz: egy olyan függvényt írunk, ami összead két számot. Ehhez unit tesztet is készítünk. Egyelőre nem feltétlenül kell megérteni minden lépést. A következő lépéseket hajtsuk végre.

  • Az Eclipse-ben készítsünk egy új Maven projektet: File → New → Maven project
  • Adjunk meg egy munkaterületet (workspace), ami praktikusan egy külön, erre a célra létrehozott könyvár.
  • Egyszerű projektet hozzunk létre: Create a simple project (skip archetype selection), az alapértelmezett munkaterületen: Use default Workspace location.
  • Kattintsunk a Next gombra
  • A Configure project oldalon az alábbiakat adjuk meg:
    • Group Id: ez a magunkra, a cégre vonatkozik, ahol dolgozunk, ill. magára a tágabb értelembe vett projektre; pl. com.mycompany.myproject.
    • Artifact Id: ez magának a projektnek a neve, pl. adder.
    • Version: legyen 1.0
    • Minden mást hagyunk alapértelmezésen, és kattintsunk a Finish gombra.
  • A létrejött adder projektet nyissuk meg, kattintsunk jobb egérgombbal az src/main/java-n, majd New → Package. Name: adder (kisbetűvel), majd Finish.
  • Az így létrejött adder csomagon jobb kattintás → New → Class. A név legyen Adder (nagybetűvel), minden más maradjon alapértelmezett, majd Finish.
  • Az Adder.java tartalma legyen a következő:
package adder;
 
public class Adder {
    public static int add(int a, int b) {
        int result = a + b;
        return result;
    }
}
  • Hasonlóan az Adder osztályhoz, hozzunk létre egy Main osztályt is, az Adder mellett az alábbi tartalommal:
package adder;
 
public class Main {
    public static void main(String[]args) {
        System.out.println(Adder.add(5, 3));
    }
}
  • Indítsuk el, pl. a következőképpen: Run → Run As → Java Application. Ha mindent jól csináltunk, megjelenik egy 5-ös.
  • Hozzunk létre unit testet! Ehhez hozzá kell adnunk egy megfelelő külső könyvtárat. Módosítsuk a pom.xml fájlt úgy, hogy a következőképpen nézzen ki:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mycompany.myproject</groupId>
    <artifactId>adder</artifactId>
    <version>1.0</version>
 
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>adder.Main</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • Az src/test/java könyvtáron belül, a fent megadott módon, hozzunk létre egy adder nevű csomagot, azon belül pedig egy AdderTest nevű osztályt. Ez utóbbinak a tartalma legyen a következő:
package adder;
 
import static org.junit.Assert.assertEquals;
 
import org.junit.Test;
 
public class AdderTest {
    @Test
    public void testAdd() {
        assertEquals(5, Adder.add(2, 3));
    }
}
  • Futtassuk le: Run → Run As → JUnit Test. Ha mindent jól csináltunk, akkor kizöldül az eredmény.
  • Most vessünk egy pillantást a létrejött könyvtár szerkezetre!
    • .settings/, valamint a gyökérben levő .classpath és .project fájlok - ezt az Eclipse használja, nem tekinthető a projekt részének.
    • src/ - a forrásfájlokat tartalmazza
      • main/ - a főprogram forrásai
        • java/ - a programozási nyelv
          • adder/ - a csomag neve
            • Adder.java
            • Main.java
        • resources/ - a főprogramhoz tartozó erőforrás fájlokat tartalmazza; jelenleg üres
      • test/ - a unit teszteléshez szükséges forrásokat találhatóak itt
        • java/adder/AdderTest.java - az összeadót tesztelő unit teszt
        • resources/ - a teszteléshez tartozó erőforrás fájlok; jelenleg üres
    • target/ - ide kerül a lefordított eredmény
    • pom.xml - a Maven leíró
  • Indítsunk egy egy parancssort a projekt főkönyvtárában, és adjuk ki a következő parancsot:
mvn clean install

El lefordítja, és a unit teszteket is lefuttatja. Ha minden jól ment, akkor BUILD SUCCESS üzenet jelenik meg az alján.

  • Az alábbi paranccsal tudjuk futtatni:
java -jar target\adder-1.0.jar

Ha mindent jól csináltunk, akkor megjelenik a 8.

Láthatjuk, hogy ha a Helló, világ! programnál egy nagyon picit is bonyolultabb dolgot szeretnénk megoldani, akkor igen sok lépést kell végrehajtanunk. Ebből is látható, hogy a Java kezdeti indulási görbéje igen hosszú. Viszont ne feledjük: kellő rutinnal mindez csak pár perc, míg a nagy Java projekteket akár fejlesztők százai fejlesztik éveken át, és a megfelelő fejlesztőeszközök valóban nagyban megkönnyítik a fejlesztést.

Anyagok

Mivel ez az egyik legnépszerűbb programozási nyelv, igen sok jó tananyag található a neten:

Nyelvi elemek

Alap szintaxis

A bevezetőben láthattunk egyszerű példaprogramokat, melyből a szintaxisra is következtethetünk. A legfontosabbak:

  • A Java-ban mindent osztályba kell tenni, tehát (pl. a C++-szal ellentétben) nincsenek globális változók, függvények.
  • Minden forrásfájl kötelezően egy publikus osztályt tartalmaz (public class MyClass {…}). A fájl neve kötelezően az osztálynév, .java kiterjesztéssel (a fenti példában tehát MyClass.java).
  • A források csomagokba szervezhetőek. A csomagnévnek kötelezően követnie kell a fájlszerkezetet.
  • A belépési pont a következő függvény: public static void main(String[] args) {…}. Arról, hogy ebből mi micsoda, később lesz szó.
  • Többsoros megjegyzés: /* … */. Egysoros megjegyzés: // …
  • Kulcsszók: abstract, assert, boolean, break, byte, case, catch, char, class, const, continue, default, do, double, else, enum, extends, final, finally, float, for, goto, if, implements, import, instanceof, int, interface, long, native, new, package, private, protected, public, return, short, static, strictfp, super, switch, synchronized, this, throw, throws, transient, try, void, volatile, while (megjegyzés: noha a goto kulcsszó, nem létezik a Java-ban feltétel nélküli ugró utasítás).
  • A Java 5-ben megjelentek az annotációk: ezek @-cal kezdődnek, és az utánuk álló nyelvi elemre vonatkoznak. Ezek nem a fordítónak, hanem a futtatónak szólnak. Pl. a bevezető példában a @Test a unit teszt futtatónak információ, hogy azt a függvényt kell unit tesztként kezelnie. (Az annotációs bevezetésének az elsődleges célja az Enterprise Java egyszerűsítése volt.)
  • A Java 8-ban megjelentek a lambda függvények, pl. (int a, int b) -> return a+b;

Változók

A változók a programozási nyelvek alap építőkövei: ezekben tudjuk eltárolni a később használatos adatokat. Számos helyen hozhatunk létre változót: pl. osztályokban (ezeket attribútumoknak nevezzük), függvényekben (ezeket lokális változóknak), a függvények fejléceiben (ezek a paraméterek). A változó szintaxisa: típus változónév;

A Java az ún. erősen típusos nyelvek közé tartozik: minden változónak meg kell adni a pontos típusát. Nincsenek előjel nélküli (unsigned) típusok, csak előjelesek. A Java alaptípusai az alábbiak:

  • byte: 1 bájtos előjeles szám.
  • short: 2 bájtos előjeles szám.
  • int: 4 bájtos előjeles szám (megjegyzés: az int minden esetben 4 bájt hosszú, ellentétben a C++-szal, melyben a processzor bitszáma dönti el).
  • long: 8 bájtos előjeles szám. A L posztfixet kell megadnunk ahhoz, hogy biztosan long típust kapjunk: long l = 1234567890L;.
  • float: 4 bájtos előjeles lebegőpontos szám (a legkevésbé ismert Java kulcsszó, a strictfp erre vonatkozik: platformfüggetlen lebegőpontos kalkulációt lehet vele kikényszeríteni). Az f posztfix jelenti ezt a típust: float f = 12.34f;
  • double: 8 bájtos előjeles lebegőpontos szám. A d posztfix jelenti ezt a típust: double f = 12.34d;
  • boolean: 1 bájtos logikai értéket vehet fel: true vagy false (a C++-szal ellentétben nem lehet számot logikai értékként felhasználni úgy, hogy a 0 jelenti a hamis, minden más az igaz értéket; a Java-ban kötelező boolean típust használni, vagy olyan kifejezést, ami boolean-ra értékelődik ki, pl. i != 0)
  • char: egy karakter ASCII kódja, ami 2 bájton tárolódik (tehát mivel nem 1 bájton, belefér az összes ékezetes karakter).

A számokat a következőképpen adhatjuk meg:

  • Tízes számrendszerben, mint a fenti példákban.
  • Binárisan, 0b prefix-szel pl. 0b01010101.
  • Hexadecimálisan, 0x prefix-szel, pl. 0x1E3F.
  • Oktálisan, prefix-szel, pl. 0123.
  • Exponenciális alakban, az e segítségével pl. 3e5 (melynek értéke 30000).

A típuskonverzió vagy jól működik, vagy rosszul, vagy egyáltalán nem, ráadásul ez verziónkét eltérő lehet, így ha egészen biztosak akarunk lenni, akkor explicit meg kell adni a típuskonverziót, a konvertálandó érték elé zárójelbe írva az új típust. Egy példán illusztrálom a problémát: (5/2)*2. Az eredmények minden kétséget kizáróan 5-nek kellene lennie, a valóságban viszont 4, mert az osztás egész osztás, maradékkal, az eredmény 2, és 2*2=4, ráadásul a fordító még csak nem is figyelmeztet erre. Megoldás: először az 5-öt kell lebegőpontosra konvertálni, majd a végeredményt vissza egésszé: (int)(((float)5/2)*2). Ebből a példából láthatóak az erős típusosság hátulütői: a nehezen felderíthető hibák, valamint a nehezen olvasható kód.

A szokásos műveleteket lehet végrehajtani a változókon, a szokásos precedenciával:

  • Műveletek számokkal:
    • Alapműveletek: +, -, *, /, % (maradékos osztás)
    • Növelés és csökkentés eggyel: ++, —
    • Bitenkénti műveletek: & (bitenkénti és), | (vagy), ^ (kizáró vagy), ~ (komplementer)
    • Bit tologató műveletek: « (pl. a « 2), », »> (ez utóbbi kettő között az a különbség, hogy az az első figyelembe veszi az előjelt, a második viszont mindig nullával tölti fel)
    • Értékadó utasítások: = (ez minden típusra működik), += (pl. a += b az a = a + b rövidítése), -=, *=, …
  • Logikai típust eredményező műveletek: == (egyenlő), != (nem egyenlő), <, <=, >, >=
  • Műveletek logikai típusokkal: && (és), || (vagy), ! (tagadás)

Fontos kérdés, hogy holy helyezkednek el a változók a memóriában. Alapvetően két memóriaterület létezik:

  • Stack (verem): ide kerülnek a lokális változók, melynek legfontosabb oka az, hogy a függvény hívási láncban minden függvény a saját változóit lássa. A memóriaterület felszabadítása akkor következik be, ha a függvény futása befejeződött. A verembe csak primitív változók kerülhetnek, valamint referenciák az objektumokra.
  • Heap (kupac): itt az objektumok helyezkednek el. Felfoghatjuk úgy, mint egyfajta lokális felhő, a pontos felépítése (ellentétben a veremmel) megvalósítás függő. Ha tehát létrejön egy objektum, akkor a hivatkozás rá a verembe kerül, míg maga az objektum a kupacba. Egy objektumra több hivatkozás is lehet (pl. egy függvény hívási láncban, ha paraméterül kerül átadásra, ill. az objektumok is hivatkozhatnak egymásra attribútumaik által). Az objektum által elfoglalt memóriaterületet "kézzel" felszabadítani nem lehetséges (ellentétben a C++-szal, ahol kötelező kézzel, azaz függvényhívással felszabadítani, különben előbb-utóbb elfogy a memória), ezt az ún. garbage collector (szemétgyűjtő) végzi, automatikusan. A Java virtuális gép dönti el, hogy mikor fusson le, de programból lehet javaslatot tenni a lefutásra (amit a JVM nem feltétlenül fogad el). Működése nem nyilvánvaló, pl. az elérhetetlen körkörös hivatkozásokat is fel kell tudnia oldani.

Létezik egy nehezen érthető kulcsszó: a volatile, amit tetszőleges változó elé írhatunk. Ez nem Java specifikus dolog, de itt is megtalálható. A lényege a következő. A fordítók nagyon sok belső optimalizációs végeznek. Vegyük pl. a következő programrészletet:

int a;
int b;
...
a = 2;
b = a + 3;

Elképzelhető, hogy a fordító "rájön" arra, hogy az utolsó utasításban a b változó értéke csakis 5 lehet, és a gyorsítás érdekében eleve azt az értéket írja be, ahelyett, hogy kiolvasná az a értékét, hozzáadna 3-at, és beleírná a b-be. Viszont tegyük fel, hogy az a változó értéke bármikor megváltozhat (pl. egy mikrokontroller esetén egy megszakítás megváltoztatja, vagy Java-ban egy másik szálon, mégpedig oly módon, hogy arra nem jön rá a fordító); ez esetben hibás eredményre vezet. A volatile kulcsszó azt jelenti, hogy bármikor megváltozhat a változó értéke (pl. akár a fenti két utolsó utasítás között is megváltozhat az a változó), tehát nem számíthat a fordító arra, hogy a pillanatnyi érték lesz benne, így ne hajtsa végre az adott változóra vonatkozó optimalizálásokat, hanem mindenképpen hajtsa végre a fent felsorolt műveleteket. Ezt a következőképpen tudjuk Java-ban megadni: a fenti példa első sorának a következőképpen kell kinéznie: volatile int a;.

Az idők folyamán megjelentek a Java-ban az ún. wrapper (csomagoló) típusok. Ezek olyan osztályok, amelyek egy primitív típust tartalmaznak, valamint hozzá tartozó műveleteket. Pl. az Integer osztály int-et tartalmaz, a Double egy double-t stb. Ez a következő előnyökkel jár:

  • Segítségével tudjuk jelezni azt, hogy egy adat hiányzik. Primitív típusok esetén ez gyakran problémához vezetett. Ott gyakori megoldás volt pl. az, hogy a 0 jelezte az adat hiányát, de ha az valós érték volt (pl. emberek száma), akkor egy olyan szám, ami nem valós (pl. -1). Viszont ez csökkentette a kód olvashatóságát, valamint hibákhoz is vezethetett (gondoljunk pl. arra, hogy összeadjuk az emberek számát, de elfelejtjük kezelni a -1-et). Itt viszont a null érték jelzi azt, hogy az adat hiányzik.
  • Számos műveletet tudunk segítségével végrehajtani, amit esetleg felül is tudunk definiálni.

Idővel megjelent az ún. autoboxing, ami azt jelenti, hogy a művelet végrehajtásakor a rendszer automatikusan átkonvertálja egyik típusból a másikba.

A Java nyelv támogatja a tömböket is. A tömb sorszámozása a 0-ról indul. A tömbök elemeit kapcsos zárójelekkel adhatjuk meg, míg egy-egy elemre szögletes zárójellel hivatkozhatunk:

int[] fibonacci = {0, 1, 1, 2, 3, 5, 8, 13};
System.out.println(fibonacci[3]);

Ez a kódrészlet 2-t ír ki.

Egy tömb elemei lehetnek tömbök:

int[][] matrix = {{1,2,3}, {4,5}, {6,7,8,9}};

Az is lehetséges, hogy az elemek felsorolása helyett lefoglaljuk a memóriaterületet, és a szögletes zárójeles módszerrel adunk kértéket egyesével (vagy ciklusban) az elemeknek:

int[] array = new int[5];
array[2] = 4;

Ez utóbbinak az a nagy előnye, hogy a tömb mérete nem feltétlenül csak konstans, lehet változó is, pl. new int[n];

Vezérlés

A vezérlő utasítások a számítógép két alapvető lényegének programozási megvalósulásai.

Feltételkezelés

A Java nyelvben az alap feltételkezelés a következőképpen néz ki:

if ([feltétel]) [utasítás];

A gyakorlatban az utasítás nem egy utasítás, hanem több, és ezeket kapcsos zárójelek közé tesszük. Jó programozási gyakorlat akkor is új kapcsos zárójelek közé tenni és új sorba írni az utasítást, ha csak egy van belőle:

if ([feltétel]) {
    [utasítás];
}

A feltételkezelésnek a továbbgondolása az, hogy akkor is történjen valami, ha a feltétel nem teljesül, és ne kelljen megismételni a feltételt, immáron tagadva, és ez az else ág:

if ([feltétel]) {
    [utasítások 1];
} else {
    [utasítások 2];
}

Persze az else is folytatódhat rögtön egy újabb if utasítással, majd a következő is stb.:

if ([feltétel 1]) {
    [utasítások 1];
} else if ([feltétel 2]) {
    [utasítások 2];
} else if ([feltétel 3]) {
    [utasítások 3];
} else {
    [utasítások 4];
}

Ez a struktúra leginkább akkor fordul elő, ha van egy változó, ami jól felsorolható értékeket vehet fel (pl. a hét napjai; a felsorolás típusról később még lesz szó), és mindegyik érték esetén mást és mást kell végrehajtani. Ebben az esetben a fenti struktúra ugyan működhet, de kissé áttekinthetetlen; jobb megoldás a switch … case … default struktúra:

switch (változó) {
case [érték 1]:
    [utasítások 1];
    break;
 
case [érték 2]:
    [utasítások 2];
    break;
 
case [érték 3]:
    [utasítások 3];
    break;
 
default:
    [utasítások 4];
}

A struktúra értelmezése: a változó értékétől függően fut le az első, a második, a harmadik vagy a negyedik utasításblokk; ez utóbbi akkor, ha a változó pillanatnyi értéke egyik felsorolt értéket sem veszi fel. Az egyes esetek végén álló break utasítás azt jelenti, hogy a lefutás ne folytatódjon ott, hanem éjen véget. Ha nem tennénk oda, akkor a következő értékre vonatkozó utasítások is lefutnának, és jó eséllyel ezt nem szeretnénk. (Valójában előfordulhatnak olyan esetek, hogy szeretnénk végrehajtani, de ez olyan nehezen észrevehető problémákhoz vezethet, hogy még a kódismétlés is jobb, mint a break szándékos lehagyása.)

Ciklus

A számláló ciklus szintaxisa a Java-van az alábbi:

for ([inicializálás]; [feltétel]; [változás]) {
    [utasítások];
}

Először a rendszer végrehajtja az inicializálást (egyszer), majd megvizsgálja a feltételt, ha teljesül, végrehajtja az utasításokat, a végén végrehajtja a változást, ismét megvizsgálja a feltételt, és így tovább. Ha a feltétel nem teljesül, akkor véget ér a ciklus.

Ezt a módszert leggyakrabban arra használjuk, hogy a ciklusmagot (a cikluson belüli utasításokat) adott számszor hajtsuk végre, az alábbi példában 10-szer:

for (int i = 0; i < 10; i++) {
    [utasítások];
}

A ciklus változót (a példában i) használhatjuk a ciklusmagon belül, viszont az értékét ne változtassuk meg, mert az nehezen felderíthető nem várt problémákhoz vezethet. A gyakorlatban a ciklusváltozók elnevezései i, j, k (egymásba ágyazott ciklusok esetén). Az is gyakran előfordul, hogy a szám, ahányszor a ciklusmagot végre kell hajtani, egy változó aktuális értéke; ez esetben a fenti példában a feltétel nem az lesz, hogy i < 10, hanem i < n.

A for ciklusnak van egy olyan változata is, amely kezdetben nem létezett: adott tömb elemein való végigiterálás:

int[] array = {3, 5, 7, 4};
for (int element : array) {
    [utasítások];    
}

A példában a ciklusmagon belül az element változó tartalmazza az adott lefutáskor a tömb aktuális elemét.

A for ciklusok közös jellemzője az, hogy előre tudjuk, hányszor fut le a ciklusmag. Ellenben a while ciklus esetén ezt nem tudjuk, ez esetben addig fut a ciklusmag, amíg a feltétel igaz:

while ([feltétel]) {
    [utasítások];
}

Ennek egy speciális esete a végtelen ciklus (amit egyébként a for (;;) szintaxissal is el tudunk érni):

while (true) {
    [utasítások];
}

Ez utóbbinál felvetődhet, hogy egy végtelen ciklus hogyan érhet véget. Van két utasítás, ami a ciklus utasításokat vezérli:

break: erről már volt szó; ennek hatására a program kilép a legbelső ciklusból, és az azt követő első utasításra ugrik. Ezt tipikusan valamilyen feltétel hatására hajtja végre, tehát többnyire egy if utasításon belül található. Megjegyzés: feltétel nélküli ugró utasítás, azaz goto nincs a Java-ban, ez viszont egy olyan pont, ahol valóban hasznát lehetne venni: teljesen kiugrani egy egybe ágyazott ciklusból. Noha megvalósítható lenne változókkal és feltételkezelésekkel, a nyelv megalkotói mégis beletettek egy lehetőséget: tetszőleges blokk elé helyezhetünk címkét, kettősponttal elválasztva a blokktól, és a break utasításnak megadhatjuk, hogy melyik ciklusra vonatkozik, pl.:

outer : for (...) {
    inner : while (...) {
        ...
        if (...) {
            break outer;
        }
        ...
    }
}

continue: a ciklusmag következő iterációját hajtja végre. Azaz végrehajtja a növekményt (ha van), a feltételvizsgálatot (ha van), és ha a feltétel teljesül, akkor lefuttatja a ciklusmagot.

(Megjegyzés: más programozási nyelvekben létezik még a do … while struktúra is, ami annyiban tér el a while-tól, hogy a ciklusmag legalább egyszer mindig lefut; ilyen a Java-ban nincs.)

Függvények

Az alábbi példa illusztrálja a függvények szintaxisát Java-ban:

public class Fuggveny {
    static int hatvany(int alap, int kitevo) {
        int eredmeny = 1;
        for (int i = 1; i <= kitevo; i++) {
            eredmeny = eredmeny * alap;
        }
        return eredmeny;
    }
 
    public static void main(String[] args) {
        System.out.println(hatvany(2, 3));
    }
}

A static kulcsszó magyarázatát később látni fogjuk, most az azt követő részekre koncentráljunk! A példafüggvény hatványozást valósít meg, melynek két paramétere van: az alap és a kitevő. A már ismert megoldásokkal számolja ki az eredményt, amit a return kulcsszóval adunk vissza a hívó félnek. A hívó oldalon kiírjuk az eredményt.

Egy függvénynek nem feltétlenül van értelmezhető visszatérési értéke, bár visszatérési típust mindenképpen meg kell adni. A Java-ban a void típus jelzi azt hogy az adott függvény nem tér vissza semmivel (ez esetben is használhatjuk a return utasítást, érték nélkül). Az ilyen függvényeket tipikusan azok melléghatásáért szoktuk használni, és helyesebb volna eljárásnak nevezni őket, mivel lényegében semmi közük sincs a matematikai értelemben vett függvényekhez.

Megengedett, hogy ugyanolyan nevű függvény különböző paraméter listákkal szerepeljen, pl. int add(int a, int b), double add(double a, double b), int add(int a, int b, int c). Ezt túlterhelésnek (angolul overloading) nevezzük.

Objektumorientáltság a Java-ban

Arról már volt szó, hogy a Java-ban mindent osztályba kell tenni, nincsenek globális függvények, változók. A programozás oldalon, az objektumorientált programozás alfejezetben leírtak itt is érvényesek; az alapelvek Java-specifikus pontosításai találhatóak itt. A Java-ban igen sok finomhangolásra van lehetőség.

Osztályok

Egy Java forrásfájl egy publikus osztályt tartalmaz, aminek a neve meg kell, hogy egyezzen a fájl nevével. Az osztály deklarációja a következő: public class [Osztálynév], majd kapcsos zárójelben jönnek az attribútumok (azaz osztály szintű változók) és metódusok (osztály szintű függvények). Ahhoz, hogy egy osztályt használni tudjunk, példányosítanunk kell a new kulcsszóval.

A következő példához hozzuk létre a MyClass.java és a Main.java forrásokat az alábbi tartalommal:

// MyClass.java
public class MyClass {
    private int param1;
    private int param2;
 
    public MyClass() {
        param1 = 0;
        param2 = 0;
    }
 
    public MyClass(int param1, int param2) {
        this.param1 = param1;
        this.param2 = param2;
    }
 
    public int getSum() {
        return param1 + param2;
    }
 
    public int getProduct() {
        return param1 * param2;
    }
}
 
// Main.java
public class Main {
    public static void main(String[] args) {
        MyClass myClass = new MyClass(2, 3);
        System.out.println(myClass.getSum());
        System.out.println(myClass.getProduct());
    }
}

Öröklődés

Az öröklődést Java-ban az extends kulcsszóval tudjuk megvalósítani. Tekintsük az aábbi példát!

// BaseClass.java
public class BaseClass {
    int baseAttribute;
    void baseFunction() {}
}
 
// SubClass.java
public class SubClass extends BaseClass {
    int subAttribute;
    void subFunction() {}
}

A SubClass nevű osztály a BaseClass leszármazottja, így örökli azok attribútumait és metódusait. A SubClass tehát valójában 2 attribútumot és 2 metódust tartalmaz: egyrészt megörökli a BaseClass-ból a baseAttribute nevű attribútumot és baseFunction nevű metódust, másrészt definiál egy saját attribútumot (subAttribute) és saját metódust (subFunction). Így aki a SubClass-t példányosítja, az mind a 4 elemet eléri. (Ill. hogy pontosan mit ér el és mit nem, azt lejjebb találjuk, a láthatóságról szóló szakaszban.)

A Java-ban nem lehetséges a többszörös öröklődés. Azokban az esetekben, amelyekben erre lenne szükség, az alábbi lehetőségek közül választhatunk:

  • Leszármazás helyett hivatkozzunk azokra az osztályokra, amelyeket használni szeretnénk. Ebben az esetben lehetséges, hogy a hivatkozott osztályt is módosítanunk kell, pl. ha egy védett (protected) eljárást szeretnénk meghívni.
  • Ha mindenképpen örökölni érdemes akkor kihasználhatjuk azt, hogy egy osztály akármennyi interfészt megvalósíthat (az interfészeket ld. lejjebb). Ehhez elképzelhető, hogy át kell szervezni az osztályhierarchiát.

Felüldefiniálás

A Java programozási nyelvben alapból mindegyik metódus felüldefiniálható. Lássuk az alábbi példát!

// BaseClass.java
public class BaseClass {
    int myFunction(int a, int b) {
        return a + b;
    }
}
 
// SubClass.java
public class SubClass extends BaseClass {
    int myFunction(int a, int b) {
        return a * b;
    }
}
 
// Main.java
public class Main {
    public static void main(String[] args) {
        BaseClass baseClass = new BaseClass();
        SubClass subClass = new SubClass();
        BaseClass baseSubClass = new SubClass();
 
        System.out.println(baseClass.myFunction(2, 3));
        System.out.println(subClass.myFunction(2, 3));
        System.out.println(baseSubClass.myFunction(2, 3));
    }
}

Az alaposztályban definiálunk egy két paraméterű függvényt, ami a paraméterek összegével tér vissza. A leszármazott ezt felüldefiniálja (figyeljük meg, hogy a függvény szignatúrája ugyanaz), és a paraméterek szorzatával tér vissza. A főprogramban háromféleképpen használjuk a fenti függvényt:

  • Az őszosztályt példányosítjuk: ez esetben az ősosztály függvénye fut le, és az összeg lesz az eredmény.
  • A leszármazott osztályt példányosítjuk: ez esetben a leszármazott osztály függvénye fut le, és a szorzat lesz az eredmény.
  • A leszármazott osztályt példányosítjuk, de egy ősosztály típusú változónak adjuk át az értéket. (Ez megengedett az objektumorientált programozásban, sőt, ez adja az objektumorientált programozás egyik lényegét.) Az ősosztályon hívjuk meg a függvényt, az eredmény mégis a szorzat lesz, mert a példányosított objektum tényleges (dinamikus) típusa a leszármazott, és a felüldefiniált metódus fut le.

Megjegyzés: egyes "szigorú" Java fordítók figyelmeztetést adnak akkor, ha felüldefiniálunk egy metódust, mert nem biztos abban, hogy véletlenül tettük-e vagy szándékosan. (Gondoljunk egy összetett osztályhierarchiára, melynek ősosztályaiban rengeteg függvény van; az egyik leszármazottban véletlenül is választhatunk pont ugyanolyan szignatúrát). Az annotációkról még lesz szó részletesebben az Enterprise Java részben; ezek @ karakterrel kezdődő jelölők, ami többnyire a futtató rendszer számára hordoz információt. Annak jelölésére, hogy mi valóban felül szerettük volna definiálni a metódust, a @Override annotációval tudjuk jelezni, a következőképpen:

public class SubClass extends BaseClass {
    @Override
    int myFunction(int a, int b) {
        return a * b;
    }
}

Konstruktor

Interfészek

Enum

Csomagok

Láthatóság

A fenti példákban többször találkoztunk (idáig magyarázat nélkül) a public és private kulcsszót. Egy osztályon belül attribútumok, metódusok és egyéb egyéb elemek esetén az alábbiakat használhatjuk:

Mindegyik attribútumnak és metódusnak van láthatósága, ami Java-ban az alábbi 4 lehetőség egyike lehet:

  • public: publikus, bármely másik osztály elérheti.
  • protected: a leszármazott osztályok, valamint az adott csomagban szereplő többi osztály elérheti, a többi nem.
  • alapértelmezett: csak az adott csomagban szereplő osztályok érhetik el.
  • private: csak az adott osztály érheti el.
public class AccessModifiers {
    private int myPrivateAttribute;
    int myPackagePrivateAttribute;
    protected void myProtectedFunction() {}
    public void myPublicMethod() {}
}

Speciális esetek

A leggyakoribb objektumorientált témákon túl vannak még ritkábban használt részek is. Első körben ez akár ki is hagyható, esetleg érdemes csak egyszer átfutni rajta, és később visszatérni rájuk.

Több osztályt tartalmazó forrásfájlok

Egy forrásfájl több osztályt is tartalmazhat, de ezek közül csak egy lehet publikus: melynek neve megegyezik a fájl nevével.

public class PublicClass {
    ....
}
 
class PrivateClass1 {
    ...
}
 
class PrivateClass2 {
    ...
}
 
...

Noha a nyelv megengedi ezt célszerű elkerülni, és ragaszkodni az egy forrás - egy osztály felálláshoz.

Belső osztályok

A Java-ban osztályon belül is deklarálhatunk osztályt, ezt belső osztálynak (angolul inner class) nevezzük. Ez nem magához az osztályhoz, hanem annak egy példányához tartozik, így a belső osztály példányosításához először a befoglaló osztályt kell példányosítani. A belső osztály eléri a külső osztály attribútumait és metódusait.

// MyClass.java
public class MyClass {
    class MyInnerClass {
        ...
    }
    ...
}
 
// Main.java
public class Main {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        MyClass.MyInnerClass myInnerClass = myClass.new MyInnerClass();
        ...
    }
}

Az osztályon belüli osztály lehet statikus (static class MyInnerClass); ez esetben a példányosításhoz nem kell osztálypéldány, hanem csak maga az osztály (MyClass.MyInnerClass myInnerClass = new MyClass.MyInnerClass();).

Absztrakt osztályok

Név nélküli osztályok

Interfész alapértelmezett megvalósítással

Kivételkezelés

Generics

Funkcionális programozás

public static void main(String[] args)

Most, hogy megismerkedtünk a Java nyelv alapjaival, lássuk, mit jelent a Java programok bűvös public static void main(String[] args) belépési pontja! Haladjunk sorban!

  • public: ez a függvény publikus, kívülről is látható. Ha nem lenne publikus, akkor csak belülről lehetne hívni, így nem tudnánk elindítani a programot.
  • static: a függvény statikus, azaz a tartalmazó osztályt nem kell példányosítani ahhoz, hogy meg tudjuk hívni.
  • void: ez a függvény visszatérési típusa, tehát nincs visszatérési értéke.
  • main: ez a függvény neve. Ha nem így nevezzük el, akkor az nem okoz fordítási hibát, csak épp nem indul el; a program belépési pontjának a neve kötelezően main.
  • String: a függvény paramétere szöveges. A String nem alap típus, hanem ez is egy osztály, melyről később lesz szó részletesebben.
  • []: a függvény paramétere valójában nem is egy egyszerű szöveg, hanem szövegek (szavak) tömbje.
  • args: a programnak átadott paramétereket ebből a változóból tudjuk kiolvasni. Valahogy el kell nevezni, a név itt mindegy, az args egy olyan konvenció, amit célszerű követni.

Belső könyvtárak

String

TBD: StringBuffer, StringReader, reguláris kifejezések is

Collection

TBD: lista, halmaz, queue, map, Collections osztály, algoritmusok, stream, …

Matematika

Dátumkezelés

Többszálúság

Fájlkezelés

Hálózat

TBD: E-mail is

Reflection

Lokalizáció

Távoli metódushívás

RMI, REST, WS, …

Swing

Külső könyvtárak

Naplózás

XML

JSON

TBD: Jackson, gson

Adatbázis kezelés

TBD: H2, Hibernate is

Unit testing

TBD: mock is

Általános célú könyvtárak

TBD: Apache Commons, Guava

Üzenetkezelés

Titkosítás

Enterprise Java

Ha egy tipikus vállalati program Java program elér egy bizonyos méretet, akkor szinte törvényszerű, hogy az alábbi problémák nagy többségébe belefutunk:

  • többszálúság, ill. leginkább az ezzel kapcsolatos problémák orvoslása,
  • skálázhatóság, magyarán az, hogy kis és nagy igénybevétel mellett is megbízhatóan működjön a rendszer,
  • perzisztencia, azaz az adatok tartós tárolása, ill. az ezzel kapcsolatos problémák kezelése (pl. kapcsolódás az adatbázishoz, a Java objektumok relációs adattáblákra való leképezése stb.),
  • tranzakció kezelés, tehát mikor történjen commit ill. rollback, és mi történjen mondjuk ez rollback esetén,
  • távoli elérés, ill. általánosabban megfogalmazva bármiféle kommunikáció két távoli rendszer között, az ezzel kapcsolatos problémák kezelése,
  • névszolgáltatás, azaz az erőforrások kezelése, a lekérdezés biztosítása,
  • üzenetkezelés, ami az üzenetek küldését és fogadását is jelenti, általában többféle módon,
  • biztonság, tehát annak a biztosítása, hogy illetéktelenek ne férhessenek hozzá az adatokhoz
  • és a sort még hosszan lehetne sorolni.

Az Enterprise Java (magyarul kb. nagyvállalati Java) ezekre a problémákra nyújt megoldást. Az elkészített program nem önmagában fut, hanem egy futtató környezetben, amely kezeli a fenti problémákat, vagy teljesen elfedve azt a fejlesztő elől (pl. a többszálúsággal kapcsolatos problémák kezelése), vagy arra a minimumra csökkentve a szükséges kódot, amit feltétlenül muszáj megadni (pl. az adatbázis kapcsolat felépítéséhez többnyire elég megadni az alap elérési adatokat, és mondjuk a kapcsolat létrehozásának a részleteivel általában nem kell törődnie a programozónak).

Szerintem nem egyértelmű a határ a Standard és az Enterprise Java között. Szigorú értelemben az Enterprise Java nem más mint egy specifikáció halmaz, amelyet a Java Community Process gondoz, és megtalálható a https://www.jcp.org oldalon. E szerint tehát az Enterprise Java az, ami megfelel a specifikációknak (ill. annak egy részének). Kevésbé szigorú értelemben viszont (szerintem) ide lehet sorolni mindazt, amely a fenti problémákra (vagy azok egy részére) igyekszik megoldást találni mégpedig úgy, hogy a program nem önmagában fut, hanem egy keretrendszerben. Ezen az oldalon e tágabb értelmezést használom, sőt, ide gyűjtöm azokat a rendszereket is, melyek önálló alkalmazásokként futnak, és interfészt nyújtanak a tőlük függetlenül futó Java alkalmazások felé.

Java EE

Spring

Apache Servicemix

Adobe Enterprise Manager

Haladó témák

Class loading

Profiling

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License