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.