Archívum

java

A Java Native Interface (JNI) segítségével C nyelven írt könyvtárakat érhetünk el, és a benne lévő függvényeket használhatjuk Java-ban. A Java kódunkban native kulcsszóval kell megadnunk a fejlécét a C-s függvényünknek, ilyen módon:

native int osszeadC(int a, int b);

Majd a System.load() vagy System.loadLibrary() segítségével meg kell adnunk a .so vagy .dll fájlunkat. a load()-nak teljes elérést kell megadni, a loadLibrary a java.library.path bejegyzésben szereplő könyvtárak egyikébe kell tenni a fenti fájlt. hogy ez hol van a

java -XshowSettings:properties

parancs kimenetében megtaláljuk.

Tehát nézzük a Java kódot:

class jnitest {
static {
  System.load("/home/anon117/programozas/libjnitest.so");
}

native int osszeadC(int a, int b);

static public void main (String arg[]) {
  jnitest jni = new jnitest();
  System.out.println(jni.osszeadC(1, 2));
  }
}

Ezt lefordítjuk a

javac jnitest.java

paranccsal, majd

javah jnitest

paranccsal létrehozzuk a C forrásunk fejléc fájlját.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jnitest */

#ifndef _Included_jnitest
#define _Included_jnitest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: jnitest
* Method: osszeadC
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_jnitest_osszeadC
(JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

Ahogy fent is látszik ezt nem kell babrálni, viszont szükségünk lesz belőle a függvényünk deklarációs részére. Ime a C kód:

#include <jni.h>
#include <stdio.h>
#include "jnitest.h"

JNIEXPORT jint JNICALL Java_jnitest_osszeadC
(JNIEnv *env, jobject obj, jint a, jint b) {
  jint result = a + b;
  return result;
}

Itt a paraméter listában csak a típusok szerepelnek, meg kell adnunk az azonosítókat is. Ha ezzel megvagyunk le kell fordítanunk. Ez nálam így néz ki:

gcc -o libjnitest.so -shared -I/usr/jdk1.8.0_45/include/ -I/usr/jdk1.8.0_45/include/linux/ jnitest.c -lc

majd jöhet a próba:

java jnitest
3

További információk a Wiki oldalon.

A nem-referencia szerinti értékadásnál klónozzuk a változónkat, ilyenek például a primitív adattípusok. De inkább a referencia alapú osztályokat használjuk a JAVA-ban. Ezért a JAVA  lehetővé teszi az objektumok klónozását más szóval field-for-field másolását. Erre az Object ősosztály clone interface-ének implementálásával vagyunk képesek.

A klónozásra váró osztályban implementáljuk a Cloneable interface-t, ennek egy metódusa van a clone(), ezt override-oljuk. De beszéljen a példakód.

class clone {
    public static void main(String[] args) {
        new clone();
    }   

    public clone() {
        cloneMe clone1 = new cloneMe(0, "probe");
        cloneMe clone2 = null;
        try {
            clone2 = (cloneMe) clone1.clone();
            System.out.println(clone2.str);
            System.out.println(clone2.eredmeny);
        } catch (CloneNotSupportedException c) {
            System.out.println(c.toString());
        }
    }
}

class cloneMe implements Cloneable {
    int i;
    String str;
    innerClass inner = null;
    int eredmeny;

    public cloneMe(int i, String str) {
        this.i = i;
        this.str = str;
        inner = new innerClass();
        eredmeny = inner.add(1, 2);
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone(); // try-catch
    }

}

class innerClass {
    public int add(int egy, int ketto) {
        return egy + ketto;
    }
}

 

Az Iterator tervezési minta lehetővé teszi, hogy egy iterátor segítségével bejárjuk a gyüjteményünket. Ugye a Java beépített Gyűjteményei meg is valósítják az ehhez szükséges inteface-eket. De mi van ha egy sima tömbön szeretnék iterátort, azt szeretném bejárni? Ilyenkor el kell készíteni az iterator mintát a tömbünkhöz. A wikipedia Iterator pattern szócikke sok segítséget nyújt nekünk. gyakorlatilag az ott felvázolt PHP megvalósítást ültetem át Java nyelvre.

De mire is van szükségünk. Alapvetően két interface-t kell implementálnunk: Container, Iterator.  Ezekről van szó:

interface Container
{
  public Iterator getIterator();
}

interface Iterator
{
  public Boolean hasNext();
  public Object next();
}

Aztán kell még egy sima tömb, amit szeretnénk végigiterálni. Legyen egy String tömb.

String gyumolcsok[] = {"alma", "körte", "dinnye", "barack", "ribizli"};

Ennek a tömbnek a Container osztályban kell szerepelnie, de én kívülről akarom bevinni, ezért a konstruktor metódusával adom át neki.

class StringKontener implements Container
{
  public String _Strings[] = null;

  public StringKontener(String[] bemenet)
  {
    this._Strings = bemenet;
  }

  @Override
  public Iterator getIterator()
  {
    // Mivel nem nested osztály, ezért átdobjuk a az Iteratornak
    // hogy lássa a változókat. objektum összefűzés.
    return new StringIterator(this); // <==[ ez a legszebb rész
  }
}

Ami fent látszik is. A getIterator metódusal pedig beinjektáljuk a konténer osztályt az iterátorba. Ezt a this-szel valósítjuk meg, mivel nem beágyazott osztályként használjuk. Lássuk az Iterator osztályt.

class StringIterator implements Iterator
{
  int index;
  private StringKontener sk = null; // this paraméter

  public StringIterator(StringKontener sk)
  {
    this.sk = sk; // meg ez
  }

  @Override
  public Boolean hasNext()
  {
    if (index < sk._Strings.length)
    {
      return true;
    }
    else
    {
      return false;
    }
  }

  @Override
  public Object next()
  {
    if (this.hasNext())
    {
      return sk._Strings[index++];
    }
    else
    {
      return null;
    }
  }
}

Ugye a fenti this paraméter átküldésre azért is szükség volt, hogy az Iterator osztály lássa a String tömböt. A hasNext() metódus ugye vizsgálja van-e következő eleme a tömbnek, a next() pedig ráugrik ha van. De ez részletesen le van írva a wikipediia cikkben.

No akkor teszteljünk.

class iterator
{
public static void main(String[] args)
{
new iterator();
}

public iterator()
{
  String gyumolcsok[] = {"alma", "körte", "dinnye", "barack", "ribizli"};

  StringKontener sk = new StringKontener(gyumolcsok);

  for (Iterator si = sk.getIterator(); si.hasNext();)
  {
    String gyumolcs = (String) si.next();
    System.out.println(gyumolcs);
  }

  Iterator si2 = sk.getIterator();
  while (si2.hasNext())
  {
    String gyumolcs = (String) si2.next();
    System.out.println(gyumolcs);
  }
 }
}

Kimenet:

alma
körte
dinnye
barack
ribizli

Wikipedia szócikkForráskód

Józan paraszti ésszel belegondolva mit is akarunk elérni? Első körben megnyitni egy fájlt, aztán ebből a fájlból olvasni vagy írni. Nem tűnik nagy kalandnak. Azért mégsem ennyire egyszerű.

A Java a fájlokat streamként kezeli, ezekből a stream-ekből két féle van InputStream és OutputStream. Ezek Abstract Osztályok, ezekből ágazódnak le a secifikált egyéb Stream típusok. Ezek közül nekünk a FileInputStream-re és a FileOutputStream-re van szükségünk. Értelem szerűen a FileInputStream-ből olvasni lehet, a FileOutputStreambe meg írni.

Van egy harmadik osztály is ez a RandomAccessFile, gyakorlatilag ez áll legközelebb a C-s fopen, fclose, fseek vonalhoz.

Node most foglalkozzunk az első kettővel. A FileInputStream konstruktorának szüksége van egy fájlnévre, amit megnyít. Ez olyan fopen() jellegű dolog, csak itt nem kell beállítanunk access mode-ot, mivel csak olvasni fogunk. Az FileinputStream-ből egyetlen dolgot tdunk csinálni bájtokat olvasni.

A read metódusból több is van. a sima read() 1 bájtot olvas be. A másik kettővel egy bájt tömbbe olvashatunk be. Itt bővebben.

Hasonló dolgok mondhatóak el a FileOutputStream-ről is. Infó itt.

Szóval a fentiek csak bájtos olvasásra, írásra alkalmasak, ezért kitalálták a Reader abstract osztályt, ami karakterek olvasására alkalmas . Neki is van jó néhány megvalósítása, de mi most csak a FileInputReader-rel foglalkozunk, na jó nem csak.

Tehát a FileInputReader karaktereket olvas be, vagyis nem teljesen, bájtokat, de azokat átalakítja karakterekké, sőt még karakter kódolást is megadhatunk. Tehát az InputReadStream read metódusai char típust olvasnak, ezt lehet bájtonként vagy char[] tömbbe is.

Van egy hasznos wrapper osztály mindemellett a BufferedReader, ami egy Reader köré épül, esetünkben egy FileInputReader. Ez nevéből adódóan bufferelt olvasást tesz lehetővé.

De mi van ha írni akarunk, igen jól gondolod van egy Writer abstract osztály is. Ez karakterek stream-be írására lesz jó. A konkrét karakteres írás megvalósítása a FileWriter osztály valósítja meg. Konstruktora egy fájlt vár. write metódusai az OutputStreamWriter osztályból öröklődik. 🙂 Ha nyers, raw bájthalmazt akarunk kiírni, akkor a fentebb említett FileOutputStream osztályt javasolja a doksi. Az OutputStreamWriter osztály pedig a karakteres adatok bájtos kiírására alkalmas. továbbá a fentiekhez hasonlóan itt is van egy BufferedWriter wrapper osztály, ami egy Writer-t vár. Egyébként a Buffered* wrapperek használata erősen ajánlott.

Beszéljünk a RandomAccessFile osztályról is. A random access ugye véletlenelérést jelent, olyan mint egy tömb, amiben ugrálhatunk ide-oda, és ott olvashatunk és írhatunk is. Ezt az ugrálást, na jó nem ugrálás, véletlen elérést a seek metódus valósítja meg. segítségével a megadott fájlpozícióra állhatunk. Ezt a kurzort fájlmutatónak (file pointer) hívjuk, aminek pontos helyzetét a getFilePointer metódussal kérhetjük le. A működése elég terjedelmes dokumentáció foglalja magába, inkább ott olvasd el. 🙂 Meódusa egy fájlnevet és egy megnyitási módot vár (pl: “rw”).

Egyelőre ennyi, mert még reggelig lehetne sorolni az osztályokat.

Az enum, azaz magyarul a felsorolt típus 1.5 JDK-tól van a Javaban. Az enum rendelkezik olyan lehetőséggel, hogy metódusokat adjunk hozzá, és ezekkel különböző műveleteket hajtsunk végre a felsorolt értékeken. No de ne szaladjunk ennyire előre. A hagyományos felsorolás, enum általában így néz ki.

enum weekdays
{
    HETFO, KEDD, SZERDA, CSUTORTOK, PENTEK, SZOMBAT, VASARNAP;
}

és lekérhetjük értékeiket:

weekdays w1 = weekdays.HETFO;
System.out.println(w1);

vagy akár be is járhatjuk:

for (weekdays w2 : weekdays.values())
    System.out.printf("ENUM: %s\n", w2);

Ugye mint tudjuk az enum értékei konstansok, tehát futás időben nem változtathatjuk meg az értékeiket.

Tovább bővítve a dolgot a felsorol értékekhez mezőket (fields) rendelhetünk, ilyen módon:

HUNGARY("Budapest", 93036, 9893899),
GERMANY("Berlin", 357168, 80716000),
SPAIN("Madrid", 505990, 46464053);

Ahhoz, hogy ezeket a mezőket használni tudjuk ahhoz definiálnunk kell egy enum konstruktort, ami az osztályokéhoz hasonló:

private String capitalTown;
private int area;
private int population;

private orszagok(String ct, int area, int population)
{
  this.capitalTown = ct;
  this.area = area;
  this.population = population;
}

Ezzel a konstruktorral nincs dolgunk. Ha viszont le akarjuk kérdezni a mezőket, s amint mondtam, műveleteket akarsz velük végrehajtani, akkor getter-eket, egyéb metódusokat kell létrehozz az enum-on belül, akár csak egy osztályban:

public String getCapitalTown()
{
  return this.capitalTown;
}

public int getArea()
{
  return this.area;
}

public int getPopulation()
{
  return this.population;
}

public int getDensity()
{
  return this.population / this.area;
}

Természetesen ezeket használhatjuk is.:

for (orszagok o2 : orszagok.values())
{
System.out.printf("ENUM: %s, főváros: %s, terület (km2): %d, 
Népsűrűség: %d\n", o2, o2.getCapitalTown(), o2.getArea(), o2.getDensity());
}

Kimenet:

ENUM: HUNGARY, főváros: Budapest, terület (km2): 93036, Népsűrűség: 106
ENUM: GERMANY, főváros: Berlin, terület (km2): 357168, Népsűrűség: 225
ENUM: SPAIN, főváros: Madrid, terület (km2): 505990, Népsűrűség: 91

A forráskód megtalálható itt.

Ha JAVA archívumot akarunk létrehozni a lefordított class-okból, akkor a jar tömörítőt kell alkalmaznunk. Ami fontos, hogy a Manifest.mf állományban meg kell adni, hogy a main metódus (belépési pont) mekyik class-ban szerepel. Ezt a következő képen tehetjük meg:

jar -cvfe kivetel.jar Kivetelek Kivetelek.class Rossz*.class

A fenti parancs az előző bejegyzésben megvalósított class-okat tömöríti jar állományba. A -e paraméter az entry, azaz belépésért felelős, itt meg is adtuk, hogy a Kivetel az az osztály, amelyben a main() szerepel. (Ha package-ot is használunk akkor azt is meg kell adni: Package.Osztály formában.)

És az eredmény:

Manifest-Version: 1.0
Created-By: 1.8.0_45 (Oracle Corporation)
Main-Class: Kivetelek

A -m kapcsolóval te is hozzáadhatod a saját Manifest.mf fájlodat, ilyen formában:

jar -cfm MyJar.jar Manifest.txt MyPackage/*.class

Az úgynevezett Metódus láncolás technika nyelvfüggetlen OOP módszer, amit JAVA-val szeretnék bemutatni. Az objektum metódusait meghívhatjuk egymás után procedurálisan:

Osztaly obj = new Osztaly
obj.setAge(25);
obj.setName("Lajos");
obj.setCountry("Hungary");

De meghívhatjuk egymás után, láncolva is:

Osztaly obj = new Osztaly
obj.setAge(25).setName("Lajos").setCountry("Hungary");

Amig a fenti deklarációnál a metódusokat void visszatérési értékkel deklaráltuk:

public void setName(String name) {
  this.name = name;
}

Addig ezt a láncolt metódusoknál ezt nem tehetjük meg. Ebben az esetben a visszatérési érték az osztály típusa.

public Osztaly setName(String name) {
  this.name = name;
  return this;
}

Így nem szükséges átmeneti változókat használnunk, hanem az Osztály saját változóit használjuk.

Ebben a jegyzetben az un. checked Exception-ökről lesz szó, ezeket mindenképpen le kell kezelni, különben a fordító hibát dob.

error: unreported exception MyOwnException; must be caught or declared to be thrown

vagyis mindenképpen try-catch ágakat kell használni.

A saját kivételeket a saját osztályukban kell létrehozni az Exception ősosztályból származtatva ilyen módon:

class RosszSzovegException extends Exception
{
  public RosszSzovegException()
  {
    super("Helytelen szöveg hossz");
  }
}

Ezt követően írjuk meg a metódust, ami majd dobja a fenti kivételt:

public void stringHossz(String str) throws RosszSzovegException
{
  if (str.length() >= 4 && str.length() <= 30)
  {
    this.szovegHossz = str;
  }
  else
  {
  throw new RosszSzovegException();
  }
}

Itt jól látszik hogy a metódus csak akkor fogja dobni a kivételt ha erre rábeszéljük a metódus deklarációs részében. Mert különben hibát dob a fordító:

error: exception RosszSzovegException is never thrown in body of corresponding try statement

Mindemellett több catch ágat is létrehozhatunk, ha több saját kivételt akarunk kezelni.

try
{
  this.labMeret(40);
  System.out.printf("Láb méret: %d\n", this.labMeret);

  this.stringHossz("aaa");
  System.out.printf("Szöveg hossza: %d\n", this.szovegHossz);
}
catch (RosszSzovegException ex)
{
  // ex.printStackTrace();
  System.out.println("Rossz szoveg.");
}
catch (RosszMeretException rme)
{
  System.out.println("RosszMeretException - catch ág");
}

A teljes forráskódot itt megtalálod.