20 Nisan 2011 Çarşamba

Bileşke Türler-Diziler

Bir problemin çözümü, problem tanımında geçen varlıkların çözüm ortamındaki araçlar tarafından doğrudan temsil edilmesi durumunda kolaylaşır. Mesela; bir çizimin çizgi, Bézier eğrisi, üçgen, kare gibi çeşitli geometrik nesneleri destekleyen ve bu nesnelere ilişkin çizme, döndürme, öteleme gibi uzmanlaşmış işlemleri sağlayan bir dil kullanılarak oluşturulması daha kolay olacaktır. Matris, denklem gibi matematiksel nesneleri ve ilişkin işlemleri doğrudan destekleyen Maxima ve Matlab gibi dillerin cebir problemleri çözmek için daha uygun olmasının sebebi de aynıdır. Her iki durumda da problem uzayındaki nesneler ile uygulanabilir işlemler ve bu öğelerin soyutlanmasıyla oluşturulan kavramlar ile çözümde kullanılan araçlar arasında eşleme çok basittir. Dolayısıyla, problem uzayını iyi bilen bir programcının üretken olması çok hızlı bir şekilde mümkün olabilmektedir.

Özel amaçlı diller için geçerli olan bu gözlem, Java gibi genel amaçlı olma iddiasındaki diller için geçerli değildir. Böylesine bir yaklaşım, söz konusu dili değişik ilgi gruplarının kavramlarıyla hantallaştıracak ve diğer ilgi gruplarına kullanılmaz hale getirecektir. Bunun yerine programlama dili, ilkel türler🔎 üzerine inşa edilen bir yeni tür tanımlama olanağı sağlar. İşte, yavaş yavaş Java'yı Java yapan bölgeye giriş yapacağımız bu yazıda, Fortran'ın ilk uyarlamalarından bu yana bizimle olan en eski [ve eskimeyen] tür işleciyle (İng., type operator) oluşturulan bileşke türlere, dizilere göz atacağız.1 Ancak, kısaca türdeş verilerin gruplanmasıyla oluşturulan bileşke türler olarak tanımlanabilecek dizilere girmeden önce, diğer bileşke türlerde de sıklıkla kullanacağımız terminolojiyi oluşturacağız.

TutacakVeNesne.java
public class TutacakVeNesne {
  public static void main(String[] ksa) {
    String ad1 = new String("Tevfik");
    String ad2 = new String("Tevfik");
    String ad3 = ad1;
    System.out.println(ad1 == ad2);
  } // void main(String[]) sonu
} // TutacakVeNesne sınıfının sonu
$ javac -encoding utf-8 TutacakVeNesne.java
$ java TutacakVeNesne
false

Yukarıdaki programın çalıştırılması sonrasında 6. satırda yapılan eşitlik denetimi, kimilerinizin beklentilerine aykırı olarak, standart çıktıya—değiştirilmediği müddetçe ekran—false yazacaktır. Bunun sebebi 6.satıra gelinmesiyle oluşan bellek seriliminin aşağıdaki temsili resmi ile açıklanabilir. Görüleceği gibi, 3. ve 4. satırda yaratılan iki nesneye üç değişken kullanılarak atıfta bulunulmaktadır.


Nesne yaratmak için kullanılan new işleci, belleğin yığın bölgesinde yeri ayrılan nesnenin işlenebilmesi için bir tutacak2 döndürmekte ve nesne döndürülen bu tutacağın değerine sahip tanımlayıcılar yoluyla dolaylı bir biçimde kullanılmaktadır. Buna göre, örneğimizde iki nesne yaratılmakta ve söz konusu nesnelerin tutacakları ad1 ve ad2 değişkenlerini ilklemek için kullanılmaktayken, ad1'in ad3'ü ilklemekte kullanılmasıyla iki tutacak3 da aynı nesneyi gösterir hale getirilmektedir. Bunun doğal bir sonucu olarak, tutacakları karşılaştırarak işini gören 6. satırdaki eşitlik denetimi de false döndürmektedir. Çünkü, temsil ettikleri nesnelerin içeriği aynı olmakla birlikte iki tutacak da farklı nesneleri göstermektedir. Eşitlik denetiminin nesne içeriği göz önüne alınarak yapılması isteniyorsa, ad1 tutacağına temsil ettiği nesneyi ad2 tutacağının temsil ettiği nesne ile karşılaştırmasını söyleyen equals iletisinin gönderilmesi gerekir. Aşağıdaki gibi yapılacak ileti gönderiminin sonrasında, ad1'in arkasındaki nesnenin türü olan String sınıfındaki equals metodu çağrılacak ve işlem tamamlanacaktır.
System.out.println(ad1.equals(ad2));
Programcının, yapısı hakkında tahminde bulunup nesneyi tutarsızlığa yol açabilecek binbir yoldan denetimsiz bir şekilde kullanımının önüne geçen bu özellik—tutacak nesne ayrımı—Java'nın programcının bilerek veya bilmeyerek kod güvenliğini tehlikeye atmasına izin vermeyeceğini gösterir. Her şey, nesnenin yaratılması sonrasında döndürülen tutacak aracılığıyla gönderilecek iletilerin çağrılmasına neden olacağı metotlar vasıtasıyla yapılır.4 Tutacak türü ile uyumlu olmayan ileti gönderimleri derleyici tarafından reddedilecektir.

Gelelim dizilere. Derleyici tarafından özel bir biçimde ele alınan sınıflar olan dizi türlerine ait nesneler, diğer bileşke türlü değerler gibi, yaratılır, kullanılır, ihtiyaç duyulmadıkları ilk noktada çöpe dönüşür ve gerekli olursa kapladıkları bellek alanı tekrar kullanılmak üzere çöp toplayıcı tarafından geri döndürülür. Ayrıca, bir dizi nesnesinin kullanımı sırasında eleman sayısının gereksinime göre artmayacağı, bu özelliğin nesnenin yaratılması noktasında sabitlendiği akıldan çıkarılmamalıdır. Aşağıdaki örnek üzerinden görelim.

Diziler.java
import java.util.Scanner;
import static java.lang.System.out;

public class Diziler {
  public static void main(String[] ksa) {
    Scanner grdKnl = new Scanner(System.in);
    int[] notlar = new int[3];
    for (int i = 1; i <= 3; i++) {
      out.print(i + ". notu giriniz: ");
      notlar[i - 1] = grdKnl.nextInt();
    }
    out.println("İkinci dizinin eleman sayısı: ");
    int[] yeniNotlar = new int[grdKnl.nextInt()];
    yeniNotlar = notlar;
    yeniNotlar[1] = notlar[1] + 5;
  } // void main(String[]) sonu
} // Diziler sınıfının sonu
Dizi nesnelerimizin yaratıldığı satırları ele alalım. 7 nolu satırdaki ilkleme komutunda dizi tutacağının adı olan notlar, int[] türüne sahip tanımlanıyor ve her üç elemanı da 0 ilk değerine sahip bir dizi nesnesini gösterecek şekilde ilkleniyor. 13. satırda, benzer bir işlem standart girdiden sağlanacak sayıda elemana sahip bir başka dizi için tekrarlanıyor. Dikkatinizden kaçmamıştır, her iki tutacak da int[] türlü ilan ediliyor; her iki durumda da eleman sayısına dair bilgi tanımda yer almıyor. Diğer bir deyişle, notlar ve yeniNotlar tutacaklarının ikisi de aynı türe sahip olacak şekilde tanımlanıyor.

Dizilere uygulanabilecek işlemler, eleman değerlerinin sorgulanması ve güncellenmesine sınırlıdır. Her iki işlem de, hedef elemanın sırasını belirleyen aritmetiksel deyimin arasına yerleştirildiği köşeli ayraç çiftini ([ ve ]) kullanır. İki işlemden hangisinin istendiği ayraç çiftinin kullanım yerinden anlaşılır. Örneğin, 15. satırdaki atama komutunun sol tarafındaki kullanım söz konusu diziye güncelleme yapıldığını gösterirken, sağ taraftaki kullanım sorgulama yapıldığına işaret eder. İşleme konu elemanın sırasını tayin eden ve indis olarak da adlandırılan aritmetik deyimin 0-başlangıçlı bir değer döndürmesi gerektiği unutulmamalıdır. Buna bağlı olarak, notlar dizisinin son elemanına aşağıdaki ifadeler ile erişilebilir. Opsiyonları anlamaya çalışırken, her dizi nesnesinde bulunan length adlı altalanın dizinin eleman sayısını tuttuğunu unutmayın.
notlar[2] = 333; // notlar[3] değil!!!
notlar[notlar.length - 1] = 333; // Daha iyi bir seçenek.
Belirlendikten sonra değişmeyen eleman sayısı, yenitNotlar dizisinde olduğu gibi çalışma anında belirlenebileceği için yukarıdaki kullanımlardan ikincinin seçilmesi yerinde olacaktır. Aksine bir tercih, her eleman sayısı değişiminde kodun ilişkin yerlerinde değişiklik yapma ihtiyacını doğurur.

