tag:blogger.com,1999:blog-73088905644001640962024-11-01T14:28:49.072+03:00Tevfik AKTUĞLU-Java ProgramlamaAğırlıklı olarak Java programlama dili ve ortamının özellikleri üzerine yazılar bulacağınız siteme hoşgeldiniz. İlgilenecek olursanız, dünya görüşümü paylaştığım <a href="http://ta-siyaset.blogspot.com/"><i>siteme</i></a> de beklerim.Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.comBlogger30125tag:blogger.com,1999:blog-7308890564400164096.post-6126959264260921712012-01-31T22:35:00.001+02:002013-07-15T18:42:36.514+03:00Doğrusal Veri Kapları<div id="anaMetin" style="text-align:justify">
Bu yazımızda, Veri Kapları Çerçevesi'nce (VKÇ) desteklenen <i>doğrusal veri yapıları</i>na değineceğiz. Bunu yaparken izleyeceğimiz yöntem, söz konusu veri yapılarının ortak özelliklerini anlattığımız giriş bölümünü takiben, anılan kategoriye giren kapları en geniş anlamda temsil eden <i>genel liste</i> veri yapısının desteklediği arayüzleri açıklamak ve kapların doğrusallaştırılmaları sonrasında ardışık erişimli görüntülerini sağlayarak dolaşılmasını olanaklı kılan <i>gezici</i>lere bakmak olacak. Yazımızın devamında ise, değişik doğrusal yapıların farklı gerçekleştirimlerine göz atarak işimizi bitireceğiz.<br/>
<br/>
<ul>
<li><a href="#giriş">Giriş</a></li>
<li><a href="#temelArayüz">Genel liste arayüzü</a></li>
<li><a href="#geziciler">Geziciler</a></li>
<li><a href="#listeGerçekleştirimleri">Genel liste gerçekleştirimleri</a></li>
<li><a href="#doğrudanErişimliListeler">Doğrudan erişimli listeler</a></li>
<li><a href="#kısıtlıListeler">Kısıtlı listeler</a></li>
<li><a href="#bağlaçlıListe">Bağlaçlı liste</a></li>
<li>Değişken uzunluklu diziler: <code>java.util.Vector</code><a href="http://ta-java.blogspot.com/2011/09/degisken-uzunluklu-diziler.html">🔎</a></li>
</ul>
<br/>
<h3 id="giriş">Giriş</h3><br/>
Doğrusal veri yapıları, eleman ekleme, silme ve güncelleme işlemlerinin hangi sırada uygulandığının bilinmesi durumunda, elemanların nasıl sıralanacağının bilindiği kaplardır. Örnek olarak, 1, 2 ve 3 sayılarının bir genel listenin, sırasıyla, başına, sonuna ve başına eklendikten sonra elde edilen kaptan ikinci elemanın silindiğini düşünün. Bu işlemlerin sonrasında listemiz, ne tür bir gerçekleştirim kullanılırsa kullanılsın, [3, 2] sıralamasıyla gezilecektir. Halbuki, küme veya eşlemlerde elde edilecek sıralama gerçekleştirime bağlı olarak oluşacak ve dolayısıyla kestirilemeyecektir.<br/>
<br/>
<h3 id="temelArayüz">Genel Liste Arayüzü: <code>java.util.List</code></h3><br/>
Doğrusal veri yapılarını en geniş anlamda temsil eden <u>soyut</u> veri yapısı <i>genel liste</i>dir. VKÇ'deki <code>List</code> arayüzü ile karşılanan bu yapı, gerçekleştirim biçiminden bağımsız olarak bir genel listenin desteklemesi beklenen işlemleri tanımlar. Yani, ister dizi gibi doğrudan erişimli bir yapı kullanılarak gerçekleştirilsin isterse bağlaçlı bir yapı kullanılarak, tüm genel listeler <code>List</code> arayüzündeki işlemleri desteklemek zorundadır.<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Uv9cZlmC8XdO4winU-WGOjz1b1j9cxjg1HnMhhJT5GqaxWnrbnTd4M0cw5yP9hO_Mrcs7Dd0ifa5ztlLNZ0Eh6hEnZ61C0GL62yKF-kicQVQ3-gfmLzfRvG83dhD4tP7gHfdDPQs4Xgd/s400/GenelListelerS%2525C4%2525B1rad%2525C3%2525BCzeni.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="209" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Uv9cZlmC8XdO4winU-WGOjz1b1j9cxjg1HnMhhJT5GqaxWnrbnTd4M0cw5yP9hO_Mrcs7Dd0ifa5ztlLNZ0Eh6hEnZ61C0GL62yKF-kicQVQ3-gfmLzfRvG83dhD4tP7gHfdDPQs4Xgd/s400/GenelListelerS%2525C4%2525B1rad%2525C3%2525BCzeni.png" /></a></div>
<br/>
<code>Collection</code>'dan kalıtlanmak suretiyle <code>List</code> arayüzünde gözüken iletilerin başında, <code>equals</code> ve <code>hashCode</code> gelir. Bu iletilerin <code>List</code> arayüzüne konulmuş olmasını başta yadırgayabilirsiniz. Ne de olsa, tüm sınıfların [dolayısıyla <code>List</code> arayüzünü gerçekleştirecek sınıfların da] kalıtladığı <code>Object</code> sınıfında bu iletilere dair gerçekleştirimler sağlanmaktadır. Ancak, buradaki amaç gerçekleştirimciye bir hatırlatma yaparak onu uyarmaktır; tanımlayıcıları <code>List</code> arayüzünü gerçekleştirmek isteyenlere şunu demektedirler:
<blockquote>Tutacakları karşılaştırmak yoluyla işini gören <code>Object</code> sınıfındaki <code>equals</code> metodu, muhtemelen senin işine gelmeyecektir. Ne de olsa, bu metot eşitlik denetimi yerine aynılık denetimi yapar. Bunun bir sonucu olarak, iki listenin eşit olması ancak ve ancak aynı olmaları durumunda mümkün olacaktır; eşit içeriğe sahip iki farklı liste eşitsiz ilan edilecektir. Dolayısıyla, sen iyisi mi, <code>equals</code> iletisini gerçekleştirmeyi bir daha düşün. Ha bu arada; <code>hashCode</code> iletisini de gözden geçirmeyi unutma. Malum, birbirine eşit farklı nesnelerin kıyım değerleri de eşit olmalıdır. Bu koşulu sağlayabilmenin yolu ise <code>hashCode</code> ile <code>equals</code>'ı birlikte ele almaktan geçer. Çünkü, <code>Object</code> sınıfındaki <code>equals</code> metodunu ezip de <code>hashCode</code> metodunu ezmezsen, eşit kapların farklı kıyım değerleri üretmesi durumu ortadan kalkmaz.
</blockquote>
<code>List</code> arayüzündeki iletilerin çoğu VKÇ'yi daha önceden kullanmış olanlara tanıdık gelecektir. Bunun başlıca sebebi, VKÇ'de desteklenen kapların bir standardizasyon çabası kapsamında birlikte ele alınmış olmasıdır.<sup><a href="#dn1" id="ref1">1</a></sup> Doğal olarak, aynı işlemler için olabildiğince aynı ileti adları seçilmeye çalışılmış ve ilintili işlemler kategorizasyon amacıyla aynı arayüze konulmuştur. Örneğin, ne tür bir kap olursa olsun, <code>isEmpty</code> hedef nesnenin boş olup olmadığını denetlerken <code>size</code> kaç eleman içerdiğini döndürür. Benzer bir şekilde, ilişkisel olmayan veri yapıları (genel liste, kuyruk, yığıt, çift-uçlu kuyruk, küme) <code>Collection</code> arayüzünde belirlenen işlevselliği paylaşırlar. Mesela, ister <code>Vector</code> olsun isterse <code>TreeSet</code>, bir nesnenin hedef kapta olup olmadığı <code>contains</code> iletisi ile sorgulanabilir; hedef kabın sonuna <code>add</code> ile eleman eklenirken, hedef kaptan verilen bir nesneye eşit ilk elemanın silinmesi <code>remove</code> ile sağlanır. Bunlara paralel olarak, <code>containsAll</code>, <code>addAll</code> ve <code>removeAll</code>/<code>retainAll</code> iletileri, sırasıyla, sorgulama, sona ekleme ve silme işlemlerini kendilerine geçirilen kapta var olan tüm nesneleri dikkate alarak yerine getirirler. <code>clear</code> iletisi ise, hedef kabın tüm elemenlarını siler. <br/>
<br/>
<code>List</code> arayüzünün <code>Collection</code>'dan kalıtladığı diğer iletiler hedef kabın dizi şeklindeki görüntüsünü döndüren <code>toArray</code> iletileridir. Kap içeriğini baştan sona dolaşılma sıralarını koruyacak şekilde diziye kopyalayan iletilerden argümansız olanı, sonucu <code>Object</code> dizisi olarak döndürürken, kopyalamanın yapılacağı dizinin bileşen türünü argümanındaki dizinin bileşen türünü belirleyen tür argümanıyla alan ikinci uyarlama, sonucu dönüş değerinin yanısıra, yeterli yere sahip olması durumunda, argümanda geçirilen dizinin içinde de döndürür. Argümandaki dizinin yeterli yere sahip olması halinde, dönüş değeri için ikinci bir dizi yaratılmayacak, aynı dizi nesnesi paylaştırılacaktır.<br/>
<br/>
Üst arayüzlerden kalıtlanmayıp <code>List</code> arayüzünün eklediği doğrusal kaplara özel tüm iletiler, kendilerine sağlanan indis değerlerini kullanarak ya da sonuçlarında indis değeri döndürerek işlerini görürler. Bunlardan <code>get</code>/<code>set</code> çifti, dizi erişim işleci (<code>[]</code>) görevini görür: <code>get</code> argümanında sağlanan indisteki elemanı döndürürken, <code>set</code> belirtilen konumdaki elemanı ikinci argümanındaki yeni değerle güncelledikten sonra söz konusu konumdaki eski değeri döndürür. <code>get</code>'in tersi olarak da görülebilecek <code>indexOf</code> ve <code>lastIndexOf</code> iletileri ise, <code>contains</code> iletisinin <code class="java-kw">boolean</code> yerine sorgulanan nesnenin, sırasıyla, kap içindeki ilk ve son geçiş yerlerini döndüren uyarlamaları olarak düşünülebilir. Argümandaki nesnenin kap içinde bulunmaması halinde her iki ileti de -1 döndürecektir.<br/>
<br/>
<code>List</code> arayüzüne özel <code>add</code>, <code>addAll</code> ve <code>remove</code> iletilerinin yukarıda anılan adaşlarından şöyle bir farkı vardır: ekleme kap sonu yerine sağlanan ekstra argümanla belirtilen konuma yapılırken, silme nesne-eleman eşitliği denetlenerek bulunan konumu değil argümandaki indisin gösterdiği konumu etkiler.<br/>
<br/>
Değineceğimiz son ileti, ilk başta göründüğünden çok daha hünerli olan <code>subList</code>. Hedef kabın argümanlar ile sınırlanan [kapalı-açık] dilimini bir <code>List</code> görüntüsü<a href="http://ta-java.blogspot.com/p/programlama-sozlukcesi.html#görüntü">≝</a> olarak döndüren bu ileti, dilim işlemlerinin gerçekleştirilmesini kolaylaştırır. İstersek, bir nesnenin kabımızın belirli bir diliminde var olup olmadığını sorgulayabiliriz; istersek, aşağıda olduğu gibi, kabımızın bir bölümünü silebiliriz.
<pre class="brush:java; gutter:false" name="subListÖrneği">import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
...
List<Integer> ls = new LinkedList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
ls.subList(3, 7).clear(); // ls → [0, 1, 2, 7, 8, 9]
</pre>
<br/>
<h3 id="geziciler">Geziciler: <code>java.util.Iterator</code> ve <code>java.util.ListIterator</code></h3><br/>
<code>List</code> arayüzünün <code>Collection</code> vasıtasıyla <code>Iterable</code>'dan kalıtladığı düşünüldüğünde, liste elemanlarının, tıpkı diziler gibi, gezici <code class="java-kw">for</code> döngüsü ile dolaşılabileceği görülecektir. Yani, gezici nesne yaratıp başa konuşlandıktan sonra <code>hasNext</code> ve <code>next</code> iletilerinden yararlanarak dolaşmaktansa, listemizi aşağıdaki gibi dolaşmak da mümkündür ve kodun anlaşılabilirliğini artırmak adına bu yöntem yeğlenmelidir.
<pre class="brush:java; gutter:false" name="geziciFor">import java.util.List;
import java.util.ArrayList;
...
List<ElmTürü> ls = new ArrayList<>();
... // ls'yi doldur]
for (ElmTürü elm : ls) { ... }
</pre>
Ön-derleme sonrasında yukarıdaki <code class="java-kw">for</code> döngüsü J2SE 5.0 öncesinde programcının yazmak zorunda kaldığı şu şablon koda çevrilecektir. Dolayısıyla, gezici <code class="java-kw">for</code> döngüsünün, dolaşılabilir kapların kullanımında yararlanılan sözdizimsel bir kolaylık olduğunu söyleyebiliriz.
<pre class="brush:java; gutter:false" name="geziciForunDönüşümü">import java.util.Iterator;
...
for (Iterator<ElmTürü> gzc = ls.iterator(); gzc.hasNext();) {
ElmTürü elm = gzc.next();
...
}
</pre>
Gezicinin soyutlanarak doğrudan kullanımının önüne geçilmesi nedeniyle, gezici <code class="java-kw">for</code> döngüsünün gövdesinde kap içeriğinin güncellenmesi olanaksızdır; dolaşma esnasında kabın güncellenebilmesi için gezicinin bizzat programcı tarafından yaratılması gerekir. Bu durumda, geziciye gönderilecek bir <code>remove</code> iletisi, <code>next</code> ile döndürülen son elemanı silecektir.
<pre class="brush:java; gutter:false" name="geziciİleSilme">...
for (Iterator<ElmTürü> gzc = ls.iterator(); gzc.hasNext();) {
ElmTürü elm = gzc.next();
if (...) gzc.remove();
...
}
</pre>
<code>iterator</code>'ın döndürdüğü gezicinin anılan üç iletiye sınırlı olması bazen kısıtlayıcı olabilir. Doğru ya, ardışık elemanların ilişkilerini görebilmek için kabımız içinde ileriye doğru olduğu gibi geriye doğru da dolaşmak isteyebiliriz; gezme sırasında elemanları silmek isteyebileceğimiz gibi güncellemek, öncesine veya sonrasına yeni bir değer eklemek isteyebiliriz. Bu takdirde, <code>List</code> tutacağı aracılığıyla gönderilebilecek <code>listIterator</code> iletisinin kullanımı düşünülebilir. Hedef kabın <code>ListIterator</code> arayüzü tutacağı ile kullandırılan bir görüntüsünü döndüren bu ileti, geziciyi kabın başına veya argümanla belirtilen konuma konuşlandırabilir. İlkleme sonrasında, gezicinin sağladığı görüntü kullanılarak altyapıdaki kap ileri/geri yönde dolaşılabilir, uygun görülen yerlere yeni eleman eklenebildiği gibi, var olan elemanlar değiştirilebilir veya silinebilir. Bu noktada, yıkıcı (güncelleyici) işlemlerin etkisini altyapıdaki kap üzerinde göstereceği unutulmamalıdır. Örneğin, aşağıdaki kod parçasında <code>gzc</code> <code>ls</code> ile gösterilen kabın ortasına konuşlanmakta ve başa doğru dolaşırken değeri 3 olan elemanları 33 olacak şekilde güncellemektedir.
<pre class="brush:java; gutter:false" name="kapsamlıGezici">import java.util.ListIterator;
...
List<Integer> ls = new ArrayList<>();
// ls'yi doldur...
int orta = ls.size / 2;
for (ListIterator<Integer> gzc = ls.listIterator(orta); gzc.hasPrevious();) {
int elm = gzc.previous(); // Derleyici Integer→int dönüşümünü yapıyor
if (elm == 3) gzc.set(33);
...
}
</pre>
Kabın paylaşılması nedeniyle, yapılan değişikliklerin hem liste hem de gezici tutacağı ile görüleceği unutulmamalıdır. Bu noktanın gözden kaçırılması, çok-izlekli programlarda—mesela, kabın bir izlekte liste tutacağı yoluyla bir diğerinde ise gezici tutacağıyla değiştirildiği durumlarda—soruna yol açabilir.<br/>
<br/>
<code>Iterator</code> arayüzünden kalıtlayan <code>ListIterator</code> aşağıdaki tabloda verilen iletileri içerir. Eklemenin yapılacağı nokta aynı kapıya çıkan iki şekilde tanımlanabilir: i) <code>hasNext</code>'in <code class="java-kw">false</code> döndürmesi durumunda listenin sonuna, aksi takdirde <code>next</code> ile döndürülecek elemanın öncesine, ii) <code>hasPrevious</code>'ın <code class="java-kw">false</code> döndürmesi durumunda listenin başına, aksi takdirde <code>previous</code> ile döndürülecek elemanın sonrasına. Ayrıca, <code>remove</code> ve <code>set</code> iletilerinin en son okunmuş olanın dışında bir elemanı etkilemeyeceği ve bu iletilerin <code>add</code> veya bir diğer <code>remove</code> iletisi sonrasında bir başka okuma yapılmaksızın kullanılamayacağı unutulmamalıdır. Aksi takdirde, gönderi <code>IllegalStateException</code> ayrıksı durumunun firlatılmasıyla sona erecektir.<br/>
<br/>
<table align="center" border="1"><caption><code>ListIterator</code> <i>arayüzü</i></caption>
<thead><tr><th>İleti</th><th style="text-align:center">İşlev</th></tr></thead>
<tbody>
<tr><td><code>hasNext</code></td><td>Liste sonu sorgulama</td></tr>
<tr><td><code>next</code></td><td>Bir sonraki eleman</td></tr>
<tr><td><code>remove</code></td><td>En son okunan elemanı silme</td></tr>
<tr><td><code>nextIndex</code></td><td>Bir sonraki elemanın indisi</td></tr>
<tr><td><code>hasPrevious</code></td><td>Liste başı sorgulama</td></tr>
<tr><td><code>previous</code></td><td>Bir önceki eleman</td></tr>
<tr><td><code>previousIndex</code></td><td>Bir önceki elemanın indisi</td></tr>
<tr><td><code>add</code></td><td>Ekleme</td></tr>
<tr><td><code>set</code></td><td>En son okunan elemanı güncelleme</td></tr>
</tbody></table><br/>
Gezici kullanımı sırasında şu husus akılda tutulmalıdır: geziciler dolaştırdıkları listenin değişebilirlik, çok/tek-izleklilik ve tür güvenlilik gibi özelliklerini korur. Buna göre, aşağıdaki kod parçasında, <code>lsGzc</code> vasıtasıyla değişikliğe izin verilirken, <code>sabitLsGzc</code>'nin aynı kabı değiştirmek amacıyla kullanılması <code>UnsupportedOperationException</code> ayrıksı durumuna neden olacaktır.
<pre class="brush:java; gutter:false" name="kapsamlıGezici">import java.util.Collections;
...
List<Integer> ls = new ArrayList<>();
// ls'yi doldur...
List<Integer> sabitLs = Collections.unmodifiableList(ls);
...
ListIterator<Integer> lsGzc = ls.listIterator();
ListIterator<Integer> sabitLsGzc = sabitLs.listIterator();
</pre>
<code>java.util.Enumeration</code> arayüzüne değinerek geziciler bahsini kapatalım. İşlevsel olarak <code>Iterator</code> arayüzünün silme özelliği barındırmayan uyarlaması olarak düşünülebilecek bu arayüzün kullanımından kaçınılmalıdır. Daha önceden tanımlanmış olup ıslah edilerek VKÇ'ye katılan <code>Hashtable</code> ve <code>Vector</code> gibi kapların bu arayüzün tutacağı ile görüntü döndüren iletileri yerine, eşdeğer bir diğer iletinin kullanılması yerinde olacaktır. Örneğin, <code>Vector</code> nesnelerine <code>elements</code> yerine <code>iterator</code> iletisi gönderilmelidir. Bu, VKÇ ile birlikte tanımlanan <code>Vector</code> benzeri (<code>ArrayList</code> gibi) kapların <code>elements</code>'i desteklemeyip <code>iterator</code>'ı desteklemesi nedeniyle kodun yeniden kullanılabilirliği adına daha doğru olacaktır.<br/>
<br/>
<h3 id="listeGerçekleştirimleri">Genel Liste Gerçekleştirimleri</h3><br/>
Altyapıda yararlanılan kabın erişim özelliğine bağlı olarak <code>List</code> arayüzünü gerçekleştiren sınıflar, doğrudan erişimli ve ardışık erişimli olmak üzere ikiye ayrılırlar. Bu ayrım, kimi iletilerin performanslarını etkilemesi ve desteklenebilecek ekstra iletiler nedeniyle, söz konusu kapların kullanıldıkları programların çalışma hızını ve anlaşılabilirliğini etkileyecektir; hangi somut sınıfın kullanılacağına dair yapılan seçimde bu nokta göz önünde bulundurulmalıdır. Kodun yeniden kullanılabilirliğini artırmak ve yapılacak muhtemel değişikliklerin etkisini en aza indirmek adına, tutacak türü olarak arayüz ve soyut sınıf adları kullanmaya çalışılmalı, somut sınıfların kullanımı nesne yaratma noktalarına sınırlı tutulmalıdır. Tercih edilen somut sınıfın kesinleşmesinden sonra bile arayüzlerde listelenen iletiler sınıfa özel eşdeğer iletilere yeğlenmeli, sınıfa özel iletiler ancak ek getirileri olmaları durumunda seçilmelidir. Örneğin, <code>Vector</code>'de karar kılınsa dahi <code>List</code> arayüzündeki <code>get</code> kullanılmalıdır, <code>Vector</code> sınıfına özel <code>elementAt</code> değil. Buna karşılık, <code>subList</code> ve tek argümanlı <code>indexOf</code> iletisinin birlikte kullanımı yerine iki argümanlı <code>indexOf</code> iletisinin kullanımı daha anlaşılabilir olması ve [marjinal] hız farkı nedeniyle tercih edilebilir.
<pre class="brush:java; gutter:false" name="indexOfTercihi">adlar.indexOf("Ali", 5); // adlar.sublist(5, adlar.size()).indexOf("Ali")
</pre>
Java VKÇ, gerçekleştirimcinin işini kolaylaştırmak amacıyla somut sınıfların üzerine inşa edileceği soyut sınıflar sağlar. Doğrudan erişimli bir altyapı üzerine oturtulan bir genel liste için bu soyut sınıf <code>AbstractList</code> iken, ardışık erişimli altyapı kullanarak aynı işe soyunanların doğru adresi, <code>AbstractList</code>'ten kalıtlayan <code>AbstractSequentialList</code>'tir. İskelet görevi gören bu sınıflar, yararlanılacak altyapının ayrıntılarına dair varsayımlarda bulunulamaması nedeniyle, kimi iletilere karşılık gerçekleştirim sağlamazken kimi iletiler için genel [ve yavaş] gerçekleştirimler sağlar. Dolayısıyla, VKÇ'ye yeni bir kap gerçekleştirimi eklemek isteyenler, soyut sınıflardan uygun olanından kalıtlayarak iskeleti ete büründürmelidir.<br/>
<br/>
VKÇ'ye yeni bir kap gerçekleştirimi eklemek isteyenlerin sabit içeriklilik ve değişken uzunlukluluk özelliklerini destekleyip desteklememelerine bağlı olarak sorumlulukları da değişir. Örneğin, tüm doğrudan erişimli listeler <code>get</code> ve <code>size</code> iletilerine karşılık gelen metotları sağlamak zorundayken, listenin değişken içerikli olmasının istenmesi durumunda, <code>AbstractList</code>'te <code>UnsupportedOperationException</code> ayrıksı durumu fırlatacak şekilde gerçekleştirilen <code>set</code> ezilmelidir. Benzer şekilde, listenin değişken uzunluklu olması isteniyorsa, iki argümanlı <code>add</code> ve <code class="java-kw">int</code> argüman alan <code>remove</code> metotlarının da ezilmesi gerekir. Ardışık erişimli listelerin gerçekleştiriminde de aynı ayrım geçerlidir: <code>AbstractSequentialList</code>'ten kalıtlayıp <code>size</code> ve <code>listIterator</code> iletilerini gerçekleştirmesi beklenen geliştiriciler, <code>listIterator</code>'ın döndürdüğü gezicinin alacağı iletileri gerçekleştirirken sabit içeriklilik ve değişken uzunlukluluk özelliklerini hesaba katmalıdırlar. Ardışık erişimli listelerden sabit içerikli [ve dolayısıyla sabit uzunluklu] olanlar, <code>ListIterator</code> arayüzünde bulunan <code>next</code>, <code>hasNext</code>, <code>nextIndex</code>, <code>previous</code>, <code>hasPrevious</code> ve <code>previousIndex</code> için gerçekleştirim sağlamak zorundayken, diğer iletilerin gerçekleştiriminde <code>UnsupportedOperationException</code> döndürmelidirler. Buna karşılık, değişken içerikli, sabit uzunluklu listeler, anılan arayüzün <code>add</code> adlı iletisini de gerçekleştirmek durumundadır. Son olarak, değişken uzunluklu listeler, arayüzde bulunan tüm iletileri gerçekleştirmelidir.<br/>
<br/>
<h3 id="doğrudanErişimliListeler">Doğrudan Erişimli Listeler: <code>java.util.ArrayList</code> ve <code>java.util.Vector</code></h3><br/>
VKÇ'de doğrudan erişimli genel liste gerçekleştirimi sağlayan iki sınıf vardır: <code>ArrayList</code> ve <code>Vector</code><a href="http://ta-java.blogspot.com/2011/09/degisken-uzunluklu-diziler.html">🔎</a>. Diziden yararlanarak işini gören bu sınıflar arasında işlevsellik ve performans açısından pek fark yoktur. Ancak, yine de şu noktaların akılda tutulmasında yarar olacaktır:<br/>
<br/>
<ul>
<li>Java'nın ilk uyarlamasından bu yana var olan <code>Vector</code>, diğer J2SE 1.2 öncesi kaplar gibi, çok-izlekli iken <code>ArrayList</code> tek-izleklidir. Dolayısıyla, <code>ArrayList</code> türlü kapların çok-izlekli bir programda kullanılması durumunda, farklı izleklerden eşzamanlı erişimin sebep olabileceği tutarsızlıkları önlemek adına bazı kod bölgelerinin <code class="java-kw">synchronized</code> bloğu içine alınarak çok-izlekli hale getirilmeleri gerekir. Buna karşılık, kamuya açık tüm metotları <code class="java-kw">synchronized</code> olan <code>Vector</code> sınıfının tek-izlekli veya erişim serileştirme gereksiniminin düşük olduğu çok-izlekli programlarda kullanılması, çalışma hızının gereksiz yere yavaşlamasına neden olacaktır. Bu gibi durumlarda, <code>ArrayList</code> türlü bir kabın kullanımı ve gerekli noktalarda <code>Collections</code> sınıfındaki <code>synchronizedList</code> metodu ile döndürülen görüntü üzerinde işlem yapılması da bir çözüm olarak hesaba katılmalıdır.<sup><a href="#dn2" id="ref2">2</a></sup></li>
<li>Her iki sınıf da, eleman sayısının artmasını gerektirecek bir işlem sırasında sığanın otomatik olarak büyümesini sağlar. Ne var ki, <code>Vector</code> sınıfında ilişkin kabın yaratılması esnasında sağlanacak bir argüman ile sığa artımına müdahale edilebilirken, <code>ArrayList</code> sınıfında böyle bir olanak yoktur.</li>
</ul>
<br/>
Yukarıda da söylediğimiz gibi, her iki sınıf da benzer performansa sahiptir. <code>get</code>, <code>set</code>, <code>size</code>, <code>isEmpty</code>, <code>iterator</code> ve <code>listIterator</code> iletileri sabit zamanlı performans sergilerler. Ayrıca, kabın sığa artımına ihtiyaç duyduğu durumlarda yaşanan düşüşe karşın, kap sonuna eleman ekleyen <code>add</code> iletisi de ortalamada sabit zamanlı bir performans gösterir. Buna karşılık, diğer iletilerin arama temelli olanları, kap içeriğini dolaşmak zorunluluğu nedeniyle doğrusal veya daha pahalı bir performans sergileyecektir.<br/>
<br/>
<h3 id="kısıtlıListeler">Kısıtlı Listeler: <code>java.util.PriorityQueue</code> ve <code>java.util.ArrayDeque</code></h3><br/>
C'den başka programlama dili bilmeyenlerin yaydığı bir efsanedir: liste denince akla bağlaçlar, bağlaç denince ise göstericiler gelir. Eğer bu bedbahtlardansanız, olasıdır ki yazımızın bağlaçlı listelerle başlamamış olması sizi şaşırtmıştır. Size kötü bir haber daha verelim, genel liste arayüzünün bir altkümesine sınırlı işlevsellik sunan kısıtlı listelere değinmeden bağlaçlı listelere geçmeyeceğiz.<br/>
<br/>
VKÇ'de desteklenen ve tür sıradüzeni aşağıda verilen kısıtlı liste çeşitlerini anımsayarak başlayalım. Üç temel işlemin de <i>üst</i> olarak nitelenen aynı liste ucunu etkilediği bir kısıtlı liste olan <i>yığıt</i> (İng., stack), herhangi bir noktada çıkarılacak (veya sorgulanılacak) ilk elemanın en son eklenen eleman olması nedeniyle <i>son giren ilk çıkar listesi</i> olarak da adlandırılır. Bir diğer kısıtlı liste, gerçek yaşamdaki aynı adlı kavramı temsil eden ve uygarlıktan nasibini almış herkesin takdir edeceği nedenlerden ötürü <i>ilk giren ilk çıkar listesi</i> olarak adlandırılan <i>kuyruk</i>tur (İng., queue). Kuyruğun elemanları arasında bir sıralamanın istenmesi halinde—yani, kuyruğun belirli bir ölçüte göre oluşturulması istendiğinde—ekleme işlemi kuyruğun sonu yerine herhangi bir yerine yapılabilir. Böylesine kuyruklara, <i>öncelikli kuyruk</i> (İng., priority queue) denir. Son olarak, temel işlemlerin aynı listenin her iki ucuna da uygulanabildiği kısıtlı listelere <i>çift-uçlu kuyruk</i> adı verilir.<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTYf1XucjeX7sSc_O17YBYTvWAKwsyVRPe0TraOUgUXEAEJrtofJn7WPYa4OOCMOYrX0A3mjZIYKZMJMrRgONe-oWPxb592WHacjm4AX_KITiSfunVYoQa0CY4gVQP4IhSsQYxVgS2dL66/s400/K%2525C4%2525B1s%2525C4%2525B1tl%2525C4%2525B1ListelerS%2525C4%2525B1rad%2525C3%2525BCzeni.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="150" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTYf1XucjeX7sSc_O17YBYTvWAKwsyVRPe0TraOUgUXEAEJrtofJn7WPYa4OOCMOYrX0A3mjZIYKZMJMrRgONe-oWPxb592WHacjm4AX_KITiSfunVYoQa0CY4gVQP4IhSsQYxVgS2dL66/s400/K%2525C4%2525B1s%2525C4%2525B1tl%2525C4%2525B1ListelerS%2525C4%2525B1rad%2525C3%2525BCzeni.png" /></a></div>
<br/>
Yukarıdaki şeklin incelenmesi kuyruk ve yığıt yapılarının VKÇ tarafından desteklenmediği kanısını yaratabilir. Java uzmanı olanlarınız ise, aslında bu desteğin var olduğunu, sorunun şeklin eksik olmasında yattığını iddia edeceklerdir. Her iki görüşte de haklılık payı olduğunu, duruma daha sonra açıklık getirileceğini söyleyelim ve <code>Queue</code> arayüzünün açıklaması ile devam edelim. <code>Collection</code>'dan kalıtlayan bu arayüz, aynı işlemin istisnai durumlarda iki farklı biçimde davranması nedeniyle, aşağıdaki tabloda listelenen altı iletiyi içerir. Sabit uzunluklu, dolu bir kuyruğa <code>add</code> ile ekleme yapılması <code>IllegalStateException</code> ayrıksı durumuna neden olurken, aynı kuyruğa <code>offer</code> ile eleman eklenmeye çalışılması, C fonksiyonlarını andırırcasına, <code class="java-kw">null</code> değerinin döndürülmesine neden olacaktır. Sorgulama ve çıkarma işlemleri de boş bir kuyruğa uygulanmaları halinde benzer bir davranış sergileyecektir.<br/>
<br/>
<table align="center" border="1"><caption><i>Kuyruk: İlk giren ilk çıkar listesi</i></caption>
<thead><tr><th style="text-align:center">İşlem</th><th style="text-align:center">İleti (<code>Queue</code>)</th><th style="text-align:center">Başarısızlıkta döndürülen değer</th><th style="text-align:center">Denk ileti (<code>Deque</code>)</th></tr>
</thead>
<tbody>
<tr><td rowspan="2">Ekleme</td><td><code>add</code></td><td><code>IllegalStateException</code></td><td><code>addLast</code></td></tr>
<tr><td><code>offer</code></td><td><code class="java-kw">false</code></td><td><code>offerLast</code></td></tr>
<tr><td rowspan="2">Sorgulama</td><td><code>element</code></td><td><code>NoSuchElementException</code></td><td><code>getFirst</code></td></tr>
<tr><td><code>peek</code></td><td><code class="java-kw">null</code></td><td><code>peekFirst</code></td></tr>
<tr><td rowspan="2">Çıkarma</td><td><code>remove</code></td><td><code>NoSuchElementException</code></td><td><code>removeFirst</code></td></tr>
<tr><td><code>poll</code></td><td><code class="java-kw">null</code></td><td><code>pollFirst</code></td></tr>
</tbody></table>
<br/>
<code>Queue</code> arayüzüne dayalı kuyruk gerçekleştirimini kolaylaştırmak amacıyla sağlanan <code>AbstractQueue</code>'dan kalıtlayan sınıflara baktığımızda, değişken uzunluklu öncelikli kuyruk yapısını <i>ikili yığın</i> (İng., binary heap) kullanarak gerçekleştiren <code>PriorityQueue</code> sınıfını görürüz. Dizi üzerine ikili ağaç benzeri bir yapı oturtarak işini gören bu gerçekleştirim stratejisi sayesinde, temel işlemlerden sorgulama sabit zamanda sonuç verirken, ekleme ve çıkarma logaritmik zaman almaktadır. Öncelikli kuyruğun eleman sayısını öğrenme, boşluk yüklemi ve gezici yaratma da sabit zamanda mümkün olmaktadır. Buna karşılık, geri kalan iletiler doğrusal zaman gerektirmektedir.<br/>
<br/>
<code>PriorityQueue</code> sınıfının yapıcılarına bakıldığında iki nokta dikkat çeker. Öncelikle, değişken uzunluklu olması hasebiyle taşmaması için büyütülen kuyruklar, istenildiği takdirde yaratıldıkları noktada belirli bir ilk sığaya sahip olacak şekilde yaratılabilir. Kuyruğun eleman eklenerek büyümesi sırasında altyapıdaki dizinin büyütülmesinden kaynaklanacak performans kaybını azaltacak bu parametre, sabit uzunluklu kuyruklarda yer ve zaman kayıplarını sıfıra indirecektir. Dikkatimizi çeken ikinci husus, iki argümanlı yapıcıdaki ikinci argümanın <code>Comparator</code> türlü olmasının hatırlattığı ayrıntıdır: öncelikli kuyruk içine konulmak istenecek nesnelerin, hangi sınıftan olurlarsa olsunlar, karşılaştırılabilir olmaları gerekir. Durup düşünüldüğünde bu önkoşulun çok doğal olduğu görülecektir: karşılaştırılamayan nesneler sıraya dizilemezler!<sup><a href="#dn3" id="ref3">3</a></sup> Dolayısıyla, nesnelerin <code>Comparable</code> arayüzünü gerçekleştiren bir sınıfa ait olması veya nesneleri karşılaştırmayı bilen bir karşılaştırıcı nesnenin fazladan sağlanması zorunludur.<br/>
<br/>
Gelelim ortalıkta gözükmeyen yığıt ve kuyruk gerçekleştirimlerinin nerede olduklarına. Bunun için, çift-uçlu kuyruk yapısının tanımlandığı <code>Deque</code> arayüzünü gerçekleştiren <code>ArrayDeque</code> sınıfına bakmak yetecektir. Çünkü, VKÇ'de bir kabın yığıt veya kuyruk gibi davranması, bu yapılara uyumlu iletilerin <code>ArrayDeque</code> nesnelerine gönderilmesiyle sağlanır.<sup><a href="#dn4" id="ref4">4</a></sup>
<pre class="brush:java; gutter:false" name="yığıtVeKuyruk">import java.util.Deque;
import java.util.Queue;
import java.util.ArrayDeque;
...
Deque<Integer> yığıt = new ArrayDeque<>();
Queue<Integer> kuyruk = new ArrayDeque<>();
</pre>
Örneğimizdeki asimetrik kullanım dikkatinizi çekmiştir: tutacak türü olarak, kuyruk için <code>Queue</code> arayüzü kullanılırken, yığıt için çok-uçlu kuyruğa dair <code>Deque</code> arayüzü kullanılmaktadır. Dolayısıyla, <code>kuyruk</code> adlı kap kuyruklara özel iletilerle kullanılacakken, <code>yığıt</code>'ın kullanımında seçtiğimiz tanımlayıcı adı aldatıcı olacaktır. Zira, söz konusu tutacak <code>Deque</code> arayüzünde tanımlı olan ve aşağıdaki tabloda sağlanan yığıt iletilerinin yanısıra diğer tüm iletilere de izin verecektir.<br/>
<br/>
<table align="center" border="1"><caption><i>Yığıt: Son giren ilk çıkar listesi</i></caption>
<thead><tr><th style="text-align:center">İşlem</th><th style="text-align:center">İleti</th><th style="text-align:center">Başarısızlıkta döndürülen değer</th><th style="text-align:center">Denk ileti</th></tr>
</thead>
<tbody>
<tr><td rowspan="2">Ekleme</td><td><code>push</code></td><td><code>IllegalStateException</code></td><td><code>addFirst</code></td></tr>
<tr><td>—</td><td><code class="java-kw">false</code></td><td><code>offerFirst</code></td></tr>
<tr><td rowspan="2">Sorgulama</td><td><code>peek</code></td><td><code class="java-kw">null</code></td><td><code>peekFirst</code></td></tr>
<tr><td>—</td><td><code>NoSuchElementException</code></td><td><code>getFirst</code></td></tr>
<tr><td rowspan="2">Çıkarma</td><td><code>pop</code></td><td><code>NoSuchElementException</code></td><td><code>removeFirst</code></td></tr>
<tr><td>—</td><td><code class="java-kw">null</code></td><td><code>pollFirst</code></td></tr>
</tbody></table>
<br/>
Bu noktada, bazılarınız, Java'nın ilk uyarlamasından bu yana var olan <code>Stack</code> sınıfı nerelerde, diye soruyor olabilir. Hemen yanıtlayalım: eskiden olduğu yerde, emrinize amade sizi bekliyor. Ama, sakın ola ki, sırf adı yığıt sözcüğünün İngilizce karşılığı olduğu için bu sınıfı kullanmanın daha iyi bir fikir olduğunu düşünmeyin. Çünkü, geliştiricilerin, muhtemelen işi aceleye getirmeleri nedeniyle, <code>Vector</code> sınıfından kalıtlayarak gerçekleştirdiği bu sınıf, yığıt kavramıyla ilgili ilgisiz çok sayıda iletiyi desteklemektedir. Mesela, aşağıdaki kod parçasında yaratılan kap, yığıta dair iletilerin yanısıra, <code>Vector</code> sınıfının desteklediği diğer iletileri de, doğrudan erişimliler dahil olmak üzere, itiraz etmeden kabul eder.
<pre class="brush:java; gutter:false" name="eskiYığıt">import java.util.Stack;
...
Stack<Integer> yığıt = new Stack<>();
// List arayüzündeki tüm iletiler yığıt'a gönderilebilir!
</pre>
Geldiğimiz noktada, üç farklı kısıtlı liste için sağladığı destek ve söz konusu yapıların farklı terminoloji kullanması nedeniyle, <code>Deque</code> arayüzünü (ve gerçekleştirimcisi <code>ArrayDeque</code> sınıfını) anlamakta zorluk çekiyor olabilirsiniz; bazı işlemlerin istisnai durumlarda iki değişik şekilde tepki vermesi işi daha da kötüleştirebilir. Ancak ürkmeyin, aşağıdaki tablonun yorumu sizi işinizin düşündüğünüz kadar zor olmadığına inandıracaktır.<br/>
<br/>
<table align="center" border="1"><caption><i>Çift-uçlu kuyruktan yararlanarak kuyruk ve yığıt</i></caption>
<thead><tr><th colspan="3" rowspan="2" style="text-align:center"></th><th colspan="2" style="text-align:center">Denk iletiler</th></tr>
<tr><th style="text-align:center">C tarzı dönüş</th><th>Ayrıksı durum</th></tr></thead>
<tbody>
<tr><td rowspan="6">Kuyruk</td><td rowspan="2">Ekleme</td><td><code>add</code></td><td>—</td><td><code>addLast</code></td></tr>
<tr><td><code>offer</code></td><td><code>offerLast</code></td><td>—</td></tr>
<tr><td rowspan="2">Sorgulama</td><td><code>element</code></td><td>—</td><td><code>getFirst</code></td></tr>
<tr><td><code>peek</code></td><td><code>peekFirst</code></td><td>—</td></tr>
<tr><td rowspan="2">Çıkarma</td><td><code>remove</code></td><td>—</td><td><code>removeFirst</code></td></tr>
<tr><td><code>poll</code></td><td><code>pollFirst</code></td><td>—</td></tr>
<tr><td rowspan="3">Yığıt</td><td>Ekleme</td><td><code>push</code></td><td><code>offerFirst</code></td><td><code>addFirst</code></td></tr>
<tr><td>Sorgulama</td><td><code>peek</code></td><td><code>peekFirst</code></td><td><code>getFirst</code></td></tr>
<tr><td>Çıkarma</td><td><code>pop</code></td><td><code>pollFirst</code></td><td><code>removeFirst</code></td></tr>
</tbody></table>
<br/>
Çift-uçlu kuyruklarda üç temel işlemin—ekleme, çıkarma (silme) ve sorgulama (göz atma)—kabın her iki ucuna da uygulanabileceğini hatırlatarak başlayalım. Söz konusu altı işlemin istisnai durumlarda farklı tepki veren iki ileti ile karşılandığı da düşünüldüğünde, yedi tanesi tablomuzun sağ kısmında yer alan on iki iletinin gizemi çözülür. Bunlara, temel işlemlerin yığıt ve kuyruk terminolojisindeki karşılıkları olan üçüncü sütunda listelenen dokuz iletiyi de eklersek, toplamda yirmi bir iletiyi buluruz. <code>Collection</code>'dan gelenler ve <code>Deque</code>'in geri kalan üç iletisi (<code>descendingIterator</code>, <code>removeFirstOccurrence</code> ve <code>removeLastOccurrence</code>) ile beraber düşünüldüğünde, bu sayı başta korkutucu gelebilir. Ancak, çift-uçlu kuyruğu ne şekilde kullanacağınıza karar vermenizle birlikte yirmi bir ileti aniden yığıt için üçe, kuyruk için üç veya altıya, çift-uçlu kuyruk içinse altı veya on ikiye düşecektir.<br/>
<br/>
<code>ArrayDeque</code> dairesel arabellek (İng., circular buffer) şeklinde kullanılan bir dizi ile gerçekleştirim sağlar. Bu sayede, temel işlemlerin her iki uca uygulanmasına karşılık gelen tüm iletiler sabit zamanda işlerini bitirirler. Aynı nedenden ötürü, temel işlemlerin toptan uygulanmaları da eleman başına sabit bir maliyet sergileyecektir. Buna karşılık, kabın taranmasını gerektiren arama iletileri doğrusal zamanlı performansa sahiptirler. Son olarak, altyapıda kullanılan dizinin yetersiz sığaya sahip olduğunun görülmesi dizinin genişletilmesine neden olacağı için, ekleme iletilerinin nadiren de olsa bazen reklam edilenden düşük bir performans sergileyebileceği unutulmamalıdır. Bu duruma düşmemek için <code>ArrayDeque</code> nesnesini muhtemel kullanıma uygun bir sığa ile yaratmak yardımcı olacaktır.<br/>
<br/>
<h3 id="bağlaçlıListe">Bağlaçlı Liste: <code>java.util.LinkedList</code></h3><br/>
VKÇ'nin genel liste gerçekleştirimi olarak sunduğu ikinci seçenek, <code>List</code> ve <code>Deque</code> arayüzlerini ardışık erişimli bir yapı kullanarak gerçekleştiren <code>LinkedList</code> sınıfıdır. Baş ve son düğümlerin tutacaklarını barındıran çift bağlaçlı bir yapıya sahip bu sınıfta ekleme ve çıkarma, liste içindeki bağlaçların güncellenmesiyle tamamlanır. Bunun doğal bir sonucu olarak, herhangi bir elemana erişmenin tek yolu, <code>get</code>/<code>set</code> gibi iletiler kullanılıyor olsa bile, ardışık erişimdir; istenen bir elemana erişilmesi için, söz konusu eleman öncesindeki/sonrasındaki tüm elemanların ziyaret edilmesi zorunludur. Dolayısıyla, doğrudan erişim üzerine kurulu ikili arama gibi algoritmalarda bu sınıf yerine <code>ArrayList</code> veya <code>Vector</code> gibi bir sınıfın kullanılması daha mantıklı olacaktır. Buna karşılık, araya eklemenin sık yapıldığı ve ardışık erişime mecbur olunan durumlarda—mesela, <i>ekleme sıralaması</i> (İng., insertion sort) algoritmasının gerçekleştiriminde—seçimin <code>LinkedList</code>'ten yana olması gerekir. Çünkü, bağlaçlı bir yapı kullanılarak gerçekleştirilen bir kabın ortasına yapılan eklemeler/çıkarmalar doğrudan erişimli kapların neden olduğu gibi kaydırmanın getireceği olumsuzluktan etkilenmez. Son olarak, kabın iki ucuna erişimin sabit maliyetli olması, size yığıt ve kuyruk gibi kısıtlı listeler için <code>LinkedList</code>'in çekici bir seçenek olduğunu düşündürebilir.
<pre class="brush:java; gutter:false" name="yığıtVeKuyruk-2">...
import java.util.LinkedList;
...
Deque<Integer> yığıt = new Linked<>();
Queue<Integer> kuyruk = new LinkedList<>();
</pre>
Yukarıda sağlanan çözümün <code>ArrayDeque</code> kullanılarak sağlanan çözümle daha sağlıklı bir şekilde karşılaştırılabilmesi için, <code>ArrayDeque</code>'in altyapıda kullanılan dizinin boş konumları için maliyet ödetmesine karşılık, <code>LinkedList</code>'in her bir eleman için fazladan iki bağlaç tutmak zorunda olduğu unutulmamalıdır. <code>ArrayDeque</code> nesnesinin duruma uygun bir ilk sığaya sahip yaratılması imkanı ile birlikte ele alındığında, bu durumun temel işlemlerin zaman maliyetini marjinal de olsa <code>ArrayDeque</code> lehine çevirdiği görülecektir. Dolayısıyla, yığıt ve kuyruk kullanımı için ilk önerdiğimiz yöntemin daha doğru olduğu söylenebilir. <br/>
<br/>
</div>
<div style="text-align: right;">
<a alt="Java'da değişken uzunluklu diziler-java.util.Vector" href="http://ta-java.blogspot.com/2011/09/degisken-uzunluklu-diziler.html">Değişken Uzunluklu Diziler: <code>Vector</code></a><br/>
<a alt="Hızlı arama zamanlı kaplar (Eşlemler)-java.util.Map, java.util.AbstractMap, java.util.HashMap, java.util.Hashtable, java.util.LinkedHashMap" href="http://ta-java.blogspot.com/2011/12/hzl-arama-zamanl-kaplar-1.html">Hızlı Arama Zamanlı Kaplar</a><br/>
</div>
<hr/>
<div id="dipnotlar" style="text-align:justify">
<ol>
<li id="dn1">VKÇ'nin ortaya konduğu J2SE 1.2 öncesinde tasarlanıp gerçekleştirilmiş olan veri kaplarının dokümantasyonunda iki noktayı kafa karıştırıcı bulabilirsiniz. [Islah edilerek çerçeveye oturtulması olanaksız gözüken] <code>Dictionary</code> ve <code>Stack</code> sınıflarını kullanmaktan kaçınılması tavsiye edilirken, <code>Hashtable</code> ve <code>Vector</code> sınıflarında tamamıyla aynı şeyi yapan farklı adlara sahip pek çok ileti çifti göze çarpar. Her iki durumda da sebep, VKÇ'nin ortaya koyduğu standart ve söz konusu sınıfların bu standarda uyumdaki başarısı(zlığı)dır. Programcı olarak bizim payımıza düşen ise, dokümantasyonda önerildiği gibi, ilk iki sınıf yerine önerilen seçeneklerden yararlanmak ve diğer iki sınıfta standartta bulunan ada sahip iletileri kullanmaktır. Böyle bir yaklaşım, yazdığımız kodun yeniden kullanılabilirliğini artıracaktır. <a href="#ref1">↑</a></li>
<li id="dn2">Metotlarının <code class="java-kw">synchronized</code> ilan edilmiş olması, <code>Vector</code> sınıfını tamamıyla izlek güvenlikli kılmaz. Bu şekilde sağlanan güvence metotların teker teker kullanımlarına ilişkindir; <code>Vector</code> sınıfındaki iki veya daha fazla sayıda metodun aynı atomik işlemin parçası olarak icra edilmesini gerektiren durumlarda, iş yine programcıya düşecektir. <a href="#ref2">↑</a></li>
<li id="dn3">Aslına bakarsanız, sıralanmak istenen verinin ikillerini 0 veya 1 olmasına göre gruplara ayırarak işini gören bazı algoritmalar karşılaştırma işleminden yararlanmazlar. Ne var ki, bu algoritmaların uygulanabilirliği bazı ilkel türlere kısıtlı olduğu için, bu istisna genelleştirilerek vardığımız yargıyı boşa çıkarmak için kullanılamaz. <a href="#ref3">↑</a></li>
<li id="dn4">Aslına bakacak olursanız, ayrıcalıklı kuyruktan yararlanarak yığıt ve kuyruk displinine sahip kapların oluşturulması mümkündür. Bunun için yapılması gereken, elemanların ekleme zamanlarına dair bilgi içermeleri ve bu bilginin ayrıcalıklı kuyruğun sıralama ölçütü olarak kullanılmasıdır. <a href="#ref4">↑</a></li>
</ol>
</div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-90337608638655094742012-01-11T15:43:00.001+02:002012-01-13T11:34:31.135+02:00Clojure, Java Platformunun Fonksiyonel Programlama Dili<div id="anaMetin" style="text-align:justify">Bu yazımızda, daha önceden fonksiyonel programlamaya aşık olup da JSM aleminde aşklarının karşılıksız kaldığını düşünenlerin yanıldığını anlatmaya çalışacağız: JSM'nin fonksiyonel programlama dillerinden Clojure'a (okunuş: Klojur) göz atacağız. Yeni bir programlama dili öğrenmek fikrinden korkanlarınızın içi rahat olsun, Common Lisp, Scheme gibi dilleri bilenlerde karşı konulmaz bir déjà vu hissi uyandıracak Lisp ailesinin bu genç üyesinin sıfırdan öğrenilmesi, anılan ailedeki dillerin yalın sözdizimi ve az sayıdaki kuralı nedeniyle pek fazla zorlayıcı olmayacak. Yeter ki, programlamanın aslında eğlenceli bir şey olabileceğine ve bir problemin birden çok farklı şekilde çözülebileceğine inanın.<br />
<br />
<ul>
<li><a href="#nedenClojure">Peki ama, neden Clojure?</a></li>
<li><a href="#kurulum">Kurulum</a></li>
<li><a href="#hesaplamaNesnesiOlarakFonksiyonlar">Fonksiyonlar birer hesaplama nesnesidir</a></li>
<li><a href="#temelVeriTürleri">Temel veri türleri</a></li>
<li><a href="#veriKapları">Veri kapları</a></li>
<li><a href="#fonksiyonTanımı">Fonksiyon tanımı, yeniden</a></li>
<li><a href="#özyineleme">Özyineleme</a></li>
</ul>
<br/>
<h3 id="nedenClojure">Peki Ama, Neden Clojure?</h3><br/>
Çok yerinde bir soru. Zira, Clojure arkasında büyük bir şirket veya programcı topluluğunun bulunduğu bir programlama dili değil. Ancak; Java, Scala<a href="http://ta-java.blogspot.com/2011/05/java-platformunun-dikkate-deger-bir.html">🔎</a>, Jython, JRuby, BeanShell<a href="http://ta-java.blogspot.com/2011/06/snama-arac-olarak-beanshell.html">🔎</a>, Groovy, vd. gibi, Clojure da bir JSM dili: Java programlama dilinin kullanımına hazır olan tüm kitaplıklar Clojure kaynak kodu içinden de kullanılabilir; Clojure kaynak kodu istenecek olursa sınıf dosyasına derlenerek Java platformunun parçası haline getirilebilir. Ayrıca, bazı diğer özelliklerinin Clojure'u önümüzdeki yıllarda daha çekici kılacağını söylemek falcılık olmayacaktır. Dolayısıyla, Clojure'u elinizin tersiyle itmeden önce biraz daha düşünmenizde yarar var.<br/>
<br/>
Clojure'un <i>Lambda Hesaplama Kuramı</i> (İng., Lambda Calculus) üzerine inşa edilmiş bir fonksiyonel programlama dili olduğunu söyleyerek başlayalım. Süslü kısmı aradan çıkararak ifade edecek olursak, problem çözümünün matematikteki karşılıklarına benzer bir şekilde ele alınan ve fonksiyon adı verilen altyordamlar yoluyla sağlandığını söyleyebiliriz; doğal olarak, problemlerin fonksiyonların bileştirilmesiyle çözüldüğü bu paradigmada birimsellik aracı olarak fonksiyonlar kullanılır. Birinci sınıf vatandaş olan fonksiyonlar, diğer fonksiyonlara argüman olarak geçirilip, fonksiyonlardan sonuç olarak döndürülebilir ve herhangi bir bileşke türlü değerin bileşeni olarak tutulabilirler. Yani, C'de fonksiyon göstericileri, Java'da arayüzler vasıtasıyla zahmetli bir şekilde, kısmen elde edilebilen bir şey, Clojure'da çok daha kapsamlı bir paketin parçası olarak bedavaya gelir.<sup><a href="#dn1" id="ref1">1</a></sup> Bu, Google tarafından geliştirilmiş olup yoğun bir biçimde kullanılan ve Apache'nin <a href="http://hadoop.apache.org/">Hadoop</a> projesi vasıtasıyla özgür yazılım dünyasına kazandırdığı <a href="http://www.mapreduce.org/">MapReduce</a> adlı bulut hesaplama çerçevesinin önümüzdeki aylar, yıllar içinde karşınıza çıktığında işinizin daha kolay olacağı anlamını taşır. Çünkü, MapReduce kullanılarak üretilecek çözümler, fonksiyonel programlama dillerinin vazgeçilmezi <code>map</code>, <code>reduce</code> ve <code>filter</code> gibi yüksek dereceli fonksiyonların—argüman olarak fonksiyon alan ve/veya dönüş değeri olarak fonksiyon döndüren fonksiyon—birlikte kullanımını andıracaktır. Buna Hadoop temelli yazılım çerçevelerinin gerçekleştirim dilinin Java olması da eklendiğinde, çok çekirdekli hesaplamaya ince ayarlı bir JSM dili olan Clojure'un farklılaştırıcı özelliği takdir edilecektir.<br/>
<br/>
Gelelim Lisp ailesi dillerinin belki de ustaları için en çekici olan özelliğine: bu dillerde kaynak kod ve veri için aynı gösterim kullanılır. Bir diğer deyişle, Clojure'un da üyesi olduğu Lisp ailesi dilleri <i>eşgösterimli</i>dir (İng., homoiconic). Bu, kaynak kodun veri olarak ele alınmasına olanak tanıdığı gibi, çalışmakta olan bir programın kendisine sunulan girdiyi kod olarak ele alıp işlemesine olanak tanır. Örneğin—Java kaynak kodu içinden XML dosyalarındaki kurulum bilgilerinin okunmasında olduğu gibi—yazılım sevkiyatı (İng., software deployment) sırasında güvenlik, seyir defteri güncelleme gibi müşteriye özel olarak farklı icra edilmesi istenebilecek hizmetlere dair bilgileri kurulum dosyasına Clojure programı yazar gibi yazıp bir diğer Clojure programından okuyarak işinizi görebilirsiniz. Ya da, kullanıcının girdi olarak sağladığı veriyi kod olarak ele alıp işleyerek programınızı çalışırken değiştirme şansına sahip olabilirsiniz. <br/>
<br/>
Clojure'u özendirmek amacıyla öne süreceğimiz son sebep, eşgösterimlilik özelliği sayesinde diğer programlama dillerindekilerin çok ötesine geçen makro olanağıdır. Clojure makro işlemcisi, programın çalıştırılması öncesinde kaynak kodu o anda tanımlı bulunan makrolara uygun bir biçimde dönüştürür ve programcıların gördüğü ile yorumlayıcının işlediği kaynak kodun birbirlerinden farklılaşmasını sağlar. Böylece, programlama dilinde bulunmayan bir denetleme yapısının—mesela, koşut işlemeli bir döngü—dile kusursuz bir biçimde yamanarak pek çok programın daha kolay yazılması mümkün olacaktır. Benzer şekilde, yinelenen kod parçalarının şablon görevi gören makrolar yoluyla yazımı kolaylaştırılabilir. Programcının problem alanına yönelik yüksek düzey denetim yapıları ve kod şablonları yaratmasına olanak tanıyan bu özellik, Clojure'u (ve diğer Lisp ailesi dillerini) <i>alana özel dil</i> (İng., domain-specific language) gerektiren durumlarda öne çıkarmaktadır.<br/>
<br/>
<h3 id="kurulum">Kurulum</h3><br/>
Sisteminizde bulunmadığı takdirde <a href="http://clojure.org/">clojure.org/download</a> adresinden indirebileceğiniz Clojure ortamının kurulumu, indirilen arşiv dosyasının içindeki clojure-<i>x</i>.<i>y</i>.<i>z</i>.jar dosyasının sınıf yolu üzerine konulması kadar basit.<sup><a href="#dn2" id="ref2">2</a></sup> Yorumlayıcı (Clojure komut kabuğu) anılan paketin veya bu paketteki <code>main</code> sınıfının JSM'ye geçirilmesi ile çalıştırılabileceği gibi, evvelden kurulu bir Clojure ortamının bulunması durumunda <code>clojure</code> komutu ile de çalıştırılabilir.<sup><a href="#dn3" id="ref3">3</a></sup><br />
<pre class="brush:bash; gutter:false" name="yorumlayıcıyıBaşlatma">$ # java -cp ".:/arşiv/yolu/clojure-1.3.0.jar:$CLASSPATH" clojure.main
$ # java -cp ".:$CLASSPATH" -jar /arşiv/yolu/clojure-1.3.0.jar
$ clojure
Clojure 1.3.0
user=>
</pre>
Yorumlayıcının başlaması, <i>oku-değerlendir-bas döngüsü</i>nün (İng., read-evaluate-print loop [REPL]) başladığı anlamına gelir. Bundan sonra yapmamız gereken, Clojure dilinin sözdizimine uygun bir şeyler girip basılan sonuçları gözlemlemektir.<br />
<pre class="brush:java; gutter:false" name="clojureOturumu">user=> (+ 2 3) → 5
user=> (def sayı 5)
#'user/sayı
user=> (+ 2 3 sayı) → 10
user=> (* 2 (+ 3 4)) → 14
user=> (System/exit 0) ; Komut kabuğuna Ctrl-D ile de dönülebilir
$
</pre>
Oturumu inceleyerek bazı saptamalarda bulunalım. Öncelikle; Clojure kaynak kodu, yorumu ilk konumundaki simgeye göre değişen, ayraçlarla çevrili <i>form</i>lardan oluşur. İstenecek olursa, bir form bir diğerinin içine gömülebilir. Böyle bir durumda, en son açılan ayracın ilk kapatılması ve dıştaki formun bittiği noktada eşleştirilmemiş ayraç olmaması gerektiği unutulmamalıdır.<br/>
<br/>
Formlar, deyimler, makrolar ve özel formlar olmak üzere üçe ayrılır. Deyimler, ilk konumdaki simgenin geri kalanlara uygulandığı işlemler olarak ele alınırken, makrolar kodu dönüştüren işlemciler olarak görülebilir; özel formların işleyiş mantığı duruma göre değişir. Örneğin, <code>(+ 2 3)</code> toplamanın <code>2</code> ve <code>3</code> argümanlarına uygulandığı işleme karşılık gelen bir deyim iken, <code>(def sayı 5)</code> çalışma ortamını <code>5</code> değerine sahip <code>sayı</code> adlı tanımlayıcının varlığı ile güncelleyen bir özel formdur.<br/>
<br/>
Kod ve veri gösterimi için işleç-önde gösterimin şaşkınlığını üzerinizden attıktan sonra, bu seçimin aslında bazı artılarının olduğunu görebilirsiniz. Örneğin, bu gösterimin bir sonucu olarak ortaya çıkan tüm deyimlerin ayraç çifti ile çevrelenmesi zorunluluğu, öncelik sırası sorununu ortadan kaldırır; her bir kapatıcı ayraç eşleştiği ayracın başlattığı formun değerlendirilmesi gerektiğini bildirir. Ayrıca, kapatıcı ayraca kadar her şey argüman olarak yorumlandığı için, aynı işleç argümansız kullanılabileceği gibi, 3 veya 5 argümanlı da kullanılabilir. Mesela, <code>(*)</code> çarpmanın etkisiz elemanı olan 1 değerini döndürürken, <code>(* 1 2 3 4 5)</code> 5!, yani 120, değerini döndürür.<br/>
<br/>
Son olarak, <code>System/exit</code> Java platformundaki <code>System</code> sınıfının <code>exit</code> adlı metoduna atıfta bulunmaktadır. Yani, oturumun son satırında, bir Java programı olan yorumlayıcıya programdan 0 değeri döndürecek şekilde çıkmak istediğimizi söylüyoruz. Genelleştirecek olursak, <code>Sınıf</code> adlı bir Java platformu sınıfının <code>üye</code> adlı <code><b>static</b></code> bir özelliğini kullanmak istediğimizde yapmamız gereken, deyimimizin ilk konumuna <code>Sınıf/üye</code> koymaktan ibarettir.<br/>
<br/>
<h3 id="hesaplamaNesnesiOlarakFonksiyonlar">Fonksiyonlar Birer Hesaplama Nesnesidir</h3><br/>
Mesele fonksiyonel bir programlama dilini anlatmaya çalışmaksa, yapılması gereken ilk şeylerden biri, hiç kusku yok ki, birimsellik aracı olan fonksiyonun tanımı ve kullanımından bahsetmektir. Bahis esnasında fonksiyonların matematikteki adaşlarına benzediği ve diğer türden değerlerle birlikte birinci sınıf vatandaşlar olarak kullanılabileceği akıldan çıkarılmamalıdır. Gelin, ne kastedildiğini aşağıdaki tanımlardan görelim.<br/>
<pre class="brush:java; gutter:true" name="fonksiyonTanımı">user=> (def çarp (fn [a b] (* a b)))
#'user/çarp
user=> (def çarp2 #(* %1 %2))) ; çarp ile aynı
#'user/çarp2
user=> (def arg1 3)
#'user/arg1
user=> (def arg2 (read)) ; standart girdiden veri alıyor
17
#'user/arg2
user=> (çarp arg1 arg2) → 51
user=> ((fn [x y] (* x y) 4 5) → 20
user=> (#(* % %2) 6 7) →42
user=> (def çarp3 (partial çarp 3))
#'user/çarp3
user=> (çarp3 20) → 60
</pre>
<code>çarp</code> ve <code>çarp2</code>'nin <code>arg1</code> ve <code>arg2</code> ile aynı form kullanılarak tanımlandığına dikkatinizi çekerek başlayalım. Komutsal programlama dillerinden gelenlere başta aykırı gözükebilecek bu durum aslında çok doğal: ne de olsa, ister tamsayı olsun isterse fonksiyon, dört tanımlayıcı da birer değeri simgeliyor. Değerlerden ikisi sayma kavramının örnekleri iken, diğer ikisi hesaplama kavramının örneklerini veriyor; ikisi (<code>arg1</code> ve <code>arg2</code>) matematikte bağımsız değişken olarak ifade edilirken, diğerleri iki argümanlı bağımlı değişkenler olarak adlandırılıyor. Dolayısıyla, heseplamayı soyutlayan fonksiyon değerlerinin, 11. ve 12. satırlarda olduğu gibi, ad verilmeksizin kullanılması da bir o kadar doğal. Ne de olsa, 3 sabitini <code>arg1</code> değeri ile temsil etmeden de kendi başına kullanabiliyoruz, fonksiyon değerlerini de ad vermeden kullanabilmeliyiz.<br/>
<br/>
<code>çarp2</code>'nin tanımı yüksek dereceli fonksiyonlara fonksiyon geçirilmesini kolaylaştıran <code>#()</code> ile yapılıyor. Tek kullanımlık fonksiyonların simgesel bir değişkene atanmadan adsız kullanımını kolaylaştıran bu gösterimde, <code>%</code> ilk argümanı temsil ederken, <code>%</code><i>n</i> <i>n</i> nolu argümana karşılık gelir.<br/>
<br/>
Bazı yüksek dereceli fonksiyonlar ve kullanımlarına dair örnekler aşağıdaki tabloda verilmiştir. Yorumlayıcının oku-değerlendir-bas döngüsünün değerlendirme aşamasında uyguladığı iki temel fonksiyondan biri olan <code>apply</code>—diğeri <code>eval</code>'dir—ilk argümanındaki fonksiyonu daha sonraki argümanlara uygular. Bu işlem sırasında, son argümandaki kap parçalanarak eleman sayısı kadar farklı argüman olarak uygulanması istenen fonksiyona geçirilir. <code>comp</code> fonksiyonu, matematikten bildiğimiz bileşke fonksiyon yaratma işlecidir ve aldığı 0 ya da daha fazla sayıda fonksiyonun bileşkesi olan bir fonksiyon döndürür. <code>juxt</code> parametre listesindeki fonksiyonları ayrı ayrı bilahare geçirilecek argümanlara uygulayacak bir fonksiyon döndürerek işini görür. <code>memoize</code> fonksiyonu argümanındaki fonksiyonun daha hızlı çalışmasını sağlayabilmek adına daha önce yapılan hesaplamaları önbellekte tutup tekrar hesaplamanın önüne geçerek hızlandırmaya çalışan bir fonksiyon döndürür. Son olarak, <code>partial</code> fonksiyonu ilk argümanındaki fonksiyonun istediğinden az sayıda argümanla uygulanmasını olanaklı kılarak geri kalan argümanları bekleyen bir başka fonksiyon döndürür.<br/>
<br/>
<table align="center" border="1"><caption><i>Bazı yüksek dereceli fonksiyonlar</i></caption>
<thead><tr><th>Fonksiyon</th><th style="text-align:center">İşlev</th><th style="text-align:center">Kullanım</th></tr></thead>
<tbody>
<tr><td><code>apply</code></td><td>Fonksiyon uygulama</td><td><code>(apply</code> <i>fn</i> <i>arg</i> <code>&</code> <i>diğer-arglar</i><code>)</code> → <i>değer</i></td></tr>
<tr><td><code>comp</code></td><td>Fonksiyon bileştirme</td><td><code>(comp</code> <code>&</code> <i>fnler</i><code>)</code> → <i>fn</i></td></tr>
<tr><td><code>juxt</code></td><td>Fonksiyon uygulama</td><td><code>(juxt</code> <i>fn</i> <code>&</code> <i>diğer-fnler</i><code>)</code> → <i>fn</i></td></tr>
<tr><td><code>memoize</code></td><td>Önbellekli fonksiyon uygulama</td><td><code>(memoize</code> <i>fn</i><code>)</code> → <i>fn</i></td></tr>
<tr><td><code>partial</code></td><td>Kısmi fonksiyon uygulama</td><td><code>(partial</code> <i>fn</i> <i>arg</i> <code>&</code> <i>diğer-arglar</i><code>)</code> → <i>fn</i></td></tr>
</tbody></table><br/>
<pre class="brush:java; gutter:false" name="yüksekDereceliFonksiyonUygulama">user=> (apply * 2 3 (range 4 10)) → 362880
user=> (def fog (comp #(* % %) #(+ %1 %2)))
#'user/fog
user=> (fog 3 4) → 49
user=> ((juxt * + /) 6 4) → [24 10 3/2]
user=> (def önbellekli-f (memoize f))
#'user/önbellekli-f
user=> (önbellekli-f ...) → ...
</pre>
Seçtiğimiz örneğin basit olması nedeniyle yapılan tek deyim uzunluğundaki tanımlar sizi yanıltmasın; istenecek olursa, tıpkı kıvrımlı ayraçlarla (<code>{}</code>) belirtilen Java bloklarında olduğu gibi, <code>do</code> özel formunu kullanarak gövdeyi istediğimiz sayıda deyimden oluşacak biçimde uzatabiliriz. İçerdiği formları baştan sona ardışık bir şekilde işleyen bu form, sonucu olarak son işlenen formun döndürdüğü değeri döndürür. Mesela, <code>çarp2</code>'nin tanımının bir selamlama ile genişletilmesi istenecek olursa, bu <code>#(do (println "Selam") (* %1 %2))</code> tanımı ile yapılabilir. Benzer bir değişikliğin <code>fn</code> formunun kullanıldığı örnekte yapılmasına gerek yoktur; bu form gizli bir <code>do</code> ile çevrelenmiş gibi çalışır.<br/>
<br/>
<h3 id="temelVeriTürleri">Temel Veri Türleri</h3><br/>
Desteklenen temel veri türlerine geçmeden şu noktayı vurgulayalım: Clojure <i>dinamik türlü</i> bir programlama dilidir. Yani, Clojure'da tanımlayıcılar dil işlemcisine yardım etmek amacıyla türe dair üstbilgi ile nitelenmezler; bir tanımlayıcının türü kendisine yapılan ilkleme/atama sonrasında temsil etmeye başlayacağı değere göre belirlenir. Statik türlemeli dillerde derleyicinin denetimi sonrasında yazılmasına izin verilmeyecek bazı işe yarar programların yazılıp çalıştırılabileceği anlamına gelen bu özellik, kimilerinin düşündüğünün aksine, Clojure'un zayıf bir tür denetimine sahip olması demek değildir. Kaynak kodun işlenmesi esnasında tüm işlemlerin tür denetimini yapan Clojure, türleme hatasına sahip herhangi bir işlemin icra edilmesine izin vermez. Özetlemek gerekirse, Clojure hem dinamik türlüdür hem de kuvvetli türlemeye sahiptir.<sup><a href="#dn4" id="ref4">4</a></sup><br/>
<br/>
Aşağıdaki tablodan da görülebileceği gibi, Clojure Java programlama dili ile ortak temel türlerden değerleri Java platformundaki sarmalayıcı sınıfların nesneleri olarak temsil eder. Biçimlendirme yapılmadığı müddetçe, tamsayılar <code>Long</code>, gerçel sayılar ise <code>Double</code> türüyle temsil edilirler. İstenecek olursa, Java'daki ilkel türlerle aynı adlara sahip fonksiyonlardan biri kullanılarak biçimlendirme yapılabilir. Örneğin, <code>(byte 3)</code> normalde <code>Long</code> olarak ele alınacak 3'ü <code>Byte</code> türlü bir sabite çevirecektir.<br/>
<br/>
<table align="center" border="1"><caption><i>Clojure'daki temel türler</i></caption>
<thead><tr><th>Gösterim</th><th style="text-align:center">Tür</th><th style="text-align:center">Kavram</th></tr></thead>
<tbody>
<tr><td><code>\a</code></td><td><code>java.lang.Character</code></td><td>Karakter</td></tr>
<tr><td><code>"Katar"</code></td><td><code>java.lang.String</code></td><td>Karakter katarı</td></tr>
<tr><td><code>5</code></td><td><code>java.lang.Long</code></td><td rowspan="2">Tamsayı</td></tr>
<tr><td><code>5N</code><sup>*</sup></td><td><code>clojure.lang.BigInt</code></td></tr>
<tr><td><code>5.0</code></td><td><code>java.lang.Double</code></td><td rowspan="2">Gerçel sayı</td></tr>
<tr><td><code>5.0M</code></td><td><code>java.math.BigDecimal</code></td></tr>
<tr><td><code>22/7</code>, <code>(/ 22 7)</code></td><td><code>clojure.lang.Ratio</code></td><td>Kesirli sayı</td></tr>
<tr><td><code>(> 1 0)</code></td><td><code>java.lang.Boolean</code></td><td>Mantıksal değer</td></tr>
<tr><td><code>'simge</code></td><td><code>clojure.lang.Symbol</code></td><td>Tanımlayıcı adı, simgesel sabit, anahtar değeri</td></tr>
<tr><td><code>:anahtar</code></td><td><code>clojure.lang.Keyword</code></td><td>Simgesel sabit, anahtar değeri</td></tr>
<tr><td colspan="3"><sup>*</sup>: Clojure 1.3 ve sonrası.</td></tr>
</tbody></table><br/>
Kayan noktalı sayı aritmetiğinde ortaya çıkabilecek taşmalar Java'da olduğu gibi kullanılmakta olan sayı formatındaki özel değerler ile temsil edilirken<a href="http://ta-java.blogspot.com/2011/04/ilkel-turler-kayan-noktal-saylar.html#temsilBölgeleri">🔎</a>, tamsayılarda sınır aşımı programcıya <code>ArithmeticException</code> ayrıksı durumu ile bildirilir. Bunun yerine <code>Long</code>'dan <code>clojure.lang.BigInt</code>'e bir genişletme istenecek olursa, aritmetik işlem adı sonuna ' eklenmesi yeterli olacaktır. Buna göre, <code>(+ 1 Long/MAX_VALUE)</code> ayrıksı duruma neden olurken, <code>(+' 1 Long/MAX_VALUE)</code> <code>9223372036854775808N</code> değerini döndürecektir.<sup><a href="#dn5" id="ref5">5</a></sup><br/>
<br/>
İki <u>tamsayının</u> oranını temsil eden kesirler, pay ve paydanın sadeleştirilmesiyle elde edilen iki <code>java.math.BigInteger</code> (<code>clojure.lang.BigInt</code> değil!) nesnesi olarak tutulurlar. Her işlemin bu <code>BigInteger</code> nesnelerden yararlanarak yapılması ve nihai sonucun bunu takip eden sadeleştirmeden sonra elde edilmesi, kesirli sayılarla işlemlerin yavaş olmasına neden olur. Bundan dolayıdır ki, Clojure sadeleşme sonucu paydası 1 olan kesirleri tamsayıya dönüştürerek yoluna devam eder. Sizin de yüksek duyarlık gerektiren hesaplar dışında kesirleri kullanmaktan kaçınmanız yerinde olacaktır.<br/>
<br/>
Lisp ailesi dışındaki programlama dillerinde pek görünmeyen simgeler, tanımlayıcı adlarının türü olarak tanımlanabilir. Bundan ne kastettiğimizi <code>(def i 5)</code> özel formunu inceleyerek açmaya çalışalım. Bu tanımlama sonrasında <code>i</code> simgesi 5 değerine sahip bir değişken olarak ortama eklenir. Bir diğer deyişle, bu tanımla birlikte <code>i</code> 5'i simgelemeye başlar. Bu noktadan sonra, <code>i</code>'ye atıfta bulunulduğunda yorumlayıcı <code>i</code> yerine <code>i</code> simgesinin ortamdaki değeri olan 5'i koyacaktır. Örneğin, <code>(+ i 3)</code> <code>(+ 5 3)</code> haline dönüştürülecektir. Ancak, istenecek olursa simgenin önüne ' konulmak suretiyle bu dönüşümün önüne geçilebilir ve simgelenen değer yerine simgenin kendisi kullanılabilir. Böylesine bir kullanım, hünerini içerdiği tutanak değerlerinin anahtarlar eşitliklerini denetleyerek sergileyen eşlemlerde (ve diğer arama yapılarında) oldukça sık görülür. Çünkü, simgelerde eşitlik denetimi çok hızlı yapılabilir. Aynı özellikten anahtar türü için de bahsedebiliriz. : ile başlayan anahtarlar, başka bir şeyi göstermeyen simgeler olarak düşünülebilir. Yani, yapılacak bir tanım yardımıyla bir değeri gösteren simgelerin aksine, anahtarlar herhangi bir değeri simgelemez. Aynı zamanda, simgelerin program metnindeki her geçişi diğer geçişlerinden farklı iken, herhangi bir anahtarın tüm geçişleri aynıdır.<br/>
<br/>
<h3 id="veriKapları">Veri Kapları</h3><br/>
Clojure tarafından sağlanan veri kapları doğrusal kaplar (liste, vektör<a href="http://ta-java.blogspot.com/2011/09/degisken-uzunluklu-diziler.html">🔎</a>), kümeler, eşlemler<a href="http://ta-java.blogspot.com/2011/12/hzl-arama-zamanl-kaplar-1.html">🔎</a> ve karakter katarları<a href="http://ta-java.blogspot.com/2011/09/karakter-katar-snflar.html">🔎</a> olmak üzere dört kategoride incelenebilir. Kategoriler veri kaplarını eşitlik temelinde denklik sınıflarına ayırmakta da kullanılır. Eşit addedilmeleri için kapların aynı türden olması gerekmez; dolaşıldıklarında karşılıklı elemanlarının eşit olduğu görülen aynı kategorideki tüm kaplar birbirine eşittir.<br/>
<br/>
Clojure'da desteklenen veri kapları, aşağıdaki tablodan da görülebileceği gibi, yapıcı görevi gören fonksiyonların yanısıra özel sözdizimi yardımıyla da yaratılabilir. Dikkat edilirse, küme ve eşlemler için, Java Veri Kapları Çerçevesi'ndekileri anımsatan, farklı yapıcılar sağlandığı görülecektir. Bu, aynı zamanda söz konusu kap için <code>(class </code><i>kap</i><code>)</code> deyimiyle keşfedilebilecek farklı bir gerçekleştirim seçildiği anlamına gelmektedir.<br/>
<br/>
<table align="center" border="1"><caption><i>Clojure destekli kaplar</i></caption>
<thead><tr><th>Cins</th><th style="text-align:center">Yapıcı</th><th style="text-align:center">Özel gösterim</th></tr></thead>
<tbody>
<tr><td>Liste</td><td><code>(list 1 2)</code></td><td><code>'(1 2)</code></td></tr>
<tr><td>Vektör</td><td><code>(vector 1 2)</code></td><td><code>[1 2]</code></td></tr>
<tr><td rowspan="3">Eşlem</td><td></td><td><code>{:Adana 1 :İzmir 35}</code></td></tr>
<tr><td><code>(hash-map :Adana 1 :İzmir 35)</code></td><td></td></tr>
<tr><td><code>(sorted-map :Adana 1 :İzmir 35)</code></td><td></td></tr>
<tr><td rowspan="2">Küme</td><td><code>(hash-set :insan :şempanze)</code></td><td><code>#{:insan :şempanze}</code></td></tr>
<tr><td><code>(sorted-set :insan :şempanze)</code></td><td></td></tr>
<tr><td>Kar. katarı</td><td><code>(str \a \b \c)</code></td><td><code>"abc"</code></td></tr>
</tbody></table><br/>
Clojure'dakilere ek olarak, Java Veri Kapları Çerçevesi'nce sağlanan eşdeğer kaplardan da yararlanılabilir. Aynı kategoride addedilen bir Clojure kabı ile Java eşdeğerinin hemen hemen aynı olduğu söylenebilir. Eşit içeriğe sahip eşdeğer kaplar birbirine eşittir; Clojure kaplarına uygulanan fonksiyonlar Java eşdeğerlerine de uygulanabildiği gibi, Clojure kapları Java nesnesi olarak da kullanılabilir. İlişkin örnekler aşağıda verilmiştir.
<pre class="brush:java; gutter:false" name="kaplarınEşitliği">user=> (import '(java.util ArrayList Vector))
java.util.Vector
user=> (def java-ls (ArrayList.))
#'user/java-ls
user=> (.add java-ls 1) → true
user=> (doto java-ls (.add 2) (.add 3)) → #<ArrayList [1, 2, 3]>
user=> (def java-vek (Vector. java-ls))
#'user/java-vek
user=> (= java-vek '(1 2 3) [1 2 3] java-ls) → true
user=> (= #{1 2} '(1 2)) → false
user=> (map #(* % %) java-vek) → (1 4 9)
user=> (reduce (fn [a b] (if (> a b) a b)) '(80 70 85)) → 85
user=> (filter odd? [1 2 3 4 5]) → (1 3 5)
user=> (remove odd? #{1 2 3 4 5})) → (2 4)
user=> (.size [1 2 3 4]) → 4
user=> (apply str (map #(Character/toUpperCase %) "Abc")) → "ABC"
</pre>
Kodu incelerken şu noktaların bilinmesi yardımcı olacaktır: i) <code>import</code> bir veya daha fazla sayıda sınıf dosyasını içselleştirip çalışma ortamında görünür hale getirir, ii) <code>.</code> ile sonlanan sınıf adları Java platformundaki bir sınıfın yapıcılarından birini çağırır, iii) önüne <code>.</code> eklenmiş fonksiyon adları ilk argümanda belirtilen alıcıya ileti göndermekte kullanılır, ve iv) <code>doto</code> makrosu ikinci ve sonrasındaki argümanlarda belirtilen iletileri ilk argümanda belirtilen alıcıya yönlendirir.<br/>
<br/>
<table align="center" border="1"><caption><i>Kaplar üzerinde çalışan bazı yüksek dereceli fonksiyonlar</i></caption>
<thead><tr><th>Fonksiyon</th><th style="text-align:center">İşlev</th><th style="text-align:center">Kullanım</th></tr></thead>
<tbody>
<tr><td><code>map</code></td><td>Kap dönüşümü</td><td><code>(map</code> <i>fn</i> <i>k</i> & diğer-kaplar<code>)</code> → <i>kap</i></td></tr>
<tr><td><code>reduce</code></td><td>İndirgeme, özetleme</td><td><code>(reduce</code> <i>f</i> <i>k</i><code>)</code>, <code>(reduce</code> <i>f</i> <i>ilk-değer</i> <i>k</i><code>)</code> → <i>değer</i></td></tr>
<tr><td><code>filter</code></td><td>Süzme</td><td><code>(filter</code> <i>fn</i> <i>k</i><code>)</code> → <i>kap</i></td></tr>
<tr><td><code>remove</code></td><td>Silerek süzme</td><td><code>(remove</code> <i>fn</i> <i>k</i><code>)</code> → <i>kap</i></td></tr>
</tbody></table><br/>
Clojure ve Java veri kapları arasındaki şu temel farklılık unutulmamalıdır: Java'da kap içeriği uygun iletiler yardımıyla değiştirilebilirken, Clojure'daki tüm kaplar değişmez içeriklidir. Bir başka deyişle, Clojure'da kaplar yaratılmaları sonrasında değiştirilemezler. Değişme etkisinin yaratılması için içerik güncelleme işlemleri yapan fonksiyonların dönüş değerlerinin kullanılması gerekir.
<pre class="brush:java; gutter:false" name="sabitİçerikliKaplar">user=> (def çete '("Veli" "Selami"))
#'user/çete
user=> (def yeni-çete (conj çete "Ali"))
#'user/yeni-çete
user=> çete → ("Veli" "Selami")
</pre>
Düşük maliyetli bir biçimde gerçekleştirilebilen bu stratejinin (bkz. <a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.21.6279">1</a>, <a href="https://github.com/krukow/clj-ds">2</a>) temelinde programcıyı değişken içerikli kaplardan uzak tutmak suretiyle çok izlekli program yazma bağlamında daha kolay sınanabilir kod üretmeye sevketmektir. <code>String</code> sınıfının<a href="http://ta-java.blogspot.com/2011/09/karakter-katar-snflar.html">🔎</a> sabit içerikli olmasının temelinde de yatan bu yaklaşım, değişmeyecek bir kabın birden çok izlekten sakıncasız bir biçimde kullanılabileceği gerçeğinden yararlanır. Aslında, Java Veri Kapları Çerçevesi de dikkatle bakan gözlere aynı mesajı vermektedir: arayüzlerde yer alan tüm içerik güncelleyici iletilerin başlığı <code>UnsupportedOperationException</code> ayrıksı durumunu içerir. Clojure kapları buna dayanarak, Clojure'a özel fonksiyonlara ek olarak [Java kodu ile birlikte çalışmak adına] Veri Kapları Çerçevesi'nin ilişkin arayüzünü gerçekleştirdiklerinde söz konusu iletileri desteklemediklerini bildiren <code>UnsupportedOperationException</code> ayrıksı durumunu fırlatacak şekilde gerçekleştirir.
<pre class="brush:java; gutter:false" name="gerçekleştirimAyrıntısı">user=> (def vek [1 2])
#'user/vek
user=> (.size vek) → 2
user=> (.toString vek) → "1 2"
user=> (.add vek 3) → java.lang.UnsupportedOperationException (NO_SOURCE_FILE:0)
</pre>
<code>map</code>, <code>reduce</code> gibi pek çok fonksiyonun ayırt etmeksizin tüm kaplara uygulanabilmesi, bu tür fonksiyonların diğer Lisp ailesi dillerinde olduğu gibi liste veya bir diğer türden kap almak yerine <code>clojure.lang.ISeq</code> arayüzünü gerçekleştiren bir kap alıyor olmasıyla mümkün olur. Buna göre, Clojure listelerinin gerçekleştirimini sağlayan Java sınıfının aşağıdaki gibi olduğunu söyleyebiliriz.
<pre class="brush:java; gutter:false" name="ISeq">package clojure.lang;
public class PersistentList extends Obj implements ISeq, ... { ... }
</pre>
<code>ISeq</code> arayüzü, eleman sayısını döndürüren <code>count</code> ve ilk argümanındaki kaba ikinci argümanındaki değerin eklenmiş halini döndüren <code>conj</code>'un yanısıra, yegâne argümanındaki kabın dolaşılmasına yarayan bir liste döndüren <code>seq</code> iletisini içerir.<br/>
<br/>
Son olarak değineceğimiz nokta, vektör veya eşlem gösteren bir simgenin fonksiyon adı konumunda kullanılabilecek olmasıdır. Aşağıdaki kod parçasında olduğu gibi, bir vektöre indis, bir eşleme ise anahtar değeri verilecek olursa ilişkin kap içindeki o indis veya anahtar karşılık gelen değer döndürülür.
<pre class="brush:java; gutter:false" name="fonksiyonOlarakKapKullanımı">user=> ([1 2 3 4] 1) → 2
user=> (plaka-nolar :İzmir) → 35
</pre>
<br/>
<h3 id="fonksiyonTanımı">Fonksiyon Tanımı, Yeniden</h3><br/>
Tanımının uzamasıyla birlikte fonksiyon gövdesinin anlaşılması da zorlaşacaktır. Bu etkiyi azaltmak amacıyla, gerçekleştirimimize yorum ve üstbilgi eklemeyi düşünebiliriz. Böyle bir durumda, makro işlemcisi tarafından <code>def</code> ve <code>fn</code> formlarına dönüştürülecek olan <code>defn</code> makrosunu kullanmamız daha yerinde olacaktır.<br/>
<br/>
<div style="color: #444444; text-align: center;">Selamlar.clj</div>
<pre class="brush:java; gutter:false" name="defn-vararg">
(import '(java.util Calendar GregorianCalendar))
(defn selamlar
"Argümanında geçirilen kişilere selam verir. Bunu yaparken aşırı yüklemeden yararlanır."
{:yazar "Tevfik AKTUĞLU" :tarih (GregorianCalendar. 2012 Calendar/JANUARY 7)}
([tanıdık] (println "Selam," tanıdık))
([x y & diğerleri] (println "Selam millet!"))
([] (println "Selam, kim olursan ol!")))
...
</pre>
Yukarıdaki fonksiyon tanımı, üç farklı parametre listesine göre üç gövde sağlamaktadır. Bir bakıma, hepsinin adı <code>selamlar</code> olan üç ayrı fonksiyon tanımlanmaktadır. Bu fonksiyonlardan ilki tek argümanlı iken, ikincisi 2+ argüman beklemektedir; son gövde tanımı ise argümansız kullanım durumunda işlenecektir. Değişken sayıda argüman alan seçenekte, ikinci argüman sonrasında geçirilecek tüm değerlerin bir listeye konulup <code>&</code> imini takip eden parametreye (<code>diğerleri</code>) karşılık gelen argümanda geçirileceği söylenmektedir.<br/>
<br/>
<code>defn</code> makrosu fonksiyon tanımında kullanılan diğer formların sunmadığı iki olanak sunmaktadır. Bunlardan ilki, tanımlanmakta olan fonksiyonun adını takiben sağlanan ve daha sonra <code>doc</code> fonksiyonuyla sorgulanabilecek dokümantasyon yorumudur. İkinci olanak ise, programcının gereksinimlerine göre seçeceği üstbilgilerin fonksiyon tanımına iliştirilmesidir. Örneğimizde, dokümantasyon yorumları sonrasındaki üstbilgi eşlemi vasıtasıyla kodu yazan kişi ve en son güncelleme tarihi bilgilerini <code>selamlar</code> simgesine iliştiriyoruz.
<pre class="brush:java; gutter:false" name="dosyaYükleme">;;Sınıf yolu üzerinde bulunan Selamlar.clj dosyasındaki formları yükler.
user=> (load "Selamlar") → nil
user=> (selamlar) → nil
Selam, kim olursan ol!
user=> (selamlar "Tevfik") → nil
Selam, Tevfik
user=> (selamlar "Ali" "Veli" "Selami") → nil
Selam millet!
user=> (doc selamlar) → nil
------------------------
user/selamlar
([tanıdık] [x y & diğerleri] [])
Argümanında geçirilen kişilere selam verir. Bunu yaparken aşırı yüklemeden yararlanır.
user=> (meta (var selamlar))
{:ns #<Namespace user>, :name selamlar, :file "Selamlar.clj",..., :tarih ...}
</pre>
<code>selamlar</code>'a alternatif olarak sunulan ve 0+ argüman geçirilerek çağrılabilecek aşağıdaki <code>selamlar2</code> fonksiyonu, yeni bazı şeyler sunmakta. Bunlardan ilki, parametre listesi sonrasındaki önkoşul tanımıdır. Bilenlerin <a href="http://eiffel.com">Eiffel programlama dili</a>nden anımsayacağı <i>sözleşme temelli tasarım</i>ın (İng., Design by Contract) bir parçası olan önkoşul (İng., precondition), fonksiyonun her çağrılışı öncesinde denetlenir. Sonucun olumsuz olması, sağlıklı çalışma için karşılanması zorunlu görülen bir koşulun oluşmadığı anlamına gelir ve bu bilgi fonksiyon çağrılmaksızın programın bitirilmesini takiben uygun bir mesajla kullanıcıya bildirilir. Mesela, <code>selamlar2</code> örneğinde selam verilecek kişileri temsil eden argümanların toplandığı listenin 3 veya daha az sayıda elemanı olması bir önkoşul olarak tanımlanıyor. Tamamlayıcı bir denetim, fonksiyon çağrısının sona erdiği noktada karşılanması zorunlu görülen durumların belirtilmesini mümkün kılan sonkoşulları listeleyen <code>:post</code> ile yapılabilir.
<pre class="brush:java; gutter:false" name="defn-DesignByContract">
...
(defn selamlar2
"cond formundan yararlanarak argümanında geçirilen kişilere selam verir. Bu arada, önkoşul denetimiyle 4 ve daha fazla sayıda kişiden oluşan gruplara selam vermeyerek asayişi korur."
{:yazar "Tevfik AKTUĞLU" :tarih (GregorianCalendar. 2012 Calendar/JANUARY 7)}
[& ahali] {:pre [(< (count ahali) 4)]}
(let [nüfus (count ahali)]
(cond
(> nüfus 1) (println "Selam millet!")
(= nüfus 1) (println "Selam," (first ahali))
:aksi-takdirde (println "Selam, kim olursan ol!"))))
</pre>
Fonksiyon tanımında dikkat çekilmesi gereken bir diğer nokta, blok değişkenlerin tanımını olanaklı kılan <code>let</code> özel formudur. Kendisine sağlanan vektörün tek sayılı sıralardaki elemanlarını değişken adı olarak kabul eden <code>let</code>, değişkeni takip eden değeri ilkleme amacıyla kullanır. Buna göre örneğimizdeki <code>let</code> formu, <code>nüfus</code> adına sahip ve argümanda geçirilen listenin eleman sayısıyla ilklenmiş bir yerel değişken tanımlamaktadır. Tanımlanan blok içindeki <code>cond</code> makrosu ise, C-temelli dillerdeki <code><b>switch</b></code>-<code><b>case</b></code> yapısına benzer bir görev görür: koşulları tepeden aşağıya sıralı bir biçimde sınar ve doğru olduğunu gördüğü ilk kolun formunu işler. Tüm durumların kapsanması için son kola her zaman doğru olacağı bilinen bir koşul konmalıdır. <code><b>false</b></code> ve <code><b>nil</b></code> dışındaki tüm değerler doğru kabul edildiği için, örneğimizde bu görevi <code>:aksi-takdirde</code> anahtarı görmektedir.
<pre class="brush:java; gutter:false" name="find-doc">
user=> (selamlar "Ali" "Veli" "Selami" "Nuri") → nil
java.lang.AssertionError: Assert failed (< (count millet) 4) (NO_SOURCE_FILE:0)
user=> (find-doc "selamlar") → nil
------------------------
user/selamlar
([tanıdık] [x y & diğerleri] [])
Argümanında geçirilen kişilere selam verir. Bunu yaparken aşırı yüklemeden yararlanır.
-------------------------
user/selamlar2
([& ahali])
cond formundan yararlanarak argümanında geçirilen kişilere selam verir. Bu arada, önkoşul denetimiyle 4 ve daha fazla sayıda kişiden oluşan gruplara selam vermeyerek asayişi korur.
</pre>
<br/>
<h3 id="özyineleme">Özyineleme</h3><br/>
Yazımızın son kısmında fonksiyonel programlama dillerinin birincil yineleme yöntemi olan özyinelemeye değineceğiz. Bu yapmamızın nedeni, Clojure'un komutsal dillerden gelenlere tanıdık gelecek türden döngüleri desteklememesi değil. Elbette ki, dilin kendisinde olmasa bile Clojure'daki gibi bir makro olanağınız varsa, bildik tüm döngüleri ve fazlasını özyineleme kavramını kullanarak tanımlayabilrsiniz. Derdimiz, fonksiyonel programlamadan optimize edilen özyinelemeli çağrılara alışmış olanlara ufak bir uyarıda bulunmak. Çok sallanmadan faktöryel fonksiyonunu gerçekleştiren aşağıdaki tanımla başlayalım.
<pre class="brush:java; gutter:false" name="özyineleme-1">
(defn ! [n]
{:pre (pos? (inc n))}
(if (<= n 1)
1
(* n (! (dec n)))))
</pre>
Argümanının eksi olmaması önkoşuluna sahip gerçekleştirimimiz, özyinelemenin bitiş koşulu olarak argümanının 1 veya 0 olmasını seçmiş ve bu durumda 1 döndürüyor. Aksi takdirde, tümevarım adımında ifade edildiği gibi, problem kendi türünden daha basit bir probleme indirgenerek çözüm sağlanıyor: argüman ile argümanın bir eksiğinin faktöryeli çarpılıp sonuç olarak döndürülüyor. Ancak, bildirimsel (İng., declarative) olması nedeniyle doğruluğu kolayca kanıtlanabilecek gerçekleştirimimizin—ne de olsa, çözümü programlama dünyasına özel yapıları kullanarak sağlamaktansa problem tanımını birebir çevirerek sağlıyoruz—sonsuz döngülerle boğuşmuş olanlara pek de yabancı gelmeyecek bir sorunu var: bitiş koşuluna uzak bir ilk durumun verilmesi durumunda, özyinelemeli çağrıların sayısı artacak ve argümanların yerleştirildiği çağrı yığıtı dolarak hesaplamamız sonuç döndürmeden <code>StackOverflowError</code> ile sona erecektir.
<pre class="brush:java; gutter:false" name="faktöryeliSına-1">
user=> (! 5) → 120
user=> (! 100000) → java.lang.StackOverflowError (NO_SOURCE_FILE:0)
</pre>
Ortaya çıkan sorun iki şekilde çözülebilir: i) ürettiği ara sonuçları biriktirici argümanlar yoluyla bir sonraki çağrıya aktaran özyinelemeli bir çözümle, ii) özyineleme yerine döngü kullanarak. Gelin birinci şıkkın gerçekleştirimi olan aşağıdaki koda bir göz atalım.
<pre class="brush:java; gutter:false" name="özyineleme-2">
(defn !2 [n]
{:pre (pos? (inc n))}
(letfn [
(!-iç [n biriktirici]
(if (<= n 1)
biriktirici
(!-iç (dec n) (* biriktirici n))))]
(!-iç n 1)))
</pre>
Örneğimizde, kullanıcının <code>!2</code>'ye sağladığı değer yerel fonksiyona o ana kadarki hesaplamanın özeti olarak niteleyebileceğimiz biriktiricinin ilk değeri ile birlikte geçiriliyor. Kullanıcının görmediği <code>!-iç</code>'i incelediğimizde, tümevarım adımının özyinelemeli çağrı sırasında ikinci argümanı o anki ilk argümanla çarparak güncellediğini ve bitiş koşulunun oluştuğu noktada ise 1 yerine biriktirilmiş değeri döndürdüğünü görüyoruz. Bu dönüşümü yapmaktaki amacımız, özyinelemeli çağrıyı fonksiyon içinde en son yapılan iş haline getirmek ve böylece fonksiyonel programlama dillerinde sıklıkla uygulandığını bildiğimiz kuyruk çağrısı eniyilemesinden yararlanmaktır. Gelin görün ki, kazın ayağı öyle değil! JSM üzerine inşa edilmiş olan Clojure, JSM'nin kuyruk çağrısı eniyileme desteği vermemesi nedeniyle bizi utandırıyor.
<pre class="brush:java; gutter:false" name="faktöryeliSına-2">
user=> (!2 100000) → java.lang.StackOverflowError (NO_SOURCE_FILE:0)
</pre>
Derdimizin çaresi, Clojure'a işi JSM'ye bırakmadan kendi yapabildiği kadarıyla yapmasını söylemek. Bu ise, aşağıdaki gibi özyinelemeli çağrıların olduğu yerlerde, fonksiyon adı yerine <code>recur</code> yazmakla mümkündür. <code>recur</code> özel formu, kuyruk konumunda yapılan özyinelemeli çağrıları arka planda goto benzeri birer sıçrama komutuna çevirir. [Sadece özyinelemeli çağrılar eniyilenir; genel kuyruk çağrısı eniyilemesi yapılmaz!] Bu, çağrı yığıtında ilk çağrıdaki argümanların işgal ettiği yerlerin yeniden kullanılacağı ve dolayısıyla sabit bellekle işimizin halledileceği anlamına gelir; ayrıca, fonksiyon çağrıları yerini daha ucuz olan sıçrama komutlarına bırakacağı için çalışma hızı da olumlu etkilenecektir. Bunu <code>time</code> makrosu yardımıyla görebilirsiniz.
<pre class="brush:java; gutter:false" name="özyineleme-3">
(defn !3 [n]
{:pre (pos? (inc n))}
(letfn [
(!-iç [n biriktirici]
(if (<= n 1)
biriktirici
(recur (dec n) (* biriktirici n))))]
(!-iç n 1)))
</pre>
<pre class="brush:java; gutter:false" name="faktöryeliSına-2">
user=> (time (!3 2000)) → "Elapsed time: 20.266474 msecs" 33162...
user=> (time (!2 2000)) → "Elapsed time: 24.969519 msecs" 33162...
</pre>
Bir diğer çözüm yöntemi <code>loop</code> özel formundan yararlanır. Döngü tutkunu olanları özyinelemeye alıştırmak için önerilebilecek bu form, eniyilenmiş özyinelemeli kuyruk çağrısı çözümüne yakın, hatta daha iyi bir çalışma hızı sağlar. Hızı daha da iyileştirmek isteyenlere önbellekli fonksiyon uygulaması olanağını sağlayan <code>memoize</code> fonksiyonu önerilir. Ancak; önceden yapılmış bazı hesaplamaları tekrar hesaplamaktansa kaydettiği sonucu kullanarak işini gören bu yüksek dereceli fonksiyonun argümanları dışında çağrı ortamına bağımlılığı bulunan fonksiyonlarla çalışamayacağı akılda tutulmalıdır.
<pre class="brush:java; gutter:false" name="özyineleme-4">
(defn !4 [n]
{:pre (pos? (inc n))}
(loop [i n, biriktirici 1]
(if (<= i 1)
biriktirici
(recur (dec i) (* biriktirici i)))))
</pre>
<pre class="brush:java; gutter:false" name="faktöryeliSına-3">
user=> (def !4-hızlı (memoize !4))
#'user/!4-hızlı
user=> (time (!4-hızlı 1900)) → "Elapsed time: 27.253187 msecs" 33162...
user=> (time (!4-hızlı 2000)) → "Elapsed time: 21.0582 msecs" 33162...
</pre>
Tüm diğer zaman ölçümlerinde olduğu gibi, hıza dönük yorumların beklenen ortalama çalışma hızı düşünülerek yapıldığı unutulmamalıdır; döndürülen değerlerin sisteme ve yüke göre değişmesi, önbellekli uygulama örneklerinde önbellek yönetiminden kaynaklanan beklenmedik farkların oluşması sizi şaşırtmamalıdır.<br/>
<br/>
</div>
<div id="diğerYazılar" style="text-align:right">
<a href="http://ta-java.blogspot.com/2011/06/snama-arac-olarak-beanshell.html">BeanShell</a><br/>
<a href="http://ta-java.blogspot.com/2011/05/java-platformunun-dikkate-deger-bir.html">Scala</a>
</div>
<hr/>
<div id="dipnotlar" style="text-align:justify">
<ol>
<li id="dn1">Java SE 8 ile birlikte, kod örtülerinin bu eksikliği büyük ölçüde gidereceğini söyleyebiliriz. Dolayısıyla, 2013'ün ikinci yarısında yargımızın Java bölümü geçerliğini yitirmiş olacak. <a href="#ref1">↑</a></li>
<li id="dn2">Clojure programlama dilinin resmi sitesine gittiğinizde, <a href="http://clojure.blip.tv/">Screencast</a> etiketli bir bağlantı gözünüze çarpabilir. Gezegenimizin mutlu insanlarını Clojure diliyle ilgili bir video serisine götüren bu bağlantı, Türkiye'de yaşayanlar için—henüz farkına varılmayan üniversite ve benzeri ortamlardaki mutlu azınlık dışında—alıştık İnternet sansürlerinden birini simgeliyor. [Son kontrol tarihi: 29-Aralık-2011] İşin hazin tarafı, bu durum Wikipedia'nın <a href="http://en.wikipedia.org/wiki/Blip.tv">blip.tv sayfası</a>nda, ülkemizin Çin'le birlikte—listede üçüncü bir ülke yok!—anılması yoluyla reklam ediliyor. <a href="#ref2">↑</a></li>
<li id="dn3">İllaki bol pencereli bir geliştirme ortamı istiyorsanız, size <a href="http://dev.clojure.org/display/doc/Clojure+Tools">bu sayfaya</a> bakmanızı öneririm. Ayrıca, çalışma ortamınızın özelliklerine bağlı olarak, örneklerdeki Türkçe'ye özel harflerde sıkıntı yaşayabilirsiniz. Bunun olası nedeni, kullanmakta olduğunuz Java arşivinin Türkçe'deki bu karakterleri dışlayacak bir kodlama ile derlenip oluşturulmasıdır. Tavsiyem, <code>clojure</code> komutuna öncelik vermeniz; bu da çözüm sağlamıyorsa, Clojure ortamını UTF-8 kodlaması ile derlemeniz. <a href="#ref3">↑</a></li>
<li id="dn4">Aslına bakılırsa, Clojure tanımlayıcılar hakkında tür bilgilerinin sağlanması için isteğe bağlı olarak kullanılabilecek bir düzenek sağlıyor. Ancak, yazımızda bu olanağa değinmeyeceğiz. <a href="#ref4">↑</a></li>
<li id="dn5">Clojure 1.3 öncesinde durum farklıydı. Öncelikle, nitelenmemiş bir sabitin türü <code>Long</code> değil, <code>Integer</code> idi. Ayrıca, taşma ayrıksı duruma sebep olmaktansa söz konusu değeri <code>Integer</code> ise <code>Long</code>'a, <code>Long</code> ise <code>java.math.BigInteger</code>—Clojure 1.3'te olduğu gibi <code>clojure.lang.BigInt</code> değil!—türüne terfi ettirerek sessizce geçiştiriliyordu. <a href="#ref5">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-52201031688058305772011-12-02T10:45:00.001+02:002012-01-31T22:52:09.439+02:00Hızlı Arama Zamanlı Kaplar-2<div id="anaMetin" style="text-align: justify;">
Kaldığımız yerden devam ediyoruz. Dizimizin ikinci ve son yazısında, ilk yazıyı okuyanlara tanıdık gelecek aşağıdaki kısmi sıradüzenin eşlemler için dengeli ağaç temelli gerçekleştirim sağlayan kısmına değineceğiz. Buraya ilk yazıyı okumadan yolunuz düşüp de kavramsal olarak kendinizi hazır hissetmiyorsanız, size tavsiyem ilk yazıya bir göz atıp daha sonra buraya dönmeniz.<a alt="Hızlı arama zamanlı kaplar (Eşlemler)-java.util.Map, java.util.AbstractMap, java.util.HashMap, java.util.Hashtable, java.util.LinkedHashMap" href="http://ta-java.blogspot.com/2011/12/hzl-arama-zamanl-kaplar-1.html">🔎</a>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXiMJMUxigHVC9-EAYdPV6gj9fvv1Uu4gwIuPmY8aH2Fe_6P9hbevHalBPdQeZCTGzO9jxVZ8s85FG3vq_IyWQYdoWF3nVlMwiithYuLHK6r8OCdVmCQiLqNNMCOvZ5i9tXDYuA3Y-QBzl/s400/ListeYap%2525C4%2525B1lar%2525C4%2525B1S%2525C4%2525B1rad%2525C3%2525BCzeni.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="209" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXiMJMUxigHVC9-EAYdPV6gj9fvv1Uu4gwIuPmY8aH2Fe_6P9hbevHalBPdQeZCTGzO9jxVZ8s85FG3vq_IyWQYdoWF3nVlMwiithYuLHK6r8OCdVmCQiLqNNMCOvZ5i9tXDYuA3Y-QBzl/s400/ListeYap%2525C4%2525B1lar%2525C4%2525B1S%2525C4%2525B1rad%2525C3%2525BCzeni.png" /></a></div>
<ul>
<li>İlk yazı<a alt="Hızlı arama zamanlı kaplar (Eşlemler)-java.util.Map, java.util.AbstractMap, java.util.HashMap, java.util.Hashtable, java.util.LinkedHashMap" href="http://ta-java.blogspot.com/2011/12/hzl-arama-zamanl-kaplar-1.html">🔎</a></li>
<li><a href="#ağaçlıEşlemler"><code>TreeMap</code> sınıfı</a></li>
<li><a href="#SortedMap"><code>SortedMap</code> arayüzü</a></li>
<li><a href="#NavigableMap"><code>NavigableMap</code> arayüzü</a></li>
<li><a href="#uzmanEşlemler">Uzmanlaşmış eşlemler</a></li>
</ul><br/>
<h3 id="ağaçlıEşlemler"><code>TreeMap</code> Sınıfı</h3><br/>
Eşlem yapısı, altyapıda anahtar bilgisine göre oluşturulan bir dengeli ağaç kullanılarak da gerçekleştirilebilir. Bu yol izlenecek olursa çözüm, eşleme gönderilen iletilerin dengeli ağaca yönlendirilmesiyle sağlanır. Bu tür ağaçların ekleme, silme ve sorgulama işlemlerini O(lg<i>n</i>) zamanda yapması nedeniyle, anılan işlemlerin eşlemde de O(lg<i>n</i>) performanslı gerçekleştirilmesi mümkün olacaktır. Bu noktadan hareketle, standart Java kitaplığındaki <code>TreeMap</code> sınıfı eşlem yapısını <i>kırmızı-siyah ağaç</i>lardan yararlanarak gerçekleştirmiştir.<sup><a href="#dn1" id="ref1">1</a></sup><br/>
<br/>
Altyapıda dengeli ağaç kullanılıyor olması, <code>TreeMap</code> sınıfının pek çok ekstra iletiyi düşük maliyetli bir gerçekleştirimle desteklemesini olanaklı kılar. Bunun başlıca nedeni, kök-ortada (İng., inorder) dolaşmanın ağaç içindeki bilgileri anahtara göre sıralı vermesidir. Bu sayede eşlem içeriği anahtara göre sıralı bir şekilde oluşturulabilir; ayrıca sıralama aşamasına gerek yoktur. Bunun doğal bir sonucu olarak, <code>TreeMap</code> sınıfı <code>SortedMap</code> ve <code>NavigableMap</code> arayüzlerinde listelenen eşlem içeriğinin uç değerleri ve dilimlerine dair iletileri de destekler. Ayrıca, <code>Map</code><a alt="Hızlı arama zamanlı kaplar (Eşlemler)-java.util.Map" href="http://ta-java.blogspot.com/2011/12/hzl-arama-zamanl-kaplar-1.html#MapAray%C3%BCz%C3%BCVeAbstractMapS%C4%B1n%C4%B1f%C4%B1">🔎</a> arayüzünde tanımlanmış olan <code>entrySet</code>, <code>keySet</code> ve <code>values</code> iletilerinin gerçekleştirimleri de sonuçlarını anahtara göre sıralı değerlerden oluşan birer kap içinde döndürürler.<br/>
<br/>
Kök-ortada dolaşmanın düğümleri anahtara göre sıralı üretebilmesi, anahtar-tutanak çiftlerinin altyapıdaki dengeli ağacın anahtar değerine göre belirlenen uygun düğümlerine eklenmeleri ile mümkün olur. Bu ise, eklenmekte olan anahtar değerinin eşlemde var olanların bir kısmı ile karşılaştırılmasıyla olanaklıdır. Benzer şekilde, silme ve sorgulama işlemlerinde de söz konusu anahtara karşılık gelen düğümün karşılaştırma işlemleri yardımıyla saptanıp işlemin tamamlanması mümkün olacaktır. Bütün bunlar, anahtar türünün karşılaştırılabilir olması zorunluluğunu beraberinde getirir. Yani, anahtar türü ya <code>Comparable</code> kategorisinde olmalıdır ya da <code>java.util.Comparator</code> arayüzünü gerçekleştirilen bir sınıf anahtar türüne ait nesneleri karşılaştırma desteğini sağlamalıdır. Gelin bu konuyu <code>TreeMap</code> sınıfının yapıcılarına açıklık getirerek anlamaya çalışalım.
<pre class="brush:java; gutter:false; highlight:[7]" name="TreeMapYapıcı1">...
import java.util.TreeMap;
public class Eşlemler {
public static void main(String[] ksa) {
...
Map<String, String> sözlük3 = new TreeMap<>();
sözlüğüDoldur(sözlük3);
System.out.println("Sözlük içeriği: " + sözlük3);
...
} // void main(String[]) sonu
...
} // Eşlemler sınıfının sonu
</pre>
Yukarıdaki kod parçasının işaretli satırındaki yapıcı çağrısı sonucunda yaratılan eşlem, karşılaştırma amacıyla, herhangi bir bilgi verilmediği için, anahtar bilgilerinin üyesi olduğu <code>String</code> sınıfınca gerçekleştirilen <code>Comparable</code> arayüzündeki <code>compareTo</code> iletisini kullanacaktır. Bunun sonucunda, <code>sözlük3</code> adlı eşlemi standart çıktıya basan komut, sözcükleri büyük harfler önce olmak üzere alfabetik sıraya göre dizecektir. Ayrıca, bu iş yapılırken Türkçe'ye özel harfler beklediğimiz sıranın aksine sona doğru yer alacaktır. Bu sorunun çözümü, Türkçe'ye özel bir sözcük karşılaştırma sınıfı gerçekleştirmektir.
<pre class="brush:java; gutter:false; highlight:[9,10]" name="TreeMapYapıcı2">...
import java.text.Collator;
import java.util.Comparator;
import java.util.Locale;
public class Eşlemler {
public static void main(String[] ksa) {
...
Map<String, String> trSözlük =
new TreeMap<>(new TürkçeSözcükKarşılaştırıcı());
sözlüğüDoldur(trSözlük);
System.out.println("Sözlük içeriği: " + trSözlük);
...
} // void main(String[]) sonu
...
} // Eşlemler sınıfının sonu
class TürkçeSözcükKarşılaştırıcı implements Comparator<String> {
private final static Collator trKarşılaştırıcısı =
Collator.getInstance(new Locale("tr"));
public int compare(String s1, String s2) {
return trKarşılaştırıcısı.compare(s1.toLowerCase(), s2.toLowerCase());
}
} // TürkçeSözcükKarşılaştırıcı sınıfının sonu
</pre>
Yukarıdaki kod parçasının işaretli satırları, dengeli ağaç altyapılı boş bir eşlem yaratırken ağacın, ve dolayısıyla eşlemin, oluşturulması amacıyla kullanılacak karşılaştırma ölçütünün argümanda geçirilen nesne tarafından belirlendiğini söylemektedir. Bu nesnenin sınıfına bakacak olursak, sözcüklerin büyük-küçük ayrımı yapılmaksızın Türkçe alfabeye göre karşılaştırılacağını ve sıranın Türkçe'den alıştığımız şekilde oluşacağını görürüz.<br/>
<br/>
<code>TreeMap</code> sınıfının diğer yapıcıları kopyalayan yapıcı olarak çalışır. Bunlardan yegâne argümanında <code>SortedMap</code> arayüzünü gerçekleştiren bir eşlem alanı, yaratılmakta olan eşlemi argümanda geçirilenin içeriği ile dolduracaktır. Birbirinden bağımsız iki eşlem oluşmasına yol açan bu işlemin sonrasında yeni eşleme uygulanacak işlemler argümandaki eşlemin kullandığı karşılaştırma ölçütü ile aynı olacaktır. Sonuncu yapıcı da buna benzer bir mantıkla çalışır. Bu sefer, kopyalamanın kaynağı olan eşlem <code>Map</code> arayüzünü gerçekleştiren herhangi bir eşlem olabilir. Yani, argüman olarak <code>TreeMap</code> nesnesi geçirilebileceği gibi, diğer altbölümlerde göreceğimiz sınıfların nesneleri de geçirilebilir ki, bu yaratılmakta olan eşlemin taklit edeceği bir karşılaştırma ölçütünün olmayabileceği anlamına gelir. Dolayısıyla, bu yapıcı çağrısının kullanımı sonrasında yeni yaratılan eşlemin içeriğinin düzenlenmesinde ölçüt olarak anahtar sınıfının <code>compareTo</code> iletisi kullanılacaktır.<br/>
<br/>
Yaratılan eşlemin içeriğini var olan bir eşlemden alırken farklı bir ölçüt kullanmak olanaklı değil mi, diye sorabilirsiniz. Hemen sorunuzun yanıtının olumlu olduğunu söyleyelim. Bunun için yapmanız gereken, eşlemi uygun bir karşılaştırma ölçütü ile boş yaratıp <code>putAll</code> iletisi ile doldurmaktan ibarettir.
<pre class="brush:java; gutter:false" name="TreeMapYapıcı3">...
public class Eşlemler {
public static void main(String[] ksa) {
...
Map<String, String> sözlük4 =
new TreeMap<>(new TürkçeSözcükKarşılaştırıcı());
sözlük4.putAll(sözlük3);
System.out.println("Sözlük içeriği: " + sözlük4);
...
} // void main(String[]) sonu
...
} // Eşlemler sınıfının sonu
</pre><br/>
<h3 id="SortedMap"><code>SortedMap</code> Arayüzü</h3><br/>
Gelelim, <code>SortedMap</code> arayüzünde olup <code>TreeMap</code> sınıfınca gerçekleştirilen iletilere. Bunlardan <code>comparator</code>, hedef eşlemin hangi ölçüte göre düzenlendiğini döndürür. Ancak, aklınızda olsun: bu iletinin <code><b>null</b></code> döndürmesi herhangi bir ölçüt kullanılmadığı anlamına gelmez; sadece, <code>Comparable</code> arayüzünü destekleyen anahtar sınıfının <code>compareTo</code> gerçekleştiriminin kullanıldığı anlamına gelir.<br/>
<br/>
<code>SortedMap</code> arayüzünde listelenen diğer iletiler uç değer ve eşlem dilimi döndürmek suretiyle görüntü sağlayanlar olmak üzere iki grupta toplanabilir. <code>TreeMap</code> türlü eşlemlerin dengeli ağaçlarla temsil edilmesi sayesinde ucuz maliyetlerle gerçekleştirilebilen bu iletilerden <code>firstKey</code> ve <code>lastKey</code>, sırasıyla, hedef eşlemin ilk ve son anahtar bilgisini döndürür. İkinci gruptaki iletiler, argüman veya argümanlarında belirtilen anahtar bilgileri ile belirlenen anahtar-tutanak çiftlerinden oluşan ve <code>SortedMap</code> tutacağı ile gösterilen eşlem görüntüleri döndürür. Bunlardan <code>subMap</code>, ilk argümanı ile başlayıp ikinci argümanının hemen öncesinde biten eşlem dilimini döndürürken, <code>headMap</code> başlangıçtan argümandaki anahtarın öncesine kadar olan dilimi, <code>tailMap</code> ise argümanındaki anahtar ile başlayıp eşlemin sonuna kadar giden dilimi döndürür.
<pre class="brush:java; gutter:false" name="eşlemDilimleri">SortedMap<String, String> adanbye = trSözlük.subMap("a", "b"); // "b" hariç!
SortedMap<String, String> jyeKadar = trSözlük.headMap("j");
SortedMap<String, String> jdenSonra = trSözlük.tailMap("j");
</pre>
Her üç iletinin de, görüntüleme grubundaki diğer iletiler gibi, yeni bir eşlem yaratmaktansa ileti alıcının gösterdiği eşlemi paylaştırdığı unutulmamalıdır. Bundan dolayı, yukarıdaki kodun işlenmesi sonrasında <code>trSözlük</code> dört farklı tutacak yoluyla değiştirilebilecektir. Mesela, <code>adanbye</code> tutacağı ile gösterilen dilimin herhangi bir eşlemesinin değiştirilmesi <code>trSözlük</code> yoluyla gösterilen eşlemi de etkileyecektir. Ancak; <code>trSözlük</code> yoluyla sözlüğe "eşlem" anahtarı ve anlamının girilmesi sadece <code>jyeKadar</code> ile temsil edilen dilimi etkileyecektir, diğerlerini değil. Ayrıca, hiçbir eşlemin geçerli olduğu dilimin dışındaki herhangi bir anahtara dair bilgiyi eklemekte kullanılamayacağı da bilinmelidir. Örneğin, <code>jdenSonra</code> aracılığıyla "eşlem" anahtarı ve ilişkin anlamın girilmeye çalışılması <code>IllegalArgumentException</code> ayrıksı durumuna neden olacaktır.<br/>
<br/>
<h3 id="NavigableMap"><code>NavigableMap</code> Arayüzü</h3><br/>
Java SE6 ile birlikte eklenen <code>NavigableMap</code> arayüzü, <code>SortedMap</code>'tekilere benzer iletiler sunar; tanımlanan işlevsellik uç değer işleme ve görüntüleme iletilerinden oluşur. Örneğin, <code>SortedMap</code>'ten kalıtlanan <code>firstKey</code> ve <code>lastKey</code> iletilerine <code>ceilingKey</code>, <code>floorKey</code>, <code>higherKey</code> ve <code>lowerKey</code> iletileri eklenmiştir. <code>ceilingKey</code> argümanındaki anahtara eşit veya ondan büyük ilk anahtar değerini döndürürken, <code>floorKey</code> argümanındaki anahtara eşit veya ondan küçük son anahtar değerini döndürür; <code>higherKey</code> ve <code>lowerKey</code> iletileri ise eşitlik denetimi yapmayan, sırasıyla, <code>ceilingKey</code> ve <code>floorKey</code> gibi çalışır.
<pre class="brush:java; gutter:false" name="eşlemDilimleri">String cVeyaSonrasındakiİlkSözcük = trSözlük.ceilingKey("c");
String cSonrasındakiİlkSözcük = trSözlük.higherKey("c");
String cVeyaÖncesindekiSonSözcük = trSözlük.floorKey("c");
String cÖncesindekiSonSözcük = trSözlük.lowerKey("c");
</pre>
Bir diğer ileti grubu, yukarıda adını andığımız iletilerle aynı şekilde çalışmakla birlikte anahtarı döndürmektense <code>Map.Entry</code> türlü bir tutacak aracılığıyla gösterilen eşlemeyi sarmalayan nesneyi döndürür. Bu iletilerin adları, ilişkin iletinin adındaki <code>Key</code> kısmı yerine <code>Entry</code> koymakla elde edilebilir. İstenecek olursa, bu noktadan sonra sarmalanan nesne <code>Map.Entry</code> arayüzündeki iletilerle sorgulanabilir.<br/>
<br/>
<code>NavigableMap</code> arayüzünce eklenen görüntüleme iletilerinden olan <code>descendingKeySet</code>, adının da haykırdığı gibi hedef eşlemdeki anahtarları bir küme içinde döndürür. Ancak, döndürülen kümenin dolaşılması anahtarları <code>keySet</code>'in döndürdüğünün tersi sırada verecektir. Anahtarlara dair görüntüyü döndüren bir diğer ileti olan <code>navigableKeySet</code>, <code>keySet</code>'e daha da çok benzer; iki iletinin döndürdükleri kümenin içeriği ve sırası tümüyle aynıdır. Ne var ki, <code>keySet</code> <code>Set</code> dönüş türüne sahipken, <code>navigableKeySet</code> <code>NavigableSet</code> döndürür.<br/>
<br/>
Tanıdık gelecek bir diğer ileti grubu dilim döndüren görüntüleme iletileridir. <code>NavigableMap</code> arayüzü, <code>SortedMap</code>'ten kalıtladıklarına ek olarak, uç değerlerin dahil edilip edilmeyeceğini belirleyen ekstra parametrelere sahip <code>headMap</code>, <code>subMap</code> ve <code>tailMap</code> adlı üç ileti destekler. Söz konusu iletiler, her bir uç değeri takiben geçirilen <code><b>boolean</b></code> değerlerle döndürülecek dilimin uç noktalarının nasıl ele alınacağını belirler; <code><b>true</b></code> geçirilmesi uç noktayı sonuca katarken, <code><b>false</b></code> geçirilmesi dışlar. Bu iletilerin bir diğer farkı, <code>SortedMap</code>'ten kalıtlanan aynı adlı iletilerin <code>SortedMap</code> döndürmesine karşılık, <code>NavigableMap</code> döndürmeleridir.<br/>
<br/>
Bir önceki paragrafta anılanlarla ele alınabilecek bir ileti de, hedef eşlemi döndürülen <code>NavigableMap</code> tutacağı vasıtasıyla ters sırada görüntülemeye yarayan <code>descendingMap</code>'tir. Yani, sözlüğümüzü ters sırada işlemek isteyecek olursak, yapmamız gereken aşağıdaki tanımlamadan ibarettir.
<pre class="brush:java; gutter:false" name="tersineEşlem">NavigableMap<String, String> trSözlük2 = trSözlük.descendingMap();
</pre>
Değineceğimiz son iletiler, uç değerleri denetleyerek [var olmaları halinde] silen <code>pollFirstEntry</code> ve <code>pollLastEntry</code> iletileridir. <code>pollFirstEntry</code>, hedef eşlemde ilk sırada bulunan eşlemeyi yoklayıp silerken, <code>pollLastEntry</code> son eşlemeyi siler. Her iki ileti de sildikleri eşlemeye dair bilgiyi <code>Map.Entry</code> tutacağı ile döndürürken, eşlemin boş olması halinde <code><b>null</b></code> döndürülür. <br/>
<br/>
<h3 id="uzmanEşlemler">Uzmanlaşmış Eşlemler</h3><br/>
Veri Kapları Çerçevesi, yukarıda anlatılanlara ek olarak içerebileceği anahtarların niteliklerinden dolayı özel bir şekilde gerçekleştirilmek suretiyle daha yüksek performanslı kullanılabilecek iki eşlem sınıfı daha sunar: <code>EnumMap</code> ve <code>WeakHashMap</code>.<br/>
<br/>
Anahtar türünün bir sabit listesi (İng,. enum) olması durumunda, eşlemdeki anahtar sayısı ilişkin sabit listesindeki simgesel sabitlerin sayısı ile sınırlanacaktır. Bu gibi bir durumda, anlattığımız eşlem sınıflarından birini kullanmaktansa, anahtarlarının sabit listesi değerleri olduğunu varsayarak eniyileme yapan <code>EnumMap</code> sınıfının kullanılması daha doğru olacaktır.<br/>
<br/>
<code>WeakHashMap</code> sınıfı da, tıpkı <code>HashMap</code> gibi, kıyım tablosu temelli bir gerçekleştirim sağlar. Ancak; <code>WeakHashMap</code> içine konulabilecek anahtar değerleri <code>WeakReference</code> türünden olmak zorundadır.<sup><a href="#dn2" id="ref2">2</a></sup> Bu ise tutanakların, eşlem kullanıcısının göndereceği <code>remove</code> iletilerinin yanısıra, çöp toplayıcısının vereceği bir karar sonrasında [kullanıcı kontrolunun dışında] usulca silinebileceği anlamına gelir. Henüz silinmesi istenmeyen bir tutanağın bu sondan korunması kuvvetli bir tutacak ile gösterilmesi sayesinde sağlanabilir.<br/>
<br/>
</div>
<div style="text-align: right;">
<a alt="Hızlı arama zamanlı kaplar (Eşlemler)-java.util.Map, java.util.AbstractMap, java.util.HashMap, java.util.Hashtable, java.util.LinkedHashMap" href="http://ta-java.blogspot.com/2011/12/hzl-arama-zamanl-kaplar-1.html">İlk Yazı</a><br/>
<a alt="Java'da doğrusal veri kapları" href="http://ta-java.blogspot.com/2012/01/dogrusal-veri-kaplar.html">Doğrusal Veri Kapları</a><br/>
<a alt="Java'da değişken uzunluklu diziler-java.util.Vector" href="http://ta-java.blogspot.com/2011/09/degisken-uzunluklu-diziler.html">Değişken Uzunluklu Diziler: <code>Vector</code></a></div>
<hr />
<div id="dipnotlar" style="text-align: justify;">
<ol>
<li id="dn1">Kırmızı-siyah ağaçlar, B-tree ailesindeki dengeli ağaçlardan [azami] dört düğümlü olanların, ki bu ağaçlara tepeden aşağıya 2-3-4'lü ağaçlar da denir, ikili ağaç şeklinde gerçekleştirilmiş biçimleridir.<a href="#ref1">↑</a></li>
<li id="dn2"><code>java.lang.ref</code> paketinin <code>Reference</code> köklü sıradüzenindeki sınıfların tutacakları, bir nesne göstermektense bir başka nesnenin tutacağını sarmalayarak onu nesneye çevirir. JSM tarafından özel muameleye tabii tutulan bu nesnelerin kullanım amacı, çöp toplayıcı ile etkileşerek kaynak yönetim sürecine uygulama gereksinimlerine göre müdahele edilmesini olanaklı kılmaktır.<a href="#ref2">↑</a></li>
</ol>
</div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-49959159840137449772011-12-02T10:44:00.001+02:002012-01-31T22:54:04.955+02:00Hızlı Arama Zamanlı Kaplar-1<div id="anaMetin" style="text-align: justify;">
Dizi<a alt="Java'da diziler" href="http://ta-java.blogspot.com/2011/04/bileske-turler-diziler.html">🔎</a>, <code>Vector</code><a alt="Java'da değişken uzunluklu diziler-java.util.Vector" href="http://ta-java.blogspot.com/2011/09/degisken-uzunluklu-diziler.html">🔎</a> veya liste yapısını gerçekleştiren <code>ArrayList</code><a alt="Java'da doğrudan erişimli genel liste-java.util.ArrayList" href="http://ta-java.blogspot.com/2012/01/dogrusal-veri-kaplar.html#doğrudanErişimliListeler">🔎</a> ve <code>LinkedList</code><a alt="Java'da ardışık erişimli genel liste-java.util.LinkedList" href="http://ta-java.blogspot.com/2012/01/dogrusal-veri-kaplar.html#bağlaçlıListe">🔎</a> gibi sınıfların bir zaafı, doğrusal bir yapıya sahip olmaları nedeniyle ortalama eleman arama zamanlarının yavaş olmasıdır. Örnek olarak, sırasız bir listede gözlerinizi baştan sona doğru gezdirerek adınızı aradığınızı düşünün. Yapmanız gereken, listenin başından adınızın bulunduğu yere kadar her sıradaki ad ile kendi adınızın eşitlik denetimini yapmaktır. Şanslıysanız, adınız başlardadır ve kısa zamanda kendinize dair bilgileri bulabilirsiniz; ancak, şansınızın yardım etmediği bir gün adınız listenin sonunda da olabilir ve aradığınız bilgiyi çok daha uzun bir zamanda elde edersiniz. Aynı listede adını arayanların harcadığı ortalama zaman ise—<i>n</i> kişi için toplam 1 + 2 + ... + <i>n</i> eşitlik denetimi olduğuna göre—<i>n</i>(<i>n</i>+1)/2<i>n</i>, yani (<i>n</i>+1)/2, eşitlik denetimi zamanı olacaktır.<br />
<br />
Her eşitlik denetimi sonrasında arama uzayını sadece bir küçülten bu yöntem işi görür fakat büyük listelerde işkence halini alan bir performansa sahiptir. Biraz düzenli bir yapıya sahip olanlarınız, performans çözümlemede O(<i>n</i>) şeklinde ifade edilen bu sonucun aşağılara çekilmesinin olanaklı olduğunu bilirler. Mesela, bir kütüphaneye yolu düşüp de kitap arayanlarınız—iyi örnek olmadı galiba... müzik dükkânından albüm alanlarınız diyelim—hemen konuya [ya da tarza] göre indeksleme ve aynı konu içinde alfabetik sıraya dizmeyi önereceklerdir. Java standart kitaplığında doğrudan karşılığı olmadığı için, indekslemeyi köşeye koyup sıralamanın bize ne kazandırabileceğini görelim; bunu yaparken de—unutmayın, asıl konumuz işi bilgisayarlara yaptırmak—adımızın özelliklerine dayalı eniyilemelerin yapılamadığını varsayalım. İyi bir arama zamanını garanti etmek için izlenmesi gereken strateji, her eşitlik denetimi sonrasında arama uzayını olabildiğince yüksek oranda küçültmelidir. Konulan bu hedefe, "İlk elemanın ad bilgisi adıma eşit mi?" şeklinde sorulan sorunun "Ortadaki elemanın ad bilgisi adıma eşit mi, eşit değilse adımdan önce mi geliyor, sonra mı?" şeklinde değiştirilmesiyle erişilebilir. Böylesine bir değişiklik arama uzayımızı bir azaltmak yerine ikiye bölerek küçültecektir. Çünkü, sıralı olduğu bilinen listemizin orta elemanının aradığımız değere eşit olmaması durumunda, adımız ya ilk yarıdadır ya da ikinci yarıda; böylece, adımız hangi yarıda olursa olsun, arama uzayımız yarı yarıya küçülecektir. Bu ise, sorgumuzun olumlu veya olumsuz sonucunun O(<i>n</i>) yerine O(lg<i>n</i>) performansla verileceği anlamını taşır. <i>İkili arama</i> adı verilen ve <code>java.util</code> paketindeki <code>Arrays</code><a alt="Java'da dizi metotları-java.util.Arrays" href="http://ta-java.blogspot.com/2011/08/baz-onemli-snflar-javautilarrays.html">🔎</a> ve <code>Collections</code> sınıflarının <code>binarySearch</code> metotlarında gerçekleştirilen bu algoritmanın ifade edilen performansı verebilmesi için veri kümesinin sıralı ve altyapıda kullanılan veri yapısının doğrudan erişimli dizi, <code>Vector</code> veya <code>ArrayList</code> gibi bir yapı olması gerekir. Çok özel durumlar<sup><a href="#dn1" id="ref1">1</a></sup> dışında sıralamanın O(<i>n</i>lg<i>n</i>) ve daha fazla bir ek yük getireceği düşünüldüğünde, bu algoritmanın, özellikle de ekleme ve silmelerle veri kümesinin büyüyüp küçüldüğü kullanım desenlerinde, her zaman sözü edilen performansı sağlayamayacağı görülür. Çözüm, ekleme/silme işlemlerinin de hızlı bir şekilde yapıldığı hızlı arama zamanlı kaplar olan ve iki yazılık dizimizin konusunu oluşturan <i>eşlem</i>lerdedir.<br />
<br />
<ul>
<li><a href="#giriş">Giriş</a></li>
<li><a href="#MapArayüzüVeAbstractMapSınıfı"><code>Map</code> arayüzü ve <code>AbsractMap</code> sınıfı</a></li>
<li><a href="#kıyımTabloluEşlemler"><code>HashMap</code> ve <code>LinkedHashMap</code> sınıfları</a></li>
<li><a href="#Hashtable"><code>Hashtable</code> sınıfı</a></li>
<li>İkinci yazı<a alt="Hızlı arama zamanlı kaplar (Eşlemler)-java.util.NavigableMap, java.util.SortedMap, java.util.EnumMap, java.util.TreeMap, java.util.WeakHashMap" href="http://ta-java.blogspot.com/2011/12/hzl-arama-zamanl-kaplar-2.html">🔎</a></li>
</ul><br/>
<h3 id="giriş">
Giriş</h3>
<br />
Standart Java kitaplığına bakıldığında eşlemlerin Veri Kapları Çerçevesi'nin bir parçası olarak <code>java.util</code> paketi içinde tanımlanıp gerçekleştirildiği görülür. Temel işlevselliği tanımlayan <code>Map</code> arayüzü ve kimi işlemlerin altyapıda kullanılan veri yapısından bağımsız bir şekilde gerçekleştirildiği <code>AbstractMap</code> sınıfının temelini oluşturduğu eşlemlere dair sıradüzeninin önemli bir bölümü aşağıda verilmiştir. <code>Object</code>, <code>Cloneable</code> ve <code>Serializable</code> dışındakilerin <code>java.util</code> paketinde olduğu türlerden <code>Hashtable</code>, Java'nın ilk uyarlamasından itibaren var olup J2SDK 1.2 ile gelen Veri Kapları Çerçevesi'ne monte edilmiştir. Dolayısıyla, J2SDK 1.2 öncesi yazılmış programları çökertmemek adına <code>Hashtable</code> sınıfı, <code>AbstractMap</code>'ten kalıtlamaktansa, <code>Map</code> arayüzünü doğrudan gerçekleştirmiştir. <b>Kadük olan <code>Dictionary</code> adlı soyut sınıfın kullanımından ise kesinlikle kaçınılmalıdır.</b><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXiMJMUxigHVC9-EAYdPV6gj9fvv1Uu4gwIuPmY8aH2Fe_6P9hbevHalBPdQeZCTGzO9jxVZ8s85FG3vq_IyWQYdoWF3nVlMwiithYuLHK6r8OCdVmCQiLqNNMCOvZ5i9tXDYuA3Y-QBzl/s400/ListeYap%2525C4%2525B1lar%2525C4%2525B1S%2525C4%2525B1rad%2525C3%2525BCzeni.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="209" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXiMJMUxigHVC9-EAYdPV6gj9fvv1Uu4gwIuPmY8aH2Fe_6P9hbevHalBPdQeZCTGzO9jxVZ8s85FG3vq_IyWQYdoWF3nVlMwiithYuLHK6r8OCdVmCQiLqNNMCOvZ5i9tXDYuA3Y-QBzl/s400/ListeYap%2525C4%2525B1lar%2525C4%2525B1S%2525C4%2525B1rad%2525C3%2525BCzeni.png" /></a></div>
<code>Map</code> arayüzü ile başlamadan önce, nasıl kullanılabileceğini örneklendirerek eşlemlerin biçimsel olmayan tanımını yapalım. <b>Eşlemler, anahtar-tutanak çifti olarak temsil edilen eşlemelerin hızlı bir biçimde işlenmesini sağlayacak şekilde düzenlenerek tutulduğu veri yapılarıdır.</b> Doğal olarak temel işlemler, eşleme bir anahtar-tutanak çiftinin eklenmesi, sağlanan bir anahtara dair tutanak bilgisinin sorgulanması veya eşlemenin silinmesidir. Bu işlemlerin hızlı bir şekilde tamamlanması ise gerçekleştirimci tarafından karşılanacağı varsayılan temel bir beklentidir.<br />
<br />
Kimi eşlemelerin eşlemlerin yanısıra diğer veri yapıları ile de temsil edilebileceği bir gerçektir. Örnek olarak, ülkemizdeki plaka kodları ile il adları arasındaki eşlemeleri düşünün. Söz konusu eşlemeler bir eşlemle tutulabileceği gibi, doğrudan erişimi destekleyen bir veri yapısı kullanılarak da tutulabilir. Aslına bakarsanız, veri kümemizin durağan yapısı—her dakika yeni bir il eklenmeyecek—ve plaka kodları ile doğrudan erişimde kullanılan indis arasındaki neredeyse mükemmel uyuşma nedeniyle—0 nolu indis boş bırakılırsa 1-1 bir uyuşmadan söz edebiliriz—dizinin kullanılması çok daha yerinde bir seçim olacaktır. Ancak; eşlemenin ters yönde (İl adı→Plaka kodu) olmasının istenmesi halinde işler değişir: doğrudan erişimden yararlanılabilmesi il adının kıyımdan geçirilerek bir tamsayıya dönüştürülmesi ile olanaklıdır. Bu gibi bir durumda, fazladan kıyım fonksiyonu yazıp belki de düşük verimli bir çözüm üretmektense, eşlem kullanmak daha doğru olacaktır.<br/>
<br/>
Eşlemleri çekici kılan bir diğer kullanım deseni, veri kümesinin anahtar-tutanak eşlemelerinin eklenmesi ve silinmesi ile büyüyüp küçüldüğü problemlerdir. Bu gibi durumlarda, kıyım fonksiyonunun maliyetine ek olarak, altyapıda kullanılan veri yapısının büyümesi veya küçülmesi ve ekleme ile silmelerin kaydırmalar nedeniyle sebep olacağı maliyet, eşlem kullanımını cazip hale getirmektedir.<br />
<br />
<h3 id="MapArayüzüVeAbstractMapSınıfı">
<code>Map</code> Arayüzü ve <code>AbstractMap</code> Sınıfı</h3>
<br />
Gelelim, <code>Map</code> arayüzüne. Anahtar ve tutanak bilgisi türlerinin tür parametresi olarak tanımlandığı bu soysal arayüzdeki <code>isEmpty</code> iletisi, hedef eşlemin boş olup olmadığı sorusuna yanıt verirken, <code>size</code> iletisi hedef eşlemdeki eşleme sayısını döndürür. Bu bağlamda, standart kitaplıkta sağlanan gerçekleştirimlerin aynı anahtara karşılık tek bir tutanak bilgisi tuttuğu hatırlatılmalıdır.<sup><a href="#dn2" id="ref2">2</a></sup><br />
<br />
Eşlemlere ekleme, argüman olarak, sırasıyla, anahtar ve tutacak bilgisini alan <code>put</code> iletisi ile yapılabilir. Anahtar değerinin eşlendiği bir önceki tutanağı döndüren bu işlemin aynı anahtar değeri kullanılarak birden çok kez icra edilmesi durumunda, en son ekleme etkili olacaktır. Birden çok sayıda eşlemenin eklenmesinin istenmesi halinde, arda arda kullanılacak <code>put</code> iletileri yerine eklenecek anahtar-tutanak bilgilerini içeren bir eşlem bekleyen <code>putAll</code> iletisi kullanılabilir.<br />
<br />
<code>get</code> iletisi argümanındaki anahtarın hedef eşlemde eşlendiği tutanak bilgisini döndürür. Benzer bir ileti, kendisine geçirilen anahtarın bir tutanak ile eşlenip eşlenmediğini döndüren <code>containsKey</code> yüklemidir. <code>containsValue</code> ise <code>containsKey</code> yükleminin tersi olarak düşünülebilir; bu yüklem, argümanındaki tutanak bilgisinin herhangi bir anahtar ile eşlenip eşlenmediği sorusunu yanıtlar. Bir an için, <code>get</code> iletisinin <code>containsKey</code> iletisini gereksiz kıldığı düşünülebilir. Ne var ki, <code>get</code> iletisinin eşlemde olmayan bir anahtarın sorgulanmasına yanıt olarak <code><b>null</b></code> döndürmesi ve kimi eşlem yapısı gerçekleştirimlerinde anahtarların <code><b>null</b></code> ile eşlenebilir olması sonucu ortaya çıkan muğlaklık ancak <code>containsKey</code> iletisinin kullanımıyla ortadan kaldırılabilir. <code>containsKey</code> aynı anahtar için <code><b>true</b></code> döndürüyorsa anahtar <code><b>null</b></code> değerine eşlenmiş demektir, aksi takdirde anahtara dair bir eşleme yoktur.<br />
<br />
Hedef eşlemden eleman silmek için iki ileti kullanılabilir. Bunlardan <code>clear</code>, eşlemi hızlı bir biçimde boşaltırken, <code>remove</code> argümanındaki anahtara ilişkin eşlemeyi silmekle yetinir ve sonucu olarak işlem öncesinde anahtarın ilişkilendirildiği tutanak bilgisini döndürür. Geçirilen anahtar değerinin hedef eşlemde bulunmaması halinde ise, <code>remove</code> bu gerçeği kullanıcısına <code><b>null</b></code> döndürerek bildirir.<br />
<br/>
<code>Map</code> arayüzünü gerçekleştiren sınıfların desteklemesi gereken bir diğer ileti grubu, hedef eşlemdeki anahtar-tutanak eşlemelerinin değişik görüntülerini sağlayanlardır. <b>Bu gruptaki iletiler tarafından döndürülen kaplar ile hedef eşlemin belleği paylaştıkları ve dolayısıyla görüntü üzerinden yapılan değişikliklerin eşleme de yansıtılacağı akılda tutulmalıdır.</b> Bunlardan <code>keySet</code>, anahtarları barındıran küme nesnesini gösteren bir <code>java.util.Set</code> tutacağı döndürürken, <code>values</code> iletisi tutanak bilgilerini içeren nesneyi temsil eden bir <code>java.util.Collection</code> tutacağı döndürür. İlk ileti için <code>Set</code> ikinci ileti içinse <code>Collection</code> türlü bir tutacak döndürülmesinin nedeni, eşlemlerin aynı anahtardan en fazla bir tane bulundururken, aynı tutanak bilgisinin birden çok sayıda eşlemede geçmesinin mümkün olmasıdır. Örnek olarak; anahtar türü <code>String</code>, tutanak türü <code>Integer</code> tanımlanmış bir eşleme "İzmir"→35 ve "Karşıyaka"→35 bilgilerinin eklendiğini varsayalım. Bu eşleme gönderilecek <code>values</code> iletisi, <code>Collection</code> tutacağı ile gösterilen iki elemanlı bir kap döndürürecektir: halbuki, dönüş türü olarak <code>Set</code>'in kullanılması her iki değerin de 35 olması nedeniyle tek elemanlı bir küme döndürülmesine neden olacaktı.<br/>
<br/>
<code>values</code> ve <code>keySet</code> iletileri gibi görüntüleme grubuna ait olan <code>entrySet</code>, hedef eşlemdeki anahtar-tutanak çiftlerini bir küme içinde döndürür. Kümelerin tek tür ile parametrize edilebiliyor olmasından ötürü, döndürülen küme anahtar ve tutacak türleri ile parametrize edilen <code>Map.Entry</code> ile gösterilen nesneler içerir. Başka bir deyişle, <code>Map</code> arayüzünün iç arayüzü olan bu tür anahtar ve tutanak bilgilerini sarmalar; anahtar veya tutanak bilgilerine dair bir şeyler yapmak istediğimizde, <code>entrySet</code> iletisinin döndürdüğü kümenin elemanlarına uygun iletileri göndermemiz gerekir. Bu iletiler, anahtarın sorgulanması (<code>getKey</code>) tutanak bilgisinin sorgulanıp değiştirilmesi (<code>getValue</code> ve <code>setValue</code>) ve eşitlik denetimi (<code>equals</code>) ile kıyım fonksiyonundan (<code>hashCode</code>) ibarettir. Döndürülen küme nesnesinin eşlem nesnesi ile aynı altyapıyı kullandığı unutulmamalıdır. Yani, <code>entrySet</code>'in döndürdüğü küme yoluyla güncellenen bilgiler eşleme yansıyacaktır; benzer şekilde, küme tutacağı eşlem tutacağı ile yapılan değişiklikleri görebilecektir.<br/>
<br/>
Son olarak göz atacağımız iletiler, <code>Object</code> sınıfından tanıdık gelecek olan <code>equals</code> ve <code>hashCode</code>. Sırasıyla, eşitlik denetimi ve kıyım fonksiyonuna karşılık gelen bu iletilerin, <code>Map</code> arayüzüne konulmasının nedeni, geliştiricinin yeni bir eşlem sınıfı gerçekleştirimi sunmak istemesi halinde dikkatsizlikle bu iletileri es geçmesinin önüne geçmektir. Diğer veri kapları gibi eşlemlerin de eşitlik denetimlerinin kap içeriği göz önüne alınarak yapılması <code>equals</code> iletisinin gerçekleştirilmesini zorunlu kılmaktadır. Elemanların belki de hepsini dolaşarak tamamlanacak bu pahalı işlemin maliyetinin düşürülmesi içinse kıyım fonksiyonundan yararlanılması olasılığı <code>hashCode</code> iletisinin gerçekleştirilmesini dikte etmektedir; ne de olsa, kıyım değerleri farklı olan iki nesnenin eşit olması olanaklı değildir.<a alt="Her sınıfa gerekebilecek metotlar" href="http://ta-java.blogspot.com/2011/05/her-snfa-lazm-metotlar.html#e%C5%9FitlikDenetimi">🔎</a><br/>
<br />
Bu noktada, önceki paragrafta andığımız iletilerin eşlemleri ilgilendirdiği, anahtar ve tutanak türleri için herhangi bir zorunluluk olmadığı vurgulanmalıdır. Ne var ki; eşlem yapısının arayüzünde yer alan iletilerin gerçekleştirimleri, alternatif bir yöntem olmadığı savlanamasa da, yüksek olasılıkla anahtar ve tutacak bilgilerinin eşitlik denetiminden yararlanarak işlerini göreceklerdir. Bir ihtimal eşlem yapısının gerçekleştirimcisi, eşitlik denetimini anahtar/tutacak bilgisinin kıyım fonksiyonundan yararlanarak kısa yoldan bitirmek isteyecektir. Dolayısıyla, eşlem yapısına konulacak nesnelerin sınıflarında <code>equals</code> ve <code>hashCode</code> iletilerinin gerçekleştirilip gerçekleştirilmeyeceği dikkatle düşünülmeli ve karara bağlanmalıdır.<br/>
<br/>
<code>Map</code> arayüzündeki kimi iletiler, altyapıda kullanılan veri yapısından bağımsız bir biçimde gerçekleştirilebilir. Örneğin, <code>get</code> iletisi eşlemdeki <code>entrySet</code>'in döndürdüğü anahtar-tutanak çiftlerinin gezilerek eşitlik denetimi yapılmasıyla gerçekleştirilebilir. Bunun, farklı sınıflarda tekrarlanmaktansa, ortak bir üst sınıfa (<code>AbstractMap</code>) konulması ve eşlem sınıflarının (<code>HahsMap</code>, <code>TreeMap</code>, vd.) bu sınıftan kalıtlayarak gerçekleştirilmesi kodun yeniden kullanımını artıracaktır. Eşlem gerçekleştirimcisinin daha uygun bir çözümü olması durumunda ise, ilişkin metot ezilerek istenen yapılabilir.<br/>
<br/>
<h3 id="kıyımTabloluEşlemler"><code>HashMap</code> ve <code>LinkedHashMap</code> Sınıfları</h3><br/>
Eşlemlerin bir gerçekleştirim yöntemi, indis ile erişimin yüksek performansından yararlanmak amacıyla altyapıda doğrudan erişimli bir yapı kullanır. Amaç, eşleme gönderilen iletilerin arka plandaki yapı üzerinde uygulanan indis işlemleri haline dönüştürülmesi ve doğrudan erişimin keyfini sürmektir. Ancak, bu söylendiği kadar kolay gerçekleştirilemez; anahtar değerlerinin türü ve alabileceği değerler işi zora sokabilir. Plaka kodu→İl adı örneğimizde işimiz çok basitti ve bundan dolayı dizinin kullanılması düşünülebilir demiştik. Ne var ki, anahtar değerlerinin çok daha geniş ve seyrek bir aralığa dağılması veya tamsayı olmaması söz konusu olabilir. Mesela, farklı yıllarda kaydolmuş öğrencilerin aldığı bir derse dair No→Ad eşlemelerini tutan diziyi düşünün. 8-10 basamaklı bir tamsayı olan öğrenci numarası ve bu numaraların değişik yıllara dağılmış olması, numaradan indise geçişi zorlaştıracaktır. Peki ya, öğrenci numaraları tamsayı olarak değil de <code>String</code> olarak temsil ediliyor olsaydı? Ya da, sözcükler ve anlamları arasındaki eşlemeleri tutmak isteseydik?<br/>
<br/>
Bir önceki paragrafta yöneltilen soruların yanıtı, anahtar değerlerin uygun bir şekilde kıyılıp tamsayıya çevrilmesinden geçer. Yapmamız gereken, önce anahtar değeri <code>hashCode</code> iletisini göndererek kıymak ve elde edilen tamsayıyı arka plandaki doğrudan erişimli yapıya indisli erişimde kullanmaktır. İşin püf noktası olan kıyım fonksiyonunu düzgün gerçekleştirirsek, kullandığımız yöntem neredeyse dizi veya <code>Vector</code> içindeki bir elemana erişim kadar hızlı olacaktır. Kıyım fonksiyonunun kötü olması ise, bir o kadar yavaş bir erişim zamanımız olacağı anlamına gelir. Dolayısıyla, mümkünse, işi bir bilene bırakmakta yarar vardır. Java'da bu yüce makam, <code>HashMap</code> sınıfıdır.<br/>
<br/>
<code>HashMap</code> nesnelerinin performansını etkileyen iki parametre vardır: <i>sığa</i> ve <i>doluluk oranı</i>. Bunlar, sırasıyla, eşlem içeriğinin tutulduğu doğrudan erişimli yapının uzunluğunu ve eşlemdeki anahtar-tutanak çifti sayısının sığaya oranını bildirir. Eşleme eklenmek istenen bir anahtar-tutanak çifti, anahtar değerinin kıyılarak 0 ila sığa-1 arasında bir tamsayıya dönüştürülmesi sonrasında yapının ilişkin konumuna, ki bu konumlara <i>kova</i> denir, eklenecektir. Bu kovanın boş olmaması durumunda ise, anahtar-tutanak çifti daha önceden kovaya konulmuş olan diğer eşlemelerin bulunduğu bir listeye eklenir. Sorgulama ve silme işlemleri de işe anahtar bilgisini kıyarak başlar. Bunun takiben, altyapıdaki doğrudan erişimli yapının ilişkin kovasına bakılır. Söz konusu kova boşsa, her iki işlem de <code><b>null</b></code> döndürür. Aksi takdirde, kovada tutulan eşlemelerin bulunduğu listenin aranması sonrasında sonuç döndürülür. Kovanın boş olmaması, eşlemin anahtara dair bilgi içerdiği anlamını taşımaz; kovada yer alan eşlemeler aynı kıyım değerine sahip farklı anahtar değerlerine ilişkin eşlemeler de olabilir.<br/>
<br/>
Zaman içinde ekleme yapıldıkça eşlem dolacak, hem altyapıda kullanılan doğrudan erişimli yapı kalabalıklaşacak hem de kovalar içindeki listeler uzayacaktır. Bu; ekleme, sorgulama ve silme için harcanan zamanların uzayacağı anlamına gelir. Böyle bir durumun oluşmasını engellemek adına <code>HashMap</code>, doluluk oranının belli bir değeri aşması üzerine doğrudan erişimli yapıyı büyütür ve içeriği yeniden düzenler. Oldukça pahalı bir işlem olması nedeniyle, yeniden düzenleme sayısının en aza indirilmesi performans açısından yararlı olacaktır. Bunun için, <code>HashMap</code> nesnesinin yaratılması sırasında sığa ve doluluk oranı parametrelerinin probleme uygun bir biçimde seçilmesi gerekir.<br/>
<br/>
<code>HashMap</code> nesneleri dört yapıcıdan biri kullanılarak yaratılabilir. Bunlardan <code><b>int</b></code> ve <code><b>float</b></code> argüman alanı, nesneyi istenen ilk sığa ve doluluk oranına sahip olacak şekilde yaratır. Yapıcıya sağlanmayıp işlem sırasında çıkarsanamaması durumunda doluluk oranı 0.75, ilk sığa ise 16 olarak kabul edilir. Dolayısıyla, tek <code><b>int</b></code> argüman bekleyen yapıcının kullanılması durumunda, yaratılan eşlem argümanda geçirilen büyüklükte bir ilk sığaya ve 0.75 doluluk oranına sahip olacaktır. Varsayılan yapıcının kullanılması halinde ise, 16 ilk sığalı ve doluluk oranı 0.75 olan bir eşlem yaratılacaktır. Her üç durumda da yaratılan eşlem boş olacaktır. Nesnemizin içeriğe sahip olarak yaratılmasını istiyorsak, yapmamız gereken <code>Map</code> arayüzünü destekleyen bir sınıfın nesnesinin argüman geçirildiği yapıcıyı kullanmaktır. Ancak, şunu unutmayın: argümanındaki eşlem ile aynı sayıda eşlemeye sahip yaratılan yeni nesne doluluk oranını miras almayacak, 0.75 olarak kabul edecektir.<br/>
<br/>
<code>HashMap</code> sınıfının akılda tutulması gereken bir özelliği, eşlem içeriğine dair görüntülerin (<code>entrySet</code>, <code>keySet</code> ve <code>values</code>) anahtar-tutanak çiftlerini herhangi bir sırada vermiyor olmasıdır. Hatta; doluluk oranının aşılması sonrasında yeniden düzenlenen eşlemlerin görüntüleri düzenleme öncesindekiyle alakalı olmayabilir. Bunu, aşağıdaki örneğin işaretli satırındaki örtük <code>toString</code> kullanımından da görebiliriz. Üretilen çıktıda ne ekleme zamanı, ne son erişim zamanı, ne de anahtar bilgileri arasındaki bir sıralama ölçütü dikkate alınmaktadır.<br/>
<br/>
<div style="color: #444444; text-align: center;">Eşlemler.java</div>
<pre class="brush:java; gutter:false; highlight:[9]" name="TreeMapYapıcı1">import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Eşlemler {
public static void main(String[] ksa) {
Map<String, String> sözlük = new HashMap<>();
sözlüğüDoldur(sözlük);
System.out.println("Sözlük içeriği: " + sözlük);
...
} // void main(String[]) sonu
private static void sözlüğüDoldur(Map<String, String> sözlük) {
Scanner grdKnlı = new Scanner(System.in);
for (;;) {
System.out.print("Yeni bir sözcük giriniz: ");
String sözcük = grdKnlı.nextLine();
if (sözcük.toLowerCase().equals("bitti")) break;
System.out.print("Anlam: ");
String anlam = grdKnlı.nextLine();
sözlük.put(sözcük, anlam);
}
} // void sözlüğüDoldur(Map<String, String>) sonu
...
} // Eşlemler sınıfının sonu
</pre>
Derdimizin çaresi, sergilenen bu kaotik davranışı değiştiren <code>LinkedHashMap</code> sınıfındadır. <code>HashMap</code>'ten kalıtlayan bu sınıf, doğrudan erişimli yapıya ek olarak, altyapıda tuttuğu çift bağlaçlı bir liste ile eşlem içeriğini yapıcı çağrısı sırasında belirlenen iki ölçütten birine göre sıralı tutarak üretilen çıktıyı beklentinize göre oluşturacaktır. Aksi söylenmediği takdirde ekleme sırasında üretilecek olan çıktı, ilk sığa ve doluluk oranına ek olarak sağlanacak üçüncü bir argümanla en son erişim sırasına göre de oluşturulabilir. Örneğin, aşağıdaki kod parçasının işaretli satırındaki yapıcı çağrısı, <code>sözlük2</code> tarafından temsil edilen eşlemin en son erişilen anahtar-tutanak çiftleri başta gelecek şekilde görüntülenmesini sağlar.
<pre class="brush:java; gutter:false; highlight:[6]" name="TreeMapYapıcı1">...
import java.util.LinkedHashMap;
public class Eşlemler {
public static void main(String[] ksa) {
Map<String, String> sözlük2 = new LinkedHashMap<>(100, 0.80, true);
sözlüğüDoldur(sözlük2);
// sözlük2'yi kullan...
System.out.println("Sözlük içeriği: " + sözlük2);
...
} // void main(String[]) sonu
...
} // Eşlemler sınıfının sonu
</pre>
Diğer yapıcıları <code>HashMap</code>'tekilerle aynı şekilde çalışan <code>LinkedHashMap</code> sınıfı, <code>HashMap</code>'te olduğu gibi <code>Map</code> arayüzündekinden başka bir işlevsellik sunmaz.<br/>
<br/>
<h3 id="Hashtable"><code>Hashtable</code> Sınıfı</h3><br/>
Buraya kadar gelip de pes etmeyenlere müjde, <code>Hashtable</code> sınıfı <code>HashMap</code> ile neredeyse tamamıyla aynıdır. J2SDK 1.2 öncesinde tanımlanmış olan bu sınıfın <code>HashMap</code>'ten farklı olarak desteklediği kimi iletiler herhangi bir ekstra işlevsellik katmaz. Ancak, sakın ola ki, bu iki sınıfın her zaman birbirlerinin yerine kullanılabileceğini sanmayın. Bunun başlıca üç sebebi vardır:
<ol>
<li>Fazladan bir işlev katmamakla beraber <code>Hashtable</code> tarafından sağlanan J2SDK 1.2 öncesinden kalma <code>contains</code>, <code>elements</code> ve <code>keys</code> iletileri <code>HashMap</code> nesnelerine gönderilemez. [Eski kodlarınızı elden geçirirken bu iletileri, sırasıyla, <code>containsValue</code>, <code>values</code> ve <code>keySet</code> iletileri ile değiştirmenizi tavsiye ederim.]</li>
<li>Diğer eşlem sınıflarının aksine, <code>Hashtable</code> anahtar değeri olarak <code><b>null</b></code> değerinin kullanılmasına izin vermez.</li>
<li>Diğer eşlem sınıflarının aksine, <code>Hashtable</code> çok izlekli kullanım düşünülerek yazılmıştır. Yani, <code>HashMap</code> [ve <code>TreeMap</code>] nesnelerinin farklı izleklerden aynı anda kullanılmak istenmeleri durumunda programcının yapının tutarlılığını korumak adına eşgüdüm kodu yazması gerekirken, <code>Hashtable</code> için böyle bir şey söz konusu değildir.</li>
</ol>
</div>
<div style="text-align: right;">
<a alt="Hızlı arama zamanlı kaplar (Eşlemler)-java.util.NavigableMap, java.util.SortedMap, java.util.EnumMap, java.util.TreeMap, java.util.WeakHashMap" href="http://ta-java.blogspot.com/2011/12/hzl-arama-zamanl-kaplar-2.html">İkinci Yazı</a><br/>
<a alt="Java'da doğrudan erişimli genel liste-java.util.ArrayList" href="http://ta-java.blogspot.com/2012/01/dogrusal-veri-kaplar.html">Doğrusal Veri Kapları</a><br/>
<a alt="Java'da değişken uzunluklu diziler-java.util.Vector" href="http://ta-java.blogspot.com/2011/09/degisken-uzunluklu-diziler.html">Değişken Uzunluklu Diziler: <code>Vector</code></a></div>
<hr />
<div id="dipnotlar" style="text-align: justify;">
<ol>
<li id="dn1">Çok özel eleman türü ve dağılımı istemeleri nedeniyle O(<i>n</i>) performans sergileseler de kimi algoritmaların bu gibi genel kullanımlarda zikredilmeleri mantıklı değildir.<a href="#ref1">↑</a></li>
<li id="dn2">Bu aslında o kadar da kısıtlayıcı bir şey değil. Anahtarın birden çok değer ile ilişkilendirilmesini istiyorsanız, tutanak türü kısmında <code>List</code> veya <code>Set</code> gibi bir kap türünün belirtilmesi yeterli olacaktır.<a href="#ref2">↑</a></li>
</ol>
</div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-57357095247690797092011-11-23T19:02:00.001+02:002012-01-19T14:52:48.347+02:00Düzenli Deyimler<div id="anaMetin" style="text-align: justify;">
Bir hesaplama sürecine girdi sağlarken, kimi zaman farklı girdilerin aynı anlama geldiğini veya farklı da olsalar benzer anlamlar taşıdığını söylemek isteriz. Örneğin, bir işleme devam etmek isteyip istemediğimizin sorulması durumunda, "Hayır" yerine "H" veya "h"nin de işi görmesi bize zamandan kazandıracak ve—"hayır" yerine "hıyar" yazdığınızı düşünün—hata oranını azaltacaktır. İşte tam bu noktada, bir grup karakter katarını sahip oldukları içeriğin ortak özelliklerini temel alarak betimleyen özel bir çeşit karakter katarı şeklinde tanımlayabileceğimiz <i>düzenli deyim</i>ler işin içine girer. Bu yazımızda da yapacağımız, Java'da düzenli deyimler için sağlanan desteğe bakmak olacak.<br />
<br />
<h3 id="temelKullanım">
Temel Kullanım</h3>
<br />
Java'da düzenli deyim desteği <code>java.util.regex</code> paketindeki türler vasıtasıyla sağlanır. Bu türlerin sunduğu işlevsellikten yararlanarak, olası girdileri betimleyen düzenli deyimin, programın çalıştırılması sırasında sağlanan asıl girdi ile eşleşip eşleşmediği kontrol edilir. Bunun için yapılması gereken, düzenli deyim ile girdinin argüman olarak geçirildiği <code>Pattern.matches</code> metodunun çağrılmasıdır. Aşağıdaki kod parçasından da görülebileceği gibi, düzenli deyimin <code>String</code> olması beklenirken, girdinin <code>CharSequence</code> arayüzünü destekleyen herhangi bir sınıftan olması mümkündür.<br />
<pre class="brush:java; gutter:false" name="tanıma">import java.util.regex.Pattern;
...
String düzenliDeyim = ...;
String girdi1 = ...;
boolean tanındıMı = Pattern.matches(düzenliDeyim, girdi1);
...
StringBuilder girdi2 = new StringBuilder("...");
tanındıMı = Pattern.matches(düzenliDeyim, girdi2);
</pre>
<code>matches</code> metodunun iki özelliği bizi ikinci bir yol aramaya sevkedecektir. Öncelikle, döndürülen <code class="java-kw">boolean</code> değer, kullanıcının sağladığı girdinin düzenli deyimle uyumlu olup olmadığı konusunda fikir verir; girdinin yapısı hakkında herhangi bir bilgi edinmek olanaksızdır. Ayrıca, <code>matches</code> metodu, kendisine geçirilen düzenli deyimi içsel bir gösterime çevirdikten sonra girdinin uyumunu denetler. Bu ise, aynı düzenli deyimin birden çok kez kullanılması durumunda, içsel gösterime dönüşümün tekrar tekrar yapılmasıyla zaman kaybetmek anlamına gelir ki, çözüm <code>Pattern.compile</code> metodunun kullanımından geçer. Bu metot, kendisine geçirilen düzenli deyimi dönüştürür ve dönüşümün sonucunu tutan <i>desen</i> nesnesinin tutacağını döndürür.<sup><a href="#dn1" id="ref1">1</a></sup> İstenecek olursa, bu tutacak aracılığıyla gönderilecek iletiler, girdinin düzenli deyimle uyuşması sonrasında girdinin bileşenlerine ilişkin sorgularımızı yanıtlayabilir.<br />
<pre class="brush:java; gutter:false" name="hızlıTanıma">import java.util.regex.*;
...
String düzenliDeyim = ...;
Pattern desen = Pattern.compile(düzenliDeyim);
String girdi1 = ...;
Matcher eşleştirci = desen.matcher(girdi1);
boolean tanındıMı = eşleştirici.matches();
if (tanındıMı) ... // girdi1'in bileşenlerini keşfet
...
StringBuilder girdi2 = new StringBuilder(...);
eşleştirici = desen.matcher(girdi1);
tanındıMı = eşleştirici.matches();
if (tanındıMı) ... // girdi2'nin bileşenlerini keşfet
</pre>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguR2ubW3DVhSJJcRxqzVJF5x9mPW3QBFWnCbhoENsBL1EcMbsiqTr2SImvpd45mD0fgyP0GyeOKstAO6tsR7in2sZKFKHhXnx0App-Xrg0cEMPyId13fcuVOcyQ3L5uT2wfXTPRx1h4bYg/s1600/D%25C3%25BCzenlifadeler.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguR2ubW3DVhSJJcRxqzVJF5x9mPW3QBFWnCbhoENsBL1EcMbsiqTr2SImvpd45mD0fgyP0GyeOKstAO6tsR7in2sZKFKHhXnx0App-Xrg0cEMPyId13fcuVOcyQ3L5uT2wfXTPRx1h4bYg/s320/D%25C3%25BCzenlifadeler.png" width="320" /></a></div>
<br/>
<h3 id="içerik">
Düzenli Deyim İçeriği</h3>
<br />
Düzenli deyim tanımında, betimlenmekte olan girdinin karakterleri ile birlikte bazı özel işleçler kullanılabilir. İstenecek olursa, düzenli deyim ile girdi arasındaki eşlemenin nasıl yapıldığını görebilmek adına düzenli deyim öbek adı verilen parçalara ayrılabilir. Ayrıca, eşleştirme sürecinin büyük/küçük harf ayrımı ve dönüşümü, satır ayırma gibi konularda nasıl davranacağını belirleyen bayraklardan da yararlanılabilir.<br />
<br />
Düzenli deyim içeriğini oluşturan karakterlerin bazıları, tıpkı karakter sabitlerinde olduğu gibi, özel bir biçimde yorumlanır. Örneğin, '(' yeni bir öbek başlatırken ')' en son başlatılan öbeği kapatır. Dolayısıyla, bu karakterleri [ve diğerlerini] özel görevleri dışında sade bir karakter olarak görmek istediğimizde niyetimizi söz konusu karakterin önüne '\' koyarak belirtmemiz gerekir. Buna ek olarak, bazı sıradan karakterler, önlerine '\' konulmak suretiyle, tıpkı 'n' karakterinin '\n' haline getirilmesinde olduğu gibi, özel bir anlam kazanır. Mesela, 'd' alfabedeki bir harfe karşılık gelen karakteri temsil ederken, '\d' ondalık sayıları yazmakta kullanılan herhangi bir rakamı temsil eden karakter grubuna karşılık gelir. Dolayısıyla, "\\{\\d\\}" yegâne elemanı tek basamaklı bir ondalık sayı olan kümeleri betimler.
<br />
<pre class="brush:java; gutter:false" name="çifteYorumlama">System.out.print(Pattern.matches("\\{\\d\\}", "{3}")); // ⇒ true
</pre>
'\' karakterlerinin çokluğu başınızı döndürdü değil mi? Bunun nedeni, <b>karakter katarımızın bir kez <code>String</code> sabiti, bir kez de düzenli deyim olarak yorumlanmasıdır.</b> Yani, yukarıdaki komutun ilk argümanı önce <code>String</code> sabiti gibi yorumlanacak ve "\{\d\}" haline dönüştürüldükten sonra düzenli deyim olarak ele alınacaktır. Bu sıkıcı durum, alıntılama düzeneği ile biraz olsun düzeltilebilir. Düzenli deyimin içinde geçen \Q (İng., quote), \E (İng., end quote) görülene kadar hiçbir şeyin yorumlanmayacağını bildirir.<sup><a href="#dn2" id="ref2">2</a></sup> İstenecek olursa, kendisine geçirilen <code>String</code> nesneyi \Q ve \E ile çevreleyen <code>Pattern.quote</code> metodu da aynı amaçla kullanılabilir.<br />
<br />
Tek bir karmaşık sayı içeren kümeyi betimleyen düzenli deyim aşağıda verilmiştir. Dikkat ederseniz, düzenli deyimin baş ve son tarafındaki yorumlanmayan parçaların sınırlarını belirleyen \Q ve \E yorumlanmakta olup, sırasıyla, \\Q ve \\E şeklinde yazılmak zorundadır.
<br />
<pre class="brush:java; gutter:false" name="çifteYorumlama2">import static java.lang.System.out;
import static java.util.regex.Pattern.*;
...
String desen ="\\d\\s*(\\+|-)\\s*\\d";
out.print(
matches("\\Q{(\\E" + desen + "\\Q)}\\E", "{(3 -5i)}")); // ⇒ true
out.print(
matches(quote("{(") + desen + quote(")}"), "{(3+4i)}")); // ⇒ true
</pre>
<br />
<h3 id="karakterSınıfları">
Karakter Sınıfları</h3>
<br />
Düzenli deyim oluştururken, karakterlerin yanısıra ortak özellikleri bulunan karakterleri gruplayan <i>karakter sınıfları</i>nı da kullanabiliriz. Örneğin, Java'da kullanılabilecek tanımlayıcı adlarını betimlemek istediğimizde, _ ve tüm alfabetik karakterlerin ilk karakter olarak geçebileceğini söyleyerek bu karakterleri aynı sınıfa koymuş oluyoruz. Bu noktada, <b>karakter sınıflarının bir katarı değil tek bir karakteri tanımladığı unutulmamalıdır.</b> Dolayısıyla, tek rakamları betimleyen bir karakter sınıfının düzenli deyim tanımında kullanılması tek rakamlardan oluşan çok basamaklı bir sayının değil, {1', '3', '5', '7', '9'} kümesinin bir tek elemanının kullanılabileceği anlamını taşır.<br />
<br />
Köşeli ayraç çifti ile sınırlanan karakterler ve diğer karakter sınıflarından oluşan karakter sınıfları, içerilen karakterlerin yanyana yazılmasının yanısıra, tümleyen sınıfın dışlanması ile de tanımlanabilir. Örneğin, "[13579]" '1', '3', '5', '7' ve '9' karakterlerinden oluşan bir karakter sınıfını tanımlarken, "[^02468]" '0', '2', '4', '6' ve '8' karakterleri dışındaki tüm karakterleri kapsar. Bu noktada, ikinci örneğimizin sadece tek rakamları kapsamadığını, listelenenler dışındaki herhangi bir karakteri kapsadığının altını çizelim. Buna karşılık, rakamların ve çift sayı olmayan karakterlerin kesişimini alan "[0-9&&[^02468]]" deyimi, tümleme işleci (^) ve kesişim işlecinden (&&) yararlanarak ilk örneğimizle eşdeğer bir sonuç verir.<br />
<br />
Betimlenen karakter sınıfındaki karakterlerin ardışık olması halinde, tüm karakterleri teker teker yazmaktansa aralık işlecini (-) kullanabiliriz. Buna göre, "[abcçde12345]" yerine "[a-eç1-5]" yazmak yeterli olacaktır. [Dikkat ederseniz, aralık tanımının ASCII temelli olması nedeniyle Türkçe'ye özel 'ç' ayrıca eklenmek zorunda.] Bunun bir sonucu olarak—karakterler arasında kullanıldığında aralık işleci görevini gördüğü için—'-' sınıf içinde ancak ve ancak birinci sırada geçebilir. Dolayısıyla, temel aritmetik işleçlerin tanımı "[-+*/]", bu sınıfın tümleyeni "[^-/*+]" şeklinde yapılmalıdır.<br />
<br />
Sıklıkla kullanılan bazı karakter sınıfları önceden tanımlanmışlardır. Söz konusu karakter sınıflarının yeniden tanımlanması hem zamandan kayıp hem de hataya açık bir çabadır. Ancak, bu karakter sınıflarından bazılarının ASCII temelli tanımlandığı ve özel ayarlamalar yapılmadığı müddetçe alfabemizdeki kimi harfleri barındırmayacağı akılda tutulmalıdır.<br />
<ul>
<li>.: Unicode tablosundaki herhangi bir karakter.</li>
<li>\p{ASCII} ([\x00-\x7F]): Unicode tablosunun ASCII altkümesinde bulunan karakterler.
</li>
<li>\p{Alpha} ([a-zA-Z]): <u>ASCII</u> tablosundaki alfabetik karakterler. \p{Lower} ve \p{Upper}, sırasıyla, küçük ve büyük alfabetik karakterleri tanımlar.</li>
<li>\p{Digit} veya \d ([0-9]): Ondalık sayıların basamaklarında kullanılabilecek rakamlar. 16'lı tabandaki sayıların basamakları \p{XDigit} ile tanımlanır. Ondalık sayıların tümleyeni olan sınıf—yani, ondalık rakam olmayan karakterler—\D ile temsil edilir.</li>
<li>\p{Alnum} ([\p{Alpha}\p{Digit}]): Alfanümerik karakterler.</li>
<li>\w ([a-zA-Z0-9_]): Çoğu programlama dili tarafından tanımlayıcı adı oluşturmakta kullanılan karakterler. Tümleyen sınıf \W ile temsil edilir.</li>
<li>\p{Punct}: Noktalama imleri, ayırıcılar ve işleçler.</li>
<li>\p{Graph} ([\p{Alnum}\p{Punct}]): Fiziksel gösterimi bulunan karakterler.</li>
<li>\p{Print} ([\p{Graph}\x20]): Basılabilir karakterler. [\x20 boşluk karakterine karşılık geliyor.]</li>
<li>\p{Cntrl} ([\x00-\x1F\x7F]): Kontrol karakterleri.</li>
<li>\p{Space} veya \s ([ \t\n\x0B\f\r]): Bir metnin görünümünü düzenlemek için yararlanılan <i>beyaz boşluk</i> karakterleri.</li>
<li>\p{Blank} ([ \t]): Boşluk ve sekme karakterleri.</li>
</ul>
<br />
Yukarıdaki karakter sınıflarından ASCII tablosuna sınırlı olanların davranışı <code>UNICODE_CHARACTER_CLASS</code> bayrağı kullanılarak değiştirilebilir. Buna göre; "\p{Lower}" ile "ç" eşleşmezken, "(?U)\p{Lower}" ile "ç" eşleşecektir. Aynı etkiyi, <code>Pattern.compile</code> metoduna ikinci argüman olarak geçirilen bayraklar arasına <code>UNICODE_CHARACTER_CLASS</code> sabitini koyarak da yaratabiliriz.
<br />
<pre class="brush:java; gutter:false" name="UnicodeTablosu">import java.util.regex.Pattern;
import static java.util.regex.Pattern.*;
...
Pattern küçükHarf = compile("(?U)\\p{Lower}");
küçükHarf = compile("\\p{Lower}", UNICODE_CHARACTER_CLASS)); // Yukarıdakiyle aynı
</pre>
Unicode tablosunu kullanmanın bir diğer yolu, <code>Character</code> sınıfındaki yüklemler vasıtasıyla işini gören karakter sınıflarından yararlanmaktır. Bu karakter sınıflarının adları, <code>Character</code> sınıfının ilişkin yüklemindeki <code>is</code> öneki yerine <code>java</code> konulmasıyla oluşturulur. Örnek olarak, argümanının birbirini tamamlayan karakter çiftlerinden ((), [], {}, <>, vd.) birine ait olup olmadığını denetleyen <code>isMirrored</code> yüklemini ele alalım. Düzenli deyimimizdeki bir karakterin bu tür bir karakter ile eşleşmesini istediğimizde yapmamız gereken, <code>isMirrored</code>'ı <code>javaMirrored</code>'a çevirmek ve karakter sınıfı adı olarak kullanmaktır. Dolayısıyla, "\p{javaMirrored}" ">" veya "(" ile eşleşirken çift olarak gelmeyen diğer karakterlerle eşleşmeyecektir.
<br />
<pre class="brush:java; gutter:false" name="UnicodeTablosu">küçükHarf = compile("\\p{javaLowerCase}"); // Yukarıdakilerle aynı
Pattern çifttenBiri = compile("\\p{javaMirrored}");
</pre>
<br />
<h3 id="ddİşleçleri">
Düzenli Deyim İşleçleri</h3>
<br />
Temel düzenli deyim oluşturma işleci olan bitiştirme, iki karakterin [veya karakter öbeğinin] yanyana yazılmasıyla ifade edilip özel bir simgenin kullanımını gerektirmezken, geçiş sayısını belirtmek amacıyla farklı niceleme işleçlerinden yararlanılabilir. Karakteri takiben yazılan bu işleçlerden joker grubu olarak adlandırabileceklerimiz *, + ve ?, sırasıyla, 0 veya daha fazla sayıda, 1 veya daha fazla sayıda ve 0 veya 1 kez anlamına gelir. Yineleme sayısının kesin olması durumunda, kıvrımlı ayraç çifti ({}) arasında yazılacak bir sayı işi görecektir; yineleme sayısının alt ve üstten sınırlandırılması ise virgülle ayrılmış sınırların kıvrımlı ayraç çifti arasında verilmesiyle mümkün olurken, üst sınırın yazılmaması yinelemenin alt sınırdan az olmamak üzere belirsiz bir sayıda olabileceği anlamına gelir.<br />
<br />
Yineleme işleçlerinden +, * ve bitiştirmenin birlikte kullanımına denk olduğu için işlevsellik adına bir şey katmaz. Örneğin "a+" "aa*" şeklinde ifade edilebilir. Ancak; okunabilirliği artırması ve düzenli deyim derleyicisinin kimi eniyilemeleri yapmasını olanaklı kılması nedeniyle "a+" deyiminin kullanımı daha doğru olacaktır. Benzer gözlemler diğer işleçlerin bazı kullanımları için de yapılabilir. Mesela, aynı nedenlerden ötürü (okunabilirlik ve eniyileme) "(ab|b)" yerine—|, ayırdığı deyimler arasında uygulanan "veya" işlecine karşılık gelir—"a?b" deyiminin yeğlenmesi yerinde olacaktır.<br />
<br />
<b>Niceleyicilerin sadece en son karakteri [veya karakter öbeğini] nicelediği unutulmamalıdır.</b> Örneğin, "Ali+" "Al" ile başlayıp bir veya daha fazla sayıda 'i' ile devam eden girdileri betimler, bir veya daha fazla sayıda "Ali" değerini değil. Buna karşılık, "(Ali){2,}" şeklinde tanımlanan düzenli deyim, ayraçlar vasıtasıyla yapılan öbek tanımı sayesinde, iki veya daha fazla sayıda "Ali" değerinin geçtiği karakter katarlarını betimler.<br />
<br />
Önceki paragraflarda anlatılan niceleyicilerin işleyiş mantığı, girdide sağlanan karakterlerden mümkün olduğunca çok tüketecek şekilde bir eşleme yapmak şeklindedir. Örneğin, "birberber" sabitini başarılı bir şekilde betimleyen ".*ber" düzenli deyiminde bulunan ".*" olabildiğince çok karakter yutacak ve "birber" ile eşleşecektir. Bir diğer deyişle, düzenli deyimin sonundaki "ber" girdinin sonundaki "ber" ile eşleşecektir. Yinelemenin en az sayıda karakter yutularak yapılması için ise niceleyici sonrasına ? konulması gerekir. Mesela, "*.?ber" deyimindeki "*.?", "birberber" içindeki ilk üç karakteri tüketecektir. Yani, eşleşmenin mümkün olduğu durumlarda ilk kullanımdaki niceleyici olabildiğince çok yiyerek <i>açgözlü</i> davranırken ikincisi olabildiğince az yiyerek <i>gönülsüz</i> davranacaktır.<br />
<br />
İşleyiş ayrıntılarına girildiğinde, açgözlü niceleyicilerin bir zaafı ortaya çıkar: düşük performans. Örneğimiz üzerinden anlamaya çalışalım. Eşleştirici, "birberber" sabitinin ".*ber" düzenli deyiminin betimlediği kümede olup olmadığına karar vermek istediğinde, öncelikle ".*" ile tüm girdiyi tüketir ve "ber" kısmının eşleştirilmesi için geriye hiçbir şey kalmaz. Sonucun başarısızlık olması üzerine eşleştirici, bir karakter geriye sarar ve ".*" ile "birberbe" sabitini eşleştirerek "ber" kısmını "r" ile eşleştirmeye çalışır. Bu da olmayınca, yapılacak olan bir kere daha geriye sarmaktır. Bu sefer, ".*" "birberb" ile eşleştirilir ve arda "er" kalır. Üçüncü hüsranın sonrasında girdinin geriye sarılması ile "*." "birber", "ber" ise sondaki "ber" ile eşleşir ve bu güzel haber [dördüncü denemeyi takiben] kullanıcıya muştulanır. Geri sarmanın getireceği performans düşüklüğünün önüne geçmek, kimi zaman bir diğer niceleyici grubunun kullanılması ile mümkün olabilir: <i>sahiplenici</i> niceleyiciler. Benzer şekilde çalışan bu niceleyiciler, açgözlü eşdeğerlerinin aksine geri sarma işlemine başvurmaz ve eşleştirmenin başarısızlıkla sonlandığını ilan eder. Bundan dolayı, "birberber" ".*+ber" tarafından kabul edilmeyecektir. Çünkü, ".*+" açgözlü davranarak tüm girdiyi tüketecek ve geriye "ber" ile eşleştirilecek bir şey kalmayacaktır. Bu, geriye sarmanın olmaması ile birleştiğinde, sonucun olumsuz olacağı anlamına gelir. Yani, açgözlü niceleyiciyle eşleştirilen bazı girdiler sahiplenici niceleyiciyle eşleştirilemeyecektir. O zaman, işlev açısından eşdeğer olup bize performans açısından kazandırdıkları bir örnek görerek sahiplenici niceleyicilerin gerekliliğine ikna olalım.
<br />
<pre class="brush:java; gutter:false" name="telefonNo">Pattern.matches("\\d*+\\(Ev\\)", "123456789Ev)") // → false
</pre>
Bir numarayı ev telefonu olduğu bilgisiyle birlikte betimleyen bu düzenli deyimde, sahiplenici niceleyici (*+) yerine açgözlü uyarlamanın (*) kullanılması yarar getirmeyeceği gibi daha düşük bir performansa neden olacaktır. Çünkü, dokuz basamaklı sayıyı yedikten sonra girdide '(' arayan eşleştirici, dokuz kez geriye sarıp önceki karakterlerin hiçbirinin aradığı karakter olmadığını pahalı yoldan öğrenecektir. Yani, geriye sarma sonucunda fazladan keşfedilecek bir eşlemenin olmaması geri sarmaya tenezzül etmeyen sahiplenici niceleyiciyi öne çıkarmaktadır.<br />
<br />
<h3>
Bayraklar</h3>
<br />
<code>Pattern.compile</code> metodu, eşlemenin nasıl yapılacağını etkileyen bayraklar da alabilir. Eşleştiricinin üzerinde çalışacağı tüm girdiler için geçerli olacak bu bayraklar, istenecek olursa, benzer bilgilerin düzenli deyimin içine yerleştirilmesi suretiyle de etkinleştirilebilir veya geçersiz hale getirilebilir. <code>Pattern</code> sınıfı içinde sabit olarak tanımlanan bu bayraklar ve anlamları şöyledir.<br />
<ul>
<li>UNICODE_CASE (?u): Büyük küçük harfler arasındaki dönüşüm ve eşitlik denetimleri Unicode tablosu temel alınarak yapılır. Türkçe'yi temel alarak işlem yapmak istiyorsanız—mesela, i'den I yerine İ'ye dönüşüm yapılmasını istiyorsanız—bu bayrağı aklınızdan çıkarmamanızda yarar olacaktır.</li>
<li>CASE_INSENSITIVE (?i): Harflerin eşlenmesi sırasında büyük küçük ayrımı yapılmayacaktır. UNICODE_CASE ile birlikte kullanılmadıkça bu bayrağın etkisinin ASCII tablosundaki karakterlere sınırlı kalacağı unutulmamalıdır. </li>
<li>UNICODE_CHARACTER_CLASS (?U): Kullanılacak ASCII temelli karakter sınıflarının Unicode tablosunu temel alarak işlev görmesini sağlar. Bu bayrağın etkin kılınması ile birlikte, UNICODE_CASE bayrağının da otomatikman etkinleştirildiği akılda tutulmalıdır.</li>
<li>COMMENTS (?x): Düzenli deyim içindeki beyaz boşluklar ve '#' karakteri ile sonrasındaki satır sonuna kadar her şey göz ardı edilir.</li>
<li>MULTILINE (?m): Girdinin satır ayırıcı karakter(ler)inin (Unix temelli işletim dizgelerinde yeni satır karakteri ('\n'), Microsoft işletim dizgelerinde satır başı ('\r') ve yeni satır karakterleri) olduğu yerlerden ayırarak satırlar halinde incelenmesini sağlar. Buna göre, <code>^</code> ve <code>$</code> artık girdinin başı ve sonunu değil, incelenmekte olan satırın başı ve sonunu belirtecektir.</li>
<li>UNIX_LINES (?d): Satırların Unix temelli işletim dizgelerinde olduğu gibi yeni satır karakteri ile sonlandığı varsayılacaktır.</li>
<li>DOTALL (?s): "." deyiminin satır ayırıcıları da betimlemesini sağlar. Bu, satır ayırıcılarının da ".*" ile yutulacağı anlamına gelir ve bu sebepten dolayı kimi zaman tek satır kipi olarak da adlandırılır.</li>
<li>LITERAL: Eşleştiricinin karakter katarını yorumlamayacağını bildiren bu bayrağın etkisi, düzenli deyimin "\Q"-"\E" çiftiyle çevrelenmesi yoluyla da elde edilebilir.</li>
<li>CANON_EQ: Unicode tablosunda birden çok nokta ile temsil edilen veya birden çok karakterin bileştirilmesi ile de oluşturulabilecek karakterlerin değişik karşılıklarının birbirine eşit olmasını sağlar. Örneğin, 231 (0xE7) nolu konumdaki ç harfi, c harfine kanca iminin (\u0327) monte edilmesiyle de oluşturulabilir.
<pre class="brush:java; gutter:false" name="telefonNo">Pattern desen = compile("c\u0327", CANON_EQ);
out.println(desen.matcher("ç").matches()); // ⇒ true
</pre>
Dikkat edecek olursanız, \u önekiyle sağlanan karakterlerin, <code>String</code> sabitinin oluşturulması sırasında içselleştirilmesi nedeniyle, önüne ikinci bir \ konulmasına gerek yoktur.</li>
</ul>
<br />
Bayrakların düzenli deyim içinde belirtilmesi durumunda, birden çok sayıda bayrak aynı noktada etkinleştirilebileceği gibi, istenen bayraklar geçersiz de kılınabilir. Örneğin, (?Ui) betimlemenin Unicode tablosunu temel alarak büyük-küçük farkı gözetmeksizin yapılacağını ifade ederken, (?-d) [belki de daha önce etkinleştirilmiş olan] Unix usulü satır ayırıcı bayrağını o anki noktadan itibaren geçersiz hale getirmektedir.<br />
<br />
<h3 id="öbekler">
Öbekler</h3>
<br />
Kimi zaman, düzenli deyimle girdi arasında bir eşleşmenin olup olmamasının yanısıra, eşleşme ile ilgili kimi ayrıntıları da öğrenmek isteriz. Örneğin, telefon numaralarını betimleyen bir düzenli deyimde, alan kodu ve numara ile ayrı ayrı ilgileniyor olabiliriz. Bu gibi bir durumda yapmamız gereken, düzenli deyimi karakter öbeklerine ayırmak ve eşleme sonrasında bu öbekleri sorgulamak olmalıdır.<br />
<br />
Öbekler, ilgilenilen karakterlerin ayraç çifti arasına alınması ile oluşturulur. Eşleştirme sonrasında atıfta bulunulabilmesi için, öbekler <u>açış</u> ayraçlarının düzenli deyimdeki geçiş sırasına göre numaralandırılırlar. Örneğin, genç kızlık soyadını koruyarak adını yazan bayanların adları "(\p{Alpha}+)\s((\p{Alpha}+)-(\p{Alpha}+))" ile betimlenebilir. [Düzenli deyimi sınamak istediğinizde, \ yerine \\ koymayı unutmayınız.] Bu düzenli deyim, ad ile başlayıp bir boşlukla devam eden ve birbirinden - ile ayrılmış iki soyadını tanımlamaktadır. Ad ilk öbekle eşleşirken, tüm soyadı ikinci, eşin soyadı üçüncü, ve nihayet, genç kızlık soyadı dördüncü öbek olarak eşleştirilecektir.<br />
<br />
Oluşturulan bir öbeği atıfta bulunarak kullanmak istediğimizde, bunu geçiş sırasının önüne \ koyarak sağlayabiliriz; \0 her zaman girdinin tümü ile eşleştirilir. İlk üç rakamın alan kodu ile aynı olduğu telefon numaralarını betimleyerek buna bir örnek verelim: "(\d{3})-(\1\d{4})". İlk öbeği alan kodu, ikinci öbeği telefon numarası ile eşleştiren bu düzenli deyim, "532-5321234" numarasını betimlerken "232-5321234" numarasını betimlemeyecektir.<br />
<br />
İstediğimiz takdirde, öbeklere kendilerine verilen ad ile de atıfta bulunulabilir. Bunun için, öbeğe uygun görülen adın üçgen ayraç çifti arasına alınıp adlı öbeği başlatan (? sonrasına yazılması gerekir; öbeğe atıfta bulunmak istenmesi durumunda ise, öbek adının üçgen ayraçlarla birlikte \k'ye eklenmesi yeterli olacaktır. Buna göre, telefon numarası örneğimiz şöyle de yazılabilir: "(?<kod>\d{3})-(\k<kod>\d{4})". Bu arada; bir öbeğe ad verilmesi, söz konusu öbeğin geçiş sırasının geçersiz olduğu anlamına gelmez. İsteyecek olursak, düzenli deyimimizi "(?<kod>\d{3})-(\1\d{4})" olarak da tanımlayabiliriz.<br />
<br />
Kimi zaman, eşleştirilen bir öbeği göz ardı etmek isteyebiliriz. Yani; eşleşmenin başarılı olması sonrasında söz konusu öbeğin hesaba katılmasını istemeyebiliriz. Bunun için yapmamız gereken şey, öbeğin (?: ile başlatılmasından ibarettir. Bu durumda, göz ardı edilen öbeğin açış ayracı öbek sırasını saptamakta yararlanılan sayacı etkilemeyecektir. Son olarak, şu nokta da unutulmamalıdır: adlandırılan öbekler göz ardı edilemezler.<br />
<br />
<h3>
Eşleştirici Kipleri</h3>
<br />
Java'daki düzenli deyim desteği, <code>Pattern</code> ve <code>Matcher</code> sınıflarındaki <code>matches</code> metotlarında gerçekleştirilenden farklı eşleştiriciler de sağlar. Bunlardan <code>Pattern</code> sınıfındaki <code>split</code> iletisi, argümanındaki <code>CharSequence</code> kategorisine ait katarı, ileti alıcının temsil ettiği deseni kullanarak <code>String</code> nesnelerine böler ve bu <code>String</code>'leri içeren diziyi döndürür.
<br />
<pre class="brush:java; gutter:false" name="splitÖrneği">...
Pattern desen = Pattern.compile(":");
String[] bilgi = desen.split("Gökçe Begüm:Ege:532-1234567");
// bilgi[0] ← "Gökçe Begüm", bilgi[1] ← "Ege", bilgi[2] ← "532-1234567"
</pre>
İstenecek olursa, <code>split</code>'e sağlanacak ikinci argüman ile döndürülecek dizinin eleman sayısı sınırlandırılabilir. Örneğin, yukarıdaki kullanımda ikinci argüman olarak 2 geçirilmesi, ilki "Gökçe Begüm" ikincisi "Ege:532-1234567" değerine sahip iki elemanlı bir dizi döndürecektir.<br />
<br />
Göz atacağımız diğer eşleştiriciler marifetlerini <code>Matcher</code> nesnelerine gönderilen iletiler yoluyla gösterirler. Dolayısıyla, düzenli deyimin <code>compile</code> metodu ile derlenmesi sonucu elde edilen desene (<code>Pattern</code> nesnesi) <code>matcher</code> iletisinin gönderilmesi yapacağımız şeylerin başında gelmelidir. İkinci adım olan eşleştiricinin çağrılması öncesinde, işlemin etkili olacağı girdi <i>bölge</i>si <code>region</code> iletisi ile belirtilebilir. [Daha sonra eşleştiricimizin girdinin hangi bölümünü ele aldığını görmek istersek, ilişkin bölgenin tanımlanmasında kullanılan argüman değerlerini döndüren <code>regionStart</code> ve <code>regionEnd</code> iletilerinden yararlanabiliriz.] Son adım olarak ise, desen ile girdinin uyuşması halinde icra edilecek bir inceleme aşaması vardır. Bu, <code>String</code> argümanlı <code>group</code> iletisinin yanısıra <code>MatchResult</code> arayüzünde yer alan iletilerin eşleştiriciye gönderilmesi ile yapılablir. Dolayısıyla, tipik eşleştirici kullanımı aşağıdaki şablonu takip edecektir.
<br />
<pre class="brush:java; gutter:false" name="eşleştiriciŞablonu">import java.util.regex.*;
...
Pattern desen = Pattern.compile(...);
Matcher eşleştirici = desen.matcher(...);
eşleştirici.region(başİndis, sonİndis + 1); // Seçimli
... // Eşleştiriciyi uygun bir kipte kullan.
if (tanındıMı) {
MatchResult sonuç = eşleştirici.toMatchResult();
... // sonuç'u incele
}
</pre>
Eşleştiricinin yaratılması sonrasında girdinin ele alınması noktasında farklı çalışma kiplerini temsil eden üç yüklemden bahsedebiliriz: <code>matches</code>, <code>lookingAt</code>, <code>find</code>. Önceki örneklerimizden de gördüğümüz gibi <code>matches</code>, girdi ile [düzenli deyimin içselleştirilmiş karşılığı olan] deseni eşleştirirken girdinin tümünü tüketmeye çalışır; aksi takdirde, eşleştirme sonucu olumsuz olacaktır. Bundan dolayıdır ki, ".*ber" "birberbere" sabitini tanımaz. Çünkü, ".*" ile "birber" ve "ber" ile girdideki "ber" eşleşmesini takiben sondaki "e" açıkta kalır ki, bu <code>matches</code> metodundan <code class="java-kw">false</code> döndürülmesine neden olur. Artık girdinin eşleşmeyi engellemesi istenmiyorsa, <code>matches</code> yerine <code>lookingAt</code> iletisi kullanılmalıdır. <code>matches</code>'da olduğu gibi, girdinin ilgilenilen bölgesinin başından itibaren eşleştirerek işini gören <code>lookingAt</code>, girdinin sonunda eşleşme ile kapsanmayan karakterlerin kalmasına itiraz etmez ve <code class="java-kw">true</code> döndürür. Dolayısıyla, bu eşleştirici kipinde ".*ber" "birberbere" sabitini betimliyor kabul edilecektir. Ne var ki, <code>lookingAt</code> iletisi de, tıpkı <code>matches</code> gibi, girdi başının desenle uyuşmaması durumunda devamında ne olursa olsun <code class="java-kw">false</code> döndürür. Örneğin "a.*b", ne <code>matches</code> ne de <code>lookingAt</code> ile kullanıldığında, "qawdesb" girdisini betimlemeyecektir. Çünkü, her iki eşleştirici kipi de düzenleyici deyim başındaki 'a' değerini girdinin en başında arayacak ve başarısızlık sonrasında <code class="java-kw">false</code> döndürecektir. Sıkıntımızın çözümü, eşleştiriciyi <code>find</code> kipiyle kullanmakta yatar. Seçimli bir <code class="java-kw">int</code> argüman bekleyen bu eşleştirici kipi, girdinin başındaki eşleşmeyen karakterleri atlar ve deseni girdi içinde arar; girdinin başındaki ve sonundaki eşleşmeyen parçalar sonucun olumsuz olmasına neden olmaz. Dolayısıyla, "a.*b" düzenli deyiminin "qawdesbc" içinde aranması, baştaki "q" değerini göz ardı ettikten sonra, "awsdesb" ile eşlemeyi sağlayacak ve sonda artan "c" değerine rağmen <code class="java-kw">true</code> döndürecektir.<br />
<br />
Eşleştirici kipleri arasındaki bir diğer fark, daha önceden eşleştirilmiş desen-girdi çiftinin sıfırlanmaksızın tekrar kullanılması durumunda sergilediği davranış biçimidir. Girdinin tümünü eşleştirmeye çalışan <code>matches</code>, girdiyi tüketmiş olduğu için ikinci ve sonraki kullanımlarında her zaman <code class="java-kw">false</code> döndürürken, <code>lookingAt</code> her zaman ilk kullanımda döndürdüğü sonucu döndürür. Dolayısıyla, aşağıdaki kod parçası sonsuz döngü içinde standart çıktı ortamına ba yazmaktan başka bir şey yapmayacaktır. [<code>MatchResult</code> arayüzü iletilerinden olan <code>group</code>, argümanında geçirilen sıradaki öbeği döndürür; 0 eşleştirilen tüm girdi parçasına karşılık gelir ve aynı etki <code>group</code> iletisini argümansız kullanmakla da yaratılabilir.]
<br />
<pre class="brush:java; gutter:false" name="çokluKullanım">String düzenliDeyim = "ba", girdi = "baba";
Pattern desen = Pattern.compile(düzenliDeyim);
Matcher e = desen.matcher(girdi);
while (e.lookingAt())
System.out.println(e.group(0));
</pre>
Bu durumun önüne geçilmesi <code>lookingAt</code> iletisinin üzerinde çalıştığı bölgenin aşağıdaki gibi değiştirilmesi ile mümkündür. Argümansız <code>end</code> iletisi, en son eşleştirmenin tükettiği son karakterin ötesindeki ilk karakterin indisini döndürür; öbek sayısını belirten bir tamsayının geçirilmesi halinde ise, aynı ileti belirtilen öbeğin sonunu takip eden karakterin indisini döndürür. [<code>e.end()</code> gönderisinin etkisi, <code>e.start() + e.group().length()</code> ile de sağlanabilir.]
<br />
<pre class="brush:java; gutter:false" name="çokluKullanım2">while (e.lookingAt()) {
System.out.println(e.group(0));
e.region(e.end(), girdi.length());
}
</pre>
<code>find</code> iletisi ise, başarılı eşleştirmenin ardından girdinin eşleşme sonrasındaki karakterinden devam ederek işini görür. Buna göre, yukarıdaki kod parçası şöyle de yazılabilir.
<br />
<pre class="brush:java; gutter:false" name="çokluKullanım3">while (e.find())
System.out.println(e.group(0));
</pre>
Girdinin aynı veya farklı bölgelerinin farklı desenler kullanılarak eşleştirilmesi için, girdiyi parçalamak ve farklı eşleştiriciler kullanmaktansa, <code>usePattern</code> iletisi tercih edilmelidir. Bu iletinin kullanıldığı noktadan itibaren, ileti alıcı konumundaki eşleştirici girdinin sonuna veya bir sonraki <code>usePattern</code> kullanımına kadar tüm eşleştirmeleri söz konusu iletiye geçirilen deseni kullanarak yapacaktır.
<br />
<pre class="brush:java; gutter:false" name="desenDeğiştirme">String düzenliDeyim = "ab", girdi = "abba";
Pattern desen = Pattern.compile(düzenliDeyim);
Matcher e = desen.matcher(girdi);
if (e.find()) {
System.out.println(e.group(0));
e.usePattern(Pattern.compile("ba"));
if (e.find())
System.out.println(e.group());
}
</pre>
Aynı eşleştiricinin farklı bir desenle kullanılmasını olanaklı kılan bir diğer ileti, <code>CharSequence</code> kategorisindeki yeni düzenli deyimi argüman olarak bekleyen <code>reset</code>'tir. <code>usePattern</code>'dan farklı olarak bu ileti, eşleştiriciyi girdinin başına konumlandırarak bölge tanımlarını geçersiz kılar. Bu işlemin desen değiştirilmeden yapılması için <code>reset</code> iletisinin argümansız uyarlamasının kullanılması yeterli olacaktır.<br />
<br />
Eşleştiricilerin kimi kullanım desenleri, yüksek kullanım potansiyelleri nedeniyle <code>Matcher</code> sınıfında gerçekleştirilen iletiler halinde desteklenirler. Bunlardan biri olan <code>appendReplacement</code>, daha ziyade <code>find</code> ile birlikte kullanılır ve girdinin eşleşmeyen kısmını ilk argümanındaki <code>StringBuffer</code> türlü karakter tamponuna olduğu gibi eklerken, eşleştirilen parça yerine ikinci argümandaki <code>String</code>'i koyar. Bu iletiyi tamamlayan <code>appendTail</code> ise, başarısız bir eşleştirme çabası sonrasında girdinin ilişkin parçasını argümanındaki karakter tamponunun sonuna ekler.
<br />
<pre class="brush:java; gutter:false" name="desenDeğiştirme">String düzenliDeyim = "k", girdi = "bakbakbakşuna";
Pattern desen = Pattern.compile(düzenliDeyim);
Matcher e = desen.matcher(girdi);
StringBuffer tampon = new StringBuffer();
while (e.find())
e.appendReplacement(tampon, "h");
String sonuç = e.appendTail(tampon).toString();
</pre>
Buna göre, yukarıdaki döngünün ilk dönüşündeki eşleştirme ilk iki karakteri atlayacak ve desenimizi üçüncü konumdaki "k" ile eşleştirecektir. Sonuç olarak, <code>tampon</code> ile gösterilen bölgeye atlanan parça ("ba") değiştirilmeden, eşleştirilen parça ("k") ise "h" olarak eklenecektir. Dolayısıyla, ilk döngünün sonuna gelindiğinde <code>tampon</code> değişkeninde "bah" biriktirilmiş olacaktır. Döngünün ikinci ve üçüncü dönüşlerinde de benzer şekilde çalışan kod parçası, tamponda biriktirilen değeri "bahbahbah" haline dönüştürecektir. Dördüncü dönüşte, deseni "şuna" ile eşleştirmeye çalışan <code>find</code> olumsuz sonuç döndürecek ve döngüden çıkılacaktır. Bunu takiben gönderilen <code>appendTail</code> iletisi ise eşleştirilemeyen girdiyi tampon sonuna ekleyerek işi tamamlayacaktır.<br />
<br />
Bir önceki paragrafta anlatılan değiştirme işlemi, <code>replaceAll</code> iletisi ile de yapılabilir. Anılan ileti, desenin eşleştirildiği girdi bölümlerini argümanında geçirilen <code>String</code> ile değiştirir. Benzer bir işlev gören <code>repeatFirst</code> ise, değişikliği eşleşmenin olduğu ilk noktada yapmakla yetinir.
<br />
<pre class="brush:java; gutter:false" name="desenDeğiştirme2">String düzenliDeyim = "k", girdi = "bakbakbakşuna";
Pattern desen = Pattern.compile(düzenliDeyim);
Matcher e = desen.matcher(girdi);
String sonuç = e.replaceAll("h"); // sonuç ← "bahbahbahşuna"
</pre>
</div>
<div style="text-align: right;">
<a alt="Java'da karakter katarı sınıfları-String, StringBuffer, StringBuilder" href="http://ta-java.blogspot.com/2011/09/karakter-katar-snflar.html">Karakter Katarı Sınıfları</a></div>
<hr />
<div id="dipnotlar" style="text-align: justify;">
<ol>
<li id="dn1"> İki yöntem ile bir programın doğrudan yorumlanması ve Bytecode gibi bir aradile çevrildikten sonra yorumlanması arasında koşutluk kurabiliriz. <a href="#ref1">↑</a></li>
<li id="dn2"> Bu uygulama, ifadenin konuşmacı tarafından yorum katılmadan aktarıldığını ifade eden dilimizdeki "Başbakan aynen şöyle dedi: ...", İngilizce'deki "The Prime Minister said, quote ... end quote" kalıplarına benzetilebilir. <a href="#ref2">↑</a></li>
</ol>
</div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-13127929630785086912011-11-10T18:15:00.001+02:002011-12-14T15:19:03.902+02:00Soysallık<div id="anaMetin" style="text-align:justify">
Kalıtlamanın getirilerinden biri, sınıflar arası ortak yönlerin bir üstsınıfta toplanmasına olanak tanıyarak kodun yeniden kullanımını sağlamasıdır.<sup><a id="ref1" href="#dn1">1</a></sup> Bu sayede, değişikliğin gerektiği durumlarda birçok sınıfta değişiklik yapmaktansa üstsınıftaki kodun değiştirilmesi ile işin daha kısa sürede ve daha düşük hata oranlı bir biçimde yapılması mümkün olacaktır. Ne var ki, aynı yöntemin değişik türden verileri tutmada yararlanılan veri kaplarının gerçekleştiriminde kullanılması derleme zamanında denetlenebilecek kimi hataların çalışma zamanına kaymasına neden olmaktadır ki, bu piyasaya çıkmış bir yazılımın müşterinin "hatalı" kullanımı sonrasında göçebileceği anlamına gelir. Ne kastettiğimizi son giren ilk çıkar mantığıyla çalışan yığıt veri yapısının aşağıdaki gerçekleştirimi üzerinden görelim.<br/><br/>
<div style="color: #444444; text-align: center;">YığıtBoşDurumu.java</div>
<pre class="brush:java; gutter:false" name="YığıtBoşDurumu">package vy.ayrıksıdurumlar;
public class YığıtBoşDurumu extends RuntimeException { ... }
</pre>
<div style="color: #444444; text-align: center;">IYığıt.java</div>
<pre class="brush:java; gutter:false" name="YığıtArayüzü">package vy.arayüzler;
import vy.ayrıksıdurumlar.YığıtBoşDurumu;
public interface IYığıt {
Object çıkar() throws YığıtBoşDurumu;
void ekle(Object yeniElm);
Object gözAt() throws YığıtBoşDurumu;
boolean boşMu();
} // IYığıt arayüzünün sonu
</pre>
<div style="color: #444444; text-align: center;">Yığıt.java</div>
<pre class="brush:java; gutter:false" name="YığıtGerçekleştirimi">package vy;
import java.util.Vector;
import vy.ayrıksıdurumlar.YığıtBoşDurumu;
import vy.arayüzler.IYığıt;
public class Yığıt implements IYığıt {
public Yığıt() { _kap = new Vector(); }
...
public Object çıkar() throws YığıtBoşDurumu {
if (boşMu()) throw new YığıtBoşDurumu();
Object üstteki = _kap.get(_kap.size() - 1);
_kap.remove(_kap.size() - 1);
return üstteki;
} // Object çıkar() sonu
...
private Vector _kap;
} // Yığıt sınıfının sonu
</pre>
Ekleme ve çıkarmanın elemanları tutan kabın aynı ucuna—mesela, <code>Vector</code> sonu veya <code>LinkedList</code> başı—yapılarak gerçekleştirilebilecek yığıt, söz konusu işlemlerin ilişkin arayüz tanımında da belirtildiği üzre <code>Object</code> tutacakları ile işlerini görmeleri nedeniyle herhangi türden bir nesneyi tutabilecektir. Buna göre, <code>Yığıt</code> sınıfının nesneleri yeri geldiğinde <code>Öğrenci</code> nesneleri tutarken, yeri geldiğinde <code>Öğretmen</code> nesneleri de tutabilecektir. Ancak, dikkat etmediğimiz takdirde aşağıda olduğu gibi bir durumun ortaya çıkması da olanaklıdır.
<pre class="brush:java; gutter:false" name="YığıtKullanımı">public static void yığıtıKullan() {
IYığıt sınıf = new Yığıt();
sınıf.ekle(new Öğrenci(...));
Öğrenci ilkÖğrenci = (Öğrenci) sınıf.gözAt();
if (Math.random() > Math.random())
sınıf.ekle(new Öğrenci(...));
else sınıf.ekle(new Öğretmen(...));
...
Öğrenci sonÖğrenci = (Öğrenci) sınıf.çıkar();
...
} // void yığıtıKullan() sonu
</pre>
Yığıt tanımımız <code>Object</code> tutacağı ile gösterilebilen türden—yani, Java'daki bileşke türlerin tümü—nesne tutabileceği için elemanların türdeş olma garantisi derleyici tarafından denetlenemez. Her ikisi de eninde sonunda <code>Object</code>'ten kalıtladığı için, aynı yığıta <code>Öğrenci</code> nesnesi de <code>Öğretmen</code> nesnesi de eklenebilmektedir. Bu ise, yukarıdaki kod parçasının son satırında olduğu gibi türdeşlik garantisinden hareketle yazılan satırların programın çalıştırılması sırasında <code>ClassCastException</code> ayrıksı durumuna neden olması demektir. Yani, yığıtımız ekleme sırasında itiraz etmediği nesnenin geri döndürülmesi sırasında kullanıcının kodunu göçertmektedir. Bunun önüne geçmek ancak öngörülen türlerin tümü için ayrı yığıt gerçekleştirimlerinin sağlanması ile olanaklıdır. Bu ise aşağıdaki gibi gereğinden kalabalık ve bakımı zor bir sınıf sıradüzeni anlamına gelir.<br/><br/>
<ul>
<li><code>Object</code></li>
<ul><li><code>Kişi</code></li>
<ul><li><code>Çalışan</code></li>
<ul><li><code>Öğretmen</code></li>
<li><code>Müstahdem</code></li></ul>
<li><code>Öğrenci</code></li></ul>
<li><code>Yığıt_Object</code>, <code>Yığıt_Kişi</code>, <code>Yığıt_Çalışan</code>, <code>Yığıt_Öğrenci</code>, <code>Yığıt_Öğretmen</code>, <code>Yığıt_Müstahdem</code>, ... </li></ul>
</ul><br/>
Dilimizde iki ucu gaytalı değnek şeklinde nitelenen bu durum, J2SE 5.0 sürümüne kadar geçerli olmuş ve anılan sürüm ile birlikte Java diline eklenen <i>soysallık</i> yoluyla ortadan kaldırılabilmiştir. Bu özellik sayesinde, olası biçimlendirme hatalarının derleme zamanında önüne geçilerek tür güvenliği sağlanmış ve tek bir tür tanımının kullanılması ile kod bakımı kolaylaştırılmıştir. Soysallıktan yararlanarak oluşturulan sıradüzeni aşağıdaki gibi olacaktir.<br/><br/>
<ul>
<li><code>Object</code></li>
<ul><li><code>Kişi</code></li>
<ul><li><code>Çalışan</code></li>
<ul><li><code>Öğretmen</code></li>
<li><code>Müstahdem</code></li></ul>
<li><code>Öğrenci</code></li></ul>
<li><code>Yığıt<E></code></li></ul>
</ul><br/>
<h3>Soysal Türlerin Tanımı ve Kullanımı</h3><br/>
<i>Soysal tür</i>lerin tanımı ve kullanımı metotları andırır. Nasıl ki, metotların hangi türden değerler geçirilerek işletilebileceğini belirtmek için ayraç çifti arasında belirtilen parametre listesi kullanılır, soysal türlerin hangi türler için parametrize edildiğini belirtmek için üçgen ayraç çifti arasında belirtilen <i>tür parametre listesi</i>nden yararlanılır. Buna göre, yukarıdaki arayüz ve sınıfın soysal uyarlaması aşağıdaki gibi olacaktır.<br/><br/>
<div style="color: #444444; text-align: center;">IYığıt.java</div>
<pre class="brush:java; gutter:false" name="SoysalYığıtArayüzü">package vy.arayüzler;
import vy.ayrıksıdurumlar.YığıtBoşDurumu;
public interface IYığıt<E> {
E çıkar() throws YığıtBoşDurumu;
void ekle(E yeniElm);
E gözAt() throws YığıtBoşDurumu;
boolean boşMu();
} // IYığıt<E> arayüzünün sonu
</pre>
<div style="color: #444444; text-align: center;">Yığıt.java</div>
<pre class="brush:java; gutter:false" name="SoysalYığıtGerçekleştirimi">package vy;
import java.util.Vector;
import vy.ayrıksıdurumlar.YığıtBoşDurumu;
import vy.arayüzler.IYığıt;
public class Yığıt<E> implements IYığıt<E> {
public Yığıt() { _kap = new Vector<E>(); }
...
public E çıkar() throws YığıtBoşDurumu {
if (boşMu()) throw new YığıtBoşDurumu();
E üstteki = _kap.get(_kap.size() - 1);
_kap.remove(_kap.size() - 1);
return üstteki;
} // E çıkar() sonu
...
private Vector<E> _kap;
} // Yığıt<E> sınıfının sonu
</pre>
Bir soysal türün nesnesinin yaratılması, tür parametrelerine karşılık gelen <i>tür argümanları</i>nın soysal türün adının ardından sağlanmasıyla mümkün olur. Bunun sonucunda soysal türün örneği olarak kullanılacak türe <i>parametreli tür</i> denir. Örneğin, aşağıdaki kod parçasında <code>IYığıt<Öğrenci></code> parametreli türüne sahip <code>sınıf</code> değişkeni <code>Yığıt<Öğrenci></code> parametreli sınıfına ait bir kabı göstermektedir ve bu kap <code>Öğrenci</code> tutacakları—dolayısıyla, <code>Öğrenci</code> köklü sıradüzenindaki sınıfların türünden nesneler—içerecektir.<sup><a id="ref2" href="#dn2">2</a></sup> Ayrıca, dikkat ederseniz, tür parametresi ile belirtilen dönüş türlerine sahip iletiler/metotlar (<code>gözAt</code>, <code>çıkar</code>) biçimlendirme olmaksızın kullanılmaktadır; zira, biçimlendirme derleyici tarafından eklenen kod sayesinde otomatikman yapılmaktadır.
<pre class="brush:java; gutter:false" name="SoysalYığıtKullanımı">public static void yığıtıKullan() {
IYığıt<Öğrenci> sınıf = new Yığıt<>();
sınıf.ekle(new Öğrenci(...));
// Biçimlendirmeye gerek yok
Öğrenci ilkÖğrenci = sınıf.gözAt();
if (Math.random() > Math.random())
sınıf.ekle(new Öğrenci(...));
else sınıf.ekle(new Öğretmen(...)); // Derleme hatası!!!
...
Öğrenci öğr = sınıf.çıkar();
...
} // void yığıtıKullan() sonu
</pre>
Bu noktada, tür argümanlarının bileşke türlü olmak zorunluluğu hatırlatılmalıdır. Dolayısıyla, <code>IYığıt<<b>int</b>> tamsayılar = <b>new</b> Yığıt<>();</code> şeklinde bir tanımın yapılması derleyici tarafından kabul görmeyecektir. Ancak bu, parametreli sınıflara ait kaplara ilkel türlü değerlerin konulamayacağı anlamına gelmez. Yapılması gereken, kabı söz konusu ilkel türün karşılığındaki sarmalayıcı tür ile ilan edip işin gerisini derleyiciye bırakmaktır. Derleyici, eklenmek istenen ilkel türlü değeri usulca sarmalarken (İng., boxing, wrapping) döndürülen tutacağın gösterdiği nesneyi açarak (İng., unboxing) içeriği ilkel değere dönüştürecektir.
<pre class="brush:java; gutter:false" name="İlkelTürlüSoysalKapKullanımı">IYığıt<Integer> tamsayılar = new Yığıt<>();
tamsayılar.ekle(3); // Aşağıdaki ile aynı
tamsayılar.ekle(new Integer(3));
...
int üstteki = tamsayılar.çıkar();
// ≡ int üstteki = tamsayılar.çıkar.intValue();
</pre>
Gerektiği takdirde, tür parametresine sınır getirerek kullanım noktasında geçirilmesi beklenen türlerin kısıtlanması sağlanabilir. Örnek olarak, sadece <code>Number</code> köklü sıradüzenindeki sınıfların nesnelerini içerebilecek bir kap düşünün. Bu istem, aşağıdaki sınıf başlığında olduğu gibi, <code>Number</code> sınıfının soysal türün parametresine üst sınır olarak getirilmesiyle ifade edilir. Böylece, <code>SayıKabı</code> soysal sınıfına üye parametreli sınıfların tür argümanı <code>Number</code> ya da <code>Number</code>'dan kalıtlayan sınıflara sınırlı olacaktır; derleyici diğer kullanımları hatalı kabul edecektir. Buna göre, <code>SayıKabı<Byte></code> ve <code>SayıKabı<Float></code> kabul görürken, <code>SayıKabı<Object></code> ve <code>SayıKabı<Character></code> reddedilecektir.
<pre class="brush:java; gutter:false" name="SınırlıTürParametreleri">public class SayıKabı<E extends Number> { ... }
</pre>
İstendiği takdirde, tür parametreleri gerçekleştirilmesi beklenen bir arayüz ile de sınırlandırılabilir. Hatta, sınıf adı ve birden çok arayüz adı birlikte verilerek de sınır konulabilir. Mesela, aşağıdaki soysal türün kullanımı noktasındaki kabul edilebilir tür argümanlarının <code>Snf</code>'den kalıtlaması ve <code>Aryz1</code> ile <code>Aryz2</code> arayüzlerini gerçekleştirmesi zorunludur.
<pre class="brush:java; gutter:false" name="SınırlıTürParametreleri2">public class SoysalTür<E extends Snf & Aryz1 & Aryz2> { ... }
</pre>
<br/>
<h3>J2SE 5.0 Öncesi Kod İle Birliktelik: Ham Türler</h3><br/>
Java'daki soysal türler, J2SE 5.0 sürümüne dek geçen dokuz yıla yakın sürede üretilen kodun kullanılmasını sağlamak adına <i>ham tür</i>leri destekler. Bu desteğin amacı, hali hazırda var olan milyonlarca satırın çöpe gitmesini önlemek ve söz konusu soysallık öncesi kodun zaman içinde dönüştürülmesini sağlamaktır.<br/>
<br/>Ham türlü tanımlayıcılar, soysal bir türün üçgen ayraç çifti ve tür argümanları olmaksızın, yani J2SE 5.0 öncesindeki gibi, kullanılması ile tanımlanır. <b>Soysal türü tür parametrelerine <code>Object</code> geçiriliyormuş gibi kullanmaya denk olan bu kullanım, her şey eninde sonunda <code>Object</code> tutacağı yoluyla görülebileceği için, derleyicinin kullanım hatalarını denetleme yeteneğini ortadan kaldırır. Dolayısıyla, soysallık sonrası yazılan kod içinde ham türlerin kullanımından kaçınılmalıdır.</b><br/>
<br/>Ham türlerin kullanımı iki durumda zorunludur: i) Yeni kod içinden soysallık öncesi kod kullanıldığında, ii) eski kod içinden soysal kod kullanıldığında. Örneğimiz ile devam ederek iki duruma da bir bakalım. Varsayalım ki, soysallık öncesinde <code>IYığıt</code> ve <code>Yığıt</code> türlerini tanımladık ve sınadıktan sonra kullanıma sunduk. Bu türler, soysallığa dair tür parametreleri olmadığı için, tür argümanları geçirilerek kullanılamaz; kullanım, ister soysallık öncesi kod içinden olsun isterse soysallık sonrası kod içinden, aşağıdaki gibi olacaktır.
<pre class="brush:java; gutter:false" name="HamTürKullanımı">public static yığıtıKullan() {
IYığıt sınıf = new Yığıt();
sınıf.ekle(new Öğrenci(...));
Öğrenci ilkÖğrenci = (Öğrenci) sınıf.gözAt();
if (Math.random() > Math.random())
sınıf.ekle(new Öğrenci(...));
else sınıf.ekle(new Öğretmen(...)); // Derleme hatası vermez!
...
Öğrenci sonÖğrenci = (Öğrenci) sınıf.çıkar();
...
} // void yığıtıKullan() sonu
</pre>
Soysallık sonrası yazılmış olan kullanıcı kodun derlenmesi hataya sebep olmamakla birlikte, derleyicinin, bu çeşit bir kullanımı potansiyel çalışma zamanı hatalarına davet çıkarmak olarak görmesi nedeniyle, denetlenemeyen olası hata uyarısını (İng., unchecked warning) vermesine yol açacaktır. Yani, soysallık öncesindeki gibi sessiz kalmaktansa, derleyici olası hataya işaret etmekte ve bizden ya kullanıcı kodunu değiştirerek duruma açıklık getirmemizi ya da elimizin altındaysa hem kullanılan kodu hem de kullanıcı kodunu soysallık sonrası standartlarına getirmemizi istemektedir. Kullanılan kodun elimizin altında olmaması halinde, iki şey yapılabilir: i) Kullanılan kod soysal değilse, kodumuz içinde kullanımımızın doğru olduğuna dair derleyiciye garanti veririz, ii) kullanılan kod soysalsa soysal türü kullanma niyetimizi belirten tür argümanlarını kullanırız. İlk şık, aşağıdaki şekilde <code>SuppressWarnings</code> açımlamasıyla yerine getirilebileceği gibi derleyiciye geçirilecek -Xlint:-unchecked opsiyonu ile de yerine getirilebilir. Her iki durumda da yaptığımız, derleyiciye "unchecked" etiketli uyarıları göz ardı etmesini söylemektir. Ancak; ilk yöntemde uyarılar açımlamanın öncesine yerleştirildiği metot boyunca göz ardı edilirken, derleme opsiyonu yeğlendiğinde derleyicinin hoşgörüsü derlenen tüm koda yaygınlaştırılmaktadır.
<pre class="brush:java; gutter:false" name="SuppressWarnings">@SuppressWarnings({"unchecked"})
public static void yığıtıKullan() {
IYığıt sınıf = new Yığıt();
sınıf.ekle(new Öğrenci(...));
Öğrenci ilkÖğrenci = (Öğrenci) sınıf.gözAt();
if (Math.random() > Math.random())
sınıf.ekle(new Öğrenci(...));
else sınıf.ekle(new Öğretmen(...));
...
Öğrenci sonÖğrenci = (Öğrenci) sınıf.çıkar();
...
} // void yığıtıKullan() sonu
</pre>
Kullanılan kodun elimizin altında olması halinde, verilen uyarı dikkate alınmalı ve kaynak kod soysallık sonrası standartlara getirilerek yeniden derlenmelidir. Yeniden derlenen kodun eski kullanıcıları bu değişiklikten etkilenmeyeceklerdir. Çünkü, soysallık öncesinde yazılmış kullanıcılar değiştirilmiş kodun gözünde ham türlerden yararlanan yeni koddan farklı değildir.<br/>
<br/>
<h3>Daha Esnek Metot İmzaları İçin Joker Türler</h3><br/>
Pek çok ölümlü Java programcısı için soysallık desteğini ustaların erişilmez alemine sınırlı kılan en başlıca etken, <i>joker tür</i> argümanlarının varlığıdır. Değişik biçimlerde kendini gösteren bu korkunç yaratığı, iki kümenin sahip olduğu ortak elemanların sayısını döndüren metodu gerçekleştirerek tanımaya başlayalım. Veri Kapları Çerçevesi'nin sağladığı <code>Set</code> arayüzünde karşılanmayan bu işlem, birinci argümandaki kümenin her bir elemanının diğer kümede var olup olmamasına göre sayaç değişkenini güncelleyen aşağıdaki metotla gerçekleştirilebilir.
<pre class="brush:java; gutter:false" name="JokerliTürGereksinimi">import java.util.Set;
...
public static int ortakElmSayısı(Set km1, Set Km2) {
int elmSayısı = 0;
for (Object elm : km1)
if (km2.contains(elm)) elmSayısı++;
return elmSayısı;
} // int ortakElmSayısı(Set, Set) sonu
</pre>
Bir önceki altbölümden dersini almış olanlarınız, metot imzasındaki ham türlere bakıp bildiklerinden kuşkuya düşerek, neden <code>Set<Object></code> kullanılmamış, diye sorabilirler. Öncelikle, bu arkadaşları doğru yolda olduklarını söyleyerek yatıştıralım; gerçekten de, yukarıdaki imzada bulunan ham türler derleyicinin tür denetim desteğini engellemek suretiyle tehlikeye davet çıkarıyor. Ancak; ham tür yerine <code>Set<Object></code> kullanmak da sorunu halletmiyor. Şöyle ki; <b><code>T</code>'nin <code>S</code>'den kalıtladığı ve <code>G</code>'nin soysal tür olduğu bir ortamda, <code>G<T></code> <code>G<S></code>'den kalıtlamaz. Bir diğer deyişle, G<T> nesneleri G<S> nesneleri olarak ele alınamaz.</b> Somutlaştıracak olursak, <code>Integer</code>'ın <code>Object</code>'ten kalıtlıyor olmasına karşın, <code>Set<Integer></code> <code>Set<Object></code>'ten kalıtlamaz. Bu ise, iki türün uyumsuz olduğu ve birbirlerini ilklemekte veya birbirlerine atanmakta kullanılamayacağı anlamına gelir. Gelin, bu sonucu aşağıdaki kod parçasının üzerinden giderek pekiştirelim. Öncelikle, 3. satırda kümeyi yaratırken elemanlarımızın <code>Integer</code> (ve otomatikman sarmalanarak <code>Integer</code>'a dönüştürülen <code><b>int</b></code>) ile tür uyumlu olabileceğini ilan ediyoruz. Daha sonraki satırlarda, verdiğimiz bu söze uyarak kümemize eleman ekliyoruz. Ancak; <code>intKüme</code> ile <code>Set<Object></code> türlü <code>objKüme</code>'yi ilkleyen son satır, derleyicinin sıkı denetimini aşamıyor ve hataya neden oluyor. Bunun sebebi, iki tutacak tarafından paylaşılan ve başta <code>Integer</code> ile tür uyumlu nesneler ile doldurulacağı ilan edilen küme nesnesinin artık <code>objKüme</code> aracılığıyla <code>Object</code> ile tür uyumlu nesneler—yani, Java nesne alemindeki bütün nesneler—ile doldurulması ihtimalidir.
<pre class="brush:java; gutter:true" name="SoysallıkVeKalıtlama">import java.util.*;
...
Set<Integer> intKüme = new TreeSet<>();
intKüme.add(new Integer(0));
...
Set<Object> objKüme = intKüme; // Derleme hatası!!!
</pre>
Derdimizin çaresi tür argümanı olarak joker kullanımından geçer. <code>?</code> ile belirtilen joker, adını bilmediğimiz veya umursamadığımız türler için kullanılır. Aşağıdaki metot imzasını buna uygun okuyacak olursak, <code>ortakElmSayısı</code>'nın herhangi bir türden elemanlara sahip iki <code>Set</code> beklediğini söyleyebiliriz.
<pre class="brush:java; gutter:false" name="JokerTürKullanımı">public static int ortakElmSayısı(Set<?> km1, Set<?> Km2) {
...
} // int ortakElmSayısı(Set<?>, Set<?>) sonu
</pre>
Şu iki noktanın akılda tutulmasında yarar olacaktır: i) farklı parametrelerde kullanılan jokerler birbirinden bağımsızdır ve farklı türlerle eşleştirilebilirler, ii) joker parametreli türün nesnesine eleman olarak sadece <code><b>null</b></code> eklenebilir.<sup><a id="ref3" href="#dn3">3</a></sup><br/><br/>
Yığıt arayüzüne iki yeni ileti ekleyerek devam edelim. Bunlardan <code>yükle</code>, argümanındaki kabın elemanlarını teker teker hedef nesneye eklerken, <code>temizle</code> hedef nesneyi boşaltırken silinen elemenları daha sonraki kullanımlar için argümanda geçirilen kaba kaydediyor.
<pre class="brush:java; gutter:false" name="ÜreticiVeTüketiciDeseni">import java.util.Collection;
public class IYığıt<E> {
...
public void temizle(Collection<E> kopya); // Kapsayıcı değil!
public void yükle(Collection<E> kaynak); // Kapsayıcı değil!
...
} // IYığıt<E> arayüzünün sonu
</pre>
İlk denememiz, soysal türlerin daha önceden bahsettiğimiz kalıtlama ile birlikte değişmeme özelliğinden dolayı tüm kullanımları kapsamıyor ve kimi zaman kullanıcı tarafında derleme hatasına neden olabiliyor. Anılan özelliği yinelemektense, <code>yükle</code> iletisinin bir kullanım örneğine bakarak durumu anlamaya çalışalım.
<pre class="brush:java; gutter:false" name="ÜreticiVeTüketiciDeseni2">IYığıt<Kişi> güruh = new Yığıt<>();
// güruh'a bir şeyler koy
Vector<Öğrenci> sınıf = new Vector<>();
// sınıf'a bir şeyler koy
güruh.yükle(sınıf); // Derleme hatası!!!
</pre>
Belli ki, yukarıdaki kodu yazan arkadaş <code>Öğrenci</code> sınıfının <code>Kişi</code>'den kalıtladığını düşünerek <code>Vector<Öğrenci></code> adlı parametreli türün de <code>Collection<Kişi></code>'yi gerçekleştiren <code>Vector<Kişi></code>'den kalıtladığı sonucuna varmış. Ne var ki, soysallığın kalıtlama ile birlikte değişme özelliği olmaması nedeniyle, öncülü doğru olan bu tümcenin sonuç kısmı hatalı. Yani, <code>Vector<Öğrenci></code> <code>Vector<Kişi></code>'den kalıtlamaz. İş böyle olunca, kodun son satırı parametre (<code>Collection<Kişi></code>) ile argüman (<code>Vector<Öğrenci></code>) arasındaki tür uyumsuzluğu nedeniyle derleme hatasına yol açıyor.<br/><br/>
İçine düştüğümüz sıkıntı, sınırlı joker kullanımıyla çözülebilir. Yukarıdaki örneği sürdürerek ifade edecek olursak; derleyiciye söylememiz gereken, <code>Yığıt<Kişi></code> türlü bir yığıta <code>Kişi</code> veya <code>Kişi</code>'den kalıtlayan herhangi bir sınıfa ait elemanlar içeren bir kap ile yükleme yapılabileceğidir. Bu ise, <code>yükle</code>'nin parametresinin <code>Collection<? extends E></code> türüne sahip ilan edilmesi ile olanaklıdır. Yani, hedef nesneye girdi sağlama görevi gören kap, <u>üst sınırlı</u> joker türüyle tanımlanmalıdır.<br/><br/>
<code>temizle</code> iletisinin imzasında ortaya çıkan sorun da sınırlı jokerlerin koşut bir kullanım biçimiyle sağlanabilir. Önce derleyicinin kabul etmeyeceği bir kullanıcı kodu görelim.
<pre class="brush:java; gutter:false" name="ÜreticiVeTüketiciDeseni3">IYığıt<Öğrenci> sınıf = new Yığıt<>();
// sınıf'a bir şeyler koy
Vector<Kişi> güruh = new Vector<>();
sınıf.temizle(güruh); // Derleme hatası!!!
</pre>
Bu örnekte de esnek olmayan bir imzanın cezasını çekiyoruz: <code>Öğrenci</code> tür argümanıyla yaratılan <code>sınıf</code> ancak ve ancak <code>Öğrenci</code> tutacakları içeren bir kaba kaydedilebiliyor. Kullanıcının yapmak istediği gibi kap <code>Kişi</code> eleman türüne sahip olduğunda, derleyici karşımıza dikiliveriyor. Halbuki, <code>Öğrenci</code> tutacağı ile görülebilen nesneler <code>Öğrenci</code>'nın atası olan tüm sınıfların (<code>Kişi</code> ve <code>Object</code>) tutacakları ile de görülebilir. Yani, imzanın kullanıcımızın yapmak istediğine izin verecek şekilde gevşetilmesi gerekir. Bir diğer deyişle, yığıt içeriğinin kaydedildiği kabın eleman türünün <code>Öğrenci</code> ve <code>Öğrenci</code>'nin atası olan herhangi bir sınıf olabileceğini derleyiciye bildirmemiz gerekir. Bu ise, <u>alt sınırlı</u> bir joker türünün kullanımı ile olanaklıdır ve örneğimizde imzadaki parametre türünün <code>Collection<? super E></code> şeklinde değiştirilmesi işimizi görecektir.<br/><br/>
Buna göre, arayüze eklenmek istenen iletilerin imzası şu şekilde oluşur. Ortaya çıkan imzalar, parametre listesindeki kaplar için genelde izlenmesinde yarar olacak bir kuralı da ele vermektedir: <b>Hedef nesneye girdi sağlama görevi gören kaplar üst sınırlı joker tür, hedef nesnenin ürettiği çıktının kaydedildiği çıktı amaçlı kaplar ise alt sınırlı joker tür ile tanımlanmalıdır.</b>
<pre class="brush:java; gutter:false" name="ÜreticiVeTüketiciDeseniSon">import java.util.Collection;
public class Collections {
...
public void temizle(Collection<? super E> kopya);
public void yükle(Collection<? extends E> kaynak);
...
} // IYığıt<E> arayüzünün sonu
</pre><br/>
<h3>Soysal Metotlar</h3><br/>
Veri kapları dışında soysallıktan yararlanılan bir diğer programlama öğesi metotlardır. Genelde soysal kaplar üzerinde çalışan bu metotların soysallığı, niteleyicilerinin sonrasında kullanılan tür parametreleri yoluyla ifade edilir. Standart Veri Kapları Çerçevesi'ndeki değişik türden kaplar üzerinde uygulanabilecek metotları içeren <code>java.util.Collections</code> sınıfında bulunan sıralama metotlarına bakarak görelim.
<pre class="brush:java; gutter:false" name="SoysalMetotlar">package java.util;
public class Collections {
...
public static <E extends Comparable<? super E>> void
sort(List<E> liste) { ... }
public static <E> void
sort(List<E> liste, Comparator<? super E> karşılaştırıcı) { ... }
...
} // Collections sınıfının sonu
</pre>
Sıralama, çok özel koşullarda kullanılabilecek bazı algoritmalar dışında, elemanların karşılaştırılması yardımıyla icra edilen bir yeniden düzenleme işlemidir. Dolayısıyla, sıralanması istenen kabın elemanlarının karşılaştırılabilir bir türe ait olması gerekir. Bu beklenti Java'da iki şekilde karşılanabilir:
<ol>
<li>Elemanların ait olduğu sınıf <code>Comparable</code> arayüzünü gerçekleştirir. Bu noktada, gerçekleştirme ilişkisinin doğrudan olması gerekmediği unutulmamalıdır. Genel olarak, bir nesne üyesi bulunduğu sınıfın atası olan herhangi bir sınıftaki <code>compareTo</code> metodu ile karşılaştırılabilir. Bu sebepten ötürü, <code>List<E></code>'nin sıralanabilmesi için <code>E</code>'nin <code>Comparable</code> arayüzünü gerçekleştirmesi veya gerçekleştiren bir üstsınıfa sahip olması gerekir. Bu ise yukarıdaki imzada olduğu gibi alt sınırlı bir joker tür ile belirtilebilir.</li>
<li>Elemanları karşılaştırmayı bilen bir başka sınıf bu talebi karşılar. Bunun için, söz konusu sınıfın <code>java.util</code> paketindeki <code>Comparator</code> arayüzünü eleman türüne uyumlu bir şekilde gerçekleştirmesi gerekir. Tür uyumluluğu, bir önceki maddede olduğu gibi alt sınırlı joker tür ile belirtilmelidir.</li>
</ol>
<br/>
<h3>Soysallık ve Diziler</h3><br/>
İlk öğrendikleri programlama dilinin komutsal olması nedeniyle Java'da da gerekli gereksiz dizi kullanmaya alışanları soysal türler kötü bir sürprizle karşılar: dizilerin bileşen türü tür parametresi kullanılarak ifade edilemez. Örneğin, <code>Yığıt</code> sınıfının aşağıdaki şekilde dizi kullanacak biçimde gerçekleştirilmesi derleme hatasına neden olacaktır.
<pre class="brush:java; gutter:false" name="SoysallıkVeKalıtlama">...
public class Yığıt<E> implements IYığıt<E> {
public Yığıt() {
_kap = new E[]; // Derleme hatası!!!
...
} // varsayılan yapıcı sonu
...
private E[] _kap;
} // Yığıt<E> sınıfının sonu
</pre>
Bunun sebebi, dizi ve soysal türler arasındaki temel bir farktan kaynaklanır: dizilerin tür bilgileri derleme sonrasına da taşınırken, soysal türlerin tür bilgileri derleyicinin kullanımı sonrasında silinir. Mesela, aşağıdaki kod parçasında <code>intDz</code> değişkeninin [ve tüm diğer <code>Integer</code> elemanlı dizilerin] üstnesnesi <code>Integer[].class</code> iken <code>dblDz</code> değişkeninin [ve tüm diğer <code>Double</code> elemanlı dizilerin] üstnesnesi <code>Double[].class</code>'dır. İstenecek olursa, ilişkin üstnesne (ve eleman türünün üstnesi [<code>Integer.class</code> ve <code>Double.class</code>]) kullanılarak içgörü<a alt="İçgörünün anlamı" href="http://ta-java.blogspot.com/p/programlama-sozlukcesi.html#içgörü">≝</a> (İng., reflection, introspection) yardımıyla <code>Integer</code> veya <code>Double</code> elemanlı yeni diziler yaratılabilir. Buna karşılık, <code>intVec</code> ve <code>dblVec</code> değişkenlerinin her ikisi de aynı üstnesneye sahip olacaktır: <code>Vector.class</code>. Çünkü, derleme sırasında kullanılan tür bilgileri <i>tür silme</i> (İng., type erasure) sonunda yok olmuş ve tüm parametreli türler aynı üstnesneyle temsil edilmek zorunda kalmıştır.<sup><a id="ref4" href="#dn4">4</a></sup>
<pre class="brush:java; gutter:false" name="DiziÜstnesneleri">Integer[] intDz = new Integer[10]();
Double[] dblDz = new Double[5]();
...
Vector<Integer> intVec = new Vector<>();
Vector<Double> dblVec = new Vector<>();
</pre>
Bu nedenden ötürü, dizi kullandığımız yukarıdaki gibi durumlarda, ya dizi kullanmaktan vazgeçerek soysal türlerden birine yönelmemiz ya da derleme hatası vermemekle birlikte derleyici uyarısına neden olan şu kodu tercih etmemiz gerekir. Tavsiye edilen birinci yolun seçimidir.
<pre class="brush:java; gutter:false" name="SoysalVeKalıtlama">...
public class Yığıt<E> implements IYığıt<E> {
public Yığıt() {
_kap = (E[]) new Object[]; // Derleyici uyarısı!
...
} // varsayılan yapıcı sonu
...
private E[] _kap;
} // Yığıt<E> sınıfının sonu
</pre>
<br/>
</div>
<hr/>
<div id="dipnotlar" style="text-align:justify">
<ol>
<li id="dn1"> Anlatım, haklı olarak, kalıtlamanın sadece sınıflar arası geçerli bir ilişki olduğu izlenimini uyandırabilir. Fakat bu kesinlikle doğru değil; kalıtlama sınıflar arasında olduğu gibi arayüzler arasında da geçerli olan bir ilişkidir. <a href="#ref1">↑</a></li>
<li id="dn2"> Kod parçasında Java SE 7 ile eklenen elmas işlecinin kullanımına dikkat ediniz. Dolayısıyla, çalışma ortamını henüz güncellememiş olanlar bu kodu denediklerinde derleyici hatası ile karşılaşacaktır. Bu hatanın giderilmesi için tanımın şu şekilde tür çıkarsama olmaksızın yapılması gerekir.<br/><br/>
<code>IYığıt<Öğrenci> sınıf = <b>new</b> Yığıt<Öğrenci>();</code><br/><br/>
Elmas işleci ve diğer Java SE 7 yenilikleri için buraya<a alt="Java SE 7 ile gelen yenilikler" href="http://ta-java.blogspot.com/2011/04/java-se-7-ile-gelen-yenilikler.html">🔎</a> bakınız.<a href="#ref2">↑</a></li>
<li id="dn3"> Bazılarınızın bunun çalışma zamanı hatasına gebe bir durum olduğunu söylediklerini duyar gibiyim. Doğru ya, ilk argüman olarak <code>Öğrenci</code> nesneleri tutan, ikinci argüman olaraksa <code>Öğrenci</code> ile alakasız <code>Koyun</code> sınıfının nesnelerini tutan bir kullanım düşünebiliriz. Bu durumda, metodumuzdaki <code>contains</code> iletisinin gerçekleştirimindeki <code>equals</code> çağrısı, <code>Öğrenci</code> nesneleri ile <code>Koyun</code> nesneleri karşılaştırılamayacağı için, çuvallayacak ... mıdır acaba? <code>equals</code> iletisinin <code>Object</code> sınıfında tanımlanan genel sözleşmesine baktığınızda, <code><b>true</b></code> döndürme koşulunun hedef nesne ile uyumlu bir türe ait argümandaki <code><b>null</b></code> olmayan nesnenin eşit addedilmesi olduğu, geri kalan durumlarda ise <code><b>false</b></code> döndürülmesi gerektiğini görürsünüz. Dolayısıyla, <code>equals</code> iletisinin <code>Öğrenci</code> sınıfındaki gerçekleştirimi hedef nesne ile uyumsuz olan bir nesne gördüğünde, kodun devamında bir hatanın oluşmasına sebebiyet vermeden <code><b>false</b></code> döndürmelidir. <a href="#ref3">↑</a></li>
<li id="dn4"> Bunun bir sonucu olarak, üstnesneden yararlanarak işini gören <code><b>instanceof</b></code> işleci de soysal türün ham tür halini bekler. Yani, kullanım <code><b>if</b> (nesne <b>instanceof</b> Yığıt) ...</code> şeklinde olmalıdır. <a href="#ref4">↑</a></li>
</ol>
</div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-4623443012971997432011-10-27T17:40:00.001+03:002011-12-14T15:25:32.066+02:00Hatasız Programcı Olmaz: Java'da Hata Kotarımı<div id="anaMetin" style="text-align:justify">
Yazılım gerçek dünyada var olan somut/soyut süreçlerin bilgisayar donanımı üzerinde çalıştırılan benzetimleridir. Amaç, sürecin, etkileşimde bulunulan diğer süreçler ile birlikte daha verimli ve sağlıklı bir şekilde işletilmesini sağlamaktır. Mesela, bir otomasyon yazılımı zayiatı azaltmak ve bakımı hızlandırmak suretiyle parçası olduğu üretim sürecinin daha verimli olmasını sağlayacaktır. Doğal olarak, bir yazılımın başarılı kabul edilmesindeki en önemli ölçüt, bilgisayarda oluşturulan hesaplama sürecinin benzetimi yapılmakta olan sürecin algılanışına<sup><a id="ref1" href="#fn1">1</a></sup> sadık kalıp kalmadığı şeklinde ifade edilebilecek <i>doğruluk</i> (İng., correctness) özelliğidir. Peşinden koşulması gereken bir diğer özellik, yazılımın öngörülmeyen koşullar altında da kabul edilebilir bir tepki göstermesi olarak da tanımlayabileceğimiz <i>dayanıklılık</i>tır (İng., robustness). Bu beklentiler yazılım sektörünün rekabete dayalı pragmatik bir dünya olması gerçeği ile birlikte ele alındığında, yazılım üreticisinin birincil görevinin düzgün bir peformansa sahip, doğru ve dayanıklı—yani, <i>güvenilir</i> (İng., reliable)—programların makul bir hızda geliştirilmesi olduğu söylenebilir. İşte bu yazıda, ifade edilen görev tanımındaki güvenilirlik özelliğine yönelik olarak Java programlama dili tarafından sağlanan desteğe değineceğiz.<br/>
<br/>
Kötü bir haber vererek başlayalım: Java, programlarınızın doğruluğunu sağlamak bağlamında kaynak kodunuzun dilin yazım kurallarına uygunluğunu denetlemek dışında pek bir destek sağlamaz; sözdizimsel hataların ötesinde Java derleyicisinin yapacağı çok şey yoktur. Aşağıdaki örnek üzerinden ne demek istediğimizi açalım.<br/>
<br/>
<div style="color: #444444; text-align: center;">Faktöryel.java</div><pre class="brush:java; gutter:false; highlight:[13]" name="MantıksalHata">import static java.lang.System.out;
...
public class Faktöryel {
public static void main(String[] ksa) {
byte b = Byte.parseByte(ksa[0]);
out.println(b + "!: " + fakt(b));
...
} // void main(String[]) sonu
public static long fakt(byte n) {
long çarpım = 1;
for (byte i = 2; i <= n; i++) çarpım += i;
return çarpım;
} // long fakt(byte) sonu
...
} // Faktöryel sınıfının sonu
</pre>
<pre class="brush:bash; gutter:false" name="MantıksalHata">$ javac -encoding UTF-8 Faktöryel.java
$ java Faktöryel 5
5!: 15 ✘
</pre>
Görüldüğü gibi, sözdizimsel bir hata bulunmamakla birlikte, faktöryel kavramının hatalı gerçekleştiriminden kaynaklanan bir <i>mantıksal hata</i> ortaya çıkmıştır. Derleyicinin müdahele ederek işaretli satırdaki <code>+</code> yerine <code>*</code> yazılması gerektiği yorumunu yapması olanaksızdır. Bu gibi hataların, kodun sınanması sırasında keşfedilerek programcı tarafından ortadan kaldırılması gerekir. Bir an için bunun yapıldığını ve hatanın düzeltilerek doğru bir gerçekleştirimin sağlandığını düşünelim. Bu durumda, programımızın çalıştırılması aşağıdaki sonuçları verecektir.
<pre class="brush:bash; gutter:false" name="MantıksalHata">$ java Faktöryel 5
5!: 120 ✔
$ java Faktöryel 20
20!: 2432902008176640000 ✔
$ java Faktöryel 21
21!: -4249290049419214848 ✘
</pre>
Bir ihtimal Java'daki tamsayı türlerinin taşma davranışını bilmeyenleriniz, herhangi bir sayının eksi değerli bir faktöryele sahip olduğuna şaşırabilir. Bu gruptaki arkadaşlara önerim, daha fazla ilerlemeden şu yazıya<a alt="Java'da ilkel tamsayı türleri" href="http://ta-java.blogspot.com/2011/03/ilkel-turler-tamsaylar.html#taşma">🔎</a> bir göz atmaları. Diğer arkadaşlar ise, bunun sebebinin çarpım sonucunun <code><b>long</b></code> değerler için söz konusu olan aralığa düşmemesi nedeniyle kırpılması olduğunu bileceklerdir. Peki bu durumda, faktöryel metodumuzun doğru olmadığını söyleyebilir miyiz? Faktöryelin tanımını bilen herkesin bu soruya hayır yanıtı vermesi gerekir; ortada olan şey bir hatadan çok yetersizliktir ve muhtemelen metodun kullanıcısı ile iletişim eksikliğinden kaynaklanmaktadır. Soruna değişik şekillerde çözüm getirilebilir.
<ol>
<li>Gerçekleştirime dokunulmaz ve kullanıcıya sağlanan belgelerde metodun 0 ile 20 arasındaki tamsayılar için doğru çözümler ürettiği ve dolayısıyla sağlanması beklenen argüman değerinin bu aralıkta olması gerektiği söylenir.</li>
<li>Duruma has bir başka çözüm sağlanarak eksiklik giderilir. Örneğimizde, dönüş türü olarak <code><b>long</b></code> yerine <code>java.math.BigInteger</code> türünün kullanılması ve kodda buna göre değişikliklerin yapılması işimizi görecektir.</li>
<li>Geçirilen argüman değerinin beklenen aralıkta olup olmadığı denetlenir. Bu, basit bir <code><b>if</b></code> komutuyla yapılabileceği gibi doğruluk savı (İng.; assertion) denetimiyle de yapılabilir. İlk yolun seçilmesi durumunda, görülen olumsuzluk dönüşte [0 gibi] özel olarak yorumlanan bir değerle belirtilebileceği gibi söz konusu durumu özetleyen bir ayrıksı durum (İng., exception(al condition)) nesnesi ile de bildirilebilir.</li>
</ol>
İkinci ve üçüncü maddelerdeki yaklaşımların nasıl gerçekleştirilebileceğini aşağıdaki alternatif çözümü izleyerek görelim. Özyinelemeli olan yeni metodumuzda, argümanın eksi olması hali, ki bu beklenilmeyen bir kullanıma işaret eder, çağırıcıya <code>IllegalArgumentException</code> türündeki bir ayrıksı durum nesnesi ile bildirilirken, argümanın 0 veya 1 olması halinde 1, geri kalan durumlarda ise matematikten tanıdık <code>n * (n-1)!</code> değeri döndürülmektedir. <code>java.lang</code> paketinde tanımlanan <code>IllegalArgumentException</code>, sağlanan argüman değerinin, -5'in faktöryelinin bulunmaya çalışılmasında olduğu gibi, uygunsuz olduğuna işaret eder.
<pre class="brush:java; gutter:false" name="İlkAyrıksıDurum">...
import java.math.BigInteger;
public class Faktöryel {
public static void main(String[] ksa) {
...
long l = Long.parseLong(ksa[1]);
try { out.println(l + "!: " + fakt2(l)); }
catch(IllegalArgumentException a) {
out.println(l + "!: " + fakt2(Math.abs(l)) + "!!!");
}
} // void main(String[]) sonu
...
public static BigInteger fakt2(long n) {
if (n < 0)
throw new IllegalArgumentException(String.valueOf(n));
if (n < 2)
return BigInteger.ONE;
else return BigInteger.valueOf(n)
.multiply(fakt2(n - 1));
} // BigInteger fakt2(long) sonu
} // Faktöryel sınıfının sonu
</pre>
<pre class="brush:bash; gutter:false" name="MantıksalHata">$ java Faktöryel 21 21
21!: -4249290049419214848 ✘
21!: 51090942171709440000 ✔
$ java Faktöryel 5 -5
5!: 120 ✔
-5!: 120!!!
</pre>
Beklenmedik koşulların oluştuğu noktada, özet bilgi içeren bir ayrıksı durum nesnesinin yaratılıp <code><b>throw</b></code> komutu ile fırlatılması gerekir. Bunun sonrasında, ortaya çıkan durumun çözümünü bilen birisinin duruma el atıp bir şeyler yapması beklenir. Biraz düşünüldüğünde, çözümün içine düşülen duruma sebep olanlar tarafından sağlanabileceği görülecektir. Bu ise, ayrıksı durumun ortaya çıktığı noktaya gelinene kadar izlenen çağrı zinciri üzerindeki noktalar demektir. Bir diğer deyişle, <b>çare ya sorunun ortaya çıktığı noktada ya da o noktaya gelinmesi ile son bulan çağrı yığıtındaki diğer metotlarda bulunabilir</b>. Çözüme dair bir şeyler yapabileceğini düşünenlerin bu iddialarını <code><b>try</b></code>-<code><b>catch</b></code> yapısı içinde bildirmeleri beklenir. <code><b>try</b></code>, takip eden kıvrımlı ayraç çifti arasındaki komutların sorun çıkarabileceğini bildirirken, <i>kotarıcı</i> olarak adlandırılan <code><b>catch</b></code> bloğu/blokları iliştirildikleri korumalı bölgede ortaya çıkabilecek sorunların tümü veya bazıları için çözümler sunar. Çağrı zinciri üzerindeki noktaların hiçbirinde çözüm önerilmemesi halinde ise, top denetim akışı geriye sarılarak programı başlatan JSM'ye atılacak ve JSM de ortaya çıkan durumu oluştuğu yerden başlayarak nerelere uğrayarak çözmeye çalıştığını söyleyen bir raporu standart hata ortamına<sup><a id="ref2" href="#fn2">2</a></sup> yazarak programı sonlandıracaktır. Bunun nasıl olduğuna aşağıdaki çıktıya göz atarak bir bakalım.
<pre class="brush:java; gutter:false" name="İlkAyrıksıDurum">...
public class Faktöryel {
public static void main(String[] ksa) {
...
long l = Long.parseLong(ksa[1]);
out.println(l + "!: " + fakt2(l));
} // void main(String[]) sonu
...
public static BigInteger fakt2(long n) {
if (n < 0)
throw new IllegalArgumentException(String.valueOf(n));
...
} // BigInteger fakt2(long) sonu
} // Faktöryel sınıfının sonu
</pre>
<pre class="brush:bash; gutter:false" name="MantıksalHata">$ java Faktöryel 5 -5
5!: 120 ✔
Exception in thread "main" java.lang.IllegalArgumentException: -5
at Faktöryel.fakt2(Faktöryel.java:20)
at Faktöryel.main(Faktöryel.java:9)
</pre>
JSM'ye bakılacak olursa, programın [main adına sahip] ana izleğinde <code>Faktöryel</code> sınıfının <code>main</code> metodu içinden 9. satırda aynı sınıftaki <code>fakt2</code> metodu çağrılmış ve denetim akışı bu metottta iken 20. satırda <code>IllegalArgumentException</code> ayrıksı durumu ortaya çıkmış. Anlamadıysanız ikinci bir örnekle açıklık getirmeye çalışalım; anlayanlar da bu sayede pekiştirmiş olurlar.
<pre class="brush:java; gutter:false;highlight:[29,30]" name="KotarımıZorunluDurumlar">import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Scanner;
public class Örnek {
public static void main(String[] ksa) {
// ...
m1();
// ...
} // void main(String[]) sonu
private static void m1() {
// ...
m2();
// ...
} // void m1() sonu
private static void m2() {
String dosyaAdı;
Scanner grdKanalı = new Scanner(System.in);
System.out.print("Dosya adını giriniz: ");
dosyaAdı = grdKanalı.nextLine();
// ...
m3(dosyaAdı);
// ...
} // void m2() sonu
private static void m3(String dosyaAdı) {
BufferedReader dosya =
new BufferedReader(new FileReader(dosyaAdı));
// ...
} // void m3(String) sonu
} // Örnek sınıfının sonu
</pre>
İşaretli satırlarda, argümanda geçirilen ada sahip ve o anki çalışma dizini içinde bulunan bir dosyanın tamponlanarak ve karakter yönelimli bir biçimde okunması için uygun türden bir akak nesnesi yaratılıyor. Bu işlem, sağlanan ada sahip bir dosyanın bulunmaması halinde <code>java.io.FileNotFoundException</code> ayrıksı durumu ile sonlanacaktır. Dolayısıyla, programın derlenip var olmayan bir dosyanın adı verilerek çalıştırılması, yukardakine benzer bir çağrı yığıtı özetinin üretilmesine neden olacaktır diye düşünebiliriz. Ancak, gelin görün ki, program derlendiğinde durumun hiç de öyle olmadığı görülür.
<pre class="brush:bash; gutter:false" name="MantıksalHata">$ javac -encoding UTF-8 Örnek.java
Örnek.java:29: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
new BufferedReader(new FileReader(dosyaAdı));
^
1 error
</pre>
Yukarıdaki çıktıdan da görülebileceği gibi, <code>IllegalArgumentException</code> için sesini bile çıkarmayan derleyici, iş <code>FileNotFoundException</code>'a geldiğinde yaygarayı basmaktadır. Çifte standardın sebebi, ayrıksı durumların iki kategoriye ayrılmasıdır:
<ul>
<li><code>RuntimeException</code> köklü sınıf sıradüzeninde olanlar: Bu kategoriye giren ayrıksı durumlar, ya ortaya çıktıkları noktada yakalanıp kotarılmalı ya da çağrı yığıtındaki diğer metotlardan birinde kotarılması gerektiğini hatırlatmak için metot imzasında ilan edilmelidirler.</li>
<li>Diğerleri: Bu ayrıksı durumların ne yakalanması ne de, yakalanmadıkları takdirde, metot başlığında ilan edilmeleri zorunludur. </li>
</ul>
Dolayısıyla, ilk kategoriye giren <code>FileNotFoundException</code> ayrıksı durumunun ortaya çıkması olasılığının belirmesiyle derleyici bizim kotarmak ya da bu görevi ihale etmek yönünde bir karar verdiğimizi görmek ister. Bu istemin karşılanmaması halinde ise söz konusu eksikliğe dikkat çekerek derlemenin başarısızlıkla sonlandığını bildirir. Burada bir noktanın özellikle vurgulanması yerinde olacaktır: <b>derleyici, kodu okuyup var olmayan bir dosyanın girileceğinden emin olduğu için değil, böyle bir olasılığın var olduğu için itiraz etmektedir.</b> Derleyicinin gözünde, <code>FileReader</code> sınıfının kullanılan yapıcısı <code>FileNotFoundException</code> türlü bir ayrıksı durum nesnesi fırlatabilir ve programcının da buna karşı hazırlıklı olduğunu kanıtlaması gerekir.<br/>
<br/>
Kotarımın sorunun çıktığı nokta yerine çağrı yığıtındaki diğer metotlara bırakıldığı metot başlığına eklenen <code><b>throws</b></code> bildirimi ile ilan edilir. Buna bir örnek aşağıda verilmiştir: <code>m3</code>'te ortaya çıkabilecek sorun öngörülmüş fakat metot içinde çözülmektense metot başlığında belirtilmek suretiyle çağrı yığıtındaki diğer metotlara bırakılmıştır. Daha kesin ifade edecek olursak, <code>m3</code>'te ortaya çıkabilecek sorun için top önce <code>m2</code>'ye ve oradan da <code>m1</code>'e atılmış ve hal çaresi bu metot içinde sağlanmıştır.
<pre class="brush:java; gutter:false" name="KotarımıZorunluDurumlar_Çözüm">...
import java.io.FileNotFoundException;
public class Örnek {
public static void main(String[] ksa) {
// ...
m1();
// ...
} // void main(String[]) sonu
private static void m1() {
// ...
try { m2(); } catch(FileNotFoundException a) { ... }
// ...
} // void m1() sonu
private static void m2() throws FileNotFoundException {
...
// ...
m3(dosyaAdı);
// ...
} // void m2() sonu
private static void m3(String dosyaAdı)
throws FileNotFoundException {
BufferedReader dosya =
new BufferedReader(new FileReader(dosyaAdı));
// ...
} // void m3(String) sonu
} // Örnek sınıfının sonu
</pre>
<b>Kotarıcıların iliştirildikleri korumalı bölgeye özel oldukları unutulmamalıdır.</b> Bundan dolayı, aşağıdaki kod parçası derleyici tarafından kabul görmeyecektir; kotarıcı <code>m2</code> metodunun sadece birinci kullanımına çözüm sağlamaktadır.
<pre class="brush:java; gutter:false" name="KotarımıZorunluDurumlar">private static void m1() {
// ...
try { m2(); } catch(FileNotFoundException a) { ... }
// ...
m2(); // ⇒ Derleme hatası!
// ...
} // void m1() sonu
</pre>
Bu noktada, neden iki kategori olduğunu soruyor olabilirsiniz. Aşağıdaki örnekle anlamaya çalışalım. Belli ki, bu basit programı yazan arkadaş Java'da dizilerin 0-temelli indislere sahip olduğunu unutmuş ve üç elemanlı bir <code><b>int</b></code> dizisi yarattıktan sonra üçüncü eleman yerine 3 indisiyle gösterilen elemanın değerini 4 olarak güncellemek istemiş. Yani, Java programlama bilgisi eksik olan arkadaş derleme zamanında yakalanamayan bir hata yapmış. Takdir edersiniz ki, böylesine bir hatanın kotarıcıda düzeltilmesi ve programa devam edilmesi olanaklı değildir. Yapılması gereken, programcı arkadaşımızın dizileri daha iyi öğrenmesi ve kullanıcıdan 0..2 aralığında değer istemesi gerektiğini farketmesidir. Çünkü, ayrıksı durum düzeneği programın beklenmedik durumlarda daha sağlıklı bir tepki vermesini sağlamak için kullanılır, kötü programcıların programlama hatalarını düzeltmesi gibi erişilmesi olanaksız bir amaç için değil.
<pre class="brush:java; gutter:false" name="ProgramlamaHatasıKotarılamaz">import java.util.Scanner;
public class DiziKullanımı {
public static void main(String[] ksa) {
int[] iDz = {1, 2, 3};
Scanner grdKanalı = new Scanner(System.in);
System.out.print("1..3 aralığında bir sayı giriniz: ");
iDz[grdKanalı.nextInt()] = 4;
} // void main(String[]) sonu
} // DiziKullanımı sınıfının sonu
</pre>
<pre class="brush:bash; gutter:false" name="MantıksalHata">$ javac -encoding UTF-8 DiziKullanımı.java
$ java DiziKullanımı
1..3 aralığında bir sayı giriniz: 3
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
at DiziKullanımı.main(DiziKullanımı.java:8)
</pre>
Benzer bir gözlem <code><b>null</b></code> değerli bir tutacak yoluyla ileti gönderilmeye çalışılması sonrasında ortaya çıkan <code>NullPointerException</code> için de yapılabilir. Sıkça kendini gösteren bu ayrıksı durumun da sebebi programlama hatasıdır ve kotarılması olanaksızdır. Dolayısıyla, yakalanması veya metot başlığında ilan edilmesi pek anlamlı olmayacaktır.<sup><a id="ref3" href="#fn3">3</a></sup> Buna karşılık, bir dosyanın olmaması kullanıcının dosya adını giren kullanıcının dikkatsizliğinden kaynaklanabilir. Doğal olarak, bu beklenmedik durumun öngörülerek kotarıcıda ikinci bir hak verilerek programın boş yere sona ermesinin önüne geçilmelidir.<br/>
<br/>
Peki, ya sizin ve kullanıcının iradesi dışında gelişen sebepler nedeniyle programınız göçecek olursa. Mesela, arkadaşınız ortaklaşa kullandığınız bilgisayardan programınızın çalışması için gerekli olan bir sınıf dosyasını silecek olursa, ne olur? Ya da, dosyanın diskte kaydedildiği sektörlerden birinin bozulması nedeniyle sınıf dosyasının okunarak içselleştirilmesi söz konusu olamazsa, ne olur? Ya da ya da, programınızın işlemesi için gereksinilen kaynaklar JSM tarafından sağlanamayacak kadar büyük olmaya başlarsa, ne olur? Gelin bu soruların yanıtını <code>Faktöryel</code> sınıfını ikinci komut satırı argümanı çok büyük bir sayı ile deneyerek birlikte görelim.
<pre class="brush:bash; gutter:false" name="Hatalar">$ java Faktöryel 5 5827 2> StackOverflowError.txt
5!: 120 ✔
</pre>
Standart hata ortamını yönlendirerek hata mesajlarını StackOverflowError.txt dosyasına gönderen yukarıdaki komutun ürettiği bilgi incelendiğinde, <code>fakt2</code>'nin sürekli kendini çağırdığı ve programın <code>StackOverflowError</code> ile sonlandığı görülecektir. Yani, JSM özyinelemeli çağrılar sonucu genişleyen çağrı yığıtını doldurmuş ve taşmanın vuku bulduğu noktada imdat çağrısını yapmıştır. Bu hatanın ortaya çıkışı, JSM'nin yararlandığı yığıt büyüklüğünü -Xss512k veya -Xss1m gibi bir opsiyonla büyüterek ertelenebilir. Ancak; işin özünde çaresiz olan JSM'dir ve belleğin bittiği yerde o da bir şey yapamayacaktır. Bir diğer deyişle, beklenmedik durum dış kaynaklıdır ve programcının yapacağı fazla bir şey yoktur. Örneğimizde, özyinelemeli çözümü <code><b>for</b></code> döngülüsüyle değiştirerek belki bir şeyler yapabiliriz ama bu her zaman mümkün olmayacaktır. İş böyle olunca, kotarımdan söz etmek de mantıksızdır.<br/>
<br/>
Bu ana kadarki bilgilerimizi maddeleyerek özetleyecek olursak aşağıdaki listeyi elde ederiz. Java programları, iç ve dış koşullara göre beklenenden farklı bir şekilde davranabilir. Davranış sapması programcının müdahele edemeyeceği dış kaynaklardan ötürüyse, programcı <code>Error</code> köklü sıradüzenindeki sınıflardan birisinin nesnesi ile haberdar edilir. Yok eğer oluşan durum içselse ve programcı tarafından çözüm bulunabilecek gibi gözüküyorsa, programcının ihtiyacının duyduğu bilgi <code>RuntimeException</code> köklü sıradüzenindeki sınıflardan birisinin nesnesi ile sağlanır. Aksi takdirde, programcının yaptığı mantıksal bir hata dönüşerek ayrıksı durum haline gelmiştir ve bu, programcının dersini tamamlaması için, <code>Exception</code> köklü sıradüzenindeki diğer sınıfların nesneleri ile haber verilmelidir.<br/><br/>
<ul>
<li><code>Throwable</code>: Hata ve ayrıksı durumlar</li>
<ul><li><code>Error</code>: JSM'nin göçmesine neden olan hatalar</li></ul>
<ul><li><code>Exception</code>: Ayrıksı durumlar</li>
<ul><li><code>RuntimeException</code> ve altsınıfları: Kotarılması zorunlu olmayanlar</li>
<li>Kotarılması zorunlu olanlar</li>
</ul></ul></ul><br/>
Hangi kategoriye giriyor olursa olsun, programcı tüm koşullar altında elinden gelenin en iyisini sergilemelidir. Mümkünse, kotarıcıdaki kod ile programın sağlıklı bir şekilde devamı sağlanmalı, bu mümkün olmuyorsa, nasıl olsa bir şey yapamıyorum, denilerek oturmaktansa programın zarif bir şekilde bitmesi için elden gelen yapılmalıdır. Mesela, kullanılmakta olan dış kaynaklar kotarıcıda döndürülmeli, programın bakıcısının muhtemel bir programlama hatasını bulmasını kolaylaştırmak adına bir seyir defterine neden ile ilgili bilgiler not düşülmelidir. Bu noktada karşımıza, ayrıksı durum oluşsa da oluşmasa da çalıştırılacak kodu içeren bir kotarıcı olarak <code><b>finally</b></code> çıkıyor. Bir örnek ile görelim.
<pre class="brush:java; gutter:false" name="KotarıcıSırası">public void m() {
...
try {
FileReader dosya = new FileReader(...);
...
} catch (java.io.IOException a) { ... }
catch (ADurumu a) { return; }
...
catch (Exception a) { ... }
finally { dosya.close(); ... }
...
} // void m() sonu
</pre>
Birden çok kotarıcıya sahip yukarıdaki kod parçasında, <code><b>try</b></code> bloğunun işlenmesi sırasında oluşacak duruma göre farklı kotarıcılar devreye girebilir ya da işler yolunda giderse denetim akışı <code><b>finally</b></code> sonrasındaki komutla devam eder. Ancak, ne olursa olsun, <code><b>finally</b></code> kotarıcısı herhalükâda işlenecektir. Bu, başka hiçbir şey yapmadan çağırıcıya dönen <code>ADurumu</code> ayrıksı durumu için de geçerlidir; çünkü metottan dönüş öncesinde işlenmemiş <code><b>finally</b></code> kotarıcıları sırayla işlenir. Dolayısıyla, ortaya çıkan ayrıksı durum nedeniyle işlenen kotarıcıda metottan dönülse bile kapatılamamış olan dosya muhakkak kapatılacaktır.<br/>
<br/>
Kod parçasında dikkat çekilmesi gereken bir diğer nokta, kotarıcıların yerleştiriliş sırasının önemli olduğudur. <b>Bir kotarıcı sadece argümanındaki sınıfa ait nesneleri değil, o sınıfın kökü olduğu sıradüzeni içindeki tüm sınıfların nesnelerini yakalar.</b> Örneğin, yukarıdaki ilk kotarıcı <code>IOException</code> nesnelerini yakaladığı gibi, <code>FileNotFoundException</code>'ın da içinde bulunduğu pek çok sayıda sınıfın nesnelerini de yakalayacaktır. Benzer şekilde, <code>Exception</code> argümanlı kotarıcı, kendisinden önce geçen kotarıcılarda yakalanmayan tüm ayrıksı durum nesnelerini yakalayarak amansız—bir o kadar da anlamsız—bir gümrük memuru görevini görecektir. Böylesine bir kotarıcının başa konulması—genelde, bir üstsınıf türlü argümana sahip kotarıcının altsınıf türlü argümana sahip kotarıcıdan önce gelmesi—her şeyi yakaladığı için takip eden kotarıcıları anlamsız kılacaktır. <b>Kotarıcıların, özelden genele olacak şekilde sıralanması gerekir.</b><br/>
<br/>
Evet, geldik kendi ayrıksı durumunu kendin pişirmeye. Ama, yazımızın vardığı uzunluğu ve muhtemel konsantrasyon azalmasını düşünerek bunu bir başka yazıya bırakalım. Onun için, şimdilik bu kadar. Unutmayın, hatasız program(cı) olmaz; birazcık defansif programlama ortaya çıkabilecek zararın faturasını azaltır. Onun için sadece doğruluk değil, dayanıklılığa da önem verin. Yani, güvenilir yazılım üretin.<br/><br/>
</div>
<hr/>
<div id="dipnotlar" style="text-align:justify">
<ol><li id="fn1">Yazılımın benzetimi yapılan sürecin geliştiriciler (alan uzmanı, gereksinim toplayıcı, mimar, tasarımcı ve gerçekleştirimci) tarafından yorumlanması ile ortaya çıktığı unutulumamalıdır. Dolayısıyla, doğruluk özelliğini sürecin algılanışına göre yapmak daha doğru olur. <a href="#ref1">↑</a></li>
<li id="fn2">Standart hata ortamı, bir programın işleyişi sırasında üretilen hata mesajlarının gönderildiği aygıttır ve yeniden yönlendirme yapılmadığı veya <code>System.setErr</code> metodu kullanılarak değiştirilmediği müddetçe standart çıktının da gönderildiği varsayılan aygıt olan ekrandır. <a href="#ref2">↑</a></li>
<li id="fn3">Yapılan ayrımın bir diğer olumlu yanı, programların fazladan <code><b>try</b></code>-<code><b>catch</b></code> yapıları ve <code><b>throws</b></code> bildirimleri ile doldurulmamasıdır. Örnek olarak verdiğimiz iki ayrıksı durum düşünüldüğünde—tüm dizi erişimi ve ileti gönderimlerinin <code><b>try</b></code>-<code><b>catch</b></code> yapısı içine alındığını veya bu işlemleri içeren metotların imzasına ayrıksı durum ilanının eklendiğini düşünün—kotarımın zorunlu olmamasının kaynak kodun okunabilirliğine yaptığı katkı takdir edilecektir. <a href="#ref3">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-77168357602562369802011-10-14T01:44:00.001+03:002011-12-14T15:41:19.951+02:00Java Platformunun Dikkate Değer Bir Dili: Scala-2<div style="text-align:left"><a alt="Önemli bir JSM dili: Scala" href="http://ta-java.blogspot.com/2011/05/java-platformunun-dikkate-deger-bir.html">İlk yazı</a><br />
</div><br />
<div id="anametin" style="text-align:justify">Scala üzerine ikinci yazımızda bu JSM dilinin sağladığı sınıf tanımlama imkânına daha yakından bakacağız ve bunu yaparken de Java ile olan farklılıklara değinmeye çalışacağız.<sup><a id="ref1" href="#dn1">1</a></sup> Matematikteki kesirli sayı kavramını soyutlayan aşağıdaki sınıf iskeletiyle başlayalım.<sup><a id="ref2" href="#dn2">2</a></sup><br />
<br />
<div style="color: #444444; text-align: center;">Kesir.scala</div><pre class="brush:scala;gutter:false" name="KesirSınıfı">package com.ta.matematik
...
class Kesir(pay: Long = 1, payda: Long = 1) {
require(payda != 0)
private var (_pay, _payda) = (pay, payda)
sadeleştir()
...
private def sadeleştir() = { ... }
} // Kesir sınıfının sonu</pre>Java'dan gelenlerin gözüne çarpacak farklılıklardan belki de ilki, <code><b>public</b></code> niteleyicisinin sırra kadem basmış olması. Bu durumun sebebi, paket çapında erişimin varsayıldığı Java'dan farklı olarak, Scala'da programlama öğelerinin nitelenmemeleri halinde <code><b>public</b></code> erişime sahip kılınmasıdır. Dolayısıyla, <code>Kesir</code> ve erişim niteleyicisi bulunmayan tüm sınıf öğeleri herkes tarafından kullanılabilecektir. Bunun uygun olmaması halinde, programcının niyetini <code><b>protected</b></code> veya <code><b>private</b></code> niteleyicilerinden birini kullanarak bildirmesi gerekir. Bu noktada, <code><b>protected</b></code> niteleyicisinin paket içindeki türleri kapsamayıp, söz konusu öğeyi sadece o anki türden doğrudan veya dolaylı bir biçimde kalıtlayan türlere erişilebilir kıldığını söylemekte yarar vardır.<sup><a id="ref3" href="#dn3">3</a></sup> Ayrıca, paket çapında erişimden de bahsedilmediği dikkatinizi çekmiştir. Ancak, telaşa düşmeyin; nitelenmekte olan öğeyi içeren paket, sınıf veya tek-örnekli sınıf adlarından birinin <code><b>protected</b></code> veya <code><b>private</b></code> ile birlikte belirtilerek daha rafine erişim politikalarının ifade edilmesi mümkündür. Örneğin, <code><b>private</b></code> yerine <code><b>private</b>[com.ta.matematik]</code> nitelemesinin yapılması, <code>sadeleştir</code> adlı metodu sınıfa özel olmaktan çıkaracak ve <code>com.ta.matematik</code> paketindeki tüm öğelere görünür kılacaktır.<br />
<br />
Java'dan gelenleri başlarda şaşırtabilecek bir diğer nokta, nesnenin yaratılması esnasında yapıcıya geçirilmesi beklenen argümanların özelliklerini belirten parametre listesinin sınıf adı sonrasında bulunmasıdır. Buna göre, <code>Kesir</code> sınıfının örneği <code>ks1 = <b>new</b> Kesir(6, 10)</code> şeklinde yaratılabilecektir. İlkleme ise <code>Kesir</code> sınıfının tanımındaki metotların dışında kalan tüm öğelerin koddaki geçiş sırasında işlenmesiyle yapılacaktır. Yani, sınıf gövdesindeki söz konusu öğeler nesne ilkleme bloğu görevini görecektir; Scala terminolojisiyle konuşacak olursak, sınıf gövdesindeki öğeler <i>birincil yapıcı</i> olarak ele alınacaktır. Örneğimiz üzerinden gidecek olursak; <code>Predef</code> adlı tek örnekli sınıftan ithal edilen <code>require</code> metodu ile geçirilen paydanın uygunluk denetiminin yapılmasını takiben, yaratılmakta olan nesnenin altalanları olan <code>_pay</code> ve <code>_payda</code> koşut bir biçimde <code>pay</code> ve <code>payda</code> ile ilklendikten sonra yaratılmakta olan kesir sadeleştirilecektir. Bu noktada, koşut atamanın tüm kollarının aynı anda işleniyormuş gibi düşünülmesi gerektiği unutulmamalıdır.<br />
<br />
Haklı olarak, <code>Predef</code> sınıfının nereden çıktığını sorabilirsiniz. Yanıtı, Scala derleyicisinin, Java derleyicisinin <code>java.lang</code> paketini otomatikman ithal edilmiş kabul etmesindeki gibi, <code>java.lang</code> ve <code>scala</code> paketleriyle <code>scala.Predef</code> tek örneklisini otomatikman tüm programlara ithal etmesinde yatar. Bunun sonucunda, <code>Predef</code> tanımı da evrensel olarak görünür hale gelecek ve bazı işlemler öncesinde önkoşul denetimine yarayan <code>require</code> metodu da yukarıdaki gibi kullanılabilecektir.<br />
<br />
Bir diğer farklılık, Java'da aynı adlı metotların ezilmesi ile öykünülen varsayılan argümanların kullanımı. Scala 2.8'den başlayarak geçerli olan bu özellik sayesinde, varsayılan değerin uygun olması durumunda, argümanın es geçilmesi de olanaklıdır. Örneğin, yukarıdaki sınıf tanımının geçerli olduğu bir ortamda, <code>tamsayı = <b>new</b> Kesir(7)</code> ve <code>bir = <b>new</b> Kesir()</code>, sırasıyla, 7/1 ve 1/1 değerlerini temsil eden kesirli sayıları yaratacaktır. Bu noktada, varsayılan argümanların metot imzasının sonunda yer alması zorunluluğu unutulmamalıdır. Dolayısıyla, 1/7 kesrini temsil eden nesnenin yaratılması için her iki argümanın da geçirilmesi gerekecektir.<br />
<br />
Scala 2.8 ile birlikte, ileti gönderileri ve metot çağrıları sırasında argümanların parametre adları kullanılarak konumlarından farklı bir sırada geçirilmesi de olanaklı kılınmıştır. Buna göre, önceki paragraflardaki <code>ks</code> ve <code>tamsayı</code> tanımlayıcılarının, sırasıyla, <code><b>new</b> Kesir(payda = 5, pay= 3)</code> ve <code><b>new</b> Kesir(pay= 7)</code> şeklinde tanımlanması mümkün olacaktır. Bu sayede, <code>birBölüYedi = <b>new</b> Kesir(payda = 7)</code> tanımlamasının da geçerli olması sağlanarak 1/7 kesrine karşılık gelen nesnenin tek argüman geçirilerek yaratılması da mümkün olmaktadır.<br />
<br />
Yukarıdaki örnekte varsayılan argümanlar yardımıyla sağlanan değişik sayıda argümanlarla kullanılabilen yapıcı metotlar görüntüsü, <i>yardımcı yapıcı</i> metotlar yoluyla da sağlanabilir. Varsayılan argümanlarla birlikte de yararlanılabilecek bu yaklaşımda, programcı ilk iş olarak uygun argümanlarla birincil yapıcıyı veya diğer yardımcı yapıcılardan birini çağıran <code><b>this</b></code> adına sahip metotlar yazar. Örneğin, aşağıdaki kod parçasında iki argümanın da sağlanması durumunda birincil yapıcı çağrılırken, bir veya sıfır argümanın sağlanması halinde, sırasıyla, 7. ve 8. satırlardaki yardımcı yapıcılar çağrılacaktır. Dikkat ederseniz, her iki yapıcı da işini bir diğer yapıcıya havale ederek görmekte.<br />
<pre class="brush:scala;gutter:true" name="yardımcıYapıcılar">...
class Kesir(private var _pay: Long,
private var _payda: Long) {
require(_payda != 0)
sadeleştir()
...
def this(_pay: Long) = this(_pay, 1)
def this() = this(1)
def pay = _pay
def payda = _payda
...
override def toString() = _pay + "/" + _payda
...
} // Kesir sınıfının sonu</pre>Birincil yapıcının parametre listesindeki değişiklik de gözünüze çarpmıştır, Tanımının önüne <code><b>var</b></code> niteleyicisinin konulmasıyla ilişkin parametrenin değişken içeriğe sahip kılınması nedeniyle, daha önceki kod parçasında olduğu gibi yapıcıya geçirilen argümanların değişken içerikli altalanları ilklemesi ve değişikliklerin bu altalanlarda yapılması artık gerekli değildir. Ancak, birincil yapıcıya özel bu durum, Scala'nın atıf geçirme (İng., pass by reference) yöntemini desteklediği şeklinde yorumlanmamalıdır; Java'da olduğu gibi, Scala'da da argümanlar ilişkin metoda değer geçirme (İng., pass by value) yöntemiyle sağlanır.<br />
<br />
Java'dan tanıdık gelecek bir nokta, üstsınıflardan kalıtlanan bir metodun ezilmekte olduğunu bildiren <code><b>override</b></code> niteleyicisidir. Ancak, Java dengi <code>@Override</code> açımlamasının kullanımı seçimli olup sadece tavsiye edilirken, Scala'daki bu niteleyicinin kullanımı aynı imzaya sahip metotların varlığında zorunludur. Bu kurala uyulmaması, derleyicinin hata mesajıyla karşılanacaktır.<br />
<br />
<code>toString</code>'i bir yerlerden gözünüzün ısırdığını düşünüyorsanız, belleğinizin sizi aldatmadığını söyleyebilirim; Java'dan bildiğimiz bu ileti, Scala'da da hoş yazım amacıyla kullanılıyor. Tıpkı Java'da olduğu gibi, programcıların belli bir türe ait değerlerin hoş yazımı için kök sınıf tarafından sağlanan ilişkin metot gerçekleştirimini ezmesi gerekiyor. Ancak, <code>toString</code>'in Scala tür sıradüzeni içinde nereden nasıl kalıtlandığını daha iyi anlamak için, Java'da olmayan bir üstkavramın tanıtılmasında yarar var: <i>çeşni</i> (İng., <a href="http://en.wikipedia.org/wiki/Mixin">mixin</a>). Kaynak kodda <code><b>trait</b></code> anahtar sözcüğüyle karşılık bulan bu programlama kavramının anlaşılması, Java ve Scala türleri arasındaki etkileşimi daha iyi kavramak için de yardımcı olacaktır. O zaman, karşılaştırılabilirlik kategorisini tanımlayan <code>scala.math.Ordered</code> çeşnisinin Scala'nın resmi sitesindeki <a href="http://www.scala-lang.org/api/current/index.html#scala.math.Ordered">gerçekleştirimi</a>ne ve <code>Kesir</code> sınıfına söz konusu çeşninin nasıl katıldığına göz atarak bu üstkavramı anlamaya çalışalım.<br />
<br />
Soysal olan <code>Ordered</code> çeşnisi, başlığındaki kalıtlama ilişkisinden de görülebileceği gibi, Java platformundaki karşılaştırılabilir nesnelerin kategorisini tanımlayan <code>Comparable</code> arayüzünden kalıtlar. Scala türlerinin Java'dakileri geliştirebileceğine örnek oluşturan bu başlığın anlamı, işini <code>compare</code> metoduna havale ederek gören <code>compareTo</code> metodu gerçekleştiriminden de gözlemlenebilir. Böylece, Scala'da yazılan bir sınıfın nesneleri Java programları içinden de kullanılabilecektir.<br />
<br />
<div style="color: #444444; text-align: center;">Ordered.scala</div><pre class="brush:scala;gutter:false" name="OrderedÇeşnisi">package scala.math
trait Ordered[A] extends java.lang.Comparable[A] {
def compare(sağ: A): Int
def <(sağ: A): Boolean = (this compare sağ) < 0
def >(sağ: A): Boolean = (this compare sağ) > 0
def <=(sağ: A): Boolean = (this compare sağ) <= 0
def >=(sağ: A): Boolean = (this compare sağ) >= 0
def compareTo(sağ: A): Int = compare(sağ)
} // Ordered[A] çeşnisinin sonu
object Ordered {
implicit def orderingToOrdered[T](x: T)
(implicit ord: Ordering[T]): Ordered[T] =
new Ordered[T] {
def compare(sağ: T): Int = ord.compare(x, sağ)
}
} // Ordered tek-örneklisinin sonu</pre><code>Ordered</code> çeşnisinin gövdesine baktığımızda, tanımlanan kategorideki iki Scala nesnesinin—ileti alıcı (<code><b>this</b></code>) ve <code>sağ</code>—karşılaştırılma sonucunu döndüren <code>compare</code> metodunun gövdesi verilmeden sağlandığını görüyoruz. Bu durum, Scala derleyicisi tarafından söz konusu metodun soyut olarak ele alınacağı anlamını taşır; derleyicinin yaptığı bu varsayım yüzünden programcının ayrıca çeşniyi veya metodu soyut olarak nitelemesine gerek yoktur.<br />
<br />
Yukarıdaki kod parçasının ortaya koyduğu bir diğer önemli nokta, Scala'nın, Java'nın aksine, işleçlerin aşırı yüklenmesini—ya da, işin doğrusunu söylemek gerekirse, bu tür bir yanılsamayı yaratabilecek özellikleri—desteklediğidir. Öncelikle, tanımlayıcı adlarının oluşturulmasında kullanılan karakterler kullanageldiğimiz işleçlerin simgelerini de kapsayan daha geniş bir yelpazeden seçilebilir.<sup><a id="ref4" href="#dn4">4</a></sup> Ayrıca, tek argüman alan iletiler işleç ortada sözdizimiyle de kullanılabilir. Örneğin <code>+</code>, <code>topla</code> veya <code>add</code> kadar geçerli bir tanımlayıcı adıdır. İsteyecek olursak, değişken/sabit veya ileti/metot adlarını <code>topla</code> veya <code>add</code> yerine <code>+</code> olarak da seçebiliriz. Aynı zamanda, adı ne şekilde verilmiş olursa olsun, tek argüman alan iletileri, <code>alıcı.ileti(arg)</code> yerine <code>alıcı ileti arg</code> şeklinde de gönderebiliriz. Dolayısıyla, yukarıdaki <code><b>this</b> compare sağ</code> ifadesi <code><b>this</b>.compare(sağ)</code> ile eşdeğerdir.<br />
<br />
Kod parçamızda dikkat çeken bir diğer bölüm, tek-örneklimizin tanımında geçen <code><b>implicit</b></code> anahtar sözcüğüdür. Bu sözcük, söz konusu metodun, programcının açıkça kullanması dışında kimi zaman arka planda derleyicinin sentezlediği kod tarafından da çağrılabileceğini belirtir. Örneğimizde olduğu gibi dönüşüm amacıyla kullanılan bu tür metotlar, nesne tutacağını argüman türünden (<code>Ordering</code>) eşlik eden türe (<code>Ordered</code>) çevirir. Mesela, <code>Ordering</code> çeşnisi tutacağıyla bir nesneye ileti gönderilmesi ve bu iletinin geçerli olmadığının anlaşılması durumunda, derleyici yukarıdaki ve benzeri metotlardan birini usulca kullanarak nesneyi başka bir açıdan görecek ve program hata vermeden devam edecektir.<br />
<br />
Tanımlanmış bir çeşni, bir diğer çeşni tarafından kalıtlanmak suretiyle geliştirilebileceği gibi, içerdiği soyut öğelerin sağlanması ve/veya bazı öğelerinin ezilmesi yoluyla bir sınıfa katılabilir. Her iki durum da <code><b>extends</b></code> seçilmiş sözcüğü ile ifade edilir. Ancak; sınıfın bir başka sınıftan kalıtlaması halinde, <code><b>extends</b></code> üstsınıfı belirtmek için kullanılırken, sınıfa katılan çeşniler <code><b>with</b></code> anahtar sözcüğü ile belirtilir. Çeşniler arası kalıtlama ilişkisinin çoklu olmasının yanısıra, bir sınıf birden çok çeşniyi gerçekleştirebilir.<br />
<br />
<code>Ordered</code> çeşnisinin <code>Kesir</code> sınıfı tarafından gerçekleştirilmesi aşağıda verilmiştir. Bu tanıma göre, yaratılacak <code>Kesir</code> ve <code>Kesir</code>'den kalıtlayan tüm sınıfların nesneleri karşılaştırılabilir nesneler kategorisine gireceklerdir. Bu özellik, <code>Ordered</code> çeşnisinin <code>Comparable</code>'dan kalıtlaması nedeniyle, söz konusu nesnelerin sadece Scala programlarında kullanılmaları halinde değil, Java ve diğer Java platformu dillerinde yazılmış programlar içinden kullanılmalarında da geçerli olacaktır. Mesela, <code>Kesir</code> nesneleri ile doldurulmuş bir liste <code>java.util</code> paketindeki <code>Collections.sort</code> metodu ile sıralanabildiği gibi, <code>scala.collection.immutable.List</code> sınıfının <code>sort</code> metoduyla da sıralanabilir.
<pre class="brush:scala;gutter:false" name="çeşniKatmak">...
import scala.math
class Kesir extends Ordered[Kesir] {
...
def equals(sağ: Kesir) = compare(sağ) == 0
def compare(sağ:Kesir) = {
val fark = this - sağ
if (fark._pay < 0) -1
else if (fark._pay > 0) 1
else 0
} // compare(Kesir): Int sonu
...
def -(sağ: Kesir): Kesir = {
val pay = _pay * sağ._payda - _payda * sağ._pay
new Kesir(pay, _payda * sağ._payda).sadeleştir()
} // -(Kesir): Kesir sonu
...
} // Kesir sınıfının sonu</pre>
<code>Kesir</code> sınıfındaki <code>equals</code> metodunun <code>Ordered</code> çeşnisindeki <code>compare</code> ile uyumlu olacak şekilde ezilerek gerçekleştirildiği gözünüze çarpmıştır. Sakın ola ki, Java'yı iyi bilen biri olarak, bunun eşitlik denetimi işlecini (<code>==</code>) etkilemeyeceğini düşünmeyin. Çünkü, Scala'da <code>equals</code> ile <code>==</code> her zaman aynı şekilde çalışır: kök sınıftaki <code>equals</code> gerçekleştiriminin bir sınıf tarafından ezilmesi <code>==</code> işlecinin de anlamını değiştirir. Eşitlik denetimine ek olarak aynılık denetimi isteyenlerin, <code>eq</code> ve onun değillemesi olan <code>ne</code> iletilerini kullanması tavsiye edilir.<br />
<br />
Bir sınıfa çeşni katılması nesnenin yaratıldığı noktada, dinamik olarak da gerçekleştirilebilir. Örnek olarak, <code>KesirEksik</code> sınıfının <code>Ordered</code> çeşnisi katılmadan tanımlanmış olduğunu varsayalım. Bu takdirde, aşağıdaki kod parçasından da görebileceğiniz gibi, bu sınıfa ait [1/11 değerine sahip] bir nesne Java'daki adsız sınıflara benzer bir tanımla söz konusu çeşniye sahip kılınabilir.
<pre class="brush:scala;gutter:false" name="çeşniKatmak2">val ks =
new KesirEksik(3, 33) with Ordered[KesirEksik] {
def compare(sağ: KesirEksik) = {
val fark = this - sağ
if (fark.pay < 0) -1
else if (fark.pay > 0) 1
else 0
} // compare(KesirEksik): Int sonu
def equals(sağ: KesirEksik) = compare(sağ) == 0
} // Kesir'i geliştiren adsız sınıfın sonu
</pre>
<code>toString</code>'in soy ağacını öğrenmek için yola çıkmıştık, şimdi <code>equals</code> ve arkadaşlarının da katılımı ile iş daha da karıştı, değil mi? Üzülmeyin, aşağıdaki kısmi tür sıradüzeninin açıklanması her şeyi yoluna koyacaktır. [Yatık yazılı türler çeşnileri, diğerleri ise sınıfları temsil ediyor.]<br />
<br />
<ul><li><code>Any</code></li>
<ul><li><code>AnyRef ≡ java.lang.Object</code></li>
<ul><li>... // Java'dan ithal ediilen bileşke türler</li>
<li><i><code>ScalaObject</code></i></li>
<ul><li>... // Scala'da tanımlanan bileşke türler</li>
</ul></ul><li><i><code>AnyVal</code></i></li>
<ul><li><code>Boolean</code> + <code>scala.runtime.RichBoolean</code></li>
<li><code>Byte</code> + <code>scala.runtime.RichByte</code></li>
<li><code>...</code></li>
<li><code>Unit</code></li>
</ul></ul></ul><br />
Java'daki bileşke tür ve ilkel tür ayrımı Scala'da bire bir karşılık bulmaz; ilkel türlerin ele alınışını C# diline benzeterek anlamak daha kolay olacaktır. Çünkü, arka planda işlemcinin desteklediği türlerden birine eşleştirilerek işlenen bu çeşit değerler, kaynak kod düzeyinde kalıtlanarak geliştirilemeyen—yani <code><b>final</b></code>—özel sınıfların nesneleri gibi ele alınır. Dolayısıyla, ilkel türden değerler de ileti alıcı konumunda kullanılabilir. Bunu akılda tutarak yukarıda verilen sıradüzenini açalım. Öncelikle, ister ilkel olsun isterse bileşke, tüm türlerin kökü <code>==</code>, <code>!=</code>, <code>equals</code>, <code>toString</code> ve diğer temel iletileri içeren <code>Any</code> sınıfına gider. Scala nesnesi olduğunun anlaşılması için <code>ScalaObject</code> adlı bir gösterge çeşninin katıldığı bileşke türlü değerler, <code>java.lang.Object</code>'tekilere ek olarak <code>eq</code> ve <code>ne</code> gibi Scala nesnelerine özel iletiler içeren <code>AnyRef</code> sınıfında belirlenen sözleşmeye göre davranırlar. İlkel türlü değerler ise, ilişkin sınıfları işaretlemek için kullanılan <code>AnyVal</code> çeşnisi katılmış sınıflara aittir. Buna ek olarak, <code>Predef</code> tek-örneklisinde yapılan dönüşümler sayesinde, tüm ilkel tür değerler daha zengin bir arayüze sahipmiş gibi görünebilirler. Örnek olarak, 1'den 10'a tüm tamsayıları standart çıktıya yazan <code>for (i <- 1 to 10) System.out.println(i)</code> komutunun perde arkasına bir göz atalım. Unuttuysanız hatırlatalım, tek argümanlı ileti gönderileri işleç ortada sözdizimiyle de yazılabilir. Dolayısıyla, ileti alıcıdan başlayarak argümanındaki değere kadar olan tamsayıları içeren bir dilim döndüren <code>to</code> iletisini dönüştürerek döngümüzü <code>for (i <- 1.to(10)) System.out.println(i)</code> şeklinde yazmak da aynı işi görecektir. Yani, <code>Int</code> türlü 1'e <code>to</code> iletisi gönderilecek ve döndürülen dilim nesnesi gezilerek döngü işlenecektir. Ne var ki, <code>Int</code> sınıfının arayüzüne bakıldığında, <code>to</code> adında bir iletinin olmadığı görülecektir. O zaman, nasıl oluyor da Scala derleyicisi böyle bir durumda itiraz etmeden yukarıda anlatılan şeyi yapıyor? Bu sorunun yanıtı, <code>Predef</code> tek-örneklisinde sağlanan dönüştürücü metodun <i>örtük çağrı</i>sında (İng., implicit call) yatıyor: derleyici, iletinin desteklenmediğini görmesinin ardından <code>Int</code> türlü hedef nesneyi <code>scala.runtime.RichInt</code> nesnesine çeviriyor ve iletiyi bu nesneye gönderiyor. Bir diğer deyişle, <code>Int</code> sınıfındaki işlevsellik <code>RichInt</code> sınıfında sağlanan işlevsellikle zenginleştiriliyor.<br />
<br />
Bu haliyle çeşnilerin soyut sınıflardan çok da farklı olmadığını düşünebilirsiniz: tıpkı soyut sınıflarda olduğu gibi, çeşniler altalan tanımlarının yanısıra iletiler ve bu iletilerin bazıları veya tümü için gerçekleştirimler içerebilir. Buna karşılık, çeşniler yardımcı yapıcılara sahip olamaz; ek olarak, birincil yapıcılar argüman alamadığı gibi kalıtladıkları türlerin yapıcılarına argüman geçiremez. Ayrıca, (soyut) sınıflar için tekli kalıtlama geçerliyken çeşniler birden çok çeşniden kalıtlayabilir. Bundan dolayı bazılarınız, önceki paragraflardaki kategori sözcüğünün kullanımının da katkısıyla, çeşnilerin biraz da arayüzlere benzediğini düşünebilir. İki grubun da bir yere kadar haklı olduğu söylenebilir. Kimin haklı olduğunu ilan etmeden önce, Scala derleyicisinin çoklu sınıf kalıtlamanın desteklenmediği JSM üzerinde nasıl olur da soyut sınıf özellikleri sergileyen çeşnilerin çoklu kalıtımını olduruyor, ona bir bakalım. Bunun için sınıf dosyalarını tanımlanan türün üyelerini listeleyerek tersine bütünleştiren <code>javap</code> komutunu ve Scala derleyicisinin bir opsiyonunu kullanacağız.<br />
<br />
<div style="color: #444444; text-align: center;">Çeşni.scala</div><pre class="brush:scala;gutter:false" name="çeşnilerinByteCodeKarşılığı">trait Çeşni { def ileti() { println("iletide ...") } }
</pre><pre class="brush:bash;gutter:false" name="çeşniDerlemeVeTersineBütünleştirme">$ scalac Çeşni.scala
$ ls Çeşni*.class
Çeşni.class Çeşni$class.class
$ javap Çeşni
Compiled from Çeşni.scala
public interface Çeşni extends scala.ScalaObject {
public abstract void ileti();
}
</pre>Çeşni.class dosyasının incelenme sonucu, tercihini arayüzden yana kullananları haklı çıkarmış gibi gözüküyor; <code>javap</code> komutunun çıktısına göre, tanımlamakta olduğumuz iletinin imzası arayüze aynen taşınmış. Ancak, doğal olarak, arayüzlerin gerçekleştirim ayrıntısı içerememesi nedeniyle, metot gövdesi uçup gidivermiş. Bu sihirbazlığa açıklık getirilmesi gerekli. İşte bu noktada, Scala derleyicisine geçirilecek <code>print</code> opsiyonu yardım çağrımıza yanıt verecektir. <pre class="brush:bash;gutter:false" name="çeşniDerlemeVeTersineBütünleştirme2">$ scalac -print Çeşni.scala
[[syntax trees at end of cleanup]]// Scala source: Çeşni.scala
package <empty> {
abstract trait Çeşni extends java.lang.Object with ScalaObject {
def ileti(): Unit
};
abstract trait Çeşni$class extends {
def ileti($this: Çeşni): Unit = scala.this.Predef.println("ileti içinde ...");
def /*Çeşni$class*/$init$($this: Çeşni): Unit = {
()
}
}
}
</pre>Görünen o ki, metot gövdesi yukarıda yaptığımız sorgu sonrasında listelenen ikinci dosyaya, Çeşni$class.class, taşınmış. Yani, ortada kaybolup giden bir şey yok. Ama,çeşnimizin dönüşümünü Scala üstkavramları cinsinden veren bu çıktıda açıklama getirilmesi gereken bir nokta var: <code>Çeşni$class</code> çeşnisi, Java'ya nasıl çevrilecek? Çok bekletmeden yanıtını verelim: soyut sınıf olarak. Her şeyi bir arada gösteren aşağıdaki kod üzerinden anlamaya çalışalım.
<pre class="brush:scala;gutter:false" name="çeşnilerinByteCodeKarşılığı">trait Çeşni { def ileti() = println("ileti içinde...") }</pre><div style="text-align:center">⇓ Scala → Java</div><pre class="brush:java;gutter:false" name="çeşnilerinByteCodeKarşılığı">public interface Çeşni { public abstract void ileti(); }</pre><div style="text-align:center">+</div><pre class="brush:java;gutter:false" name="çeşnilerinByteCodeKarşılığı">public abstract class Çeşni$class {
static void ileti1(Çeşni $this) = { ... }
...
}</pre><pre class="brush:scala;gutter:false" name="çeşnilerinByteCodeKarşılığı">class ÇeşniciBaşı extends Çeşni { ... }</pre><div style="text-align:center">⇓ Scala → Java</div><pre class="brush:java;gutter:false" name="çeşnilerinByteCodeKarşılığı">public class ÇeşniciBaşı implements Çeşni {
...
void ileti() { Çeşni$class.ileti(this) }
...
}</pre>Özetleyecek olursak; çeşni tanımının sözleşmesi bir arayüze, gerçekleştirim ayrıntıları ise bir soyut sınıfa konulur; çeşninin katıldığı sınıfta ise çeşninin arayüzündeki iletilere karşılık gelen metotlar, işlerini soyut sınıftaki gerçekleştirime havale ederek görürler.<br />
<br />
Dikkatinizi çekeceğimiz son nokta, çeşni katılmış bir sınıfın nesnesine gönderilen iletilerin işlemesi sırasında <code><b>super</b></code> anahtar sözcüğünün anlamını ilgilendiriyor. Tekli sınıf kalıtlamanın geçerli olduğu ve arayüzlerin gerçekleştirim ayrıntısı içermediği Java'da üstsınıftaki yapıcı veya diğer metotlara atıfta bulunmak için kullanılan bu sözcük, çoklu çeşni kalıtlama, bir sınıfa birden çok çeşni katılabilmesi ve çeşnilerin gerçekleştirim ayrıntısı içermesi nedeniyle Scala'da anlaşılması daha zor bir anlama sahiptir. <a href="http://www.amazon.com/Programming-Scala-Comprehensive-Step---Step/dp/0981531644/ref=sr_1_1?ie=UTF8&qid=1318355374&sr=8-1">Programming in Scala kitabı</a>ndaki örnek ile anlamaya çalışalım.<br />
<pre class="brush:scala;gutter:false" name="çeşnilerinByteCodeKarşılığı">class Hayvan
trait Tüylü extends Hayvan
trait Bacaklı extends Hayvan
trait DörtBacaklı extends Bacaklı
class Kedi extends Hayvan with Tüylü with DörtBacaklı</pre>
<code><b>super</b></code> çağrılarının etkisini anlamak için, Scala'nın sıradüzenindeki türleri nasıl sıraya dizdiğini anlamak gerekir. <i>Doğrusallaştırma</i> adı verilen bu işlemin üç temel kuralı vardır:
<ol>
<li>Bir sınıf üstsınıfları ve katılan çeşnilerinin öncesinde doğrusallaştırılır.</li>
<li>Doğrusallaştırılmada daha önceden geçmiş bir tür bir daha sıraya konmaz.</li>
<li>Bir sınıfın üstsınıfı ve birden çok çeşnisi olması durumunda, ilk olarak en son katılan çeşni doğrusallaştırılır.</li>
</ol>
Anlamanızı kolaylaştırmak için yalın bir tanımı olan <code>Hayvan</code> sınıfından başlayalım. Birinci madde gereği, doğrusallaştırma sonucu türler <code>Hayvan</code>, <code>AnyRef</code>, <code>Any</code> şeklinde sıralanacaktır. Yani, <code>Hayvan</code> sınıfının içinde geçen bir <code><b>super</b></code> çağrısı, <code>AnyRef</code> sınıfı içindeki ilişkin metoda atıfta bulunacaktır. <code>Hayvan</code>'ı geliştiren <code>Tüylü</code> çeşnisinin doğrusallaştırılması ise, kendisi ve <code>Hayvan</code>'ın doğrusallaştırılması ile elde edilen <code>Tüylü</code>, <code>Hayvan</code>, <code>AnyRef</code>, <code>Any</code> sırasını verecektir. <code>Bacaklı</code> ve <code>DörtBacaklı</code> için de benzer bir şekilde oluşturulan sıralama, sırasıyla, <code>Bacaklı</code>, <code>Hayvan</code>, <code>AnyRef</code>, <code>Any</code> ve <code>DörtBacaklı</code>, <code>Bacaklı</code>, <code>Hayvan</code>, <code>AnyRef</code>, <code>Any</code> olarak bulunacaktır. <code>Hayvan</code> sınıfına doğrudan ve <code>Tüylü</code> ve <code>DörtBacaklı</code> çeşnileri üzerinden olmak üzere üç değişik yoldan ulaşan <code>Kedi</code> sınıfı, ikinci ve üçüncü maddeler akılda bulundurularak doğrusallaştırılmalıdır. Yani, istediğimiz sıra <code>Kedi</code> sınıfının <code>DörtBacaklı</code>, <code>Tüylü</code> ve <code>Hayvan</code>'ın doğrusallaştırılması ile birleştirilmesi sonucunda bulunacaktır. <code>DörtBacaklı</code>'nın doğrusallaştırılması ile elde edilen sıranın <code></code> öteki türlerin sıralamasındaki tüm türleri içermesi nedeniyle, sonuç <code>Kedi</code>, <code>DörtBacaklı</code>, <code>Bacaklı</code>, <code>Tüylü</code>, <code>Hayvan</code>, <code>AnyRef</code>, <code>Any</code> olarak ortaya çıkacaktır. Bu sıra, zincirleme <code><b>super</b></code> çağrılarının bulunduğu bir kodda bize denetim akışının izleyeceği yolu verir.<br />
<br />
</div><div style="text-align:right"><a alt="Önemli bir JSM dili: Scala" href="http://ta-java.blogspot.com/2011/05/java-platformunun-dikkate-deger-bir.html">İlk yazı</a><br />
</div><hr/><div id="dipnotlar" style="text-align:justify"><ol><li id="dn1">Değinilecek farklılıklardan çoğunun kaynak kodu kısaltıp okumayı kolaylaştırdığına ve alana özel diller geliştirirken gerekli olacak <a href="http://en.wikipedia.org/wiki/Fluent_interface">akıcı arayüz</a> yazımını olanaklı kıldığına dikkatinizi çekerim. <a href="#ref1">↑</a></li>
<li id="dn2"> Yazıyı okurken Scala'nın kimi özelliklerini gösterebilmek adına, kodumuzu sınıf yazma reçetesinin anlatıldığı yazıda<a alt="Java'da sınıf tanımı" href="http://ta-java.blogspot.com/2011/05/bileske-turler-snflar.html">🔎</a> tavsiye edilenden farklı bir biçimde oluşturduğumuzu aklınızdan çıkarmayınız. <a href="#ref2">↑</a></li>
<li id="dn3"> Hatırlayacak olursanız, <code><b>protected</b></code> niteleyicisi Java'da altsınıfların yanısıra, aynı paketteki türlere de erişim hakkı sağlar. <a href="#ref3">↑</a></li>
<li id="dn4"> Tanımlayıcı adının oluşturulmasında işleçler ve diğer karakterlerin birbirine karıştırılarak kullanılması mümkün değildir. <a href="#ref4">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-21109557856432853752011-09-25T03:16:00.004+03:002011-12-14T15:47:52.957+02:00Karakter Katarı Sınıfları<div id="anaMetin" style="text-align: justify;">Java; ad, adres, makale içeriği gibi metinsel bilgilerin temsil edilmesinde kullanılmak üzere iki başlık altında incelenebilecek seçenekler sunar: dizi temelli çözümler metni eleman türü <code><b>char</b></code> olan diziler olarak ele alırken, karakter katarı kavramını soyutlayan sınıflar—üç tane—0 veya daha fazla sayıda karakter içeren katarların kullanımını kolaylaştıran iletiler sağlar. Bizim de bu yazıda yapacağımız, ikinci grupta geçen seçeneklere bakmak olacak.<br />
<br />
İşimize neden üç sınıf bulunduğuna açıklık getirerek başlayalım. <code>StringBuilder</code> sınıfının varlık nedeni, <u>içeriği değişmeyen</u> karakter katarlarını temsil eden <code>String</code> sınıfının aşağıdaki kullanımının çalışma hızına dönük yorumla açıklanabilir.<br />
<pre class="brush:java; gutter:false;highlight:[6, 15]" name="nedenStringBuilder">public class SBuilder {
...
public static String ekle(String katar, char[] ek) {
String sonuç = katar;
for (int i = 0; i < ek.length; i++)
sonuç = sonuç + ek[i];
return sonuç;
} // String ekle(String, char[])
...
} // SBuilder sınıfının sonu
...
String ktr = "Ali";
char[] dizi = {'V', 'e', 'l', 'i'};
ktr = SBuilder.ekle(ktr, dizi);
</pre>İlk argümanındaki karakter katarına ikinci argümanındaki karakter dizisinin eklenmesi ile elde edilen sonucu döndüren <code>ekle</code>, işaretlenmiş satırdan dolayı işini yüksek maliyetli bir biçimde yapmaktadır. Benzer bir gözlem, son satırdaki atama için de geçerlidir. Bunun sebebi, <code>String</code> nesnelerinin değişmez içeriğe sahip olması ve içerik değişikliği etkisinin bitiştirme işleci (+) sonucunun aynı tutacağı güncellemesi yoluyla sağlanmasında yatar. Ne kastedildiğini döngü içindeki komutun işlemesi sırasında arka planda olup bitenleri açarak anlamaya çalışalım. <br />
<ol><li><code>sonuç</code> tarafından gösterilen <code>String</code> nesnesine <code>dizi</code>'nin döngü değişkeni ile gösterilen indisteki elemanının eklenmesi ile elde edilen yeni bir <code>String</code> nesnesi yaratılır.</li>
<li>Yapılan atama sonrasında <code>sonuç</code> yeni yaratılan nesneyi göstermeye başlar. Bu işlemin bir diğer sonucu olarak, <code>sonuç</code> tutacağının göstermekte olduğu eski nesne erişilmez olur ve çöp haline gelir.</li>
</ol>Yukarıdaki maddelerin döngünün her dönüşünde icra edildiği düşünüldüğünde, dizi uzunluğu sayısında yeni nesnenin yaratılıp aynı sayıda eski nesnenin çöp haline dönüşeceği görülecektir. Nesne yaratma bağlamında belleğin ayrılması ve ilkleme için gereken zaman çöpe dönüşen eski nesneler ile birlikte düşünüldüğünde, yöntemimizin hem zaman hem de bellek kullanımı açısından tatminkar olmadığı kabul edilecektir. Aradığımız şey, değişken içerikli karakter katarlarının desteklenmesidir ki, bu özellik <code>StringBuilder</code> sınıfı tarafından sağlanır. Yani, özetlemek gerekirse, <code>String</code> içeriği değişmeyen karakter katarlarını soyutlarken, <code>StringBuilder</code> içeriği değişen, uzayıp kısalabilen karakter katarlarını soyutlar.<br />
<br />
Bu noktada, sadece <code>StringBuilder</code> sınıfı ile yetinip içeriği değişmeyen karakter katarlarının kullanıcının disiplinli programlamasına bırakılmasıyla tek bir sınıfın yetebileceğini düşünenleriniz çıkabilir. Doğru ya, neden durup dururken karakter katarlarını içeriği değişen ve değişmeyen diye ayıralım ki? Bu haklı sorunun yanıtı, sabit içeriğin çok izlekli<a alt="İzleğin anlamı" href="http://ta-java.blogspot.com/p/programlama-sozlukcesi.html#izlek">≝</a>li (İng., thread) programlarda sağladığı avantajda yatar. Birden çok denetim akışının aynı nesneyi kullanması durumunun ortaya çıktığı bu tür programlarda, nesne içeriğinin değişkenlik göstermesi halinde söz konusu nesneye değişik izlekler içinden eşgüdümlü ve sırasallaştırılmış bir biçimde erişilmesi gerekir. Her zaman doğru yerine getirilmeyen bu koşul, çalışma hızını olumsuz etkilediği gibi programlamayı da zorlaştırır. Sabit içerikli nesnelerde ise durum farklıdır; içeriğin güncellenmeyeceği bilindiği için, söz konusu nesne izlekler içinden herhangi bir sırada eşgüdüm kaygısı olmadan okunabilir. Dolayısıyla, hem programlama daha kolay olacaktır hem de ortaya çıkan program daha hızlı çalışacaktır. Bu nedenle, <code>String</code> ve <code>StringBuilder</code> sınıflarının her ikisine de gereksinim vardır. Peki, ya üçüncü sınıf <code>StringBuffer</code>? <b><code>StringBuilder</code> ile tamamıyla aynı işlevselliğe sahip olan bu sınıf, <code>StringBuilder</code>'ın performans kaygıları nedeniyle sağlamadığı birden çok izlek içinden güvenli kullanım garantisini ve <code>String</code>'in desteklemediği içerik değişkenliği özelliklerini birleştirir.</b><br />
<br />
<table align="center" border="1" style="text-align: center;"><tbody>
<tr><td></td><td>Değişken içerik</td><td>Çok izleklilik</td></tr>
<tr><td align="left"><code>String</code></td><td>✘</td><td>✔</td></tr>
<tr><td align="left"><code>StringBuffer</code></td><td>✔</td><td>✔</td></tr>
<tr><td><code>StringBuilder</code></td><td>✔</td><td>✘</td></tr>
</tbody></table><br />
<i>Not</i>: Yazımızın devamında yukarıdaki farklılıkları dışında işlevsel olarak birbirlerinin aynısı olan <code>StringBuilder</code> ve <code>StringBuffer</code> sınıflarını ayrı ayrı incelemeyeceğiz; birisi için yapacağımız açıklamalar diğeri için de geçerli olacak.<br />
<br />
Karakter katarlarını temsil etmek için neden üç tane sınıf sağlandığına açıklık getirdikten sonra, dışsallaştırma desteğine işaret eden <code>java.io.Serializable</code> arayüzüne ek olarak her üç sınıfa da ortak olan <code>CharSequence</code> arayüzündeki iletilerin açıklamaları ile devam edelim. <code>Object</code> sınıfındaki ilişkin metodun gerçekleştiren sınıflarda ezilmesini garanti etmek için <code>toString</code>'i içeren bu arayüz, katar içindeki karakter sayısını döndüren <code>length</code> ve argümanındaki indiste bulunan karakteri döndüren <code>charAt</code> iletilerine ilaveten hedef nesnenin argümanlarda belirtilen aralıktaki dilimini <code>CharSequence</code> tutacağı ile gösterilecek şekilde döndüren <code>subSequence</code> iletisini içerir.<br />
<br />
Her üç karakter katarı sınıfında da bulunan diğer iletiler, hedef nesnenin içeriğini sorgulamaya yarayan iletilerdir. Bunlardan <code>substring</code>, hedef nesnenin belirtilen dilimi içindeki karakterlerden oluşan bir <code>String</code> döndürür ve iki uyarlamaya sahiptir. Uyarlamalardan ilki argümanlarda verilen sınırlar dahilindeki dilimi döndürürken, tek argümanlı olan ikinci uyarlama yegâne argümanda geçirilen indiste başlayıp katarın sonuna kadar tüm karakterleri kapsayan dilimi döndürür.<br />
<br />
<div style="text-align: center;"><code>alıcı.substring(i, alıcı.length()) ≡ alıcı.substring(i)</code></div><br />
Ortak olan bir diğer ileti grubu, karakterlerin Java temsilinde kullanılan UTF-16 kodlamasının bir özelliğini hatırlamamızı gerektiriyor: Her ne kadar modern dillere ait tüm simgeler iki sekizli genişliğindeki <code><b>char</b></code> değerlerle temsil ediliyorlarsa da, kimi özel ilgi gruplarının yararlandığı simgeler dört sekizliyle temsil edilir. (Ayrıntı için, buraya<a alt="İki sekizliden uzun Unicode karakterlerin Java'da temsili" href="http://ta-java.blogspot.com/2011/03/ilkel-turler-karakterler_30.html#istisnaiKarakterler">🔎</a> bakınız) Bir diğer deyişle, çoğu karakter (simge) tek bir <code><b>char</b></code> ile temsil edilirken kimileri ardışık iki <code><b>char</b></code> ile temsil edilebilir. Bu tür karakterleri içeren katarlara <code>charAt</code> iletisinin gönderilmesi, beklenen sonucu vermeyecektir; sorgulamanın başarıyla yapılması karaktere dair her iki <code><b>char</b></code> değerin birlikte okunmasıyla mümkün olur ki, incelediğimiz sınıflar bu amaçla <code>charAt</code> yerine kullanılmak üzere <code>codePointAt</code> iletisini sağlar. Argümanındaki indiste bulunan <code><b>char</b></code> değeri okuyan bu ileti, okuduğu değerin özel gösterimli karakterlerden birine ait olması durumunda ikinci bir <code><b>char</b></code> daha okur ve sonucu olarak okuduğu değeri—duruma göre bir veya iki <code><b>char</b></code>—bir <code><b>int</b></code> olarak döndürür. Benzer bir şekilde çalışan <code>codePointBefore</code>, kendisine geçirilen indisin öncesindeki karakteri döndürür. Bu noktada, bir hatırlatmanın yapılması yerinde olacaktır: geçirilen indisin değeri <code>codePointAt</code> için 0 ile katardaki <code><b>char</b></code> sayısının bir eksiği aralığında iken, <code>codePointBefore</code> için 1 ile katardaki <code><b>char</b></code> sayısı aralığında olmalıdır. İşini karakterlerin temsilindeki özel istisnayı göz önüne alarak gören bir diğer ileti, katar içinde kaç tane <code><b>char</b></code> olduğunu döndüren <code>length</code> yerine kullanılması gerekebilecek <code>codePointCount</code> iletisidir. Bu ileti, argümanlarında belirtilen indisler arasındaki karakterlerin, <code><b>char</b></code> değerlerin değil, sayısını döndürür. Dört sekizli ile temsil edilen karakterlerin dilimin başı ve/veya sonunda yarıda kesilmesi durumunda ise, karakter(ler)in varlığı döndürülen <code><b>int</b></code> değere yansıtılır. Herhangi bir indisin işaret ettiği konumdaki karakterin yarıda kesilip kesilmediği ise, <code>offsetByCodePoints</code> iletisi yardımıyla öğrenilebilir. Bu ileti, ilk argümanında verilen indis değerinden başlayarak ikinci argümanındaki sayı kadar sonraki karakterin hangi indiste başladığını döndürür.<br />
<br />
<code>indexOf</code> ve <code>lastIndexOf</code> iletilerinin ilk argüman olarak <code>String</code> alanları da her üç sınıfa ortaktır. Bu iletilerden <code>indexOf</code>, ilk argümanında geçirilen <code>String</code> nesnenin hedef nesne içinde ilk hangi indiste geçtiğini döndürürken, <code>lastIndexOf</code> son geçişin indisini döndürür. Aranan katarın hedef nesnede bulunmaması durumu ise dönüş değerindeki -1 ile haber verilecektir. İstenecek olursa, her iki ileti de aramalarını aldıkları ikinci bir argümanın sonrasına/öncesine sınırlayabilir. Bu noktada, tüm iletilerin <code>String</code> sınıfına özel olmak üzere, <code>String</code> yerine <code><b>char</b></code> türünde ilk argüman alarak çalışan uyarlamaları da olduğu söylenmelidir.<br />
<br />
Karakter katarı sınıflarına ait nesnelerin yaratılması için kullanabileceğimiz yapıcılara bakarak devam edelim. Her üç sınıf da varsayılan yapıcıya ek olarak argümanındaki <code>String</code> nesnenin kopyasını kullanarak ilkleme yapan yapıcılara sahiptir. Ancak, <code>StringBuilder</code> ve <code>StringBuffer</code> sınıflarının ortak bir özelliği, yapıcı kullanımı noktasında unutulmamalıdır: <code><b>int</b></code> argüman geçirilerek çağrılan yapıcı dışında, her iki sınıfta da nesnenin ilklenmesinde kullanılan karakterlerin ötesinde 16 <code><b>char</b></code>'lık bir bölge nesneye eklemeler yapılması durumunda kullanılmak üzere fazladan ayrılır. Bu gerçeği, aşağıdaki kod parçasının son satırındaki uzunluk ve sığayı döndüren <code>length</code> ve <code>StringBuilder</code> ile <code>StringBuffer</code>'a özel <code>capacity</code> iletilerinin ürettiği çıktıdan da gözlemleyebiliriz.<br />
<pre class="brush:java; gutter:false" id="yapıcılar1" name="yapıcılar1">String ad = "Tevfik", soyad = new String("Aktuğlu");
String adSoyad1 = ad.concat(" " + soyad);
StringBuilder adSoyad2 = new StringBuilder(adSoyad1);
System.out.println(adsoyad2.length()); // ⇒ 14
System.out.println(adsoyad2.capacity()); // ⇒ 30
</pre><div id="icrek" style="text-align:justify">Yukarıdaki kod parçası incelendiğinde bazı noktalar dikkatinizi çekecektir. Öncelikle, <code>String</code> nesneleri <code><b>new</b></code> işleci ve sınıf adı olmaksızın, ilklemede kullanılacak katarın tırnak içinde yazılması ile de yaratılabilir. Bir diğer deyişle, <code>String</code> türlü tutacaklar derleyici tarafından <code>String</code> türlü olarak görülen karakter katarı sabitleri ile ilklenebilirler. Ancak, bunun özel bir durum olduğu ve sizi şaşırtacak sonuçlara neden olabileceği unutulmamalıdır. Ne demek istediğimizi aşağıdaki kod parçasını inceleyerek görelim: bir önceki kod parçası ile aynı biçimde yaratılan iki nesnenin eşitlik denetimleri beklediğimiz sonuçları verirken, aynılık denetimleri birbirleriyle farklı sonuçlar vermekte.<br />
</div><pre class="brush:java; gutter:false" name="intern">String ad2 = "Tevfik", soyad2 = new String("Aktuğlu");
System.out.println(soyad.equals(soyad2)); // ⇒ true
System.out.println(ad.equals(ad2)); // ⇒ true
System.out.println(soyad == soyad2); // ⇒ false
System.out.println(ad == ad2); // ⇒ true
</pre>Aynılık denetiminin beklenilenin aksine <code><b>true</b></code> döndürmesinin nedeni, karakter katarı sabitleri için yerin <code>String</code> sınıfının yönetimindeki bir <i>içrek</i><a href="http://tr.wiktionary.org/wiki/i%C3%A7rek">≝</a> bellekten ayrılmasıdır. <b>Yer ayrımı öncesinde söz konusu sabite eşit bir değer için bu özel bölgeden daha önce yer ayrılıp ayrılmadığına bakılır ve yanıtın olumlu olması durumunda yeni yaratılacak bir <code>String</code> nesnenin tutacağındansa, önceden yaratılmış olan nesnenin tutacağı döndürülür.</b> Örneğimize dönecek olursak; <code>ad</code> tutacağının ilklenmesi noktasında içrek belleğin "Tevfik" değerine sahip bir katar içermemesi nedeniyle [içrek bellekte] yeni bir nesne yaratılarak döndürülmüş, <code>ad2</code>'nin ilklenmesi noktasında ise içrek bellekte aynı değerli bir katarın varlığı saptandığı için daha önceden yaratılan bu nesne kullanılmıştır. Yerden tasarruf sağlamasının yanısıra eşitlik denetimini aynılık denetimine indirgeyen bu özellik, iki karakter katarı sabitinin eşitlik denetiminde <code>==</code> işlecinin kullanılmasına olanak tanıyarak hızdan da kazandıracaktır.<br />
<br />
Karakter katarı sabitleri için geçerli olan içrekleştirme—yani, yerin içrek bellekten ayrılması—<code>intern</code> iletisi vasıtasıyla diğer <code>String</code> nesnelerine de yaygınlaştırılabilir. Bu ileti, hedef nesnenin içeriğine sahip olan fakat yeri içrek bellekten ayrılmış bulunan bir nesnenin tutacağını döndürür. Ancak; <code>intern</code> iletisinin tüm <code>String</code> nesneler için kullanılmasının yığın belleğin bir bölgesi olan içrek belleği kolayca doldurup taşırabileceği unutulmamalıdır.<br />
<pre class="brush:java; gutter:false" name="içrekleştirme">String soyad3 = soyad.intern();
String soyad4 = soyad2.intern();
System.out.println(soyad3 == soyad4); // ⇒ true
</pre>Yapıcılara dair ilk örneğimizde<a href="#yap%C4%B1c%C4%B1lar1">▵</a> açıklama bekleyen ikinci nokta, bitiştirme işleci (<code>+</code>) ile benzer bir işlev gören <code>concat</code> iletisidir. Argümanlarından birisinin <code>String</code> olması halinde diğerini de <code>String.valueOf</code> metotlarından uyumlu türe sahip olanını çağırarak <code>String</code>'e dönüştürüp diğeri ile bitiştiren <code>+</code> işlecinin aksine, <code>concat</code> iletisinin yegâne argümanının <code>String</code> olmaması derleyici hatasına neden olur.<sup><a href="#dn1" id="ref1">1</a></sup> Ayrıca, <code>concat</code> argümanındeki karakter katarının boş olması durumunda yeni bir nesnenin tutacağı yerine ileti alıcıyı döndürür.<br />
<br />
<code>String</code> sınıfının yapıcıları arasında, <code>StringBuffer</code> ve <code>StringBuilder</code> nesnelerinden <code>String</code> nesnelerine dönüşümü sağlayan <i>çeviren yapıcı</i>lara ek olarak, <code><b>byte</b></code>, <code><b>char</b></code> ve <code><b>int</b></code> dizilerini <code>String</code> nesnesine dönüştürenler de vardır. Bu yapıcılar, aynı veya farklı makinelerdeki süreçler arasında akan verilerin <code>String</code> olarak işlenmesini sağlamak için gereklidir. Örnek olarak, yazdığınız Java programının iletişimde bulunduğu bir C programından kendisine gönderilen karakter verileri işlemek istediğini düşünün. Karakter desteğinin ASCII tablosu üzerine inşa edilmesi nedeniyle tek sekizli büyüklüğündeki bir <code><b>char</b></code> türüne sahip C tarafından gönderilen karakterler, Java tarafında <code><b>byte</b></code> dizisi olarak görülecektir. Bu dizi içindeki karakterlerin ASCII kodlamasına uygun bir şekilde Java programında kullanılabilir bir karakter katarına çevrilmesi gereklidir. Bunun için yapılması gereken aşağıdaki gibi bir yapıcının kullanılması olacaktır.<sup><a href="#dn2" id="ref2">2</a></sup> Yapıcının ilk argümanı dönüşüme kaynaklık eden diziyi sağlarken, ikinci argüman dönüşümde kullanılacak karakter kodlamasını belirtir. İstenecek olursa, dizinin tamamı yerine bir dilimi kaynaklık edebileceği gibi, kodlama bir <code>java.nio.charset.Charset</code> nesnesi vasıtasıyla da bildirilebilir veya sağlanmadığı durumlarda Java platformunun o anki kodlaması olarak varsayılabilir. Mesela, aşağıdaki kod parçasının son satırı, <code>tampon</code>'da biriktirilmiş <code><b>byte</b></code>'ların ilk indisten başlayarak 100 tanesini, o anki kodlamayı kullanarak <code>String</code> nesnesine çevirmektedir.<br />
<pre class="brush:java; gutter:false" name="yapıcılar2">byte tampon[] = new byte[1024];
... // Karşı taraftan akan veriyle tampon'u doldur.
String katar = new String(tampon, "US-ASCII");
... // katar'ı kullan.
...// tampon'u yeniden doldur.
katar = new String(tampon, 0, 100);
</pre>Bu noktada, karşı taraftaki program C değil de Java programı olsaydı ve <code><b>byte</b></code> yerine <code><b>char</b></code> gönderiliyor olsaydı diye sorabilirsiniz. Hatta, ya gönderilen veri UTF-16'da iki <code><b>char</b></code> ile temsil edilebilen istisnai karakterleri de içeriyor olsaydı diye üsteliyebilirsiniz. Rahat olun, sorularınız yanıtlanacak ama ilk önce Java'dan C tarafına veri göndermek istediğimizde ne yapmamız gerektiğine bir göz atalım. Bu durumda, <code>String</code> nesnesi içindeki karakterlerin karşı taraftaki programın farkında olduğu bir kodlama ile <code><b>byte</b></code> dizisine dönüştüren <code>getBytes</code> işimizi görecektir. Hedef nesnedeki tüm karakterleri argümanındaki <code>String</code> veya <code>Charset</code> nesnesiyle belirtilen kodlamaya göre <code><b>byte</b></code> dizisine çeviren <code>getBytes</code>, argümansız kullanıldığı takdirde dönüşümü platformun o an varsayılan kodlamasını kullanarak yapar.<br />
<pre class="brush:java; gutter:false" name="getBytes">StringBuffer katar = new StringBuffer(...);
... // katar'ı güncelle.
String katar2 = new String(katar);
byte tampon[] = new byte[1024];
katar2.getBytes("US_ASCII")
... // katar2'nin içeriğini karşı tarafa akıt.
</pre><code>String</code> nesnesinden <code><b>char</b></code> dizisine benzer bir dönüşüm ise, diğer karakter katarı sınıflarınca da desteklenen <code>getChars</code> ile yapılabilir. Hedef nesnenin ilk iki argümanında belirtilen dilimini son argümandaki indisten başlayarak üçüncü argümanda sağlanan diziye kopyalayan bu ileti, <code><b>char</b></code> değerlerin ve <code>String</code> içindeki karakterlerin aynı kodlamayı kullanması nedeniyle kodlama bilgisine gereksinim duymaz. Aynı işi çok daha basit bir imza ile halletmek isterseniz, hedef nesnenin bütün karakterlerini hedef nesnenin uzunluk özelliğine göre yeri ayrılmış bir <code><b>char</b></code> dizisine kopyalayan ve sonucu olarak bu diziyi döndüren <code>toCharArray</code> iletisini de kullanabilirsiniz.<br />
<br />
Gelelim iki Java programı arasında gidip gelen <code><b>char</b></code>'ların <code>String</code> nesnesine dönüştürülmesi işine. Bunun için <code><b>char</b></code> dizisi veya dizi dilimi alan yapıcılardan biri kullanılabileceği gibi, aynı parametre listelerine sahip uyarlamaları bulunan <code>String.copyValueOf</code> metotları da kullanılabilir. Kimi zaman aynı iş için <code><b>int</b></code> dizisi veya dizi dilimi alan yapıcıların kullanılması gerekebilir. Bu gereklilik iki nedenden ötürü ortaya çıkar: i) veri kaynağında UTF-16'nın tek <code><b>char</b></code>'da temsil edemediği karakterlerin var olması, ii) okuduğu <code><b>char</b></code> değere ek olarak aykırı durumlar için de kullanılan ekstra dönüş değerlerinden dolayı <code><b>int</b></code> döndüren <code>java.io.BufferedReader</code> sınıfındaki <code>read</code> ve benzeri iletilerin doldurduğu dizilerin işlenmesi.<br />
<br />
Alışageldiğimiz eşitlik denetimi (<code>equals</code>), hoş yazım (<code>toString</code>) ve kıyım fonksiyonunun (<code>hashCode</code>) ezilmesine ek olarak, <code>Comparable</code> arayüzünü gerçekleştiren <code>String</code> sınıfı, doğal olarak, <code>compareTo</code> iletisine karşılık bir metot sağlar. Ayrıca, büyük-küçük harf ayrımı gütmeksizin işini gören eşitlik denetimi (<code>equalsIgnoreCase</code>) ve karşılaştırma (<code>compareToIgnoreCase</code>) iletileri de sunulur. Bu iki iletiyi, <code>toLowerCase</code> iletisinden de yararlanarak şu şekilde tanımlamamız mümkündür.<sup><a href="#dn3" id="ref3">3</a></sup><br />
<br />
<div style="text-align: center;"><code>alıcı.equalsIgnoreCase(k)<br />
≡<br />
alıcı.toLowerCase().equals(k.toLowerCase());<br />
alıcı.compareToIgnoreCase(k)<br />
≡<br />
alıcı.toLowerCase().compareTo(k.toLowerCase());</code></div><br />
<code>compareTo</code> ve <code>compareToIgnoreCase</code> iletilerini Türkçe içerikli katarları karşılaştırmak için kullandığınızda beklemediğiniz bir sürprizle karşılaşabilirsiniz. Çünkü, bu iletilerin katarlardaki aynı indisli <code><b>char</b></code>'ların Unicode tablosundaki sıralarının farkını döndürerek işini gören <code>String</code> sınıfındaki gerçekleştirimleri, Türkçe'de olup İngilizce'de olmayan harflerin Unicode tablosunda diğerlerinden sonra gelmesi nedeniyle, aşağıdaki gibi alfabemizdekiyle çelişkili sonuçlar döndürebilir.<br />
<pre class="brush:java; gutter:false" name="compareToSürpizi">boolean önceMi = "a".compareTo("b") > 0; // önceMi ← true
önceMi = "ş".compareTo("t") > 0; // önceMi ← false
</pre>Bu can sıkıcı durumun giderilmesi, bir yörenin (<code>Locale</code>) özelliklerine bağlı kalarak karşılaştırma yapan karşılaştırıcı nesne (<code>Collator</code>) kullanımı ile olanaklıdır.<br />
<pre class="brush:java; gutter:false" name="compareToSürpizi">import java.text.Collator;
import java.util.Locale;
...
Collator tr = Collator.getInstance(new Locale("tr"));
...
önceMi = tr.compare("ş", "t") < 0; // şÖncet ← true
</pre>Eşitlik denetimi amacıyla kullanılabilecek bir diğer ileti, <code>StringBuffer</code> ve <code>CharSequence</code> türlü iki uyarlaması bulunan tek argümanlı <code>contentEquals</code> iletisidir. <code>equals</code> iletisinin varlığında gereksiz gibi gözükebilecek bu ileti, derleyiciye sağladığı ekstra hata denetimi imkanı ve performans avantajından dolayı tercih edilmelidir. <code>equals</code> iletisinin <code>Object</code> türlü bir argümana sahip olmasının bir sonucu olarak, bu iletinin <code>String</code> sınıfı içindeki gerçekleştirimi olan metotta, ilk yapılan şey argümanın <code>String</code> türlü olup olmadığının kontroludur. <code>contentEquals</code> iletisinin gerçekleştirimlerinde ise, daha sıkı bir parametre türü bulunduğu için böylesine bir kontrola gerek yoktur.<br />
<br />
Tabii, bazılarınız <code>StringBuffer</code>'a özel bir uyarlamanın bulunup da <code>StringBuilder</code>'a özel bir uyarlamanın neden bulunmadığını sorabilir. Bu haklı sorunun yanıtı, <code>StringBuilder</code>'ın değişken içerikli ve tek izlekli olmasında yatar. Bu sınıf, nesnelerinin aynı anda tek bir izlek içinden kullanılacağı varsayılarak gerçekleştirilmiş, çok izlekli kullanım durumlarında ise sorumluluğu programcıya bırakmıştır. Dolayısıyla, <code>StringBuilder</code> sınıfı çok izlekli bir programda eşitlik denetiminin başladığı noktadan bittiği noktaya kadar karşılaştırılmaya söz konusu olan nesnenin bir başka izlek içinden değiştirilmeyeceğini garanti edemez. Bu ise, sonucun döndürüldüğü noktada bile yanlış olabileceği anlamına gelir. Aşağıdaki maddeleri izleyerek böyle bir durumun nasıl ortaya çıkabileceğini görelim. <br />
<ol><li>(İzlek 1) Eşitlik denetimi başlar. İki uzunluklu katarlardaki (<code>String</code> ve <code>StringBuilder</code>) ilk <code><b>char</b></code>'ların eşit olduğu görülür.</li>
<li>(İzlek 2) Argüman olarak geçirilen <code>StringBuilder</code> nesnesinin ilk indisindeki <code><b>char</b></code> değiştirilir.</li>
<li>(İzlek 1) İkinci ve son <code><b>char</b></code>'ların eşit olduğu görülerek <code><b>true</b></code> döndürülür. Halbuki, diğer izlek tarafından yapılmış değişiklik nedeniyle, bu sonuç o anki durumu yansıtmamaktadır.</li>
</ol>Bu noktada, <code>StringBuilder</code> nesnelerinin <code>String</code> nesneleri ile eşitlik denetiminin <code>equals</code>'a mahkum edilerek bu sınıfa haksızlık edildiğini düşünüyorsanız, size bir kere daha her üç karakter katarı sınıfının da <code>CharSequence</code> arayüzünü gerçekleştirdiğini hatırlatayım. İstenecek olursa, <code>contentEquals</code> iletisinin <code>CharSequence</code> tutacağı bekleyen uyarlaması kullanılarak <code>equals</code>'a göre hızlı bir karşılaştırma yapılabilir. Ancak, bu bağlamda şu uyarının yapılması yerinde olacaktır: <code>StringBuilder</code> ve <code>StringBuffer</code> sınıfları kısa süre içinde yoğun bir şekilde değişmesi beklenen karakter katarlarını modellemek için düşünülmüştür; daha sonra yararlanılacak gerekli bilgiyi biriktirmek için tampon olarak kullanılan ve tipik olarak kısa ömürlü olan bu sınıflara ait nesnelerin eşitlik denetimi ve karşılaştırma gibi işlemlere konu olması beklenmez. Bundan dolayıdır ki, <b>her iki sınıf da <code>Comparable</code> arayüzünü gerçekleştirmez, <code>Object</code> sınıfından kalıtladıkları <code>equals</code> ve <code>hashCode</code> metotlarını ezmez.</b><code> </code><br />
<br />
<code>equals</code> iletisinin bir diğer akrabası olan <code>regionMatches</code>, hedef nesnenin bir diliminin kendisine geçirilen bir diğer <code>String</code> nesnenin eşit uzunluklu dilimi ile eşit olup olmadığı sorusuna yanıt bulur. İstendiği takdirde, geçirilecek bir bayrak değeri ile eşitlik denetiminin büyük-küçük harf ayrımı yapıp yapmaması da kontrol edilebilir.<br />
<br />
Eşitlik denetimini andıran <code>matches</code> yüklemi, hedef nesnenin argümanında sağlanan düzenli ifadenin (İng., regular expression) betimlediği karakter katarlarından biri olup olmadığına bakar. Örneğin, aşağıdaki kod parçasının son satırında "H" veya "Hayır" girilmesi durumunda programdan çıkılması isteniyor. <br />
<pre class="brush:java; gutter:false" name="matchesÖrneği">import java.util.Scanner;
...
System.out.println("Devam etmek istiyor musunuz? ");
String devamıMı = new Scanner(System.in).nextLine();
...
if (devamMı.matches("H(ayır)?")) System.exit(0);
</pre>Düzenli ifade kullanarak işini gören bir diğer ileti, hedef nesnenin argümandaki düzenli ifadeyle betimlenen karakter katarlarının bulunduğu konumlardan bölündüğü alt katarları <code>String</code> dizisi içinde döndüren <code>split</code>'tir. Örneğin, aşağıdaki kod parçasının son satırı, <code>satır</code> içinde birbirlerinden ";" veya ":" ile ayrılmış olan katarları döndürecektir. İstenecek olursa, <code>split</code>'e ikinci argümanında geçirilen bir <code><b>int</b></code> değerle parçalardan ilk kaçının kullanıcıyı ilgilendirdiği de belirtilebilir. <br />
<pre class="brush:java; gutter:false" name="splitÖrneği">String satır;
... // satır'ın içine bir şeyler doldur.
String[] parçalar = satır.split(";|:");
</pre>Göz atacağımız son düzenli ifade kullanan <code>String</code> sınıfı iletileri, ilk argümanlarında betimlenen karakter katarlarının yerine ikinci argümanlarındaki <code>String</code> nesnenin içeriğini yerleştiren <code>replaceAll</code> ve <code>replaceFirst</code> iletileridir. Bunlardan ilki, istenen değişikliği tüm noktalarda uygularken, ikincisi sadece uyan ilk noktadaki parçayı değiştirir. Buna göre, aşağıdaki kod parçasının son satırı sonrasında, <code>yeniSatır</code> <code>satır</code>'ın başı ve sonu boşluklardan arındırılmış ve içindeki boşlukların "?" ile değiştirilmiş halini tutacaktır. Anlatım dilinin olası aldatıcılığını düşünerek <code>String</code> nesnelerinin değişmezlik özelliğini bir kez daha hatırlatmakta yarar var: hedef nesne—örneğimizde <code>satır</code>'ın gösterdiği nesne—kesinlikle değişmemektedir; dönüşümler döndürülen <code>String</code> nesneye—örneğimizde <code>yeniSatır</code>'ın gösterdiği nesne—yansıtılmaktadır. <br />
<pre class="brush:java; gutter:false" name="replaceAllÖrneği">String satır;
... // satır'ın içinde bir şeyler oku.
String yeniSatır = satır.trim().replaceAll(" ", "?");
</pre>İlk argümanda belirtilen değerin hedef nesne içinde geçtiği tüm konumlarda ikinci argümandaki ile değiştirildiği hedef nesne mutantını döndüren <code>replace</code> iletisi, <code><b>char</b></code> ve <code>CharSequence</code> argümanlı olmak üzere iki uyarlamaya sahiptir.<br />
<br />
Biraz nefes alarak, yolumuza <code>String</code> nesnelerinin içerik denetiminde yararlanılabilecek yüklemlere göz atalım. Hedef nesnenin boş olup olmadığına yanıt veren <code>isEmpty</code>'ye ek olarak, <code>endsWith</code> ve <code>startsWith</code>, sırasıyla, hedef nesnenin argümandaki <code>String</code> nesne ile sonlanıp sonlanmadığı ve başlayıp başlamadığı sorusunu yanıtlarken, <code>contains</code> argümanındaki <code>CharSequence</code> kategorisindeki nesnenin hedef nesnede geçip geçmediğine yanıt verir. <code>startsWith</code> ile sorulan sorunun ikinci bir argüman geçirilmek suretiyle hangi indisten başlayarak geçerli olduğu da söylenebilir.<br />
<br />
Programlamanın <code>printf</code> ve <code>scanf</code>'den ibaret olduğunu düşünen C gezegeni vatandaşları, eğer bu noktaya kadar sabredebildilerse, bayram edebilirler: <code>String</code> sınıfından göz atacağımız son şey, <code>sprintf</code> fonksiyonunun dengi olan <code>format</code> metodu. Bu metot, ilk argümanında sağlanan format katarının dönüştürülmesiyle elde edilen <code>String</code> nesnesinin tutacağını döndürür. <code>java.util.Formatter</code> sınıfında anlatılan kurallara göre oluşturulan format katarındaki dönüşüm, <code>format</code>'a geçirilen diğer argümanların format katarı içinde eşleştirildiği bildirime göre <code>String</code>'e dönüştürülüp format katarı içine yerleştirilmesi ile yapılır. Örneğin; aşağıdaki tanımların ardından, <code>a</code>'nın <code>%d</code> ile 10'lu tabana, <code>b</code>'nin <code>%o</code> ile 8'li tabana, <code>a + b</code>'nin <code>%x</code> ile 16'lı tabana ve <code>%n</code>'nin yeni satır karakterine dönüştürülerek format katarı içine yerleştirilmesiyle <code>ktr</code> değişkeni "a(37) + b(111) = 6e\n" ile ilklenecektir. <br />
<pre class="brush:java; gutter:false" name="replaceAllÖrneği">int a = 37, b = 73;
String ktr = String.format("a(%d) + b(%o) = %x%n", a, b, a + b);
</pre>Gelelim <code>StringBuilder</code> ve <code>StringBuffer</code> sınıflarına. Bu sınıflar, değişken içerik ve ekleme sonrasında ortaya çıkacak uzamanın maliyetini azaltmaya dönük olarak tutulan boş bölge nedeniyle, <code>String</code> sınıfından farklı olarak ekleme, silme ve değiştirme iletileriyle sığa işlemleme işlemlerini de destekler. Daha önce de söylendiği gibi, bu sınıfların nesnelerinin yaratılması sırasında ilklemede kullanılan katarın ihtiyacından on altı <code><b>char</b></code> daha geniş bir yer ayrılır. Nesnenin kullanımı esnasında, taşma olmadığı müddetçe eklemeler bu fazlalık alandan yararlanılarak yapılacak ve taşmanın söz konusu olduğu ilk noktada nesneyi tutan bellek bölgesi genişletilecektir.<br />
<br />
İçeriği güncelleyen iletilerin değişiklikleri hedef nesnede yaptığı ve ileti alıcıyı döndürdüğünün bilinmesi zincirleme ileti gönderimini ve dolayısıyla daha akıcı okunabilen kod yazımını olanaklı kılacaktır. Örnek olarak aşağıdaki kod parçasının 7. satırını ele alalım. Bu satır, zincirleme ileti gönderme özelliği sayesinde <code>önder.insert();</code> ve <code>önder.trimToSize();</code> gönderilerinin arka arkaya gönderilmesiyle elde edilecek etkiyi yaratacaktır. Benzer bir durum 4. satır için de söz konusudur. Ayrıca, <code>append</code> ve <code>insert</code> iletilerinin tüm ilkel ve bileşke türlü değerleri alabilecek şekilde aşırı yüklenerek gerçekleştirildiğinin de bilinmesi gereksiz yere kullanılacak dönüşümleri ortadan kaldırmak suretiyle daha kısa kod yazılmasını olanaklı kılacaktır. <br />
<pre class="brush:java; gutter:ture" name="öznitelikİşlemleme">StringBuffer ad = new StringBuffer("Mustafa");
StringBuilder önder = new StringBuilder(ad);
System.out.println(önder.capacity()); // ⇒ 23
önder.append('_').append("Atatürk");
önder.setCharAt(7, ' ');
System.out.println(önder.capacity()); // ⇒ 23
önder.insert(8, "Kemal ").trimTosize();
System.out.println(önder.capacity()); // ⇒ 21
int i = önder.indexOf("Atatürk");
önder.replace(i, önder.length(), "ATATÜRK");
System.out.println(önder); // ⇒ Mustafa Kemal ATATÜRK
</pre>Yukarıdaki kod parçasında iki noktaya açıklık getirilmesinde yarar var. Öncelikle, yaratılmakta olan nesneyi argümanında geçirilen <code>StringBuffer</code> nesnenin içeriği ile ilkleyen ikinci satırdaki yapıcı çağrısının <code>StringBuilder</code> sınıfında desteklenmediğini düşünen arkadaşları yatıştıralım. <code>StringBuilder</code> sınıfında <code>StringBuffer</code> argüman alan bir yapıcının bulunmaması ile telaşlanan bu arkadaşlar her üç karakter katarı sınıfının da <code>CharSequence</code> arayüzünü desteklediğini ve arayüz tutacaklarının çokbiçimli kullanımını anımsarlarsa rahatlayacaklardır. Çünkü, <code>StringBuilder</code>—ve onun çok izlekli uyarlaması olan ikizi <code>StringBuffer</code>—<code>CharSequence</code> arayüzü türlü tutacak bekleyen bir yapıcıya sahiptir. Dolayısıyla, <code>StringBuffer</code> nesneler <code>CharSequence</code> kategorisinde oldukları için, 2. satırda <code>CharSequence</code> türlü tutacak bekleyen yapıcı çağrılacaktır. Açıklık getirilmesi gereken ikinci nokta, ekleme ve silme işlemlerinin etkiledikleri indis değerlerine bağlı olarak maliyetlerinin artabileceğidir. 6. satırdaki <code>insert</code> iletisini ele alalım. Bu gönderi, hali hazırda on beş karakter içeren bir katara 8. indisten başlayarak altı karakterli bir katar eklemektedir. Bu isteğin yerine getirilebilmesi için, sondaki yedi karakterin kaydırılarak eklenecek karakterler için yer açılması gerekir. Gönderilerin düzenlenerek <br />
<div style="text-align: center;"><code>önder.append(' ').append("Kemal ").append("Atatürk")</code> </div>şekline dönüştürülmesi daha iyi bir çözüm olacaktır. Çünkü, katar sonuna ekleme yaptığı için karakterlerın kaydırılmasına neden olmayan <code>append</code>'in maliyeti daha düşüktür. Hatta, gönderilerin birleştirilerek tek bir postada işin halledilmesi daha da iyi olacaktır. Son olarak; benzer maliyet kaygılarının argümanında sağlanan indisteki karakteri silen <code>deleteCharAt</code> ve argümanlarında belirlenen katar dilimini silen <code>delete</code> iletileri için de geçerli olduğu unutulmamalıdır.<br />
<br />
<code> append</code> ve <code>insert</code> iletilerinin tamsayı türlü değerler geçirilerek kullanılan uyarlamalarında şu nokta unutulmamalıdır: hedef nesneye eklenen, sayının hoş yazımla elde edilen gösterimidir, karakter tablosunun sayı ile belirtilen indisindeki karakter değil. İstenenin ikinci seçenek olması durumunda <code><b>int</b></code> argüman alan <code>appendCodePoint</code> iletisinin kullanılması gerekir. Aşağıdaki kod parçasını takip ederek farkı anlamaya çalışalım. İkinci satırdaki <code>append</code> boş katara "65" eklerken, bir sonraki satırda gönderilen <code>appendCodePoint</code> iletisi katar sonuna Unicode tablosunun 65. konumundaki 'A' karakterini ekleyecektir. <br />
<pre class="brush:java; gutter:ture" name="appenCodePoint">StringBuilder katar = new StringBuilder("");
katar.append(65);
katar.appendCodePoint(65);
System.out.println(katar); // ⇒ 65A
</pre><code>capacity</code>, <code>ensureCapacity</code> ve <code>trimToSize</code> <code>Vector</code><a alt="Java'da değişken uzunluklu diziler-java.util.Vector" href="http://ta-java.blogspot.com/2011/09/degisken-uzunluklu-diziler.html">🔎</a> sınıfındaki adaşları ile benzer şekilde çalışırken, <code>length</code> iletisi katardaki <code><b>char</b></code> sayısını döndürür. <code>setLength</code> ise, geçirilen argümanın ve o anki uzunluk değerine göre katarı uzatıp kısaltabilir; argümanın o anki uzunluktan büyük olması durumunda hedef nesne uzatılarak yeni konumlar '\u0000' değeri ile doldurulurken, yeni uzunluk değerinin eskisinden küçük olması durumunda hedef nesne yeni uzunluk ile eskisi arasındaki değerlerin kırpılması ile küçültülecektir.<br />
<br />
<code>StringBuilder</code> ve <code>StringBuffer</code> sınıflarındaki iletilerden değineceğimiz sonuncusu, hedef nesnenin içeriği yerinde tersine çeviren <code>reverse</code> iletisidir. Gelin bu iletiyi kullanan ufak bir örnekle yazımızı bitirelim. <br />
<pre class="brush:java; gutter:ture" name="reverseÖrneği">import java.io.Console;
import java.util.Locale;
public class Palindromik {
public static void main(String[] ksa) {
System.out.print("Palindromikliği sınanacak katar: ");
String sözcük = System.console().readLine().trim().toUpperCase(new Locale("TR"));
String tersi = new StringBuilder(sözcük).reverse().toString();
if (sözcük.equals(tersi)) System.out.println("palindromik");
else System.out.println("palindromik değil");
} // void main(String[]) sonu
} // Palindromik sınıfının sonu
</pre>Yukarıdaki kodu düzenleyerek bazı alternatif çözümler geliştirebilirsiniz. Mesela, küçük harften büyük harfe çeviren <code>toUpperCase</code> yerine ters yönde çeviri yapan <code>toLowerCase</code> iletisini kullanmanız aynı işi görecektir. Ya da, <code>equals</code> yerine <code>compareTo</code> iletisi kullanılarak dönen değerin 0 ile eşitlik denetiminin yapılması da aynı kapıya çıkacaktır. Ancak; <code>String</code> sınıfını işe bulaştırmadan kullanılacak bir <code>equals</code> <code>Object</code> sınıfındaki aynılık denetimi yapan aynı adlı metodu çağıracağı için beklediğinizden farklı sonuçlar verecektir. Çünkü, <b><code>StringBuilder</code> ve <code>StringBuffer</code> sınıfları, değişken içerikli ve genelde kısa ömürlü karakter katarı tamponlarını soyutlar; bu özellikteki nesnelerin eşitlik denetimi ve karşılaştırılmaları içeriklerinin değişkenliği nedeniyle pek anlamlı değildir. Yapılması gereken, ihtiyaç duyulan bilginin <code>StringBuilder</code> veya <code>StringBuffer</code> nesnesi içinde doldurulması sonrasında <code>String</code> nesnesine çevrilmesi ve yola öyle devam edilmesidir.</b><br />
</div><br />
<div style="text-align:right"><a alt="Java'da karakter türü: char" href="http://ta-java.blogspot.com/2011/03/ilkel-turler-karakterler_30.html">Karakterler: <code><b>char</b></code> Türü</a><br/>
<a alt="Java'da düzenli deyimler-java.util.regex.Matcher, java.util.regex.Pattern" href="http://ta-java.blogspot.com/2011/11/duzenli-deyimler.html">Düzenli Deyimler</a><br />
</div><hr /><div id="dipnotlar" style="text-align: justify;"><ol><li id="dn1"> Bitiştirme esnasındaki dönüşümün nesneye <code>toString</code> iletisi gönderilerek yapıldığını düşünenler yanılmıyorlar; dönüştürülecek değerin bileşke türlü olması durumunda işi kotaran gerçekten de hoş yazım iletisi olarak da bilinen <code>toString</code>'dir. Ancak, hoş yazımı istenen değerin ilkel türden olması halinde, ileti alan tutacakları olmaması nedeniyle, iş <code>String.valueOf</code> metotlarından uygun olanı çağrılarak yerine getirilir. Aslına bakacak olursanız, bir nesnenin hoş yazımı için gönderilen <code>toString</code> iletisi, <code>Object</code> türlü argüman bekleyen <code>String.valueOf</code> metodu içinden gönderilir. <a href="#ref1">↑</a></li>
<li id="dn2"> Doğruyu söylemek gerekirse, böyle bir durumda <code><b>byte</b></code>'tan <code><b>char</b></code>'a dönüşümü otomatikman yapan <code>java.io.InputStreamReader</code> gibi bir akak sınıfının kullanılması daha yerinde olacaktır. <a href="#ref2">↑</a></li>
<li id="dn3"> Benzer bir denklik, <code>toUpperCase</code> ile de kurulabilir. <a href="#ref3">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-91876707007366835532011-09-14T00:17:00.001+03:002012-01-31T22:39:51.429+02:00Değişken Uzunluklu Diziler: java.util.Vector<div id="anaMetin" style="text-align: justify;">
Dizi yapısının belki de en büyük artısı, eleman erişim ve eleman güncellemenin sabit maliyetli olmasıdır; hangi indisteki eleman söz konusu olursa olsun, doğrudan erişim sayesinde her iki işlem de sabit (ve yüksek) hızlı bir şekilde yapılabilmektedir. Ancak, bunu mümkün kılan "elemanların dizinin yaratıldığı noktada ayrılan ardışık bellek konumlarına yerleştirilmesi" özelliği, yapının statik olması sonucunu da doğurur. Yani, dizinin yaratılması sonrasında uzunluğunun değiştirilmesi olanaklı değildir. Dolayısıyla, kullanım sırasında bir taşmanın olmaması için, yer ayrımının olası en yüksek eleman sayısı düşünülerek yapılması gerekir. Bunun sonucu da kimi zaman, ayrılan yerin tümünden yararlanılmadığından, belleğin verimsiz kullanımı olacaktır.<br />
<br />
Bu olumsuzluğu gidermek için, dizi tutacağını yeri yeni ayrılmış ve baş kısmında eski dizi nesnesinin kopyasına sahip daha büyük bir dizi nesnesini gösterecek şekilde güncellemeyi düşünebilirsiniz. Java karşılığı aşağıda sunulan bu yaklaşımın iki zaafı vardır: i) dizinin büyümesi için gerekli olan kod programcı tarafından yazılmak zorundadır ve ii) programcı dizinin ne zaman büyümesi gerektiğini bilmelidir.<br />
<pre class="brush:java; gutter:false" name="diziBüyütme">int[] dizi = {0, 1, 4};
...
dizi = Arrays.copyOf(dizi, dizi.length * 2);
dizi[3] = 9;
</pre>
İşte bu yazımızda, Java kitaplığı tarafından değişken uzunluklu dizileri desteklemek amacıyla sağlanan <code>java.util.Vector</code> sınıfına bakacağız. <code>Vector</code> nesnelerinin sahip olması gereken öznitelikler ile başlayalım: <i>sığa</i>, <i>eleman sayısı</i> ve <i>sığa artımı</i>. Sığa, bir <code>Vector</code> nesnesinin büyümeye gerek olmadan kaç değer tutabileceğini gösterirken; eleman sayısı, sorgulandığı anda <code>Vector</code>'de kaç değer tutulmakta olduğunu gösterir. <code>Vector</code> nesnesinin dolması sonrasında—yani eleman sayısı ile sığanın eşit olması halinde—yeni bir elemanın eklenmesi istenecek olursa, taşmayı önlemek adına <code>Vector</code> nesnesinin sığası, sığa artımı özelliğinde öngörüldüğü miktarda artırılarak yeni eleman için yer açılır ve ekleme yapılır.<br />
<br />
<h3>
Yapıcılar</h3>
<br />
<code>Vector</code> nesnelerine gönderilebilecek iletilere geçmeden önce, sağlanan yapıcılara bir göz atalım. Aşağıda verilen örneklerden de görülebileceği gibi, <code>Vector</code> sınıfı soysaldır; tutacaklarının tanımlandığı ve nesnelerinin yaratıldığı noktalarda ilişkin kapta tutulacak elemanların türü bilgisini tür argümanı olarak ister. Tür argümanının es geçilmesi, <code>Vector</code> sınıfının J2SE 5.0 öncesinde olduğu gibi tür güvenliği olmaksızın kullanılacağı anlamına gelir.<br />
<pre class="brush:java; gutter:false" name="yapıcılar">Vector<Integer> intVec1 = new Vector<Integer>(50, 10);
Vector<Integer> intVec2 = new Vector<Integer>(50);
Vector<Integer> intVec3 = new Vector<Integer>();
intVec1.add(1);
...
Vector<Integer> intVec4 = new Vector<Integer>(intVec1);
boolean aynıMı = intVec4 == intVec1; // aynıMı ← false
boolean eşitMi = intVec4.equals(intVec1); // eşitMi ← true
intVec4.set(0, 11);
eşitMi = intVec4.equals(intVec1); // eşitMi ← false
</pre>
İlk yapıcı çağrısı, başlangıç sığası 50 olan ve taşma noktalarında 10 elemanlık artımla genişletilecek bir <code>Vector</code> nesnesi yaratır. Sığa artım argümanı olarak pozitif olmayan bir değer geçirilmesi veya ikinci satırdaki yapıcı çağrısında olduğu gibi artım değerinin sağlanmaması durumunda, <code>Vector</code> nesnesinin sığası her taşma noktasında ikiye katlanarak artırılacaktır. Buna göre, <code>intVec2</code> tutacağı ile gösterilen nesne 51. elemanın eklenmesi istendiği noktada 100, 101. elemanın eklenmek istendiği noktada ise 200 eleman tutabilecek şekilde genişletilecektir. Benzer bir sığa artırım politikası izlenecek üçüncü satırdaki varsayılan yapıcı çağrısı, on elemanlık başlangıç sığasına sahip bir <code>Vector</code> nesnesi yaratacaktır. Son satırdaki çağrı ise, kopyalayan yapıcı görevini görecek ve kendisine geçirilen <code>Collection<E></code> kategorisindeki bir kabın elemanlarını gezicinin<a alt="Gezicinin anlamı" href="http://ta-java.blogspot.com/p/programlama-sozlukcesi.html#gezici">≝</a> döndürdüğü sırada yaratılmakta olan <code>Vector</code> nesnesine kopyalayacaktır. Kopyalamanın sonrasında eşit fakat birbirlerinden farklı (ve bağımsız) iki <code>Vector</code> nesnesinin var olacağı ve bunlardan birisine yapılacak değişikliğin diğerini etkilemeyeceği unutulmamalıdır. Ayrıca, yeni yaratılan <code>Vector</code> nesnesinin ilk sığası diğerinin eleman sayısı ile sınırlı olacaktır.<br />
<br />
<h3>
Desteklenen Arayüzler</h3>
<br />
<code>Vector</code> sınıfının dokümantasyonuna baktığınızda, desteklenen iletilerin çokluğu gözünüzü korkutabilir. Ancak, bunların bir bölümünün bir diğer ileti ile eşdeğer olduğu, bir bölümünün ise bildiğiniz sınıflardan tanıdık geleceği düşünüldüğünde, işimizin o kadar da zor olmadığı görülecektir. Ne de olsa, aşağıda verilen soy ağacına sahip olan <code>Vector</code> sınıfı, gerçekleştirilen ortak arayüzler ve kalıtlanılan ortak üstsınıflar nedeniyle Veri Kapları Çerçevesi'ndeki diğer sınıflarla büyük benzerlikler gösterir.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6otBH3Po1LATOe2X-BmFYUTja5iV4dZfhRtzmV44W3LEW6tozp4sWA_DRVoO5Kz9GF9NXOFx3BRpYN7MlPGbrzsKyiHqOssP3B4wMly6muNIVVcstmBdAixur6Jr3n_NVbBZUVtgyLEKa/s800/VectorS%2525C4%2525B1rad%2525C3%2525BCzeni.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="237" width="432" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6otBH3Po1LATOe2X-BmFYUTja5iV4dZfhRtzmV44W3LEW6tozp4sWA_DRVoO5Kz9GF9NXOFx3BRpYN7MlPGbrzsKyiHqOssP3B4wMly6muNIVVcstmBdAixur6Jr3n_NVbBZUVtgyLEKa/s800/VectorS%2525C4%2525B1rad%2525C3%2525BCzeni.png" /></a></div>
<code>Iterable</code> arayüzü ile başlayalım. Genelde programcı tarafından doğrudan kullanılmayacak olan <code>iterator</code> iletisini içeren bu arayüz, derleyiciye söz konusu sınıfın gezici <code><b>for</b></code> döngüsü ile [baştan sona] dolaşılabileceğini garanti eder. Mesela, aşağıdaki kod parçası, hiç gezici nesne yaratıp, bu nesneye ileti göndermek zahmetine katlanmadan <code>intVec4</code>'in gösterdiği kaptaki tüm elemanların kareköklerini standart çıktıya basar.<br />
<pre class="brush:java; gutter:false" name="geziciFor">for (Integer eleman : intVec4)
System.out.println(Math.sqrt(eleman));
</pre>
Gerçekleştirilen arayüzler listesindeki <code>Cloneable</code>, <code>Vector</code> nesnelerinin kopyalarının çıkarılabileceği anlamına gelir. <code>Object</code> sınıfındaki <code>clone</code> metodunun gösterge arayüz<a alt="Gösterge arayüzün anlamı" href="http://ta-java.blogspot.com/p/programlama-sozlukcesi.html#g%C3%B6stergeAray%C3%BCz">≝</a> olan <code>Cloneable</code>'ı gerçekleştiren sınıflarda özel bir biçimde ezilmesiyle yerine getirilen sözleşme sonucunda, <code>clone</code> iletisi <code>Object</code> türlü bir tutacakla dahi gönderilebilir ve yaratılan kopya <code>Object</code> tutacağı aracılığıyla döndürülür. Dolayısıyla, yeni kopyaya <code>Vector</code> sınıfına has iletiler gönderilmesi öncesinde sonucun biçimlendirilmesi gerekecektir. Ayrıca; işlem sırasında elemanları tutan kap kopyalandığı için hedef nesne ile çıkarılan kopya ayrı nesneler olacaktır. Bu, nesnelerin atıflarının (tutacak) geçirildiği Java'da, değer geçirme yönteminin benzetimi için kullanılabilir.<br />
<pre class="brush:java; gutter:false" name="değerGeçirmeBenzetimi">...
public void mtt(Vector<Integer> vec) {
Vector<Integer> vecKopya =
(Vector<Integer>) vec.clone();
// vecKopya'yı kullan.
...
} // void mtt(Vector<Integer>) sonu
</pre>
<code>java.util.RandomAccess</code> de gösterge arayüz olup herhangi bir ileti içermez. İşaret görevini gören bu arayüzün varlığı, kimi zaman doğrudan erişimli kimi zamansa doğrudan erişimli olmayan kapları işleyerek işini gören soysal kod parçalarında, doğrudan erişimliliğin avantajlarından yararlanılabilecek noktalarda bu özelliğin kullanılmasıyla performansın artmasını olanaklı kılar. Bunun için, nesnenin <code>RandomAccess</code> arayüzünü destekleyip desteklemediğinin aşağıdaki gibi denetlenmesi yeterli olacaktır.<br />
<pre class="brush:java; gutter:false" name="doğrudanErişimlilik">...
if (kap instanceof RandomAccess) {
// Doğrudan erişimin avantajlarından yararlan.
...
} else { ... }
...
</pre>
Desteklenen gösterge arayüzlerden bir diğeri olan <code>java.io.Serializable</code>, <code>Vector</code> nesnelerinin disk dosyası, ağ gibi çevre aygıtlara dışsallaştırılıp, aynı aygıtlardan içselleştirilebileceğini gösterir. Bu, <code>Vector</code> nesnelerinin <code>java.io.ObjectOutputStream</code> türünden akak<a alt="Akağın anlamı" href="http://ta-java.blogspot.com/p/programlama-sozlukcesi.html#akak">≝</a>lara <code>writeObject</code> ile yazılıp, <code>java.io.ObjectInputStream</code> türlü akaklardan <code>readObject</code> ile okunabileceği anlamını taşır.<br />
<br />
Değineceğimiz son arayüzler, <code>Vector</code> nesnelerini ekleme, güncelleme, silme, sorgulama gibi işlemlerle manipüle eden <code>java.util.Collection</code> ve <code>java.util.List</code> arayüzleridir. <code>Collection</code>, Veri Kapları Çerçevesi'ndeki kapların pek çoğu için geçerli olan işlemlerin genel sözleşmelerini sağlar. Bu yapılırken, ne çeşit bir kabın ele alındığı konusunda—aynı değerden iki veya daha fazlası tutulabilir mi, uygulanan işlemlerin sıra ve sonucuna göre elemanların yapı içindeki sırası bilinebilir mi?—bir varsayımda bulunulmaz. Buna karşılık, <code>List</code> arayüzü, kaptaki değerlerin tekrarlanabileceğini ve uygulanan işlemlerin sonrasında yapı içindeki sıranın bilinebileceğini varsayar.<br />
<br />
<h3>
Desteklenen İletiler</h3>
<br />
İletilere öznitelikleri işlemlemek için yararlanabileceklerimizle başlayalım. Argümansız <code>size</code> ve <code>capacity</code> iletileri, sırasıyla, hedef nesnenin kaç eleman içerdiğini ve sığasını döndürürken, <code>isEmpty</code> boşluk yüklemi olarak görev görür. Hedef nesnenin içeriğini eleman sayısına eşit sığalı bir kaba koyarak bellekten tasarruf sağlayan <code>trimToSize</code>, eleman sayısının artmayacağından emin olduğumuz durumlarda kullanılabilir. Sığanın değiştirilmesine yarayan bir diğer ileti olan <code>ensureCapacity</code>, hedef nesneyi argümanda geçirilen değere eşit veya daha büyük bir sığaya sahip olacak şekilde değiştirir. Bu işlemin icra edilmesi esnasında, hedef nesne yaratılırken sağlanan/varsayılan sığa artım değerinden yararlanılacaktır. Son olarak, <code>setSize</code>, hedef nesnedeki eleman sayısı özniteliğini günceller. Bu iletinin kullanımında dikkat edilmesi gereken bir nokta, geçirilen argümana göre eleman sayısının artması gibi azalmasının da mümkün olduğudur: argümanın ileti gönderimi anındaki eleman sayısından büyük olması eleman sayısının <code><b>null</b></code> değerine sahip elemanlar eklenerek artırılmasına neden olurken, küçük olması kabın sonuna doğru bazı değerlerin kırpılmasına, yani eleman sayısının azaltılmasına neden olacaktır.<br />
<br />
Sınıflarını yazarken titiz davrananlara tanıdık gelecek iletilerle devam edelim. <code>equals</code> ve <code>toString</code>, beklendiği gibi, sırasıyla, eşitlik denetimi ve hoş yazım işlemlerini karşılarken <code>hashCode</code>, hedef nesneyi kıyarak özet değeri görevini gören bir <code><b>int</b></code> döndürür. Bilmeyenler için, bunun kulağa geldiği kadar vahşi bir şey olmadığını, nesneyi değiştirerek zarar vermediğini söyleyelim ve ne işe yaradığını biraz açalım. Potansiyel olarak pek çok eleman içerebilecek kapların eşitlik denetimi oldukça uzun bir zaman alabilir; ilk elemandan başlayarak eşit olmayan eleman çiftine kadar kontrol edilen iki kabın aynı indislerdeki elemanları birbirleriyle karşılaştırılır. Bu, eleman sayısı ile doğru orantılı olarak artan oldukça pahalı bir işlemin söz konusu olduğu anlamına gelir. Maliyet kap içeriğinin özeti olarak bir değerin tutulması ile azaltılabilir; kap değiştikçe—bu, eleman ekleme, güncelleme ve eleman silme ile mümkün olabilir—özet değer yeniden hesaplanır. Bir diğer kap ile eşitlik denetiminin yapılması istendiği durumlarda ise, önce eleman sayıları sonra ise <code>hashCode</code> ile öğrenilebilecek özet değerler karşılaştırılır. Özet değerler eşit değilse, kaplar da eşit değildir; aksi takdirde, eşitsiz kapların özetlerinin eşit olması olanaklı olduğundan, yukarıda bahsettiğmiz pahalı yöntemle eşitlik denetimi yapılır.<br />
<pre class="brush:java; gutter:false" name="hashCodeÖrneği">public class Vector<E> extends AbstractList<E>
implements List<E>, Cloneable, RandomAccess, Serializable {
...
public boolean equals(Object sağ) {
Vector<E> sağTaraf = (Vector<E>) sağ;
if (size() != sağTaraf.size()) return false;
if (hashCode() != sağTaraf.hashCode()) return false;
// Kapları dolaşarak karşılıklı elemanları denetle.
...
} // boolean equals(Object) sonu
...
} // Vector<E> sınıfının sonu
</pre>
<code>Vector</code> sınıfı, içerik güncellenmesi ve sorgulanması için pek çok ileti sunar. Ancak, kimi iletilerin bir diğer Java sürümünde eklenen eşdeğerleri bulunduğu için, ileti listesini anlamlandırmak işinin altından kalkmak görünenden kolay olacaktır. Bu gruba giren iletilerden aşağıdaki tablonun ilk sütununda verilmiş olanları kullanmanız daha akıllıca olacaktır. Zira, <code>Collection</code> ve <code>List</code> arayüzlerinde tanımlanmış olan bu iletilerin kullanımı, <code>Vector</code> yerine Veri Kapları Çerçevesi'ndeki diğer sınıfların kodunuzda değişiklik yapılmadan kullanılmasını sağlayacak ve yeniden kullanımı olanaklı kılacaktır. <br />
<br />
<table align="center" border="1" id="eşdeğerİletiler"><caption><code>Vector</code> sınıfındaki eşdeğer iletiler</caption><tbody>
<tr><td><code>add(e)</code></td><td width="37%"><code>addElement(e)</code></td><td><code>e</code>'yi kabın sonuna ekler</td></tr>
<tr><td><code>add(i, e)</code></td><td><code>insertElementAt(e, i)</code></td><td><code>e</code>'yi <code>i</code> indisli konuma ekler</td></tr>
<tr><td><code>clear()</code></td><td><code>removeAllElements()</code></td><td>Hedef nesneyi boşaltır</td></tr>
<tr><td><code>get(i)</code></td><td><code>elementAt(i)</code></td><td><code>i</code> indisli elemanı döndürür</td></tr>
<tr><td><code>remove(e)</code></td><td><code>removeElement(e)</code></td><td><code>e</code> ile eşit ilk konumdaki değeri siler</td></tr>
<tr><td><code>remove(i)</code></td><td><code>removeElementAt(i)</code></td><td><code>i</code> indisli elemanı siler</td></tr>
<tr><td><code>set(i, e)</code></td><td><code>setElementAt(e, i)</code></td><td><code>i</code> indisli elemanı <code>e</code> ile değiştirir ve eski değeri döndürür.</td></tr>
</tbody></table>
<br />
İçerik sorgulama ve güncelleme iletilerinin anlatımına geçmeden önce, eleman ekleme ve silme iletilerinin kullanımı sırasında akılda tutulması yararlı olacak şu noktayı hatırlatalım: ekleme sırasında, eklemenin yapıldığı indis sonrasındaki elemanlar sona doğru, silme sırasında, silinen elemanın sonrasındaki elemanlar öne doğru kaydırılacaktır. Dolayısıyla, bu işlemlerin etkilediği indis kap önlerine yaklaştıkça maliyet artacaktır.<br />
<br />
İki uyarlaması bulunan <code>add</code>, hedef nesneye yeni eleman eklemeye yarar. Bu iletilerden, tek argümanlı olanı, argümanındaki değeri <code>Vector</code> nesnesinin sonuna ekleyip <code><b>true</b></code> döndürürken, iki argümanlı olanı, ikinci argümandaki değeri ilk argümanda belirtilen indisteki konuma ekler. Eklemenin toptan yapılması istendiğinde, bir döngü veya pek çok <code>add</code> iletisi kullanmaktansa <code>addAll</code> iletisi kullanılabilir. <code>add</code> iletisine koşut iki uyarlaması bulunan bu ileti, kendisine geçirilen <code>Collection</code> arayüzünü destekleyen kabın içindeki elemanları gezicisinin döndürdüğü sırada hedef nesneye ekler.<br />
<br />
<code>get</code> iletisi, yegâne argümanında belirtilen indisteki elemanı döndürür. Bu iletinin özel kullanımları için, sırasıyla ilk ve son elemanları döndüren <code>firstElement</code> ve <code>lastElement</code> iletileri tercih edilebilir. Ancak, kodun yeniden kullanım kaygıları ağır basıyorsa, <code>List</code> arayüzünde tanımlanmış <code>get</code>'i yeğlemek daha yerinde olacaktır. Bir grup ardışık elemanın döndürülmesi ise, <code>List</code> arayüzü türündeki tutacakla gösterilen bir <code>Vector</code> nesnesi döndüren <code>subList</code> iletisi ile olanaklıdır. Bu ileti, hedef nesnenin ilk argümandaki indisten başlayıp ikinci argümandaki indisin bir öncesinde sonlanan dilimini döndürür.<br />
<br />
<code>indexOf</code> ve <code>lastIndexOf</code> iletileri de sorgulama amacıyla kullanılablir. <code>get</code> verilen bir indisteki elemanı döndürürken, <code>indexOf</code> ve <code>lastIndexOf</code> eleman türündeki bir değer alıp bu değerin hedef nesnede geçtiği indisi döndürür. İki uyarlaması var olan <code>indexOf</code>, tek argümanlı kullanılacak olursa argümanda geçirilen nesnenin bulunduğu ilk indisi döndürürken, iki argümanlı kullanılırsa, ilk argümanda geçirilen nesnenin ikinci argümanda belirtilen indisten sonra bulunduğu ilk yerin indisini döndürür. Benzer şekilde; <code>lastIndexOf</code>, tek argümanlı kullanılacak olursa argümanda geçirilen nesnenin bulunduğu son indisi döndürürken, iki argümanlı kullanılırsa, ilk argümanda geçirilen nesnenin ikinci argümanda belirtilen indisten önce bulunduğu ilk yerin indisini döndürür. Aranan nesnenin hedef nesnede bulunmaması kullanıcıya -1 döndürülerek bildirilecektir.<br />
<br />
Sorgulama amacıyla yararlanabileceğimiz bir diğer ileti çifti, yegâne argümanlarında geçirilen nesnenin veya <code>Collection</code> arayüzünü destekleyen kaptaki elemanların hedef nesnede geçip geçmediğinin yanıtını veren <code>contains</code> ve <code>containsAll</code> yüklemleridir.<br />
<br />
Hedef nesneden eleman silme işlemi, argüman olarak indis veya silinmesi istenen değere eşit bir nesne bekleyen iki <code>remove</code> iletisi ile karşılanır. İndis alan uyarlama istenen konumdaki elemanı silip sonucu olarak döndürürken, diğer ileti argümanındaki nesnenin hedef nesne içinde, varsa, geçtiği ilk noktadaki elemanı siler ve silmenin gerçekleşmesi durumunda <code><b>true</b></code>, aksi halde <code><b>false</b></code> döndürür.<br />
<br />
Çoklu silme, <code>removeAll</code>, <code>retainAll</code> veya <code>clear</code> iletilerinden biri kullanılarak yapılabilir. <code>removeAll</code>, argümanındaki <code>Collection</code> arayüzünü destekleyen kap içindeki elemanların hedef nesnedeki bütün kopyalarını silerken, <code>retainAll</code> argümandaki kabın elemanlarının hedef nesnedeki tüm kopyalarının korunması ve geri kalan elemanların silinmesini sağlar. Her iki ileti de, işleyişleri sırasında hedef nesneyi değiştirecek olurlarsa <code><b>true</b></code>, aksi takdirde <code><b>false</b></code> döndürür. Son olarak, <code>clear</code> komutu, hedef nesnedeki tüm elemanları siler.<br />
<br />
Eleman güncellemede kullanılan <code>set</code> iletisi, ilk argümanında sağlanan indisteki elemanı ikinci argümandaki değer ile değiştirir ve değişim öncesindeki eleman değerini sonucu olarak döndürür.<br />
<br />
Değineceğimiz bir sonraki ileti grubu, gezici nesnesi döndürenler.<sup><a href="#dn1" id="ref1">1</a></sup> <code>listIterator</code> adlı bu iletiler, hedef nesneyi çift yönlü dolaşıp, güncellememizi sağlayan <code>ListIterator</code> türlü bir gezici nesne döndürür.<sup><a href="#dn2" id="ref2">2</a></sup> Bunlardan argümansız olanı, dolaşmaya hedef nesnemizin ilk indisinden başlarken, tek argümanlı olanı, dolaşmaya argümanda belirtilen indisten başlar. İstenen yere konuşlanılmasını takiben, gezici nesneye sonraki/önceki elemanı döndürme (<code>next</code>, <code>previous</code>), o anki konuma ekleme (<code>add</code>), o anki konumdaki elemanı güncelleme (<code>set</code>) ve silme (<code>remove</code>) imkanını veren iletiler gönderilebilir. Geziciye gönderilen iletiler esas etkilerini dolaşılmakta olan kap üzerinde gösterecektir. Bunu, <code>Vector</code> nesnesi içindeki 3 değerine sahip elemanları gezici vasıtasıyla silen aşağıdaki kod parçasından görebilirsiniz.<br />
<pre class="brush:java; gutter:false" name="geziciNesneÖrneği">import java.util.ListIterator;
...
ListIterator<Integer> gezici = intVec4.listIterator();
while (gezici.hasNext())
if (gezici.next() == 3) gezici.remove();
</pre>
Göz atacağımız son ileti grubu, <code>Vector</code> yerine bir başka yapının gerektiği veya daha uygun olduğu zamanlarda ihtiyacını duyduğumuz dönüşümü sağlar. Öncelikle, Veri Kapları Çerçevesi'nde <code>ArrayList</code>, <code>HashSet</code> ve <code>LinkedList</code>'in de içinde olduğu pek çok sınıf, <code>Collection</code> arayüzünü gerçekleştiren bir kap bekleyen yapıcıya sahiptir. Bu yapıcılar, geçirilen kabı baştan sona dolaşarak elemanları yaratılmakta olan yeni kaba eklerler. Dolayısıyla, <code>Vector</code> ve diğer pek çok sınıfın <code>Collection</code> arayüzünü gerçekleştirdiği anımsanacak olursa, <code>Vector</code> nesnelerinin pek çok diğer türden kaba ve diğer türden kapların <code>Vector</code> nesnesine çevrilmesi kolaylıkla mümkün olacaktır.<br />
<br />
<code>Vector</code> nesneleri dizi nesnelerine <code>copyInto</code> ve <code>toArray</code> iletileri kullanılarak dönüştürülebilir. <code>copyInto</code> hedef nesnenin içerdiği elemanları argümanında sağlanan dizinin içine doldururken dizinin yeterli uzunlukta olmamasını kullanıcıya <code>IndexOutOfBoundsException</code> ayrıksı durumu ile bildirir. Buna karşılık, benzer imzaya sahip <code>toArray</code> iletisi, argümanda geçirilen dizinin yetersiz kalması durumunda sızlanmaz ve gerekli uzunluğa sahip yeni bir dizi nesnesi yaratıp işini bu yeni dizi üstünde tamamladıktan sonra bu diziyi döndürür. <code>toArray</code>'in argümansız ikinci uyarlaması da, hedef nesnenin eleman sayısına sahip bir dizi döndürerek işini görür.<sup><a href="#dn3" id="ref3">3</a></sup></div>
<div id="diğerYazılar" style="text-align: right;">
<a alt="Java'da diziler" href="http://ta-java.blogspot.com/2011/04/bileske-turler-diziler.html">Diziler</a><br/>
<a alt="Doğrusal veri kapları" href="http://ta-java.blogspot.com/2012/01/dogrusal-veri-kaplar.html">Doğrusal Veri Kapları</a><br/>
<a alt="Hızlı arama zamanlı kaplar: Eşlemler" href="http://ta-java.blogspot.com/2011/12/hzl-arama-zamanl-kaplar-1.html">Hızlı Arama Zamanlı Kaplar</a></div>
<hr />
<div id="dipnotlar" style="text-align: justify;">
<ol>
<li id="dn1"> <code>elements</code> iletisi <code>iterator</code> ve <code>listIterator</code> iletilerinin eklenmesi ile kullanımdan düşmüştür. Dolayısıyla, anlatımımız bu iletiyi kapsamayacaktır. <a href="#ref1">↑</a></li>
<li id="dn2"> İletilerin adı olarak <code>vectorIterator</code> yerine <code>listIterator</code> (liste gezici) seçilmiş olması sizi şaşırtmasın. Ne de olsa baştan sona dolaşılmak tüm doğrusal veri yapıları için makul bir işlem ve bu yüzden bu işleme karşılık gelen iletiler <code>List</code> arayüzüne konulmuş. <a href="#ref2">↑</a></li>
<li id="dn3"> Bu iletinin <code>Vector</code> sınıfındaki gerçekleştirimi <code>Arrays.asList</code> metodu ile bir bütün olarak düşünülmelidir. <a href="#ref3">↑</a></li>
</ol>
</div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-25437235007323941982011-09-08T19:11:00.000+03:002011-12-14T15:53:56.433+02:00Kategorize Etmenin Yararları ve Arayüzler<div id="anaMetin" style="text-align: justify;">Her ne kadar <a href="http://tr.wikipedia.org/wiki/B%C3%BClent_Orta%C3%A7gil">Bülent <span style="font-variant: small-caps;">Ortaçgil</span></a>, beni kategorize etme, demişse de, kategorizasyon çevremizi anlamamızı kolaylaştıran ve sıklıkla başvurduğumuz çok önemli bir keşif aracıdır. Birbirlerine benzesin ya da benzemesin, değişik türden varlıkların ortak yönlerini gruplamamızı olanaklı kılarak soyutlama yapmamızı sağlayan bu araç, keşfimizin paylaşılabilir olması durumunda, kendimizi daha kısa yoldan anlatmamızı da olanaklı kılar. Örneğin, üçüncü bir şahsın obez olduğu konusundaki gözlemimizi paylaştığımızda, karşımızdaki kişi, hangi ulustan olursa olsun, konuşmaya konu olan kişinin saçının, gözlerinin ne renk olduğunu bilemese de nasıl vücut hatlarına sahip olduğunu kafasında canlandırılabilir. Çünkü, ortak tanımı üzerinde anlaşılmış—yani, <i>standart</i> bir tanımı bulunan—obez olma özelliği kişilerin ait olduğu grupları aşan bir özelliktir; birbirleriyle hiç alakası bulunmayan grupların üyeleri arasında ortak payda olarak kullanılabilir. Yani, kafamızın içindeki gri materyalin tasnif etme donanımı, biz istesek de istemesek de, diğer kişileri müstahdem, öğretmen, yönetici, vb. şeklinde <i>sınıf</i>landırdığı gibi, obez, zeki, işbilir, vb. şeklinde de <i>kategori</i>ze etmektedir. Yeri geldiğinde, sınıflarla kategoriler bitiştirilmekte ve obez öğretmen, işbilir yönetici gibi yeni sınıflamalar da yapılabilmektedir.<sup><a href="#dn1" id="ref1">1</a></sup><br />
<br />
İşte bu yazımızda, Java programlama dilinin nesneler alemini tasnif etmekte sınıf ile birlikte kullanılabilecek ikinci üstkavramı olan <i>arayüz</i> kavramına değineceğiz. Ama ilk önce pedagojik senaryomuz. Yer ülkemizdeki liselerden birinin müdüriyeti ve uyuşturucunun ilk paketinin bedava olduğunu bildiği için bazı şirketlerce öğretim ordusunun üyelerine "bedava" dağıtılan programlama dillerini kullanmayı reddederek Java'yı tercih eden müdürümüz, yönetimindeki öğrenci ve öğretmenlerin performansını özetleyecek bir programı nasıl yazabileceğini düşünmekte. Müdürümüzün amacı, öğrenci ve öğretmenleri kendi aralarında uygun bir ölçüte göre sıralayıp listelemek. Çiziktirmelerle dolu sayfalara baktığımızda, müdürümüzün öğrencileri önce ortalamaları sonra—ortalamaların eşit olması durumunda—numaraları karşılaştırırak sıraya dizmeyi düşünürken, öğretmenleri önce öğrenci anketlerindeki konu yeterliliği notunun ortalamasını sonra ders yükünü karşılaştırırak sıraya dizmek istediğini görüyoruz. <br />
<br />
Yordamsal programlama alışkanlıkları olduğu anlaşılan müdürümüz, ilk çözüm girişiminde <code>Performans</code> adlı bir sınıftaki dört metot ile işini halletmeye çalışmış. Buna göre, sıralama metotları kendilerine geçirilen veri kümesinin elemanlarını ilişkin karşılaştırma metodunu çağırarak sıraya dizmekte.<br />
<br />
<div style="color: #444444; text-align: center;">Performans.java</div><pre class="brush:java; gutter:false" name="YordamsalGerçekleştirim">public class Performans {
...
public static int karşılaştır(Öğrenci sol, Öğrenci sağ) {
...
} // int karşılaştır(Öğrenci, Öğrenci)
public static int karşılaştır(Öğretmen sol, Öğretmen sağ) {
...
} // int karşılaştır(Öğretmen, Öğretmen)
public static void sırala(Öğrenci[] veri) { ... }
public static void sırala(Öğretmen[] veri) { ... }
...
} // Performans sınıfının sonu
</pre><br />
<code>Performans</code>'ı <code>Öğrenci</code> ve <code>Öğretmen</code> sınıflarına bağımlı kılması—<code>Öğrenci</code> ve <code>Öğretmen</code> sınıflarının değişmesi ilişkin karşılaştırma metotlarının değişmesine neden olabilir—ve sıralama metotlarının neredeyse aynı olmasından—ne de olsa, aynı algoritma kullanılıyor—huylanan müdürümüz, <code><b>static</b></code> niteleyicisinin nesne paradigmasının düşmanı olduğunu anımsaması ve, düşülen nota inanırsak, İnternet'teki bir yazıyı<a alt="Java'da kalıtlama" href="http://ta-java.blogspot.com/2011/08/turlerin-evrimi-ya-da-kaltlama.html">🔎</a> okuması sonrasında, sınıf ve kalıtlama kavramlarının kullanıldığı bir çözümü yeğleyerek bu çözümden vazgeçmiş.<br />
<br />
<div style="color: #444444; text-align: center;">Karşılaştırılabilir.java</div><pre class="brush:java; gutter:false" name="NesneYönelimli">public abstract class Karşılaştırılabilir {
public abstract int karşılaştır(Karşılaştırılabilir sağ);
} // Karşılaştırılabilir sınıfının sonu
</pre><br />
<div style="color: #444444; text-align: center;">Öğrenci.java</div><pre class="brush:java; gutter:false" name="NesneYönelimli2">public class Öğrenci extends Karşılaştırılabilir {
...
public int karşılaştır(Karşılaştırılabilir sağ) {
Öğrenci sağTaraf = (Öğrenci) sağ;
... // ileti alıcı ile sağTaraf'ı karşılaştır.
} // int karşılaştır(Karşılaştırılabilir)
...
} // Öğrenci sınıfının sonu
</pre><br />
<div style="color: #444444; text-align: center;">Öğretmen.java</div><pre class="brush:java; gutter:false" name="NesneYönelimli3">public class Öğretmen extends Karşılaştırılabilir {
...
public int karşılaştır(Karşılaştırılabilir sağ) {
Öğretmen sağTaraf = (Öğretmen) sağ;
... // ileti alıcı ile sağTaraf'ı karşılaştır.
} // int karşılaştır(Karşılaştırılabilir)
...
} // Öğretmen sınıfının sonu
</pre><br />
<div style="color: #444444; text-align: center;">Performans.java</div><pre class="brush:java; gutter:false" name="NesneYönelimli4">public class Performans {
...
public static void sırala(Karşılaştırılabilir[] veri) {
...
} // void sırala(Karşılaştırılabilir[]) sonu
...
} // Performans sınıfının sonu
</pre><br />
Anlaşılan, yukarıda kaba hatlarıyla verilen çözümün, Java'nın çoklu sınıf kalıtlamayı desteklememesi nedeniyle, <code>Öğrenci</code> ve <code>Öğretmen</code>'in bir diğer sınıftan daha—mesela, iki sınıfın ortak noktalarını içeren <code>Kişi</code> gibi—türetilmesini engellediğini gören ve karşılaştırmanın genel bir işlem olduğunu düşünen müdürümüz, <code>Karşılaştırılabilir</code>'in aslında gereksiz olduğuna karar vermiş ve söz konusu işleme karşılık gelen iletiyi <code>Object</code> sınıfının içinde aramış. Buna gerekçe olarak da, <code>equals</code> (eşitlik denetimi) ve <code>toString</code> (hoş yazım) gibi <u>tüm</u> nesneler için geçerli olan iletilerin tüm sınıfların ortak noktalarını barındıran <code>Object</code>'te yer alması gerektiğini not düşmüş. Görünen o ki, bu varsayımın arama sonucunda yanlışlanması, müdürümüzü Java programlamayı iyi bilen bir matematik hocasının kapısını çalmaya zorlamış. Derdinin çaresinin <code>Object</code> sınıfında değil, arayüz kavramının kullanımında yattığını söyleyen matematik hocası, küme, matris, karmaşık sayı gibi matematiksel nesnelerin aslında karşılaştırılabilir olmadığı konusunda garanti vererek müdürümüzü ikna etmiş. Gelin bundan sonrasını birlikte görelim.<sup><a href="#dn2" id="ref2">2</a></sup><br />
<br />
Birbirleriyle <u>ilintili veya ilintisiz</u> olabilecek <u>kimi</u> kavramların ortak özelliklerini soyutlamaya yarayan kategoriler Java'da arayüz üstkavramı ile karşılık bulurlar; bir arayüzün tanımlanması, Java sınıf kitaplığını o arayüzü gerçekleştiren ve gerçekleştirmeyen sınıflar olmak üzere iki kategoriye ayırır. Bu tanımda altı çizilmesi gereken noktalar, aynı arayüzü gerçekleştiren sınıfların birbirleriyle ilintisiz olma ve kimi sınıfların arayüzü gerçekleştirmeme olasılığıdır. Örneğin, karşılaştırılabilir sınıflar, <code>Öğretmen</code> ve <code>Müstahdem</code> gibi ortak üstsınıfları (<code>Çalışan</code>) yoluyla ilintilendirilebilirken, tamsayılar ve vergi mükellefleri gibi alakasız da olabilirler. Aynı şekilde, tamsayılar karşılaştırılabilirlik özelliği taşırken, disk dosyalarını soyutlayan sınıfların aynı özelliği taşıması beklenmez.<sup><a id="ref3" href="#dn3">3</a></sup><br />
<br />
<div style="color: #444444; text-align: center;">Karşılaştırılabilir.java</div><pre class="brush:java; gutter:false" name="ArayüzÖrneği">public interface Karşılaştırılabilir {
public int karşılaştır(Object);
/* public static final */ String
BELİRLEYİCİ_KOMİTE = "Ağır Abiler, A.Ş.";
} // Karşılaştırılabilir arayüzünün sonu
</pre><br />
Arayüz tanımı, destekleyen sınıfların gerçekleştirmesi zorunlu olan iletilerin imzalarını ve bu sınıfların üyesi olan nesnelerin tümü için değişmeyen özellikleri içerir. Örneğin, yukarıdaki arayüz <code>karşılaştır</code> adlı bir iletiyi gerçekleştiren sınıflara ait nesneleri soyutlarken, bu nesnelerin ele alınması esnasında yararlanılacak bir sabit tanımlamaktadır.<br />
<br />
Arayüz tanımının kuru kuruya kullanıcılara sağlanması pek yararlı olmayacaktır; arayüzü belirleyen geliştiricilerin, arayüzün gerçekleştirimleri arasında tutarlılığı sağlamak adına, derleyicinin kullanım doğruluğu denetimleri için gerekli olan yukarıdaki sözdizimsel tanımın yanısıra, arayüzdeki öğelerin anlamlarına dair açıklamaları da sağlaması gerekir. Örneğin, <code>karşılaştır</code> iletisinin imzasının sağlanması yalnız başına bir işe yaramayacaktır; iletinin ne yaptığına, parametrelerin ve döndürülen değerin ne anlama geldiğine aşağıdaki gibi açıklık getirilmelidir. Yani, arayüzün sözdizimsel tanımına ek olarak anlambilimsel açıklamasının da sağlanması gereklidir.<br />
<blockquote><code>karşılaştır</code> iletisi, hedef nesne ile argümanda geçirilen nesneyi karşılaştırır. Hedef nesnenin büyük olması durumu döndürülen pozitif bir <code><b>int</b></code> ile bildirilirken, eşitlik durumu 0 ile, küçük olma durumu ise negatif bir <code><b>int</b></code> ile bildirilir. Hedef nesne ile argümandaki nesnenin uyumsuz olması, kullanıcıya <code>ClassCastException</code> ayrıksı durumuyla bildirilmelidir. Benzer şekilde, argümanda geçirilen tutacağın <code><b>null</b></code> olmasının, kullanıcıya <code>NullPointerException</code> ayrıksı durumuyla bildirilmesi beklenir.</blockquote><br />
Arayüzün gerçekleştirimci ile kullanıcıları arasında sözleşme görevi gören bir bağlantı noktası oluşturduğu unutulmamalıdır; hem gerçekleştirimci hem kullanıcı, arayüz tanımından değişik amaçlarla da olsa yararlanacaktır. Bu, arayüz tanımındaki bir değişikliğin, birbirlerinden çok farklı yerlerde bulunan pek çok kişinin kodunu değiştirmesi gibi maliyetli bir sonuca neden olabileceği anlamına gelir. Dolayısıyla, <b>değişme ihtimali yüksek olan altalan tanımı ve metot gövdesi gibi gerçekleştirim ayrıntılarının arayüz tanımında yer almasına izin verilmez.</b> Altalan ve metot gerçekleştirimlerinin olması gerektiği durumlarda, arayüz yerine soyut sınıf kullanımı düşünülmelidir.<br />
<br />
Arayüz tanımındaki tüm öğelerin <code><b>public</b></code> erişimli ve tüm veri öğelerinin derleme anı sabiti—yani, <code><b>static final</b></code> ile nitelenmiş—olduğu varsayılır. Bunun nedeni, arayüzün, önceki paragrafta değindiğimiz gibi, birbirinden bağımsız gerçekleştirimci ve kullanıcılar arasında sözleşme görevini görmesidir; arayüzde değişmesi muhtemel ve kamuya açık olmayan gerçekleştirim ayrıntıları bulunamaz. [Müzik setinizin arayüzünde kullandırılmayan bir düğmenin bulunmasını mantıklı bulur musunuz?] Bu sayede kullanıcı, arayüzü hangi sınıf gerçekleştiriyor olursa olsun, arayüz tutacağını arkasındaki nesnenin türünden bağımsız bir şekilde ele alıp kodun yeniden kullanımını sağlayabilecektir. Bunu, aşağıdaki örnek üzerinden görelim.<br />
<pre class="brush:java; gutter:false; highlight:11" name="arayüzleÇokbiçimlilik">public class Performans {
...
public static void sırala(Karşılaştırılabilir[] veri) {
if (veri.length <= 1) return;
boolean takasYapıldı = false;
int geçiş = 1;
do {
takasYapıldı = false;
for (int i = 0; j < veri.length - geçiş; i++)
if (veri[i].karşılaştır(veri[i + 1]) > 0) {
Karşılaştırılabilir geçici = veri[i];
veri[i] = veri[i + 1];
veri[i + 1] = geçici;
takasYapıldı = true;
} // if sonu
geçiş++;
} while(geçiş < veri.length && takasYapıldı);
} // void sırala(Karşılaştırılabilir[]) sonu
...
} // Performans sınıfının sonu
</pre>Yukarıdaki koda göre, <code>sırala</code> metodu <code>Karşılaştırılabilir</code> arayüzünü gerçekleştiren herhangi bir sınıfın nesnelerini gösteren tutacaklardan oluşan bir dizi alabilir. Metot içinde, geçirilen dizinin elemanlarının <code>Karşılaştırılabilir</code> arayüzünde listelenen özellikleri kullanılabilecektir. <code>sırala</code> metodunu nesnenin diğer özelliklerinden koparan bu kısıtlama, bir nesneye hangi iletilerin gönderilebileceğinin tutacak süzgecinden geçmesi gerektiği kuralı ile de uyumludur: arayüz tutacağı ile gösterilen bir nesneye, tutacağın izin verdiği—yani, ilişkin arayüzde listelenen—iletiler gönderilebilir; iletinin gönderilmesi sonrasında nesnenin ait olduğu sınıftaki aynı imzalı metot çağrılacaktır. Örneğin, programın çalıştırılması sonrasında yukarıda işaretlenmiş olan satırdaki gönderinin sonrasında hangi metodun çağrılacağına geçirilen dizinin eleman türüne göre çalışma anında dinamik iletimle karar verilecektir. Bir diğer deyişle, <b>bir sınıftaki iletilerin söz konusu sınıfın kalıtlama [ilişkisi] ile ilişkilendirildiği altsınıfların nesnelerine gönderilmesi durumunda olduğu gibi, bir arayüzde listelenen iletiler, söz konusu arayüzün gerçekleştirme [ilişkisi] ile ilişkilendirildiği sınıfların nesnelerine gönderilmeleri durumunda çokbiçimli olarak davranır.</b> Dolayısıyla, <code>sırala</code> <code>Karşılaştırılabilir</code> arayüzünü gerçekleştiren tüm sınıflar için işini sınıfın özelliklerine göre yaparak çalışacak ve bu sayede kodun yeniden kullanımını sağlayacaktır.<br />
<br />
İlgilenenlerin bir arayüzü gerçekleştirmesi, ilişkin sınıf ile arayüz arasındaki gerçekleştirme ilişkisinin sınıf başlığında belirtilmesi ve arayüzde listelenen <u>tüm</u> iletilere karşılık metotların sağlanması ile mümkün olur. Sınıf ve arayüz arasındaki ilişki, sınıf adından sonra <code><b>implements</b></code> anahtar sözcüğü yoluyla belirtilir; kalıtlama ilişkisine dair bir bilginin bulunması durumunda, gerçekleştirme ilişkisine dair bilginin bunu takip etmesi gerekir.<br />
<pre class="brush:java; gutter:false" name="ArayüzGerçekleştirimi1">public class Öğrenci implements Karşılaştırılabilir {
...
public int karşılaştır(Object sağ) {
if (this == sağ) return 0;
Öğrenci sağTaraf = (Öğrenci) sağ;
if (_ortalama > sağTaraf._ortalama) return 1;
else if (_ortalama < sağTaraf._ortalama) return -1;
if (_no > sağTaraf._no) return 1;
else if (_no < sağTaraf._no) return -1;
else return 0;
} // int karşılaştır(Object) sonu
...
private float _ortalama;
private long _no;
} // Öğrenci sınıfının sonu</pre><pre class="brush:java; gutter:false" name="ArayüzGerçekleştirimi2">public class Öğretmen implements Karşılaştırılabilir {
...
public int karşılaştır(Object sağ) {
if (this == sağ) return 0;
Öğretman sağTaraf = (Öğretmen) sağ;
if (_ankNotu > sağTaraf._ankNotu) return 1;
else if (_ankNotu < sağTaraf._ankNotu) return -1;
if (_dersYükü > sağTaraf._dersYükü) return 1;
else if (_dersYükü < sağTaraf._dersYükü) return -1;
else return 0;
} // int karşılaştır(Object) sonu
...
private float _ankNotu;
private int _dersYükü;
} // Öğretmen sınıfının sonu
</pre>Gerektiği durumlarda, bir sınıf birden çok sayıda arayüz gerçekleştirebilir; bu, gerçekleştirilen arayüzlerin birbirlerinden ',' ile ayrıldığı arayüz listesinin <code><b>implements</b></code>'ten sonra belirtilmesi ile mümkün olur. Böyle bir sınıfın kullanıcıları, bu sınıfa ait bir nesneyi sınıf ve üstsınıflarının tutacaklarının yanısıra herhangi bir arayüzünün tutacağı yoluyla da kullanabilir.<br />
<br />
Kimi zaman, yeni bir arayüz daha önceden tanımlanmış olan bir arayüzün ileti listesine ekler yapılarak tanımlanabilir. Yazılımların sürüm değişikliği ile birlikte arayüzlerin yeni uyarlamalarının tanımlanması buna tipik bir örnektir. Bu gibi bir durumda, yeni arayüz sıfırdan tanımlanabileceği gibi, eski arayüzden kalıtlayacak şekilde tanımlanabilir. Bu yolu seçmenin getirisi, derleyicinin arayüzler arasındaki kalıtlama ilişkisini sınıflar arasındaki kalıtlamaya benzer bir şekilde yorumlayacak olmasıdır: yeni arayüz (altarayüz) eski arayüz (üstarayüz) gibidir; eski arayüzdeki iletilerin tümünü içeren yeni arayüzün tutacağı, eski arayüz türündeki tutacağın kullanıldığı her yerde kullanılabilir. Ne kastettiğimizi aşağıdaki tanımlar üzerinden görelim.<br />
<pre class="brush:java; gutter:false" name="ArayüzKalıtlama">public interface A1 {
void i1();
void i2();
} // A1 arayüzünün sonu
</pre><pre class="brush:java; gutter:false" name="ArayüzKalıtlama2">public interface A2 extends A1 {
void i1(int i);
void i3();
} // A2 arayüzünün sonu
</pre>Sağlanan tanımlara göre, <code>A2</code> arayüzünü destekleyen bir sınıfın <code>A1</code>'dekileri de içeren dört iletinin karşılığındaki metot gerçekleştirimlerini sağlaması gerekecektir: <code>i1()</code>, <code>i2()</code>, <code>i1(<b>int</b>)</code> ve <code>i3()</code>.<sup><a href="#dn4" id="ref4">4</a></sup> Bu noktada, arayüz kalıtlamanın, sınıf kalıtlamanın aksine, çoklu olduğu unutulmamalıdır. Yani, bir arayüz birden fazla üstarayüzden kalıtlayabilir. Çoklu arayüz kalıtlamadan yararlanıldığı durumlarda, üstarayüzlerden gelen öğelerin ad çakışmalarına dikkat edilmelidir. Aynı imzaya sahip olup değişik dönüş türüne sahip iletilerin kalıtlanması derleme hatasına neden olacağı gibi, aynı ada sahip olup değişik arayüzlerde farklı türlere ait tanımlanan sabitler, kullanıldıkları noktada derleme hatasına yol açacaklardır.<br />
<br />
Son olarak, arayüzlerin önemli bir görevine değinerek yazımızı bitirelim: yazılım bileşenleri için standart oluşturmayı mümkün kılmaları. Bileşen standartları, bir bileşenin aynı arayüzleri destekleyen bir diğeri yerine tak-çıkar mantığıyla kodun güncelllenmesini gerektirmeden değiştirilmesine olanak tanır. [Yani, eskimesi nedeniyle doğramanızı değiştirmek istediğinizde duvarın yıkılmasına gerek olmayacaktır.] Bunu aşağıda verilen zamana göre sıraya dizilmiş çizelgeyi izleyerek görelim.<br />
<br />
<ol><li>Bir grup uzman, ortaya çıkan ihtiyaç üzerine A1 arayüzünü tanımlar.</li>
<li>Ş1 şirketi, A1'i gerçekleştiren Ş1_A1 sınıfını piyasaya sürer.</li>
<li>Ş2 şirketi, Ş1'den Ş1_A1'i satın alır ve kendi programlarında kullanmaya başlar.</li>
<li>Ş3 şirketi, A1'i gerçekleştiren Ş3_A1 sınıfını piyasaya sürer.</li>
<li>Ş1_A1'in performansından muzdarip Ş2 şirketi, Ş3'ten Ş3_A1'i alır ve Ş1_A1'in yerine kullanmaya başlar.</li>
</ol><br />
Dikkat edilecek olursa, Ş3 şirketi Ş3_A1 sınıfını Ş2 sınıfının geliştirdiği programlardan sonra geliştirmiştir. Bir diğer deyişle, Ş2 şirketinin geliştirdiği programlar, kendilerinden sonra geliştirilen bileşenleri kullanmaktadır. Zaman içinde geleceğe yapılan bu yolculuk, arayüzlerin değişmez özellikler barındırması ve sözdizimlerinin yanısıra anlamlarının da tanımlanmak suretiyle gerçekleştirimci kullanıcı arasında bir sözleşme oluşturması ile mümkün olmaktadır.<br />
<br />
</div><div style="text-align: right;"><a alt="Java'da sınıf tanımı" href="http://ta-java.blogspot.com/2011/05/bileske-turler-snflar.html">Sınıflar</a><br />
<a alt="Java'da kalıtlama" href="http://ta-java.blogspot.com/2011/08/turlerin-evrimi-ya-da-kaltlama.html">Kalıtlama</a> </div><hr /><div id="dipnotlar" style="text-align: justify;"><ol><li id="dn1">Obezlik örneğini çok da ilerilere götürmeyiniz. Çünkü, obez olan birisinin zayıflayarak zaman içinde bu özelliğini yitirmesi olanak dahilindedir ki, böylesine bir özelliğin, nesne yönelimli modelleme dünyasının arayüze benzer bir üstkavramı olan rol ile karşılanması daha doğru olacaktır. Arayüzler, ilişkin nesnelerin kaybetmeyeceği özelliklerini tanımlayabilir. <a href="#ref1">↑</a></li>
<li id="dn2">Sağladığımız çözümdeki türlerin soysal tanımlanarak daha güvenli bir çözümün mümkün olduğunun bilinmesinde yarar var. <a href="#ref2">↑</a></li>
<li id="dn3">Aslına bakılacak olursa, karşılaştırılabilirlik kategorisi Java platformundaki <code>Comparable</code> arayüzü tarafından hali hazırda tanımlanmıştır. Dolayısıyla, <code>Karşılaştırılabilir</code> arayüzüne gerek yoktur. <a href="#ref3">↑</a></li>
<li id="dn4">Yeri gelmişken tekrar hatırlatalım: dönüş türü Java'da aynı aduzayındeki iletileri (ve metotları) ayrıştırmak için kullanılmaz. Dolayısıyla, <code>A2</code>'ye <code><b>int</b></code> dönüş türlü <code>i1()</code> iletisinin eklenmesi, ortaya çıkan muğlaklık nedeniyle, derleme hatasına yol açacaktır. <a href="#ref4">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-68232776287422666642011-08-22T12:31:00.001+03:002011-11-26T00:41:50.918+02:00Türlerin Evrimi ya da Kalıtlama<div style="text-align:justify">Bir şirketin rekabet gücü, hangi alanda faaliyet gösteriyor olursa olsun, şirket içinde tanımlanmış süreçlerin verimli yönetilmesiyle artar. Rekabetin üst düzeyde olduğu ve zamanın çok hızlı aktığı bir sektörde yer alan yazılım şirketleri için bu, alandaki bir problemin düşük hata oranlı, ucuz, randımanlı ve nitelikli bir programa dönüştürülmesinin daha hızlı yapılması demektir. Böylesine zorlu bir amaca erişmek ise daha önceden harcanmış çabaları yinelemekten kaçınarak olanaklı hale gelebilir. <i>Yeniden kullanım</i>—yani, daha önceden geliştirilmiş bileşenlerin kullanımı—aşağıdaki yazılım ölçülerini olumlu bir şekilde etkileyecektir.<br />
<br />
<ul><li><i>Hata oranı</i>: Önceden geliştirilmiş bileşenler daha yoğun kullanılıp sınanmış olacağından, bu bileşenleri içeren son ürün daha düşük bir hata oranına sahip olacaktır.</li>
<li><i>Maliyet</i>: Önceden geliştirilmiş bileşenlerin yeniden geliştirilmesine kaynak harcanmayacağı, bu bileşenlerin sadece kullanımı söz konusu olacağı için üretim maliyeti düşecektir.</li>
<li><i>İşlevsel nitelik</i>: Önceden geliştirilmiş bileşenleri yeniden geliştirmekte harcanacak zamanın bir bölümü var olan bileşenlere yeni özellikler eklemekte kullanılabileceği için son ürün daha nitelikli olacaktır.</li>
<li><i>Geliştirme zamanı</i>:<sup><a id="ref1" href="#fn1">1</a></sup> Önceden geliştirilmiş bir bileşeni kullanmak aynı bileşeni ikinci bir defa geliştirmekten daha hızlı yapılacağı için son ürün daha hızlı bir şekilde üretilecektir.</li>
<li><i>Kaynak verimliliği</i>: Önceden geliştirilmiş bileşenler daha yoğun eniyilenmiş olacakları için bu tür bileşenlerden oluşturulan son ürün daha hızlı ve/veya daha az bellek tutacak şekilde geliştirilecektir.</li>
</ul><br />
Dolayısıyla, yazılım geliştirme sürecinin tüm aşamalarındaki ürünlerin olabildiğince yeniden kullanılan bileşenlerle geliştirilmeye çalışılması bir gerekliliktir. Bu, yazımızın konusu olan kalıtlamanın nesne yönelimlilik bağlamında neden öne çıkarıldığının başlıca sebebidir: kalıtlama, kodun<sup><a id="ref2" href="#fn2">2</a></sup> yeniden kullanımını sağlayan araçlardan biridir. Burada, çok önemli bir noktaya vurgu yapmakta yarar var: <b>kalıtlama bir araçtır, amaç değil; amaç kodun yeniden kullanılması yoluyla yazılımın yukarıda anılan ölçüleri olumlu etkileyecek bir biçimde geliştirilmesidir</b>. Aklınızda olsun! <br />
<br />
Yazımızın başlığındaki anıştırmayı açarak başlayalım. Ortaçağ'da takılmayanlarınız bilir; birey olarak sahip olduğumuz biyolojik özellikler, türümüzün kollektif DNA'sındaki genlerin milyarlarca yıldır sürdürdükleri bir ölüm kalım savaşının sonrasında ebeveynlerimizin söz konusu genleri bize aktarmasıyla ortaya çıkar. Kimi zaman, bireyin oluşması<sup><a id="ref3" href="#fn3">3</a></sup> sürecindeki mutasyonlar, onu (ve onun gen haritasından üretilecek bireyleri) türün geri kalanından farklı kılar. Nadiren de olsa, biriken mutasyonlar, çevresel etkenlerce "ödüllendirilerek" yeni bir türün doğmasına neden olabilir. Belki de, başka bir yerde başka bir zamanda, temel türün geri kalan bireylerini etkileyen mutasyonlar da olacaktır. Artık, birbirine benzeyen iki tür vardır ve bu benzerlik türlerin tarihçelerini anlatmakta kulanılma gibi bir soyut varoluşa indirgenen temel türde gizlidir; Tabiat Ana, ekonomik davranmış ve yeni türleri oluştururken temel türü yeniden kullanmıştır.<br />
<br />
Evrim dersimizi burada keserek, Java programlamada kalıtlama ne şekilde ifade edilebilir, ona bir bakalım. Ancak, önce anlatımımızda yararlanacağımız pedagojik senaryoyu ortaya koyalım. Mekânımız, öğrenci ve çalışanlarla dolu bir ilköğretim okulu koridoru; görevimiz, sürdürmekte olduğumuz bir bilimsel çalışma için koridordan geçmekte olan kişilere soru sorarak veri toplamak; amacımız ise, işimizi en kısa sürede bitirmek. Amacımıza doğru kişilere doğru soruları yöneltirsek daha kolay erişebiliriz. Örneğin, herkese "Adınız ne?" sorusunu yöneltmek mümkünken, görünüşü öğrenci olduğunu haykıran birine "Kaç çocuğunuz var?" sorusunu yöneltmek pek de akıl kârı olmayacaktır. Kimi zaman, farklı gruptan kişilere aynı soruyu yöneltebilmemize karşın, yanıtın elde ediliş biçimi muhatabın ait olduğu gruba göre değişebilir. Örneğin, aylık gelir sorulduğunda, yanıt müstahdem için maaş ve ekstraların toplanmasıyla bulunurken, öğretmen için verilen özel derslerin ücretinin de eklenmesi gerekecektir.<br />
<br />
<ul><li>Kişi: Ad ...</li>
<ul><li>Çalışan: Çocuk sayısı, maaş, ekstra gelir, toplam aylık gelir ...</li>
<ul><li>Öğretmen: Verilen dersler, özel ders geliri ...</li>
<li>Müstahdem: Kömür yardımı, ...</li>
</ul><li>Öğrenci: Sınıf ...</li>
</ul></ul><br />
O zaman, gelin yukarıda verilen özeti Java'da nasıl ifade edeceğimize bakalım. Bunu yaparken de, işimize evrim saatini işletmeye başlayan ve koridordan geçen herkesin ortak noktalarını tanımlayan kişi kavramını gerçekleştirmekle girişelim.<br />
<br />
<div style="color: #444444; text-align: center;">Kişi.java</div><pre class="brush:java; gutter:false" name="KişiSınıfı">public abstract class Kişi {
...
public final String ad() { return _ad; }
protected final void adDeğiştir(String yeniAd) {
_ad = yeniAd;
} // void adDeğiştir(String) sonu
...
private String _ad;
} // Kişi sınıfının sonu
</pre><br />
<pre class="brush:java; gutter:false" name="soyutSınıf">Kişi muhatap = new Kişi(...); // Derleme hatası!
</pre><br />
Sağlanan kod parçasına bakıldığında göze ilk çarpan, <code>Kişi</code> sınıfının, nesnesinin yaratılamayacağını belirtecek şekilde <code><b>abstract</b></code> nitelenmek suretiyle, <i>soyut sınıf</i> olarak tanımlanmış olmasıdır. Bu çeşit sınıflar, kendisinden kalıtlamak suretiyle türetilecek sınıfların gerçekleştirilmesi bağlamında çerçeveyi çizmek amacıyla tanımlanırlar. Örneğimizde, koridordan geçmekte olan kişinin hangi grupta olmasına bağlı olmaksızın geçerli olan ad bilgisinin sorgulanması ve güncellenmesi işlemleri gerçekleştirilmiş ve böylece söz konusu işlemin diğer sınıflarda yinelenmesinin önüne geçilmiştir. Sağlanan yeniden kullanım sayesinde, değişiklik gereksiniminin belirmesi durumunda veya ortaya çıkan bir hatanın giderilmesi istendiğinde, bakılması gerekecek yer sayısı bire indirilmiş ve böylece kodun bakımı kolaylaştırılmıştır.<br />
<br />
Göze çarpan ikinci nokta, <code>adDeğiştir</code> metodunun tanımında kullanılan niteleyici: <code><b>protected</b></code>. Bu erişim niteleyicisi, ilişkin öğeye, ait olduğu sınıfın paketindeki ve aynı sınıfın kökü olduğu kalıtım sıradüzenindeki sınıflar tarafından erişim izni verildiğini söylüyor. Buna göre, <code>adDeğiştir</code> metodu <code>Kişi</code> ve dolaylı veya dolaysız <code>Kişi</code>'den kalıtlayan sınıflara ve <code>Kişi</code> ile aynı paketteki sınıflara <code><b>public</b></code> gibi gözükürken, diğer sınıflara <code><b>private</b></code> gibi gözükecektir.<br />
<br />
Açıklamaya muhtaç bir diğer nokta, metotlarımızı nitelemekte kullandığımız <code><b>final</b></code>. İliştirildiği programlama öğesinin tanımında aldığı değerin daha sonra değişmeyeceğini ilan eden bu niteleyicinin kullanımı, söz konusu öğenin veri olması durumunda sabitliğe işaret ederken, örneğimizde olduğu gibi bir davranışsal öğeyi nitelemesi durumunda bu öğeye içinde bulunulan sınıftan türetilecek altsınıflarda ezilmek suretiyle yeni bir gerçekleştirim sağlanamayacağını belirtir.<br />
<br />
Peki ama Java'da kalıtlama nasıl ifade edilir? Yani, bir kavramın bir diğeri gibi olduğuna dair gözlemimizi Java kaynak koduna nasıl aktarabiliriz? Yanıt oldukça basit: türetilmekte olan sınıfın tanımında sınıf adını takiben kullanılan <code><b>extends</b></code> ile sınıfı üstsınıfa bağlamak işimizi görür.<br />
<br />
<div style="color: #444444; text-align: center;">Öğrenci.java</div><pre class="brush:java; gutter:false" name="ÖğrenciSınıfı">public class Öğrenci extends Kişi {
...
public String sınıfı() { return _sınıf; }
...
private int _sınıf;
} // Öğrenci sınıfının sonu
</pre><br />
Yukarıdaki tanıma göre, <code>Öğrenci</code> sınıfının <code>Kişi</code>'den türetildiğini gören derleyici, <code>Öğrenci</code> nesnelerinin <code>Kişi</code> nesneleri gibi olduğunu ve bunun sonucu olarak <code>Kişi</code>'de tanımlanan iletileri de alabileceğini bilir. Bundan dolayıdır ki, aşağıdaki çıktı komutu, gönderilen ileti <code>Öğrenci</code> sınıfında bulunmamasına rağmen bekleneni yapacak ve <code>öğr</code> ile temsil edilen öğrencinin <code>Kişi</code>'den kalıtladığı ad özelliğini çıktı ortamına basacaktır.<br />
<pre class="brush:java; gutter:false" name="kalıtlama">Öğrenci öğr = new Öğrenci(...);
System.out.println(öğr.ad());
</pre>Bu noktada, C++ ile nesne yönelimli programlama yapanlara iki hatırlatma yapmakta yarar var. Öncelikle, Java'da sınıf kalıtlaması teklidir; bir sınıf sadece bir sınıftan türetilebilir.<sup><a id="ref4" href="#fn4">4</a></sup> Ayrıca, sınıf tanımı başlığında belirtilmemiş olsa bile, her sınıf bir diğerinden kalıtlar; üstsınıf bilgisinin eksik olduğu tanımlarda, sınıfın <code>Object</code> sınıfından kalıtladığı varsayılır. Dolayısıyla, şu ana kadar ki kısmi sınıf sıradüzeni şöyle oluşacaktır.<br />
<br />
<ul><li><code>Object</code></li>
<ul><li><code>Kişi</code></li>
<ul><li><code>Öğrenci</code></li>
</ul></ul></ul><br />
Buna göre, <code>Öğrenci</code> nesneleri, geçişli olan <i>gibi olmak</i> ilişkisi nedeniyle, <code>Öğrenci</code> ve <code>Kişi</code> sınıflarındaki iletilerin yanısıra, <code>Object</code> sınıfındaki <code>equals</code>, <code>toString</code> gibi iletilere de yanıt verecektir.<br />
<br />
Gelelim, koridorda rastlayacağımız çalışanların ortak yönlerini temsil etmek için sağlayacağımız sınıfa. Aşağıdaki kod parçasından da görülebileceği gibi, müstahdem ve öğretmenlerin paylaştığı fakat öğrencilerin sahip olmadıkları özellikleri soyutlayan bu sınıf, <code>Kişi</code> sınıfı gibi soyut tanımlanmış. Sınıfın soyut olmasına ek olarak, metotlardan biri de (<code>sızlan</code>) soyut ilan edilmiş. Bu, içinde bulunulan sınıfta söz konusu iletiye dair bir metot gövdesi sağlanmayacağı anlamına gelir.<br />
<br />
<div style="color: #444444; text-align: center;">Çalışan.java</div><pre class="brush:java; gutter:false" name="ÇalışanSınıfı">public abstract class Çalışan extends Kişi {
...
public int aylıkGelir() { return _ekstralar + _maaş; }
public final int çocukSayısı() { return _çckSayısı; }
public abstract void sızlan();
...
private int _çckSayısı, _ekstralar, _maaş;
} // Çalışan sınıfının sonu
</pre><br />
<code><b>abstract</b></code> ilan edilen bir metot, içinde bulunduğu sınıfı da otomatikman soyut kılar. Çünkü, metot gövdesinin bulunmaması ilişkin iletinin nesneye gönderilmesi durumunda çağrılacak bir metodun olmaması anlamına gelir ki. bu, arayüzde listelenen bir işlevin yerine getirilmediği anlamını taşır. [Müzik setinizdeki ses düğmesinin (ileti) donanımdaki devrelere (metot) bağlı olmadığı için işlev görmemesinin pek de mantıklı olmadığını takdir edersiniz.] Çözüm, bu tür aykırı durumların önüne geçmek için ortaya çıkmalarını engellemekten geçer. Dolayısıyla, sınıfın da <code><b>abstract</b></code> ilan edilmesi gereklidir.<br />
<br />
Farkındayım, çok uzun oldu. Ama, işin en heyecanlı kısmına geldik. Onun için biraz daha sabredin de birlikte aşağıda verilen <code>Öğretmen</code> sınıfının gerçekleştirimine göz atalım. Somut—yani, nesnesi yaratılabilir— bir sınıf olarak tanımlanan <code>Öğretmen</code> sınıfında daha önceden görmediğimiz iki şey var: <code>Override</code> açımlaması<a href="http://ta-java.blogspot.com/p/programlama-sozlukcesi.html#açımlama">≝</a> ve <code><b>super</b></code> ayrılmış sözcüğü.<br />
<br />
<div style="color: #444444; text-align: center;">Öğretmen.java</div><pre class="brush:java; gutter:false" name="ÖğretmenSınıfı">import java.util.Vector;
public class Öğretmen extends Çalışan {
...
@Override
public int aylıkGelir() {
return super.aylıkGelir() + _özelDersGeliri;
} // int aylıkGelir() sonu
@Override
public void sızlan() {
System.out.print("Ne olacak bu memleketin hali!!!");
} // void sızlan() sonu
public Vector<String> dersler() {
return _verdiğiDersler;
} // Vector<String> dersler() sonu
...
private int _özelDersGeliri;
private Vector<String> _verdiğiDersler;
} // Öğretmen sınıfının sonu
</pre><br />
<code><b>super</b></code>, içinde bulunulan sınıfın üstsınıfındaki bir özelliğin kullanılmak istendiğini belirtir. Buna göre, öğretmenlerin gelirleri, <code>Çalışan</code> sınıfında sağlanan toplama özel derslerden alınan ücretin eklenmesi ile hesaplanacaktır. <code><b>super</b></code>'in konulmaması, metodun kendisini çağırarak sonsuz döngüye girmesine neden olacaktır.<br />
<br />
<code>@Override</code> açımlaması, iliştirildiği metodun kalıtlanılan aynı imzalı bir metodu ezmekte olduğunu ilan eder. Bundan hareketle derleyici, üstsınıflarda—<code>Object</code>, <code>Kişi</code> ve <code>Çalışan</code>— bu imzaya sahip bir metodun varlığını denetler ve sonucun olumsuz olması durumunda hata vererek derlemeyi durdurur. Zorunlu olmayan bu açımlamanın kullanılması, metot imzasının yanlış yazılması durumunda ortaya çıkabilecek sinsi mantık hatalarının önüne geçilmesine yarar. Örnek olarak, yukarıdaki kod parçasında <code>aylıkGelir</code> yerine yanlışlıkla <code>aylıkgelir</code> yazdığınızı düşünün. <code>@Override</code> açımlaması olmadan yazıldığı takdirde, derleyici <code>aylıkgelir</code> ve <code>aylıkGelir</code> iletilerine karşılık, biri üstsınıftan kalıtlanan diğeri sınıf tarafından sağlanan, iki metot bulunduğunu düşünecektir. <code>@Override</code> açımlamasının kullanılması durumunda ise, metot adlarındaki küçük farklılık görülecek ve yazım hatası mantıksal hataya dönüşmeden derleme hatası olarak yakalanacaktır.<br />
<br />
Peki ama o zaman, <code>Öğretmen</code> sınıfındaki <code>sızlan</code> metodu <code>@Override</code> ile açımlanırken neden aşağıda verilen <code>Müstahdem</code> sınıfındaki aynı imzalı metot açımlanmamıştır? İşin sırrı, <code>sızlan</code> metodunun üstsınıfta soyut tanımlanmış olmasında yatmaktadır. Bu, somut bir altsınıfın anılan metoda dair bir gerçekleştirim vermeden kullanılamayacağı anlamına gelir; altsınıfta <code>sızlan</code> metodunun sağlanmaması, derleyici tarafından yakalanacak ve gerçekleştirim sağlanmadıkça derleme hatası ortadan kalkmayacaktır. Yani, metodun üstsınıfta soyut ilan edilmesi zaten <code>@Override</code> açımlamasının görevini görmektedir; söz konusu metodun ayrıca açımlanması gereksizdir.<br />
<br />
<div style="color: #444444; text-align: center;">Müstahdem.java</div><pre class="brush:java; gutter:false" name="MüstahdemSınıfı">public class Müstahdem extends Çalışan {
...
public boolean kömürYardımı { return _kömürYardımı; }
public void sızlan() {
System.out.print("Allah devletimize milletimize zeval vermesin!!!");
} // void sızlan() sonu
...
private boolean _kömürYardımı;
} // Müstahdem sınıfının sonu
</pre><br />
Evet, problemimizde geçen kavramlara karşılık gelen sınıfları gerçekleştirdik. Sıra, bu sınıfları ve bu sınıfların nesnelerini nasıl kullanabileceğimizi anlamaya geldi. Aşağıdaki kod parçasını takip ederek görelim. <br />
<pre class="brush:java; gutter:false" name="çokbiçimlilik">Çalışan birisi;
if (Math.random() > 0.5)
birisi = new Öğretmen(...);
else birisi = new Müstahdem(...);
System.out.print(birisi.aylıkGelir());
System.out.print(birisi.dersler()); // Derleme hatası!
</pre>Kod parçasına göre, <code>Çalışan</code> türünde ilan edilen <code>birisi</code> adlı tutacak, üretilen rastgele sayıya göre kimi zaman bir <code>Öğretmen</code> nesnesini gösterirken kimi zaman bir <code>Müstahdem</code> nesnesini göterecektir. Bu bağlamda, iki nokta sizi kod parçamızda hata olabileceği düşüncesine sevkedebilir.<br />
<br />
<ol><li>Nasıl olur da, soyut tanımlanan <code>Çalışan</code> sınıfı <code>birisi</code> adlı değişkenin türünü tanımlamakta kullanılabilir? Yanıt: Bileşke türden değerlerin tutacak ve nesne olmak üzere iki kısımdan oluştuğunu unutmuşa benziyorsunuz; burada yaptığımız nesne yaratmak değil, nesne iliştirilmemiş bir tutacak tanımlamak. [Bileşke türlerin bu özelliği size yabancı geliyorsa, şu yazıyı<a href="http://ta-java.blogspot.com/2011/04/bileske-turler-diziler.html">🔎</a> okumanızı tavsiye ederim.]</li>
<li>Nasıl olur da, <code>Çalışan</code> türündeki bir tutacak <code>Öğretmen</code> veya <code>Müstahdem</code> türünde bir nesne gösterebilir? Yanıt: her iki sınıfın nesneleri de <code>Çalışan</code> merceğinden görülebilir. Ne de olsa, öğretmen veya müstahdem, her ikisi de bir çalışandır. Ancak, nesne ne türden olursa olsun, <code>birisi</code> tutacağı yoluyla arkadaki nesneye <code>Çalışan</code> sınıfının arayüzünde bulunmayan iletiler gönderilemez.</li>
</ol><br />
Örneğimize derleyicinin gözlerinden bakarak ikinci maddeyi açalım. Birincil görevi koddaki tanımlayıcıların ilan edildiklerine uygun bir biçimde kullanıldığını denetlemek olan derleyici, <code>birisi</code> adlı değişkenin doğru kullanıldığını garanti etmek isteyecektir. Bu, <code>birisi</code> vasıtasıyla arkadaki nesnenin doğru kullanılmasının—yani, nesneye anlayabileceği iletilerin gönderilmesinin—her koşulda sağlanması demektir. Örneğimizi, durum durum ele alarak görelim.<br />
<br />
<ol><li><code>birisi</code>'nin arkasındaki nesne <code>Öğretmen</code> türünde: Nesnemiz, <code>Öğretmen</code> sınıfının arayüzündeki iletilere yanıt verebilir. Bu iletiler, <code>Object</code>, <code>Kişi</code>, <code>Çalışan</code> ve <code>Öğretmen</code> sınıflarının arayüzlerindeki iletilerin bileşkesidir.</li>
<li><code>birisi</code>'nin arkasındaki nesne <code>Müstahdem</code> türünde: Nesnemiz, <code>Müstahdem</code> sınıfının arayüzündeki iletilere yanıt verebilir. Bu iletiler, <code>Object</code>, <code>Kişi</code>, <code>Çalışan</code> ve <code>Müstahdem</code> sınıflarının arayüzlerindeki iletilerin bileşkesidir.</li>
</ol><br />
Aynı kodun kullanılıyor olmasına karşın, programın çalışması sırasında rastgele sayı üretecinin döndürdüğü değere göre dinamik olarak belirlenen kullanım senaryosu, doğruluk denetimi esnasında elinde kaynak kod ve kaynak kodun derlenmiş hali olan sınıf dosyalarından başka bir şey bulunmayan derleyicinin nesnenin türünden yararlanarak karar veremeyeceği anlamına gelir. Programı çalıştırarak nesnenin hangi sınıfa ait olacağını görme lüksü olmayan derleyici, görevini koddan çıkarsayabileceği bilgilerle görmek zorundadır. Aradığımız bilgi, nesnelere erişimde aracılık eden tutacaktadır: Derleyici, nesnenin doğru kullanımını denetlemek için <i>statik tür</i> olarak da adlandırılan tutacak türünü kullanır. Yani, <b>bir nesneye hangi iletilerin gönderilebileceğine nesneyi gösteren tutacağın türü kullanılarak karar verilir</b>. Bundan dolayıdır ki, derleyici <code>birisi</code>'nin arkasındaki nesneye <code>dersler</code> iletisinin gönderilmesine izin vermeyecektir. Çünkü, <code>Çalışan</code> türündeki bir tutacak, <code>Çalışan</code>'ın kökü olduğu sınıf sıradüzeni içindeki somut sınıfların türünden olan nesneleri gösterebilir ve bu nesnelerin bazıları <code>Öğretmen</code>'e özel <code>dersler</code> iletisini anlamaz.<br />
<br />
Peki, gönderilmesine izin verilen bir ileti, hangi metodun çağrılmasına yol açar? Buna, koridorda rastladığımız çalışanlara aylık gelirlerini sorduğumuz senaryoyu düşünerek yanıt vermeye çalışalım. Çalışanın müstahdem olması durumunda yanıt, maaş ve ek gelirlerin toplanması ile verilirken, muhatabımızın öğretmen olması durumunda maaş ve ek gelirlere özel derslerden alınan ücretlerin eklenmesi gerekecektir. İşin kısası, aynı soru muhatabımızın özelliklerine bağlı olarak farklı yöntemler kullanılarak yanıtlanacaktır. Vardığımız sonucu programlama sözlükçemiz ile ifade edecek olursak, aynı gönderinin (ileti gönderme) farklı metotların çağrılmasına neden olabileceğini söyleyebiliriz. <i>Çokbiçimlilik</i> olarak adlandırılan bu özelliğin örneği, <code>aylıkGelir</code> iletisinin gönderilmesinde görülebilir: <code>birisi</code>'nin süzgecinden geçen <code>aylıkGelir</code> iletisi, rastgele sayı üreticinin döndürdüğü değere bağlı olarak yaratılan nesnenin türüne—<i>dinamik tür</i> olarak da adlandırılır—göre ya <code>Öğretmen</code> sınıfındaki ya da <code>Müstahdem</code> sınıfındaki aynı imzalı metodun çağrılmasına neden olacaktır. Yani, <b>bir nesneye gönderilen ileti sonucu hangi metodun çağrılacağına nesnenin türüne bakılarak karar verilir</b>. Bir iletinin çağrılacak metoda çalışma anında bağlanmasına ise <i>dinamik iletim</i> denir.<br />
<br />
Özetleyecek olursak;<br />
<br />
<ol><li>Öncelikle, derleme sırasında tutacak türü kullanılarak gönderinin doğruluk denetimi yapılır. Bu denetim sırasında, farklı imzalara sahip iletiler farklı addedilir.</li>
<li>Programın çalışması sırasında, nesnenin türüne bakılarak hangi metodun çağrılacağına karar verilir.</li>
</ol><br />
Evet, kalıtlama ile ilgili ilk yazımız için bu kadar yeter sanırım. Aklınızdan çıkmaması gereken şu sloganı yineleyerek kapatalım: <b>kalıtlama bir araçtır, amaç değil; amaç kodun yeniden kullanılması yoluyla yazılımın yukarıda anılan ölçüleri olumlu etkileyecek bir biçimde geliştirilmesidir</b>.<br />
<br />
</div><div style="text-align:right"><a href="http://ta-java.blogspot.com/2011/05/bileske-turler-snflar.html">Sınıflar</a><br />
<a href="http://ta-java.blogspot.com/2011/09/kategorize-etmenin-yararlar-ve.html">Arayüzler</a><br />
</div><hr /><div id="dipnotlar" style="text-align: justify;"><ol><li id="fn1">Altını çizmekte yarar var; en son okudukları programlama kitabı 16 ikillik mimarilerin teknoloji harikası olarak tanıtıldığı yıllarda basılmış kişilerin aksine, bir sonraki maddede atıfta bulunulan çalışma hızının artırılmasından bahsetmiyoruz. Bilgisayar donanımının görece değerinin azalıp, en değerli kaynak olarak yerini geliştiriciye bıraktığını vurgulamak için geliştirme zamanı diyoruz. Nedenimiz basit: aklı başında şirket yöneticileri, düşük maliyetli bir kaynaktan (işlemci, bellek, vb.) ziyade yüksek maliyetli kaynağın (insan) verimliliğini artırmaya öncelik verir. Ancak bu, düşük maliyetli kaynağın har vurup harman savrularak kullanılabileceği anlamına gelmemelidir. <a href="#ref1">↑</a></li>
<li id="fn2">Kalıtlama, çözümleme ve tasarım aşamalarında da kullanılan bir araçtır. Ancak, yazımızda kalıtlamanın Java programı yazarken nasıl kullanılabileceğine değinmekle yetineceğiz. <a href="#ref2">↑</a></li>
<li id="fn3">Aslında, yapılan araştırmalar, mutasyonların bireyin yaşamı sırasında maruz kaldığı yüksek radyasyon nedeniyle de olabileceğini gösteriyor. <a href="#ref3">↑</a></li>
<li id="fn4">Eksiklik gibi gözüken bu durum, çoğu zaman sınıfların arayüz(ler) gerçekleştirmesi ile giderilebilir. <a href="#ref4">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-31939632474077419152011-08-17T14:54:00.000+03:002011-12-14T15:55:14.805+02:00Java ve J'li Harf Çorbası<div id="anaMetin" style="text-align: justify;">Java programlamaya başlayanların aklını karıştıran konuların başında, ekseriye J ile başlayan teknoloji, standart ve ürün adları gelir. Çoğu zaman arka planda ne olup bittiğinin bilinmemesinden kaynaklanan bu durum, pek çok zamansa Sun şirketinin seçtiği (ve değiştirdiği) adlandırma biçiminden kaynaklanır.<br />
<br />
İnternet olgusunun ayırdındaki herkesin bildiği ve yazıya giriş tümcesinden de anlaşılabileceği gibi, Java gözde bir programlama dilinin adıdır. Java kullanılarak yazılan programlar, genelde derlemeli-yorumlama yöntemi ile çalıştırılır. Yani; Java kaynak kodu önce derlenerek [<a href="http://en.wikipedia.org/wiki/Bytecode"><i>Bytecode</i></a> adındaki] bir ara dilin komutlarını içeren sınıf dosyalarına çevrilir, sonra <a href="http://en.wikipedia.org/wiki/JVM"><i>Java Sanal Makinesi</i></a> (JSM; İng., Java Virtual Machine [JVM]) adı verilen bir diğer sistem yazılımı tarafından çalıştırılır. JSM, Bytecode komutlarından oluşan sınıf dosyalarını yorumlamanın yanısıra, sınıf yükleme ve çalıştırma sırasında güvenlik denetimleri de yapar. Örneğin, derleme ile yükleme arasında geçen zaman zarfında sınıf dosyasında oluşabilecek değişiklikler, sınıfın yüklenmesi sırasında yapılan sağlamalar ile saptanabilir. Böylece, kimi kötü niyetle yapılan değişikliklerin kullanıcıya zarar vermesinin önüne geçilmiş olur. Benzer şekilde, programı çalıştıran kullanıcının yetkisiz olduğu güvenlik zaafı yaratabilecek işlemler programın çalıştırılması sırasında yapılan denetimler ile engellenir. JSM'nin bir diğer önemli görevi, kullanılmaz hale gelmiş yığın bellek bölgelerini toplayan <a href="http://en.wikipedia.org/wiki/Garbage_collector_%28computer_science%29"><i>çöp toplayıcı</i></a> bileşeni yoluyla kaynak yönetimini kolaylaştırmaktır. Bu sayede, Java programcıları kaynak yönetimine dönük daha az kod yazacaklar ve bunun sonucunda üretilen kod daha güvenilir olacaktır.<br />
<br />
Rekabete açık bir sektör olan yazılım geliştirme işinde başarıya giden yol, olabildiğince az kaynak (geliştirici zamanı, bilgisayar zamanı ve belleği, vb.) kullanarak piyasanın rağbet edeceği nitelikli ürünleri en kısa sürede geliştirip piyasaya sürmekten geçer. Bu bağlamda uygulanan evrensel kural, aynı ürünün tekrar tekrar geliştirilmesinden kaçınarak daha önceden yapılmış eş işlevli bir ürünün kullanılmasıdır. Yeniden geliştirme için harcanacak zamanın ürünün eniyileştirilmesi ve son ürüne tümleştirilmesine ayrılması, daha güvenilir ve daha yüksek performanslı yazılım üretilmesini olanaklı kılacaktır. İşte bu noktada, Java geliştiricisinin hizmetine <i>Java ortamı</i> (<i>platform</i>) adı verilen standart kitaplıklar sunulur. Söz konusu ortam, hedeflenen kitleye bağlı olarak farklı işlevsellikler sunabilir ve buna bağlı olarak değişik şekillerde adlandırılabilir. <a href="http://en.wikipedia.org/wiki/Java_SE"><i>Java SE</i></a>, standart (dizüstülü/masaüstülü) geliştiriciye hitap ederken, <a href="http://en.wikipedia.org/wiki/Java_EE"><i>Java EE</i></a> başta İnternet olmak üzere dağıtık ortamlarda çalışacak sunucu tarafı programların geliştirilmesini kolaylaştıran yazılım çerçeveleri içerir; <a href="http://en.wikipedia.org/wiki/Java_ME"><i>Java ME</i></a> ise mobil aygıtlar başta olmak üzere gömük sistemler düşünülerek hazırlanmıştır.<br />
<br />
Java platformlarının oluşturulması, Java Topluluğu'nun ortaklaşa çabasıyla ortaya çıkarılan çeşitli standartların ürün haline getirilmesiyle mümkün olur. Örneğin, programcıların sıklıkla ihtiyaç duydukları veri yapılarına dair arayüz/sınıf tanımlarının bulunduğu <a href="http://en.wikipedia.org/wiki/Java_Collections"><i>Java Kapları</i></a> (İng., Java Collections) Java SE'nin bir parçası iken, bileşen temelli dağıtık sunucu programları geliştirmekte kullanılan <a href="http://en.wikipedia.org/wiki/EJB"><i>Java Şirket Taneleri</i></a> (İng., Enterprise Java Beans [EJB]) Java EE'nin bir parçasıdır.<br />
<br />
Java geliştiricileri çalıştıkları platformdaki sınıf dosyası kitaplıklarına ek olarak, yazılım geliştirmenin değişik aşamalarında kullanılmak üzere, derleyici, JSM, arşivci ve dokümantasyon aracı gibi bir çok sistem programına ihtiyaç duyar. Bu araçlar ve platformun bileşkesi, <i>Yazılım Geliştirme Kutusu</i> (YGK; İng., Software Development Kit [SDK]) olarak adlandırılır. Söz konusu platformun Java SE olması durumunda, adlandırma <a href="http://en.wikipedia.org/wiki/JDK"><i>Java Geliştirme Kutusu</i></a> (JGK; İng., Java Development Kit [JDK]) şeklinde yapılır.<br />
<br />
Java kaynak kodu yazmaktansa Java platformunda hazırlanmış programları kullananların geliştirme araçlarının tümüne ihtiyacı olmaz. Örneğin, bu kişiler programları çalıştırmak için JSM'ne ihtiyaç duyarken, derleyici ve arşivci gibi sistem programlarına ihtiyaç duymayacaklardır. Sadece kullanıcı olmak özelliği taşıyan bu kişiler için <a href="http://en.wikipedia.org/wiki/JRE#Execution_environment"><i>Java Çalıştırma Ortamı</i></a> (JÇO; İng., Java Runtime Environment [JRE]) yeterli olacaktır.<br />
<br />
Pek çok zaman, kullanılan platform hangisi olursa olsun, Java geliştiricileri ilişkin kısaltmayı da kısaltarak, Java SE/Java EE/JavaME yerine Java demek yoluna giderler. Ancak bu kullanımın programlama dili adı kullanımından ayırt edilmesi gerekir. Çünkü; en yaygın dilin Java olmasına karşın Java platformu, kaynak kodları sınıf dosyasına derlenebilen tüm dillere açıktır. Ne de olsa, programlar sınıf dosyalarının JSM üzerinde çalıştırılması ile işini görmektedir. Dolayısıyla, Python'un Java platformu uyarlaması olan <a href="http://www.jython.org/">Jython</a> veya <a href="http://groovy.codehaus.org/">Groovy</a>'nin platform indinde Java programlama dilinden hiçbir farkı yoktur. Ne demek istediğime aşağıdaki örnek üzerinden açıklık getireyim.</div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwW-ghebRAywMu4qWz7rxh1rp4UuElfynBoFVDtpWbITHtHlkcfI84InNorgUYEAPOk_xgj712lULKv0rnuz2wfUDF4zkMBQCZ8we4K13ORmRO5dUsGzD98jHVhKSXucTVzNE1jqqaOQCA/s1600/JSM_Platform.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="121" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwW-ghebRAywMu4qWz7rxh1rp4UuElfynBoFVDtpWbITHtHlkcfI84InNorgUYEAPOk_xgj712lULKv0rnuz2wfUDF4zkMBQCZ8we4K13ORmRO5dUsGzD98jHVhKSXucTVzNE1jqqaOQCA/s320/JSM_Platform.png" width="320" /></a></div><br />
<div style="text-align: justify;">Sırasıyla, A ve B şirketleri tarafından yazılan a.groovy ve b.py dosyalarının derlenmesiyle elde edilen a.class ve b.class, iki şirketin ortak müşterilerinden birinin yazdığı Prog.java içinden kullanılmaktadır. Sınıf yolu (İng., classpath) ayarlarına uygun bir şekilde müşteri bilgisayarının erişimindeki disk konumlarına yerleştirilen bu dosyalar, Prog.java dosyasındaki ilişkin atıfların doğruluk denetimlerinin yapılması noktasında derleyici tarafından kullanılır. Derleyicinin kaynak kod yerine sınıf dosyasından yararlanabilmesini olanaklı kılan özellik, sınıf dosyası formatı ve dosyanın içeriğini oluşturan Bytecode komutlarının standardize edilmiş olmasıdır. Bir diğer deyişle, Java kaynak kodlarının taşınabilirliğine ek olarak, sınıf dosyaları da nesne kodu düzeyinde taşınabilirliğe sahiptir. Daha sonra, derleme sonucunun olumlu olması durumunda üretilen Prog.class, JSM'ne geçirilerek çalıştırılacak ve Java, Python ve Groovy dillerinin işlevsellik kattıkları program işini görecektir.</div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-21497240472366237542011-08-16T15:50:00.001+03:002011-12-14T16:00:12.184+02:00Bazı Önemli Sınıflar-java.util.Arrays<div id="anaMetin" style="text-align:justify">Dizilere uygulanabilecek işlemlerin eleman erişim ve güncelleme ile uzunluk sorgulamaya sınırlı olması, size Java'da bir şeylerin eksik olduğunu düşündürebilir. Java platformu tasarımcıları da bu görüşü paylaşıyor olmalılar ki, <code>java.util</code> paketindeki <code>Arrays</code> sınıfı vasıtasıyla epey geniş bir işlevsellik sunmuşlar. İşte bu yazıda yapacağımız, anılan sınıf tarafından sağlanan ve değişik eleman türlerine göre ezilmiş metotlara bakmak olacak.<br />
<br />
Kopyalama işlemi ile başlayalım. Dizileri tanıttığımız yazıdan<a alt="Java'da diziler" href="http://ta-java.blogspot.com/2011/04/bileske-turler-diziler.html">🔎</a> da hatırlayacağınız gibi, dizi türlü bir değişkenin bir diğerine atanması, etkisini tutacaklar aracılığıyla göstereceği için kopyalama değil paylaşmaya neden olacaktır; kopyalama döngü içinde her elemanın teker teker kopyalanması ile mümkündür. Ancak, standart olmayan bu çözümün yerine, ilk argümanındaki dizinin başlangıcından itibaren ikinci argümandaki kadar elemanı kopyalayan ve çıkardığı kopyanın tutacağını döndüren <code>Arrays.copyOf</code> metodunun kullanılması yerinde olacaktır.<br />
<pre class="brush:java; gutter:false" name="diziKopyalama">import java.util.Arrays;
...
Integer[] tekler = new Integer[] {1, 3, 5, 7, 9};
Integer[] tKopya = Arrays.copyOf(tekler, tekler.length);
</pre>Benzer bir işleve sahip <code>Arrays.copyOfRange</code>, ikinci ve üçüncü argümanlarının işaret ettiği indisler arasındaki dilimi kopyalayarak işini görür. Dolayısıyla, <code>Arrays.copyOf</code> ile <code>Arrays.copyOfRange</code> arasında şu denklik kurulabilir.<br />
<br />
<div style="text-align: center;"><code>Arrays.copyOf(dz. i) ≡ Arrays.copyOf(dz, 0, i)</code><br />
</div><br />
Bileşke türlü değerlerin Java'da tutacak ve nesne olmak üzere iki parça ile temsil edildiğini unutanları şaşırtacak bir diğer işlem eşitlik denetimidir. Ancak, <code>==</code> ile işin olmayacağını bilip şansını <code>equals</code> iletisi ile denemek isteyenler için de sonuç hayal kırıklığı olacaktır. Bu durumun nedeni dizi türlerinin Java programlama dili tarafından ele alınışında yatar: eleman sayısını dizinin türünü belirlemekte kullanmayan Java derleyicisi, her farklı eleman türü için, programcı tarafından doğrudan erişilemeyen ve <code>Object</code> sınıfından kalıtlayan özel bir sınıf sentezler. Bu bilgi, sentezlenen sınıflarda <code>Object</code>'teki <code>equals</code> gerçekleştiriminin ezilmediği bilgisiyle birleştirildiğinde, <code>equals</code> kullanımının neden derdimize çare olmadığı görülecektir: dizi nesnelerine <code>equals</code> iletisinin gönderilmesi <code>Object</code>'te sağlanan ve <code>==</code> ile aynı şekilde çalışan metodun işlemesine neden olacaktır. Bir diğer deyişle, dizi nesneleri için <code>equals</code> ile <code>==</code> arasında bir fark yoktur. O zaman, ne yapmamız gerek? Yanıt, <code>Arrays</code> sınıfındaki iki metottan birini kullanmaktan geçer. Bunlardan ilki, tek boyutlu dizilerin eşitlik denetimini yapan <code>Arrays.equals</code> metodudur.<br />
<pre class="brush:java; gutter:false" name="diziEşitliği">boolean eşitMi = tekler == tKopya; // eşitMi ← false
eşitMi = tekler.equals(tKopya); // eşitMi ← false
eşitMi = Arrays.equals(tekler, tKopya); // eşitMi ← true
</pre><code>Arrays.equals</code> metodunun neden tek boyutlu dizilere sınırlı olduğunu, çok boyutlu dizilerin Java'da nasıl temsil edildiğine bir kez daha değinerek açıklık getirelim: Java'da çok boyutlu diziler, elemanları dizi olan diziler şeklinde tutulur. Dolayısıyla, dizinin elemanlarına <code>equals</code> iletisini göndermek suretiyle işini gören <code>Arrays.equals</code> metodunun çok boyutlu diziler için iş görmesi olanaksızdır. Bunu, aşağıdaki kod parçası üzerinden görelim.<br />
<pre class="brush:java; gutter:false" name="diziEşitliği2">char[][] bulmaca = new char[10][12];
char[][] bulmaca2 = new char[10][12];
// Dizileri eşit içeriğe sahip olacak şekilde doldur
eşitMi = Arrays.equals(bulmaca, bulmaca2); // eşitMi ← false
eşitMi = Arrays.deepEquals(bulmaca, bulmaca2); // eşitMi ← true
</pre>Yukarıdaki dizilerin eşitlik denetimi için <code>Arrays.equals</code> metodunun kullanılması, dizilerin eleman türü olan ve derleyici tarafından sentezlenen <code>char[]</code> sınıfındaki <code>equals</code> metodunun kullanılması ile işini görecektir. Bu ise, söz konusu metodun <code>==</code> ile aynı şekilde çalışması nedeniyle, dizilerin karşılıklı elemanlarının eşitlik yerine aynılık denetimi yapılarak kontrol edilmesi anlamına gelir ve beklediğimiz sonucu vermez. Çözüm, <code>Arrays.deepEquals</code> metodunun kullanımından geçer.<br />
<br />
Eşitlik denetimi için öne sürülen sebepten ötürü, hoş yazım ve kıyım işlemleri<sup><a href="#dn1" id="ref1">1</a></sup> de metot çiftleri ile karşılanır: <code>Arrays.toString</code>, <code>Arrays.deepToString</code> ve <code>Arrays.hashCode</code>, <code>Arrays.deepHashCode</code>.<br />
<br />
<code>Arrays</code> sınıfında yer alan bir diğer metot, kendisine geçirilen dizinin elemanlarını aynı değer ile doldurmaya yarayan <code>fill</code> metodudur. Bu metot, dizinin tümü ve bir dilimi üzerinde etkisini gösteren iki farklı uyarlamaya sahiptir ve bu uyarlamalar aşağıdaki denklikle ilişkilendirilebilir.<br />
<br />
<div style="text-align: center;"><code>Arrays.fill(dz. i) ≡ Arrays.fill(dz, 0, dz.length, i)</code><br />
</div><br />
Değineceğimiz bir sonraki metot, belki de gezegenimizdeki kurulu bilgisayar sistemlerinin işlemcilerini en çok meşgul eden arama işleminin gerçekleştirimini sağlar. Algoritma derslerinden tanıdık ikili aramayı gerçekleştiren <code>Arrays.binarySearch</code> metodu, ilk argümanında sağlanan elemanları <u>artan sırada</u> dizilmiş dizinin içinde bir anahtar değerin geçip geçmediğine bakar. Denetim sonucunun olumlu olması durumunda, anahtarın geçtiği dizi elemanının indisi döndürülür;<sup><a href="#dn2" id="ref2">2</a></sup> aramanın başarısız olması durumunda ise, anahtarın sırayı bozmayacak şekilde diziye eklenmesi halinde eklemenin yapılacağı indisin -1 ile çarpımından 1 çıkarılması sonucu elde edilen değer döndürülür. Daha hızlı sonuç elde edilmesi amacıyla, arama dizinin belirli bir dilimine sınırlandırılabilir. Tüm dizi ve dizi dilimi üzerinde çalışan uyarlamalar arasındaki ilişki aşağıda verilmiştir.<br />
<br />
<div style="text-align: center;"><code>Arrays.binarySearch(dz. anahtar)<br />
≡<br />
Arrays.binarySearch(dz, 0, dz.length, anahtar)</code><br />
</div><br />
<code>Arrays.binarySearch</code> metodunun bileşke türlü elemanlara sahip bir dizide arama yapması öncesinde, ilişkin türe dair sınıfın gerçekleştiriminde <code>equals</code> metodunun uygun bir biçimde sağlanmış olmasına dikkat edilmelidir. Aksi takdirde, dizide var olan bir anahtarın bulunmadığının ilan edilmesi gibi bir hata ortaya çıkabilir. Böylesine bir durumun önüne geçmek için başvurulacak ikinci bir yol, <code>java.util.Comparator</code> arayüzünü gerçekleştiren karşılaştırıcı bir sınıfta <code>equals</code> iletisinin gerçekleştirilmesi ve bu sınıfın bir nesnesinin diğer argümanların ardından son argüman olarak <code>Arrays.binarySearch</code>'e geçirilmesidir.<br />
<br />
İkili aramanın hünerini sıralı bir dizi üzerinde göstermesi, dizinin sıralı bir şekilde oluşturulması veya ikili arama öncesinde sıralanması zorunluluğunu da beraberinde getirir. Programcıya yüklenen bu sorumluluk, <code>Arrays.sort</code> metodu ile hafifletilebilir. İsteğe göre dizinin tümü veya belirli bir dilimini sıralayan bu metot, dizinin eleman türünün karşılaştırılabilir olmasını bekler. Bu ise, bileşke türler için ilişkin sınıfın <code>Comparable</code> arayüzünü gerçekleştirmesi ve/veya karşılaştırma ölçütünün <code>java.util.Comparator</code> arayüzünü gerçekleştiren bir sınıf vasıtasıyla söz konusu işlevselliğin sağlanması gerektiği anlamına gelir.<br />
<br />
<div style="text-align: center;"><code>Arrays.sort(dz) ≡ Arrays.sort(dz, 0, dz.length)</code><br />
</div><br />
Göz atacağımız son metot olan <code>Arrays.asList</code>, yegâne argümanındaki diziyi Veri Kapları Çerçevesi'nce tanımlanan <code>java.util.List</code> ve <code>java.util.RandomAccess</code> arayüzlerine sahip bir nesneye çevirir ve bu nesneyi gösteren bir <code>java.util.List</code> tutacağı döndürür. Nesneye gönderilen iletilerin diziyi de etkilemesi nedeniyle, bu işlem dizimizin işlevselliğinin <code>java.util.List</code> arayüzündeki iletilerle—<code>java.util.RandomAccess</code> bir <i>gösterge arayüz</i><a href="http://ta-java.blogspot.com/p/programlama-sozlukcesi.html#göstergeArayüz">≝</a> (İng., marker interface)<sup><a href="#dn3" id="ref3">3</a></sup> olduğu için bu yönde bir katkı sağlamaz—genişletilmesini sağlar. Ancak, bu arayüzün veri kümesini büyütmeye yönelik iletilerinin, dizinin statik doğası nedeniyle, kullanılamayacağı unutulmamalıdır.<br />
<pre class="brush:java; gutter:false" name="listeyeçevirme">List<Integer> tList = Arrays.asList(tekler);
// tekler[2]'de 121 değerine sahip olacak.
tList.set(2, 121);
tList.add(123); // → UnsupportedOperationException
</pre></div><br />
<div style="text-align: right;"><a alt="Java'da diziler" href="http://ta-java.blogspot.com/2011/04/bileske-turler-diziler.html">Diziler</a><br />
</div><hr /><div id="dipnotlar" style="text-align: justify;"><ol><li id="dn1">Kıyım işlemi, doğrudan veya dolaylı bir biçimde dizi gibi rasgele erişimli veri yapılarının kullanıldığı arama algoritmalarında, aranan anahtar değerinin tamsayıya çevrilmesini sağlayarak rasgele erişimli veri yapılarının avantajlarından yararlanmayı olanaklı kılar. <a href="#ref1">↑</a></li>
<li id="dn2">Aranmakta olan anahtar değerin dizi içinde birden çok geçmesi durumunda, hangi indisin döndürüleceği konusunda garantili bir tahmin yapılması olanaklı değildir. <a href="#ref2">↑</a></li>
<li id="dn3">İleti içermeyen arayüzlere gösterge arayüz denir. <a href="#ref3">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-75877222945619479682011-08-04T18:00:00.000+03:002011-12-14T16:03:58.165+02:00Arşiv Dosyaları<div id="anaMetin" style="text-align:justify">Java'nın kısa sürede yaygınlaşmasındaki önemli etkenlerden biri—belki de en önemlisi—Bytecode komutları içeren standardize edilmiş sınıf dosyalarının sanal makine üzerinde çalışmasıyla sağlanan makine kodu düzeyindeki taşınabilirlik özelliğidir. Java'nın doğum yeri olan Sun şirketi tarafından "Bir kez yaz, her yerde çalıştır" (İng., <a href="http://en.wikipedia.org/wiki/Write_once,_run_anywhere">write once, run anywhere (WORA)</a>, write once, run everywhere (WORE)) sloganıyla tanıtılan bu özellik sayesinde, Java programlarının derlendikten sonra JSM bulunduran herhangi bir platformda çalıştırılması mümkün olmaktadır. Bunun için, gerekli sınıf dosyalarının konulduğu bir siteden indirilmek veya CD içinde verilmek suretiyle kullanıcıya sağlanması yeterli olacaktır.<br />
<br />
<ol><li>[Geliştirici] Geliştirilen programı derleyerek sınıf dosyası haline çevir.</li>
<li>Sınıf dosyalarını kullanıcıya sağla.</li>
<li>[Kullanıcı] Programı çalıştır.</li>
</ol><br />
Yazılımın büyümesiyle birlikte yukarıdaki 2 nolu adıma konu olan dosyaların sayısı artacak ve bu da dağıtım işini zorlaştıracaktır. Ayrıca, gereksinilen disk alanı ve indirme zamanı da olumsuz etkilenecektir. Örnek olarak, 90'ların ortalarında Microsoft'un Wintel bağımlısı çözümlerine karşı alternatif olarak ortaya çıktığında büyük heyecan yaratan <i>uygulamacık</i>ların (İng., <a href="http://en.wikipedia.org/wiki/Java_Applet">applet</a>) örün tarayıcıları tarafından çalıştırılmasına göz atalım. Kendisine bağlantı veren sayfalara dinamik içerik sağlayan ve etkisini kullanıcı tarafında çalıştırılarak gösteren uygulamacıklar işlerini şöyle görür.<br />
<br />
<ol><li>Kullanıcı uygulamacık bağlantısı barındıran bir sayfaya tıklar.</li>
<li>Sayfa ile birlikte uygulamacığı oluşturan sınıf dosyaları ve diğer kaynaklar (görüntü, ses, vb.) indirilir.</li>
<li>Uygulamacık, tarayıcıdaki JSM tarafından çalıştırılır.</li>
</ol><br />
Yukarıda kabaca anlatılan sürecin hızı, uygulamacığın gerçekleştiriminde seçilen algoritma ve veri yapıları seçimlerine ek olarak, 2. adımda indirilen dosyaların sayısı ve büyüklüğüne göre değişecektir. Dosyaların artması ve büyümesi performansı ve kullanıcı deneyimini olumsuz yönde etkileyecektir. İşte bundan dolayı—daha genel bir ifadeyle, yazılım dağıtımını daha etkin hale getirmek için—Java platformu tasarımcıları arşiv dosyalarını sağlamışlardır. Buna göre, geliştiricilerden beklenen, birlikte indirilecek/kurulacak dosya ve kaynakları sıkıştırarak bohçalayan arşiv dosyalarının kullanımıdır.<br />
<br />
<h3>Arşiv Manipülasyonu</h3><br />
Arşiv dosyaları Yazılım Geliştirme Kutusu'nun parçası olan <code>jar</code> adlı arşivci komutu yardımı ile oluşturulabilir. Bu komut, ZIP-temelli formata sahip bir arşiv oluşturmanın yanısıra, verilen bir arşivin güncellenmesi, içeriğinin listelenmesi ve açılması için de kullanılabilir. Hangi işlemin kastedildiği komut adını takiben verilen ve aşağıdaki değerleri alabilen bir opsiyon ile belirtilir. Listelenenlere ek olarak, <code>f</code> opsiyonunun işlemin hedefi olan arşiv dosyasının adı için kullanıldığı bilinmelidir. Ayrıca, işlemin neler yaptığını daha ayrıntılı bir biçimde standart çıktıya basan <code>v</code> opsiyonu da kimi zaman yardımcı olabilir. Son olarak, girdi sağlanmasını gerektiren birden çok opsiyonun kullanılması durumunda, arşivcinin opsiyon sırası ile girdi sırasının aynı olduğunu varsaydığı unutulmamalıdır.<sup><a href="#dn1" id="ref1">1</a></sup><br />
<br />
<table align="center" border="1"><caption><i>Arşiv dosyası işlemleri</i></caption> <thead>
<tr><th>Opsiyon</th><th><b>Anlamı</b></th></tr>
</thead> <tbody>
<tr><td>c</td><td>Oluşturma</td></tr>
<tr><td>u</td><td>Güncelleme</td></tr>
<tr><td>t</td><td>İçerik listeleme</td></tr>
<tr><td>x</td><td>Açma</td></tr>
</tbody></table><br />
Buna göre, aşağıdaki komut o anki çalışma dizininin içindeki a.class ve b.class dosyalarının yanısıra images dizininin içindeki dosyalardan oluşan ArşivDosyası.jar adlı bir arşiv yaratacaktır.<sup><a href="#dn2" id="ref2">2</a></sup> Dizin içindeki dosyaların ele alınması sırasında, altdizinlerin özyinelemeli bir biçimde işlendiği unutulmamalıdır. Yani, komutumuz images içindeki tüm dosyaları, altdizinler içindekiler de dahil olmak üzere, arşive katacaktır. Arşive nelerin girdiğini görmek isterseniz, <code>v</code> opsiyonu işinizi görecektir.<br />
<pre class="brush:bash;gutter:false" name="arşivYaratma">$ # Arşiv oluşturma
$ jar cf ArşivDosyası.jar a.class b.class images</pre>İşlemin daha az zahmetli olmasını sağlamak için, dosya adı sağlama sırasında joker karakterinden yararlanılabilir. Örneğin, çalışma dizini içindeki sınıf dosyalarının a.class ve b.class'a sınırlı olması durumunda aşağıdaki komut yukarıdaki ile aynı işlevi görecektir.<br />
<pre class="brush:bash;gutter:false" name="arşivYaratma2">$ jar cf ArşivDosyası.jar *.class images</pre>Arşivin oluşturulması sırasında, girdi dosyalarının dizin yapısı korunur. Örneğin, yukarıdaki komutların icra edilmesi sonrasında a.class ve b.class arşivin kök dizininde yer alırken, görüntü dosyaları images altdizini içine konulmuş olacaktır. Bu davranışın değiştirilerek görüntü dosyalarının da sınıf dosyaları ile aynı dizine konulması istenecek olursa, <code>-C</code> opsiyonunun kullanılması gerekecektir. Bu opsiyon, geçici olarak kendisinden sonra sağlanan altdizine geçer ve dosya adını arşivleme sanki geçici altdizinde yapılıyormuş gibi arşive ekler. Buna göre, aşağıdaki komut o anki çalışma dizininde bulunan sınıf dosyaları ile images altdizinindeki tüm dosyaları arşivin kök dizinine yerleştirir.<br />
<pre class="brush:bash;gutter:false" name="arşivYaratma3">$ jar cf ArşivDosyası.jar *.class -C images *</pre>Kimi zaman, sıfırdan oluşturmak yerine ufak bir ekleme veya değişiklik yaparak var olan bir arşivi güncellemek isteyebiliriz. Oluşturmaya göre daha ucuz olan bu işlem <code>u</code> opsiyonu ile gerçekleştirilebilir. Arşiv oluşturma ile aynı argümanlara sahip bu işlem, kendisine geçirilen dosyanın arşivde var olması durumunda yeni dosyayı arşivdekinin üzerine yazar. Buna göre, aşağıdaki örnek kullanım ArşivDosyası.jar dosyasına c.class dosyasını eklerken, arşivde hali hazırda var olan a.class dosyasını yeni haliyle güncelleyecektir.<br />
<pre class="brush:bash;gutter:false" name="arşivGüncelleme">$ # Arşiv güncelleme
$ jar uf ArşivDosyası.jar a.class c.class</pre>Daha önceden oluşturulmuş bir arşivin içeriği <code>t</code> opsiyonu ile listelenebilir. Standart çıktıya basılan listede arşivdeki dosya ve altdizinlerin kök dizine göre adları yer alır. Altdizinlerin <code>/</code> ile sonlandırıldığı bu listelemenin daha ayrıntılı yapılabilmesi için <code>v</code> opsiyonu kullanılabilir.<br />
<pre class="brush:bash;gutter:false" name="arşivListeleme">$ #Arşiv içeriği listeleme
$ jar tf bsh-2.0b4.jar
META-INF/
META-INF/MANIFEST.MF
bsh/
bsh/BSHAllocationExpressin.class
bsh/BSHAmbiguousName.class
...
</pre>Bir arşivin açılması arşivciye <code>x</code> opsiyonunun sağlanmasıyla mümkün olur. Arşiv içindeki dosya sıradüzenini disk üzerinde aynen oluşturan bu işlemin hali hazırda var olan dosyaların üzerine yazacağı unutulmamalıdır.<br />
<pre class="brush:bash;gutter:false" name="arşivAçma">$ # Arşiv açma
$ jar xf bsh-2.0b4.jar</pre><br />
<h3 id="manifesto">Arşiv Manifestosu</h3><br />
İçerik listeleme örneği farklı arşivler için denenecek olursa, META-INF dizini içindeki MANIFEST.MF dosyasının her zaman çıktıda yer aldığı görülecektir. Sakın, bunun arşiv yaratıcılarının sizi paranoyaya sürüklemek amacıyla sözleşerek uyguladığı bir komplo olduğunu düşünmeyin; nasıl ki, gemiler taşıdıkları kargonun ayrıntısını içeren bir manifesto bulundururlar, arşiv dosyaları da söz konusu arşivin sahip olduğu özellikleri belirtmek amacıyla META_INF/MANIFEST.mf dosyası içinde bir manifesto bulundurur.<br />
<br />
Aksine bir yönlendirmede bulunmadığınız takdirde, arşiv oluşturma sırasında sizin için minimal içerikli bir manifesto oluşturulur.<sup><a href="#dn3" id="ref3">3</a></sup> <code>Manifest-Version: 1.0</code> satırından oluşan bu manifestoyu arşiv oluşturma veya güncelleme sırasında sağlanacak <code>m</code> opsiyonu ile isteğinize göre değiştirebilirsiniz. Yapılacak değişikliklerin üzerine yazma yerine kaynaştırarak işini gördüğü akıldan çıkarılmamalıdır. Ne demek istediğimizi bir örnekle görelim. Varsayalım ki, ArşivDosyası.jar işini görebilmek için YardımcıArşiv1.jar ve YardımcıArşiv2.jar arşivlerine ihtiyaç duyuyor. Bu durum, anılan bağımlılık bilgisini içeren bir dışsal manifesto dosyasının MANIFEST.MF'ye yamanması ile karşılanabilir.<br />
<br />
<div style="color: #444444; text-align: center;">EkManifesto.mf</div><pre class="brush:bash;gutter:false" name="code">Class-Path: YardımcıArşiv1.jar YardımcıArşiv2.jar</pre><pre class="brush:bash;gutter:false" name="code">jar umf EkManifesto.mf ArşivDosyası.jar</pre><br />
EkManifesto.mf dosyasının MANIFEST.MF'ye yamandığı yukarıdaki satırın işlenmesi sonrasında MANIFEST.MF dosyası şu içeriğe sahip olacaktır.<br />
<br />
<div style="color: #444444; text-align: center;">MANIFEST.MF</div><pre class="brush:bash;gutter:false" name="code">Manifest-Version: 1.0
Class-Path: YardımcıArşiv1.jar YardımcıArşiv2.jar</pre><br />
<h3>İndeksleme</h3><br />
Dosya ve altdizin sayısının artmasıyla birlikte arşivdeki bir dosyanın bulunup kullanılması daha uzun bir zaman alacaktır. Böyle bir durumda, <code>i</code> opsiyonunun var olan bir arşiv ile birlikte arşivciye geçirilmesi aramayı hızlandıran bir indeks dosyası yaratarak maliyeti azaltabilir.<br />
<pre class="brush:bash;gutter:false" name="arşivİndeksleme">$ # Arşiv indeksleme
$ jar i ArşivDosyası.jar</pre>META-INF altdizini içindeki INDEX.LIST dosyasına yazılan indeksleme bilgisinin, arşivin her güncellenmesi sonrasında yeniden yaratılması gerektiği unutulmamalıdır. Ayrıca, indekslemenin diğer işlemlerden ayrı yapılmasının zorunlu olduğu da akıldan çıkarılmamalıdır.<br />
<br />
<h3>Arşiv Kullanımı</h3><br />
Edindiğiniz arşiv dosyalarından değişik şekillerde yararlanabilirsiniz. Öncelikle, arşiv dosyaları sınıf yolu üzerindeki bir dizine açılmak suretiyle kullanılır hale getirilebilir. Daha zahmetsiz bir diğer yöntem, arşiv dosyasını CLASSPATH ortam değişkenine eklemekten geçer. Örnek olarak, ArşivDosyası.jar'ı kullanan Prog.java adındaki bir dosyanın nasıl derleneceğine bakalım.<br />
<pre class="brush:bash;gutter:false" name="code">javac -cp ".:/home/tevfik/Java Paketleri/ArşivDosyası.jar:$CLASSPATH" Prog.java</pre>Yukarıdaki komuta göre, Prog.java dosyasında ihtiyaç duyulan sınıf dosyaları öncelikle o anki çalışma dizininde, sonra /home/tevfik/Java Paketleri dizininde bulunan ArşivDosyası.jar arşivinde ve nihayet CLASSPATH ortam değişkenince işaret edilen yerlerde aranacaktır. Bu noktada, CLASSPATH ortam değişkeninin içerdiği arşiv dosyası atıflarının mutlak konum verilerek yapılması gerekirken, komut satırında yapılacak uygulamaya özel eklemelerin göreceli olarak da verilebileceği akılda tutulmalıdır.<br />
<br />
Arşiv dosyaları, edimli kütüklere benzer şekilde bir komut gibi de kullanılabilir. Bunun için, öncelikle arşiv manifestosuna giriş noktasını içeren çalıştırılabilir sınıfın hangisi olduğunu gösteren <code>Main-Class</code> anahtarı ve ilişkin değerin eklenmesi gerekir. Yukarıda<a href="#manifesto">▵</a> gösterdiğimiz şekilde manifestoya yapılacak bir değişiklikle sağlanabilecek bu bilgi, arşivciye <code>e</code> opsiyonu ile de bildirilebilir.<br />
<pre class="brush:bash;gutter:false" name="arşivGirişNoktası">$ # Arşiv giriş noktası
$ jar cfe ArşivDosyası.jar AnaSınıf.class</pre>Yukarıdaki komutun işlenmesi sonrasında, arşivci tarafından oluşturulacak minimal manifesto <code>Main-Class: AnaSınıf.class</code> satırını da içerecektir.<br />
<br />
Giriş noktası bilgisine sahip bir arşivin çalıştırılması oldukça basittir; yapılması gereken, JSM'ye <code>-jar</code> opsiyonunun geçirilmesinden ibarettir. Böylesine bir kullanım, JSM'ye argümanda sağlanan dosyanın bir arşiv olduğu ve çalışmanın çalıştırılabilir sınıf adının manifestodaki <code>Main-Class</code>'a dair değerden öğrenilip bulunması sonrasında başlayacağı anlamına gelir. <br />
<pre class="brush:bash;gutter:false" name="arşivÇalıştırma">$ # Arşiv çalıştırma
$ java -jar ArşivDosyası.jar</pre></div><div style="text-align: right;"><a alt="Java'da paketler ve sınıf yolu" href="http://ta-java.blogspot.com/2011/07/paketler-ve-snf-yolu.html">Paketler ve Sınıf Yolu</a> </div><br />
<hr /><div id="dipnotlar" style="text-align: justify;"><ol><li id="dn1">Pek fazla kullanılmasa da, arşivciye sağlanan <code>0</code> opsiyonu arşivin sıkıştırma yapılmaksızın oluşturulacağı anlamına gelir. <a href="#ref1">↑</a></li>
<li id="dn2">Unix temelli işletim dizgelerinden alışmış olanlara uyarı: opsiyon öncesinde - yok. <a href="#ref2">↑</a></li>
<li id="dn3">İstenecek olursa, <code>M</code> opsiyonu ile minimal manifestonun oluşturulmasının önüne geçilebilir. <a href="#ref3">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-18506568898627152772011-07-14T17:08:00.007+03:002011-12-14T16:06:41.275+02:00Paketler ve Sınıf Yolu<div id="anaMetin" style="text-align: justify;">Okumakta olduğu üniversitenin kütüphanesinde kitap arayan hevesli bir öğrenciyi düşünün. Bu öğrenci, belli bir düzene göre düzenlenmiş kütüphaneyi bu düzene uygun biçimde dolaşarak arayışını kolaylaştırabilir. Örneğin, kitapların fakülte, dal ve konu sıradüzenine (hiyerarşi) göre gruplanması durumunda, programlama dersine dair bir kitabı arayan öğrenci, arayışını önce mühendislik kitaplarına, sonra mühendislik kitapları içindeki bilgisayar mühendisliği kitaplarına ve nihayet bilgisayar mühendisliği kitapları içindeki programlama kitaplarına kısıtlayacaktır. Son adımda ise, konuya dair kitaplar içinde yapılan aramayı takiben, istenen kitap ya bulunacak ya da bulunamayacaktır. Kütüphanedeki kitap sayısı düşünüldüğünde, bu yöntemin kütüphanedeki tüm kitapları baştan sona sırasal bir biçimde tarayarak aramaktan çok daha verimli olacağı kesindir.<br />
<br />
Java platformundaki türler de (sınıf ve arayüzler) kütüphanedeki kitapları andıran bir sıradüzenine konulur. Aynı konudaki kitapların kütüphanede aynı bölgeye yerleştirilmesinde olduğu gibi, ilişkili türler aynı <i>paket</i> içine konulur. Gerektiği takdirde, birbirine yakın kavramların gruplandığı paketler bir üstpaket içine yerleştirilebilir. Paket içine paketlerin ve/veya türlerin konulması ile oluşturulan bu sıradüzeninin amacı, platformdaki türlerin algılanmasını ve ilişkin sınıf dosyalarının ihtiyaç duyan paydaşlarca bulunmasını kolaylaştırmaktır.<br />
<br />
Paket oluşturmanın bir diğer sebebi, farklı kişiler tarafından hazırlanmış türlerin ad çakışması olmadan kullanılmasını olanaklı kılmaktır. Ne kastettiğimizi, bir sergideki tabloların envanterini tutmak için yazılmış olan (Prog.java dosyasındaki) Java programının resmedildiği aşağıdaki şekil üzerinden görelim.<sup><a href="#dn1" id="ref1">1</a></sup><br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9Ka8w3DuXsTAuM4bWQYGJuiZqKF-9RYQkL2gp9M2YTb4i6_AwOZK8Vn4lqJ9hwg2HNE8MFWKrZBScAG4kfmybhhISqHr3pO6wcfsZbYM95J84cmZm1Y1kke10yWsg-NtNC6DiUXlygypz/s1600/NeedForPackages.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9Ka8w3DuXsTAuM4bWQYGJuiZqKF-9RYQkL2gp9M2YTb4i6_AwOZK8Vn4lqJ9hwg2HNE8MFWKrZBScAG4kfmybhhISqHr3pO6wcfsZbYM95J84cmZm1Y1kke10yWsg-NtNC6DiUXlygypz/s320/NeedForPackages.png" width="320" /></a></div><br />
abc şirketi tarafından hazırlanan ve nesne kabı olarak hizmet veren <code>Tablo</code> sınıfı ile, def şirketince gerçekleştirilen ve sanat eseri olan tablo kavramını soyutlayan aynı adlı sınıftan yararlanılan Prog.java içindeki tür tanımlarının başarılı bir biçimde derlenebilmesi için aynı ada sahip her iki sınıfın birbirinden ayırt edilmesini mümkün kılacak bilginin sağlanması veya sınıf adlarından birisinin ya da her ikisinin de değiştirilmesi gerekir. Kaynak kodun elimizde olmayabileceğini düşünürsek, ad değiştirmenin çoğu zaman pek de uygulanabilir bir seçenek olmadığı görülecektir. Yapılması gereken, her iki şirketin de olası kullanım senaryolarını göz önünde bulundurarak tür tanımlarını diğer şirketlerinki ile çakışma ihtimali olmayan bir paketin içine koymasıdır. Bu bağlamda önerilen yöntem, şirketin İnternet üzerindeki varlığını temsil eden ve diğer şirketlerinki ile çakışması olanaksız olan alan adının kullanılmasıdır. Ek olarak, şirket içindeki yazılım envanterini tutmayı kolaylaştırmak adına, bazı ek bilgiler kullanılabilir. Buna göre, abc şirketinin ürettiği Tablo.java dosyasındaki <code>Tablo</code> sınıfının aşağıdaki gibi yazıldığı düşünülebilir: Türkiye'deki bir ticari şirket olan abc'nin yazılım mimarlarınca belirlenen kurallara bağlı olarak, <code>Tablo</code> sınıfı bakımını kolaylaştırmak adına diğer veri yapıları ile birlikte bulunabileceği <code>vy</code> paketine koyulmakta.<br />
<br />
<div style="color: #444444; text-align: center;">Tablo.java</div><pre class="brush:java;gutter:false" name="packageBildirimi">package tr.com.abc.vy;
...
public class Tablo {
...
} // Tablo sınıfının sonu</pre><br />
Bu noktada, hemen ilk uyarımızı yapalım: dosya içinde tanımlanan türlerin ait olduğu paketi ilan eden <code><b>package</b></code> bildirimi, kaynak kodun yorum satırları dışındaki ilk öğesi olmalıdır. Ayrıca, aynı dosyada sadece bir paket bildirimi bulunabilir.<br />
<br />
Yapılan tanımların paketlere yerleştirilmesi, söz konusu türlerin kullanımı aşamasında bazı şeylere dikkat edilmesini gerektirir. Öncelikle, atıfta bulunurken artık türün yerleştirildiği paket de hesaba katılmalıdır; türün adı kaynak dosyada sağlanan ilişkin tanımdaki ile aynı değildir. Örneğin, <code>tr.com.abc.vy</code> paketindeki <code>Tablo</code> sınıfı, <code>tr.com.abc.vy.Tablo</code> adına sahiptir, <code>Tablo</code> değil. Dolayısıyla, bu sınıfın nesnesi aşağıdaki gibi yaratılmalıdır.<br />
<br />
<pre class="brush:java;gutter:false" name="uzunAd">...
tr.com.abc.vy.Tablo tablo =
new tr.com.abc.vy.Tablo(...);</pre><br />
Yukarıdaki gibi uzun adların kullanılmasının doğuracağı olası yazım hataları iki şekilde azaltılabilir. Öncelikle, programlama dilinden bağımsız bir şekilde kullanılan geliştirme ortamlarındaki ad tamamlama özelliğinden yararlanılabilir. Java programcısı olarak bizi ilgilendiren ikinci seçenek ise, <code><b>package</b></code> bildiriminin kullanıcı tarafındaki tamamlayıcısı olarak görülebilecek <code><b>import</b></code> bildirimidir. Buna göre, <code>Tablo</code> sınıfı aşağıdaki gibi de kullanılabilir.<br />
<br />
<pre class="brush:java;gutter:false" name="kısaAd">...
import tr.com.abc.vy.Tablo;
...
Tablo tablo = new Tablo(...);</pre><br />
<code><b>import</b></code> bildirim(ler)inin <code><b>package</b></code> bildiriminin ardından gelmesi gerektiği unutulmamalıdır. Ayrıca, <code>java.lang</code> paketinin içindeki türlerin özel bir şekilde ele alındığı ve Java derleyicisi tarafından otomatikman program içine getirildiği hatırda tutulmalıdır.<br />
<br />
Program içinde yararlanılan tür sayısının artmasıyla birlikte <code><b>import</b></code> bildiriminin jokerli uyarlamasının kullanımı düşünülebilir. Bu bildirim, söz konusu paket içindeki tüm türlere kısa adlarıyla atıfta bulunmayı olanaklı kılar.<br />
<br />
<pre class="brush:java;gutter:false" name="kısaAd">...
import tr.com.abc.vy.*;
...
Tablo tablo = new Tablo(...);</pre><br />
Bu noktada bir uyarının daha yapılması yerinde olacaktır: bir paketin jokerli <code><b>import</b></code> bildirimi ile görünür kılınması, paketin "içindeki" diğer paketlerin görünürlüğünü etkilemez; tüm paketlerin ayrı ayrı görünür kılınması gerekir. Çünkü, <b>paketler sadece türler için aduzayı görevi görürler, diğer paketler için değil</b>. Mesela, <code>java.util</code> paketinin görünür kılınması <code>java.util.regex</code> paketinin içindeki sınıfları görünür hale getirmeyecektir.<br />
<br />
Değinilmesi gereken bir diğer bildirim, J2SE 5 ile birlikte Java'ya eklenen <code><b>static import</b></code> bildirimidir. Bu bildirim, bir sınıfın—paketin değil!—<code><b>static</b></code> bir öğesinin (veya tüm <code><b>static</b></code> öğelerinin) sınıf adı ile nitelenmeksizin kullanılabilmesini sağlar. Buna göre, aşağıdaki kodda <code>java.lang.System</code> sınıfının tüm <code>static</code> öğeleri sınıf adıyla nitelenmeksizin kullanılabilirken, <code>java.lang.Math</code> sınıfının yalnızca <code>PI</code> adlı özelliği benzer biçimde kullanılabilecektir. <br />
<br />
<pre class="brush:java;gutter:false" name="kısaAd">...
import static java.lang.Math.PI;
import static java.lang.System.*;
...
double alan = PI * Math.pow(yarıçap, 2);
out.println("Alan: " + alan);</pre><br />
Gerçekleştirimci tür tanımını <code><b>package</b></code> bildirimi ile bir pakete koyar, kullanıcı söz konusu türü kısa adı ile kullanmak isteyecek olursa <code><b>import</b></code> bildiriminden yararlanır demekle işimizin bittiğini zannediyorsanız, üzülerek yanıldığınızı söylemeyelim. Kullanıcı, program içinde yararlanılan türlere ait gerçekleştirimleri içeren sınıf dosyalarının nerede aranması gerektiğine dair bilgiyi derleyiciye sağlamalıdır. Kütüphanede bıraktığımız hevesli öğrenciye dönecek olursak, öğrencimizin bir şekilde kütüphanenin bulunduğu yeri bilmesi gerekir. Aslına bakılırsa, öğrencimizin biraz daha azimli davranıp, aranan kitabın bulunmaması halinde aramaya devam edeceği diğer kütüphanelerin adresini öğrenmesinde yarar olacaktır. Buna göre, kitap arama aşağıdaki adımlar izlenerek yapılır diyebiliriz.<br />
<br />
<ol><li>Adres listesinden bir sonraki kütüphanenin adresini bul. Böyle bir adresin olmaması durumunda, boynunu bükerek evine dön.</li>
<li>Adreste belirtilen kütüphanede aramaya başla.</li>
<li>Aramanın başarıyla sona ermesi durumunda kitabı çantaya koy. Aksi takdirde, 1. adıma git.</li>
<li>Kitabın gerekli bölümlerini oku.</li>
</ol><br />
Algoritmamızı Java derleyicisinin<sup><a href="#dn2" id="ref2">2</a></sup> sınıf dosyası arama algoritmasına uyarlayacak olursak...<br />
<br />
<ol><li>Sınıf yolu listesinden bir sonraki sınıf yolu dizininin konumunu bul. Böyle bir konumun olmaması durumunda, kullanılmak istenen türe dair tanımın bulunmadığını ilan et ve derlemeyi başarısızlıkla bitir.</li>
<li>Sınıf dosyasını sınıf yolu dizininden başlayarak ara. Bunun için, türün uzun adındaki '.' karakterlerini yol ayırıcı karakteri—Unix temelli sistemlerde '/', MS Windows temelli sistemlerde '\'—ile değiştir. Buna göre, <code>tr.com.abc.vy.Tablo</code> sınıfı sınıf yolu dizininin içindeki tr/com/abc/vy altdizininde aranacaktır.</li>
<li>Aramanın başarıyla sona ermesi durumunda sınıf dosyasını belleğe yükle.. Aksi takdirde—yani, Tablo.class dosyasının bulunmaması durumunda— 1. adıma git.</li>
<li>Türün kullanımı noktasında, kullanımın doğruluğunun denetimi için belleğe yüklenen dosyadaki bilgilere başvur.</li>
</ol><br />
Benzetmeyi tamamlamak için yapmamız gereken, sınıf yolu listesini Java derleyicisine aktarmak. Bu, sınıf dosyalarının aranmaya başlanacağı dizinlerin konumlarını tutan CLASSPATH ortam değişkenine PATH ortam değişkenine benzer bir şekilde değer sağlanması yoluyla olur. Bir diğer yöntem, Java derleyicisine <code>-cp</code> opsiyonu ile uygulama bazında bu bilginin verilmesini içerir. Aşağıda her iki yöntemin birlikte kullanımına dair bir örnek verilmektedir.<sup><a href="#dn3" id="ref3">3</a></sup><br />
<br />
<pre class="brush:bash;gutter:false" name="classpath">javac -cp ".:/Java Paketleri/sergi:$CLASSPATH" Prog.java</pre><br />
Prog.java dosyasındaki Java programlama dili tanım(lar)ını derleyen bu komut, kullanım doğruluğunu denetlemek için ihtiyaç duyulan türlerin karşılığındaki sınıf dosyalarını önce derlenmekte olan kaynak dosya ile aynı dizinde, sonra /Java Paketleri/sergi ve nihayetinde CLASSPATH ortam değişkeni ile belirtilen dizinler içinde arayacaktır. Arama sırasında sınıf dosyasından daha sonra güncellenmiş aynı adlı bir .java dosyasına rastlanması durumunda, arama başarıyla bitmiş kabul edilecek, derleyici kaynak dosyayı derleyerek denetimlerinde bu derlenmiş dosyadan yararlanacaktır.<br />
<br />
Son olarak, bu günceyi daha önce ziyaret edip de kod örneklerine göz atmış olanlardan bazılarınızın, <code><b>package</b></code> bildirimi olmaksızın yazılan türlere ne olduğunu sorduklarını duyar gibiyim. Bu haklı sorunun yanıtını vererek yazımızı bitirelim: söz konusu türler, <i>varsayılan paket</i> dediğimiz ve kaynak kodun bulunduğu dizinle ilişkilendirilen paketin içinde konuluyordu.</div><br />
<div style="text-align: right;"><a alt="Java arşiv dosyaları" href="http://ta-java.blogspot.com/2011/08/arsiv-dosyalar.html">Arşiv Dosyaları</a> </div><br />
<hr /><div id="footnote" style="text-align: justify;"><ol><li id="dn1"> Şekli bir yerden hatırladıklarını düşünenler belleklerinin oynadığı bir oyuna kurban edilmiyorlar; benzer bir şekil, Java adıyla birlikte anılan kavram ve kısaltmalara değindiğimiz yazıda<a alt="Java platformunun belli başlı bileşenleri" href="http://ta-java.blogspot.com/2011/02/java-ve-jli-harf-corbas.html">🔎</a> da vardı. Dolayısıyla, bu yazıda değindiğimiz kavramların diğer JSM dillerinden yararlanılarak üretilmiş sınıf dosyaları için de geçerli olduğu unutulmamalı. <a href="#ref1">↑</a></li>
<li id="dn2"> Sınıf dosyası arama Java derleyicisine sınırlı bir işlem değil. Sınıf dosyaları ile işi olan tüm sistem yazılımlarının bu algoritmayı kullanması söz konusu olacaktır ki, buna en güzel örnek sınıf dosyalarını işleten JSM'dir. <a href="#ref2">↑</a></li>
<li id="dn3"> MS Windows temelli işletim dizgelerinde çalışanların ':' yerine ';', $CLASSPATH yerine %CLASSPATH% kullanmaları gerekecektir. <a href="#ref3">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-25791479062115748492011-06-03T00:48:00.003+03:002011-12-14T16:09:21.414+02:00Sınama Aracı Olarak BeanShell<div id="anaMetin" style="text-align: justify;">Herhangi bir mühendislik ürününün en önemli özelliği, ister oturduğunuz ev olsun isterse kullanmakta olduğunuz yazılım, işlevini spesifikasyonlarına uygun bir şekilde görmesidir. <i>Doğruluk</i> olarak adlandırılan bu özellik, ürünün inşa edilmesi sırasında yapılan tetkikler ile denetlenebileceği gibi kullanım esnasında ortaya çıkan aykırı davranışların yönlendirmesi sonrasında yapılan bakım çalışmaları ile de sağlanabilir.<sup><a href="#dn1" id="ref1">1</a></sup> İşte bu yazımızda, Java programlama dili kullanılarak yazılan sınıfların nasıl sınanabileceğine dair bir seçenek sunan BeanShell'e göz atacağız. Bir diğer deyişle, Java kaynak kodunun doğruluğunun nasıl temin edilebileceğine değineceğiz.<br />
<br />
<ul><li><a href="#temelBeanShell">Temel BeanShell</a></li>
<li><a href="#BeanShellDosyaları">BeanShell kaynak dosyaları</a></li>
<li><a href="#nesneler">Kod örtüsü ve nesneler</a></li>
<li><a href="#kodSınama">BeanShell ile birim sınama</a></li>
</ul><br />
<h3 id="temelBeanShell">Temel BeanShell</h3>BeanShell yorumlayıcısı, gerçekleştirimi barındıran arşiv dosyasının <a href="http://beanshell.org/">http://beanshell.org/</a> adresindeki <i>Download</i> sekmesinden indirilip sınıf yolu üzerindeki bir yere konulmasından<sup><a href="#dn2" id="ref2">2</a></sup> sonra ilişkin sınıfın JSM'ye yüklenmesi ile başlatılır. Kimi ortamlarda, komut satırından <i>bsh</i> girilmesi de yeterli olacaktır.<sup><a href="#dn3" id="ref3">3</a></sup> Yorumlayıcıdan çıkış ise BeanShell komut kabuğunda girilecek <code>exit</code> ile sağlanabilir.<br />
<br />
Aşağıdaki kod parçasından da görülebileceği gibi, dinamik türlemeli olan BeanShell'de bir tanımlayıcının ait olduğu türün belirtilmesi gerekmez; aynı tanımlayıcının farklı zamanlarda farklı türlerden değerlere sahip olması da mümkündür. Ancak bu, söz konusu tanımlayıcının kısıtsız bir biçimde kullanılabileceği anlamına gelmez; tanımlayıcıya atfın yapıldığı andaki türle uyumsuz bir kullanım hataya neden olacaktır.<br />
<pre class="brush:bash;gutter:false" name="BeanShelliBaşlatma">$ # Bazı ortamlarda bsh komutu da aynı görevi görür.
$ java bsh.Interpreter</pre><pre class="brush:java;gutter:false" name="BeanShelliBaşlatma">BeanShell 2.0b4 - by Pat Niemeyer (pat@pat.net)
bsh % // Tanımlayıcı türlerinin ilan edilmesi gerekmez.
ad = 2;
bsh % ad = ad + ".Mehmet";
bsh % print("Ad: " + ad);
Ad: 2.Mehmet
bsh % exit();
$
</pre>Standart çıktıya değer göndermek için <code>print</code>'e ek olarak <code>show</code> komutu da kullanılabilir. Her kullanımında bir bayrak değişkeninin o anki durumunu değilleyerek aç-kapa düğmesi gibi işini gören bu komut, işlem gören deyimlerin sonucunun standart çıktıya basılıp basılmayacağını belirler.<br />
<pre class="brush:java;gutter:false" name="showKomutu">bsh % ad = "Ahmet Adnan";
bsh % soyad = "Saygun";
bsh % ad + " " + soyad;
bsh % /* Sonuçlar basılacak */ show();
bsh % ad + " " + soyad.toUpperCase();
<Ahmet Adnan SAYGUN>
bsh % show();
bsh % ad;
bsh %
</pre>Bilinmesinde yarar bulunan diğer BeanShell komutlarından <code>getClassPath</code>, kullanıldığı andaki sınıf yolu ortam değişkeninin (<code>CLASSPATH</code>) hangi dizin ve arşiv dosyalarını içerdiğini döndürürken, <code>addClassPath</code> sınıf yoluna yeni bir dizin veya arşiv dosyası ekler; <code>setClassPath</code> ise, <code>CLASSPATH</code> değişkenini argümanındaki <code>java.net.URL</code> dizisinin elemanlarına sahip olacak şekilde günceller. <br />
<pre class="brush:java;gutter:false" name="sınıfYoluKomutları">bsh % print(getClassPath());
java.net.URL []: {
file:/home/tevfik/,
...
file:/usr/lib/jvm/java-6-openjdk/jre/lib/rt.jar,
}
bsh % pwd();
/home/tevfik
bsh % addClassPath("a/b/c");
bsh % print(getClassPath());
java.net.URL []: {
file:/home/tevfik/,
...
file:/usr/lib/jvm/java-6-openjdk/jre/lib/rt.jar,
file:/home/tevfik/a/b/c,
}
</pre>Dikkat edilirse, sınıf yoluna eklenen dizin o anki çalışma dizinine eklenerek oluşturulmuş bir mutlak yol ile belirleniyor; <code>pwd</code> ile sorgulanabilen bu yol <code>cd</code> komutu ile değiştirilebilir. Dosya/dizin işlemeye dönük diğer komutlar aşağıda verilmiştir:<br />
<dl><dt>dir</dt>
<dd>- Argümansız kullanıldığında o anki çalışma dizininin içeriğini listeleyen bu komut, karakter katarı argümanla çağrıldığında listelemeyi söz konusu konumun göreceli olduğunu kabul ederek yapar.</dd>
<dt>cat</dt>
<dd>- Argümanındaki metin dosyasının içeriğini basar.</dd>
<dt>cp</dt>
<dd>- İlk argümanındaki dosya veya dizini ikinci argümanındaki konuma kopyalar.</dd>
<dt>mv</dt>
<dd>- İlk argümanındaki dosya veya dizini ikinci argümanındaki konuma taşır.</dd>
<dt>rm</dt>
<dd>- Argümanındaki dosya veya dizini siler.</dd> </dl>Yukarıda verilen Unix temelli işletim dizgelerindeki adaşları ile aynı işlevli komutlara ek olarak, <code>exec</code> komutunu kullanarak herhangi bir komutu BeanShell komut satırından çalıştırabiliriz. Örneğin, aşağıdaki komut çalışma dizininin ayrıntılı bir listesini basacaktır.<br />
<pre class="brush:java;gutter:false" name="komutİşletme">bsh % exec("ls -la");</pre>Dışsal bir uygulamanın çalıştırılması için kullanılan <code>exec</code>'e benzer bir komut, Lisp temelli dillerden birini bilenlere tanıdık gelecek olan <code>eval</code>'dir. Bu komut, argümanında geçirilen karakter katarını BeanShell komutları olarak algılar ve sanki komut kabuğundan girilmişçesine yorumlar. Dolayısıyla, yorumlama esnasında yaratılan olası yan etkiler komut sonrasında da geçerli olacaktır.<br />
<pre class="brush:java;gutter:false" name="evalÖrneği">bsh % a = 5;
bsh % b = 10;
bsh % eval("c = a * b;");
bsh % print(c);
50</pre><br />
<h3 id="BeanShellDosyaları">BeanShell Kaynak Dosyaları</h3>Diğer komut kabuğu dillerinde olduğu gibi, BeanShell de komutların toptan okunup işlenmesi için araçlar sunar. Bunun için kullanılabilecek komutlardan <code>source</code>, Bash'in . komutuna benzer bir şekilde, argümanında verilen ada sahip dosyayı işleyerek yorumlayıcının aduzayını günceller. Örneğin, aşağıda verilen içeriğe sahip Kapasite.bsh dosyasının içselleştirilmesi sonrasında yorumlayıcının aduzayı söz konusu dosyada tanımlanmış olan <code>kapasiteTest</code> metodu ile zenginleştirilmiş olacaktır.<br />
<br />
<div style="color: #444444; text-align: center;">Kapasite.bsh</div><pre class="brush:java;gutter:false" name="BeanShellKaynakDosyaları">kapasiteTest(ilkKap, sonKap) {
vec = new Vector(ilkKap);
kap = vec.capacity();
print("İlk kapasite: " + kap);
for (i = 1; i < sonKap; i++) {
vec.add(i);
if (vec.capacity() != kap) {
kap = vec.capacity();
print("Yeni kapasite: " + kap);
}
}
} // kapasiteTest(ilkKap, sonKap) sonu
</pre><pre class="brush:java;gutter:false" name="BeanShellKaynakDosyaları">bsh % source("Kapasite.bsh");
bsh % kapasiteTest(0, 10);
İlk kapasite: 0
Yeni kapasite: 1
Yeni kapasite: 2
Yeni kapasite: 4
Yeni kapasite: 8
Yeni kapasite: 16
bsh %
</pre>Benzer bir etkiye sahip olan <code>run</code>, içselleştirdiği dosyanın içindeki tanımları komutla birlikte yaratılan yeni bir yorumlayıcı aduzayı içine yerleştirir. Yani, <code>run</code> komutunun işlemesi öncesi ve sonrasındaki aduzayları arasında fark olmayacaktır.<br />
<br />
<h3 id="nesneler">Kod Örtüsü ve Nesneler</h3>Fonksiyonel programlama dillerine aşina olanlar bilir, nesne <i>kod örtüsü</i>nden (İng., closure) evrilmiş bir kavramdır. Özetleyecek olursak, bir ya da birden çok fonksiyonda serbest tanımlayıcı olarak var olan ve bu fonksiyonlar tarafından "yakalanmak" suretiyle örtülen verilerin bileşkesine nesne denir. Söz konusu nesne, fonksiyonların çağrılması sırasında varsayılan argüman olarak manipüle edilir.<br />
<br />
BeanShell, önceki paragrafta ne kastettiğimizi açmak için güzel bir araç sunar: içiçe metotlar. Gelin bunu aşağıdaki sayaç nesnesi gerçekleştiriminden anlamaya çalışalım.<br />
<br />
<div style="color: #444444; text-align: center;">Sayaç.bsh</div><pre class="brush:java;gutter:false" name="nesneÖrneği">yeniSayaç(ilkDeğer) {
print("Hoşgeldiniz...");
s = ilkDeğer;
sayaç() { return s; }
artır() { s = s + 1; }
print("Gene bekleriz.");
return this;
} // yeniSayaç(ilkDeğer) sonu
</pre>Sayaç nesneleri için yapıcı metot görevini gören <code>yeniSayaç</code>, standart çıktıya bilgilendirme mesajları basmaya ek olarak, gövdesinde tanımlanan her iki metotta da serbest değişken olarak geçen <code>s</code>'yi yapıcının çağrıldığı noktada geçirilen argümanın değerine sahip yerel değişken ile bağlamaktadır. Nesne paradigmasından alıştığımız biçimde ifade edecek olursak, yaratılacak sayaç nesnelerinin ilk değeri yapıcıya geçirilen değer olan <code>s</code> adlı bir alt alanı olacak ve bu nesneler <code>artır</code> ve <code>sayaç</code> adlı metotlar ile işlenecektir. Ancak, altalanın dışarıdan müdaheleye karşı korunmadığı dikkatli olanlarınızın gözünden kaçmamıştır.<sup><a href="#dn4" id="ref4">4</a></sup><br />
<pre class="brush:java;gutter:false" name="nesneKullanımı">bsh % m = yeniSayaç(0);
Hoşgeldiniz...
Gene bekleriz.
bsh % m.artır();
bsh % m.artır();
bsh % n = yeniSayaç(10);
Hoşgeldiniz...
Gene bekleriz.
bsh % print(n.sayaç());
2
bsh % print(m.s);
10
</pre><br />
<h3 id="kodSınama">BeanShell İle Birim Sınama</h3>Bir betik dili olan BeanShell'in yorumlayıcısı, BeanShell'in yanısıra Java programlama dili sözdiziminin büyük kısmını desteklemesi nedeniyle, Java'nın komut kabuğu olarak da görülebilir. Yani, Java komutlarını BeanShell yorumlayıcısının komut satırından etkileşimli bir biçimde girebiliriz. Girilen komutun sonucuna göre bir sonraki komutu seçebileceğimiz ve ortaya çıkacak olası hataları anında görebileceğimiz anlamına gelen bu özellik, BeanShell yorumlayıcısının aynı zamanda Java için verimli bir sınama ortamı oluşturması demektir.<br />
<br />
BeanShell'den Java kodu sınamak amacıyla yararlanmak için yorumlayıcı ortamını Java'nın kurallarına daha sıkı uyulan sıkı denetleme kipine geçirmemiz gerekir. Bu, <code>setStrictJava</code> komutunun <code>true</code> argümanı geçirilerek kullanılmasıyla sağlanır.<br />
<pre class="brush:bash;gutter:false" name="sıkıDenetimKipi">bsh % ad = "Ahmet Adnan";
bsh % setStrictJava(true);
bsh % soyad = "Saygun";
// Error: EvalError: (Strict Java mode) Assignment to undeclared variable: soyad
: at line: 3 : in file: <unknown file> : soyad = "Saygun"
bsh % String soyad = "Saygun";</pre>Sıkı denetim kipine geçildikten sonra yapılması gereken şey, birim sınamasını yapmak istediğimiz sınıfı <code><b>import</b></code> bildirimi ile görünür hale getirmek ve gerek <code>source</code> (ve <code>run</code>) ile okuyarak gerekse komut satırından girerek sağlanan Java kodu ile sınıfımızın işlevselliğini sınamaktır. Bu noktada, BeanShell yorumlayıcısının JSM'ye yüklenmesi sırasında <code>java.lang</code> paketinin yanısıra <code>java.awt</code>, <code>java.awt.net</code>, <code>java.io</code>, <code>java.net</code>, <code>java.util</code>, <code>javax.swing</code>, <code>javax.swing.event</code> paketlerinin de otomatikman görünür hale getirildiği bilinmelidir. Dolayısıyla, listelenen paketlerin <code><b>import</b></code> ile ithal edilmesine gerek yoktur. <br />
<pre class="brush:bash;gutter:false" name="birimSınama">bsh % import a.b.c.XYZ;
bsh % XYZ x = new XYZ(), y = new XYZ(10);
bsh % // XYZ nesnelerinin davranışını sına.
bsh % ...
bsh % reloadClasses();
bsh % // Sınamaya yeniden başla.
bsh % ...</pre>Sınanmakta olan sınıfta hata saptanması durumunda ilişkin kod parçası gözden geçirilmeli ve değişiklik yapıldıktan sonra sınıfımızın yeni halinin BeanShell yorumlayıcısına görünür kılınması gereklidir ki, bu <code>reloadClasses</code> komutu ile sağlanır. Argümansız kullanıldığında sınıf yolu üzerindeki sınıflar arasından değişmiş olanların tümünü yeniden JSM'ya yükleyen bu komut, istenecek olursa bir paketteki tüm sınıflara veya tek bir sınıfa yönelik olarak da icra edilebilir. Ancak, arşiv dosyaları içindeki sınıfların yeniden yüklenemeyeceği bilinmelidir.<br />
<br />
BeanShell yorumlayıcısına sağlanan Java kodunda kimi kısıtlamaların olduğu unutulmamalıdır. Bunlardan en önemlisi, soysal türlerin tür argüman(lar)ı olmaksızın kullanılması zorunluluğudur. Başta büyük bir gaf gibi gözükebilecek bu eksiklik Java kodunun BeanShell tarafında nasıl ele alındığı öğrenildiğinde mazur görülecektir: yorumlayıcıya görünür kılınan Java sınıfları, kaynak kod düzeyinde ele alınmaz, derleme sonucunda oluşturulan sınıf dosyalarının yüklenmesi yoluyla kullanılır. Bu da, derleme sırasında yer alan <i>tür silme</i> işlemi nedeniyle soysallık bilgilerinin sınıf dosyasına yansıtılmayacağı ve dolayısıyla BeanShell yorumlayıcısının dikkatine sunulan dosyalarda soysallık kavramının aslında hiç olmayacağı anlamına gelir.<br />
<br />
Birim sınama bağlamında değineceğimiz son nokta, muhtemelen, tümleşik geliştirme ortamlarına alışık arkadaşların haklı bir beklentisini karşılayacaktır: BeanShell'in tipik kullanımı, yazımızda önerildiği gibi komut satırından çalıştırılan yorumlayıcıya başka bir ortamda geliştirilerek elde edilen sınıf dosyalarının sağlanması ve bu sınıfların sınanması değildir. Normalde, Eclipse, Emacs veya jEdit gibi geliştirme ortamlarında BeanShell yorumlayıcısı, Java düzenleyici penceresine özel bir konsol penceresi olarak eklemlenir ve programcı eşgüdümlü bir biçimde yönetilen iki pencere arasında gidip gelerek sınama işini hızlı bir şekilde yapar.<br />
</div> <br />
<hr /><div id="footnote" style="text-align: justify;"><ol><li id="dn1">İnşa edilen ürünün yazılım olması durumunda, yapılan tetkike <i>sınama</i> bakım çalışmalarına ise <i>hatadan arındırma</i> denilir. <a href="#ref1">↑</a></li>
<li id="dn2">Bu işlem, indirilen arşiv dosyasının diskte uygun bir konuma yerleştirilmesi sonrasında mutlak konum adı kullanılarak <code>CLASSPATH</code> ortam değişkenine eklenmesi kadar basittir. <a href="#ref2">↑</a></li>
<li id="dn3">Aslında seçeneklerimiz bunlarla sınırlı değil. Görsel arayüz sevdalıları, <code>java bsh.Console</code> ve <code>xbsh</code> komutlarından biri ile emellerine ulaşabilirler. <a href="#ref3">↑</a></li>
<li id="dn4">Verdiğimiz örnek, tüm altalanların tüm metotlarca kullanılacağını düşündürmesin; kimi metotlar kimi altalanları kullanmadan da işini görebilir. <a href="#ref4">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-65912251909306709492011-05-24T11:12:00.002+03:002012-01-27T01:21:09.835+02:00Her Sınıfa Lazım Metotlar<div id="anaMetin" style="text-align:justify">Sınıflarınızı <i>Bileşke Türler-Sınıflar</i> adlı yazıda<a alt="Java'da sınıf tanımı" href="http://ta-java.blogspot.com/2011/05/bileske-turler-snflar.html">🔎</a> önerilen veya diğer kaynaklarda okuduğunuz reçetelere uyarak gerçekleştirdikten sonra bile, programlarınızın sıklıkla beklentileriniz dışında davranış sergilediğini gözlemleyebilirsiniz. Bu hayal kırıklığı kimi zaman yaptığınız bir dikkatsizlikten, kimi zamansa öngöremediğiniz bir işlevselliğin yokluğundan veya eksik gerçekleştirilmiş olmasından kaynaklanır. İşte bu yazıda, tüm sınıflarda özel olarak ele alınıp gerçekleştirilmeleri konusunda karar verilmesi gereken bazı özel metotlara değineceğiz ve Kuantum Tanrısı'nın JSM'nin içine kötü niyetli cinler yerleştirdiği kuruntusundan kurtulmaya çalışacağız.<br />
<br />
<ul><li><a href="#hoşYazım">Hoş yazım</a></li>
<li><a href="#eşitlikDenetimi">Eşitlik denetimi</a></li>
<li><a href="#kopyalayanYapıcı">Kopyalama</a></li>
<li><a href="#varsyılanYapıcı">Varsayılan yapıcı</a></li>
<li><a href="#karşılaştırma">Karşılaştırma</a></li>
</ul><br />
<h3 id="hoşYazım">Hoş Yazım</h3><br/>
Hatadan arındırma amacıyla başvurduğunuz çıktı komutlarınızın neden anlaşılmaz şeyler yazdığını açıklamakla başlayalım. Aşağıda verilen [oldukça yetersiz] <code>Öğrenci</code> sınıfını kullanarak ne yapmamız gerektiğine bir bakalım.<br />
<br />
<div style="color: #444444; text-align: center;">Öğrenci.java</div><pre class="brush:java;gutter:false;highlight:7" name="toStringinGerekliliği">import static java.lang.System.*;
public class Öğrenci {
public static void main(String[] ksa) {
Öğrenci öğr1 = new Öğrenci(123, "Ayşe", "Öztürk", 3.5F);
out.println(öğr1);
// metodun devamı...
} // void main(String[]) sonu
public Öğrenci(long no, String ad, String soyad, float ort) {
_ad = ad;
_no = no;
_ortalama = ort;
_soyad = soyad;
} // yapıcı(long, String, String, float) sonu
public String ad() { return _ad; }
public long no() { return _no; }
public float ortalama() { return _ortalama; }
public String soyad() { return _soyad; }
private String _ad, _soyad;
private long _no;
private float _ortalama;
} // Öğrenci sınıfının sonu
</pre><pre class="brush:bash;gutter:false">$ javac -encoding UTF-8 Öğrenci.java
$ java Öğrenci
Öğrenci@6e1408
...
</pre>İşaretli satır tarafından basılanın bir <code>Öğrenci</code> nesnesi olmakla birlikte, haklı olarak, çıktıda nesne içeriğine dair bilgilendirici bir şey olmadığını düşünebilirsiniz. <code>println</code> (ve <code>print</code>) metotlarının işleyişine bakarak bunun neden tam olarak doğruyu yansıtmadığını anlamaya çalışalım. Söz konusu metotlar herhangi bir türden—ister ilkel tür olsun isterse bileşke tür—argüman alacak şekilde aşırı yüklenmiştir. Bu metotlardan <code><b>char</b></code>, <code><b>char</b>[]</code> ve <code>String</code> argüman bekleyenlerin dışındakiler, aldıkları argümanı önce bir <code>String</code> nesnesi haline dönüştürür, ardından bu katar içindeki karakterleri basarak işini görür. Bileşke türlü değerlerden karakter katarına dönüşüm, aşağıdaki denkliklerden de görülebileceği gibi, önce <code>String</code> sınıfındaki <code>valueOf</code> metodunu çağırarak daha sonra ise bu metottan dönüşümü yapılmak istenen nesneye <code>toString</code> iletisi gönderilerek yapılır.<br />
<pre class="brush:java;gutter:false"name="toStringinAçılımı">out.println(2 + öğr1);
</pre><center>≡</center><pre class="brush:java;gutter:false"name="toStringinAçılımı">out.println(String.valueOf(2) + String.valueOf(öğr1));
</pre><center>≡</center><pre class="brush:java;gutter:false"name="toStringinAçılımı">out.println(String.valueOf(2) + öğr1.toString()));
</pre>Dolayısıyla, sorumlunun <code>öğr1</code>'e <code>toString</code> iletisinin gönderilmesi sonucunda çağrılan aynı adlı metot olduğunu söyleyebiliriz. Ancak, <code>öğr1</code>'in üyesi olduğu <code>Öğrenci</code> sınıfına bakıldığında bu ada sahip bir metodun bulunmadığı görülür. O zaman, olmayan bir metodun çağrılmasıyla hata verilmesi gerekirken neden yukarıdaki çıktı üretilmektedir? Yanıt, söz konusu metodun bir diğer sınıftan kalıtlanmış olmasında yatar. Hoş yazım gibi tüm sınıflarda bulunması beklenen bir işlevsellik çok genel bir şekilde tüm sınıfların ortak atası olan <code>Object</code> sınıfındaki <code>toString</code> metodunun gerçekleştirimi ile sağlanmış ve <code>Öğrenci</code> sınıfının kendi özelliklerini yansıtan bir gerçekleştirim sağlamaması sonrasında derleyici tarafından bu metot kullanılmıştır. Tüm sınıfların ortak paydasından yararlanarak içi doldurulan bu metot ise nesnenin ait olduğu sınıfın adı, '@' ve nesnenin kıyım değerini birleştirerek işini görmektedir.<sup><a href="#dn1" id="ref1">1</a></sup> Nesnelerimizin çıktı ortamına basıldığında sonucun anlaşılır olmasını istiyorsak, <code>toString</code> adlı bu metodu nesnelerimizin özelliklerini düşünerek gerçekleştirmemiz gerekir. <code>Öğrenci</code> sınıfı için uygun bir gerçekleştirim aşağıda verilmiştir.<br />
<pre class="brush:java;gutter:false" name="toStringÖrneği">...
public class Öğrenci {
...
public String toString() {
return _no + " " + _ad " " +
_soyad + " " + _ortalama;
} // String toString() sonu
...
} // Öğrenci sınıfının sonu
</pre><br />
<h3 id="eşitlikDenetimi">Eşitlik Denetimi</h3><br/>
Hangi sınıfa ait olursa olsun tüm nesnelere gönderilmesi söz konusu olabilecek bir diğer ileti, hedef nesneye kendisini aynı türden bir diğer nesne ile eşit olup olmadığını denetlemesini söyleyen <code>equals</code> iletisidir. Başta işin, ilkel türlü değerlerde olduğu gibi, <code>==</code> ile halledilebileceğini, aslında <code>equals</code> iletisine ihtiyaç bile duyulmaması gerektiğini düşünenleriniz olabilir. Bu arkadaşlara, diziler konusunu işlediğimiz yazının<a alt="Java'da diziler" href="http://ta-java.blogspot.com/2011/04/bileske-turler-diziler.html">🔎</a> aşağıya kopyaladığımız örneğiyle bunun olanaksız olduğunu hatırlatalım. Çünkü <code>==</code> tutacakların eşitliğini denetler. Bu da, eşitlik denetiminin <code><b>true</b></code> sonuç üretmesinin ancak aynı nesneyi gösteren iki tutacakla mümkün olacağı anlamına gelir. Bir diğer deyişle, <code>==</code> nesnelerin aynılık denetimini yapar, eşitlik denetimini değil.<br />
<pre class="brush:java; gutter:false" name="tutacakveNesne">String ad1 = new String("Tevfik");
String ad2 = new String("Tevfik");
System.out.println(ad1 == ad2); // => false
</pre>O zaman, <code>Object</code> sınıfında sağlanan <code>equals</code> metodu da eşitlik denetimini yapsaymış keşke diyebilirsiniz. Biraz düşündüğünüzde, farklı yapıya sahip belirsiz sayıda sınıfta eşitlik kavramının birbirlerinden çok farklı olacağını anlarsınız ve bunun da <code>toString</code>'de olduğu gibi sınıf gerçekleştirimcisinin müdahelesini gerektirdiğini görürsünüz. Zira, <code>equals</code> iletisinin tüm sınıflar için ortak payda olarak sunulan <code>Object</code> sınıfındaki metot gerçekleştirimi, <code>==</code> gibi davranır ve aynılık denetimi yapar. Yüksek performanslı bu çözümün isteklerinizi karşılamaması durumunda, aşağıdaki gibi amaçlarınıza uygun bir metodu sağlamanız yerinde olacaktır.<br />
<pre class="brush:java;gutter:false" name="equalsÖrneği">...
public class Öğrenci {
...
public boolean equals(Object diğerNesne) {
Öğrenci diğerÖğr = (Öğrenci) diğerNesne;
return _no == diğerÖğr._no;
} // boolean equals(Object) sonu
...
} // Öğrenci sınıfının sonu
</pre>
Dikkat edecek olursanız, metot gövdesinin ilk satırında parametrede geçirilen <code>Object</code> türlü tutacağı biçimlendirmek suretiyle arkadaki nesneyi bir <code>Öğrenci</code> nesnesi gibi kullanmak isteğimizi bildiriyoruz.<sup><a href="#dn2" id="ref2">2</a></sup> Böylece, biçimlendirmenin sonucu olarak döndürülen <code>Öğrenci</code> türlü tutacak değerini atadığımız <code>diğerÖğr</code> adlı yerel değişkeni, kaplamının (İng., scope) sonuna kadar <code>Öğrenci</code> sınıfının özelliklerini yansıtacak şekilde kullanma hakkını kazanıyoruz. Bunun sebebi, <code>Object</code> sınıfının tüm Java sınıflarının ortak özelliklerini tutuyor olması ve <code>_no</code> altalanının bu ortak özellikler arasında bulunmaması.<br />
<br />
<code>equals</code> metodunun ezilmesi yönünde bir karar verilmesi durumunda, <code>hashCode</code> iletisinin de gözden geçirilmesi gerekecektir. Hedef nesnenin <code>Hashtable</code>, <code>HashMap</code> ve <code>HashSet</code> gibi kapların altyapısında yararlanılan doğrudan erişimli veri yapılarında tutulabilmesi için kıyımdan geçirilerek tamsayı karşılığını döndüren bu iletinin <code>Object</code> sınıfındaki sözleşme tanımına bakılacak olursa, eşit olan nesnelerin aynı kıyım değerine sahip olması gerektiği görülür. Dolayısıyla, aynı öğrenciyi temsil eden farklı nesnelerin eşit ilan edildiği <code>Öğrenci</code> sınıfında, <code>hashCode</code> metodunun da bu eşitliğin altını çizen bir biçimde ezilmesi gerekecektir. Buna göre, aynı öğrenci numarasına sahip tüm <code>Öğrenci</code> nesnelerini eşit ilan eden yukarıdaki <code>equals</code> metoduna koşut <code>hashCode</code> gerçekleştirimi aşağıda verilmiştir.<br/>
<pre class="brush:java;gutter:false" name="hashCodeÖrneği">...
public class Öğrenci {
...
public int hashCode() { return (int) _no; }
...
} // Öğrenci sınıfının sonu
</pre>
Dikkat edecek olursanız, <code><b>long</b></code> olan numaranın <code><b>int</b></code> olarak biçimlendirilmesi sonucu veri kaybı yaşanmış ve farklı iki öğrenciyi temsil eden <code>Öğrenci</code> nesnelerinin aynı kıyım değerine sahip olma olasılığı belirmiştir. <code><b>long</b></code> türünün <code><b>int</b></code> türünden daha büyük bir değer uzayına sahip olmasından kaynaklanan bu durum, <code>hashCode</code> sözleşmesini ihlal etmemektedir; istenen aynı öğrenciyi temsil eden nesnelerin eşit kıyım değerlerine sahip olmasını garanti etmektir, ayrı öğrencilerin aynı kıyım değerine sahip olmasını engellemek değil.<br/>
<br/>
<code>hashCode</code> iletisinin kullanımı, nesnelerin kıyım tablosu temelli kaplarda tutulmasına sınırlı değildir. Eşitlik denetiminin uzun zaman alan bir işlem olması durumunda, <code>hashCode</code> olumsuz yanıtın hızlı verilmesini olanaklı kılar. Örnek olarak, binlerce elemana sahip ve ön tarafları birbirleriyle aynı olan eşit uzunluklu iki liste düşünün. Listeleri dolaşıp elemanları karşılıklı olarak eşitlik denetimine tabi tutmaktansa, listelerin öncelikle kıyım değerleri karşılaştırılır. Karşılaştırmanın olumsuz sonuç vermesi, listelerin eşit olmaması anlamına geldiği için hızlı bir biçimde yanıtı bulmamızı sağlayacaktır.<sup><a href="#dn3" id="ref3">3</a></sup> Dolayısıyla, <b><code>hashCode</code> metodu da <code>equals</code> ile birlikte ele alınmalıdır.</b><br/>
<br/>
<h3 id="kopyalayanYapıcı">Kopyalama</h3><br/>
Programlamaya yeni başlayıp eşitlik denetiminde tatsız bir sürpriz yaşayanların çoğu, nesne kopyalama işleminde de benzer bir sıkıntı yaşarlar. Bunun sebebi, kopyalama amacıyla ilkleme veya atamadan yararlanmaları ve bu işlemlerin nesne yerine tutacağı kopyalamasıdır. Dolayısıyla, nesneler kopyalanmayacak, paylaşılacaktır. İhtiyacını duyduğumuz şey <i>kopyalayan yapıcı</i> gerçekleştirimidir. Kopyalanmak istenen nesnenin türünden bir parametresi bulunan bu metot, nesne kopyalamaktan ne anlaşıldığını gerçekleştiren bir gövdeye sahip olmalıdır. <code>Öğrenci</code> nesnelerinin kopyalanması için kullanılabilecek bir kopyalayan yapıcı gerçekleştirimi aşağıda verilmiştir.<br />
<pre class="brush:java;gutter:false" name="kopyalayanYapıcıÖrneği">...
public class Öğrenci {
...
public Öğrenci(Öğrenci diğerÖğr) {
_no = diğerÖğr._no;
_ad = new String(diğerÖğr._ad);
_soyad = new String(diğerÖğr._soyad);
_ortalama = diğerÖğr._ortalama;
} // kopyalayan yapıcı sonu
...
} // Öğrenci sınıfının sonu
</pre>Dikkat edersiniz, <code>_ad</code> ve <code>_soyad</code> altalanları için <code>String</code> sınıfının kopyalayan yapıcısı kullanılıyor. Bu durumda, <code>String</code> nesnelerinin değişmez içerikli karakter katarları tuttuğunu bilenler, haklı olarak, kopyalayan yapıcı yerine ilklemenin daha akılcı olacağını söyleyeceklerdir. Ne de olsa, değişmeyen bir şeyin kopyalanması ile paylaşılması arasında bir fark yoktur; paylaşılan nesnenin değişmesi söz konusu olmayacağı için bir tutacak yoluyla yapılan değişikliğin bir diğer tutacak yoluyla görülmesi gibi bir durum asla ortaya çıkmayacaktır. Dolayısıyla, <code>Öğrenci</code> sınıfı için aşağıda verilen daha verimli gerçekleştirim de işimizi görecektir.<br />
<pre class="brush:java;gutter:false" name="kopyalayanYapıcıÖrneği">...
public class Öğrenci {
...
public Öğrenci(Öğrenci diğerÖğr) {
_no = diğerÖğr._no;
_ad = diğerÖğr._ad;
_soyad = diğerÖğr._soyad;
_ortalama = diğerÖğr._ortalama;
} // kopyalayan yapıcı sonu
...
} // Öğrenci sınıfının sonu
</pre><br />
<h3 id="varsayılanYapıcı">Varsayılan Yapıcı</h3><br/>
Sizi gafil avlayabilecek bir diğer metot, nesnelerin ilklenmesi esnasında derleyicinin sentezlediği kod tarafından arka planda usulca çağrılabilecek olan argümansız yapıcıdır. Programcının talebi ile çağrılmayıp derleyicinin araya girerek yapıcı metot çağrılması gerekliliğine hükmettiği durumlarda çağrıldığı için <i>varsayılan yapıcı</i> olarak adlandırılan bu yapıcı da sınıf gerçekleştirimi sırasında göz önünde bulundurulmalıdır. Mesela, geliştirmekte olduğunuz sınıftan kalıtlayan bir sınıfın nesnesinin yaratılması sırasında sizden habersiz bir şekilde sınıfınızın varsayılan yapıcısı çağrılacaktır. Dolayısıyla, böylesine bir senaryoyu öngörmeniz durumunda varsayılan yapıcıyı sağlamanız yerinde bir hamle olacaktır.<br />
<pre class="brush:java;gutter:false" name="varsayılanYapıcıÖrneği">...
public class Öğrenci {
...
public Öğrenci() { }
...
} // Öğrenci sınıfının sonu
</pre>Bu noktada, derleyicinin varsayılan yapıcı sentezleme noktasındaki yardımını bir kez daha hatırlatarak, bu desteğin programcı tarafından herhangi bir yapıcının sağlanması durumunda geçerli olmadığını yineleyelim. Bundan dolayı, yukarıdaki işlevsiz gibi gözüken yapıcı gerçekleştirimi zorunludur.<br />
<br />
<h3 id="karşılaştırma">Karşılaştırma</h3><br/>
Sınıf gerçekleştirimi sırasında gerekliliğine karar vermemiz icap eden bir diğer işlem, sınıfımızın bir nesnesini aynı türden bir diğeri ile karşılaştırma işlemidir. Biraz kolaycı olanlarınız, <code>toString</code> ve <code>equals</code>'da olduğu gibi, aynı türe ait nesnelerin öncelik-sonralık sıralamasını saptayan bu işleme karşılık da <code>Object</code> sınıfında bir metot bulunduğunu ve yapılması gerekenin sınıfımızın ihtiyaçlarına göre bu metodun ezilmesi olduğunu düşünebilirler. Ancak, karşıt bir örnek kolay varılan bu yargının tüm sınıflar için doğru olmadığını gösterir. Örneğin, matemetikteki küme kavramını gerçekleştirmekte olduğumuzu düşünün; kümelerin sıraya dizilmesinin, iki kümeden hangisinin daha önce/sonra geldiği sorusunun pek de mantıklı olmadığı görülecektir. Dolayısıyla, karşılaştırma işlemi hoş yazım ve eşitlik denetimi işlemlerinden farklı ele alınmalıdır.<br />
<br />
Derdimizin çaresi, Java alemindeki sınıfları karşılaştırılabilir olanlar ve olmayanlar şeklinde ikiye ayırmaktır. Aradığımız bu çözüm bize <i>arayüz</i> üstkavramınca sağlanan tasnifin ta kendisidir. Birbirleriyle ne kadar alakasız olurlarsa olsunlar, aynı arayüzü gerçekleştiren iki (veya daha fazla sayıda) sınıf, arayüz tutacağı vasıtasıyla görüldükleri takdirde aynı kullanılabilirliğe sahip olacaklardır; söz konusu sınıfların nesneleri kendi türlerinden bir diğer nesne ile karşılaştırılabilecek ve bu işlemin sonucunda sıraya dizilebileceklerdir. Dolayısıyla, yapmamız gereken Java platformundaki nesneler için karşılaştırılabilirlik kategorisini tanımlayan <code>Comparable</code> arayüzünü sınıfımızda gerçekleştirmektir. Bunun örneği aşağıda verilmiştir.<br />
<pre class="brush:java;gutter:false" name="kopyalayanYapıcıÖrneği">...
public class Öğrenci implements Comparable<Öğrenci> {
...
public int compareTo(Öğrenci diğerÖğr) {
if (_ortalama > diğerÖğr._ortalama)
return 1;
else if (_ortalama < diğerÖğr._ortalama)
return -1;
else if (_no > diğerÖğr._no)
return 1;
else return -1;
} // int compareTo(Öğrenci) sonu
...
} // Öğrenci sınıfının sonu
</pre><code>Comparable</code> arayüzü, içerdiği yegâne ileti olan <code>compareTo</code>'nun Java dokümantasyonunda ifade edilen anlamına uygun bir biçimde gerçekleştirimini zorunlu koşar. Bu, dönüş değeri olarak hedef nesnenin diğer nesneden "önce" gelmesi durumda artı, "sonra" gelmesi durumunda eksi, aksi takdirde 0 döndürüleceği anlamına gelir.<br />
</div><br />
<div style="text-align:right"><a alt="Java'da sınıf tanımı" href="http://ta-java.blogspot.com/2011/05/bileske-turler-snflar.html">Sınıf Tanımlama Reçetesi</a><br />
<a alt="Java'da nesne ilkleme" href="http://ta-java.blogspot.com/2011/05/nesnelerin-ilklenmesi.html">Nesne İlkleme</a><br />
</div><hr/><div id="dipnotlar" style="text-align: justify;"><ol><li id="dn1">Bir nesnenin kıyım değeri—yani, nesnenin altalanlarının özeti—nesneye <code>hashCode</code> iletisinin gönderilmesi ile elde edilir. <a href="#ref1">↑</a></li>
<li id="dn2">Burada herhangi bir şekilde nesnenin kopyalanması gibi bir durum olmayacaktır. Aynı nesne değişik türlü iki tutacak vasıtasıyla, <code>diğerNesne</code> ve <code>diğerÖğr</code>, iki farklı arayüze sahipmiş gibi kullanılacaktır. <a href="#ref2">↑</a></li>
<li id="dn3">Anlatılanın geçerli olması için, kap içeriğini değiştirmeye dönük herhangi bir işlemin başarıyla sonuçlanması sonrasında kaba dair kıyım değerinin güncellenmesi gerekir. Bu ise, içerik güncelleme işlemlerinin maliyetini olumsuz bir biçimde etkileyecektir. Dolayısıyla, önerilen yöntem daha ziyade değişmez içerikli kaplara sınırlıdır. <a href="#ref3">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-68572510073038184642011-05-18T15:35:00.003+03:002011-12-14T16:15:55.918+02:00Nesnelerin İlklenmesi<div id="anaMetin" style="text-align:justify">İki önceki yazımızda<a alt="Java'da diziler" href="http://ta-java.blogspot.com/2011/05/bileske-turler-snflar.html">🔎</a> programcının yeni bir sınıf tanımlamak yoluyla Java platformunu nasıl geliştirebileceğine ve sınıfların nesnelerinin nasıl yaratılıp kullanılabileceğine değinmiştik. Bu yazımızda da, nesnelerin yaratılmaları esnasında ne şekilde ilkleneceğine göz atacağız.<br />
<br />
Java'da, diğer nesne paradigmasını destekleyen dillerde olduğu gibi, nesnenin yaratılması iki aşamada tamamlanır: i) nesne için <i>yığın bellek</i>ten (İng., heap memory) yerin ayrılması, ii) ayrılan belleğin tutarlı değerlerle ilklenmesi ve yığın bellek dışındaki kaynakların elde edilmesi. Nesnenin yaşam döngüsünü başlatan bu iki adımın atomik olarak icra edildiği ve iki adımdan birinin başarısızlığa uğraması durumunda nesne yaratmanın mümkün olmayacağı unutulmamalıdır. Örneğin, sınırlı bir kaynak olan yığın belleğin tükenmesi olasılığına karşılık programcı, <code>OutOfMemoryError</code> hatasının oluşabileceğinin farkında olmalıdır. Benzer şekilde, ikinci aşamada elde edilmek istenen bir dış kaynağın elde edilememesi de ilkleme esnasında durumu özetleyen bir ayrıksı durumun atılmasına neden olabilir.<br />
<br />
Yığın belleğin JSM'nin parçası olan bir bileşen tarafından ayrılması nedeniyle, nesne yaratmak isteyen programcının ilk aşamanın yerine getirilmesi için <code><b>new</b></code> işlecini kullanmak dışında bir şey yapması gerekmez. Buna karşılık, ilkleme ve dış kaynak elde edilmesinin söz konusu olduğu ikinci aşama sınıf gerçekleştirimcisinin sağladığı yapıcı metotlardan birinin çağrılması ile tamamlanır. İki aşama arasındaki bağlantı ise derleyicinin <code><b>new</b></code> işleci ve uygun bir yapıcıyı ilişkilendirilmesi ile sağlanır. Aşağıdaki örnekler üzerinden anlamaya çalışalım.<br />
<pre class="brush:java;gutter:false" name="nesneYaratma">char[] karakterDizisi = new char[]{'D', 'i', 'z', 'i'};
String katar1 = new String(karakterDizisi);
String katar2 = new String(karakterDizisi, 0, 3);
</pre>İlk satırda oluşturulan dört karakterli dizi kullanılarak yaratılan her iki nesne de yerlerinin ayrılmasının ardından <code><b>new</b></code> işleci sonrasında sağlanan sınıftaki (<code>String</code>) yapıcılardan biri kullanılarak ilkleniyor. Buna göre, <code>katar1</code> tutacağı tarafından temsil edilen nesne, <code><b>char</b>[]</code> bekleyen yapıcıda dizi içindeki tüm elemanlar kullanılarak ilklenirken, <code>katar2</code>'nin gösterdiği nesne, ilk argümanında geçirilen karakter dizisinin ikinci argümanda verilen konumdan başlayarak üçüncü argümanda sağlanan sayıda karakteri kullanılarak bir başka yapıcıda ilkleniyor.<br />
<br />
Genel çerçeveyi çizdikten sonra, nesne ilkleme bağlamında bilinmesi gereken diğer noktalara değinelim. Nesnenin kimi altalanlarına ilk değer sağlanmaması durumu ile başlayalım. İlişkin altalana ait belleğin o anki rasgele içeriğini ilk değer olarak kabul eden C++ dilinin aksine, Java'da ilklenmeyen altalanlar aşağıdaki tabloda verilen değerlere sahip olacak şekilde ilklenir.<br />
<br />
<table align="center" border="1"><caption><i>Altalanların varsayılan ilk değerleri</i></caption> <thead>
<tr><th>Tür</th><th>İlk değer</th></tr>
</thead> <tbody>
<tr><td style="text-align: left;">Tamsayı türler</td><td style="text-align: center;">0</td></tr>
<tr><td style="text-align: left;">Kayan noktalı türler</td><td style="text-align: center;">0.0</td></tr>
<tr><td style="text-align: left;"><code><b>char</b></code></td><td style="text-align: center;">'\u0000'</td></tr>
<tr><td style="text-align: left;"><code><b>boolean</b></code></td><td style="text-align: center;"><code><b>false</b></code></td></tr>
<tr><td style="text-align: left;">Bileşke türler</td><td style="text-align: center;"><code><b>null</b></code></td></tr>
</tbody></table><br />
Buna göre, aşağıdaki sınıfın nesnesinin yaratılması durumunda <code>_yaşadığıÜlke</code> adlı altalan <code><b>null</b></code> değerine sahip olacaktır.<br />
<pre class="brush:java;gutter:false" name="varsayılanDeğerler">public class Vatandaş {
public Vatandaş(String ad, String anneAdı, String babaAdı) {
_ad = ad;
_anneAdı = anneAdı;
_babaAdı = babaAdı;
} // yapıcı(String, String, String)
...
private String _ad, _anneAdı, _babaAdı;
private String _yaşadığıÜlke;
} // Vatandaş sınıfının sonu
</pre>Altalanların hiçbirine değer sağlanmaması halinde, gerçekleştirimci tarafından yapıcı metot yazılması pek anlamlı olmaz. Boş bir gövdeye sahip boş parametre listeli bir yapıcı ile kendini gösteren böylesine bir durumda derleyici araya girer ve gerçekleştirimciyi yapıcı metot yazma yükümlülüğünden kurtararak tüm altalanları varsayılan değerler ile ilkleyen bir yapıcı sentezler. Ancak, bu dost eli sadece yapıcı sağlanmadığı takdirde uzanacaktır; gerçekleştirimcinin sınıf tanımında bir veya daha fazla sayıda yapıcıya yer vermesi derleyicinin yapıcı sentezlemesinin önüne geçecektir.<br />
<br />
İlkleme bağlamında bilinmesi yararlı olacak bir diğer konu, ilkleme bloklarının kullanımıdır. Yukarıdaki örneği ele alarak anlamaya çalışalım. <code>Vatandaş</code> sınıfının Türkiye'de kullanılacak olduğunu düşündüğümüzde, yaratılacak nesnelerdeki <code>_yaşadığıÜlke</code> adlı altalanın çoğunlukla <code>"Türkiye"</code> olacağı öngörülebilir. Dolayısıyla, derleyicinin bileşke türler için kullandığı <code><b>null</b></code> yerine <code>"Türkiye"</code> değerli bir karakter katarının varsayılan ilk değer olması daha yerinde olacaktır. İşte bu gözlemimizi, derleyicinin kullanacağı varsayılan ilk değerlerin değiştirilmesi için her nesne yaratılması noktasında, nesnenin ilklenmesi öncesinde işlenen bir <i>nesne ilkleme bloğu</i> içinde belirtmemiz gerekir. Aşağıdaki değiştirilmiş sınıf tanımı bunun nasıl yapılabileceğini gösteriyor.<br />
<pre class="brush:java;gutter:false" name="nesneİlklemeBloğu">public class Vatandaş {
// Nesne ilkleme bloğu.
{
System.out.println("Nesne yaratılıyor...");
_yaşadığıÜlke = "Türkiye";
}
public Vatandaş(String ad, String anneAdı) {
_ad = ad;
_anneAdı = anneAdı;
} // yapıcı(String, String)
public Vatandaş(String ad, String anneAdı, String babaAdı) {
_ad = ad;
_anneAdı = anneAdı;
_babaAdı = babaAdı;
} // yapıcı(String, String, String)
public Vatandaş(String ad, String anneAdı, String babaAdı, String ülke) {
_ad = ad;
_anneAdı = anneAdı;
_babaAdı = babaAdı;
_yaşadığıÜlke = ülke;
} // yapıcı(String, String, String, String)
...
// Nesne ilkleme bloğu.
{ _babaAdı = "Mehmet"; }
private String _ad, _anneAdı, _babaAdı;
private String _yaşadığıÜlke;
} // Vatandaş sınıfının sonu
</pre>Dikkat ederseniz, metot gövdeleri dışında bulunan kıvrımlı ayraç çifti arasındaki komutlardan oluşan nesne ilkleme blokları, sınıfın herhangi bir yerine konulabileceği gibi tanım içinde birden çok kez de geçebiliyor. Ortaya çıkacak toplam etki, sınıfın başından sonuna doğru tüm blokların geçiş sırasına göre işlenmesi ile elde edilir. Dolayısıyla, yukarıdaki sınıfta geçen nesne ilkleme bloklarının etkisi aşağıdaki blokla da elde edilebilir.<br />
<pre class="brush:java;gutter:false" name="sınıfİlklemeBloğu">{
System.out.println("Nesne yaratılıyor...");
_yaşadığıÜlke = "Türkiye";
_babaAdı = "Mehmet";
}
</pre>İlkleme amacıyla kullanılabilecek bir diğer programlama aracı, söz konusu sınıfın <u>ilk</u> kullanılışı öncesinde <u>bir kereliğine</u> işlenen <i>sınıf ilkleme bloğu</i>dur. Örneğin, aşağıdaki tanıma göre, <code>Vatandaş</code> sınıfının ilk nesnesinin yaratıldığı veya nesne yaratılmadan kullanılabilecek <code><b>static</b></code> öğelerinden birinin kullanıldığı nokta öncesinde standart çıktıya <code>"Vatandaş'ın ilk kullanımı..."</code> ve <code>"Lütfen Vatandaş'ı özenle kullanınız..."</code> mesajları basılacak, [daha sonra yaratılacak] nesnelerin paylaşacağı ortak bir özellik olan <code>_vatandaşSayısı</code> 1 ile ilklenecektir. Aynı sınıfın nesnesinin yaratıldığı her noktada işlenen nesne ilkleme bloğu sayesinde ise, çağrılan yapıcı metodun başlaması öncesinde, ilklenmekte olan nesnenin sabit değerli <code>_vatNo</code> altalanı <code>_vatandaşSayısı</code>'nın o anki değeri ile ilklenecektir.<br />
<pre class="brush:java;gutter:false" name="sınıfİlklemeBloğu">public class Vatandaş {
// Sınıf ilkleme bloğu.
static {
System.out.println("Vatandaş'ın ilk kullanımı...");
System.out.println("Lütfen Vatandaş'ı özenle kullanınız...");
}
// Nesne ilkleme bloğu.
{ _vatNo = _vatandaşSayısı++; }
...
public static long vatandaşSayısı() {
return _vatandaşSayısı - 1;
} // long vatandaşSayısı() sonu
...
static { _vatandaşSayısı = 1; }
private String _ad, _anneAdı, _babaAdı;
private String _yaşadığıÜlke;
private final long _vatNo;
private static long _vatandaşSayısı;
} // Vatandaş sınıfının sonu
</pre></div><br />
<div style="text-align:right"><a alt="Java'da sınıf tanımı" href="http://ta-java.blogspot.com/2011/05/bileske-turler-snflar.html">Sınıf Tanımlama Reçetesi</a><br />
<a alt="Tüm Java sınıflarına gerekebilecek metotlar" href="http://ta-java.blogspot.com/2011/05/her-snfa-lazm-metotlar.html">Bazı Önemli Metotlar</a><br />
</div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-19625500038851816592011-05-06T16:41:00.000+03:002011-12-14T16:18:39.439+02:00Java Platformunun Dikkate Değer Bir Dili: Scala-1<div id="anaMetin" style="text-align:justify">Başlık yazının konusunu ele veriyor: sınıf dosyasına derlenmek suretiyle JSM tarafından çalıştırılabilir programlar üretmekte kullanılabilecek <a href="http://www.scala-lang.org/">Scala dili</a>. .NET'in CIL komutları içeren DLL formatına da derlenebilen, fonksiyonel programlama kavramlarını destekleyen bu nesne yönelimli dil, Java'ya eklenmesi düşünülen pek çok kavram için bir laboratuar ve esin kaynağı olma özelliği taşıyor. Scala programlama diline dair dizimizin ilk bölümü olan bu yazıda da bu dili Java bilenlere tanıtmaya çalışacağız.<br />
<br />
<ul><li><a href="#basitBirProgram">Basit bir Scala programı</a></li>
<li><a href="#JavaKullanımı">Java programlama dili sınıflarının kullanımı</a></li>
<li><a href="#dizilerSoysalTürdür">Diziler soysal bir türün örneğidir</a></li>
<li>İkinci yazı: Scala'da sınıf tanımı<a alt="Scala'da sınıf tanımı" href="http://ta-java.blogspot.com/2011/10/java-platformunun-dikkate-deger-bir.html">🔎</a></li>
</ul><br />
<h3 id="basitBirProgram">Basit Bir Scala Programı</h3><br/>
Java'da olduğu gibi, Scala'da da her şey bir sınıf içine yazılmak zorundadır. Aslına bakılırsa, ilkel türleri de sınıflarla temsil eden Scala bu konuda Java'dan daha katıdır diyebiliriz. Bir diğer farklılık, ';' karakterinin pek çok C-temelli dil tarafından kabul edilen komut sonlandırıcılık görevindeki değişikliktir. Satır sonlarının komut sonlandırma noktası olarak algılanması nedeniyle, birden fazla komutun aynı satıra konulmaları durumunda kullanılması dışında, Scala'da komut sonlarına ';' yerleştirilmesine gerek yoktur.<br />
<br />
<div style="color: #444444; text-align: center;">SelamMillet.scala</div><pre class="brush:scala;gutter:false" name="ilkScalaProgramı">object SelamMillet {
def main(args: Array[String]) =
println("Selam millet!")
} // SelamMillet sınıfının sonu</pre>Yukarıda verilen kaynak kodda <code>main</code> metodunun dönüş türünün eksik olduğunu düşünüyorsanız, Scala'nın bir diğer özelliğini söyleyerek yanıt verelim: Scala, ML ve Haskell programlama dillerinde olduğu gibi, program metninin sağladığı bilgilerden yararlanarak programlama öğelerinin türlerini çıkarsamaya çalışır. Bundan dolayı, Scala programcılarının derleyicinin çıkarsayamadığı yerler dışında hiçbir yere tür bilgisi koymasına gerek yoktur. Derleyici tarafından kabul görmekle birlikte, <code>main</code> metodunun imzasının aşağıdaki şekilde yazılması gerekmez.<br />
<pre class="brush:scala;gutter:false" name="türÇıkarsama">def main(args: Array[String]): Unit = </pre>Örneğimizde sınıf kavramının izlerini arayanlar, kendilerini ikna etmekte zorlanabilirler. Zira, sınıf sözcüğü (<code><b>class</b></code>) yerini nesne sözcüğüne (<code><b>object</b></code>) bırakmıştır. Sınıfın bir şablon, nesnenin ise söz konusu sınıfın bir örneği olduğunu bilenleriniz bilgilerinden kuşku duymaya başladıysalar üzülmesinler, her şey eskiden olduğu gibi. Beklenen <code><b>class</b></code> sözcüğü yerine <code><b>object</b></code> sözcüğünün olması, tanımlanmakta olan sınıfın bir özelliğini yansıtmaktadır: Java terminolojisi ile açıklayacak olursak, <code>SelamMillet</code> sınıfının yegâne metodu olan <code>main</code> <code><b>static</b></code>'tir. Bir diğer deyişle, <code>main</code> metodu, <code>SelamMillet</code> sınıfının nesnesi yaratılmadan kullanılabilir. Nesne paradigması ile bağdaşmayan bu durum—problem çözümünde merkezi rol oynayan nesnenin bırakın kullanılmasını, yaratılması bile söz konusu değil—kimi zaman çalıştırılabilir sınıfın tek bir nesnesinin yaratılıp bu nesneye ileti gönderilmesi yoluyla aşılır. İşte, tasarım desenleri dünyasında <i>tek örnek[li sınıf]</i> (İng., singleton [class]) deseni olarak adlandırılan bu kullanım, Scala'da <code><b>object</b></code> anahtar sözcüğünün kullanımı ile belirtilir. Bu şekilde tanımlanmış sınıfların, tüm öğelerinin <code><b>static</b></code> olduğu varsayılır. Bundan dolayıdır ki, Scala'da <code><b>static</b></code> anahtar sözcüğüne yer yoktur, programcı bu tür özellikleri tek örnekli sınıflar içinde toplayarak bir <code><b>object</b></code> olarak tanımlamalıdır.<br />
<br />
Kaynak kodun yazılması sonrasında yapılacaklar Java'daki ile aynıdır: ilk adımda sınıf dosyasına çevrilerek derlenen kod, JSM tarafından çalıştırılmalıdır.<br />
<pre class="brush:bash;gutter:false" name="code"># Derleme aşaması
$ scalac -encoding utf-8 SelamMillet.scala
$ ls SelamMillet*
SelamMillet.class SelamMillet$.class SelamMillet.scala
# Yorumlama aşaması
$ scala SelamMillet
Selam millet!</pre>Yukarıdaki listelemenin iki noktası dikkatinizi çekmiştir. Öncelikle, Scala programlama dilinin derleyicisi olan <code>scalac</code> komutunun ürettiği iki tane sınıf dosyası vardır. Bunlardan, SelamMillet.class uygulama dosyası iken, SelamMillet$.class tek örnekli sınıf deseninin gerçekleştirildiği kodu içerir. İkinci nokta ise, JSM'nin <code>java</code> yerine <code>scala</code> komutuyla çağrılmasıdır. Bu, her programlama dili için JSM'nin ayrı ayrı gerçekleştirilmesi gerektiği yanılgısına neden olmamalıdır. Çünkü, <code>scala</code> komutu <code>java</code> komutunun Scala'ya has sınıf dosyalarını yükleyerek işini gören uyarlaması olarak düşünülebilir. Dolayısıyla, yorumlama aşması aşağıdaki şekilde de yapılabilir.<br />
<pre class="brush:bash;gutter:false" name="code"># Yorumlama aşaması
$ java -cp .:/usr/share/java/scala-library.jar SelamMillet
Selam millet!</pre>Bunun güzel bir sonucu, çöp toplama, güvenlik denetimleri, anında derleme gibi JSM tarafından sağlanan pek çok hizmetin Scala programları için de otomatikman sağlanmasıdır; Scala dilinin (ve diğer JSM üzerinde çalışan dillerin) geliştiricilerinin bu hizmetleri ayrıca gerçekleştirmelerine gerek yoktur.<br />
<br />
<h3 id="JavaKullanımı">Java Programlama Dili Sınıflarının Kullanımı</h3><br/>
JSM dili olmaları nedeniyle, gerek Java gerekse Scala, kurulumunuzun sınıf yolu üzerindeki tüm sınıf dosyalarını, kaynak kodları hangi dilde yazılmış olursa olsun, kullanabilir. Scala programcısı açısından bakıldığında bu, Scala kaynak kodu içinden Java platformunun bildik tüm işlevselliğinden sanki Scala'da yazılmış gibi yararlanılabileceği anlamına gelir. Bu, aşağıdaki örnekte verildiği gibi işlevselliği kullanmak şeklinde olabileceği gibi, platformdaki bir sınıftan kalıtlama veya bir arayüzü gerçekleştirme şeklinde de olabilir. Ancak, Java'dan gelip Scala kaynak kodu üretenlerin yararlanılacak işlevi Scala sözdizimi ile kullanmaları gerektiğini akılda tutmaları gerekir. Aşağıdaki örnek üzerinden görelim.<sup><a href="#dn1" id="ref1">1</a></sup><br />
<br />
Açıklamalara Scala'nın Java ile farklılık gösteren bir özelliğine dikkat çekerek başlayalım: dosya içinde dosya ile aynı ada sahip bir sınıfın bulunması gerekmez; Scala'da böyle bir zorunluluk yok. Ancak, daha sonraki derleme/çalıştırma komutlarında örneği verildiği gibi, derleyiciye geçirilenin dosya adı iken yorumlayıcıya geçirilenin sınıf dosyasının adı olduğu unutulmamalıdır.<br />
<br />
Örneğimizin Scala'ya yeni gelenler için dikkat çeken bir yönü, <code><b>import</b></code> bildiriminin ilk satırdaki seçimli kullanımı.<sup><a href="#dn2" id="ref2">2</a></sup> Tekli ve jokerli<sup><a href="#dn3" id="ref3">3</a></sup> kullanımlara sahip bu bildirim, örneğimizde de olduğu gibi, bir veya daha fazla sayıda türün görünür kılınmasında da kullanılabilir. Dikkat çekecek bir diğer anahtar sözcük ise, simgesel sabit tanımlamak için kullanılan <code><b>val</b></code>.<sup><a href="#dn4" id="ref4">4</a></sup><br />
<br />
<div style="color: #444444; text-align: center;">Örnek2.scala</div><pre class="brush:scala;gutter:false" name="ScaladanJava">import java.util.{Scanner, Vector}
object ScaladanJava {
val grdKnl = new Scanner(System.in);
println("İkinci Scala örneğine hoş geldiniz...")
def main(args: Array[String]) = {
val tekSayılar = new Vector[Int]()
print("Artı bir tamsayı giriniz: ")
val j = grdKnl.nextInt()
for(i <- 1 to j by 2) tekSayılar.add(i)
println("1 ile" + j + " arasındaki tek sayılar: " + tekSayılar)
}
println("Bu örnek Scala kodundan Java'da yazılmış işlevselliği kullanmaya dönük")
} // ScaladanJava sınıfının sonu</pre>
Metot gövdeleri dışındaki komutların varlığı da sizi telaşa sürüklemesin; bu komutların sınıf içindeki sıraları korunacak şekilde sınıf ve/veya nesne ilkleme bloğu içine konulduğunu düşünmeniz işinizi kolaylaştıracaktır. Tek örnekli bir sınıf olması nedeniyle tüm öğeleri <code><b>static</b></code> varsayılan sınıfımızda, metot tanımları dışındaki her şey <i>sınıf ilkleme bloğu</i>na konulup uygulamanın çalıştırılması sonrasında ilk iş olarak işlenirken, <code><b>new</b></code> işleci ile nesnesi yaratılabilen çok örnekli sınıflarda bu tür öğeler <i>nesne ilkleme bloğu</i>na konulacak ve sentezlenen bu nesne ilkleme bloğu her nesne yaratılışı noktasında yapıcı çağrısı öncesinde işlenecektir.
<pre class="brush:bash;gutter:false" name="code">$ scalac -encoding utf-8 Örnek2.scala
$ scala ScaladanJava
İkinci Scala örneğine hoşgeldiniz...
Bu örnek Scala kodundan Java'da yazılmış işlevselliği kullanmaya dönük
Artı bir tamsayı giriniz: 16
1 ile 16 arasındaki tek sayılar: [1, 3, 5, 7, 9, 11, 13, 15]</pre><par/>
<br/>
<h3 id="dizilerSoysalTürdür">Diziler Soysal Bir Türün Örneğidir</h3><br/>
Verdiğimiz örneklerde komut satırından geçirilen argümanları tutmak için kullanılan dizi ve ikinci örneğimizdeki <code>Vector</code> nesnesinden de görülebileceği gibi, Scala'da soysallık tür parametrelerine karşılık gelen tür argümanlarının köşeli ayraç çifti arasında geçirilmesiyle sağlanır. Buna göre <code>Vector[<b>Int</b>]</code>, <code><b>Int</b></code> türlü elemanlara sahip bir kabın kullanılacağını gösterir. Bu tanım sonrasında, söz konusu kap parametrik türün (<code>java.util.Vector</code>) sağladığı tüm işlevsellikten yararlanarak manipüle edilebilir. Bu, soysal <code>Array</code> türünün örneği olan diziler için de geçerlidir. Tanımlanan bir dizi, <code>Array</code> sınıfınca sunulan tüm işlevsellikten yararlanabilecektir.
</div><br/>
<div style="text-align:right"><a alt="Önemli bir JSM dili: Scala" href="http://ta-java.blogspot.com/2011/10/java-platformunun-dikkate-deger-bir.html">Scala'da Sınıf Tanımı</a></div>
<hr/><div id="footnote" style="text-align: justify;"><ol><li id="dn1">Gözünüzden kaçmış olabilir, işaret etmekte yarar var: ilk örneğimizin aksine ikinci örneğimizin metot gövdesi kıvrımlı ayraç çifti arasına yazılmıştır. Bunun sebebi, tek komuttan oluşması durumunda Scala'daki metot gövdelerinin, tıpkı Java'daki <code><b>if</b></code>, <code><b>while</b></code> yapılarının gövdeleri gibi, kıvrımlı ayraç çifti arasına yazılmalarının zorunlu olmamasıdır. <a href="#ref1">↑</a></li>
<li id="dn2"><code>java.lang</code> paketindeki türler, herhangi bir bildirime gerek olmadan Scala derleyicisi tarafından görünür hale getirilirler. <a href="#ref2">↑</a></li>
<li id="dn3">Söz konusu paketin tümünü görünür kılan jokerli kullanım, Java'da "*" ile belirtilirken Scala'da '_' ile belirtilir. <a href="#ref3">↑</a></li>
<li id="dn4">Tanımlayıcının güncellenebilir bir içerik tutabilmesi için <code><b>val</b></code> yerine <code><b>var</b></code> ile nitelenmesi gerekir. <a href="#ref4">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-45836556587065453792011-05-02T19:09:00.006+03:002011-12-14T16:22:42.108+02:00Bileşke Türler-Sınıflar<div id="anaMetin" style="text-align: justify;">Bir önceki yazımızda<a alt="Java'da diziler" href="http://ta-java.blogspot.com/2011/04/bileske-turler-diziler.html">🔎</a> programlama dilinin sunduğu kavram dağarcığının çözülmekte olan probleminki ile örtüşmesi durumunda çözümün daha kolay olacağına değinmiş ve Java gibi genel amaçlı dillerde problem ve çözüm uzayları arasındaki kusursuz bir uyum beklentisinin genel olma iddiasıyla çelişeceğini söyleyerek, gerçek çözümün programcıya yeni bileşke tür tanımlama olanağı sağlamaktan geçtiğini ifade etmiştik. Bu yönde sunduğumuz ilk araç ise türdeş verilerin gruplanmasına yarayan diziler olmuştu. İkinci yazımızın konusu ise, türdeş olsun ya da olmasın, birbirleriyle ilişkili verilerin gruplanması ve bu veri öbeği üzerindeki makul işlemlerin tutarlı bir biçimde uygulanmasını sağlamak için soyut veri türleri tanımlamayı olanaklı kılan sınıf kavramı olacak.<sup><a href="#dn1" id="ref1">1</a></sup> Daha fazla uzatmadan başlayalım.<br />
<br />
<div style="color: #444444; text-align: center;">Matris.java</div><pre class="brush:java;gutter:false" name="sınıfÖrneği">...
public class Matris {
...
} // Matris sınıfının sonu</pre>Problem tanımında geçen ve Java platformunca doğrudan desteklenmeyen bir kavramın Java koduna aktarılması, söz konusu kavramın bizi ilgilendiren özelliklerini içeren bir sınıfın tanımlanması ile olur. Bu ise yukarıdaki gibi bir kod iskeletinin uygun tanımlarla doldurulması yoluyla sağlanır. Tanımların sağlanması sırasında, tanımları sağlayacak gerçekleştirimci(ler) ile sınıftan yararlanacak kullanıcıların farklı kitleler olduğu unutulmamalıdır; gerçekleştirimcilerin aksine kullanıcılar ayrıntılarla ilgilenmeyecek, sunulan işlevselliğin <u>nasıl</u> realize edildiğine değil, <u>ne</u> olduğuna bakacaktır.<br />
<br />
Ne demek istediğimizi, <code>Matris</code> sınıfının içini matematikteki matris kavramının özellikleriyle doldurarak gösterelim. Ama ilk önce, nereden başlamamız gerektiği konusunda bir uzlaşıya varalım. Bu noktada, acemi arkadaşlar ve C/C++ gibi dillerden gelip bellek serilimleri ile oynamaya alışmış ustalara bir uyarı: soyut veri türü tanımlamaya türün yapısını belirleyen bellek seriliminden başlamak doğru olmaz! Mesela, aceleyle atılıp matris elemanlarını tutmak için iki boyutlu bir dizi tanımlamaya soyunmak yerinde bir hamle olmayacaktır. Zira, matris elemanlarını tutmak için iki boyutlu dizinin yanısıra, <code>Vector</code> içeren <code>Vector</code> veya sıra no-sütun no çifti anahtarıyla erişilen kıyım tablosu da kullanılabilir. Matris içeriğinin özelliklerine göre bir seçim bazen iyiyken bazen kötü olabilir.<sup><a href="#dn2" id="ref2">2</a></sup> Ayrıca, bu konudaki bir karar değişikliği gerçekleştirimimizdeki pek çok şeyin değişmesini gerektirecektir ki, kodumuzun güvenilirlik düzeyine olumsuz etkide bulunan bu tür şeylerden kaçınılmalıdır. Bunun yerine, işimize Java karşılığını ifade etmeye çalıştığımız kavramın değişmeyen yönlerini, <i>öznitelik</i>lerini, belirleyerek başlamalıyız.<br />
<br />
Biraz programlama yapmış olanlarınız, koda dökülmekte olan kavrama ait varlıkların belirleyici özellikleri olan özniteliklerin, aslında altalanlara verilen yeni bir ad olduğunu düşünebilir. Bu yanılgıyı, altalanların sınıf şablonu kullanılarak yaratılacak nesnelerin özel belleğini oluştururken, özniteliklerin diğer özniteliklerdan yararlanarak hesaplanmak suretiyle de temsil edilebileceğini vurgulayarak ortadan kaldıralım. Örnek olarak, bir üçgenin özniteliklerini düşünün. Kenar uzunlukları ve kenarlar arasındaki açılar, değil mi? Peki ama, bu altı özniteliğin altısı da üçgen kavramının karşılığındaki sınıfta altalan olarak karşılık bulmalı mı? Tabii ki hayır! İstenecek olursa, <a href="http://en.wikipedia.org/wiki/Law_of_sines">Sinüs kuralı</a>ndan yararlanarak işimizi dört altalanla da görebiliriz. Dolayısıyla, yapacağımız şey, her bir öznitelik için altalan tanımlamak değil, altalanların tanımını sonraya bırakarak öznitelik değerlerini döndüren/güncelleyen <i>erişici</i>/<i>değiştirici</i> metotları sağlamak olmalıdır.<br />
<pre class="brush:java;gutter:false" name="sınıfÖrneği2">public class Matris {
public int sıraSayısı() { ??? }
public int sütunSayısı() { ??? }
public double eleman(int sıra, int sütun) { ??? }
public void elemanGüncelle(int sıra, int sütun, double yeniDeğer) { ??? }
...
} // Matris sınıfının sonu</pre>Kıs kıs güldüğünüzü görür gibi oluyorum. Ne de olsa, yukarıdaki metotların gövdesini sağlayabilmemiz için ne çeşit bir kap kullanacağımıza ve altalanlarımızın hangi öznitelikler olacağına karar vermemiz gerekiyor. Doğru ama, ben biraz sorumsuzluk göstererek bu itirazınızı umursamayacağım ve sanki çok güvendiğim birileri metot gövdelerini sağlamış gibi davranacağım. Bazen, hayal görmek de işe yarayabilir. Kabul ettiyseniz bir sonraki adıma geçelim: matrisler üzerinde uygulanabilecek işlemleri tanımlamaya. Ancak, ilk önce metot imzalarının yanlış olduğunu düşünenlerin kafalarındaki soruya yanıt verelim: Hayır, imzaların parametrelerinden biri eksik değil. Tanımlanan tüm işlemler bir matris nesnesi üzerinde etki yapacağı için, bu ortak nesnenin tüm metotlara saklı argüman olarak geçirildiği varsayılıyor. Yani, tüm metotlar işlerini <i>ileti alıcı</i> veya <i>hedef nesne</i> olarak da adlandırılan özel bir matris nesnesi bağlamında yapıyor.<br />
<pre class="brush:java;gutter:false" name="sınıfÖrneği3">public class Matris {
...
public Matris çarp(Matris sağMatris) { ... }
public Matris çarp(double skalar) { ... }
public Matris çıkar(Matris sağMatris) { ... }
public Matris ters() { ... }
public Matris topla(Matris sağMatris) { ... }
// diğer işlemler
...
} // Matris sınıfının sonu</pre>Dikkatinizi çekmiştir, sınıf tanımımız <code>çarp</code> adında iki metot içeriyor ve metot adları matematikten alışageldiğimiz +, - ve * gibi evrensel işleçler arasından seçilmemiş. Bu, Java'nın metot <i>aşırı yükleme</i>yi desteklerken işleç aşırı yüklemeyi desteklemediği anlamına gelir. Metot aşırı yükleme bağlamında, farklı imzalara sahip iki veya daha fazla sayıdaki metot aynı aduzayında yer alabilir. Dolayısıyla, metot adı ve parametre listesinden birisinin farklı olması metotların birlikte tanımlanmasına olanak tanıyacaktır. Parametre sayısının veya karşılıklı parametre türlerinin farklı olması, metot imzalarının farklı olması anlamına geleceği için, örneğimizdeki aynı adlı iki metodun aynı sınıfta tanımlanmaları bir sakınca doğurmayacaktır.<sup><a href="#dn3" id="ref3">3</a></sup><br />
<br />
Her şey yolunda gidip de sınıfımızı tamamladıktan sonra, böylesine bir sınıfın kullanımı aşağıda verilen örnekteki gibi olacaktır. <code>main</code> metodunun gövdesinde <code><b>new</b></code> işleci kullanılarak yaratılan 3x5'lik iki matrisin toplamı, <code>m1</code>'e, kendisini <code>m2</code> ile toplamasını sağlayan <code>topla</code> iletisinin gönderilmesiyle elde ediliyor. İleti alıcı rolünü oynayan <code>m1</code>, iletinin gönderilmesi sonrasında çağrılan <code>Matris</code> sınıfındaki aynı adlı metodun çağrılması sırasında saklı argüman olarak geçirilirken <code>m2</code> sıradan bir argüman olarak geçiriliyor.<br />
<pre class="brush:java;gutter:false" name="nesneKullanımı">...
public class MatrisKullanıcı {
public static void main(String[] ksa) {
Matris m1 = new Matris(3, 5);
Matris m2 = new Matris(3, 5);
// matrisleri doldur...
Matris m3 = m1.topla(m2);
...
} // void main(String[]) sonu
} // MatrisKullanıcı sınıfının sonu</pre>Gelelim eksiklerimizi tamamlamaya. Son adımda listelediğimiz işlemlere karşılık gelen metotlar ile başlayalım. Nasıl ilerlememiz gerektiğine <code>topla</code> adlı metodun gerçekleştirimini vererek açıklık getirelim. Bu amaçla sağladığımız aşağıdaki koda dikkat edecek olursanız, metot gövdesinde altalan ve matris içeriğini tutan kaba herhangi bir atıf yok. Her şey, gerçekleştirim ayrıntılarını saklayan erişici/değiştirici metotlar vasıtasıyla yapılıyor. Dolayısıyla, erişici/değiştirici metotların gövdesini sağladığımız an bu metot gerçekleştirimi de işlevsellik kazanmış olacak. Böylesine bir yaklaşımın artısı, gerçekleştirdiğimiz algoritmayı soyut bir biçimde ifade etmemizi olanaklı kılması ve kodun doğruluğu hakkında daha kolay ikna olmamızı sağlamasıdır.<br />
<br />
Kodda yabancı gelebilecek bir diğer öğe, <code><b>this</b></code> anahtar sözcüğünün kullanılışı. Özel bir tutacak adı olan <code><b>this</b></code>, metoda saklı argüman olarak geçirilen hedef nesneye atıfta bulunmak için kullanılır. Mesela, <code>m1.topla(m2)</code> şeklinde ifade edilen gönderi, ileti alıcı konumundaki <code>m1</code>'in <code><b>this</b></code> ile eşleştirilmesine neden olacaktır.<br />
<br />
Hatalı gibi gözükebilecek bir diğer nokta, ileti alıcının metot gövdesinde kullanılmasının, ad çakışması sonucu derleme hatasının ortaya çıkacağı durumlar dışında, seçimlik olmasıdır. Yani, istenecek olursa <code><b>this</b></code> anahtar sözcüğünün kullanımı es geçilebilir. Bundan dolayıdır ki, <code>m</code> adlı yerel değişkenin ilklenmesinde kullanılan ifade hatalı değildir ve <code><b>this</b>.sütunSayısı()</code> ile eşdeğerdir.<br />
<pre class="brush:java;gutter:false" name="metotGövdesi">public class Matris {
...
public Matris topla(Matris sağM) {
int n = this.sıraSayısı(), m = sütunSayısı()
if (n != sağM.sıraSayısı() ||
m != sağM.sütunSayısı()) return null;
Matris sonuç = new Matris(n, m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
sonuç.elemanGüncelle(i, j, eleman(i, j) + sağM.eleman(i, j));
return sonuç;
} // Matris topla(Matris) sonu
...
} // Matris sınıfının sonu</pre>Her şey bitti gibi. Ama durun daha nesnemizin yapısına, yani altalanların ne olacağına karar vermedik. İş böyle olunca, nesnelerimizi yaratırken nesne içeriklerinin ilklenmesi için çağrılacak olan <i>yapıcı</i> metotları da yazamadık. O zaman gelin, düşündük taşındık, iki boyutlu bir dizide karar kıldık diyelim ve bu eksikliği de tamamlayalım. <br />
<pre class="brush:java;gutter:false" name="altalanlar">public class Matris {
public Matris(int sıraSayısı, int sütunSayısı) {
_kap = new double[sıraSayısı][sütunSayısı];
} // yapıcı(int, int)
public Matris(int sıraSayısı, int sütunSayısı, double ilkDeğer) {
this(sıraSayısı, sütunSayısı);
for (int i = 0; i <= sıraSayısı; i++)
java.util.Arrays.fill(_kap[i], ilkDeğer]);
} // yapıcı(int, int, double)
public int satırSayısı() { return _kap.length; }
public int sütunSayısı() { return _kap[0].length; }
public double eleman(int i, int j) {
return _kap[i - 1][j - 1];
} // double eleman(int, int)
public double elemanGüncelle(int i, int j, double yeniDeğer) {
_kap[i - 1][j - 1] = yeniDeğer;
} // double eleman(int, int)
...
private double[][] _kap;
} // Matris sınıfının sonu</pre>Kod parçasına açıklık getirmeye, hatalı gibi gözükebilecek yapıcı metotlar ile başlayalım. Nesne yaratılması noktasında, yerin ayrılmasını takiben nesnenin tutarlı bir ilk duruma getirilmesini sağlamak görevini gören ve sınıf ile aynı ada sahip yapıcıların dönüş türünün belirtilmesine gerek yoktur.<sup><a href="#dn4" id="ref4">4</a>, </sup><sup><a href="#dn5" id="ref5">5</a></sup> <code><b>new</b></code> işlecinin kullanılması bağlamında arka planda çağrılan yapıcılar, programcı tarafından doğrudan çağrılmaz. Dikkat çekici ikinci nokta, üç argümanlı yapıcımızın ilk satırı. İleti adı içermeyen bu kullanım, bir yapıcı içinden aynı sınıfın bir diğer yapıcısını çağırmak için kullanılır ve içinde bulunduğu yapıcının birinci komutu olmak zorundadır. Aksi bir durum, derleyici tarafından hata olarak görülecektir. Değinilmesi gereken bir diğer nokta, altalanımızın erişim niteleyicisinin <code><b>private</b></code> olması. Bu, altalanın sınıfa özel olduğu ve sadece içinde bulunduğu sınıfın diğer öğeleri tarafından kullanılabileceği anlamını taşır. Nesne yapısını dişarıdan soyutlayan böylesine bir kısıtlamanın sebebini anlamak için altalanın <code><b>public</b></code> varsayıldığı aşağıdaki örneği ele alalım. <br />
<pre class="brush:java;gutter:false" name="nesneKullanımı2">...
public class MatrisKullanıcı {
public static void main(String[] ksa) {
Matris m1 = new Matris(3, 5);
// matrisi doldur...
Matris m2 = new Matris(3, 5);
for (int i = 0; i <= m1.length; i++)
for (int j = 0; j <= m1[0].length; j++)
m2[i][j] = m1[i][j] * 7;
...
} // void main(String[]) sonu
} // MatrisKullanıcı sınıfının sonu</pre>Matrisi temsil etmek için iki boyutlu bir dizinin kullanıldığını kabul eden kod parçası, <code>m1</code> matrisini 7 ile çarpmak için bu ön kabulü kullanmaktadır. Ancak, belki de marjinal bir hız artışı saplantısıyla yazılan bu kod, gerçekleştirimci ile kullanıcı arasında bir bağımlılık oluşturarak aslında zarar vermektedir. Zira, gerçekleştirimcinin matris temsilini daha önceden bahsettiğimiz alternatiflerden birine değiştirmesi, kullanıcı kodunun da—tek gerçekleştirimci kodu varken binlerce kullanıcı kodu olabileceğini unutmayın—değişmesi zorunluluğunu doğuracaktır. Bir başka deyişle, işlevsellik değişmediği halde gerçekleştirimcinin yaptığı bir değişiklik pek çok kullanıcıyı etkileyecektir. Önlenmesi gereken bu felaket senaryosu, kullanıcıya <u>ne</u> sorusunun yanıtını veren ve nadiren değişen öğelerin sunulması, değişme ihtimali bulunan ve <u>nasıl</u> sorusunun yanıtını veren altalanlar ve yardımcı metotlara erişimin kısıtlanması ile mümkün olur.<sup><a href="#dn6" id="ref6">6</a></sup><br />
<sup> </sup> </div><div style="text-align: right;"><a alt="Java'da diziler" href="http://ta-java.blogspot.com/2011/04/bileske-turler-diziler.html">Diziler</a><br />
<a alt="Java'da nesne ilkleme" href="http://ta-java.blogspot.com/2011/05/nesnelerin-ilklenmesi.html">Nesne İlkleme</a><br />
<a alt="Tüm Java sınıflarına gerekebilecek metotlar" href="http://ta-java.blogspot.com/2011/05/her-snfa-lazm-metotlar.html">Bazı Önemli Metotlar</a><br />
<a alt="Java'da paketler ve sınıf yolu" href="http://ta-java.blogspot.com/2011/07/paketler-ve-snf-yolu.html">Paketler ve Sınıf Yolu</a><br />
<a alt="Java'da kalıtlama" href="http://ta-java.blogspot.com/2011/08/turlerin-evrimi-ya-da-kaltlama.html">Kalıtlama</a> </div><hr /><div id="dipnotlar" style="text-align: justify;"><ol><li id="dn1">Yazımızın sınıf kavramını tanıtmaya yönelik olduğu ve anlamayı kolaylaştırmak adına paketler, ayrıksı durumlar gibi daha sonraki yazılarda anlatılacak bazı şeyleri es geçtiği söylenmeli. <a href="#ref1">↑</a></li>
<li id="dn2">Örneğin, genelde küçük matrislerle uğraşacaksak, yerden maliyetli fakat hızlı işlemi olanaklı kılan bir veri yapısı seçilebilir. Ya da, 0 değerinin çok geçtiği seyrek matrislerde 0 değerlerini doğrudan tutmaktansa dolaylı yollardan belirtmeyi tercih edebiliriz.... <a href="#ref2">↑</a></li>
<li id="dn3">Dönüş türü bilgisi aşırı yüklemenin geçerliliğinin denetiminde kullanılmaz. Buna göre, sınıfımıza <code><b>double</b></code> argüman alıp <code><b>double</b></code> döndüren <code>çarp</code> adlı bir metodun eklenmesi hataya neden olacaktır. Böylesine bir kuralın var oluş sebebi, metot dönüş değerinin göz ardı edilmesi opsiyonundan kaynaklanır. Dönüş değerinin göz ardı edilmesi durumunda hangi dönüş türlü metodun kastedildiği bilinemeyeceği için, derleyici ortaya çıkan muallak durumu hata olarak bildirecektir. <a href="#ref3">↑</a></li>
<li id="dn4">Tüm altalanların ilklenmesine gerek yoktur. Bazılarının ilklenmemesi durumunda, Java derleyicisi söz konusu altalanların varsayılan bir ilkdeğere sahip olmasını garanti edecektir. Tamsayılar için 0, kayan noktalı sayılar için 0.0 ve mantıksal değerler için <code><b>false</b></code> olan varsayılan ilkdeğer, bileşke türler için tutacağın bir nesneyi göstermediğini belirten <code><b>null</b></code> olacaktır. <a href="#ref4">↑</a></li>
<li id="dn5">Bir sınıftaki yapıcı sayısı ihtiyaca göre değişebilir. Gerekli gördüğü takdirde, programcı yapıcı yazmamayı da tercih edebilir. Bu durumda, Java derleyicisi tüm altalanlara uygun ilkdeğerler sağlayan bir argümansız yapıcı sentezleyecektir. Ancak, yapıcı sentezlemenin programcı tarafından sağlanan yapıcıların varlığında söz konusu olmayacağı akılda tutulmalıdır. <a href="#ref5">↑</a></li>
<li id="dn6">Sınıf gerçekleştiriminin bitmesi, her şeyin bittiği anlamına gelmez. Kodun kullanıcılarla buluşması öncesinde, son bir aşama olarak, performansı düşüren noktalarda eniyilemeler (İng., optimization) yapılması düşünülebilir. <a href="#ref6">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-22822217354141173032011-04-20T18:11:00.009+03:002012-01-21T01:01:27.548+02:00Bileşke Türler-Diziler<div id="anaMetin" style="text-align: justify;">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.<br />
<br />
Ö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<a alt="Java'da ilkel türler" href="http://ta-java.blogspot.com/2011/03/ilkel-turler-tamsaylar.html">🔎</a> ü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] <i>tür işleci</i>yle (İng., type operator) oluşturulan <i>bileşke türler</i>e, <i>dizi</i>lere göz atacağız.<sup><a href="#dn1" id="ref1">1</a></sup> 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.<br />
<br />
<div style="color: #444444; text-align: center;">TutacakVeNesne.java</div><pre class="brush:java" name="tutacakveNesne">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</pre><pre class="brush:bash;gutter:false" name="code">$ javac -encoding utf-8 TutacakVeNesne.java
$ java TutacakVeNesne
false</pre><br />
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—<code><b>false</b></code> yazacaktır. Bunun sebebi 6.satıra gelinmesiyle oluşan bellek seriliminin aşağıdaki <u>temsili</u> 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.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXhS3HmN5AyqExI6bEKuEJEUPAZUdUGUCcFYL4mGb-cu1LJYYAVplh6mPFqymOECbc_h9TQn4yoFiNiVdENxJ8hIVUhyy7IkTEyeKdJ0XZesrXjLh8mt6Tb0QCgGxX-Loces1w8QXQwYkS/s1600/TutacakVeNesne.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="124" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXhS3HmN5AyqExI6bEKuEJEUPAZUdUGUCcFYL4mGb-cu1LJYYAVplh6mPFqymOECbc_h9TQn4yoFiNiVdENxJ8hIVUhyy7IkTEyeKdJ0XZesrXjLh8mt6Tb0QCgGxX-Loces1w8QXQwYkS/s400/TutacakVeNesne.png" width="400" /></a></div><br />
Nesne yaratmak için kullanılan <code><b>new</b></code> işleci, belleğin <i>yığın</i> bölgesinde yeri ayrılan nesnenin işlenebilmesi için bir <i>tutacak</i><sup><a href="#dn2" id="ref2">2</a></sup> 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ı <code>ad1</code> ve <code>ad2</code> değişkenlerini ilklemek için kullanılmaktayken, <code>ad1</code>'in <code>ad3</code>'ü ilklemekte kullanılmasıyla iki tutacak<sup><a href="#dn3" id="ref3">3</a></sup> 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 <code><b>false</b></code> 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, <code>ad1</code> tutacağına temsil ettiği nesneyi <code>ad2</code> tutacağının temsil ettiği nesne ile karşılaştırmasını söyleyen <code>equals</code> iletisinin gönderilmesi gerekir. Aşağıdaki gibi yapılacak ileti gönderiminin sonrasında, <code>ad1</code>'in arkasındaki nesnenin türü olan <code>String</code> sınıfındaki <code>equals</code> metodu çağrılacak ve işlem tamamlanacaktır.<br />
<pre class="brush:java;gutter:false" name="iletiGönderme">System.out.println(ad1.equals(ad2));</pre>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.<sup><a href="#dn4" id="ref4">4</a></sup> Tutacak türü ile uyumlu olmayan ileti gönderimleri derleyici tarafından reddedilecektir.<br />
<br />
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 <i>dizi nesnesi</i>nin 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.<br />
<br />
<div style="color: #444444; text-align: center;">Diziler.java</div><pre class="brush:java" name="diziler">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
</pre>Dizi nesnelerimizin yaratıldığı satırları ele alalım. 7 nolu satırdaki ilkleme komutunda <i>dizi tutacağı</i>nın adı olan <code>notlar</code>, <code><b>int</b>[]</code> 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 <code><b>int</b>[]</code> türlü ilan ediliyor; her iki durumda da eleman sayısına dair bilgi tanımda yer almıyor. Diğer bir deyişle, <code>notlar</code> ve <code>yeniNotlar</code> tutacaklarının ikisi de aynı türe sahip olacak şekilde tanımlanıyor.<br />
<br />
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 (<code>[</code> ve <code>]</code>) 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 <i>indis</i> 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, <code>notlar</code> dizisinin son elemanına aşağıdaki ifadeler ile erişilebilir. Opsiyonları anlamaya çalışırken, her dizi nesnesinde bulunan <code>length</code> adlı altalanın dizinin eleman sayısını tuttuğunu unutmayın. <br />
<pre class="brush:java;gutter:false" name="indisDeğeri">notlar[2] = 333; // notlar[3] değil!!!
notlar[notlar.length - 1] = 333; // Daha iyi bir seçenek.</pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd46c0S7xNSZ2abr-Bu13jQ9ZJgdi5_-jot0uhB4bWsTGxePregZjG4xvwimnsjOPF50LeLE0IwDRTxL2xDBRUfrUXTtDmgEF6b87RfQz-kJxbTI8EuCt1OOK-urmu_SSHgeMm3ketkz1x/s1600/DiziSerilim.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="201" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd46c0S7xNSZ2abr-Bu13jQ9ZJgdi5_-jot0uhB4bWsTGxePregZjG4xvwimnsjOPF50LeLE0IwDRTxL2xDBRUfrUXTtDmgEF6b87RfQz-kJxbTI8EuCt1OOK-urmu_SSHgeMm3ketkz1x/s400/DiziSerilim.png" width="375" /></a></div>Belirlendikten sonra değişmeyen eleman sayısı, <code>yenitNotlar</code> 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.<br />
<br />
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 <i>toptan ilkleme</i> 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ığı <code><b>new</b></code> 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. <br />
<pre class="brush:java;gutter:false" name="toptanİlkleme">int[] notlar = new int[]{60, 70, 80};
int[] yeniNotlar =
new int[]{grdKnl.nextInt(), grdKnl.nextInt()};
</pre>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 <code>notlar</code> tutacağının <code>yeniNotlar</code>'a atanması, <code>yeniNotlar</code>'ı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. <br />
<pre class="brush:java;gutter:false" name="diziAtama">yeniNotlar = notlar;
yeniNotlar[1] = notlar[1] + 5;
</pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA7T-7wbKg6bPic4sT7NhSf-VRHAR7u4O6IiQlirRxPf1w81LXOvN1GH3YUaXoNbKbPZZkPF675z8TNIFYxL5sV-RczzBzZfId-NjMvHmIcPQ87w6FAN-04cj9Zz0B4gI1kknRjtv_I_Zl/s1600/DiziPayla%25C5%259Fma.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA7T-7wbKg6bPic4sT7NhSf-VRHAR7u4O6IiQlirRxPf1w81LXOvN1GH3YUaXoNbKbPZZkPF675z8TNIFYxL5sV-RczzBzZfId-NjMvHmIcPQ87w6FAN-04cj9Zz0B4gI1kknRjtv_I_Zl/s400/DiziPayla%25C5%259Fma.png" width="225" /></a></div>
<div style="text-align:left"><a alt="Java'da ilkel türler" href="http://ta-java.blogspot.com/2011/03/ilkel-turler-tamsaylar.html">İlkel Türler</a></div>
<div class="separator" style="clear: both; text-align: right;"><a alt="Java'da dizi metotları" href="http://ta-java.blogspot.com/2011/08/baz-onemli-snflar-javautilarrays.html">Dizi Metotları</a></div>
<div class="separator" style="clear: both; text-align: right;"><a alt="Java'da sınıf tanımı" href="http://ta-java.blogspot.com/2011/05/bileske-turler-snflar.html">Sınıf Tanımlama Reçetesi</a></div>
<div class="separator" style="clear: both; text-align: right;"><a alt="Java'da değişken uzunluklu diziler-java.util.Vector" href="http://ta-java.blogspot.com/2011/09/degisken-uzunluklu-diziler.html">Değişken Uzunluklu Diziler</a> </div></div><hr /><div id="dipnotlar" style="text-align: justify;"><ol><li id="dn1">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. <a href="#ref1">↑</a></li>
<li id="dn2">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, <i>hendıl</i> (İ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.<br />
<br />
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. <a href="#ref2">↑</a></li>
<li id="dn3">Bundan sonraki anlatımımızda, "... nesnesinin tutacağına sahip tanımlayıcı" demektense, tutacak demeyi tercih edeceğiz. <a href="#ref3">↑</a></li>
<li id="dn4">Nesne paradigmasına sadık kalınarak yapılan bu anlatım her zaman geçerli olmayacaktır. <code><b>static</b></code> 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. <a href="#ref4">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-32941699833037287352011-04-15T11:31:00.003+03:002011-12-14T16:29:38.887+02:00boolean Türü ve İlkel Türler Arasında Tür Uyumluluğu<div id="anaMetin" style="text-align: justify;">İ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 <code><b>boolean</b></code> türüne baktıktan sonra ilkel türden değerlerin birbirleri yerine kullanılabilme özelliklerine göz atacağız. <br />
<br />
<h3><code><b>boolean</b></code> Türü</h3>Koşullu ve yinelemeli komut işlemenin temeli olan doğruluk denetimi, <b>boolean</b> türlü sabitler olan <code><b>true</b></code> veya <code><b>false</b></code> değerlerinden birini üreten <i>ilişkisel işleçler</i>in<sup><a href="#dn1" id="ref1">1</a></sup> kullanılmasıyla yapılır. Denetlenmesi istenen koşulun karmaşıklaşması durumunda ilişkisel işleçli ifadeler <i>mantıksal işleçler</i><sup><a href="#dn2" id="ref2">2</a></sup> kullanılarak birleştirilebilir.<sup><a href="#dn3" id="ref3">3</a></sup> Bir örnekle görelim.<br />
<br />
<div style="color: #444444; text-align: center;">Faktöryel.java</div><pre class="brush:java" name="faktöryel">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</pre><br />
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 <code>Scanner</code> türündeki <code>grdKnl</code> kullanılarak alınan tamsayı değerin faktöryelini hesaplıyor. Bu amaçla yazılmış olan <i>özyinelemeli</i> <code>fakt</code> metodunun sonsuz döngüye girmesini engellemekte kullanılan koşul deyimi, eşitlik denetleme işleci (<code>==</code>) ile oluşturulan iki altdeyimin veya bağlacı (<code>||</code>) 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.<br />
<br />
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 (<code>NULL</code>, 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.<br />
<br />
<pre class="brush:java;gutter:false" name="CUsulüKoşul">if (n % 2)
System.out.println(n + " çift değil");
else System.out.println(n + " çift");</pre><br />
<h3>İlkel Türler Arasında Tür Uyumluluğu</h3>C/C++ dünyasından aşina son satırların Java derleyicisi tarafından kabul görmemesi, tamsayıların <code><b>boolean</b></code> 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.<br />
<br />
Tamsayıların mantıksal değerlerin yerine kullanılamamasına benzer bir şekilde, <code><b>byte</b></code> türlü bir tanımlayıcıya <code><b>int</b></code> türlü bir değer sağlanması da hata olarak görülecektir. Bunun sebebi, <code><b>int</b></code> türünün temsil aralığının <code><b>byte</b></code> türünün temsil aralığında bulunmayan değerler içermesidir. Mesela, yukarıda verilen <code>Faktöryel.fakt</code> metodunun parametre türü <code><b>byte</b></code> olacak biçimde değiştirilmesi hataya neden olacaktır. Çünkü, söz konusu metodun çağrıldığı satırda geçirilen argüman <code><b>int</b></code> türlüdür ve -2<sup>31</sup> ile 2<sup>31</sup>-1 arasındaki herhangi bir değere sahip olabilir; buna karşılık, argümanın değeriyle ilklenecek parametre <code><b>byte</b></code> türlüdür ve -128 ile 127 arasındaki değerleri temsil edebilir. İşte bu yüzdendir ki, parametre türünün <code><b>byte</b></code> olarak değiştirilmesi durumunda geçirilen argüman değerinin aşağıdaki kod parçasında olduğu gibi <i>biçimlendirilmesi</i> gerekir.<br />
<br />
<pre class="brush:java" name="code">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</pre><br />
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.<br />
<br />
<div style="text-align: center;"><code><b>double</b></code> ⊃ <code><b>float</b></code> ⊃ <code><b>long</b></code> ⊃ <code><b>int</b></code> ⊃ <code><b>short</b></code> ⊃ <code><b>byte</b></code><br />
<code><b>int</b></code> ⊃ <code><b>char</b></code><br />
<code><b>double</b></code> ∩ <code><b>boolean</b></code> = ∅ </div><br />
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.<sup><a href="#dn4" id="ref4">4</a></sup> Buna göre, <code><b>double</b></code> değerin beklendiği yerlerde diğer tüm sayısal türlerden değerler kullanılabilir. Buna karşılık, <code><b>short</b></code> değerin beklendiği yerlerde sadece <code><b>byte</b></code> türlü değerler kulanılabilirken, diğer tüm sayısal değerler ancak biçimlendirme yapılmak suretiyle kullanılabilecektir.<br />
<br />
Dizilimde <code><b>char</b></code> 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 <code><b>short</b></code> -32768 ile 32767 aralığındaki değerleri temsil edebilirken, <code><b>char</b></code> 0 ile 65536 arasındaki değerleri temsil edebilir. Bir diğer deyişle, <code><b>short</b></code>'un <code><b>char</b></code>, <code><b>char</b></code>'ın <code><b>short</b></code> yerine konulması kimi zaman değer kaybına sebep olacaktır.</div><br/><div style="text-align: right;"><a alt="Java'da ilkel tamsayı türleri" href="http://ta-java.blogspot.com/2011/03/ilkel-turler-tamsaylar.html">İlkel Tamsayı Türleri</a><br />
<a alt="Java'da karakter türü: char" href="http://ta-java.blogspot.com/2011/03/ilkel-turler-karakterler_30.html">Karakterler</a><br />
<a alt="Java'da kayan noktalı sayı türleri" href="http://ta-java.blogspot.com/2011/04/ilkel-turler-kayan-noktal-saylar.html">Kayan Noktalı Sayılar</a><br />
</div><hr /><div id="dipnotlar" style="text-align: justify;"><ol><li id="dn1">Yararlanılabilecek ilişkisel işleçler ve varlığı denetlenen ilişki çeşidi şunlardır: <code>==</code> (eşitlik), <code>!=</code> (eşitsizlik), <code>></code> (büyüklük), <code><</code> (küçüklük), <code>>=</code> (büyük veya eşitlik), <code><</code> (küçük veya eşitlik). <a href="#ref1">↑</a></li>
<li id="dn2">Mantıksal değerleri birleştirmede kullanılabilecek bağlaçlar ve anlamları şunlardır: <code>!</code> (değilleme), <code>&&</code> (ve), <code>||</code> (veya). <a href="#ref2">↑</a></li>
<li id="dn3">Gerekli görüldüğü takdirde, denetleme işinin tümü veya bir bölümü, <i>yüklem</i> olarak da adlandırılan <code><b>boolean</b></code> dönüş türlü bir metodun çağrılması ile yapılabilir. <a href="#ref3">↑</a></li>
<li id="dn4">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ü, <code><b>long</b></code> ile temsil edilen kimi değerler ne <code><b>double</b></code> ne de <code><b>float</b></code> ile temsil edilebilirken, <code><b>int</b></code> ile temsil edilen kimi değerler <code><b>float</b></code> ile temsil edilemez. Ancak, anılan türler arasında dizilimin önerdiği cinsten bir tür uyumluluğundan bahsetmek her zaman doğrudur. <a href="#ref4">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-79696097994451806562011-04-13T19:43:00.002+03:002011-12-14T16:31:07.769+02:00Java SE 7 İle Gelen Yenilikler<div id="anaMetin" style="text-align: justify;">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<a alt="Java'da soysallık" href="http://ta-java.blogspot.com/2011/11/soysallk.html">🔎</a> (İ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.<sup><a href="#dn1" id="ref1">1</a></sup><br />
<br />
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.<sup><a href="#dn2" id="ref2">2</a></sup><br />
<br />
<ul><li><a href="#yenilik1"><code><b>switch-case</b></code> Komutlarında String Türlü Seçici İfade</a></li>
<li><a href="#yenilik2">İkili Taban Gösterimi ve Rakamların Gruplanması</a></li>
<li><a href="#yenilik3">Kaynak Yönetimli <code><b>try</b></code> Bloğu</a></li>
<li><a href="#yenilik4">Kotarıcı Paylaşımı ve Yeniden Fırlatmanın Yeni Anlamı</a></li>
<li><a href="#yenilik5">Sınırlı Tür Çıkarsama: Elmas İşleci</a></li>
<li><a href="#yenilik6"><code>SafeVarargs</code> Açımlaması</a></li>
</ul><br />
<h3 id="yenilik1"><code><b>switch-case</b></code> Komutlarında String Türlü Seçici İfade</h3><code><b>switch-case</b></code> komutlarındaki seçici ifadenin türü hakkındaki kısıtlamanın <code>String</code> türünü kapsayacak şekilde gevşetilmesi ile başlayalım. Buna göre, eskiden seçici ifadenin tamsayı türleri ve <code>char</code>'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.<br />
<pre class="brush:java;gutter:false" id="switchCase7">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);
...
</pre><br />
<h3 id="yenilik2">İkili Taban Gösterimi ve Rakamların Gruplanması</h3>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 (<code>'_'</code>) kullanılarak dilendiği gibi gruplanması da mümkün.<br />
<pre class="brush:java;gutter:false" id="switchCase7">double bütçeAçığı = 8_000_000_000_000_000;
double piSayısı = 3.1415_92_6535_897_93;
short bayrakYazmacı = 0b0000_010010_000100;
...
</pre>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.<br />
<pre class="brush:java;gutter:false" id="switchCase7">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_;
...
</pre><br />
<h3 id="yenilik3">Kaynak Yönetimli <code><b>try</b></code> Bloğu</h3>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.<sup><a href="#dn3" id="ref3">3</a></sup> 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 <u>geliştirici tarafından</u> 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, <code><b>try</b></code> 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 <code><b>try</b></code> bloğunu sunmaktadır.<br />
<br />
Ne denmek istendiğini aşağıdaki kod şablonundan izleyerek görelim. İlk satırda, <code><b>try</b></code>'ı takip eden ayraç çifti arasındaki ilkleme komutu, Java derleyicisine program içinde <code>d</code> 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, <code><b>try</b></code> bloğu sonunda söz konusu kaynağın geri döndürüleceği anlamına gelir. <br />
<pre class="brush:java;gutter:false" id="tryCatch7">try (FileInputStream d = new FileInputStream("Veri.dat")) {
// d'yi kullan.
} catch (IOException e) { ... }
</pre>Örneğimiz otomatik olarak yönetilmesi istenen kaynağın Java SE 7 ile birlikte eklenen <code>java.lang.AutoCloseable</code> arayüzünü gerçekleştirmesiyle geçerlilik kazanır. Bu, <code>java.lang.Closeable</code> 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 <code><b>try</b></code> bloğuyla otomatik olarak yönetilebileceği anlamına gelir.<br />
<br />
<code><b>finally</b></code> kotarıcısını (İng., handler) bilenleriniz—sabırsızlıklarını görür gibiyim—kaynak yönetimli <code><b>try</b></code> 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 <code><b>finally</b></code> bloğu içine yazarak da sağlanabilir. Bir diğer deyişle, aşağıdaki kod şablonu da işimizi görecektir.<br />
<pre class="brush:java;gutter:false" id="kotarıcıPaylaşımı">try {
// d'yi kullan.
} catch (IOException e ) { ... }
finally { d.close(); }
</pre>Bu itirazı seslendiren arkadaşlar bir yere kadar haklı. Ne var ki, şu iki nokta onları da ikna edecektir.<br />
<ul><li><code><b>try</b></code> ve <code><b>catch</b></code> 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, <code><b>finally</b></code> bloğunun işletileceği garantisi verilemez.</li>
<li>Kaynak yönetimli <code><b>try</b></code> komutunun varlığı, programcıya hatırlatıcı olmasının yanısıra belgeleme yönüyle de katkıda bulunur.</li>
</ul><br />
<h3 id="yenilik4">Kotarıcı Paylaşımı ve Yeniden Fırlatmanın Yeni Anlamı</h3>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 <code><b>try</b></code> bloğu içindeki komutların icra edilmesi sırasında <code>AyrıksıDurumA</code> veya <code>AyrıksıDurumB</code> türlü bir ayrıksı durum ortaya çıkacak olursa, aynı kotarıcı soruna çözüm bulmaya çalışacaktır. <br />
<pre class="brush:java;gutter:false" id="kotarıcıPaylaşma">...
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 { ... }
...
</pre>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 <code>e</code>'nin kullanımı <code>AyrıksıDurumA</code> ve <code>AyrıksıDurumB</code> 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.<sup><a href="#dn4" id="ref4">4</a></sup><br />
<br />
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.<br />
<pre class="brush:java;gutter:false" id="yenidenFırlatma">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
</pre>İşlerin yolunda gitmemesi halinde, <code>birMetot</code> metodundaki <code>try</code> bloğu, <code>AyrıksıDurumA</code> veya <code>AyrıksıDurumB</code> türlü bir ayrıksı durumla sonlanabilir. Ayrıksı durum nesnesinin fırlatılması sonrasında, <code>Exception</code> 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 <code>try</code> bloğu içinden çağrılan metotların imzalarını incelemesi sonucunda kotarıcıda yeniden fırlatılan nesnenin <code>Exception</code> değil de <code>AyrıksıDurumA</code> veya <code>AyrıksıDurumB</code> türünde olabileceği bilgisi korunuyor. Bu nedenledir ki, önceki uyarlamalarda <code>Exception</code> fırlatabileceği ilan edilmek zorunda kalınan <code>birMetot</code>, artık <code>AyrıksıDurumA</code> ve <code>AyrıksıDurumB</code> fırlatabileceğini ilan eden bir imza ile tanımlanabiliyor.<br />
<br />
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 <code><b>final</b></code> 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.<br />
<br />
<h3 id="yenilik5">Sınırlı Tür Çıkarsama: Elmas İşleci</h3>Ş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ığı <code><></code> karakterlerinin görünüşünden dolayı <i>elmas işleci</i> olarak da adlandırılan bu yenilik aşağıdaki gibi kullanılabilir.<br />
<pre class="brush:java;gutter:false">import java.util.*;
...
Map<String, Vector<String>> sözlük =
new HashMap<String, Vector<String>>();
Map<String, Vector<String>> yeniSözlük = new HashMap<>();</pre><h3 id="yenilik6"><code>SafeVarargs</code> Açımlaması</h3>Göz atacağımız son yenilik olan <code>SafeVarargs</code> 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.<br />
<br />
<div style="color: #444444; text-align: center;">SVÖrneği.java</div><pre class="brush:java;" id="safeVarargs">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
</pre><pre class="brush:bash;gutter:false" name="uncheckedUyarı">$ 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
</pre><code>asList</code> 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.<br />
<br />
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 <i>tür silme</i> 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 <code>List<Integer></code> olan <code>tl1</code> ve <code>tl2</code> değişkenlerinin türü <code>List</code>'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 <code>List</code> nesneleri olarak görecektir. Bu ise, çağrılan metodun bilerek ya da bilmeyerek <code>Integer</code> 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. <br />
<br />
Örneğimizdeki uyarının ortadan kaldırılması 8. satır öncesine <code>@SuppressWarnings("unchecked")</code> 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, <code>SafeVarargs</code> açımlamasının sağladığı budur. Örneğimize uyarlayacak olursak, <code>Arrays</code> 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.<br />
<pre class="brush:java;" id="safeVarargsKullanımı">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</pre><code>Arrays.asList</code> metoduna benzer şekilde <code>SafeVarargs</code> açımlamasıyla bezenen diğer Java platformu metotları aşağıda verilmiştir.<br />
<ul><li><pre class="brush:java;gutter:false" id="safeVarargsMtt2">public static <T> boolean
java.util.Collections.addAll(
Collection<? super T> c, T... elemanlar)</pre></li>
<li><pre class="brush:java;gutter:false" id="safeVarargsMtt3">public static <E extends Enum<E>>
java.util.EnumSet<E> EnumSet.of(E ilk, E... gerisi)</pre></li>
<li><pre class="brush:java;gutter:false" id="safeVarargsMtt4">protected final void
javax.swing.SwingWorker.publish(V... parçalar)</pre></li>
</ul>Listedeki metotlardan da görülebileceği gibi, <code>SafeVarargs</code> açımlaması ile bezenmesi düşünülen metotların bazı özellikleri olması gerekir.<br />
<ul><li>Yapıcılara ek olarak, ancak <code><b>static</b></code> veya <code><b>final</b></code> metotlara <code>SafeVarargs</code> açımlaması iliştirilebilir.</li>
<li>Yapıcı veya metodun değişken sayıda argüman alıyor olması gerekir.</li>
</ul>Bu özellikleri taşımayan metotların <code>SafeVarargs</code> açımlamasıyla ilan edilmesi, derlenmekte olan sınıfın reddedilmesine neden olacaktır, haberiniz ola!<br />
</div><br />
<hr /><div id="dipnotlar" style="text-align: justify;"><ol><li id="dn1">Adı geçen üstkavramlar hakkında bilgi edinmek için, şu videoları izlemek isteyebilirsiniz: <a href="http://video.google.com/videoplay?docid=-8224544168880829029">Java birimleri</a>, <a href="http://video.google.com/videoplay?docid=4051253555018153503">kod örtüleri</a>. Bu videolar, bir sonraki uyarlama ile Java'ya giriş yapacak bu programlama kavramları ile ilgili gerekli bilgileri sağlayacaktır. <a href="#ref1">↑</a></li>
<li id="dn2">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 (<code>invokedynamic</code>) 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. <a href="#ref2">↑</a></li>
<li id="dn3">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. <a href="#ref3">↑</a></li>
<li id="dn4">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. <a href="#ref4">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0tag:blogger.com,1999:blog-7308890564400164096.post-69589587940618638702011-04-06T11:25:00.526+03:002012-01-06T02:02:50.396+02:00İlkel Türler-Kayan Noktalı Sayılar<div id="anaMetin" style="text-align: justify;">İ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<a alt="Java'da karakter türü: char" href="http://ta-java.blogspot.com/2011/03/ilkel-turler-karakterler_30.html">🔎</a> da tanıdık gelecek çoğul eki bu amaçla kullanılabilecek iki türe işaret ediyor: <code><b>float</b></code> ve <code><b>double</b></code>.<br />
<br />
<code><b>float</b></code> ve <code><b>double</b></code> 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 <a href="http://en.wikipedia.org/IEEE754">IEEE754</a> 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.<sup><a href="#dn1" id="ref1">1</a></sup></div><pre class="brush:java;gutter:false" id="kusurluTemsil">float pi = 3.14f;
float piKare = 9.8596f;
System.out.println(pi * pi == piKare); // ⇒ false
</pre><div style="text-align: justify;">Değişkenlerin ilklenmesinde kullanılan ilk değerlerin sonundaki <code>f</code>—<code>F</code> de olabilir—sabitlerin <code><b>float</b></code> türünden ele alınması gerektiğini ifade etmek için kullanılır. Bu niteleyicinin yokluğunda, sabitin türü <code><b>double</b></code> olarak hesaba katılacaktır.<sup><a href="#dn2" id="ref2">2</a></sup><br />
<br />
Kayan noktalı tanımlayıcılara değer sağlamakta kullanılan sabitler değişik şekillerde yazılabilir.<br />
<pre class="brush:java;gutter:false" name="kayanNoktalıSabitler">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))
</pre>Yukarıda verilen seçeneklerin kullanımında şu noktaların akılda tutulması yararlı olacaktır.<br />
<ul><li>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.</li>
<li>Bilimsel gösterim sayılarda ölçekleme yapılırken, çarpan olarak 16'lı tabanda 2, 10'lu tabanda ise 10 kullanılır.</li>
</ul><br />
İlk kod parçasındaki eşitlik denetiminde<a href="#kusurluTemsil">▵</a> 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ş) <a href="http://en.wikipedia.org/wiki/Scientific_notation">bilimsel gösterim</a>in temel alındığını söyleyerek başlayalım: kayan noktalı değerlerin içerikleri ∓i<sub>0</sub>.i<sub>-1</sub>i<sub>-2</sub>...x2<sup><it>n</it></sup> şeklinde yorumlanır. Dolayısıyla, normalize edilmiş bilimsel gösterimde i<sub>0</sub>'ı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.<br />
<br />
<center>∓1.i<sub>-1</sub>i<sub>-2</sub>...x2<sup><it>n</it></sup> = ∓(1 + i<sub>-1</sub>2<sup>-1</sup> + i<sub>-2</sub>2<sup>-2</sup> + ...)2<sup>n</sup></center><br />
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 <code><b>float</b></code> ve <code><b>double</b></code> 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.</div><br />
<table align="center" border="1"><caption><i>Kayan noktalı sayı türleri ve özellikleri</i></caption> <thead>
<tr><th colspan="2">Özellik</th><th><code><b>double</b></code></th><th style="text-align: center;"><code><b>float</b></code></th></tr>
</thead> <tbody>
<tr><td rowspan="3">Uzunluk (ikil)</td><td style="text-align: left;">İşaret</td><td style="text-align: center;">1</td><td style="text-align: center;">1</td></tr>
<tr><td style="text-align: left;">Üs</td><td style="text-align: center;">11</td><td style="text-align: center;">8</td></tr>
<tr><td style="text-align: left;">Kesir</td><td style="text-align: center;">52</td><td style="text-align: center;">23</td></tr>
<tr><td colspan="2" style="text-align: left;">Duyarlık (Ondalık)</td><td style="text-align: center;">16</td><td style="text-align: center;">7</td></tr>
<tr><td colspan="2" style="text-align: left;">Merkez</td><td style="text-align: center;">1023</td><td style="text-align: center;">127</td></tr>
</tbody></table><br />
<div style="text-align: justify;">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.<br />
<ul><li>İş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.</li>
<li>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<sup>-kesir uzunluğu</sup> olabilir. Bu değerlerin, başta varsayılan 1 değerine eklenmesi mantis değerini [1..2-2<sup>-kesir uzunluğu</sup>] 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.<sup><a href="#dn3" id="ref3">3</a></sup></li>
<li>[1..2-2<sup>-kesir uzunluğu</sup>] 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, <code><b>float</b></code> 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<sup>üs uzunluğu</sup>-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.</li>
</ul><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6x2HipQFESmvbiF6lMxRZOfw642UkdxjJeHp6Lq1p0eRWvxfCdlE4hgRgbtFO6erS48TS2Hdp4XATdTqClwQMaOUoMTV615YPdIQHeG6nhAMKPp-YOllEydzXQNCqzjARNmSX53DZo6NO/s1600/IEEE754.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="177" width="377" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6x2HipQFESmvbiF6lMxRZOfw642UkdxjJeHp6Lq1p0eRWvxfCdlE4hgRgbtFO6erS48TS2Hdp4XATdTqClwQMaOUoMTV615YPdIQHeG6nhAMKPp-YOllEydzXQNCqzjARNmSX53DZo6NO/s400/IEEE754.png" /></a></div><br />
Buna göre, <code><b>double</b></code> ve <code><b>float</b></code> türlerince temsil edilebilecek en büyük değer, sırasıyla, (2-2<sup>-52</sup>)2<sup>2046-1023</sup> ve (2-2<sup>-23</sup>)2<sup>254-127</sup> olarak hesaplanacaktır.<sup><a href="#dn4" id="ref4">4</a></sup> Temsil edilebilecek mutlak değerce en küçük sayılar ise, her iki tür için de 2<sup>1-merkez</sup> 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. <br />
<br />
<table align="center" border="1"><caption><i>Kayan noktalı sayıların değer sınırları</i></caption> <thead>
<tr><th colspan="2">Sınır türü</th><th colspan="2" style="text-align: center;"><code><b>double</b></code></th><th colspan="2" style="text-align: center;"><code><b>float</b></code></th></tr>
</thead> <tbody>
<tr><td colspan="2" style="text-align: left;">En büyük</td><td style="text-align: center;">2<sup>1024</sup>-2<sup>971</sup></td><td><code>Double.MAX_VALUE</code></td><td style="text-align: center;">2<sup>128</sup>-2<sup>104</sup></td><td><code>Float.MAX_VALUE</code></td></tr>
</tbody> <tbody>
<tr><td colspan="2" style="text-align: left;">En küçük</td><td style="text-align: center;">2<sup>-1022</sup></td><td></td><td style="text-align: center;">2<sup>-126</sup></td><td></td></tr>
<tr><td colspan="2" style="text-align: left;">En küçük (özel)</td><td style="text-align: center;">2<sup>-1075</sup></td><td><code>Double.MIN_VALUE</code></td><td style="text-align: center;">2<sup>-150</sup></td><td><code>Float.MIN_VALUE</code></td></tr>
</tbody></table><br />
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<sup>-merkez</sup>'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.<br />
<br />
<div id="temsilBölgeleri">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.</div>
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj058-afYA_R9Qyi0Y13eKMkeUHWZY4zBPcz-dpOM43xY4IVFA0TWqvUGgLYlf-_h-kthZdCGh-vkdz0PxHehe7mpiRjeUOVNo59O3szRk5UJCOElJmQV6kDG5dT6Ant-Gv-KLjGeUWyuFm/s1600/FPNumberLine.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="63" width="275" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj058-afYA_R9Qyi0Y13eKMkeUHWZY4zBPcz-dpOM43xY4IVFA0TWqvUGgLYlf-_h-kthZdCGh-vkdz0PxHehe7mpiRjeUOVNo59O3szRk5UJCOElJmQV6kDG5dT6Ant-Gv-KLjGeUWyuFm/s400/FPNumberLine.png" /></a></div><br />
Kısaca açmak gerekirse;<br />
<ul><li>➃ 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ı, <code><b>double</b></code> türü için [-<code>Double.MAX_VALUE</code>..-<code>Double.MIN_VALUE</code>], <code><b>float</b></code> türü için [-<code>Float.MAX_VALUE</code>..-<code>Float.MIN_VALUE</code>] olarak belirlenirken, ➅ nolu bölgenin sınırları, sırasıyla, [<code>Double.MIN_VALUE</code>..<code>Double.MAX_VALUE</code>] ve [<code>Float.MIN_VALUE</code>..<code>Float.MAX_VALUE</code>] olarak belirlenmiştir.</li>
<li>➀ 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, -∞ (<code>Double.NEGATIVE_INFINITY</code> veya <code>Float.NEGATIVE_INFINITY</code>) ve +∞ (<code>Double.POSITIVE_INFINITY</code> veya <code>Float.POSITIVE_INFINITY</code>) 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.</li>
</ul>Açıklık getirmek için aşağıdaki kod parçasını ele alalım. <code>f1</code> değişkenini <code><b>float</b></code> 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 <code>Float.MAX_VALUE</code> 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 <code><b>float</b></code> değer olarak hesaba katılan <code>d1</code>'in tersini bir kez daha <code>d1</code> 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, <code>f3</code> ve <code>f4</code>, sırasıyla, ∞ ve -∞ ile ilkleniyor. Kod parçasının son satırında ise, ∞'un -∞ ile bölünmesi, <code>f5</code> değişkeninin "Sayı Değil" (İng., Not A Number) özel değeriyle ilklenmesi sonucunu doğuruyor.<br />
<pre class="brush:java" name="özelDeğerler">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
</pre></div><div style="text-align: right;"><a alt="Java'da ilkel tamsayı türleri" href="http://ta-java.blogspot.com/2011/03/ilkel-turler-tamsaylar.html">İlkel Tamsayı Türleri</a><br />
<a alt="Java'da karakter türü: char" href="http://ta-java.blogspot.com/2011/03/ilkel-turler-karakterler_30.html">Karakterler</a><br />
<a alt="Java'da mantıksal değer temsili ve ilkel türler arasında uyumluluk" href="http://ta-java.blogspot.com/2011/04/boolean-turu-ve-ilkel-turler-arasnda.html">Mantıksal Değerler</a><br />
</div><hr /><div id="dipnotlar" style="text-align: justify;"><ol><li id="dn1">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. <a href="#ref1">↑</a></li>
<li id="dn2">Aslına bakarsanız, <code>f</code> 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. <a href="#ref2">↑</a></li>
<li id="dn3">Duyarlığın hesaplanmasında yapılması gereken, ikili basamak sayısını log<sub>10</sub>2 değeriyle çarpmak ve çıkan sonucu bir üst tamsayıya yuvarlamak. <a href="#ref3">↑</a></li>
<li id="dn4">1 + 1/2 + 1/4 + ... 1/2<sup>-n</sup> ifadesinin 2-2<sup>-n</sup>'e eşit olduğunu hatırlamanız, hesaplamanın doğruluğuna ikna olmanızı kolaylaştıracaktır. <a href="#ref4">↑</a></li>
</ol></div>Tevfik AKTUĞLUhttp://www.blogger.com/profile/17342526040125152904noreply@blogger.com0