Bitwise Operatörler
Bilgisayarın konuştuğu dil 0 ve 1. Bitwise operatörler, sayılarla bit düzeyinde çalışmanı sağlar — her bir bit'i tek tek manipüle edebilirsin. Günlük programlamada çok sık kullanmasan da, bazı alanlarda (permission sistemi, renk işlemleri, performans optimizasyonu) hayat kurtarır.
Bunu şöyle düşün: Normal operatörler sayıları bir bütün olarak işler, bitwise operatörler ise sayıyı açıp iç devrelerini tek tek kontrol eder — adeta bir saatin kapağını açıp çarkları tek tek ayarlamak gibi.
Günlük iş uygulamalarında (web API, CRUD) seyrek kullanılır ama sistem programlama, oyun geliştirme, performans optimizasyonu ve mülakatlarda karşına çıkar. En azından temelini bilmen gerekiyor.
Sayıların İkili Gösterimi
Bitwise operatörleri anlamak için önce sayıların ikili (binary) gösterimini bilmen lazım.
// Sayıların ikili gösterimi
// 5 = 00000101
// 3 = 00000011
// 12 = 00001100
System.out.println(Integer.toBinaryString(5)); // 101
System.out.println(Integer.toBinaryString(12)); // 1100
System.out.println(Integer.toBinaryString(255)); // 11111111
// İkili literaller (Java 7+)
int a = 0b00000101; // 5
int b = 0b00000011; // 3
System.out.println(a); // 5
System.out.println(b); // 3int 32 bit'tir. Basitlik için örneklerde 8 bit göstereceğiz ama mantık aynı.
Bitwise Operatörler Tablosu
| Operatör | Ad | Açıklama | |
|---|---|---|---|
& | AND | İki bit de 1 ise 1 | |
| `\ | ` | OR | En az bir bit 1 ise 1 |
^ | XOR | Bitler farklıysa 1 | |
~ | NOT (Complement) | Bitleri tersine çevirir | |
<< | Sol kaydırma | Bitleri sola kaydırır | |
>> | Sağ kaydırma (işaretli) | Bitleri sağa kaydırır, işaret biti korunur | |
>>> | Sağ kaydırma (işaretsiz) | Bitleri sağa kaydırır, sol taraf 0 ile dolar |
& (Bitwise AND)
İki bit de 1 ise sonuç 1, aksi halde 0.
5 = 00000101
& 3 = 00000011
--------------
1 = 00000001int a = 5; // 00000101
int b = 3; // 00000011
int c = a & b;
System.out.println(c); // 1 (00000001)
// Her bit pozisyonu bağımsız kontrol edilir:
// Bit 0: 1 & 1 = 1
// Bit 1: 0 & 1 = 0
// Bit 2: 1 & 0 = 0AND Ne İşe Yarar?
Maskeleme (belirli bitleri okuma):
int sayi = 0b11010110; // 214
int maske = 0b00001111; // Alt 4 biti al
int altDort = sayi & maske;
System.out.println(altDort); // 6 (0110)
// Üst 4 bit maskelendi, sadece alt 4 bit kaldıÇift/tek kontrolü (performanslı):
int sayi = 42;
if ((sayi & 1) == 0) {
System.out.println("Çift"); // En düşük bit 0 ise çift
} else {
System.out.println("Tek");
}| (Bitwise OR)
En az bir bit 1 ise sonuç 1.
5 = 00000101
| 3 = 00000011
--------------
7 = 00000111int a = 5; // 00000101
int b = 3; // 00000011
int c = a | b;
System.out.println(c); // 7 (00000111)OR Ne İşe Yarar?
Bitleri açma (set etme):
int izinler = 0b0000; // Hiçbir izin yok
int okuma = 0b0001; // Okuma biti
int yazma = 0b0010; // Yazma biti
izinler = izinler | okuma; // Okuma iznini aç
System.out.println(Integer.toBinaryString(izinler)); // 1 (0001)
izinler = izinler | yazma; // Yazma iznini de aç
System.out.println(Integer.toBinaryString(izinler)); // 11 (0011)^ (Bitwise XOR)
Bitler farklı ise sonuç 1, aynı ise 0.
5 = 00000101
^ 3 = 00000011
--------------
6 = 00000110int a = 5; // 00000101
int b = 3; // 00000011
int c = a ^ b;
System.out.println(c); // 6 (00000110)XOR'un Özel Özellikleri
// Kendisiyle XOR = 0
System.out.println(5 ^ 5); // 0
// 0 ile XOR = kendisi
System.out.println(5 ^ 0); // 5
// İki kere XOR = geri döner
int x = 42;
int key = 123;
int encrypted = x ^ key; // "şifrele"
int decrypted = encrypted ^ key; // "çöz"
System.out.println(decrypted); // 42 — geri geldi!Geçici değişken olmadan swap (takas):
int a = 10;
int b = 20;
a = a ^ b; // a = 30
b = a ^ b; // b = 10
a = a ^ b; // a = 20
System.out.println("a=" + a + ", b=" + b); // a=20, b=10Bu numara ilginç ama pratikte okunabilirlik açısından geçici değişken kullanmak daha iyi.
XOR ile Tekrar Eden Eleman Bulma
Klasik bir algoritma sorusu: bir dizide her eleman 2 kez geçiyor, sadece 1 eleman 1 kez. Onu bul.
int[] dizi = {4, 1, 2, 1, 2};
int tekil = 0;
for (int sayi : dizi) {
tekil ^= sayi;
}
System.out.println("Tekil eleman: " + tekil); // 4
// Neden çalışır?
// 4 ^ 1 ^ 2 ^ 1 ^ 2
// = 4 ^ (1 ^ 1) ^ (2 ^ 2)
// = 4 ^ 0 ^ 0
// = 4
// Çift geçenler XOR ile birbirini götürür!Bu algoritma O(n) zamanda ve O(1) bellekte çalışır — çok verimli.
~ (Bitwise NOT / Complement)
Tüm bitleri tersine çevirir. 0 → 1, 1 → 0.
int a = 5; // 00000000 00000000 00000000 00000101
int b = ~a; // 11111111 11111111 11111111 11111010
System.out.println(b); // -6Neden -6? Java negatif sayıları ikiye tümleyen (two's complement) ile saklar. ~x her zaman -(x + 1) sonucunu verir.
System.out.println(~0); // -1
System.out.println(~1); // -2
System.out.println(~(-1)); // 0NOT Ne İşe Yarar?
Belirli bir biti kapatma:
int izinler = 0b1111; // Tüm izinler açık
int yazma = 0b0010; // Yazma biti
izinler = izinler & ~yazma; // Yazma iznini kapat
System.out.println(Integer.toBinaryString(izinler)); // 1101~yazma = 1111...1101, bunu AND ile uygulayınca sadece yazma biti 0 olur, diğerleri korunur.
Kaydırma Operatörleri
<< (Sol Kaydırma)
Bitleri sola kaydırır, sağ taraf 0 ile dolar. Her kaydırma 2 ile çarpmaya eşdeğer.
int a = 5; // 00000101
int b = a << 1; // 00001010 = 10
int c = a << 2; // 00010100 = 20
int d = a << 3; // 00101000 = 40
System.out.println(b); // 10 (5 * 2)
System.out.println(c); // 20 (5 * 4)
System.out.println(d); // 40 (5 * 8)x << n = x * 2ⁿ
>> (Sağ Kaydırma — İşaretli)
Bitleri sağa kaydırır, sol taraf işaret biti ile dolar. Her kaydırma 2'ye bölmeye eşdeğer.
int a = 40; // 00101000
int b = a >> 1; // 00010100 = 20
int c = a >> 2; // 00001010 = 10
int d = a >> 3; // 00000101 = 5
System.out.println(b); // 20
System.out.println(c); // 10
System.out.println(d); // 5
// Negatif sayılarda işaret korunur
int neg = -8; // 11111111...11111000
int neg2 = neg >> 1; // 11111111...11111100 = -4
System.out.println(neg2); // -4x >> n ≈ x / 2ⁿ (negatif sayılarda tam eşdeğer değil, aşağı yuvarlar)
>>> (Sağ Kaydırma — İşaretsiz)
Bitleri sağa kaydırır, sol taraf her zaman 0 ile dolar. İşaret bitini korumaz.
int a = -1; // 11111111 11111111 11111111 11111111
int b = a >>> 1; // 01111111 11111111 11111111 11111111
System.out.println(b); // 2147483647 (Integer.MAX_VALUE)
int c = a >> 1; // 11111111 11111111 11111111 11111111
System.out.println(c); // -1 (işaret korundu)Flag Pattern — Bayrak Deseni
Bitwise operatörlerin en yaygın kullanım alanı: birden fazla boolean değeri tek bir int içinde saklamak.
public class Izinler {
static final int OKUMA = 0b0001; // 1
static final int YAZMA = 0b0010; // 2
static final int SILME = 0b0100; // 4
static final int YONETIM = 0b1000; // 8
public static void main(String[] args) {
int kullanici = 0; // Hiçbir izin yok
// İzin ekleme (OR)
kullanici = kullanici | OKUMA;
kullanici = kullanici | YAZMA;
// veya kısa: kullanici |= OKUMA | YAZMA;
// İzin kontrolü (AND)
boolean okuyabilir = (kullanici & OKUMA) != 0;
boolean silebilir = (kullanici & SILME) != 0;
System.out.println("Okuyabilir: " + okuyabilir); // true
System.out.println("Silebilir: " + silebilir); // false
// İzin kaldırma (AND NOT)
kullanici = kullanici & ~YAZMA;
boolean yazabilir = (kullanici & YAZMA) != 0;
System.out.println("Yazabilir: " + yazabilir); // false
// İzin toggle (XOR)
kullanici ^= SILME; // Silme izni yoktu, şimdi var
kullanici ^= SILME; // Vardı, şimdi yok
}
}💡 Bu pattern Unix dosya izinlerinde (
chmod 755), Android permission sisteminde, oyun geliştirmede yaygın kullanılır. Her bit bir özelliği temsil eder, tek birintile 32 farklı özelliği saklayabilirsin.
Gerçek Dünya Kullanımları
Renk İşlemleri (ARGB)
Renk değerleri genellikle 32-bit integer'da saklanır: 8 bit Alpha, 8 bit Red, 8 bit Green, 8 bit Blue.
int renk = 0xFFAA5533; // ARGB: FF=Alpha, AA=Red, 55=Green, 33=Blue
// Bileşenleri çıkarma
int alpha = (renk >> 24) & 0xFF; // 255
int red = (renk >> 16) & 0xFF; // 170
int green = (renk >> 8) & 0xFF; // 85
int blue = renk & 0xFF; // 51
System.out.printf("A=%d, R=%d, G=%d, B=%d%n", alpha, red, green, blue);
// Renk oluşturma
int yeniRenk = (255 << 24) | (100 << 16) | (200 << 8) | 50;
System.out.printf("Renk: #%08X%n", yeniRenk);Hızlı 2'nin Katı Kontrolü
// Bir sayının 2'nin kuvveti olup olmadığını kontrol et
int n = 16;
boolean ikininKuvveti = (n > 0) && ((n & (n - 1)) == 0);
System.out.println(n + " 2'nin kuvveti mi? " + ikininKuvveti); // true
// Neden çalışır?
// 16 = 10000
// 15 = 01111
// 16&15 = 00000 → 0 → 2'nin kuvveti!
// 12 = 01100
// 11 = 01011
// 12&11 = 01000 → 8 → 0 değil → 2'nin kuvveti değilHızlı Çarpma/Bölme
int x = 10;
// 2 ile çarpma (sol kaydırma)
System.out.println(x << 1); // 20
// 2'ye bölme (sağ kaydırma)
System.out.println(x >> 1); // 5
// 8 ile çarpma (3 bit sol)
System.out.println(x << 3); // 80⚠️ Modern JVM'lerde derleyici * 2 ve << 1 arasında otomatik optimizasyon yapar. Kaydırma operatörünü "hız için" kullanma — okunabilirlik daha önemli. Sadece bit manipülasyonu gerçekten gerekiyorsa kullan.
Kaydırma ile 2'nin Kuvvetleri
Kaydırma operatörleriyle 2'nin kuvvetlerini hesaplamak veya bölmek çok kolay:
// 1'i n bit sola kaydırınca 2^n elde edersin
System.out.println(1 << 0); // 1 (2^0)
System.out.println(1 << 1); // 2 (2^1)
System.out.println(1 << 2); // 4 (2^2)
System.out.println(1 << 3); // 8 (2^3)
System.out.println(1 << 10); // 1024 (2^10 = 1KB)
System.out.println(1 << 20); // 1048576 (2^20 = 1MB)
System.out.println(1 << 30); // 1073741824 (2^30 = 1GB)
// Buffer boyutu hesaplama (2'nin kuvveti)
int bufferSize = 1 << 16; // 65536 = 64KB
byte[] buffer = new byte[bufferSize];Compound Assignment (Bileşik Atama)
Bitwise operatörler de kısayol atama ile kullanılabilir:
int x = 0b1010;
x &= 0b1100; // x = x & 0b1100
x |= 0b0001; // x = x | 0b0001
x ^= 0b0010; // x = x ^ 0b0010
x <<= 2; // x = x << 2
x >>= 1; // x = x >> 1Pratik Örnek: Basit Yetki Sistemi
public class YetkiSistemi {
// Yetkiler (her biri farklı bir bit)
static final int GORUNTULE = 1; // 0001
static final int DUZENLE = 1 << 1; // 0010
static final int SIL = 1 << 2; // 0100
static final int YONET = 1 << 3; // 1000
// Roller
static final int OKUYUCU = GORUNTULE;
static final int EDITOR = GORUNTULE | DUZENLE;
static final int ADMIN = GORUNTULE | DUZENLE | SIL | YONET;
static boolean izinVar(int kullaniciYetki, int gerekliYetki) {
return (kullaniciYetki & gerekliYetki) == gerekliYetki;
}
static String yetkileriGoster(int yetki) {
StringBuilder sb = new StringBuilder("[");
if ((yetki & GORUNTULE) != 0) sb.append("GÖRÜNTÜLE ");
if ((yetki & DUZENLE) != 0) sb.append("DÜZENLE ");
if ((yetki & SIL) != 0) sb.append("SİL ");
if ((yetki & YONET) != 0) sb.append("YÖNET ");
return sb.toString().trim() + "]";
}
public static void main(String[] args) {
int ali = EDITOR; // Görüntüle + Düzenle
System.out.println("Ali yetkileri: " + yetkileriGoster(ali));
System.out.println("Görüntüleyebilir: " + izinVar(ali, GORUNTULE));
System.out.println("Düzenleyebilir: " + izinVar(ali, DUZENLE));
System.out.println("Silebilir: " + izinVar(ali, SIL));
}
}Bit Sayma ve Bit İşlem Metotları
Java'nın Integer ve Long sınıflarında kullanışlı bit işlem metotları var:
int sayi = 0b10110101; // 181
// Kaç bit 1?
System.out.println(Integer.bitCount(sayi)); // 5
// En yüksek 1 bitinin pozisyonu
System.out.println(Integer.highestOneBit(sayi)); // 128 (10000000)
// En düşük 1 bitinin pozisyonu
System.out.println(Integer.lowestOneBit(sayi)); // 1 (00000001)
// Baştaki sıfır sayısı
System.out.println(Integer.numberOfLeadingZeros(sayi)); // 24
// Sondaki sıfır sayısı
System.out.println(Integer.numberOfTrailingZeros(sayi)); // 0
// Bitleri ters çevir
System.out.println(Integer.toBinaryString(Integer.reverse(sayi)));
// Byte sırasını değiştir (endianness)
System.out.println(Integer.reverseBytes(0x12345678)); // 0x78563412EnumSet — Bitwise'ın Modern Alternatifi
Java'da flag pattern yerine EnumSet kullanmak daha tip güvenli ve okunabilir:
import java.util.EnumSet;
enum Izin {
OKUMA, YAZMA, SILME, YONETIM
}
public class ModernIzinSistemi {
public static void main(String[] args) {
// Bitwise flag yerine EnumSet
EnumSet<Izin> editorIzinleri = EnumSet.of(Izin.OKUMA, Izin.YAZMA);
EnumSet<Izin> adminIzinleri = EnumSet.allOf(Izin.class);
// İzin kontrolü
System.out.println(editorIzinleri.contains(Izin.OKUMA)); // true
System.out.println(editorIzinleri.contains(Izin.SILME)); // false
// İzin ekleme
editorIzinleri.add(Izin.SILME);
// İzin kaldırma
editorIzinleri.remove(Izin.SILME);
System.out.println(editorIzinleri); // [OKUMA, YAZMA]
}
}💡 Yeni kodda
EnumSetkullan. Bitwise flag pattern'ı eski API'ler ve performans kritik durumlarda hâlâ geçerli amaEnumSettip güvenliği sağlar ve hataya daha dayanıklıdır. Arka planda zaten bit vektörü kullanır, yani performans farkı yok.
Pratik Örnek: IP Adresi İşlemleri
public class IpAdresi {
// IP adresini int'e çevir (32 bit = 4 oktet)
static int ipToInt(String ip) {
String[] parts = ip.split("\\.");
return (Integer.parseInt(parts[0]) << 24) |
(Integer.parseInt(parts[1]) << 16) |
(Integer.parseInt(parts[2]) << 8) |
Integer.parseInt(parts[3]);
}
// int'i IP adresine çevir
static String intToIp(int ip) {
return ((ip >> 24) & 0xFF) + "." +
((ip >> 16) & 0xFF) + "." +
((ip >> 8) & 0xFF) + "." +
(ip & 0xFF);
}
// Subnet mask ile ağ adresi hesapla
static String networkAddress(String ip, String mask) {
int ipInt = ipToInt(ip);
int maskInt = ipToInt(mask);
return intToIp(ipInt & maskInt);
}
public static void main(String[] args) {
String ip = "192.168.1.100";
String mask = "255.255.255.0";
System.out.println("IP: " + ip);
System.out.println("IP (int): " + ipToInt(ip));
System.out.println("Ağ adresi: " + networkAddress(ip, mask));
// Ağ adresi: 192.168.1.0
}
}Bu örnek gerçek dünya kullanımını gösteriyor. Ağ programlama, düşük seviye sistemler ve oyun geliştirmede bitwise operatörler vazgeçilmez.
Hash ve Checksum Hesaplama
Bitwise operatörler hash fonksiyonlarının temelini oluşturur:
// Basit hash fonksiyonu (gerçek kullanım için uygun değil, eğitim amaçlı)
public static int basitHash(String str) {
int hash = 0;
for (char c : str.toCharArray()) {
hash = (hash << 5) - hash + c; // hash * 31 + c
// 31 ile çarpma: (x << 5) - x = x * 32 - x = x * 31
}
return hash;
}
// Java'nın String.hashCode() metodu tam olarak bunu yapar
String test = "Merhaba";
System.out.println(test.hashCode());
System.out.println(basitHash("Merhaba")); // Aynı sonuç!(hash << 5) - hash ifadesi hash * 31 ile aynı şey. Neden 31? Çünkü asal sayı ve JVM bunu kaydırma ile optimize edebilir.
Bit Maskeleme — Veri Paketleme
Birden fazla küçük değeri tek bir int içinde paketleyebilirsin:
public class VeriPaketleme {
// Bir tarihi tek int'te sakla: YYYYMMDD
// Yıl: 12 bit (0-4095), Ay: 4 bit (1-12), Gün: 5 bit (1-31)
static int tarihPaketle(int yil, int ay, int gun) {
return (yil << 9) | (ay << 5) | gun;
}
static int yilCikar(int paket) {
return (paket >> 9) & 0xFFF; // 12 bit
}
static int ayCikar(int paket) {
return (paket >> 5) & 0xF; // 4 bit
}
static int gunCikar(int paket) {
return paket & 0x1F; // 5 bit
}
public static void main(String[] args) {
int tarih = tarihPaketle(2026, 2, 19);
System.out.println("Paketlenmiş: " + tarih);
System.out.println("Yıl: " + yilCikar(tarih)); // 2026
System.out.println("Ay: " + ayCikar(tarih)); // 2
System.out.println("Gün: " + gunCikar(tarih)); // 19
System.out.println("Binary: " + Integer.toBinaryString(tarih));
}
}Bu teknik oyun programlama, ağ protokolleri ve gömülü sistemlerde çok kullanılır. Bellek ve bant genişliği tasarrufu sağlar.
Sık Yapılan Hatalar
1. & ile && karıştırmak:
// Mantıksal AND (short-circuit) — boolean ifadeler için
if (x > 0 && y > 0) { ... }
// Bitwise AND — sayılar için
int masked = value & 0xFF;
// Boolean'da tek & da çalışır ama short-circuit yapmaz!
// Her iki tarafı da değerlendirir — genellikle istemezsin2. Operatör önceliğini unutmak:
// Yanlış! == önceliği & 'den yüksek
if (flags & OKUMA == OKUMA) { ... } // flags & (OKUMA == OKUMA)
// Doğru — parantez koy
if ((flags & OKUMA) == OKUMA) { ... }3. Negatif sayılarda >> ve >>> farkını bilmemek:
int neg = -1;
System.out.println(neg >> 1); // -1 (işaret korunur)
System.out.println(neg >>> 1); // 2147483647 (işaretsiz)4. Sol kaydırmada taşma:
int x = 1;
System.out.println(x << 31); // -2147483648 (negatif!)
System.out.println(x << 32); // 1! (32 bit döndü, 32 % 32 = 0)
// Java'da kaydırma miktarı int için mod 32, long için mod 64 alınır5. byte ile bitwise işlem yaparken sign extension:
byte b = (byte) 0xFF; // -1 (signed)
int i = b; // 0xFFFFFFFF (-1, sign extended!)
int j = b & 0xFF; // 0x000000FF (255, doğru!)
// byte'ı unsigned olarak kullanmak istiyorsan & 0xFF yapÖzet
& (AND): İki bit de 1 ise 1 — maskeleme ve bit kontrolü için kullan
| (OR): En az biri 1 ise 1 — bitleri açmak (set) için kullan
^ (XOR): Farklıysa 1 — toggle ve basit şifreleme için kullan
~ (NOT): Bitleri tersle — belirli bir biti kapatmak için
& ~maskepattern'ı kullan<< ve >>: Sol/sağ kaydırma, 2'nin kuvvetleriyle çarpma/bölme etkisi yapar
Flag pattern ile tek bir
intiçinde birden fazla boolean bilgi saklayabilirsin — yetki ve izin sistemlerinde yaygınModern Java'da flag pattern yerine `EnumSet` tercih et — tip güvenli ve performanslı
Hash fonksiyonları, renk işlemleri, veri paketleme, IP adresi manipülasyonu gibi alanlarda bitwise vazgeçilmez
byteile çalışırken `& 0xFF` maskesini unutma — sign extension tuzağı sık yaşanır
Mülakat Soruları
S: & ile && farkı nedir? C: & bitwise AND'dir, her zaman iki tarafı da değerlendirir. && mantıksal AND'dir, short-circuit yapar (sol taraf false ise sağ tarafa bakmaz). Boolean ifadelerde && kullan.
S: XOR ile swap nasıl yapılır? C: a ^= b; b ^= a; a ^= b; — ama pratikte geçici değişken kullanmak daha okunabilir ve güvenli.
S: Bir sayının 2'nin kuvveti olduğunu nasıl kontrol edersin? C: (n > 0) && ((n & (n - 1)) == 0) — 2'nin kuvvetlerinde sadece 1 bit 1'dir, n-1 ile AND'lediğinde 0 çıkar.
S: -1'in binary gösterimi nedir? C: Tüm bitler 1: 11111111 11111111 11111111 11111111 (32 bit). İkiye tümleyen sisteminde -1 böyle saklanır.
AI Asistan
Sorularını yanıtlamaya hazır