Değineceğimiz bir diğer husus, eleman değerlerinin dizi nesnesinin yaratıldığı noktada sağlanması durumunda kullanılabilecek ve kaynak kodu anlamak açısından olumlu bir katkıda bulunan toptan ilkleme komutudur. Aşağıda örnekleri verilen bu komut sayesinde, dizi elemanlarına tanım noktasından farklı bir yerde atama yapılarak kodun uzamasına gerek kalmaz. Dikkat edecek olursanız, toptan ilklemenin kullanıldığı new işlecine dizimizin eleman sayısının verilmesi söz konusu değildir; programcının hatalı bir biçimde belirleme ihtimali bulunan bu değeri derleyici kendisi belirler.
int[] notlar = new int[]{60, 70, 80};
int[] yeniNotlar =
        new int[]{grdKnl.nextInt(), grdKnl.nextInt()};
Daha sonraki kimi yazılarımızda dönme şansını bulacağımız diziler konusunu, şu uyarıyı yineleyerek kapatalım: dizi türleri, derleyici tarafından özel bir biçimde ele alınan sınıflardır. Buna göre, örneğimizdeki notlar tutacağının yeniNotlar'a atanması, yeniNotlar'ın eskiden gösterdiği dizi nesnesini çöpe dönüştürür ve her iki tutacağın da aynı dizi nesnesini paylaşmasına neden olur. Dolayısıyla, bir dizi tutacağı yoluyla yapılacak güncelleme diğeri aracılığyla da görülecektir. Aşağıdaki temsili bellek seriliminden bunun nedenini daha kolay görebilirsiniz.
yeniNotlar = notlar;
yeniNotlar[1] = notlar[1] + 5;

  1. Dizilerin ilk olma şerefi, çalışmakta olan programın karalama defteri olarak düşünülebilecek birincil belleğin, genelde "kutucuklar" dizisi olarak kavramsallaştırılmasına dayanır. Dolayısıyla, sakın ola ki, bileşke türler içinde ilk olması nedeniyle dizilerin yararsız olduğunu düşünmeyin. Daha sonraki yazılarda değineceğimiz kimi olumsuz yönlerine rağmen, veri yapısı olarak dizilerin de kullanımının uygun olduğu pek çok durum olacaktır.
  2. Diğer dillerden aşırma sözcükleri kullanmayı profesyonelliğin belirleyici özelliği zannedenler, ki bu arkadaşlar genelde bir şey anlatmak için değil anlatmamak için konuşurlar, hendıl (İng., handle) demeyi yeğleyebilirler. Ancak, sözcük tercihimdeki mantığı açıklarsam bu arkadaşları da sanırım tarafımıza kazanabiliriz. Bunun için, bir tavayı yemek pişirmekte nasıl kullandığınızı düşünün. Tavanın tutacağı olan sapıyla değil mi? Dolayısıyla, nasıl ki, aklı başında insanlar tavayı sapı yardımıyla kullanırlar, nesneye aracısız erişim imkanının olmadığı Java'da nesneler tutacakları aracılığıyla kullanılırlar.

    Bu noktada, nadiren de olsa tutacak yerine tutamak sözcüğünü önerenlere de ufak bir tavsiyem var. Bir şeye tutunmakta kullanılan gövde üzerindeki oyuk veya çıkıntıların adı olan tutamak, maalesef, doğru bir seçim değil. Çünkü, adını koymaya çalıştığımız kavram nesnenin gövdesinde bulunmuyor ve yapılan atamalarla değişik zamanlarda değişik nesneleri temsil edebiliyor.
  3. Bundan sonraki anlatımımızda, "... nesnesinin tutacağına sahip tanımlayıcı" demektense, tutacak demeyi tercih edeceğiz.
  4. Nesne paradigmasına sadık kalınarak yapılan bu anlatım her zaman geçerli olmayacaktır. static olarak nitelenen metotlar, sadece tüm sınıf üyelerinin paylaştığı ortak özellikleri kullanarak işini görür ve ileti gönderilmeden doğrudan çağrılırlar.

15 Nisan 2011 Cuma

boolean Türü ve İlkel Türler Arasında Tür Uyumluluğu

İlkel türleri tanıtmaya devam ettiğimiz bu yazıda, çalışmakta olan programda bir koşulun oluşup oluşmadığını denetlemek amacıyla kullanılan boolean türüne baktıktan sonra ilkel türden değerlerin birbirleri yerine kullanılabilme özelliklerine göz atacağız.

boolean Türü

Koşullu ve yinelemeli komut işlemenin temeli olan doğruluk denetimi, boolean türlü sabitler olan true veya false değerlerinden birini üreten ilişkisel işleçlerin1 kullanılmasıyla yapılır. Denetlenmesi istenen koşulun karmaşıklaşması durumunda ilişkisel işleçli ifadeler mantıksal işleçler2 kullanılarak birleştirilebilir.3 Bir örnekle görelim.

Faktöryel.java
import java.util.Scanner;

public class Faktöryel {
  public static void main(String[] ksa) {
    Scanner grdKnl = new Scanner(System.in);
    System.out.print("Faktöryeli bulunacak sayı: ");
    int n = grdKnl.nextInt();
    System.out.println(n + "!: " + fakt(n));
  } // void main(String[]) sonu

  public static long fakt(int n) {
    if (n == 1 || n == 0)
      return 1;
      else return n * fakt(n-1);
  } // long fakt(int) sonu
} // Faktöryel sınıfının sonu

Programımız, standart girdi dosyasından—değiştirilmediği müddetçe klavye—girilen karakterleri tarayarak istenen türden veriler haline dönüştüren Scanner türündeki grdKnl kullanılarak alınan tamsayı değerin faktöryelini hesaplıyor. Bu amaçla yazılmış olan özyinelemeli fakt metodunun sonsuz döngüye girmesini engellemekte kullanılan koşul deyimi, eşitlik denetleme işleci (==) ile oluşturulan iki altdeyimin veya bağlacı (||) ile birleştirilmesiyle oluşturuluyor. Buna göre, metottan parametre değerinin 0 veya 1'e eşit olması durumunda 1, aksi takdirde parametrenin 1 küçük değerin faktöryeli ile çarpımı döndürülüyor.

Mantıksal türün sonradan eklendiği C'nin yerleştirdiği alışkanlıklar sonucu, C/C++'da bir koşulun oluşmama durumu sıfır-benzeri bir değerle (NULL, 0,0, 0, vd.) temsil edilebilirken, oluşma durumu sıfır-benzeri olmayan herhangi bir değerle temsil edilebilir. Tür güvenliğine önem veren Java'da bu alışkanlığın unutulması gerekecektir. Dolayısıyla, aşağıdaki kod parçası Java derleyicisi tarafından kabul edilmez.

if (n % 2)
  System.out.println(n + " çift değil");
  else System.out.println(n + " çift");

İlkel Türler Arasında Tür Uyumluluğu

C/C++ dünyasından aşina son satırların Java derleyicisi tarafından kabul görmemesi, tamsayıların boolean değerler ile tür uyumlu olmadığı şeklinde ifade edilir. C/C++ dilinin "Programcı hata yapmaz, hata gibi gözüken şey muhtemelen yaratıcı bir kestirmedir" anlayışına karşılık, "Programcı da bir insandır ve hata yapabilir" felsefesinden hareket eden Java, belirtiyi ciddiye alır ve olası hatanın başkalaşmasına izin vermeden programcıya en erken aşamada (derleme) durumu bildirmeyi tercih eder.

Tamsayıların mantıksal değerlerin yerine kullanılamamasına benzer bir şekilde, byte türlü bir tanımlayıcıya int türlü bir değer sağlanması da hata olarak görülecektir. Bunun sebebi, int türünün temsil aralığının byte türünün temsil aralığında bulunmayan değerler içermesidir. Mesela, yukarıda verilen Faktöryel.fakt metodunun parametre türü byte olacak biçimde değiştirilmesi hataya neden olacaktır. Çünkü, söz konusu metodun çağrıldığı satırda geçirilen argüman int türlüdür ve -231 ile 231-1 arasındaki herhangi bir değere sahip olabilir; buna karşılık, argümanın değeriyle ilklenecek parametre byte türlüdür ve -128 ile 127 arasındaki değerleri temsil edebilir. İşte bu yüzdendir ki, parametre türünün byte olarak değiştirilmesi durumunda geçirilen argüman değerinin aşağıdaki kod parçasında olduğu gibi biçimlendirilmesi gerekir.

import java.util.Scanner;

public class Faktöryel {
  public static void main(String[] ksa) {
    ...
    System.out.println(n + "!: " + fakt((byte) n));
  } // void main(String[]) sonu

  public static long fakt(byte n) { ... } // long fakt(int) sonu
} // Faktöryel sınıfının sonu

Pek çok—kesin olmak gerekirse, sekiz—ilkel türün varlığı, tür uyumluluğu konusunda karar verme işini zorlaştırabilir. Bu noktada, türlerin kayan noktalı ve tamsayı türler şeklinde dizilmesi imdadınıza yetişecektir.

doublefloatlongintshortbyte
intchar
doubleboolean = ∅

Yukarıdaki geçişken üstküme-altküme ilişkisi ile resmedilen dizilim, en baştaki türün en kapsayıcı en sondakinin ise en az kapsayıcı olduğu şeklinde yorumlanmalıdır.4 Buna göre, double değerin beklendiği yerlerde diğer tüm sayısal türlerden değerler kullanılabilir. Buna karşılık, short değerin beklendiği yerlerde sadece byte türlü değerler kulanılabilirken, diğer tüm sayısal değerler ancak biçimlendirme yapılmak suretiyle kullanılabilecektir.

