Operátorok

Kritika: bitmanipulációk a gyakorlatban

 

www.informatika-programozas.hu - Ez a programozási feladat nehéz lesz!

 

Figyelem! Az oldal haladó anyagot tartalmaz!

 

X. szerző Y. című könyvében felveti annak lehetőségét, hogy a program különböző belső állapotait ne boolean típusú változókkal tároljuk, hanem más módon oldjuk meg. Természetesen nem kívánom a kolléga szaktudását megkérdőjelezni (amelyről egyébiránt személyes találkozásunkkor én is meggyőződtem és elért szakmai eredményeit csakis csodálni tudom), de a jelen témakörről ebben fejezetben szívesen nyitok vitát, mert ebből mindkettőnk tudása, sőt a Tisztelt Olvasóé is sokat gazdagodhat. Mivel szakmai nézetkülönbségről és nem személyeskedésről van szó, ezért a szerző nevét és könyvének címét nem említem meg.

Először nézzük meg a kérdéses feltevéseket és hozzá a járulékos kódot. A szakmai nézetkülönbség a logikai bitek felhasználásakor keletkezik, ezt a programozástechnika összefoglaló néven flag-nek nevezte el (Az állapotjelzők (flag) című fejezet). A logikai bitek 2 állapot tárolására hivatottak, ez tipikusan true vagy false. Ennek legbejáratottabb adattípusa a boolean, például:

boolean editable = true;

A fenti deklaráció azt jelenti, hogy (az objektum) szerkeszthető vagy sem. Ennél több ezen állapot modellezésére nem is kell. Nézzük meg azonban a kolléga témához fűződő megjegyzését (vett idézetek dőlt betűvel)!

Egyebek között a bitenkénti műveletekkel (lásd előző fejezetet: A mütyűrködés csúcsa: a bitoperátorok – PL.) hasznosan kezelhetők a logikai bitek is. Tegyük fel például, hogy az adott programban vannak logikai bitek, melyek a különböző összetevők állapotát határozzák meg a programban. Ez célravezetőbb, mint különböző boolean változók definiálása. Bitmanipuláció segítségével beállíthatók és változtathatók az adott bitek értékei. Először definiáljunk konstansokat, melyek a program különböző bitjeit határozzák majd meg. Ezek a konstansok a kettes számrendszer különböző helyiértékei, így biztosíthatjuk, hogy később nem lesznek összekeverhetők. Később ezek a konstansok a bit változók értékeit segítik majd kinyerni. A következő példában a biteket 0 értékűnek inicializáljuk, ami azt jelenti, hogy minden érték hamis.

static final int VISIBLE = 1;
static final int DRAGGABLE = 2;
static final int SELECTABLE = 4;
static final int EDITABLE = 8;
int flags = 0;

A VISIBLE szimbolikus konstans jelzi, ha valami láthatóvá válik a programban. Ezt a bitet állítja egyesre a következő sor:

flags = flags | VISIBLE;

A láthatóságot a következő képen tesztelhetjük:

if ((flags & VISIBLE) == VISIBLE) {
...
}

Itt látható a teljes program, mely magában foglalja a fenti kódot:

public class BitwiseDemo {

static final int VISIBLE = 1;
static final int DRAGGABLE = 2;
static final int SELECTABLE = 4;
static final int EDITABLE = 8;
public static void main(String[] args) {
int flags = 0;
flags = flags | VISIBLE;
flags = flags | DRAGGABLE;
if ((flags & VISIBLE) == VISIBLE) {
if ((flags & DRAGGABLE) == DRAGGABLE) {
System.out.println("Flags are Visible "
+ "and Draggable.");
}
}
flags = flags | EDITABLE;
if ((flags & EDITABLE) == EDITABLE) {
System.out.println("Flags are now also Editable.");
}
}
}

A magyarázat kissé túl tömörre sikerült, ezért újramagyarázom a kódot, egyúttal közben felfedem a nézetbéli különbségeket. Bevezetésképpen nézzük meg a lecsupaszított, a saját rendszer szerint már futtatható Java-kódot:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {



static final int VISIBLE = 1;
static final int DRAGGABLE = 2;
static final int SELECTABLE = 4;
static final int EDITABLE = 8;

public static void main(String[] args) {
int flags = 0;
flags = flags | VISIBLE;
flags = flags | DRAGGABLE;
    if ((flags & VISIBLE) == VISIBLE) {
        if ((flags & DRAGGABLE) == DRAGGABLE) {
            System.out.println("Flags are Visible " + "and Draggable.");
            }
        }
        flags = flags | EDITABLE;
        if ((flags & EDITABLE) == EDITABLE) {
        System.out.println("Flags are now also Editable.");
        }
    }
}

