Operátorok
Kritika: bitmanipulációk a gyakorlatban
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:
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:
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.
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!
Ö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:
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:
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:
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