Dizilimde char türü için ayrı bir kapsama ilişkisinin tanımlanması, bu türün tamsayı türü olarak ele alınması durumunda eksi sayıları temsil edemeyecek olmasından kaynaklanır. Aynı büyüklükte bir alan kaplayan short -32768 ile 32767 aralığındaki değerleri temsil edebilirken, char 0 ile 65536 arasındaki değerleri temsil edebilir. Bir diğer deyişle, short'un char, char'ın short yerine konulması kimi zaman değer kaybına sebep olacaktır.


  1. Yararlanılabilecek ilişkisel işleçler ve varlığı denetlenen ilişki çeşidi şunlardır: == (eşitlik), != (eşitsizlik), > (büyüklük), < (küçüklük), >= (büyük veya eşitlik), < (küçük veya eşitlik).
  2. Mantıksal değerleri birleştirmede kullanılabilecek bağlaçlar ve anlamları şunlardır: ! (değilleme), && (ve), || (veya).
  3. Gerekli görüldüğü takdirde, denetleme işinin tümü veya bir bölümü, yüklem olarak da adlandırılan boolean dönüş türlü bir metodun çağrılması ile yapılabilir.
  4. Aslına bakacak olursanız, tür uyumluluğu için üstküme-altküme ilişkisinin kullanılması yanıltıcı bir uygulama. Çünkü, long ile temsil edilen kimi değerler ne double ne de float ile temsil edilebilirken, int ile temsil edilen kimi değerler float ile temsil edilemez. Ancak, anılan türler arasında dizilimin önerdiği cinsten bir tür uyumluluğundan bahsetmek her zaman doğrudur.

13 Nisan 2011 Çarşamba

Java SE 7 İle Gelen Yenilikler

Bu yazıda, pek yakında piyasaya çıkacak olan Java SE 7'nin Java programlama diline getirdiği yeniliklere göz atacağız. Yeni uyarlamanın tartışmalarını J2SE 5.0 ile eklenen yeniliklerin—soysallık🔎 (İng., generics), açımlamalar (İng., annotations), sabit listeleri (İng., enum types), vd.—getirdiği zorlu öğrenme eğrisini hatırlayıp içi ürpererek izleyenlere müjdeyi vererek başlayalım: daha önceleri eklenmesi düşünülen birim (İng. module) ve kod örtüsü (İng. closure) kavramlarının bir sonraki uyarlamaya bırakılması nedeniyle, yeni uyarlamanın J2SE 5.0'de olduğu gibi zorlayıcı olması söz konusu değil. Ama, siz yine de yerlerinizde rahat oturmayın; çünkü, bu sadece bir erteleme, her iki kavram da eninde sonunda Java dilinin üstkavram dağarcığına—yani, problem uzayındaki kavramları koda dökerken kullanılan kavramlar listesine—eklenecek.1

Yeniliklerin içeriğine girmeden önce, okumakta olduğunuz yazının günlükteki diğer yazılardan belirgin bir farklılık gösterdiğini söylemekte yarar var. Daha öncekiler tek bir konuya odaklı ve basit içerikli iken, bu yazı Java altyapınızın sağlam olduğunu varsayıyor ve farklı konulara atıfta bulunuyor. Bir diğer uyarı da yenilikleri deneyip kullanmak isteyeceklere yönelik: söz konusu yeniliklerin hiçbiri yeni bir Bytecode komutu kullanılması sonucunu doğurmadığı için, Java SE 7 uyumlu bir derleyici ile üretilen sınıf dosyaları daha önceki uyarlamaların JSM'leri tarafından çalıştırılabilecektir.2


switch-case Komutlarında String Türlü Seçici İfade

switch-case komutlarındaki seçici ifadenin türü hakkındaki kısıtlamanın String türünü kapsayacak şekilde gevşetilmesi ile başlayalım. Buna göre, eskiden seçici ifadenin tamsayı türleri ve char'a kısıtlı olması nedeniyle geçerli olmayan aşağıdaki kod parçası, Java SE 7 ile birlikte Java derleyicileri tarafından kabul görecektir.
import java.util.Locale;
import static java.lang.System;
...
devamMı:
do {
  switch (seçim.toUpperCase(new Locale("tr"))) {
    case "EVET": case "YES": ...; break devamMı;
    case "HAYIR": case "NO": ...; break devamMı;
    default: out.println("Evet veya Hayır giriniz!!!");
  }
} while(true);
...

İkili Taban Gösterimi ve Rakamların Gruplanması

Bir diğer küçük yenilik, tamsayı sabitlerin artık 8'li, 10'lu ve 16'lı tabanın yanısıra 2'li tabanda da yazılabilecek olması. Ayrıca, sayısal sabitlerin, ister tamsayı türlü olsun isterse kayan noktalı, doğru ve çabuk algılanmasını sağlamak için altçizgi karakteri ('_') kullanılarak dilendiği gibi gruplanması da mümkün.
double bütçeAçığı = 8_000_000_000_000_000;
double piSayısı = 3.1415_92_6535_897_93;
short bayrakYazmacı = 0b0000_010010_000100;
...
Bir sonraki yeniliğe geçmeden bir uyarıda bulunalım: gruplama karakteri sayıyı oluşturan bileşenlerin (tamsayı kısmı, kesir kısmı, üs kısmı) başında veya sonunda kullanılamaz. Buna göre, aşağıdaki örneklerin hiçbiri Java derleyicisinin kabulünü görmeyecektir.
double bütçeAçığı = _8000_000_000_000_000;
double piSayısı = 3_.1415_92_6535_897_93;
short bayrakYazmacı = 0b0000_010010_000100_;
double büyükSayı = 0x1.2345p_123;
double küçükSayı = 0x1.2345p-123_;
...

Kaynak Yönetimli try Bloğu

Java'yı C/C++ dilinden ayıran temel özelliklerden birisi, geliştiricinin insan olduğu ve hata yapabileceği varsayımına binaen bazı sorumlulukları geliştiriciden derleyici ve sanal makine gibi sistem yazılımlarına aktarmasıdır. Bunun en bilinen örneği, çöp toplayıcı yardımıyla yığın bellek yönetimini yazılım geliştirme aşaması etkinliği olmaktan çıkarmasıdır.3 Ancak, çöp toplama desteği kaynak yönetme kaygılarının tamamen ortadan kalktığı anlamına gelmez; geliştiriciler yığın bellek dışındaki dosya, ağ bağlantısı, veri tabanı bağlantısı gibi kaynakları yönetmek zorundadırlar. Örnek olarak, bir veri tabanı tablosunun yazma kilidini düşünün. Kilidin sahibi olan program işini bitirip tabloyu serbest bırakmadıkça bir diğer programın aynı tabloda değişiklik yapması mümkün olmayacaktır; işin bitirilip ne zaman dönüleceğini ise ancak programı yazan geliştirici(ler) bilebilir. Dolayısıyla, bu tür kaynakların mümkün olan en erken noktada geliştirici tarafından döndürülmesi gerekir. Aksi bir durum, aynı kaynağın diğer kullanıcılarının boş yere beklemesi anlamını taşır. İşte bu yüzden, yeni uyarlama ile birlikte Java, try bloğu içinde yararlanılan kaynakların otomatik olarak döndürülmesini garanti ederek olası programcı hatasının önüne geçen kaynak yönetimli try bloğunu sunmaktadır.

Ne denmek istendiğini aşağıdaki kod şablonundan izleyerek görelim. İlk satırda, try'ı takip eden ayraç çifti arasındaki ilkleme komutu, Java derleyicisine program içinde d tanımlayıcısı yoluyla temsil edilen disk dosyasının otomatik olarak yönetilmesi istendiğini belirtiyor. Bu istemin yerine getirilmesi, işlenmesi ne şekilde sona ererse ersin, try bloğu sonunda söz konusu kaynağın geri döndürüleceği anlamına gelir.
try (FileInputStream d = new FileInputStream("Veri.dat")) {
  // d'yi kullan.
} catch (IOException e) { ... }
Örneğimiz otomatik olarak yönetilmesi istenen kaynağın Java SE 7 ile birlikte eklenen java.lang.AutoCloseable arayüzünü gerçekleştirmesiyle geçerlilik kazanır. Bu, java.lang.Closeable arayüzünün bu arayüzden kalıtlayacak şekilde tanımının değiştirilmesi nedeniyle, G/Ç sınıflarınca temsil edilen tüm kaynakların kaynak yönetimli try bloğuyla otomatik olarak yönetilebileceği anlamına gelir.

finally kotarıcısını (İng., handler) bilenleriniz—sabırsızlıklarını görür gibiyim—kaynak yönetimli try komutunun aslında pek de bir şey getirmediğini düşünebilir. Ne de olsa, yukarıda anlatılan işlev kaynak döndürme komutunu finally bloğu içine yazarak da sağlanabilir. Bir diğer deyişle, aşağıdaki kod şablonu da işimizi görecektir.
try {
  // d'yi kullan.
} catch (IOException e ) { ... }
  finally { d.close(); }