Végeredmény:
Flags are Visible and Draggable.
Flags are now also Editable.

A szerző tehát először létrehoz 5 db flag-et…

static final int VISIBLE = 1;
static final int DRAGGABLE = 2;
static final int SELECTABLE = 4;
static final int EDITABLE = 8;
int flags = 0;

...de nem boolean, hanem int adattípussal. Ebből 4 konstans, azaz nem változtatható a final módosítószó miatt (Ami a változót (és a többieket) változatlanná teszi: a final című fejezet), míg az 5. egyfajta tároló. Vegyük észre, hogy ekkor a Java-rendszer konkrét memóriahelyfoglalása 5 x 32 = 160 bit (mert az int adattípus garantált helyfoglalása mindig 32 bit). Nincs arról tudomásom, hogy a rendszer boolean adattípus esetén mekkora konkrét memóriahelyfoglalást végez, de mivel ez garantáltan nem 2 bit, szerintem 8 bit lehet (azaz 1 byte). Ebből következően 5 db boolean flag memóriahelyfoglalása 5 x 8 bit = 40 bit lehet. Ha feltételezésem helyes, ez pontosan negyede az int adattípusnak. Nyilvánvalóan ez a méretkülönbség nem lényeges ilyen kis programok esetében, de minél több flag-et kell használnunk, annál nagyobb, még pedig négyszeres lesz a különbség és ez már nem elhanyagolható tényező. Ráadásként később látni fogjuk azt is, hogy boolean esetén felesleges az 5. változó.

Most nézzük meg az int flag-eket binárisan is:
 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {


static final int VISIBLE = 1;
static final int DRAGGABLE = 2;
static final int SELECTABLE = 4;
static final int EDITABLE = 8;

public static void main(String[] args) {
    int flags = 0;
    System.out.println(Integer.toBinaryString(VISIBLE));
    System.out.println(Integer.toBinaryString(DRAGGABLE));
    System.out.println(Integer.toBinaryString(SELECTABLE));
    System.out.println(Integer.toBinaryString(EDITABLE));
    System.out.println(Integer.toBinaryString(flags));
    }
}

Végeredmény:
1
10
100
1000
0


A konzol kiíráskor egyszerűsít, mert a bináris számok valójában így néznek ki (a 32 bitet az olvashatóság végett 4 bitenként csoportosítottam, valamint lehetséges, hogy az 1. bit balról 1, mert az nem helyiértéket, hanem a szám pozitív-negatív jellegét jelöli):

0000 0000 0000 0000 0000 0000 0000 0001
0000 0000 0000 0000 0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0100
0000 0000 0000 0000 0000 0000 0000 1000

0000 0000 0000 0000 0000 0000 0000 0000

Ha az int flag-et (utolsó, elkülönített bináris sor) feltöltjük az első 4 változó logikai értékével, akkor ezt kapjuk binárisan:

0000 0000 0000 0000 0000 0000 0000 1111

Decimálisan ekkor a System.out.println() függvény a 15 értéket írná ki (15bin = 1111).

Most nézzük meg mindezt boolean adattípussal is, először szabványos megjelenítésben, majd binárisan:

boolean VISIBLE = true;
boolean DRAGGABLE = true;
boolean SELECTABLE = true;
boolean EDITABLE = true;

0000 0001
0000 0001
0000 0001
0000 0001

Ebben az adattípusban azonban a csoporthoz már nincs értelme külön deklarálnunk további boolean flag-et (boolean flags = false;), hiszen láthatjuk, hogy a szerző verziójában az int flag-et egy általános logikai tárolónak használja, még pedig a 4 változó állapotának tárolására. Itt egyébként a különböző koncepciók ötvözeteként további kombinációkat lehetne alkotni, ám ez a vonal most nem lényeges. Az eredeti koncepció valódi problémája, hogy míg a boolean flag-ek értékei…

boolean VISIBLE = true;
boolean DRAGGABLE = true;
boolean SELECTABLE = true;
boolean EDITABLE = true;

...közvetlenül és azonnal kiolvashatók-állíthatók, valamint ellenőrizhetők, addig az integer adattípusé nem:

static final int VISIBLE = 1;
static final int DRAGGABLE = 2;
static final int SELECTABLE = 4;
static final int EDITABLE = 8;
int flags = 0;

