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.