Bu itirazı seslendiren arkadaşlar bir yere kadar haklı. Ne var ki, şu iki nokta onları da ikna edecektir.
  • try ve catch bloklarının işlemesi sırasında JSM sonlan(dırıl)acak olursa veya söz konusu işletilen bloğun izleği (İng., thread) durdurulur veya sonlandırılacak olursa, finally bloğunun işletileceği garantisi verilemez.
  • Kaynak yönetimli try komutunun varlığı, programcıya hatırlatıcı olmasının yanısıra belgeleme yönüyle de katkıda bulunur.

Kotarıcı Paylaşımı ve Yeniden Fırlatmanın Yeni Anlamı

Kimi zaman, birden çok ayrıksı durum kotarıcısının aynı gövdeye sahip olması durumuyla karşılaşabiliriz. Aynı kodun tüm kotarıcılara tekrar tekrar yazılması, kod şişmesinin yanında gerekebilecek bir değişikliğin birden çok yerde yapılması sonucunu doğurur. İşte bu noktada, Java'nın yeni uyarlaması programcıya kotarıcı paylaşımı şansını verir. Buna göre, aşağıda verilen try bloğu içindeki komutların icra edilmesi sırasında AyrıksıDurumA veya AyrıksıDurumB türlü bir ayrıksı durum ortaya çıkacak olursa, aynı kotarıcı soruna çözüm bulmaya çalışacaktır.
...
try {
  // AyrıksıDurumA, AyrıksıDurumB veya AyrıksıDurumC türünde
  // ayrıksı durumların ortaya çıkabileceği bir şeyler yap.
} catch (AyrıksıDurumA | AyrıksıDurumB e} { ... }
  catch (AyrıksıDurumC e) { ... }
  finally { ... }
...
Kotarıcı paylaşımında şu nokta unutulmamalıdır: ayrıksı durum nesnesi kullanılarak kotarıcı içinde yapılacak şeyler kotarıcı parametresinin türlerinin ortak paydasına sınırlı kalacaktır. Dolayısıyla, yukarıda sağlanan paylaşım örneğinde e'nin kullanımı AyrıksıDurumA ve AyrıksıDurumB türlerinin ortak atasına gönderilebilecek iletilere sınırlı olacaktır. Bunun doğal bir sonucu olarak, parametrenin türleri arasında biri diğerinin atası olan sınıfların bulunması bir hata olarak görülecek ve söz konusu kod derleyici tarafından kabul edilmeyecektir.4

Hata kotarımını ilgilendiren ikinci yenilik, yeniden atılan ayrıksı durum nesnelerinin ele alınışına yönelik. Java SE 7 öncesinde hata verecek aşağıdaki kod parçasından izleyelim.
public void birMetot() throws AyrıksıDurumA, AyrıksıDurumB {
  ...
  try {
    birŞeylerYap(...); // AyrıksıDurumA fırlatabilir.
    başkaBirŞeylerYap(...); // AyrıksıDurumB fırlatabilir.
  } catch (Exception e} {
      ... // Bir şeyler yap.
      throw e;
    }
  ...
} // void birMetot() sonu
İşlerin yolunda gitmemesi halinde, birMetot metodundaki try bloğu, AyrıksıDurumA veya AyrıksıDurumB türlü bir ayrıksı durumla sonlanabilir. Ayrıksı durum nesnesinin fırlatılması sonrasında, Exception türlü parametreye sahip kotarıcı devreye girecek ve ortalığa çeki düzen verdikten sonra parametreyi yeniden fırlatarak topu metodun çağrıcısına atacaktır. İşte bu noktada, Java SE 7'nin farkı ortaya çıkıyor: derleyicinin try bloğu içinden çağrılan metotların imzalarını incelemesi sonucunda kotarıcıda yeniden fırlatılan nesnenin Exception değil de AyrıksıDurumA veya AyrıksıDurumB türünde olabileceği bilgisi korunuyor. Bu nedenledir ki, önceki uyarlamalarda Exception fırlatabileceği ilan edilmek zorunda kalınan birMetot, artık AyrıksıDurumA ve AyrıksıDurumB fırlatabileceğini ilan eden bir imza ile tanımlanabiliyor.

Bu bölümü küçük bir uyarı ile kapatalım: Değindiğimiz her iki yeniliğin kullanımında da kotarıcı parametresinin final olduğu varsayılır. Bir başka deyişle, söz konusu parametre yeni bir ayrıksı durum nesnesini gösterecek şekilde değiştirilemez; parametrenin değiştirilmesi anlatılan yeniliklerin kullanılmasını engeller.

Sınırlı Tür Çıkarsama: Elmas İşleci

Şapkadan çıkaracağımız bir sonraki yenilik de kendini derleyicinin yaptığı ek mesainin programcıya sağladığı kolaylıkla gösteriyor: tür çıkarsama. Ancak, Haskell veya ML bilenleriniz beklentilerini yüksek tutmasın. Çünkü, Java derleyicilerinin yeni uyarlamayla birlikte—soysallığın eklenmesi sonrasında sağlananın üstüne—sağlayacağı tür çıkarsama özelliği soysal türlerin nesnelerinin yaratıldığı kullanımlara kısıtlı. Derleyicinin tür çıkarsama için işaret olarak algıladığı <> karakterlerinin görünüşünden dolayı elmas işleci olarak da adlandırılan bu yenilik aşağıdaki gibi kullanılabilir.
import java.util.*;
...
Map<String, Vector<String>> sözlük =
  new HashMap<String, Vector<String>>();

Map<String, Vector<String>> yeniSözlük = new HashMap<>();

SafeVarargs Açımlaması

Göz atacağımız son yenilik olan SafeVarargs açımlaması, sınıf kitaplıkları yazanlarınız dışındakileri doğrudan etkilemiyor. Ancak, kitaplık kullanıcıları da kimi zaman anlamakta zorlandıkları derleyici uyarılarının azalması nedeniyle epey mutlu olacaklar. Ne kastettiğimizin anlaşılması için önce bu yeni açımlamanın ne zaman gerekeceğinin bir örneğini verelim.

SVÖrneği.java
import java.util.*;

public class SVÖrneği {
  public static void main(String[] args) {
    List<Integer> tl1 = Arrays.asList(1, 2, 3);
    List<Integer> tl2 = Arrays.asList(4, 5);

    List<List<Integer>> tll = Arrays.asList(tl1, tl2);
  } // void main(String[]) sonu
} // SVÖrneği sınıfının sonu
$ javac -encoding utf-8 -Xlint:unchecked SVÖrneği.java
SVÖrneği.java:8: warning: [unchecked] unchecked generic array creation of type java.util.List<java.lang.Integer>[] for varargs parameter
    List<List<Integer>> tll = Arrays.asList(tl1, tl2);
                                           ^
1 warning
asList metodunun işini nasıl yaptığını öngörebilen birisi olarak, aynı metodun çağrıldığı 5 ve 6. satırlar onaylanırken 8. satırın uyarı üretmesi, bazılarınıza garip gelebilir. Sanırım soysallıkla ilgili kısa bir açıklama bu arkadaşları ikna edecektir.

J2SE 5.0 sürümünde eklenen soysallığı kullanan kod ile J2SE 5.0 öncesindeki kodun bir arada kullanılabilmesi için derleme sırasında tür silme adı verilen bir dönüşüme başvurulur. Bu, soysal türlere dair tür argümanlarının korunmayacağı anlamını taşır. Mesela, örneğimizin 8. satırında argüman olarak geçirilen ve [statik] türleri List<Integer> olan tl1 ve tl2 değişkenlerinin türü List'e dönüştürülecektir. Dolayısıyla, çağırılan metot, argümanları değişik ve birbirinden bağımsız türlerden değerler içerebilecek List nesneleri olarak görecektir. Bu ise, çağrılan metodun bilerek ya da bilmeyerek Integer nesneler tutması beklenen liste(ler)i herhangi bir türden nesne tutacak hale döndürmesinin mümkün olduğu anlamına gelir. Buna karşın, derleyicinin böylesine bir kodu reddetmesi de, J2SE 5.0 öncesi ve sonrasında üretilmiş kodların bir arada çalışamayacağı anlamına geleceğinden, kabul edilebilir bir seçenek değildir. Çözüm, olası hataya dikkat çekmek için programcıya verilen bir uyarı ile sağlanır.

Örneğimizdeki uyarının ortadan kaldırılması 8. satır öncesine @SuppressWarnings("unchecked") açımlamasının konulması ile sağlanabilir. Java SE 7 öncesinde geçerli olan bu çözümün doğurduğu bir sorun, çok sayıda kullanıcısı bulunan metotlarda her kullanıcının söz konusu uyarı ile uğraşmak zorunda kalmasıdır. Bunun yerine, derleyicinin istediği garantinin metot gerçekleştirimcisi tarafından verilmesi daha yerinde olacaktır. İşte, SafeVarargs açımlamasının sağladığı budur. Örneğimize uyarlayacak olursak, Arrays sınıfının gerçekleştirimcilerinin kaynak kodu aşağıdaki gibi yazıp derlemesi tüm kullanıcıların yükünü omuzlarından alacaktır.
package java.util;
...
public class Arrays {
  ...
  @SafeVarargs
  public static <T> List<T> asList(T... değerler) { ... }
  ...
} // java.util.Arrays sınıfının sonu
Arrays.asList metoduna benzer şekilde SafeVarargs açımlamasıyla bezenen diğer Java platformu metotları aşağıda verilmiştir.
  • public static <T> boolean
      java.util.Collections.addAll(
        Collection<? super T> c, T... elemanlar)
  • public static <E extends Enum<E>>
      java.util.EnumSet<E> EnumSet.of(E ilk, E... gerisi)
  • protected final void
      javax.swing.SwingWorker.publish(V... parçalar)
