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.