Ez főleg a flags változóra vonatkozik, hiszen egyedül ennek értékei változnak. Ezeket csak közvetetten, a bináris és decimális számok ismeretében és/vagy további programozási trükkök alkalmazásával tudjuk megtenni. Az alábbi módosított Java-kódban az int flags változó állapotváltoztatásait a System.out.println() függvénnyel követjük nyomon. Mivel az értékek követését közvetlenül nem tudjuk megtenni, marad a bináris és decimális számok fejben történő ellenőrzése.

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {

static final int VISIBLE = 1;
static final int DRAGGABLE = 2;
static final int SELECTABLE = 4;
static final int EDITABLE = 8;

public static void main(String[] args) {
int flags = 0;
flags = flags | VISIBLE;
System.out.println(flags);
flags = flags | DRAGGABLE;
System.out.println(flags);
    if ((flags & VISIBLE) == VISIBLE) {
        if ((flags & DRAGGABLE) == DRAGGABLE) {
            System.out.println("Flags are Visible " + "and Draggable.");
            }
        }
        flags = flags | EDITABLE;
        System.out.println(flags);
        if ((flags & EDITABLE) == EDITABLE) {
            System.out.println("Flags are now also Editable.");
        }
    }
}

Végeredmény:
1
3
Flags are Visible and Draggable.
11
Flags are now also Editable.


A végeredmény korrekt (a flags változó 3 bitje került átállításra; ugyanakkor vegyük észre, hogy a SELECTABLE változó nem lett felhasználva), de a bitmanipuláció csakis közvetetten ellenőrizhető, ráadásul a bitállítások miatt a kód túlbonyolított. Ember legyen a talpán, aki tisztán a kód alapján figyelemmel tudja kísérni az int flags változó aktuális értékeit!
 

www.informatika-programozas.hu - Ezt most meg kell tanulni!


Összességében: szakmai véleményem szerint koncepcionális tévedés logikai, valójában 2 állapot tárolására más adattípust használni boolean adattípus helyett. Sőt, ez az adattípus egyenesen erre a tárolási helyzetre lett megalkotva.

A dolgok rémegyszerűvé válnak boolean adattípus bevetésével. Módosítsuk ilyen irányban a kódot:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {


public static void main(String[] args) {
boolean VISIBLE = true;
boolean DRAGGABLE = true;
boolean SELECTABLE = true;
boolean EDITABLE = true;
    if (VISIBLE == true && DRAGGABLE == true) {
        System.out.println("Flags are Visible " + "and Draggable.");
    }
    if (EDITABLE == true) {
        System.out.println("Flags are now also Editable.");
        }
    }
}

Végeredmény:
Flags are Visible and Draggable.
Flags are now also Editable.

Ugye látjuk, hogy nem is kellett 5. változót felhasználnunk? Sőt, a feltételmegadások tovább egyszerűsíthetők:
 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {

public static void main(String[] args) {
boolean VISIBLE = true;
boolean DRAGGABLE = true;
boolean SELECTABLE = true;
boolean EDITABLE = true;
    if (VISIBLE && DRAGGABLE) {
        System.out.println("Flags are Visible " + "and Draggable.");
    }
    if (EDITABLE) {
        System.out.println("Flags are now also Editable.");
        }
    }
}

Végeredmény:
Flags are Visible and Draggable.
Flags are now also Editable.


Nyilvánvalóan a flag-ek szintén éppúgy egyetlen egységbe rendezhetők, amint ezt az int flags próbálja megtenni, mondjuk 1 tömbbe. Az alábbi kód már kissé erőltetett, hiszen erre valójában nincs szükség boolean adattípus esetén, de azért nézzük meg. Tehát létrehozunk egy 5., tároló típusú tömböt is (boolean [] STORE), amelybe belevezetjük a deklarált flag-ek értékeit:

 

STORE[0] = VISIBLE;
STORE[1] = DRAGGABLE;
STORE[2] = SELECTABLE;
STORE[3] = EDITABLE;

 

Nézzük meg a futtatható Java-kódot:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {

public static void main(String[] args) {
boolean VISIBLE = true;
boolean DRAGGABLE = false;
boolean SELECTABLE = true;
boolean EDITABLE = false;

boolean [] STORE = new boolean[4];
STORE[0] = VISIBLE;
STORE[1] = DRAGGABLE;
STORE[2] = SELECTABLE;
STORE[3] = EDITABLE;

for(int i = 0; i < STORE.length; i++) {
    System.out.println(STORE[i]);
        }
    }
}

Végeredmény:
true

false

true

false