Listedeki metotlardan da görülebileceği gibi, SafeVarargs açımlaması ile bezenmesi düşünülen metotların bazı özellikleri olması gerekir.
  • Yapıcılara ek olarak, ancak static veya final metotlara SafeVarargs açımlaması iliştirilebilir.
  • Yapıcı veya metodun değişken sayıda argüman alıyor olması gerekir.
Bu özellikleri taşımayan metotların SafeVarargs açımlamasıyla ilan edilmesi, derlenmekte olan sınıfın reddedilmesine neden olacaktır, haberiniz ola!


  1. Adı geçen üstkavramlar hakkında bilgi edinmek için, şu videoları izlemek isteyebilirsiniz: Java birimleri, kod örtüleri. Bu videolar, bir sonraki uyarlama ile Java'ya giriş yapacak bu programlama kavramları ile ilgili gerekli bilgileri sağlayacaktır.
  2. Aslına bakarsanız, Java platformunun JRuby ve Jython gibi dinamik türlemeli dilleri kullanılarak yazılan programların daha hızlı çalışmasını sağlayacak bir yenilik, Java SE 7 ile birlikte gelen JSM'nin yeni bir Bytecode komutunu (invokedynamic) desteklemesi durumunu ortaya çıkardı. Dolayısıyla, bu diller kullanılarak yazılacak yeni programların derlenmesi sonucu oluşturulacak sınıf dosyalarının önceki uyarlamaların JSM'leri tarafından çalıştırılmaları mümkün olmayacaktır. Ancak, statik türlemeli olan Java kullanılarak yazılan programlarda böylesine bir durumun söz konusu olmaması nedeniyle bu bizi ilgilendirmiyor.
  3. Bu noktada, olası bir yanılgıyı düzeltip Sezar'ın hakkını Sezar'a vermemiz gerekli. Her ne kadar Java yaygın kullanımlı diller içinde çöp toplayıcı desteğini getirerek öncü rolünü oynamış olsa da, bu aslında Lisp-temelli dillerde 1960 yılından bu yana bulunan bir özellik.
  4. Biraz düşünüldüğünde bunun sebebi daha iyi anlaşılacaktır. Böylesine iki sınıfın ortak atası sınıflardan daha genel olanıdır ve kotarıcı parametresine gönderilecek iletiler bu sınıfın desteklediği iletilere sınırlı olacaktır. Yani, kalıtlayan sınıfın gönderilebilecek iletilerin belirlenmesinde—akılları karıştırmak dışında—hiçbir etkisi olmayacaktır.

6 Nisan 2011 Çarşamba

İlkel Türler-Kayan Noktalı Sayılar

İlkel türler hakkındaki dizimizin üçüncü yazısında, matematikteki gerçel sayıların temsil edilmesinde kullanılan kayan noktalı sayı türlerine göz atacağız. Tamsayı türlerinin anlatıldığı yazıdan🔎 da tanıdık gelecek çoğul eki bu amaçla kullanılabilecek iki türe işaret ediyor: float ve double.

float ve double türlü değerlerin bellek gösterimi, gerçel sayıların temsili için tanımlanan ve pratikte tüm donanımlar (ve programlama dilleri) tarafından benimsenmiş olan IEEE754 standardına göre oluşturulur. Önerilen gösterimin ayrıntısına girmeden önce, anılan türlerin kullanımına dair şu uyarı yerinde olacaktır: tanım aralığındaki tüm değerlerin kusursuz bir biçimde temsil edildiği tamsayı türlerinin aksine, kayan noktalı sayı türleri tanım aralıklarındaki gerçel sayıların sadece bazılarını kusursuz olarak temsil edebilir, diğer sayılar ancak yaklaşık olarak temsil edilebilirler. Bu, tanım aralığındaki gerçel sayılar ile kayan noktalı sayılar arasında bire-bir bir ilişki olmadığı anlamına gelir. Bir diğer deyişle, aynı kayan noktalı sayı birden çok—aslında, sonsuz—gerçel sayıyı temsil eder. Dolayısıyla, aşağıdaki kod parçasının üreteceği çıktı bizi şaşırtmamalıdır. 3.14'ün yaklaşık temsil edilmesi nedeniyle 9.8596 olması gereken çarpma sonucu 9.859601 olarak hesaplanacak ve son satırdaki eşitlik denetimi beklenmedik bir yanıt üretecektir.1
float pi = 3.14f;
float piKare = 9.8596f;
System.out.println(pi * pi == piKare); // ⇒ false
Değişkenlerin ilklenmesinde kullanılan ilk değerlerin sonundaki fF de olabilir—sabitlerin float türünden ele alınması gerektiğini ifade etmek için kullanılır. Bu niteleyicinin yokluğunda, sabitin türü double olarak hesaba katılacaktır.2

Kayan noktalı tanımlayıcılara değer sağlamakta kullanılan sabitler değişik şekillerde yazılabilir.
double bütçeAçığı = 8000000000000000;
bütçeAçığı = 8e15; // 8.0e15 olarak da yazılabilir
bütçeAçığı = 80e14; // Yukarıdakiler ile aynı.
final float üç = 3.f; // 3.0f yazmak daha iyi bir fikir.

double dSayı = 0x49.0p0; // dSayı ← 73 (2^0(4*16^1+9*16^0))
dSayı = 0x4.9p4; // dSayı ↞ 73 (2^4(4*16^0+9*16^-1))
dSayı = 0x1.24p6; // dSayı ↞ 73 (2^6(1*16^0+2*16^-1+4*16^-2))
dSayı = 0xA.5p3; // dSayı ↞ 82.5 (2^3(10*16^0+5*16^-1))
Yukarıda verilen seçeneklerin kullanımında şu noktaların akılda tutulması yararlı olacaktır.
  • Sayının kesir kısmının var olması durumunda, 16'lı taban sadece bilimsel gösterimle birlikte kullanılabilir. Buna göre, Java derleyicisi 0x49 ve 0x49.0p0 seçeneklerini kabul ederken 0x49.0'ı reddedecektir.
  • Bilimsel gösterim sayılarda ölçekleme yapılırken, çarpan olarak 16'lı tabanda 2, 10'lu tabanda ise 10 kullanılır.

İlk kod parçasındaki eşitlik denetiminde ortaya çıkan beklenmedik durumun nedenini daha iyi anlayabilmek için IEEE754'ün koyduğu kurallara bakalım. Öncelikle, gerçel sayıların temsilinde kullanılan kayan noktalı değerlerin içeriklerinin yorumlanmasında ortaokul yıllarından hatırlayacağınız (normalize edilmiş) bilimsel gösterimin temel alındığını söyleyerek başlayalım: kayan noktalı değerlerin içerikleri ∓i0.i-1i-2...x2n şeklinde yorumlanır. Dolayısıyla, normalize edilmiş bilimsel gösterimde i0'ın 0 olamayacağı düşünüldüğünde, kayan noktalı sayı bulunduran bellek bölgelerinin aşağıdaki gibi hesaplanan bir sayıyı tuttuğunu söyleyebiliriz.

∓1.i-1i-2...x2n = ∓(1 + i-12-1 + i-22-2 + ...)2n

Bir diğer deyişle, gerçel sayılar işaret bilgisi, kesir kısmındaki ikili basamaklar ve ölçeklemek amacıyla kullanılan üs değeri kullanılarak temsil edilirler. İşaret bilgisinin bir ikil ile gösterildiği float ve double türleri arasındaki fark, diğer iki özellik için ayrılan alanın büyüklüğünden kaynaklanır. Bu alanların büyüklüğü aşağıdaki tabloda verilmiştir.

Kayan noktalı sayı türleri ve özellikleri
Özellikdoublefloat
Uzunluk (ikil)İşaret11
Üs118
Kesir5223
Duyarlık (Ondalık)167
Merkez1023127

Temsil edilebilecek gerçel sayılara dair yoruma girişmeden önce, tablodaki özelliklerin sayının değerini nasıl etkilediğine bir bakalım.
  • İşaret ikilinin 0 olması sayının artı, 1 olması ise eksi olduğunu gösterir. Bu ikilin 1'e tümlenmesi—yani, 1 iken 0 veya 0 iken 1 yapılması—temsil edilen sayıyı -1 ile çarpma etkisini yaratır. Bunun doğal bir sonucu olarak, artı ve eksi sıfırdan bahsedilmesi de mümkündür.
  • Kesir kısmının en küçük değeri—tüm ikillerin 0 olması durumu—0 olabilirken en büyük değeri—tüm ikillerin 1 olması durumu—1-2-kesir uzunluğu olabilir. Bu değerlerin, başta varsayılan 1 değerine eklenmesi mantis değerini [1..2-2-kesir uzunluğu] aralığına taşıyacaktır. Ayrıca, 52 ve 23 ikili basamak duyarlığında temsil edilen sayılarımızın ondalık basamak türünden en yüksek duyarlığı, sırasıyla, 16 ve 7 olacaktır.3
  • [1..2-2-kesir uzunluğu] aralığındaki mantisin eksi olmayan üs değerleriyle ölçeklenmesi sonucunda mutlak değerce 1'den küçük sayıların temsil edilemeyecek olması nedeniyle, üs kısmı tablodaki Merkez adlı maddedeki değerin sıfır noktası seçildiği bir tamsayı çizgisine eşlenir. Örneğin, float türlü bir sayının üs kısmının 130 olması, ölçeklemekte kullanılan üs değerinin 3 (130-127) olarak alınmasına neden olacaktır. Ayrıca, üs kısmının 1'lerle dolu gösterimi ∓∞ ve temsil edilemeyecek mutlak değerce büyük sayıları göstermek için kullanıldığından, en büyük üs değeri 2üs uzunluğu-1-merkez olacaktır. Benzer şekilde, üs kısmının 0'larla dolu gösterimi ∓0 ve temsil edilemeyecek mutlak değerce küçük sayıları göstermekte kullanıldığından, en küçük üs değeri de 1-merkez olacaktır.


Buna göre, double ve float türlerince temsil edilebilecek en büyük değer, sırasıyla, (2-2-52)22046-1023 ve (2-2-23)2254-127 olarak hesaplanacaktır.4 Temsil edilebilecek mutlak değerce en küçük sayılar ise, her iki tür için de 21-merkez formülü kullanarak bulunabilir. Bu değerler ve Java programları içinden bu değerlere atıfta bulunmak için kullanılabilecek simgesel sabit adları aşağıdaki tabloda verilmiştir.

Kayan noktalı sayıların değer sınırları
Sınır türüdoublefloat
En büyük21024-2971Double.MAX_VALUE2128-2104Float.MAX_VALUE
En küçük2-10222-126
En küçük (özel)2-1075Double.MIN_VALUE2-150Float.MIN_VALUE

Yukarıdaki tablonun son sırasında verilen değerler, üs kısmının tümüyle 0 olduğu gösterimde kullanılan özel hesaplamanın üreteceği değerlerdir. Bu özel gösterime sahip bir kayan noktalı sayının temsil ettiği değerin hesaplanması esnasında kesir kısmına 1 eklenmez ve sonuç kesir kısmı ile 2-merkez'in çarpılması yoluyla elde edilir. Bu özel uygulama sayesinde, mutlak değerce daha küçük sayıların temsil edilmesi olanaklı kılınır.

Verilen sınırlar göz önünde bulundurularak, kayan noktalı sayı türlerinin hangi aralıktaki gerçel sayıları temsil edebileceği aşağıdaki şekille özetlenebilir. Şeklin incelenmesi sırasında temsil edilebilir aralıktaki sayıların bazıları dışında tümünün ancak yaklaşık olarak temsil edildiği unutulmamalıdır.


Kısaca açmak gerekirse;
  • ➃ 0 noktasını, ➁ ve ➅ nolu bölgeler ise 0 dışındaki temsil edilebilir gerçel sayı aralıklarını gösterir. ➁ nolu bölgenin sınırları, double türü için [-Double.MAX_VALUE..-Double.MIN_VALUE], float türü için [-Float.MAX_VALUE..-Float.MIN_VALUE] olarak belirlenirken, ➅ nolu bölgenin sınırları, sırasıyla, [Double.MIN_VALUE..Double.MAX_VALUE] ve [Float.MIN_VALUE..Float.MAX_VALUE] olarak belirlenmiştir.
  • ➀ ve ➆ nolu bölgeler mutlak değerce temsil edilemeyecek büyük gerçel sayıları, ➂ ve ➄ nolu bölgeler ise mutlak değerce temsil edilemeyecek küçük gerçel sayıları kapsar. ➀ ve ➆ nolu bölgelerdeki gerçel sayılar, sırasıyla, -∞ (Double.NEGATIVE_INFINITY veya Float.NEGATIVE_INFINITY) ve +∞ (Double.POSITIVE_INFINITY veya Float.POSITIVE_INFINITY) olarak ele alınırken, ➂ ve ➄ nolu bölgelere düşen gerçel sayılar, sırasıyla, -0 ve +0 olarak ele alınır.
Açıklık getirmek için aşağıdaki kod parçasını ele alalım. f1 değişkenini float tanımlayıcıların alabileceği en yüksek değerin 1 fazlasıyla ilklememiz sonrasında, ikinci satırda aynı değişkeni Float.MAX_VALUE ile karşılaştırıyoruz. Normalde yanlış olması gereken eşitlik denetiminin sonucu, söz konusu gerçel sayıların aynı kayan noktalı sayı ile temsil edilmesi nedeniyle, doğru sonucunu üretiyor. Sonraki iki satırda, en büyük float değer olarak hesaba katılan d1'in tersini bir kez daha d1 ile bölüyoruz. Sonuç, temsil edilemeyecek kadar küçük bir sayı olduğu için, 0.0 (ve -0.0) olarak ele alınıyor. Benzer bir durum, 5. ve 6. satırlar için de geçerli; bu sefer, işlemin sonucu temsil edilemeyecek kadar büyük olduğu için, f3 ve f4, sırasıyla, ∞ ve -∞ ile ilkleniyor. Kod parçasının son satırında ise, ∞'un -∞ ile bölünmesi, f5 değişkeninin "Sayı Değil" (İng., Not A Number) özel değeriyle ilklenmesi sonucunu doğuruyor.
float f1 = Float.MAX_VALUE + 1; // f1 ← 3.4028235E38
System.out.println(f1 == Float.MAX_VALUE); // ⇒ true
float f2 = 1 / f1 / f1; // f2 ← 0.0
f2 = -1 / f1 / f1; // f2 ↞ -0.0
float f3 = f1 * f1; // f3 ← Float.POSITIVE_INFINITY
float f4 = -f1 * f1; // f4 ← Float.NEGATIVE_INFINITY
float f5 = f3 / f4; // f5 ← Float.NaN

  1. Bazılarınız, böylesine can sıkıcı bir durumun varlığını IEEE754 komitesinin yaptığı bir gafa bağlayabilir. Ancak, bilgisayarların sonlu makine olma özelliği ve herhangi bir gerçel sayı çifti arasında sonsuz sayıda gerçel sayı olması birlikte düşünüldüğünde, bunun çok haksız bir yakıştırma olduğu kolaylıkla görülecektir.
  2. Aslına bakarsanız, f ekinin konmaması örneğimizde bir hataya neden olmayacaktır. Ancak, ilkleme sonrasında değişkenlerimize benzer bir şekilde niteleyicisiz bir şekilde kayan noktalı sayı sabitlerinin atanmaya çalışılması derleyici tarafından reddedilecektir.
  3. Duyarlığın hesaplanmasında yapılması gereken, ikili basamak sayısını log102 değeriyle çarpmak ve çıkan sonucu bir üst tamsayıya yuvarlamak.
  4. 1 + 1/2 + 1/4 + ... 1/2-n ifadesinin 2-2-n'e eşit olduğunu hatırlamanız, hesaplamanın doğruluğuna ikna olmanızı kolaylaştıracaktır.

30 Mart 2011 Çarşamba

İlkel Türler-Karakterler

İlkel türlere dair ikinci yazımızda metinsel öğelerin atomik parçası olarak düşünülebilecek simgelerin Java karşılığı olan karakter türüne—char'a—değineceğiz. Anlatımımıza, tamsayı türlerinden bahsederken üzerinde durmadığımız bir noktayı vurgulayarak başlayalım: ne tür bir değer temsil ediyorsak edelim, eninde sonunda her şeyin işlemci tarafından yorumlanabilen ikil (İng., bit) desenleri haline dönüştürülmesi gerekir. Örneğin, geçen yazımızın konusu olan tamsayı türleri 2'nin tümleyeni adı verilen bir temsil yöntemi kullanılarak ikillere dönüştürülür. Karakter tablosu olarak adlandırılan bir küme içinden seçilebilen karakterler—yani, metni oluşturan simgeler—için de benzer bir dönüşüm söz konusudur. Bu dönüşüm, karakter tablosundaki karakterler ile ikil desenleri arasındaki eşlemeyi tanımlayan bir karakter kodlaması yoluyla yapılır.

Değişik ortamlardaki karakterler için farklı kodlamaların kullanıldığı Java'da, Unicode tablosundan seçilen karakterler bellekte UTF-16 adı verilen bir kodlamaya göre temsil edilir. Bir diğer deyişle, karakterleri tutmakta kullanılan iki sekizli büyüklüğündeki char türüne ait bir tanımlayıcı, kendisine sağlanan Unicode karakterin UTF-16 kodlamasını tutar. Buna göre, aşağıdaki satırların işlenmesi sonrasında tanımlanan simgesel sabitler, sırasıyla, ilklemede kullanılan karaktere karşılık gelen 97, 305 ve 351 değerlerine sahip olacaktır.
final char aHarfi = 'a'; // aHarfi <- 97
final char ıHarfi = 'ı'; // ıHarfi <- 305
final char şHarfi = 'ş'; // şHarfi <- 351
Karakterlerin temsilinde kullanılan değerin eksi olmayan bir tamsayı olması, karakter sabitlerin ve karakter tanımlayıcıların tamsayı türüne sahipmiş gibi kullanılabileceği anlamına gelir. Ancak, anlaşılması ve bakımı zor kodlara neden olacağı için, bu özelliğin kullanımından olabildiğince kaçınılmalıdır. Ayrıca, tamsayı türlerinin 0-merkezli bir sayı çizgisindeki eksi ve artı sayıları temsil edebilirken, char türünün eksi olmayan tamsayıları temsil edebileceği unutulmamalıdır.
int sıraNo = 'a'; // sıraNo <- 97
sıraNo++;
char harf = (char) sıraNo; // harf <- 'b'
harf = (char) 67; // harf <- 'C'
char türlü bir tanımlayıcıya tamsayı değer sağlanmasının bir getirisi, klavyenizde bulunmayan Unicode karakterlerin girilmesine olanak tanımasıdır. Örneğin, matematiksel ifadelerin işlendiği bir metnin parçası olarak kullanmak isteyebileceğiniz sigma (Σ), klavyenizde bulunmadığı halde karşılığı olan 0x3A3—931 veya 03243 de olabilir—değerinin yazılmasıyla girilebilir.
final char sigma = '\u03A3'; // sigma <- 'Σ'
Dikkat edecek olursanız, yukarıda sağlanan örnek bir öncekinden farklı bir gösterim kullanıyor. \u önekinin derleyiciye verdiği ön bilgi sayesinde, bu gösterimin kullanıldığı durumlarda biçimlendirme yapmak gerekmiyor; derleyici \u öneki sonrasındaki sayıyı ilişkin karakterin Unicode tablosundaki konumunun 16'lı tabandaki dört basamaklı hali olarak ele alıyor ve bekleneni yapıyor.

Tamsayı değerlerin char türlü olarak ele alınabilmesinin bir diğer yararı, varlığı metnin görünümünde dolaylı bir biçimde etki yaratan kontrol karakterlerinin temsil edilmesinde ortaya çıkar. Örnek olarak, bir sonraki satırın başına geçiren yeni satır karakterini ele alalım. Bu karakter, metni okuyacak düzenleyici benzeri bir yazılım tarafından okunmalı ve hesaba katılmalı, fakat bu karakterin varlığı metni okuyan kişilere doğrudan yansıtılmamalıdır. Bundan dolayı, Unicode tablosunda 10. sırada yer alan bu karakter '\u000A' değeri ile temsil edilir. Sıklıkla kullanılan bu karakterin daha kolay yazımını sağlamak amacıyla algılaması daha kolay ikinci bir gösterim kullanılabilir: '\n'. Bu ve bazı diğer kontrol karakterleri aşağıdaki tabloda verilmiştir.

Özel gösterimli karakterler
Karakter adıGösterimKarakter
Geri alma'\b''\u0008'
Sayfa ilerletme'\f''\u000C'
Yeni satır'\n''\u000A'
Satır başı'\r''\u000D'
Sektirme'\t''\u0009'
Tek tırnak'\'''\u0027'
Tırnak'\"''\u0022'
Ters kesme'\\''\u005C'

Tablonun son üç sırasındaki karakterlerin, diğerlerinden farklı olarak, görsel gösterimli karakterler olduğunu fark etmişsinizdir. Kontrol karakteri sınıfına girmeyen bu karakterlerin bu şekilde kullanılması, söz konusu karakterlere Java'da yüklenen özel anlamdan kaynaklanır. Örneğin, karakter sabitlerinin başlangıç ve sonunu işaretlemek için kullanılan tek tırnak iminin bu görevi dışında kullanılabilmesi için özel ele alınması isteğimizin derleyiciye iletilmesi gerekir. Bu da, yukarıdaki tabloda belirtildiği gibi yapılacaktır.

Yazımızı, çok ufak bir olasılıkla da olsa, bazılarınızın canını yakabilecek bir ayrıntıya değinerek kapatalım: Başlangıcında iki sekizliye sığan indislerle kullanılabilen Unicode tablosu, zaman içinde yeni karakterlerin eklenmesiyle genişlemiş ve indis değerleri iki sekizliye sığmaz hale gelmiştir. Bu da, Unicode tablosundaki kimi karakterlerin iki sekizli yer tutan char türündeki bir tanımlayıcı ile temsil edilememesi durumunu doğurmuştur. Yeryüzünde konuşulmakta olan modern dillerin simgeleri için geçerli olmayan bu durumun, nadiren de olsa sürprizler yaratabileceği unutulmamalıdır. Örneğin, müzik gösteriminde yararlanılan simgelerin kullanımı unutkan bir programcıya bu durumu hatırlatacaktır. Zira, bu tablodan da görüleceği gibi, müziksel simgeler Unicode tablosunun 0x1D100 ile 0x1DFF nolu indisleri arasında bulunur ve ancak dört sekizli ile temsil edilebilir. İşin kötüsü, dört sekizli kaplayan bu alana ilişkin karakterin Unicode tablosundaki indis değeri değil UTF-16 kodlamasının ürettiği bambaşka bir değer konulması gerekecektir. Bu değerin ne olacağını merak edenlere UTF-16 kodlaması konusundaki şu Sıklıkla Sorulan Sorular belgesini okumalarını tavsiye ederim.

16 Mart 2011 Çarşamba

İlkel Türler-Tamsayılar

Bilgisayar programları, gerçek dünyada ve zihinlerimizde hali hazırda var olan süreçleri benzetmek (simüle etmek) amacıyla yazılır. Bu, kimi zaman verilen bir sayının çift olup olmadığını bulan bir hesaplama kadar basit olabilirken, kimi zamansa Mars gezegenine gönderilen bir aracın (Mars Rover) gezegen yüzeyinde güvenli bir biçimde dolaşmasını sağlayan yazılım kadar karmaşık olabilir. Ancak, üretilmesi istenen yazılım ne kadar karmaşık olursa olsun, iş eninde sonunda benzetilen süreçteki varlıkların özelliklerini temsil eden—ve genelde bilgisayar donanımları tarafından da bilinip makine kodları yoluyla doğrudan işlenebilen—basit türden değerlerin işlenmesi yoluyla icra edilir.  Örneğin, öğrenci kayıtlarını işleyen bir yazılımda gerçek iş, öğrencinin no, ad, ortalama gibi basit değerli özelliklerinin işlenmesiyle yapılır. İşte bu yazımızda, bilgisayar donanımı tarafından doğrudan desteklenmesi nedeniyle ilkel veri türü (kısaca, ilkel tür) olarak sınıflandırılan bu basit değerlerin ait oldukları türlerin tamsayıları temsil etmekte kullanılanlarına bakacağız.

Öncelikle, son tümcenin programlamaya yeni başlayanlarınızı şaşırtabilecek çoğul olma özelliğine dikkatinizi çekerim. Matematikte ℤ = {..., -2, -1, 0, 1, 2, ...} şeklinde tanımlanan ve sayma sayıları olarak adlandırılan tek bir tür varken, Java'da belirli sınırlar içindeki tamsayıları temsil eden dört türden bahsedebiliriz: byte, short, int ve long.1 Söz konusu türlerin belirli sınırlar içinde olmasının sebebi bilgisayarın sonlu bir belleğe sahip olmasında yatar: türleri ne kadar büyütmek istersek isteyelim, bilgisayar belleği pratik bir sınır olarak karşımıza çıkacaktır. Dört değişik tamsayı türünün bulunmasının nedeni ise, ortaya çıkan farklı gereksinimler sonucunda belleği ekonomik kullanma seçeneğini programcıya sunmak isteğidir.

Java tarafından desteklenen tamsayı türlerinin bellekte kapladığı alan ve bu türlere sahip terimlerin alabileceği değerlerin alt ve üst sınırları aşağıda verilmiştir. Buna göre, öğrenci notlarını tutmakta kullanılacak bir tamsayı dizisinin elemanlarının int yerine byte seçilmesi yerden %75'lik bir tasarruf anlamına gelecektir.

Tamsayı türleri ve sınır değerler2
Tür adıAlan (ikil)En küçükEn büyük
byte8-128127
short16-3276832767
int32-21474836482147483647
long64-9223372036854775808L9223372036854775807L

Tamsayı türlü ifadelerin işlenmesi sırasında, sınır dışına taşabilecek işlem sonuçlarının sessizce göz ardı edildiği ve elde edilen sonuçların aşağıda int türü için verilmiş olan öncelik-sonralık ilişkisine göre dönüştürüleceği unutulmamalıdır. [Integer.MAX_VALUE, herhangi bir int türlü ifadenin alabileceği en büyük değeri gösterirken, Integer.MIN_VALUE aynı ifadenin sahip olabileceği en küçük değeri temsil eder. Diğer türler için benzer işlev gören simgesel sabitlerin adları, Integer yerine Byte, Short veya Long konularak elde edilebilir.]


Dolayısıyla, aşağıdaki program s: -32767 b: 124 şeklinde bir çıktı üretecektir. Çünkü, s'nin yeni değeri 32767'den iki sonra gelen 32769 değil, yukarıdaki şeklin short türüne adapte edilmiş uyarlamasının gerektirdiği gibi -32767 olacaktır. Benzer şekilde, -127 değerine sahip b değişkeninden 5 çıkarılması sonrasında bu değişkenin yeni değeri 124 olacaktır, -132 değil.

TamsayıSınırları.java
public class TamsayıSınırları {
  public static void main(String[] ksa) {
    short s = 0x7ffF; // short s = 32767;
    s = (short) (s + 2);
    byte b = -0177; // byte b = -127;
    b = (byte) (b - 5);
    System.out.println("s: " + s + " b: " + b);
  } // void main(String[]) sonu
} // TamsayıSınırları sınıfının sonu

Örnek programımızın 4. ve 6. satırlarındaki biçimlendirmeler dikkatinizi çekmiştir. Zorunlu olarak yapılan bu işlemlerin sebebi, 2 ve 5 gibi tamsayı sabitlerinin değerleri ne kadar küçük olursa olsun int türlü kabul edilmeleridir. Bunun bir sonucu olarak, söz konusu satırların  biçimlendirmeler olmaksızın yazılması, bir short/byte ile bir int değerin toplanması/çıkarılması işleminin short/byte türü sınırları dışında olması olasılığı nedeniyle derleyici tarafından reddedilecektir. Çünkü, elde edilecek int türündeki değerin atama sonucunda daha küçük türden bir değişkenin yeni değeri olması olası bir değer kaybına işaret eder. Derleyici engeli, programcının bilinçli olarak bu işleme izin verdiğini belirten biçimlendirme ile aşılabilir.

Program metninin 3. ve 5. satırlarından da görülebileceği gibi, tamsayı sabitleri ondalık sistem dışında bir gösterim ile de yazılabilir; istenecek olursa, sayıların yazımında 8'li ve 16'lı taban da kullanılabilir.3 Bu takdirde, 0 ve 0x öneki ile başlayan sayıların yazımında kullanılabilecek simgelerin, sırasıyla, [0..7] ve [0..9, A..F, a..f] içinden seçilebileceği unutulmamalıdır.

Değineceğimiz son nokta, int sınırları dışındaki bir tamsayı sabitinin program metnine yazımı için kullanılması gereken gösterim. Küçük mutlak değerli tamsayılara da uygulanabilecek bu gösterim sayesinde, herhangi bir sabitin long türlü ele alınması sağlanabilir. Yapılması gereken tek şey, söz konusu sabitin sonuna L (veya l) eklemektir. Dikkat edildiğinde, aynı işin biçimlendirme ile, aşağıdaki kod parçasının ikinci satırında olduğu gibi, yapılmasının olanaksızlığı görülür. Zira, biçimlendirilme işlemi tamsayı sabitinin öncelikle int büyüklüğündeki bir bellek bölgesine konulmasını gerektirir ki, bu biçimlendirme öncesinde değer kaybının zaten yaşanmış olacağı anlamını taşır.
long l = 123456789098765L;
long l2 = (long) 123456789098765; // Derleme hatası!

  1. Karakterler bahsinde, char türünün de tamsayıları temsil etmek için kullanılabileceğini göreceğiz.
  2. Java 7 ile birlikte, sayıların algılanmasını kolaylaştırmak amacıyla alt çizgi (_)  kullanılarak rakamların gruplanması mümkün olacaktır. Örneğin, 123456789 yerine 123_456_789 yazılabilecektir.
  3. Java 7 ile birlikte, tamsayı sabitlerinin 0b öneki belirtilerek 2'li tabanda yazılması mümkün olacaktır.

1 Mart 2011 Salı

Türkçe İçerikli Java Programları

Unicode hareketinin başlatılmasından bu yana 20 küsur yıl geçmesine rağmen ASCII karakter tablosunun etkisinden kurtulamayanlar ve İnternet'teki İngilizce kaynakları kopyalayarak programlamayı öğrenenlerin sıklıkla karşılaştığı bir sorun, kaynak kodda kullanılan tanımlayıcı adları ve karakter katarı içeriklerini (ne kuş ne deve) Türkçe ile İngilizce arasında bir dilde yazmak zorunda kalmalarıdır. Dolayısıyla, bu yazımda Türkçe içerik barındıran Java dosyalarının oluşturulup derlenmesine dair ufak bir örnek vereceğim; kendisine dert edenlere aşağıdaki programda geçen sıkıcıMesaj değişkenini İngiliz Türkçesi ile yazmak utancından nasıl kurtulunacağını anlatacağım.

TürkçeÖrnek.java
// System sınıfındaki statik metotları sınıf adı olmaksızın kullanılabilir hale getir.
import static java.lang.System.*;

public class TürkçeÖrnek {
  public static void main(String[] ksa) {
    String sıkıcıMesaj = ksa[0];

    out.println("Komut satırından vermiş olduğunuz mesaj: " + sıkıcıMesaj);
  } // void main(String[]) sonu
} // TürkçeÖrnek sınıfının sonu

Öncelikle, Türkçe'de kullanılan tüm simgeleri destekleyen ve metin düzenleyiciniz tarafından bilinen bir karakter kodlama standardı seçiniz; Türkçe simgeler içeren dosyayı diske kaydederken bu standardı kullandığınızdan emin olunuz. Bu, dosya kaydetme diyaloğunda yapılacak bir seçimle belirtilebileceği gibi, muhtemelen Ayarlar (ya da Tercihler) adına sahip bir menüden de yapılabilir.

Bu noktada, yaygınlığı ve ekonomikliği nedeniyle UTF-8'i seçmenizi tavsiye ederim. Söz konusu standart, İngilizce yazım için gerekli simgeleri ASCII standardındaki gibi tek sekizli ile temsil ederken, Unicode karakter tablosunda yer bulan diğer simgeleri iki, üç veya dört sekizlide temsil eder. Böylesine bir yaklaşım, Türkçe metinlerin yazımında yararlanılan pek çok simgenin İngilizce'dekilerle aynı olması ve Türkçe'ye özel simgelerin iki sekizli yer tutması nedeniyle, dosyanın kaplayacağı disk alanındaki (veya ağ bant genişliğindeki) artışı sınırlar.

Karakter kodlama bağlamındaki seçeneklere UTF-16, UTF-16BE, UTF-16LE,  UTF-32, UTF-32BE, UTF-32LE, iso8859-9 ve cp1254 standartlarını ekleyebiliriz. Ancak, anılan standartların UTF-8'e göre eksileri olduğu unutulmamalıdır. UTF-16, UTF-16BE ve UTF-16LE kodlamaları, yaşayan doğal dillerin yazımında kullanılan simgeleri iki, geri kalanları ise dört sekizliyle temsil ederek yerden kayba neden olacaktır. Aynı sıkıntı,  tüm simgeleri dört sekizliyle temsil eden UTF-32, UTF-32BE ve UTF-32LE kodlamaları için de geçerlidir. Ayrıca, UTF-16 veya UTF-32'nin seçilmesi durumunda, temsilde kullanılan sekizlilerin dışsallaştırılma (yani, diske yazılma veya ağa gönderilme) sırası donanım mimarileri arasında farklılık göstereceğinden, kaynak kodun taşınabilirliği azalmaktadır. Türkçe'ye özel geliştirilmiş iso8859-9 ve cp1254 standartları ise, Türkçe (ve İngilizce) dışındaki dilleri desteklememeleri ve UTF-8'e göre daha kısıtlı bir desteğe sahip olmaları nedeniyle iyi bir sebep olmadıkça seçilmemelidir.

Dosyanın diske kaydedilmesi sonrasında yapılması gereken, kayıt esnasında kullanılan kodlama standardına dair bilgiyi derleyiciye geçirmekten ibaret. Bu ise ilişkin bilginin derleyici opsiyonlarından encoding'in değeri olarak verilmesi ile mümkün olur.

# Seçilen kodlamaya göre UTF-8 yerine başka bir değer konulabilir.
$ javac -encoding UTF-8 TürkçeÖrnek.java

Başarı ile derlenmesinin ardından programınızın çalıştırılması için fazladan bir şey yapmanız gerekmez. Çünkü, derleme esnasında hangi karakter kodlama standardını kullanmış olursanız olun, derleyicinin üreteceği sınıf dosyalarının içindeki tanımlayıcı adları ve karakter katarı içerikleri, Java Sanal Makinesi'nin tanımında standardize edilmiş olan ve büyük ölçüde UTF-8'e benzeyen bir kodlama ile tutulur.

$ java TürkçeÖrnek "saçma sapan" bir şeyler
Komut satırından vermiş olduğunuz mesaj: saçma sapan 

Özetleyecek olursak, Türkçe içerikli bir Java kaynak dosyasının derlenip çalıştırılması aşağıda verilen şekildeki gibi sağlanabilir. Taşınabilirliğin zahmetsiz bir biçimde sağlanması ve tasarruflu disk alanı kullanımı için, derleyiciye geçirilen karakter kodlamasının UTF-8 olarak seçilmesi yerinde olacaktır; duruma göre, bu değerin UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE, iso8859-9, veya cp1254 olması mümkündür. JSM'ne ise kodlamaya ilişkin bir değerin geçirilmesi söz konusu değildir; bu değerin hem derleyici hem de JSM'nin farkında olduğu UTF-8'den çok az farklılık arz eden bir kodlama olduğu varsayılır.