BÖLÜM-1-

advertisement
NESNEYE YÖNELİK YAZILIM GELİŞTİRME
BÖLÜM-11.1. Nesneye Yönelik Yazılım Geliştirme Kavramı
Yazılım geliştirme süreci, ilk yazılımların geliştirildiği tarihlerden bu yana bir çok kez köklü değişikliklere
uğramıştır. Bu değişikliklerin nedenlerinin başında son derece yüksek bir ivmeyle yenilenen ve güçlenen
donanımlar gelmektedir. Yeni donanımlar geliştirildikçe, bu donanımlar üzerinde çalışacak daha hızlı, daha çok
özellikli yazılımlar gerekmektedir. Günümüzde en yaygın kullanılan yazılım geliştirme süreci Nesneye Yönelik
Programlama (NYP) sürecidir. Bu yaklaşımın geniş bir kullanım alanı bulmasının temel nedenleri şunlardır:
NYP yaklaşımının daha birçok yararı bulunmaktadır. NYP yaklaşımı, yararları ve zayıf
noktaları ile ilgili ayrıntılı bilgiler, 7. bölümde anlatılacaktır.
NYP yaklaşımının kullanılarak yazılım geliştirme sürecini öğretilmesi için seçilmesi en
uygun olan araç Java programlama dilidir. Doğası gereği tamamen NYP ilkelerine göre
tasarlanmış olan Java’nın ayrıntıları bu ders kapsamında ele alınacaktır.
1.2. Java Dilinin Tarihçesi
Günümüzde Java dili akla getirilince hemen internet ve genel ağ (www) akla gelmektedir. Ancak sanılanın
aksine Java’nın geliştirilmesi ile internetin ortaya çıkması tamamen bağımsız iki olaydır. Internet’in ortaya
çıkması ile Java birkaç kez şekil değiştirmiş ve bugünkü haline ulaşmıştır. Java’nın bu denli yaygın bir dil olana
kadar geçirdiği aşamalar, alt bölümlerde anlatılmıştır.
1.2.1.
“OAK” Projesi
Sun firmasının mühendislerinin, 1991 yılının başlarında bir araya gelerek fikir tohumlarını attıkları “Stealth”
isimli projenin amacı, buzdolabı, çamaşır makinesi, telefon gibi elektronik ürünlerde bilgisayarların kullanımını
üzerine araştırma ve geliştirme yapmaktı. Projenin hedefi, evlerde, tek bir merkezden kumanda edilebilen,
birbirleri ile konuşabilen büyük bir elektronik aygıtlar ağı oluşturmaktı. Projeye daha sonra birçok kez değişecek
olan “OAK” (meşe) ismi verilmişti çünkü proje çalışanlarından bir tanesinin penceresinden bir meşe ağacı
görülmekteydi. Ancak grubun sonraları yaptığı bir araştırma sonucunda, OAK isminin bir programlama dili
tarafından daha önce tescil ettirilmiş olması nedeni ile isim değişikliği kararı alınmış ve uzun tartışmalar sonucu
proje çalışanlarının sürekli gittikleri bir kahve dükkânında JAVA ismi görülüp beğenilmiştir.
JAVA aslında Endonezya’ya bağlı bir adanın ismidir, bu adada yoğun miktarda kahve üretimi yapılmakta ve bu
kahveler üzerinde JAVA yazan çuvallarla tüm dünyaya dağıtılmaktadır.
1.2.2.
Tasarım Ölçütleri
Proje sonunda üretilecek ürün, evdeki elektronik cihazlarda kullanılacağından, projenin emekleme
aşamalarında geliştiriciler, bazı “tasarım ölçütleri” ortaya koymuşlar ve bu ölçütlere uygun bir sistem
geliştirmeye çalışmışlardır.
En önemli ölçütlerin başında, geliştirilecek sistemin üzerinde çalışacağı donanımın işlemcisinden bağımsız
çalışabilmesi gelmekteydi.
Ev eşyalarında birçok markanın çok farklı özelliklerde birçok modeli bulunmaktadır. Her modelde kullanılan
mikroişlemcinin markası ve özellikleri gereksinimlere göre çok çeşitlilik göstermektedir. Dolayısı ile geliştirilecek
yazılım sisteminin mikroişlemciden (bir diğer deyişle mikroişlemcinin komut kümesinden) bağımsız olması ön
koşul olarak kabul edilmiştir. Bu nedenle OAK dili yorumlayıcı ile çalışacak bir dil olarak geliştirilmeye
başlanmıştır.
(Yorumlayıcı ve derleyici arasındaki farklar Bölüm 1.5’te ayrıntılı olarak ele alınacaktır.)
Bir diğer ölçüt ise sistemin güvenirliliğiydi. Sistemin güvenilir olması ve sürekli çalışması istenmekteydi. Sürekli
kilitlenen ve fişinin çekilip tekrar takılması gereken bir buzdolabı herhalde kimsenin hoşuna gitmez. Bunu
engellemek için tasarlanacak olan yeni programlama dilinde, sık karşılaşılan programcı hatalarını yaptırmamak
için çeşitli önlemler alınmıştır.
1.2.3.
“FirstPerson” Projesi
1992 yılının sonlarında Sun firması projenin ismini “FirstPerson” olarak değiştirdi ancak
ev eşyalarında yakalanamayan başarıdan sonra projenin akıbeti belirsizdi.
Bu noktada, ABD genelinde yayın yapan büyük bir TV kuruluşu, izleyicilerin seçtikleri
programları izlemelerine olanak sağlayan bir cihaz tasarımı için Sun firmasının kapısını
çaldı.
Ellerindeki sistemi bu yeni pazar için uygun gören Sun firması yetkilileri bu konuda
çalışmaya başladılar. Ancak beklenen gene olmadı ve projeyi başka bir firma kazandı. TV
teknolojileri üzerinde yapılan çalışmalar durduruldu.
Proje sorumluları tarafından yapılan yeni iş planlarında, ellerindeki sistemi OAK tabanlı
çevrim içi (online) ve CD tabanlı uygulamalarda kullanmaktı. Ancak bu plan da Sun
kurmayları tarafından uygun görülmeyerek rafa kaldırıldı.
1.2.4.
Gerçek JAVA’nın Doğuşu
1994 Haziran’ında Naughton isimli bir OAK geliştiricisi, yeni yeni yaygınlaşan internet
kullanımı için bir web tarayıcı hazırlarken aklına internette çalışacak “LiveOAK” isimli
yeni bir proje gelir.
OAK projesinin başında düşünülen güvenirlilik, güvenlik ve donanımdan bağımsız
olma ölçütlerinin tamamı internet uygulamaları için de mutlak gerekli olan ölçütlerdi.
İnternet uygulamalarının gereksinimleri ile Java’nın tasarım ölçütleri tamamen
uyumludur.
Aynı yılın Ekim ayında “HotJava” isimli Java tabanlı bir web tarayıcı program
hazırdı. Sun firması 1995 yılında “Java” ve “HotJava” isimli ürünleri resmi olarak
duyurdu. Hemen ardından Netscape firmasının tarayıcılarında Java desteğini
vereceğini duyurması, Java’nın gelişim ve yayılma ivmesini olağanüstü bir şekilde
arttırmış oldu. Ardından Microsoft firmasının da tarayıcılarında Java desteğini getirmesi
Java’nın internet dünyasındaki yerini sağlamlaştırmış oldu.
Her ne kadar projenin başlangıcındaki hedefler çok farklı olsa da, Java dilinin
oluşturulması aşamasında temel olarak alınan tasarım ölçütleri “güvenirlilik”,
“güvenlik” ve “donanımdan bağımsız çalışabilme” gibi kavramlar, internet
uygulamalarının da birebir gerek duyduğu kritik öneme sahip ölçütlerdi.
Java ile internetin bu mükemmel uyumu ile internet dünyasında çok büyük değişiklikler
olmuştur. Sadece veri dağıtan bir internet dünyası, veri dağıtırken aynı zamanda bu
verileri işleyerek her türlü sonucu üretebilecek uygulamaları da internet üzerinden
dağıtılabilir hale gelmiştir.
1.3. Neden Java?
Java dili ile C, C++, Fortran ya da Basic dili ile yapılabilecek her türlü iş
yapılabilmektedir.
Java, kendinden önce geliştirilen bütün programlama dillerinin olumlu ve olumsuz
yanları incelenerek hazırlanmıştır.
Java, C, C++ dillerine çok benzemesine karşın, bu dillerin hiçbirini temel alınarak
hazırlanmamış ve herhangi bir dil ile uyumlu olmasına çalışılmadan tamamen yeni bir
programlama dili ortaya çıkarılmıştır. Döngüler, işleçler vb. noktalar Java dilinin C ya
da C++ dilinin bir türevi gibi görülmesine neden olsa da bu sadece bir benzerlik olarak
nitelendirilmelidir. Bu iki programlama dili arasında çok büyük temel farklılıklar
bulunmaktadır.
Zaten Bölüm 1.1 Java’nın Tarihçesi’nde anlatılığı gibi, Java dilinin tasarımcıları, var olan
programlama dillerinde gördükleri eksiklikleri içermeyen ve daha gelişmiş bir dil
tasarlamak için bazı ölçütler belirlemişlerdi
1.3.1. Platform Bağımsızlığı
Platform bağımsızlığı, teorik olarak bir yazılımın, üzerinde çalıştığı donanıma ve işletim
sistemine bağlı olmadan her türlü platformda çalışabilmesi anlamına gelmektedir. Bir
yazılımın “platform bağımsızlığı” özelliği olmazsa, her platform için yazılımın tekrar
elden geçirilerek gerekli yerlerin değiştirilmesi, her platforma uygun derleyicilerle yeniden
derlenmesi ve sınanması gerekmektedir.
Örneğin bir ofis otomasyonu yazılımı üreten bir firma, platforma bağımlı bir
programlama dili ile geliştirme yaparsa, Intel tabanlı Microsoft Windows™ işletim
sistemine sahip bilgisayarlar için geliştirdiği programın aynısını Motorola tabanlı Apple
Macintosh™ işletim sistemine sahip diğer bir bilgisayarda çalıştıramaz. Her iki platform
için ayrı ayrı kodlar yazmak kaçınılmaz olur.
Platformdan bağımsız, diğer bir deyişle taşınabilir programlar, çalıştığı bir donanım
veya işletim sisteminden alınıp başka tip bir donanımda ve işletim sisteminde sorunsuz
olarak çalışabilme özelliğini göstermektedir.
İnternetin 1995 yılından itibaren hızla yaygınlaşması sonucu hemen hemen her türlü
donanımın internete erişimi gündeme gelmiştir. Günümüzde değişik türde
mikroişlemcilere sahip IBM PC, Apple Macintosh, Linux vb. tabanlı kişisel
bilgisayarlardan, çok değişik marka ve modellerde mikroişlemcilere sahip gelişmiş cep
telefonları, elektronik kişisel yardımcılar (PDA) ve hatta buzdolapları bile
internete bağlanarak bir takım işlevleri gerçekleştirmektedirler.
İnternete bağlanan cihazların çeşitliliği, bu cihazların donanım ve işletim sistemlerinin
farklı olması nedeni ile her türlü donanımda ve işletim sisteminde çalışabilen
“platformdan bağımsız” yazılımların geliştirilmesi gündeme gelmiş ve Java bu konuda
tartışmasız tek ve en güçlü programlama dili haline gelmiştir.
1.3.2. Nesneye Yönelik Mimari
Nesneye yönelik program geliştirme kavramı, son 15 yılda oldukça popüler hale gelmiştir.
Özellikle çok sayıda yazılım geliştirme uzmanı ile gerçeklenen büyük projelerde, nesneye
yönelik yazılım geliştirme mimarisinin birçok yararı bulunmaktadır:
C++ dili nesneye yönelik programlama desteğine sahip olmasına karşın bu destek C
diline zorla eklenmiş gibi sırıtmaktadır. C++ dili, C dili gibi karışık ve zor bir dildir.
Ayrıca C diline uyumlu olmak adına C++ dilinde nesneye dayalı program geliştirme
ilkelerine aykırılıklar (struct, union, typedef) içermektedir. Bu yüzden saf bir nesneye
dayalı programlama dili değildir.
Java dili ise tamamen nesneye yönelik program geliştirme ilkeleri temel alınarak
tasarlanmış, doğuştan nesneye yönelik destek vermektedir. Yaygın bilinen bir deyişle,
Java dilinde yapılan her iş, nesnelerle yapılır. Java dilinden her şey birer nesnedir.
1.3.3. Sadelik
Günümüzde de popülerliğini koruyan ve çok geniş ölçekte kullanım alanı bulan
programlama dillerinden C/C++ dilleri çok gelişmiş özelliklere sahiptirler. Öyle ki bu
özelliklerin çoğu çok nadir olarak kullanılır. Java’da ise sadelik ilkesi benimsenmiştir.
Yüzlerce sayfalık C/C++ dili tanımlarına karşılık Java dilinin tanımlanması yüz sayfayı
geçmez. Ancak sade olmasına karşın Java birçok yönden C/C++ dillerine kıyasla çok
daha fazla işlerliğe sahiptir.
Program hatalarını engellemenin en basit yolu, programlama dilinin
sadeleştirilmesidir. Bu sayede programların yazılması ve okunması çok daha kolay hale
gelmektedir.
C/C++ dillerini kullanan programcıların ortak olarak yaptıkları hataların büyük bir oranı,
bellek alıp-verme işlemlerinden yapılan hatalardan kaynaklanmaktadır. Potansiyel
hata kaynağı olan bu işlemler Java mimarisinde gerçeklenmemiştir. Böylelikle olası birçok
hata da engellenmiş olmaktadır.
1.3.4. Güvenlik
Sun firması mühendisleri, Java’yı geliştirirken Bölüm 1.2. Java’nın Tarihçesi’nde ayrıntılı
olarak anlatıldığı gibi oluşturdukları yeni dilin güvenli olmasını bir tasarım ölçütü
olarak belirlemişler ve Java’yı bu ölçüte göre hazırlamışlardır. (Bölüm 1.2.2. için
tıklayınız.)
C/C++ ve Pascal gibi dillerde mevcut olan ve bir programın belleğin herhangi bir
bölgesine doğrudan erişmesine olanak sağlayan işaretçi (pointer) kavramı, çok ciddi
güvenlik sorunlarına yol açtığı için Java dilinde işaretçiler bulunmamaktadır.
Bu sayede doğrudan bellek erişimleri engellenmiş ve kötü niyetli Java programlarının,
güvenlik açıkları oluşturmaları ya da çalışan sistemi sekteye uğratarak sistemin
dayanıklılığını kırması olasılıkları yok edilmiştir.
Ayrıca Java dilinin sadeliği, daha kolay ve anlaşılır programlar yazılmasını dolayısı ile
en büyük güvenlik sorunu olan yanlış kodların (buggy code) en aza indirilmesinde büyük
rol oynamaktadır.
1.3.5. Çok Görevlilik
Java’nın çok görevliliği (multi-threading) destekleyen bir dil olması demek, bir Java
programına ait, aynı anda çalışan birden fazla Java görevinin bulunabilmesi anlamına
gelmektedir.
Çok görevlilik günümüz programlarında kullanıcı ile etkileşimi sağlamanın en iyi
yollarından bir tanesidir. Java bu desteği doğuştan barındırmaktadır.
1.3.6. Anlamsız Veri Toplayıcı
Programlar çalışırken, değişik yerlerde kalıcı veya geçici bellek gereksinimleri oluşur.
Önceki programlama dillerinde, programcılar, kendilerine bir bellek alanı gerektiği
zaman bunu, programın ilgili yerinde, o dilin ilgili komutunu kullanarak elde ederler. Elde
edilen bu bellek alanının işi bitince sistem kaynaklarına iadesi de gene programcının
istediği yerde yazdığı ilgili bir komut ile sağlanmaktadır.
Örneğin C/C++ dillerinde programcı bir bellek bölgesi istediğinde malloc ya da new
komutları ile bellek bölgesini alır, işi bitince de free ya da delete ile bu bellek bölgesini
sisteme iade eder.
Programcının inisiyatifine bırakılmış bu tür bir çalışma düzeni hatalara oldukça açıktır.
Zaten çoğu program hatasının ana kaynağı bu tür kaynakların uygun kullanılamamasıdır.
Java geliştiricileri, bu tür hataların önüne geçmek için bir yöntem geliştirmişlerdir. Java’da
bir bellek alanı gerektiği zaman bu bellek alanı sistemden alınır.
Java kodlarını yürüten Java yorumlayıcısında sürekli çalışır halde bulunan bir Anlamsız
Veri Toplayıcı (Garbage Collector) programı da artık kullanılmayan bellek bölgelerine
sisteme otomatik olarak iade eder. Böylelikle bir bellek alanının sonsuza kadar işgal
etmek gibi hatalı bir durum ile hiç karşılaşılmaz.
1.4.
Java’nın Olumsuz Yönleri
Her yeni çıkan programlama dili gibi Java’nın da halen eksikleri ve olumsuz yönleri
bulunmaktadır.
Java’nın göze batan en önemli olumsuz yönü, diğer dillere kıyasla daha yeni
olmasından dolayı oturmamış yapısıdır. Java dili halen değiştirilmekte ve yeni
teknolojilere uyacak biçimde evrim geçirmektedir.
Genelde yorumlayıcı ile çalışan Java programları, diğer dillerde yazılarak makine
koduna çevrilmiş programlar kadar hızlı çalışamamaktadır. Yorumlayıcı ile çalışma ve
yavaşlığın nedenleri Bölüm 1.5’te ayrıntılı olarak işlenecektir. Bölüm 1.5. görmek için
tıklayınız.
Günümüzdeki Java yorumlayıcıları ile çalıştırılan bir Java programı, aynı işi yapan bir C
programına göre 30-60 kat daha yavaş çalışmaktadır. Bir Java derleyicisi ile derlenerek
çalıştırılan Java programı ise aynı C programından 3-6 kat daha yavaş çalışmaktadır.
Uluslararası arenada Java’nın gelişimine katkıda bulunan kuruluşlar daha hızlı çalışan
Java programlarını ortaya çıkarmak için gerekli yorumlayıcıları ve derleyicileri geliştirmek
üzere yoğun bir şekilde çalışmaktadır.
Bir diğer olumsuz nokta da anlamsız veri toplayıcı özelliğinin halen tam verimli
çalışan bir uygulamasının gerçeklenememesidir. Her ne kadar bu alanda yeni
yöntemler ve uygulamalar geliştirilmiş olsa da henüz teorik çalışmayı sağlayan bir
uygulama hayata geçirilememiştir.
1.5. Derleyici ve Yorumlayıcı
Her bilgisayarda, belirli bir programın komutları adım adım yürüten bir mikroişlemci
bulunur. Her mikroişlemci ailesinin kendisine özgü bir komut kümesi vardır. Bir
mikroişlemci, yalnız kendi komut kümesinde yer alan yani “tanıdığı ve bildiği”
komutları yürütebilir.
Bu yüzden Intel tabanlı mikroişlemciler kullanan IBM PC uyumlu bilgisayarlarda çalışan
programlar, Motorola tabanlı mikroişlemciler kullanan Apple Macintosh bilgisayarlarında
çalıştırılamazlar. Bu durumun tersi de doğrudur. Bu komutlardan oluşan dile “makine
dili” adı verilir.
Örnekteki gibi ikili makine kodlarını içeren dosyalara çalıştırılabilir (executable)
dosyalar da denir. Bu tür çalıştırabilir dosyalar Microsoft Windows™ işletim
sistemlerinde genellikle EXE uzantılı, Apple MacOS™ işletim sistemlerinde ise BIN
tipindeki dosyalardır. Linux sistemlerinde ise dosya haklarında x harfi bulunan dosyalar
genellikle çalıştırılabilir makine kodu içeren dosyaları gösterir.
Örnekten de görüldüğü gibi makine kodunda program yazmak gerçekten çok zordur.
Programcıların işlerini kolaylaştırmak üzere çeşitli düzeylerde programlama dilleri
geliştirilmiştir. Programlama dilleri genel olarak 4 sınıfta incelenebilir.
Simgesel dil (assembly language), yazılması çok zor ve karmaşık olan makine dilini
daha rahat yazmak için geliştirilmiş bir dildir. Bu dilde, her makine kodu komutuna
karşılık gelen, daha anlaşılır ve harflerle ifade edilen komutlar kullanılmıştır. Bir çevirici
program yardımı ile bu komutlar ikili makine kodlarına çevrilir.
Yüksek seviyeli dil olarak tanımlanan programlama dilleri, programların doğal dile
yakın bir şekilde kodlanmasına olanak sağlamaktadır. Java, C, C++, Ada gibi diller
yüksek seviyeli diler olarak nitelendirilirler. Yüksek düzeyli diller bir yardımcı program
kullanılarak makine koduna çevrildikten sonra çalıştırılabilir hale gelmektedir. Yüksek
seviyeli programlama dilleri sayesinde, programcıların çalıştıkları donanımın detaylarını
bilmelerine gerek yoktur. Programcılardan beklenen, kullandıkları dilin yapısını ve
komutlarını iyi bilerek yazacakları programı, dilin özelliklerini iyi şekilde kullanarak
hazırlamalarıdır.
Örneğin üst düzey bir dilde program yazan bir programcı ekrana bir metin yazdırmak
istediği zaman sadece ilgili ekrana yazma komutunu ve yazdırılacak metni belirtir:
ekrana_yaz_komutu(“Ekrana yazdırılacak metin budur”)
Aslında bu üst düzey program komutunun karşılığında birçok (>100-150) makine kodu
üretilir. Böylelikle üst düzey dilleri hazırlayanlar, bu dilleri kullanan programcıların işlerini
daha kolay yapmalarını sağlarlar.
Hangi dilde hazırlanırsa hazırlansın, bir programın mikroişlemci tarafından
çalıştırılabilmesi için öncelikle makine koduna dönüştürülmesi gerekmektedir.
Bu dönüştürme işlemi bir yardımcı program ile yapılır. Bu yardımcı program aracılığıyla
kullanıcı hazırlamış olduğu programı makine koduna çevirebilir. İki şekilde çevirme söz
konusudur: derleme ve yorumlama. Bu çevirme yöntemlerinin ayrıntıları aşağıda
verilecektir.
1.5.1.
Derleme İşlemi ve Derleyiciler
Derleme (compilation) işlemi, herhangi bir dilde yazılan program komutlarının hepsinin
birden makine koduna çevrilmesini ifade etmektedir. Bu işi yapan yardımcı programa da
derleyici (compiler) adı verilir. Programcı, istediği bir dilin komutlarını kullanarak bir dizi
komuttan oluşan bir program hazırlar.
Hazırladığı bu komutları bir dosyaya yazar ve saklar. Daha sonra o programlama diline ait
derleyici programını çalıştırır ve hazırlamış olduğu dosyayı, “kaynak” (source) dosya
olarak derleyici programa gösterir. Derleyici program ise bu kaynak dosyadaki komutlara
ve komutların parametrelerine bakarak her komutun karşılığında bir veya çoğu kez birden
fazla makine kodu oluşturur. Sonuçta, ikili makine kodlarını içeren“çalıştırılabilir” program
dosyası oluşur.
Derleme işleminin en önemli özelliği kaynak dildeki tüm komutların tamamının bir seferde
makine koduna çevrilerek çalıştırılabilir dosyanın oluşturulmasıdır. Bir kez derlendikten
sonra artık çalıştırılabilir dosya dağıtılabilir. Farklı bir bilgisayarda hazırlanan programın
çalışması için sadece yeni oluşan çalıştırılabilir dosyanın bulunması yeterlidir. Bu
programın çalışacağı her bilgisayarda kaynak dosyanın ya da derleyici programının olması
gerekmez.
Programda bir değişiklik yapılmak istendiğinde, bu değişiklik kaynak program
dosyasında yapılır ve bu dosya tekrar derlenir ve yeni bir çalıştırılabilir dosya
oluşturulur.
Kaynak dosya programcı için hayati önem taşımaktadır, çünkü bu kaynak dosya bir
şekilde silinirse, eldeki çalıştırılabilir dosyadan bu kaynak dosyanın oluşturulması (çoğu
kez) mümkün değildir. Bu ise programda artık bir değişiklik yapılamayacağı anlamına
gelmektedir.
Programcı açısından çok önemli olan kaynak dosyanın korunması da dikkat edilmesi
gereken ayrı bir konudur. Kaynak dosyayı elinde bulunduran kişi programı istediği şekilde
değiştirebilir. Yazılımların güvenliğini sağlamak için kaynak dosyalar özenle korunmalı ve
üçüncü kişilerin erişmelerine karşı korunma altına alınmalıdır.
Derlenerek çalışan programlama dillerine örnek olarak C, C++, Pascal, Delphi vb. diller
sayılabilir.
1.5.2. Yorumlama İşlemi ve Yorumlayıcılar
Yorumlama (interpretation) işlemi, herhangi bir dilde yazılan program komutlarının
teker teker makine koduna çevrilerek çalıştırılmasını ifade etmektedir. Bu işi yapan
yardımcı programa da yorumlayıcı (interpreter) adı verilir.
Programcı, istediği bir dilin komutlarını kullanarak bir dizi komuttan oluşan bir program
hazırlar. Hazırladığı programın yorumlayıcı programa kaynak dosya olarak gösterir.
Derleme işleminin aksine yorumlama işleminde bütün komutlar bir seferde makine
koduna dönüştürülmez. Yorumlama işleminde yorumlayıcı program, kaynak programın ilk
komutundan çalışmaya başlar. Kaynak programdaki ilk komutu, makine kodu karşılığına
dönüştürür, bu komutu çalıştırır ve bir sonraki komuta geçer. Bu döngü program
komutları bitinceye kadar devam eder.
Yorumlama işleme kaynak olarak yorumlayıcıya, program komutlarını içeren bir kaynak
dosya gösterilebileceği gibi yorumlayıcı komutları doğrudan kullanıcıdan da alabilir. Örnek
olarak Microsoft DOS™ işletim sistemi yorumlayıcıya güzel bir örnektir. Bu sistem
kullanıcıdan bir komut bekler, komut girildikten sonra bunu dönüştürür ilgili kodu çalıştırır
ve sonucu gösterdikten sonra kullanıcıdan yeni bir komut beklemeye başlar.
Şekil 1.3’ te Microsoft DOS™ işletim sisteminin yorumlayıcısının çıktıları görülmektedir.
Bu yorumlayıcı, kullanıcıdan bir komut girmesini beklemiş, kullanıcı “dir” komutunu
girdikten sonra da bu komutu dönüştürerek çalıştırmıştır. Çalışan program ekranda bir
sonuç çıktı üretmiştir.
Girilen “dir” komutunun yürütülmesi bittikten sonra yorumlayıcı kullanıcıdan bir sonraki
komutu beklemeye başlamıştır.
1.5.3. Yorumlama ile Derleme Arasındaki Farklar
Bir programlama dilinin derleyicisi varsa bu dilde yazılmış programlar derlenerek
çalıştırılabilir dosyalar oluşturulabilir. Eğer bu dilin yorumlayıcısı da varsa bu dilde
yazılmış bir program ister çalıştırılabilir dosyalar kullanılarak çalıştırılır istenirse
de yorumlayıcı kullanılarak çalıştırılır. Yorumlama işleminin bir diğer sakıncası da
yorulmayıcı programın ve kaynak dosyanın, bu programın çalışacağı bütün
bilgisayarlarda olması zorunluluğudur. Derlenmiş bir programda sadece
çalıştırılabilir dosyanın dağıtımı yeterli iken yorumlayıcı ile çalışan programlarda her
bilgisayarda yorumlayıcı programın ve kaynak dosyanın bulunması zorunludur.
1.5.4. Java Derleyicisi ve Yorumlayıcısı
Java dili geliştirilirken hedeflenen donanımdan bağımsız olma ve güvenlik gibi bazı
ölçütler doğrultusunda, Java programlama dilinin çalıştırılma ilkesi olarak derleme ve
yorumlama yöntemleri beraber benimsenmiştir.
1.5.4.1. Javanın Çalıştırılması
Bir Java programı hazırlamak için öncelikle programın görevlerini yerine getirecek Java
komutlarından oluşan bir kaynak dosya hazırlanmalıdır. Bu kaynak kod daha sonra bir
“Java Derleyicisinde” (Java Compiler) derlenerek “Java İkili Kodu” (Java Bytecode)
içeren bir çıkış dosyası oluşturulur. Normal derleme işleminden farklı olarak bu dosya
içerisinde çalıştırılabilir makine kodları bulunmaz yani bu dosya doğrudan işletim sistemi
tarafından çalıştırılamaz.
Derleyici tarafından üretilen bir Java İkili Kodunun çalıştırılabilmesi için iki yöntem vardır.
“Java Yorumlayıcısı” (Java Interpreter) kullanılarak bu ikili kod yorumlayarak
çalıştırılma ilkelerinde anlatıldığı gibi çalıştırılabilir. Bu yorumlayıcının sıkça kullanılan adı
Java Sanal Makinesidir (Java Virtual Machine – JVM).
Bu çalışma düzeni yüksek seviyeli bir dilin yorumlanarak çalışmasından daha farklı ve
hızlıdır. Çünkü yüksek düzeyli bir dilin her bir komutunun çözümlenmesi, çevrilmesi ve
çalıştırılması çok daha karmaşık bir iştir. Oysa Java İkili Kodu, makine koduna çok yakın
bir kod üretir, dolayısı ile her bir Java İkili Kodu komutunu çevirme ve yürütme işlemi çok
daha basittir bu da işlemin daha hızlı yapılması anlamına gelir.
İkinci çalıştırma yöntemi ise Java İkili Kodunun “Java İkili Kod Derleyicisinde” (Java
Bytecode Compiler) derlenerek ortaya bir çalıştırılabilir makine kodu dosyasının
çıkartılmasıdır. Java İkili Kod Derleyicisi, aslında tam bir derleyici gibi çalışmamaktadır.
Sun tarafından geliştirilen JAVA tam-yerinde-derleyici (Just-in-time JIT compiler) ile
Java ikili kodları yorumlanarak çalıştırılırken sadece ilk defasında makine koduna
dönüştürme işlemi gerçekleştirilir. Aynı ikili kod yeniden çalışacağı zaman dönüştürme
işlemi yapılmaz, bunun yerine önceden dönüştürülmüş makine kodu kullanılır.
Çalışma hızı açısından karşılaştırıldığında, yorumlanarak çalıştırılan Java programının,
derlenerek çalıştırılan Java programından daha yavaş çalıştığı görülmektedir. Her ne
kadar yorumlama sırasında yapılan çevirme işlemi, yüksek düzeyli dillere göre daha basit
olsa da, her bir ikili Java komutu için her defasında bir çevrilme işlemi yapıldığından,
derlenerek çalışmaya göre daha yavaş çalışmaktadır. Bu nedenden dolayı, çalışma hızının
önemli olduğu uygulamalar için Java İkili Kodu derlenerek makine koduna çevrilmelidir.
Her iki çalıştırma yöntemini gerçekleyebilmek için gerek duyulan Java İkili Kod Derleyicisi
ve Java Yorumlayıcısının, yani Java Sanal Makinesinin, çalıştırılacak platform için
mevcut olması gereklidir. Örneğin eğer herhangi bir Java programı, Intel tabanlı bir
işlemcisi ve Microsoft Windows™ işletim sistemi olan bir bilgisayarda çalıştırılmak
istenirse, bu mikroişlemci ve işletim sistemi için geliştirilmiş Java Yorumlayıcısının ya da
Java İkili Kod Derleyicisinin anılan bilgisayarda kurulu olması gerekmektedir.
Bir önceki sayfada gördüğünüz Şekil 1.4’te kesikli mavi kutu içerisinde yer alan
bileşenler, programın çalıştırılacağı platforma doğrudan bağımlıdır. Yani bu kısımdaki
bileşenler, aynı Java programının Microsoft Windows™ işletim sisteminde Linux işletim
sistemindekinden farklıdır.
Java dilinin donanımdan ve işletim sisteminden bağımsız olmasının ana nedeni, Java
kaynak kodunun ve Java İkili Kodunun platformdan bağımsız olmasıdır.
Mikroişlemciye (ya da mikroişlemcinin komut kümesine) bağımlı olmayan Java kaynak
kodu, platformlar arası taşınabilir bir program olarak görülebilir.
Bir Java programının bir platformda çalışabilmesi için tek koşul, o platform için yazılmış
bir Java Yorumlayıcısının veya Java İkili Kod Derleyicisinin bulunmasıdır.
1.6. Java Kurulumu
Bir önceki bölümde geçen Java Derleyicisi, Java Yorumlayıcısı ve Java İkili Kod
Derleyicisi, Sun tarafından ücretsiz olarak dağıtılan Java Yazılım Geliştirme Paketi
(Java Software Development Kit- SDK veya JDK) içerisinde bulunmaktadır. Bu paket
içerisinde ayrıca yazılım geliştiricilere kolaylıklar sağlayan birtakım araçlar daha
bulunmaktadır. Bu yazılım paketi "http://java.sun.com" adresinden ücretsiz olarak
indirilebilir.
Standart Java SDK içerisinde yer alan araçların tamamı komut satırından çalıştırılan
programlardır yani görsel öğeler içermezler. Bunun dışında değişik firmalar tarafından
hazırlanmış ve Tümleşik Geliştirme Ortamları - TGO (Integrated Development
Environments – IDE) bulunmaktadır. Bu ortamlar, bir Java komutlarını yazacağınız bir
metin editörü, Java derleyicileri ve yorumlayıcısı ve bir dizi yardımcı görsel araç
içermekte bu sayede yazılımcının daha rahat yazılım geliştirmesini sağlamaktadır.
Günümüzde kullanılabilecek popüler geliştirme ortamları aşağıda listelenmiştir:
Bu yazılım geliştirme ortamlarının çoğunda Java programının düzenlenmesi, derlenmesi,
hata ayıklanması ve çevrim-içi yardım seçeneklerinin görüntülenmesini sağlayan birçok
kullanışlı özellik bir arada bulunmaktadır.
Ancak bu araçların hiçbirisini kullanmadan sadece Sun tarafından sağlanan standart Java
Paketini bilgisayarınıza kurarak ve Java kaynak kodlarınızı herhangi bir metin editörü ile
hazırlayarak bir Java programı ortaya çıkartabilirsiniz
BÖLÜM-2JAVA DİLİNİN TEMEL YAPISI
2.1. Bir Java Programının Yapısı
Bir Java programında yer alabilecek bir takım temel bileşenler bulunmaktadır.
Bir Java programının kaynak kodunun hazırlanması için bu temel bileşenler iyi
bilinmelidir.
2.1.1. Anahtar Sözcükler (Reserved Words veya Keywords)
Bu başlık altındaki sözcükler, Java derleyicisi tarafından özel amaçlar için
kullanılmaktadır. Dolayısı ile programın herhangi bir yerinde bu amaçlardan farklı olarak
bu sözcüklerin kullanılması olası değildir. Örneğin int, class gibi sözcükler özel anlamlar
taşımaktadır ve bu anlamların dışında bir görevde kullanılamazlar.
Aşağıda Java’nın anahtar sözcüklerinin bir listesi verilmiştir:
2.1.2. Deyimler (Statements)
Deyimler, bir ya da birden fazla işlemi ifade etmektedir. Her deyimin sonunda noktalı
virgül ( ; ) işaretinin bulunması zorunludur. Aşağıda bir deyim örneği verilmiştir:
System.out.println(“Merhaba!”);
Bu deyim sonucunda ekrana Merhaba yazısı yazdırılır.
Bu ders kapsamında, deyim kavramı ile komut kavramı çoğu kez eş anlamlı olarak
kullanılmaktadır.
2.1.3. Komut Blokları (Blocks)
Komut blokları, birden fazla komutun (veya diğer Java bileşenlerinin) gruplanması
amacıyla kullanılır. Bir komut bloğu, küme parantezi açma işareti ( { ) ile başlar ve
küme parantezi kapatma işareti ( } ) ile biter.
Örnek;
2.1.4. Sınıflar (Classes)
Sınıflar, daha önce de değinildiği gibi nesneye yönelik program geliştirme kavramında
merkezi öneme sahip bir yapıdır. Bir Java programı en az bir, genelde ise birçok Java
sınıfının tanımlanması ile oluşturulur. Sınıflar, Java dilinin en temel bileşenlerinden biridir.
Sınıflar ile ilgili özet bilgiler, bu bölümün devamında verilecektir. Sınıfların ayrıntılı olarak
anlatılması ise 7. bölümde yapılmıştır.
2.1.5. Metodlar (Methods)
Sınıflara ait bazı veriler üzerinde işlem yapan komut gruplarına metot adı verilmektedir.
Aşağıdaki örnekte system.out isimli nesne üzerinde ekrana yazdırma işlemini
gerçeklemek üzere hazırlanmış println isimli bir metod çağrılmaktadır.
System.out.println(“Merhaba!”);
2.1.6. Yorumlar (Comments)
Yorum satırları, bir programcı için hayati önem taşıyan unsurlardan bir tanesidir.
Yorumlar sayesinde programcı, yazılan kodları daha rahat inceleyebilir ve anlayabilir.
Aslında yorum satırları, programcılara yardım etmekten başka bir işe yaramaz,
derleyiciler tarafından tamamen göz ardı edilirler. Java dilinde, C/C++ dillerinde olduğu
gibi yorum satırı tek bir satırdan oluşuyorsa // işaretleri ile başlar, eğer birden fazla
satırdan oluşuyorsa /* işareti ile başlar */ işareti ile biter.
Aşağıda bazı yorum satırı örnekleri verilmiştir:
// Bu satır bir yorum satırıdır.
/* Bu program ekrana Merhaba yazdıran bir programdır.*/
/*
Program Adı : MerhabaYazdir
Son Değişiklik : 09/09/2005
Değişikliği Yapan : Bill Gates
*
2.1.7. Belirteçler (Modifiers)
Dilin yapısında verilen anahtar sözcüklerden bazıları, verilerin, metotların veya sınıfların
bazı özelliklerini belirlediği için belirteç olarak adlandırılırlar.
Örneğin final, abstract, protected, public, static anahtar sözcükleri birer belirteçtir.
Örneğin final belirteci, değişken olarak tanımlanan verilerin birer sabit olduğunu
belirtmek için kullanılır. Değişkenler ve sabitler ile ilgili bilgiler 3. bölümde ayrıntılı olarak
verilecektir.
2.1.8. main Metodu
Her Java programında, program ilk çalışmaya başlatıldığında yürütülecek bir main
metodu bulunması zorunludur. Java yorumlayıcısı, main metodundan başlayarak
programı çalıştırır. Bu metot, aşağıda gibi tanımlanır:
public static void main(String[] args){
// program komutları
}
2.2. Sınıf ve Nesne Kavramları
Nesneye dayalı mimariyi doğuştan bünyesinde barından ve tamamen nesneye yönelik
programlama ilkelerine göre tasarlanan Java dilinin anlaşılması için öncelikle sınıfların
ve nesnelerin anlaşılması gerekmektedir.
Sınıflar ve nesneler hakkında ayrıntılı bilgi bölüm 7’ de ayrıntılı olarak ele alınacaktır.
Ancak Java dilinin temel yapısını anlatan ilerleyen bölümlerdeki konu anlatımlarında ve
verilen örneklerde sık sık sınıflar ve nesneler kullanılacaktır. Java dilinin tamamen
nesneye dayalı bir dil olmasından dolayı nesneleri kullanmadan basit bir örnek program
bile yazmak olası değildir.
Bu nedenden dolayı, bu bölümde, Java dilinin temel yapısını incelemeden önce sınıf ve
nesne kavramları üzerinde kısaca bilgi verilecektir. (Sınıf ve nesne kavramları, özellikler,
metotlar, kalıtım vb. gibi ayrıntılı konular 7. bölümde ele alınacaktır.)
Yazılım geliştirme metodolojisinin ilerlemesi sonucu, yazılımların gerçek dünyadaki
“nesnelere” benzer şekillerde tasarlanmasının, özellikle büyük ölçekli projelerde
geliştirme hızı, programcılar arası verimin arttırılması gibi birçok konuda önemli yararlar
sağladığı görülmüştür.
Java dilinde bilgiler, tamsayı, reel sayı, Boole gibi temel tipler adı verilen temel tiplerden
biri şeklinde saklanabileceği gibi nesneler şeklinde de saklanarak işlem görebilirler.
Aslında nesneler, bilgi saklamanın dışında birçok ek özellik daha getirmektedir.
2.2.1. Özellikler ve Metotlar
Teorik olarak her şey birer nesne olarak düşünülebilir. Örneğin geometri dersindeki
üçgen, kare, dikdörtgen, çember, daire, elips her bir şekil, bir sınıf olarak ele alınabilir.
Bu gerçek dünya nesnelerini, yazılım nesneleri haline getirince, bu nesnelere ait bir takım
bilgiler saklanmalı ve bu nesnelere ait bir takım işler gerçeklenmelidir.
Örneğin “kare” isimli bir yazılım nesnemiz olsun. Kare ile ilgili tutulabilecek özelliklerden
birkaç tanesi aşağıdaki gibi olabilir:
Bu liste gereksinimlere göre çoğaltılabilir. Ayrıca bu “kare” nesnesinin sahip olduğu bu
özelliklere ek olarak bu “kare” nesnesi üzerinde bazı işlemler yapılabilir. Gene
gereksinimlere bağlı olarak daha farklı birçok işlem gerçeklenebilse de aşağıda birkaç
örnek işlem verilmiştir:
Özet olarak, etrafımızdaki her nesnenin sahip olduğu bir takım özelikler ve bu nesneler
üzerinde gerçeklenebilecek bir takım işlemler bulunmaktadır. Gerçekten de yazılım
dünyasındaki nesneler de aynı yapıdadır. Her bir nesnenin sahip olduğu özelliklerin
saklandığı “özellikler” (attributes) kısmı ve bu nesne üzerinde yapılacak işlemleri
gerçekleyen komutları içeren metotlar (methods) kısmı bir nesnenin temel öğeleridir.
2.2.1.1. Örnek
Yapılan tanımlamalara göre örnek bir “kare” sınıfı, aşağıdaki gibi oluşturulabilir.
public class Kare
{
// ---------- Özellikler (attributes) kısmı ---------int a; // Karenin bir kenarının boyu
int x,y; // Karenin 2 boyutlu uzaydaki konumu
int renk; // Karenin içinin rengi
// ---------- İşlemler (metotlar) kısmı ---------// Karenin kenar boyutunu ayarlama işini yapan metot
public void KenarAyarla(int yeni_a);
// Karenin konumunu ayarlama işini yapan metot
public void KonumAyarla(int yeni_x, int yeni _y);
// Karenin içini boyama işini yapan metot
public void RenkDegistir(int yeni_renk);
// Karenin merkezini isaretleyen metot
public void MerkezIsaretle();
}
Program Kodu 2.1: Örnek bir sınıf tanımlaması
Bu örnekte çizgi ile ayrılan bölgenin yukarısında kalan bölgede, bir kare için
saklanması gerekli görülen bilgiler ve tipleri tanımlanmış (özellikler) devamında ise
bir kare üzerinde yapılabilecek işlemleri içeren bazı işlemlerin (metotlar) başlıkları
verilmiştir.
Program Kodu 2.2’ de, bu sınıf tanımlaması kullanılarak 2 adet “kare” nesnesinin
oluşturulmasına ilişkin bir örnek yer almaktadır.
...
// 2 adet kare nesnesi tanımla
Kare kare_1, kare_2;
// 1. kareyi 10,20 koordinatına yerleştir
kare_1.x = 10;
kare_1.y = 20;
// 2 kareyi 50,60 koordinatına yerleştir
kare_2.KonumAyarla(50,60);
...
Program Kodu 2.2: Nesne tanımlanması, özellikler ve metotların kullanılması
Program Kodu 2.2, dikkatlice incelendiğinde, 2 adet “kare” sınıfından nesne
oluşturulduğu ve bu nesnelerden “kare_1” isimli nesneni x ve y özelliklerine
doğrudan erişilip 1. karenin geometrik uzayda 10,20 koordinatlarına
yerleştirildiği görülmektedir. Oluşturulan 2. nesne “kare_2”nin bulunduğu
konum ise KonumAyarla isimli “kare” sınıfına ait metodun çağrılması ile
gerçeklenmektedir.
2.2.2. Kavramsal Nesne Hiyerarşisi ve Kalıtım
Nesnelerin bir diğer önemli özelliği ise kavramsal bir hiyerarşiye sahip olmalarıdır.
Örneğin bahsedilen kare, üçgen, daire gibi tüm nesneler aslında birer geometrik
şekildir. Bu durum, şekil 2.1’ de ayrıntılı olarak gösterilmiştir:
Nesneye yönelik programlamaya cazip kılan en hassas noktalardan bir tanesi de
“kalıtımdır” (inheritance). Yazılım sınıfları olarak hiyerarşik bir yapıda belirli bir mantığa
göre düzenlenen sınıflar, ataları olan yani hiyerarşide kendilerinden daha üstte olan
sınıfların özelliklerini “kalıtım” yolu ile alırlar.
Örneğin bütün geometrik şekiller, uzayda belirli bir konumda yani yatay ve düşey (eğer 3
boyutlu bir uzay söz konusu ise dikey doğrultu da eklenir) doğrultularda belirli bir yerde
(x, y, z) bulunurlar. Bu bilgi bütün geometrik şekiller için ortaktır ve kalıtım yolu ile
“geometrik şekil” sınıfından “türetilen” bütün sınıflarda da bulunur. Bu özelliklerin ayrıca
tanımlanmasına gerek yoktur.
Örneğin, şekil 2.1’ de gösterildiği gibi eğer “dörtgen” sınıfı “geometrik şekil” sınıfından
türetilmişse ve “kare” sınıfı da “dörtgen” sınıfından türetilmişse, “kare” sınıfının da
“özellik” olarak x, y, z özellikleri atası olan “geometrik şekil” sınıfından kendisine kalıtım
yolu ile aktarılmış olur.
Örnek bir sınıf tanımlaması aşağıda verilmiştir:
public class GeometrikSekil
{
// Geometrik şeklin 3 boyutlu uzaydaki konumu
int x,y,z;
// Şeklin konumunu ayarlamaya işini yapan metot
public KonumAyarla(int yeni_x, int yeni _y, int yeni _z);
}
Program Kodu 2.3: Örnek bir sınıf tanımlaması
Örnekten de rahatça anlaşılacağı gibi GeometrikSekil sınıfı aslında “soyut” (abstract)
bir sınıftır çünkü gerçek hayatta bu sınıfa ait herhangi bir şekil yoktur. Aslında bu sınıf,
gerçek hayatta da soyut olarak düşünülmektedir.
2.3. Örnek Java Programı
Şekil 2.2’ de yer alan programda 3 farklı sınıf dolayısı ile 3 farklı dosya
bulunmaktadır. Bu sınıflardan herhangi bir tanesine bir adet main metodunun
tanımlanmış olması gerekliliğini lütfen unutmayınız.
İlk Java programımız, geleneksel olarak bir programlama dilini anlatan bütün kitaplarda
olduğu gibi ekrana “Merhaba Java!” yazdıran bir program olarak seçilmiştir.
public class Merhaba
{
public static void main(String[] args)
{
System.out.println(“Merhaba Java!”);
}
}
Bu program derlenerek çalıştırıldığında ekranda açılacak bir çıkış penceresinde “Merhaba
Java!” yazılacaktır. Program çalıştırıldığında ilk önce main isimli bir metot aranacak ve bu
metodun ilk komutu yürütülmeye başlanacaktır.
Bu örnekte ise bu ilk komut, System nesnesine ait out nesnesinin bir metodu olan println
metodunun çağrılmasını sağlamaktadır. Bu println metodu çağrılırken parametre olarak
da ekrana yazdırılmak istenen metin karakter katarı şeklinde (“Merhaba Java!”)
gönderilmektedir. Bu komut yürütülecek ilk ve son komut olduğundan main metodu
sona erer ve program normal bir şekilde sonlandırılır.
2.3.1. Örnek Java Programının Çalıştırılması
Programı komut satırından çalıştırabilmek için aşağıdaki adımlar izlenmelidir:
2.4. Sınıf Kütüphaneleri ve Paketler
Sınıf kütüphanesi (class library), belirli bir konuda bir araya getirilerek hazırlanmış
sınıflar kümesidir. Bu kütüphane içerisinde yer alan sınıflardan bazıları, programcılar için
vazgeçilmez temel sınıflar iken bazıları programcıların işini kolaylaştıran özellikler içeren
sınıflardır. Her sınıf kütüphanesi, içerdiği sınıfların tanımlamalarını bulunduran
dosyalardan oluşmaktadır. Birbirleri ile ilgili ya da benzer amaçlar için hazırlanmış sınıflar
aynı küme içerisinde yer alırlar. Bu kümelere paket (package) adı verilmektedir.
Bir paket içerisinde yer alan sınıfların tanımlamalarını içeren dosyalar belirli bir dizin
altında saklanırlar. Aynı paket içerisinde bir sınıf, diğer sınıflar için erişilebilir
konumdayken, paketler arasında sınıfların erişimleri olabilir ya da olmayabilir. Örneğin
String sınıfı, Java standart sınıf kütüphanesinde bulunan bir sınıftır. Bu sınıf, Java’nın
temel geliştiricisi Sun tarafından hazırlanmıştır ve dağıtılmaktadır. Bu sınıf, java.lang
isimli pakette bulunmaktadır. Java ile beraber gelen bu standart kütüphaneye ek olarak
çeşitli üreticilerin hazırladığı birçok farklı kütüphane de bulunmaktadır. Sınıf
kütüphanesindeki sınıfların gruplanmasında bir başka tür de uygulama ara birimleridir
UAB (Application Programmer Interface – API).
2.4.1. Java’nın Standart Kütüphanesinde Yer Alan Paket
İsimleri
Aşağıda Java’nın standart kütüphanesinde yer alan bazı paketlerin isimleri ve tanımları
verilmiştir:
2.5. Kullanıcı Girişlerinin İşlenmesi
Programlarda sık sık kullanıcıdan bazı verilerin girilmesi gerekliliği doğmaktadır. İlerleyen
bölümlerdeki örnek programlarda da kullanılacağı için kullanıcının girdiği bilgilerin
işlenmesi bu bölümde anlatılacaktır.
Kullanıcının veri girişi yapmasını sağlamak için birçok yöntem bulunmakla beraber
bunlardan en yaygın olanı swing paketinde yer alan giriş penceresinin kullanılmasıdır.
Swing paketi içerisinde yer alan JOptionPane isimli sınıfa ait statik metot
showInputDialog isimli metot çağrıldığında aşağıdaki gibi bir veri giriş penceresi
kullanıcıya sunulur.
Şekil 2.3: Kullanıcının veri girişi için açılan pencere
Bu deyim yürütüldükten sonra kullanıcının girdiği yaş bilgisi String tipindeki giris
değişkenine atanır. Doğal olarak yaş gibi bir verinin int tipinde bir veri olarak kullanılması
daha uygundur. Bunu sağlamak üzere Integer.parseInt ve Double.parseDouble isimli
metotlar, String tipindeki veriyi sayı tipinden verilere çevirmek için kullanılabilirler:
Eğer girilen veri, tamsasyı tipine çevrilemiyorsa bir aykırı durum oluşturulur.
JOptionPane.showInputDialog kullanılan her programın main metodunun sonuda
aşağıdaki gibi bir deyim kullanılmalıdır:
Kullanıcı girişi için JOptionPane.showInputDialog kullanıldığında bir iş parçacığı
(thread) oluşturulur ve bu iş parçacığı, program sonlansa bile çalışmaya devam eder. Bu
iş parçacığını sonlandırmak için, main metodunun sonunda yukarıdaki deyim çalıştırılmalı
ve bu iş parçacığının sonlandırılmasının sağlanması gerekmektedir.
2.5.1. Örnek
Örnek bir program aşağıda verilmiştir:
import javax.swing.JOptionPane;
public class YasOku {
public static void main(String[] args) {
String giris = JOptionPane.showInputDialog("Lütfen yaşınızı giriniz:");
int yas = Integer.parseInt(giris);
System.out.println("Yaşınız : "+ yas);
System.exit(0);
}
}
Program Kodu 2.6: Grafik arabirimi ile kullanıcının veri giriş
Program kodu 2.6’ da verilen program ile kullanıcıya yaşını girmesi sorulmakta ve
kullanıcının girdiği yaş bilgisi tamsayıya çevrilerek ekrana yazdırılmaktadır.
Kullanıcının veri girmesinin bir başka yolu da komut satırından veri girişidir. Her veri girişi
için kullanıcıya açılan bir pencerenin oluşması bazı programcılar tarafından tercih edilmez,
bunun yerine komut satırından veri girişi yeğlenmektedir.
Komut satırından yapılan girişler, System.in nesnesi kullanılarak okunur. Ancak bu nesne
verileri sekizlik (byte) olarak okumaktadır. Bu sekizlik değerlerden karaktere geçmek
için, System.in nesnesi InputStreamReader nesnesine dönüştürülür:
Bu ifade ile oluşturulan bir InputStreamReader nesnesi karakter okuması yapabilirken,
karakter katarlarının okunmasında kullanılamaz. Karakter katarı türünden verileri okumak
için de bu giriş akışı, BufferedReader nesnesine dönüştürülmelidir:
Bu iki deyim aşağıdaki gibi birleştirilebilir:
Bu şekilde BufferedReader sınıfından bir giris nesnesi oluşturulduktan sonra bu sınıfın
readLine isimli metodu kullanılarak kullanıcının komut satırından veri girmesi sağlanabilir.
Program kodu 2.6’ daki işlemin aynısını, komut satırından okuma yaparak gerçekleyen
program aşağıda verilmiştir:
import java.io.*;
public class KomSatOku {
public static void main(String[] args) throws IOException
{
BufferedReader giris = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Lütfen yaşınızı giriniz:");
String yasStr = giris.readLine();
int yas = Integer.parseInt(yasStr);
System.out.println("Yaşınız = "+ yas);
}
}
Program Kodu 2.7: Kullanıcının komut satırından veri girişi
Bu örnek programda dikkat edilmesi gereken nokta, main metodunun yanında throws
IOException biçiminde aykırı durumların ne olacağının belirtilmesidir. Bunun nedenini
aykırı durumların incelendiği bölümde ayrıntılı olarak göreceksiniz.
BÖLÜM-3TEMEL VERİ TİPLERİ
3.1. Değişkenler
* Bu yerleşim şekli Intel marka işlemciler için geçerlidir. Farklı işlemci ailelerinde farklı
yerleşim biçimleri olabilir. Daha deyatlı bilgi için "Little Endian", "Big Endian" konularını
araştırabilirsiniz.
Programlama dillerinde her türlü veri, bir “değişken” (variable) aracılığı ile saklanır ve
kullanılır. Herhangi bir veriyi, daha sonra erişmek üzere saklamak için bir değişken tanımı
yapılır. Bu tanımlama, program çalışırken bu verinin saklanması için belirli boyutta bir
bellek bölgesinin ayrılmasını sağlar. Böylelikle ilgili veri bu bellek bölgesinde saklanır ve
gerek duyulduğunda bu bellek bölgesinden çağrılır.
Bu bellek bölgesine tüm erişimler, tanımlanan bu değişkenin ismi ile yapılır. Örneğin
aşağıda, tamsayı tipinden bir veriyi saklamak üzere bir değişken tanımlaması örneği
verilmiştir:
int kilo;
Örnekte yer alan tanımlama ile kilo isimli bir değişken için bellekte belirli bir yer
ayrılmıştır. Bu değişkenin tipi ise int yani tamsayı olarak belirtilmiştir. Dolayısı ile ayrılan
bu bellek bölgesinde ancak ve ancak bir tamsayı saklanabilir.
Örneğin bu bellek bölgesinde 50, 76, 101 gibi tamsayı değerler saklanabilirken 65,5 ya
da 99,9999 gibi ondalıklı sayılar saklanamaz. Ayrılan bu bellek bölgesine yapılan tüm
erişimler de kilo değişken ismi kullanılarak yapılır.
3.1.1. Değişken İsimleri
Java’da verilen bütün isimler gibi değişken isimleri de birer tanıtıcıdır (identifier). Dilin
yapısı gereği tüm Java tanıtıcılarının uyması gereken bazı kurallar vardır. Örneğin bir
tanıtıcı, herhangi bir karakter, altçizgi ( _ ) ya da dolar işareti ( $ ) ile başlayabilir.
Bunun haricindeki başka bir karakter ile tanıtıcı isimleri başlayamaz.
Java tanıtıcı isimleri, küçük büyük harflere duyarlıdır (case-sensitive). Yani “kilo” ile
“Kilo” tanıtıcıları Java için aynı değildir. Bu nokta, C/C++ dillerini bilmeyen ve Java dilini
öğrenmeye başlayanlar için çok sık karıştırılan ve hata yapılan bir noktadır.
Tanıtıcı isimleri asla Java’da dahili amaçlar için kullanılan anahtar sözcüklerden (keyword)
bir tanesi olamaz. Örneğin yukarıda gösterilen tanımlamada mavi olarak belirtilen int
sözcüğü bir Java anahtar sözcüğüdür. Bu yüzden “int” isimli bir değişken tanımlaması
yapmak hatalara yol açacaktır. Java için tanımlı anahtar sözcüklerin bir listesini bölüm
2.1.1’ de bulabilirsiniz. (Bölüm 2.1.1 görmek için tıklayınız.)
Yukarıda anılan kurallara uymak koşulu ile istenilen tanıtıcı ismi kullanılabilir. Ancak genel
ilke olarak, bir değişken isminin seçiminde, bu değişken ile saklanan bilgiyi açıklayıcı bir
isim seçilmesi uygun olacaktır. Örneğin, “sonOkunanDeger” uygun bir değişken ismi
olabilir. Ancak “d” ya da “ddd” gibi anlamsız isimler geçerli birer tanıtıcı adı olsalar da,
programın okunabilirliğini azaltırlar ve açıklayıcı özellikleri yoktur. Bu yüzden açıklayıcı
isimler kullanılması tavsiye edilmektedir.
3.1.2. Değişken Tanımlama ve İlk Değer Verme
C++ diline çok benzer olarak Java’da değişken tanımlama işleminde önce değişkenin tipi
belirtilir ardından da değişken için geçerli bir isim (tanıtıcı) belirtilir.
Tanımlanan değişkene ilk değer vermek için ise değişken adından sonra eşittir (=)
işareti yazılır ve değişkenin tipine uygun sabit bir değer belirtilir.
Aynı satırda birden fazla değişken tanımlaması da yapılabilir:
3.2. Temel Veri Tipleri
Java dilinde sınıflar dışındaki tüm veriler mutlaka bir temel veri tipi ile temsil edilmek
zorundadır. Bu temel veri tipinin saklayabileceği verinin türü ve boyutu farklıdır.
3.2.1. Sayılar
Sayılar tam sayılar ve ondalıklı sayılar olmak üzere ikiye ayrılır.
3.2.1.1. Tamsayılar
Tamsayı veri tipi adından da anlaşılacağı üzere 10, 11, 20, 30 gibi sadece tamsayı
tipindeki verileri saklayabilirken 13,23 veya 19,99999 gibi ondalıklı sayıları
saklayamazlar.
Java dilinde 4 tip tamsayı bulunmaktadır. Bu ayrımın temel nedeni, her bir tamsayı
tipi için farklı boyutlarda bellek alanı ayrılmasından kaynaklanmaktadır. Bir veri tipi
için ne kadar büyük boyutta bellek alanı ayrılırsa, o alanda o kadar büyük sayılar
saklanabilmektedir. Unutulmaması gereken önemli bir nokta ise, ayrılan bellek alanı
çok büyük de olsa sonsuz değildir. Dolayısı ile bu bellek alanı içerisinde saklanabilecek
tamsayıların mutlaka bir üst sınırı vardır.
Aşağıda tamsayı tipleri, her bir tip tamsayının saklanması için bellekte ayrılan bellek
miktarı, bu tip ile temsil edilebilecek verinin alt ve üst sınırları verilmiştir.
Tablo-3.1: Tamsayı tipleri
Tamsayı veri tipleri için birden fazla veri tipinin var olmasının nedeni, programcılara
uygun veri tiplerini seçmelerine olanak sağlamaktır.
Örneğin bir programcı, hazırladığı programda bütün tamsayı tipli verileri long tipinden
tanımlarsa, gereksiz bellek işgal etmiş olur. Boy, kilo veya yaş gibi sınırlı aralıklarda
değerler alabilen bir veri için 64 bitlik koca bir yer ayırmak, kötü bir programcılık
örneğidir. İyi bir programcı, en uygun boyutta değişken tipi seçmek durumundadır.
Örnek olarak “yaş” bilgisinin saklandığı bir değişkenin tipinin byte olması normaldir
ancak “kilo” bilgisini tutmak için gene byte tipinden bit değişken tanımlamak uygun
değildir. Programcı, değişken tipini seçerken, değişkenin mantıksal olarak alabileceği
değer aralığını önceden kestirmeli ve en uygun aralığa sahip veri tipini seçmelidir.
“Kilo” bilgisi için “byte” tipinden bir değişken yetersiz kalır, 130 kg gibi bilgileri
saklayamaz. Dolayısı ile “kilo” bilgisini saklamak için en uygun tamsayı tipi short
olarak görülmektedir.
3.2.1.2. Ondalıklı Sayılar
Programlama dillerinde ondalıklı sayılar genelde kayan-noktalı (floating-point) sayılar
olarak adlandırılırlar. Bellekte ayrılan sınırlı boyuttaki bir bellek bölgesi içerisinde sınırsız
aralıkta ondalıklı sayı bulundurmak olası olmadığı için, kayan-noktalı sayıların belirli bir
aralıkta sayılar gösterilebilmektedir.
Kayan-noktalı sayılara aşağıdaki örnekler verilebilir:
Görüldüğü gibi örnekteki bütün sayılarda değişen tek şey virgülün yeridir. İngilizcede
sayılar yazılırken ondalıklı kısım için virgül yerine nokta kullanıldığı için bu gösterime
kayan-virgüllü gösterimi yerine kayan-noktalı gösterim adı verilmiştir.
Java dilinde iki tür ondalıklı sayı tipi bulunur:
Bir Java programı içerisinde geçen her türlü ondalıklı-sayı, eğer özel olarak
belirtilmemişse - double tipi olarak yorumlanır. Örneğin bir Java programında yer alan
“2.0” veya “3.756” gibi sabitlerin tipi ön tanımlı olarak double tipi seçilir. Eğer
programcı bu sayıların tipinin float tipinde olduğunu özel olarak belirtmek isterse sayıların
sonuna “f” ya da “F” karakteri ekler. Örneğin “2.0f” veya “3.756F” sayıları, float
tipinde saklanır.
Çok büyük ya da çok küçük ondalıklı sayıların ifade edilmesini kolaylaştırmak için bir
üstel gösterim yöntemi kullanılmaktadır. Örneğin bir Java programında dünyanın
güneşe olan uzaklığını olan 149.600.000 km’ yi ifade etmek için 1.496E8 yazılabilir. Ya
da çok küçük olan bir atomun ağırlığı 0.0000000000000000000000000009 gramı
ifade etmek için 9.0E-28 yazılabilir.
3.2.2. Karakterler
Karakter veri tipinde bir karakterlik bilgiler saklanmaktadır. Çoğu programlama dilinin
aksine Java’da her karakter tipinde her bir değişken, 16 bit bellek alanı kaplar çünkü
Java’da bütün karakterler Unicode olarak saklanır. Diğer dillerde kullanılan 8 bitlik USASCII kodlama sistemindeki karakterlerin aksine, Unicode ile dünya üzerinde kullanılan
birçok dildeki harfler ve karakterler (karakterleri) desteklenmektedir.
Karakter tipinden bir değişken tanımlama örneği aşağıda verilmiştir:
Karakter sabitleri, tek tırnak işareti ( ‘ ) arasında bulunan tek bir karakterden
oluşmaktadır. Sabit değerini tanımlamak için çift tırnak işareti ( “ ) kullanılırsa bu bir
karakter sabiti olmaz. Yani ‘A’ ile “A” aynı Java için farklı iki sabiti ifade eder. Ayrıntılı bilgi
için Karakter Katarları (string) bölümüne bakınız.
Tuş takımı ile veri girişi yaparken standart olarak bütün karakterlerin girişinin yapılması
olası değildir. Örneğin sekme (TAB), silme (Backspace), yeni satır (return)
karakterlerinin yazılması olası değildir çünkü bu karakterler, Java kodunun yazıldığı metin
düzenleyicilerde özel amaçlar için kullanılmaktadır. Özel karakterlerin (escape
characters) yazılabilmesi için “\u” işaretini takip edecek şekilde ilgili karakterin unicode
kodu onaltılık (hexadecimal) düzende yazılmalıdır. Örneğin:
Bu tanımlama ile seciliKarakter değişkeni tanımlanmış ve ilk değer olarak da ‘X’ değeri
atanmış olur.
Bazı özel karakterler çok sık kullanıldıkları için bunlar için özel bir gösterim de
kullanılmaktadır.
3.2.2.1. Karakter Katarları
Ardı ardına gelen karakterlerden oluşan dizilere karakter katarı (string) adı verilmektedir.
Karakter katarı sabitleri çift tırnak içerisinde ( “ ) tanımlanır. Örnek karakter katarları
aşağıda verilmiştir.
3.2.3. Mantıksal Veriler
Doğru ya da yanlış olarak sadece iki değer alabilen veriler, mantıksal (boolean)
veriler olarak isimlendirilirler. Mantıksal veriler Java’da true ve false şeklinde iki hazır
değerden bir tanesini alabilirler. Bir mantıksal değişken tanımlaması aşağıdaki gibi
yapılır:
3.3. Sabitler
Bazı veriler, program boyunca hiç değiştirilmeden kullanılırlar. Örneğin pi sayısı bir
program boyunca hiç değiştirilmeden kullanılır. Başka bir örnek olarak en fazla 100 kişi
alabilen otobüsler için bir program hazırlandığı düşünülsün. Program içerisinde çeşitli
yerlerde geçen 70 ifadesini daha anlaşılır ve açıklayıcı EN_FAZLA_YOLCU gibi bir ifade
ile değiştirmek çok daha uygun olacaktır.
Sabitler, içeriği değişmeyen değişkenler olarak düşünülebilinir. Sabitler için de aynı
değişkenler için ayrıldığı gibi bir bellek alanı ayrılır. Sabitlerin de aynı değişkenler gibi
tipleri bulunur.
Java’da bir değişken tanımlamak için, anahtar sözcüklerden biri olan final sözcüğünün
kullanılması gerekir.
Herhangi bir kısıtlama olmamasına rağmen, neredeyse tüm yazılımcıların ortak olarak
kullandıkları bir yöntem olarak sabitlerin isimleri genelde büyük harflerle yazılır. Bu
sayede normal değişkenler ile sabitler birbirlerinden rahatlıkla ayırt edilebilirler.
Programlarda sabitleri kullanmanın bir başka yararı da sabitlerin değiştiği durumlarda
görülmektedir. Örneğin yukarıda anılan ve en fazla 70 yolcu kapasiteli otobüslerin 100
yolcu kapasiteli yeni modelleri çıktığında, programın değiştirilmesi gereklidir.
Sabitler kullanılmadan hazırlanmış bir programda kullanıcı 70 geçen bütün yerlerde
değişikliğe gitmek zorundadır. Oysa sabitler kullanılarak hazırlanmış bir yazılımda
sadece sabitin tanımlandığı yerde verilen ilk değerin değiştirilmesi yeterli olacaktır.
3.4. Veri Tipleri Üzerinde Tanımlı İşlemler
Bir program tarafından çeşitli veriler saklandığı gibi bu veriler üzerinde de çeşitli
işlemler (operation) yapılmaktadır. Örneğin sayısal veriler üzerinde matematiksel
işlemler yapılabileceği gibi mantıksal veriler üzerinde de VE, VEYA gibi mantıksal
işlemler yapılabilir.
Bu bölümde, hangi veriler üzerinde ne gibi işlemlerin hangi işleçlerle (operator)
gerçeklenebileceği anlatılacaktır.
3.4.1. Atama Deyimi
Atama işlemi, bir değişkenin içerdiği değeri değiştirmek için kullanılır. İşleç olarak eşittir
karakterleri ( = ) kullanılır. Örnek bir atama işlemi:
Atama işlemi yapılan her bir satıra atama deyimi (assignment statement) adı verilir.
İşlecin (=) sağ tarafındaki değer hesaplanarak sonuçta çıkan değer sol taraftaki
değişkende saklanır. Değişkenler konusunda anlatıldığı gibi bir değişken belirli bir anda,
tanımlandığı tipten sadece bir tane değeri saklayabilir.
3.4.2. Aritmetik Deyimler
Sayısal veriler üzerinde gerçeklenebilecek işlemlerden oluşan ifadeye aritmetik
deyimler (arithmetic expression) adı verilir. Çoğu kez ondalıklı sayılar ile tamsayılar
üzerindeki işleçlerin benzer etkileri olsa da bazı durumlarda farklılıklar ortaya
çıkmaktadır. Bu yüzden her iki aritmetik türü ayrı ayrı incelenmiştir.
3.4.2.1. Tamsayı Aritmetiği
Tamsayı tipindeki veriler üzerinde toplama (+), çıkarma (-), çarpma (*), bölme
(/), modunu alma ( % ) gibi ikili (binary) işlemler yapılabilir.
Örnek ifadeler:
Atama işlemlerinin sağ tarafındaki bölümler, aritmetik ifadelerdir.
Tamsayılar arasında yapılan bölme işlemlerinde virgülden sonraki basamaklar ihmal
edilir.
Örneğin,
işlemi sonucunda 9 / 4 = 2,25 sonucunun virgülden sonraki kısmı göz ardı edilerek
tamsayı kısmı olan 2 değeri toplam değişkenine atanır.
Kalanı elde etmek için ise modülo (%) işleci kullanılabilir.
Bu kez 9 / 4 işlemi gerçeklenir ve tamsayı bölüme göre kalan 1 değeri toplam
değişkenine atanır. Farklı tamsayı tipleri olduğu için bazı durumlarda dikkat edilmesi
gerekli noktalar bulunmaktadır.
Örneğin aşağıdaki gibi bir program düşünülsün:
İlk bakışta herhangi bir hata göze çarpmamasına karşın bu program derlenirken hata
ortaya çıkacaktır. Java’da tamsayılar üzerinde gerçeklenen bütün aritmetik işlemler 32
bit üzerinden yapılır. Yani burada 16 bitlik short tipi ile temsil edilen 2 değişken
toplanırken bile bu sayılar önce 32 bite çevrilir ve daha sonra toplanır. Dolayısı ile
ortaya çıkan 32 bitlik sonucun, 16 bitlik değişkene aktarılması hata verecektir.
Sorunun çözümü için tip zorlaması (type casting) yapılmak durumundadır:
3.4.2.2. Ondalıklı Sayı Aritmetiği
Ondalıklı sayılar üzerinde de tamsayılarda olduğu gibi dört temel matematik işlemi olan
toplama (+), çıkarma
(-), çarpma (*), bölme (/) işlemleri uygulanabilir.
Örnek ifadeler içeren bir program aşağıda verilmiştir:
Ondalıklı sayılarda farklılık gösteren bir diğer işleç de modülo (%) işlecidir.
Ondalıklı sayılarda bu işlem hesaplanırken şu yöntem izlenir: 27.3 / 4.3 işleminin sonucu
bölümün tamsayı kısmı tekrar bölen ile çarpılır. Çıkan sonuç ile bölünen sayı arasındaki
fark atama aritmetik ifadenin sonucu olur.
3.4.3. Arttırma ve Eksiltme İşleçleri
Atama işlemi kullanmadan tamsayı değişkenlerin değeri 1 arttırılabilir ya da eksiltilebilir.
Bunun için sırasıyla ++ ve -- işleçleri kullanılır.
Bu işleçler bu haliyle çok cazip olmayan işleçlerdir. Ancak arttırma ve eksiltme işleçlerin
gerçek gücü, bir aritmetik ifade içinde kullanıldığında ortaya çıkar.
Yukarıdaki örnek programda, toplam kalemleri sayısı hesaplanırken maviKalemAdedi
değişkeninden önce -- işleci ile bir eksiltme işlemi bulunmaktadır. Dolayısı ile öncelikle
maviKalemAdedi isimli değişken 1 azaltılır, daha sonra aritmetik ifade hesaplanır.
Bu tek bir atama deyimi, aşağıdaki iki atama deyimi ile aynı işi yapmaktadır:
Yani atama yapıldıktan sonra da maviKalemAdedi isimli değişkenin içeriği 8 olarak
kalmaktadır. Bu işleç, C/C++ dillerini daha önce kullanmış kişiler için bilindik bir işleçtir.
Bu işlecin diğer bir adı da “önceden eksiltme” işlecidir.
Benzer şekilde arttırma işleci de kullanılabilir.
Arttırma ve eksiltme işleçlerin yaptığı iş, bir değişkenin solunda ya da sağında
bulunmalarına göre değişmektedir. Eğer bu işleçler, bir değişkenden önce geliyorsa,
yukarıda anlatıldığı gibi öncelikle bu işleçlerin gereği yerine getirilir daha sonra aritmetik
ifade hesaplanır.
Eğer azaltma ve eksiltme işleçleri bir değişkenin sağında ise, öncelikle aritmetik ifadenin
sonucu hesaplandıktan sonra ilgili işleçlerin gereği yerine getirilir. Örneğin aşağıdaki
programda son atama işleminde, önce aritmetik ifade hesaplanarak 12 değeri bulunur, bu
değer toplamKalemAdedi değişkenine atanır. Daha sonra maviKalemAdedi değişkeni 1
eksiltilerek 8 değerini alır.
Tamsayı aritmetiğinde anlatılanların çoğu, ondalıklı sayı aritmetiği için de ufak farklarla
geçerlidir. Örneğin arttırma ve azaltma işleçleri tamsayılarda ±1 işlemi yaparken ondalıklı
sayılarda ±1.0 işlemi yapmaktadır.
3.4.4. Mantıksal İşleçler
Mantıksal veriler üzerinde VE, VEYA gibi mantıksal işleçlerle bazı işlemler
gerçekleştirilebilir. Bu tür işleçler ile birden fazla işlem bir arada yapılarak sonuçta tek bir
mantıksal bilgiye ulaşılabilir. Bir veya birden fazla mantıksal işleç ile oluşturulan deyime
mantıksal deyim (logical expression) adı verilir.
Mantıksal veriler üzerinde işlem yapan işleçler 5 tanedir:
Ayrıca sayısal veriler üzerinde işlem yaparak mantıksal veriler üreten işleçler de
bulunmaktadır.
Örneği iki sayının karşılaştırılması sonucu, işleç türüne göre “doğru” ya da “yanlış”
şeklinde mantıksal sonuçlar üretilir:
3.4.4.1. VE İşleci
VE işlemi, teorik olarak her iki tarafındaki mantıksal deyimin “doğru” olması
durumunda “doğru” sonucunu üreten, diğer bütün durumlarda “yanlış” sonucunu
üreten bir ikili işleçtir.
Aşağıdaki gibi kullanılır:
VE işleminin doğruluk tablosu aşağıda verilmiştir:
Örnekler:
İki türlü VE işleci bulunmaktadır: mantıksal (&) ve koşullu (&&). Her iki işleç de
temelde aynı işi yapmasına karşın, çalışma biçimleri arasında küçük bir fark vardır.
3.4.4.2. VEYA İşleci
VEYA işlemi, teorik olarak her iki tarafındaki mantıksal deyimin en az 1 tanesinin
“doğru” olması durumunda “doğru” sonucunu üreten, ancak her iki deyimin de
“yanlış” olması durumunda “yanlış” sonucunu üreten bir ikili işleçtir. Aşağıdaki gibi
kullanılır:
VEYA işleminin doğruluk tablosu aşağıda verilmiştir:
Örnek kullanım:
Mantıksal VEYA (||) ve koşullu VEYA (|) işleçlerinin çalışma biçimleri arasındaki
farklar VE işlemindeki farklar ile paraleldir. Ayrıntılı bilgi için lütfen VE işlecine bakınız.
3.4.4.3. DEĞİL İşleci
DEĞİL işlemi, bir mantıksal deyimi “doğru” ise “yanlış” sonucunu, deyim “yanlış” ise
de “doğru” sonucunu üreten tek işlenenli mantıksal bir işlemdir. Kullanımı aşağıdaki
gibidir:
Doğruluk tablosu aşağıda verilmiştir: DEĞİL
Örnek kullanım:
Bu işlem ile yaşı 16’ dan küçük ve 65’ den büyük olanlar için “doğru” sonucu elde edilir.
3.4.4.4. YADA İşleci
YADA işlemi, bir mantıksal deyimi iki işlenen birbirinin aynısı ise “yanlış”, birbirinden
farklı ise “doğru” sonucunu üreten ikili bir işleçtir. Aşağıdaki gibi kullanılır:
3.5. Bit Bazında İşlem Yapan İşleçler
Sadece tamsayılar ve karakterler üzerinde bit bazında işlem yapan işleçlere (bitwise
operators) bu bölümde değinilecektir. Aşağıda bu işleçler listelenmiştir:
Bu işleçler, aynen mantıksal veriler üzerinde çalıştığı gibi bitler üzerinde de işlem görür.
Ancak mantıksal verilerdeki “doğru” değeri bit değeri olarak 1, “yanlış” değeri de 0
olarak gösterilir.
Aşağıda bit bazında yapılabilecek bütün mantıksal işlemler gösterilmiştir:
Bu işlemler bit bazında yapılır. Ancak uygulandıkları tamsayı ya da karakter tipindeki
değişkenin bütün bitleri üzerinde gerçeklenir. 8 bitlik byte tipindeki veriler üzerinde bit
bazında yapılan bazı örnek işlemler aşağıda verilmiştir:
Öteleme işlemleri de bit bazında yapılan ikili işlemlerdir. Bu işlemde işlenenlerden ilki,
bitleri ötelenecek veriyi, ikinci işlenen ise öteleme işleminin kaç defa yapılacağını belirtir.
İşlemden önce bölüm 3.9.2’de anlatılacak olan aritmetik yükseltme işlemi gerçeklenir ve
eğer varsa short veya byte tipindeki değerler int tipine yükseltilir. Eğer işlenenlerden
sadece bir tanesi long tipinde ise, diğer işlenen de long tipine dönüştürülür.
3.5.1. Sola Öteleme İşlemleri
Örneğin aşağıdaki ifade ile sayi isimli değişkenin bitleri birer sola ötelenir ve en sağda
boşalan iki bitlik yere sıfır (0) değerleri yerleştirilir.
Bu işlem öncesinde
gibi ise işlemden sonra bu değerler aşağıdaki gibi değişir:
Görüldüğü gibi her bir sola öteleme işlemi sayıyı 2 ile çarpmaktadır. Burada 3 defa
öteleme işlemi yapıldığı için sayı değeri 2.2.2=8 ile çarpılmış olmaktadır. Doğaldır ki en
yüksek anlamlı bitlerde veri varsa, öteleme sonrasında bu bitler kaybolacaktır. Dolayısı ile
bu öteleme işlemi ile çarpma yaparken dikkat edilmeli ve veri kayıpları olasılıkları
değerlendirilmelidir.
3.5.2. Sağa Öteleme İşlemi
Sağa öteleme işlemi ise iki türlüdür; aritmetik ve mantıksal öteleme.
Mantıksal sağa öteleme işlemi, sola öteleme işleminin sağ tarafa yapılan türüdür. Her
bir bit bir (ya da belirtilen basamak kadar) sağına kayar, en sağdaki (en düşük anlamlı)
bit kaybolur, en soldaki (en yüksek anlamlı) bit boşalır ve buraya sıfır (0) yazılır.
Bu işlem öncesinde;
gibi ise işlemden sonra bu bitlerin sıralanışı aşağıdaki gibi değişir:
Görüldüğü gibi her bir mantıksal sağa öteleme işlemi sayıyı ikiye bölmektedir. 3 defa
öteleme işlemi yapıldığından sayı, 2.2.2=8 ile bölünmüştür.
Sağa öteleme işlemi pozitif sayılar için doğru çalışmaktadır ancak negatif sayılarda
bölme işlemi hatalı sonuçlar üretmektedir. Örneğin eğer sayı değeri aşağıdaki gibi
olsaydı:
Yukarıdaki gibi ifade edilen bir sayı mantıksal sağa ötelemede yapıldığı şekilde sağa üç
defa ötelenirse:
hatalı sonucu elde edilirdi. Negatif sayılarda gerçeklenen sağa öteleme işleminde,
ötelenen sayının işaretini koruyabilmesi için aritmetik sağa öteleme işleminin
gerçekleştirilmesi gereklidir. Aritmetik sağa öteleme işleminde, sayının bütün bitleri, bir
(ya da belirtilen basamak kadar) sağa kaydırılır, en düşük anlamlı bitler kaybolur. En
yüksek anlamlı bit(ler) ise doğrudan sıfır (0) ile doldurulmaz. Eğer sayı pozitif ise sıfır
(0) ile doldurulur, eğer sayı negatif ise bir (1) değeri ile doldurur. Örneğin;
Bu aritmetik sağa öteleme işlemi sonrasında aşağıdaki değer oluşur:
Yukarıda bit bazında değerler ile verilen bütün örnekler her ne kadar 16 bit üzerinden
verilmişse de bu işlemler 32 ve 64 bitlik veriler için de geçerliliğini korumaktadır.
3.6. Atama ve Diğer İşlemleri Birleştiren İşleçler
Aynen C/C++ dillerinde olduğu gibi atama ve diğer işlemleri birleştirerek daha hızlı yazım
sağlayan bazı kısayol işleçleri bulunmaktadır.
Genelde veriler üzerindeki işlemlerin çoğunda bir işlenen, atama yapılacak değişken ile
aynıdır. Örneğin,
şeklinde bir değişken üzerinde bir işlem yapılarak çıkan sonucun yine aynı değişkende
saklanmasını sağlayan ifadeler çok karşılaşılan ifadelerdir. Bu tür işlemleri daha hızlı
yazmak için atama işlemi ile diğer işlemi bir arada yapan bazı kısayol işleçleri
tanımlanmıştır.
Örneğin aşağıdaki deyim, yukarıda verilen ve toplam değişkeninin değeri 1 arttıran deyim
ile aynı işi yapmaktadır:
Aşağıda bazı işleçler ile bunların kısayol işleçleri verilmiştir:
3.7. İşleç Öncelikleri
Bir ifade içerisinde birden fazla işleç bir arada bulunabilir. Üstelik bu işleçlerden bazıları
aritmetik işleçler iken bazıları da mantıksal işleçler olabilir. Bu deyimlerin sonuçları
hesaplanırken bazı işlemlerin önceliği yüksek olduğundan önce gerçeklenmektedir.
İşlem önceliğini sağlamak üzere en kesin yol, önce yapılması gereken işlemleri parantez
içerisinde yazmaktır.
Örnekten de görülebileceği gibi iç içe parantezler kullanarak öncelikler belirlenebilir. En
içteki parantez ilk önce hesaplanır. Bu örneğin sonucu ise 54’tür. Parantezler ile
öncelikler belirlenirken dikkat edilmesi gereken en önemli nokta parantezlerin doğru bir
şekilde yerleştirilmesidir. Açılan parantezler kapatılmazsa derleme sırasında hata
oluşturulur.
Eğer parantezler hangi işlemin önceliğini kesin olarak belirlemiyorsa, işleçlerin kendi
öncelikleri devreye girerek hangi işlemin daha önce yapılacağını belirler. Eşit öncelikli
işlemler için soldan sağa doğru hesaplama yapılarak gidilir.
3.7.1. İşleç Öncelikleri Tablosu
Eğer parantezler hangi işlemin önceliğini kesin olarak belirlemiyorsa, işleçlerin kendi
öncelikleri devreye girerek hangi işlemin daha önce yapılacağını belirler. Eşit öncelikli
işlemler için soldan sağa doğru hesaplama yapılarak gidilir.
İşleçlerin öncelik seviyeleri aşağıdaki tabloda verilmiştir:
Tablo 3.4: İşleç öncelikleri
Yukarıdaki aritmetik deyimler, aynı sonucu üreten ve aynı işlemlerin aynı sırayla yapıldığı
deyimlerdir. Sonda yer alan iki adet “-” işaretinden sağdakinin 9 sayısını negatif yapmak
için kullanıldığına dikkat ediniz!
3.8. Tip Dönüşümleri
Tamsayılar üzerindeki aritmetik işlemler bölümünde de anlatıldığı gibi bazı durumlarda,
değişkenler için bellekte ayrılan yerlerin boyutları farklı olduğu için sayısal veriler
üzerindeki bazı işlemler veya atamalar sonucunda veri kaybı yaşanabilmektedir. Örneğin
short veri tipinde 2000 sayısını içeren bir veriyi byte tipindeki bir değişkene atarken
verinin üst kısmındaki bitler kaybolur ve sonuçta byte tipindeki değişkenin değeri 2000
sayısı yerin 16 olarak değişir.
Bir temel tipten diğerine dönüşümler ikiye ayrılır. Genişleyen dönüşümler ve daralan
dönüşümler.
Genişleyen dönüşümlerde, veri, bulunduğu temel veri tipinden daha büyük bir bellek
bölgesinde tutulan daha büyük bir veri tipindeki alana aktarılmaktadır. Örneğin short
tipindeki 2000 sayısı, int tipindeki bir alana aktarıldığında 16 bitlik bir saklama alanından
32 bitlik bir saklama alanına kopyalanır. Dolayısı ile herhangi bir kayıp olası değildir,
aktarılan değer olduğu gibi hedef alana koyulur. Genişleyen dönüşümler bu yüzden
güvenli dönüşümlerdir.
Her ne kadar genişleyen dönüşümlerde bir kayıp söz konusu değilse de ondalıklı sayı
gösterime dönüştürmelerde duyarlık (precision) kaybı olabilmektedir. Örneğin int ve long
tipindeki bir tamsayı float tipine ya da long tipindeki bir veri double tipine dönüştürülürse,
virgülden sonraki en sağdaki (en düşük anlamlı) basamaklardan bazıları yanlış olabilir.
Daralan dönüşümlerde ise hataya ve veri kaybına açık dönüşümlerdir. Bu dönüşümler,
büyük bir saklama alanından daha küçük bir saklama alanına kopyalanan veriler için söz
konusudur.
Java dilinde tip dönüşümü 3 şekilde yapılmaktadır.
3.8.1. Atama Dönüşümü
Bu tür tip dönüşümleri, bir veri tipinden diğer bir veri tipine atama yaparken
gerçekleşir. Sadece genişleyen dönüşümler gerçeklenebilir. Eğer agirlik değişkeni
float tipinde ise kilo değişkeni de int tipinde ise aşağıdaki atama kilo değişkenini
otomatik olarak float tipine dönüştürür:
Bu atamanın tersi yapılmış olsaydı derleme aşamasında derleyici bir hata iletisi
üretecektir. Eğer bu atama işleminin gene de yapılması isteniyorsa da tip zorlaması
(casting) kullanılmalıdır.
3.8.2. Aritmetik Yükseltme
Aritmetik yükseltme (arithmetic promotion), aritmetik ifadelerde işleçlerin,
işlenenleri değiştirmeye gerek duyduğu zaman gerçeklenir. Örneğin aşağıdaki kod
içerisinde
toplam değişkeni float tipinde adet değişkeni ise int tipinde ise, bölme işlemi
gerçeklenmeden önce adet değişkeni otomatik olarak float tipine dönüştürülür.
3.8.3. Tip Zorlaması
Tip zorlaması (casting), programcının bilinçli olarak bir veri tipinden diğerine dönüşüm
yapmak istediğini belirtmesi anlamına gelmektedir. Programcı, dönüştürmek istediği
yeni veri tipini parantez içerisinde yazarak derleyiciye “ben bu değeri bu tipte
istiyorum” demiş olur.
Burada agirlik değişkeni float tipinde, kilo değişkeni ise int tipindedir. Dolayısı ile bu
dönüşüm daralan bir dönüşümdür. Örneğin agirlik değişkeninin değeri 96.7 ise atama
işleminden sonra kilo değişkeninin değeri 96 olur. Yani veri kırpılarak aktarılır. Tip
zorlamalarında kaybolan verinin sorumluluğu tamamen programcıya aittir.
Tip zorlamaları genelde geçici tip dönüşümleri yapmak için kullanılır. Örneğin aşağıdaki
deyimi ele alalım:
Burada toplam ve adet int tipindedir dolayısı ile bölme işlemi normalde tamsayı bölmesi
olarak gerçeklenecek ve sadece tamsayı sonuç üretilecektir. Oysa ortalama isimli
değişken float tipindedir ve bölme işleminin sonucunun ondalıklı sayı olarak
hesaplanması ve bu sayının da ortalama değişkenine aktarılması istenmektedir. Bu
durumda yukarıdaki örnekteki gibi tip zorlaması kullanılır.
Tip zorlamaları, diğer birçok işleçten yüksek önceliğe sahip olduğu için bölme işleminden
önce tip zorlaması işlemi yapılır ve toplam değişkeni int tipinden float tipine
dönüştürülür. Daha sonra bölme işlemi yapılırken float tipinden bir verinin, int tipinden
bir veriye bölündüğü görülür ve int tipindeki adet değişkeni aritmetik yükseltme ile
float tipine dönüştürülerek bölme işlemi yapılır. Doğal olarak çıkan sonuç float tipinden
olur ve bu değer de ortalama isimli değişkene atanır.
Tip zorlamasının önceliği, diğer birçok işleçlerden yüksek olduğu için tip zorlaması bölme
işleminden sonra çıkan sonuç üzerinde değil, bölme işleminden önce sadece toplam
isimli değişken için gerçekleştirilir.
BÖLÜM-4DENETİM İFADELERİ
4.1. Java Dilinde Denetim İfadeleri
Bir programın komutlarının yürütülme sırası “program akışı” (flow of control) olarak
adlandırılmaktadır. Diğer çoğu programlama dilinde olduğu gibi Java dilinde de program,
ilk komutundan yürütülmeye başlanır ve aksi belirtilmedikçe son komut çalıştırılıncaya
kadar program komutları ardı ardına yürütülür.
Bir programın akışı, iki yöntem ile değiştirilebilir: denetim ifadeleri (conditionals) ve
döngüler.
Denetim ifadeleri çoğunlukla seçim ifadeleri olarak adlandırılırlar çünkü bir adım sonra
çalışacak komutun seçilebilmesine olanak verirler. Java dilinin denetim ifadeleri, if-else
deyimleri veya switch deyimi ile oluşturulur. Her iki deyim ile bir sonraki adımda
çalıştırılacak komutun seçilmesi gerçekleştirilir. Bu seçim, deyim içerisinde belirtilen bir
boolean ifadenin sonucu hesaplanarak gerçekleştirilir.
4.1.1. if Denetim Deyimi
Birçok dilde de bulunan if deyimi Java dilinde de program akışını değiştirmek için
kullanılır. Örnek bir kullanım aşağıda verilmiştir:
Bir if denetim deyimi oluşturmak için, if anahtar sözcüğünün devamında parantez içine
alınmış bir mantıksal ifade (boolean expression) yerleştirilmelidir. Mantıksal ifade
hesaplandığında, sonuç “doğru” ise bir alt satırdaki komut yürütülür. Eğer mantıksal
ifadenin sonucu “yanlış” ise bir alt satırdaki komut atlanır ve bu komut yürütülmeden
devam eden ilk komuttan çalışmaya devam edilir.
Yukarıdaki örnekte, toplam değişkeninin değeri, kritikMiktar değişkeninin değerinden
büyük ise toplam değişkeninin değeri kritikMiktar’a çekilmektedir. Eğer toplam,
kritikMiktar’ ı aşmıyorsa, herhangi bir işlem yapılmadan (toplam = kritikMiktar satırı
yürütülmeden) program devam eder.
Örnekte, atama işleminin if deyiminden bir sonraki satıra yazılması önemlidir. Her ne
kadar, bu atama işlemi if deyimi ile birlikte bir tek komut gibi yürütülse de, bu tür bir
yazım tarzı, programın okunabilirliğini arttırmaktadır. Bu sayede atama işleminin, if
deyiminin sonucuna bağlı olduğu rahatlıkla görülebilir. if deyimlerinin akış şemasındaki
gösterimi şu şekildedir:
Aşağıdaki örnekte yer alan program ile kullanıcıdan bankamatikten çekmek istediği para
sorulmakta ve bu para miktarı ekrana yazdırılmaktadır. Ancak eğer kullanıcı
bankamatikten, günlük üst sınırdan fazla çekmek isterse girdiği miktar, bu üst sınıra
çekilmektedir.
4.1.1.1. Örnek
Aşağıdaki örnekte yer alan program ile kullanıcıdan bankamatikten çekmek istediği para
sorulmakta ve bu para miktarı ekrana yazdırılmaktadır. Ancak eğer kullanıcı
bankamatikten, günlük üst sınırdan fazla çekmek isterse girdiği miktar, bu üst sınıra
çekilmektedir.
public class Bankamatik
{
public static void main(String[ ] args)
{
// Üst sınır sabiti
final int UST_SINIR = 400;
// Çekilecek para miktarı
int miktar = 450;
if (miktar > UST_SINIR)
miktar = UST_SINIR;
System.out.print(“Cekebileceginiz miktar: ” + miktar);
}
}
Program Kodu 4.1 – Bankamatik Örneği
4.1.2. if – else deyimi
Bazı durumlarda bir koşul doğru ise bir komut, yanlış ise bir diğer komut çalıştırılmak
istenebilir. Bu tür durumlarda if deyiminin genişletilmiş türü olan if-else deyimi
kullanılabilir. Örnek bir if-else deyimi aşağıda verilmiştir:
Bu örnekte eğer toplam değişkeninin değeri kritikMiktar değişkeninin değerinden büyük
ise toplam’ ın değeri kritikMiktar’ a eşitlenir. Eğer değilse, toplam’ın değeri 1 arttırılır.
else anahtar sözcüğü barındıran if deyimlerinde, eğer koşul sağlanırsa (mantıksal ifade
sonucu “doğru” olarak hesaplanırsa), if sözcüğünün altındaki komut çalıştırılır. Eğer
koşul sağlanmazsa (mantıksal ifade sonucu “yanlış” olarak hesaplanırsa) bu kez else
anahtar sözcüğünün altındaki komut çalıştırılır.
if-else denetim deyiminin akış diyagramındaki gösterimi aşağıdaki gibidir:
4.1.2.1. Örnek
Program kodu 4.2’de, örnek bir if-else deyimi verilmiştir.
public class Notmatik
{
public static void main(String[] args)
{
// Öğrencinin notu
int not=45;
// Geçme notu sabiti
final int GECME_NOTU_SINIRI=50;
if (not >= GECME_NOTU_SINIRI)
System.out.print(“Tebrikler! Dersten gectiniz”);
else
System.out.print(“Uzgunum, dersten kaldiniz.”);
}
}
Program Kodu 4.2 – Ders geçme örneği
Örnek programda kullanıcının girdiği not, geçme notu sınırı olan 50’nin üzerinde ise
ekrana “Tebrikler! Dersi gectiniz” şeklinde bir ileti çıkartılmaktadır. Eğer not >
GECME_NOTU_SINIRI mantıksal ifadesi “doğru” değilse bu kez else anahtar
sözcüğünden sonraki komut çalıştırılır ve ekrana “Uzgunum, dersten kaldiniz.”
şeklinde bir ileti çıkartılır.
4.1.3. else-if Deyimi
Tek bir else deyimi ile if koşulu sağlanmaması halinde hangi komutun yürütüleceği
belirtilebilmektedir. Ancak eğer bu ilk koşulun sağlanmaması halinde başka koşullara
göre değişebilen birden fazla alternatif akış varsa else if deyimi kullanılmalıdır.
Aşağıda else if deyiminin akış şemasındaki karşılığı verilmiştir:
Şekil 4.3: else – if deyiminin akış şemasındaki gösterimi
public class Ders
{
public static void main(String[] args)
{
// Harfli not
int not = 45;
if (not >= 90)
System.out.print(“Harfli
else if (not >= 80)
System.out.print(“Harfli
else if (not >= 70)
System.out.print(“Harfli
else if (not >= 60)
System.out.print(“Harfli
else if (not >= 50)
System.out.print(“Harfli
else if (not >= 40)
System.out.print(“Harfli
else if (not >= 30)
System.out.print(“Harfli
else
System.out.print(“Harfli
}
}
Not : AA”);
Not : BA”);
Not : BB”);
Not : CB”);
Not : CC”);
Not : DC”);
Not : DD”);
Not : FF”);
4.1.4. Komut Blokları
Şimdiye kadar anlatılan denetim ifadelerinin hepsinde koşula bağlı olarak sadece tek
bir komut çalıştırılmıştır. Ancak bazen birkaç komutun bir bütün olarak yürütülmesi ya
da yürütülmemesi bir koşula bağlı olabilir. C/C++ dillerinde de olduğu gibi Java dilinde
de her bir komut satırının yerine birden fazla komut yerleştirilebilir. Bunlara “komut
blokları” (block statement) adı verilir.
4.1.4.1. Örnek
Örneğin, Program Kodu 4.3’ de yer alan programda if sözcüğünden sonra ya da else
sözcüğün sonra yürütülmesi istenen komut sayısı birden fazla olsaydı, bu komutlar bir
komut bloğu içerisinde yazılmalıdır. Bu durum Program Kodu 4.4’te gösterilmiştir.
public class Ders
{
pulic static void main(String[] args)
{
// Üst sınır sabiti
final int GECME_NOTU_SINIRI = 50;
// Dönem sonu notu
int not = 34;
if (not > GECME_NOTU_SINIRI)
{
System.out.print(“Tebrikler! \n”);
System.out.print(“Dersten gectiniz.”);
}
else
{
System.out.print(“Uzgunum! \n”);
System.out.print(“Dersten kaldiniz.”);
}
}
}
Program Kodu 4.4 – Ders geçme örneği (komut blokları ile)
Örnek programda eğer if deyiminin koşulu doğru ise hemen altta küme parantezleri
arasına alınmış iki komut arka arkaya çalıştırılır. Eğer koşul yanlış olursa bu kez else
sözcüğünden sonra küme parantezleri içerisine alınmış iki komut arka arkaya yürütülür.
Örnek program çıktısında kullanıcı ders notu olarak 45 girmiştir.
Bu not 50’den küçük olduğu için koşul (not > GECME_NOTU_SINIRI) mantıksal ifadesi
hesaplanmış ve “yanlış” olduğu belirlenerek else sözcüğünden sonraki iki komut
çalıştırılır. Bu komutlardan ilki ekrana “Uzgunum!” yazısını yazdırdıktan “\n” karakteri,
yeni satır karakteri olduğu için için ekranda bir sonra yazılacak karakter, yeni bir satırın
başından itibaren yazdırılır. İkinci komut ise “Dersten kaldiniz.” yazısını ekrana yazar.
Blokların başını görmek genel kolay olurken, iç içe geçmiş bloklarda hangi küme kapama
parantezinin ( } ) hangi bloğu bitirdiği çok kolay anlaşılmayabilir. Bir programcılık
alışkanlığı olarak blok bitişi işaretinin bulunduğu satırda, bu bloğun hangi nedenle
açıldığının (if, else, for, while vb.) belirtilmesi, programın daha sonra okunabilirliğini
arttırmaktadır.
Çoğu programlama dilinde olduğu gibi Java dilinde programlamaya yeni başlayanların çok
sık düştükleri komutların işletilme sıraları ile ilgili bir hata bulunmaktadır. Aşağıdaki
örnek program parçasını ele alalım:
Yukarıdaki program parçasında if deyiminden sonra koşul doğru ise çalışacak iki atama
komutu varmış gibi görülmektedir. Ancak bu sadece hizalamadan dolayı böyle
görülmektedir. Yukarıdaki gibi if deyiminden sonra koşula bağlı olarak birden fazla
komutun yürütülebilmesi için mutlaka bu komutları içeren bir komut bloğu
oluşturulmalıdır.
Yukarıdaki örnekteki program parçası derlendiğinde, sadece ilk atama işlemi koşula bağlı
olarak yapılacak ya da yapılmayacaktır. İkinci atama işlemi, programın devamında yer
alan sıradaki başka bir komuttur. Yukarıdaki program aslında aşağıdaki program ile aynı
programdır:
Burada, programlamaya yeni başlayanları bekleyen en büyük tehlike hizalamanın
görünüşüdür. Sırf üstteki atama ile aynı hizadan başladığı için komutlar bir blok
oluşturmazlar.
Komut blokları, Java dilinde yazılmış bir program kodundaki her bir komut satırının yerine
yerleştirilebilir. Sadece if deyiminin devamı komut bloğu olabileceği gibi sadece else kısmı
da komut bloğu olabilir. if ifadesinin her iki kısmı da komut bloğu olarak (örnek Program
Kodu 4.3’teki gibi) yazılabilir.
4.1.5. İç İçe if Deyimleri
Bazı durumlarda, bir denetim ifadesinde bir koşula göre karar verdikten sonra bir başka
koşula göre tekrar bir karar verilmesi gerekebilir. Bu tür ifadeler, iç içe koşul denetimleri
(nested if statements) adı verilmektedir. Aşağıda iç içe geçmiş bir koşul denetimi örneği
gösterilmiştir:
public class UcSayininEnKucugu
{
public static void main(String[] args)
{
int enKucuk=0, sayi1, sayi2, sayi3;
sayi1 = 30;
sayi2 = 14;
sayi3 = 55;
if (sayi1 < sayi2)
if (sayi1 < sayi3)
enKucuk = sayi1;
else
enKucuk = sayi3;
else
if (sayi2 < sayi3)
enKucuk = sayi2;
else
enKucuk = sayi3;
System.out.print(“En kücük sayi:” + enKucuk);
}
}
Program Kodu 4.5 – Üç sayıdan en küçüğünü bulan program
Örnek programda ilk önce sayi1 ile sayi2 karşılaştırılır. Eğer sayi1, sayi2’den küçükse
bu kez sayi1 ile sayi3 karşılaştırılır. Eğer sayi1, sayi3’den de küçükse en küçük sayı
sayi1 olarak saklanır, eğer değilse en küçük sayı sayi3’ tür. Benzer bir karşılaştırma,
sayi1’in sayi2’den büyük olmaması durumunda (else kısmı), sayi2 ile sayi3 arasında da
yapılarak en küçük sayı bulunur.
Şekil 4.4 – Program Kodu 4.5’in akış şeması
İç içe koşul denetimlerinde dikkat edilmesi gereken bir konu, else ifadesinin hangi if
deyiminin bir parçası olduğunun doğru olarak yorumlanmasıdır.
Örneğin Program Kodu 4.4’ de yer alan ilk else deyimi, kendinden önceki ilk if
deyiminin yani if (sayi1 < sayi3) deyiminin bir parçasıdır. Bu durum, hizalamadan da
anlaşılmaktadır.
Bir else deyiminin, kendinden bir önceki if deyimine değil de ondan önceki if deyimine
ait olması gerekiyorsa, bu işlem küme parantezleri kullanılarak sağlanabilir.
Bu örnekte else deyimi kendinden bir önceki if deyimine bağlı değildir çünkü ilk if
deyiminden sonra gelen ikinci if deyimi bir komut bloğu içerisine alınmıştır. Bu blok, tek
bir komut gibi düşünülebilir. Dolayısı ile else deyimi, ilk if deyiminin bir parçası olur.
4.1.6. Switch Deyimi
Java’nın bir başka koşul denetim deyimi ise, tek bir değere göre birden fazla yoldan bir
tanesi seçilmesini sağlayan switch deyimidir.
Bu deyim ile bir ifade hesaplandıktan sonra ortaya çıkan değer, daha önceden belirlenen
durumlardan (case) bir tanesine uyuyor mu diye karşılaştırılır. Eğer sonuca uyan bir
durum bulunursa, bu durum altında tanımlanan komut(lar) yürütülür.
switch deyiminin temel şablonu aşağıdaki gibidir:
4.1.6.1. Örnek
Aşağıda switch deyimi kullanan örnek bir program parçası verilmiştir:
switch deyimi çalıştırılırken öncelikle ifade hesaplanır. Daha sonra hesaplanan ifade
sonucuna uyan bir durum araştırılır. Sonuç ile uyan bir durum bulunursa bu durumda
belirtilen komut(lar) çalıştırılır. Eğer uygun bir durum bulunamazsa ve default
anahtar sözcüğü kullanılmışsa, default yani ön tanımlı olarak belirtilen komut(lar)
çalıştırılır.
Her bir durumun komutunu break anahtar sözcüğü takip etmek zorundadır. Böylelikle
yürütme işlemi durdurulur ve program akışı, switch deyiminden bir sonraki komut
yürütülerek devam edilir. Eğer break deyimi kullanılmazsa, koşul ifadesinin sonucuna
uyan durumun komutları çalıştırıldıktan sonra program akışı bir sonraki durumun
komutları yürütülerek devam ettirilir. Bu ise çoğunlukla istenen bir durum değildir.
Örneğin aşağıdaki program parçası ele alınsın:
Bu örnekte çeşitli program komutları çalıştıktan sonra evetHayir isimli değişkende ‘h‘
veya ‘H‘ varsa hayır seçeneğinin seçildiği, ‘e‘ veya ‘E‘ varsa evet seçeneğinin seçildiği
sınanmak istenmektedir. Dikkat edilirse evetHayir isimli değişkende ‘h’ değeri varsa,
ilgili duruma gidilir.
Bu durumda herhangi bir komut yoktur. Ancak break ifadesi de yoktur. Dolayısı ile
program akışı bir sonraki durumun, yani ‘H’ durumunun, komutlarını çalıştırarak
devam ettirilir ve ekrana hayır seçeneğinin seçildiğine dair bir ileti yazdırılır. Daha
sonra break ifadesi ile karşılaşılır ve program akışı, switch deyiminden sonra gelen
komut çalıştırılarak devam ettirilir.
switch deyiminde yer alan koşul ifadesinden hesaplanan değer mutlaka int ya da char
tipinden olması gerekmektedir. Bu değer, boolean, float gibi tiplerde olamayacağı
gibi byte, short, long gibi diğer tamsayı tipleri de olamaz.
Ayrıca her durum ifadesinde yer alan durum değeri sabit olmak zorundadır. Yani case
ifadesinden sonra mutlaka sabit bir değer gelmek durumdadır, bu değer kesinlikle bir
değişken ya da başka bir ifade olamaz.
Aslında switch deyimi, birçok durumu kontrol etmek için yazılan iç içe if deyimleri ile
gerçeklenebilir. Ancak böyle bir gerçekleme, programın anlaşılabilirliğini düşürdüğü
için bir ifade için birden çok değerle karşılaştırma yapılacak ise switch deyiminin
kullanılması daha uygun olur.
4.1.7. Koşullu İşleç
Bu özel işleç, aslında koşul denetimleri ile yapılabilecek bazı çok sık kullanılan
işlemleri basitleştirmek amacı ile yapılmıştır. Bu işleç, şimdiye kadar öğrenilen
işleçlerin aksine üçlü bir işleçtir. Koşullu işleç (conditional operator) aşağıdaki gibi
kullanılır:
Koşullu işlecin simgesi ( ? ) işaretidir. İşlecin devamında ise iki ifadeyi birbirinden
ayıran iki nokta üst üste ( : ) işareti bulunmaktadır. İşlecin solundaki mantıksal ifade
hesaplanarak ortaya çıkartılan sonuç “doğru” ise ifade_1, “yanlış” ise ifade_2
dönüş değeri olarak döndürülür.
Koşullu işleç, diğer işleçler gibi işlenenler (3 adet) üzerinde işlem yaptıktan sonra bir
sonuç değeri döndürür. Bu değer de başka komutlar tarafından kullanılır. Örneğin
dönen sonuç değeri, bir değişkenin yeni değeri olarak atanabilir:
Bu örnek, Program Kodu 4.3’ teki örnek ile aynı işlevi gerçekleştirmektedir. Eğer
toplam değişkenin değer, kritikMiktar’ ı aşmışsa, toplam’ın değeri kritikMiktar’ a
çekilir. Eğer aşmamışsa, toplam’ın değeri 1 arttırılır.
Her ne kadar bazı yerlerde kullanışlı ve pratik çözümler getirse de, koşul işlecinin
okunabilirliği daha az olduğu için genelde if-else yapısının kullanılması daha uygun
olmaktadır.
4.1.8.Karakter Katarlarının Karşılaştırılması
Sayısal veriler gibi karakter tipindeki veriler de karşılaştırma işleçleri ile
karşılaştırılabilirler. Bu karşılaştırma, karakterlerin Unicode karakter kümesindeki
sıralarına göre yapılır. Örneğin bu kümede (ASCII kümesinde olduğu gibi) ‘a’ karakteri
‘z’ karakterinden önce gelmektedir (yani küçüktür). Karakterlerin karşılaştırılması için
Unicode karakter kümesinin incelenmesi yeterli olacaktır.
Karakter tipindeki veriler üzerinde karşılaştırma işleminin yapılabilmesi, karakter
katarlarının da karşılaştırabilir hale getirmektedir. Karakter katarlarının
karşılaştırılmasında sözlük sırasına (lexicographic) benzer bir yöntem
kullanılmaktadır. Ancak bu yöntemde karakterler alfabetik sırasına göre değil,
Unicode karakter kümesindeki sırasına göre sıralanır.
Ancak bilinen karşılaştırma işleçleri, karakter katarlarını karşılaştırma amacı ile
kullanılamaz. String sınıfında, bu işlevleri yerine getirmek üzere hazırlanmış metotlar
bulunmaktadır.
Örneğin iki karakter katarının eşit olup olmadığının sınanması amacı ile equals isimli
bir metot kullanılmaktadır:
Bu metot, iki karakter katarı eşit ise “doğru” değerini döndürmektedir.
İki karakter katarı arasında isim1 == isim2 şeklinde bir eşitlik karşılaştırması
karakter katarlarının eşit olup olmadığını sınamaz ancak String tipinden isim1 ve
isim2 adlarındaki iki nesnenin referanslarının aynı nesneyi gösterip göstermediğini
sınamaktadır.
İki karakter katarlarının sıralarının belirlenmesi için ise compareTo metodu
kullanılmaktadır.
Bu metodun sonucunda, eğer isim1, sözlük sırasına göre isim2’ den önce ise negatif
bir sonuç döndürmektedir. Eğer iki karakter katarı aynı ise 0 değerini döndüren
compareTo metodu isim1, sözlük sırasına göre isim2’ den sonra geliyorsa 1 değerini
döndürmektedir. Sözlük sırasına göre yapılan karşılaştırmada büyük-küçük harf
ayrımının olduğu ve sözlük sırasını değiştirdiği akılda tutulmalıdır.
4.1.9. Ondalıklı Sayıların Karşılaştırılması
Ondalıklı sayıların karşılaştırılması programlarda dikkat edilmesi gereken hassas bir
konudur. Ondalıklı sayıların karşılaştırılmasında eşittir ( == ) işlecinin kullanılması
genelde istenilen sonucu vermemektedir.
İki ondalıklı sayının eşittir işleci ile karşılaştırıldığında eşit olarak çıkması için her iki
sayının da bütün bitlerinin aynı olması gerekmektedir.
Özellikle aritmetik ifadelerin sonucunda ortaya çıkan değerler söz konusu olduğunda, iki
sayı birbirine çok yakın olsa bile bu iki sayı arasında çok çok küçük basamak
değerlerinde farklılık olması çok yüksek olasılıktır. Bu yüzden iki ondalıklı sayı
birbirlerine çok yakın olsalar dahi genelde eşittir işlecinin sonucu “doğru” olarak
çıkmaz.
İki ondalılı sayının birbirine eşit olup olmadığını sınamak için en iyi yol, bu iki sayı
arasındaki farkın mutlak değerinin, belirli bir eşik değerinden küçük olmasının
sınanmasıdır. Bu sayede, ancak iki sayı arasındaki fark bu eşik değerinden küçük olursa
eşit olarak nitelendirilecektir.
Aşağıda, bu mantık ile iki ondalıklı sayının eşit olup olmadığının sınanmasını gerçekleyen
bir kod parçası bulunmaktadır:
Bu kod ile sayi_1 ile sayi_2 arasındaki farkın mutlak değeri 0.000001 gibi küçük bir
değerden küçük ise bu iki sayının eşit olduğu kararına varılabilir.
BÖLÜM-5DÖNGÜLER
5.1. Döngüler
Hemen hemen her türlü programda bu tür gereksinimlere değişik şekillerde ihtiyaç
duyulmaktadır.
Döngüler üç farklı deyim ile kurulabilir: while, do, for. Bu üç deyimin arasında
küçük farklar bulunmakla beraber her üç deyim de belirli bir koşul sağlanıncaya dek
bir komut bloğunu çalıştırmasını sağlamaktadır. Bu üç farklı döngü türünün
arasındaki farklar ve hangi durumlarda hangi tür döngülerin daha uygun olduğu
5.1.7. bölümde anlatılacaktır.
5.1.1. while Deyimi
while deyimi de, tıpkı if deyimi gibi belirli bir mantıksal ifadenin değerini hesaplar
ve eğer bu değer “doğru” çıkarsa, döngünün gövdesi (body) adı verilen komutu
ya da komut bloğunu yürütülür. Döngünün gövdesini oluşturan komut(lar) tamamen
yürütüldükten sonra, mantıksal ifade tekrar hesaplanır ve sonuç yine “doğru”
olarak hesaplanırsa, döngünün gövdesi yine çalıştırılır. Bu çalışma düzeni, mantıksal
ifade “yanlış” sonucunu üretene kadar devam eder.
Mantıksal ifade “yanlış” sonucunu üretirse, program akışı, while deyiminin
gövdesini oluşturan komuttan ya da komut bloğundan sonra gelen komutun
çalıştırılması ile devam eder.
5.1.1.1. Örnek
Örnek bir while döngüsü aşağıda gösterilmiştir:
public class WhileDongusu
{
public static void main(String[] args)
{
final int baslikParasi = 15000;
int calisilanAy=0, aylikMaas = 800, toplamPara = 0;
while(toplamPara < baslikParasi)
{
if ( (++calisilanAy % 3) == 0)
toplamPara = toplamPara + (2 * aylikMaas);
else
toplamPara = toplamPara + aylikMaas;
} // while
System.out.println(“Baslik parasi icin calisilacak ay:”);
System.out.println(calisilanAy);
} // main
}
Örnekte, while döngüsü kullanılarak, aylık 800 YTL alan bir kişinin 15.000 YTL
başlık parasının denkleştirilmesi için kaç ay çalışması gerektiği hesaplanmıştır. Bu kişi
3 ayda bir ek olarak 800 YTL ikramiye almaktadır.
while döngüsünün çalışma koşulu, toplamPara’ nın baslikParasi’ndan küçük olması
koşuludur. Bu koşul ilk çalışma için doğru olduğundan (0 < 15000), while
döngüsünün gövde komut bloğu yürütülür. Bu blokta öncelikle if deyimi ile çalışılan ay
1 arttırıldıktan sonra 3’e bölünüp bölünmediği yani ikramiye ayı olup olmadığı koşulu
sınanır.
Eğer çalışılan ay sayısı, 3’e bölünebilirse (calisilanAy % 3 = 0), bu ay ikramiye
ayıdır ve toplamPara değişkeni aylık maaşın 2 katı kadar artar. Eğer bu ay ikramiye
ayı değilse, toplamPara sadece aylikMaas kadar artar. Daha sonra tekrar while
döngü koşulu sınanır ve bu durum bu koşul yanlış olana dek, yani eldeki toplamPara,
baslikParasi’ na eşit ya da bu miktardan fazla oluncaya dek devam eder.
5.1.2. do Deyimi
do deyimi de while deyimine çok benzemekle birlikte tek farklı noktası, koşul
kontrolünün, döngünün gövdesi çalıştırıldıktan sonra yapılmasıdır. Bu döngü türü de, tıpkı
while döngüsü gibi gerekli koşulun sağlandıkça yani döngü ifadesinin sonucu “doğru”
oldukça, döngünün gövdesindeki komutları yürütür. do deyiminin akış şemasındaki
gösterimi aşağıda verilmiştir:
Yukarıdaki gösterimden de anlaşılacağı gibi, do deyiminde öncelikle döngü gövdesindeki
komutlar yürütülür daha sonra döngü koşulu sınaması yapılır. Dolayısı ile do deyiminde,
gövdeyi oluşturan komut(lar) mutlaka en az bir kez çalıştırılırlar.
5.1.2.1. Örnek
Aşağıda bir do döngüsü içeren örnek bir program verilmiştir.
public class DoDongusu
{
public static void main(String[] args)
{
int sayi = 1234, sonBasamak, tersi = 0;
System.out.println(“Sayi : ”+sayi);
do
{
sonBasamak = sayi % 10;
tersi = (tersi * 10) + sonBasamak;
sayi = sayi / 10;
}
while ( sayi > 0)
System.out.println(“Sayinin tersi : “+ tersi);
} // main
}
Program Kodu 5.2 – do döngüsü için örnek program
Bu örnek programda, bir sayının rakamları tersten yazılarak yeni bir sayı elde
edilmektedir. Programda, döngüye girildikten sonra öncelikle sayi’nin son basamağı
(yani sayi’nın 10’a bölümünden kalan) bulunur.
Daha sonra tersi isimli değişken sola bir basamak kaydırılır (10 ile çarpılarak) ve son
basamak ile toplanarak, son basamak değeri en sağdaki yerini alır. Daha sonra sayi
değişkeni 10’a bölünür, tamsayı bölmesi olduğu için bölümün sadece tamsayı kısmı kalır
(yani sayi değişkenin rakamları bir sağa kaydırılır) ve döngü artık bu yeni sayı için
tekrarlanır.
5.1.3. for Deyimi
Bir diğer döngü türü olan for döngülerin, diğer iki döngü türü olan do ve while
döngülerinden farklıdır. do ve while döngüleri, döngünün gövdesinin kaç kez
çalıştırılacağının belli olmadığı durumlarda kullanışlı olurken, eğer program yazılırken bir
döngünün kaç defa çalıştırılması gerektiği biliniyorsa yada değişkenler kullanılarak bir
ifade ile hesaplanabiliyorsa, for döngüleri daha kullanışlı olmaktadır.
for döngüsünün akış şemasındaki gösterimi aşağıdaki gibidir:
Bir for döngüsünde, diğer döngü türlerinden farklı olarak bir başlık ifadesi bulunur. Bu
ifade 3 kısma ayrılmıştır:
Bu şekilde başlık, birbirlerinden noktalı virgül (;) ile ayrılmış 3 temel kısım
içermektedir. İlk işlemler kısmındaki komutlar, çalışma sırası ilk kez döngüye geldiğinde
yürütülür. Yani bu kısımdaki işlemler sadece döngünün başında bir defalık yapılır.
Mantıksal ifade kısmında, döngünün devam etmesi için gerekli mantıksal ifade bulunmak
durumundadır. Bu kısımdaki ifade, aynen while döngüsünde olduğu gibi döngünün
gövdesi çalıştırılmadan önce sınanır. Eğer koşul sağlanıyorsa döngü gövdesi çalıştırılır,
koşul sağlanıyorsa bir sonraki komuttan devam edilir.
for döngü başlığının sonuncu kısmında yer alan sayaç işlemleri bölümünde yer alan her
komut, döngü gövdesi yürütüldükten hemen sonra çalıştırılır. Akış diyagramındaki
gösterimden de anlaşılacağı gibi, ilk işlemler kısmı sadece döngü başlarken sadece bir
defa çalıştırılırken, sayaç işlemleri bölümündeki bütün komutlar, döngü gövdesindeki
komutların çalıştırılması bittikten sonra, her defasında çalışır.
5.1.3.1. Örnek
Örnek bir program aşağıda verilmiştir:
public class ForDongusu
{
public static void main(String[] args)
{
int ust_ sinir = 5;
for (int sayac = 1; sayac < ust_sinir; sayac++)
System.out.println(sayac);
System.out.println("Dongu sona erdi!");
} // main
}
Program Kodu 5.3 – for döngüsü için örnek program
Örnek programda, for döngüsü başlamadan önce, ilk işlemler kısmında int tipinde sayac
isimli bir değişken tanımlanmıştır. Döngünün mantıksal ifadesi (sayac < ust_sinir) ilk
çalışma için hesaplanmış ve 1<5 olduğu için döngünün gövdesi çalıştırılarak ekrana sayac
değişkenin değeri yazdırılmıştır.
Daha sonra sayaç işlemleri kısmındaki komut çalıştırılmış ve sayac değişkeninin değeri 1
arttırılmıştır. Döngü gövdesinin ikinci kez çalıştırılıp çalıştırılmayacağının anlaşılması için
mantıksal ifade yeniden hesaplanır ve 2<5 olduğu için döngü gövdesi yine çalıştırılır. Bu
çalışma düzeni, sayac değişkenin değeri 5 olunca, 5<5 ifadesinin “yanlış” sonucunu
üretmesi ile sona erer ve bir sonraki komut ile ekrana döngünün sonlandığını bildiren bir
yazı yazdırılır.
Bu örnekte de görüldüğü gibi ilk işlemler kısmında değişken tanımlaması yapılıp bu
değişkene ilk değer verilebilmektedir. Bu sayede, bu değişkenin kapsamı da sadece bu
döngü içerisinde kalmakta, döngü sonlandığı zaman bu değişen de yok olmaktadır. İlk
işlemler bölümünde değişken tanımlama zorunlu olmamakla birlikte, döngüde sadece
sayaç amaçlı kullanılacak ve daha sonra gerekmeyecek geçici değişkenlerin oluşturulması
için en uygun bölümdür.
for döngülerinde sık karşılaşılan bir hata, programcının döngü gövdesinde, for
döngüsünün çalışma düzenini sağlayan sayaç değişkeninin değerini değiştirmesidir.
Sağlıklı bir çalışma için, programcı, for döngülerinde kullanılan sayaç değişkenlerini
döngü gövdesinde hiçbir yerde değiştirmemeli, bu değişkenin değerinin sadece for
döngüsünün başlığında gerekli ifadelerle değiştirilmesini sağlamalıdır.
Sayaç işlemleri kısmında, döngü sayacının değeri 1 arttırılabileceği gibi 1 azaltılabilir.
Bunun için bu kısımda yer alan sayac++ gibi bir ifadeyi sayac-- şeklinde değiştirmek
yeterli olacaktır. Üstelik bu bölgede herhangi bir aritmetik işlem bile
gerçeklenebilmektedir.
5.1.4. Sonsuz Döngüler
Bir programda yer alan bir döngünün mantıksal ifadesi hiçbir zaman “yanlış” değerini
almıyorsa, yani program akışı bir kez bu döngü deyimine geldiği zaman döngü gövdesi
sürekli çalıştırılıyorsa, bu tür döngülere sonsuz döngü (infinite loop) adı verilir.
Örneğin aşağıdaki while deyimi sonsuz bir döngüye örnektir:
Bu örnekte mantıksal ifadede yer alan 1 == 1 ifadesi her zaman “doğru” değerini
alacaktır, hiçbir zaman “yanlış” değerini almayacaktır. Dolayısı ile döngü gövdesi yani
sayac değişkeninin değerini 1 arttıran atama deyimi sürekli çalıştırılacaktır.
Bir döngünün sonsuz olması genelde istenen bir durum değildir. Bu durumda program
hiçbir işletim sistemi mesajına ya da kullanıcı etkileşimine cevap veremez duruma
gelir, kilitlenir. Bu tür programlar ancak işletim sistemi tarafından kesilerek
sonlandırılabilir.
Her sonsuz döngü koşulu, yukarıdaki örnekte belirtildiği gibi basitçe görülemez.
5.1.4.1. Örnek
Örneğin aşağıdaki gibi bir döngü sonsuz döngü olacaktır:
Bu örnekte, sayac değeri her zaman 10 değerinden küçük olacağı için (1, 0, -1, -2, …)
mantıksal ifade hep “doğru” değerini üretecek ve döngüden asla çıkılamayacaktır (teorik
olarak en küçük tamsayı değerine ulaşıldıktan sonra 1 azaltılması sonucunda en büyük
pozitif sayı elde edilir ve bu sayede döngüden çıkılabilir).
Sık yapılan bir başka sonsuz döngü hatası da, sayısal sınırların belirlenmesidir:
Bu örnekte de sanki sayac 50’ den farklı olduğu sürece arttırılacak, 50’ ye eşit olduğu
zaman da döngüden başarılı bir şekilde çıkılacakmış gibi görünmektedir. Ancak dikkatli
incelenirse sayac değişkeninin ilk değeri 1’ dir ve her döngü adımında bu değer 2 ile
arttırılmaktadır.
Yani sayac değişkenin değeri 1, 3, 5,… gibi tek sayılarda artmaktadır. Dolayısı ile
sayac’ın değeri ne kadar artarsa artsın hiçbir zaman 50 değerine eşit (49, 51 olacaktır
ama 50 olmayacaktır) olmayacaktır. Sonuç olarak bu döngü de sonsuz bir döngü olarak
karşımıza çıkacak ve programı kilitleyecektir.
Bir başka yaygın hata da ondalıklı sayıların kullanıldığı programlarda yaşanmaktadır:
Bu örnek de ilk bakışta sorunsuz çalışabilir gibi görünmesine karşın gerçekte sayi
değişkeni hiçbir zaman 0.0 değerine ulaşamaz. Ondalıklı sayıların, ondalıklı kısımlarının
gösterilmesi için belirli büyüklükte bir yer ayrıldığı için bu tip sayılarda çok çok küçük
basamaklarda veri kaybının olabileceği Bölüm 3.2.1.2’de anlatılmıştır.
Bu örnekteki sayi değeri de 0’ a çok yakın (belki de en son basamaktaki sayı hariç)
olacaktır ama asla tam olarak 0.0 değerine eşit olmayacaktır. Bu ise mantıksal ifadenin
hiçbir zaman “yanlış” olmaması gibi bir sonucu doğurur.
Bu tür sonsuz döngüleri önlemenin en iyi yolu, iki ondalıklı sayının eşit olduğunu
doğrudan sınamak yerine aradaki farkın belirli bir küçük değerden daha az
olmasının sınanmasıdır. Örneğin, bu hatalı döngünün düzeltilmiş hali aşağıdaki gibi
yazılabilir:
5.1.5. İç İçe Döngüler
Bir döngü gövdesi içinde bir başka döngünün de yer aldığı döngülere iç içe döngüler
(nested-loops) adı verilir.
İç-içe döngülerde önemli olan nokta, dışarıdaki döngünün her bir adımı için içerideki
döngünün tamamen çalıştırılmasıdır.
public class CarpimTablosu
{
public static void main(String[] args)
{
int sayi1, sayi2;
sayi1 = 1;
while (sayi1 <= 10)
{
sayi2 = 1;
System.out.println(sayi1+" icin carpim tablosu:");
while (sayi2 <= 10)
{
System.out.println(sayi1+" x "+sayi2+" = "+ sayi1 * sayi2);
sayi2++;
} // while
sayi1++;
} // while
} // main
Program Kodu 5.4 – İç içe while döngüleri
Bu örnek programda 1’ den 10’ a kadar olan sayıların çarpım tablosu hazırlanmıştır.
Örnekte görülen programda dışarıdaki ilk döngü sayi1 için 1’ den 10’a kadar saymakta
iken içerideki döngü sayi2 için 1’ den 10’ a kadar çalışmaktadır.
Dikkat edilirse sayi1’in her bir değeri için önce ekrana örneğin “2 için çarpım tablosu:”
yazılır. Ardından içerideki döngü sonlanma koşulu gerçekleşinceye kadar çalışır ve ekrana
sayi1’ in çarpım tablosunu yazdırır. Yani dıştaki döngünün her bir adımı için, içerideki
döngü sonlanma koşulu yerine getiriline kadar tamamen çalıştırılır.
Benzer şekilde for döngüleri de iç içe kullanılabilir:
public class Factorial {
public static void main(String[] args)
{
long sinir = 20;
long faktoriyel = 1;
for (int i = 1; i <= sinir; i++)
{
faktoriyel = 1; // ilk deger
for (int carpan = 2; carpan <= i; carpan ++)
{
faktoriyel = faktoriyel * carpan;
} // for
System.out.println(i + "!" + " = " + faktoriyel);
} // for
}
}
Program Kodu 5.5 – İç içe for döngüleri
İç içe for döngüleri kullanılmış bu örnek program da 1’ den 20’ ye kadar olan sayıların
faktöriyelini hesaplayarak ekrana yazdırmaktadır. Faktöriyeli hesaplanacak sayı (i
değişkeni) dış döngü ile arttırılırken iç for döngüsünde 2’den başlanarak bu i sayısına
kadar olan tüm tamsayılar çarpılarak faktöriyel hesaplanmaktadır. Örneğin i = 5 için
içerideki döngü (carpan = 2, 3, 4, 5) toplam 4 defa dönecek ve 5! = 120 değerini
üretecektir.
5.1.6. Döngü Kontrol Komutları
Bazı durumlarda döngü koşulu sağlanmadan döngüden çıkmak ya da belirli bir komuttan
sonra döngü gövdesindeki diğer komutları çalıştırmadan döngünün tekrar başa dönüp bir
sonraki adımdan devam etmesi gerekebilir.
Bu tür uygulamalar için, genel programcılık ilkeleri açısından uygun olmayan ama gene
de dilin yapısında bulunan iki deyim bulunmaktadır.
5.1.6.1. break Deyimi
Bu deyim, switch deyimi anlatılırken de görüldüğü gibi belirli bir komut bloğundaki bütün
komutlar yürütülmeden program akışının bir sonraki komuta geçmesini sağlar.
Döngü içerisinde break deyimi kullanmak hiçbir zaman zorunlu değildir. Aynı etkiyi
sağlayan bir döngü gövdesi break kullanmadan da yazılabilir. Bu deyim ile program akışı
bir noktadan diğer noktaya doğrudan atladığından programcılık ilkeleri açısından break
deyimini kullanmaktan kaçınılmalıdır.
switch deyimindeki durumlar için yazılan program kodlarını birbirinden ayırmanın başka
bir yolu olmadığı için break deyimi sadece burada kullanılmalıdır.
5.1.6.2. continue Deyimi
Döngü gövdesi içerisinde continue deyimi çalıştırılırsa, döngü koşulu tekrar sınanır ve
döngüye devam edilir ya da edilmez.
break deyimi gibi continue deyimi de programcılık ilkeleri ile bağdaşmadığından
kullanılması sakıncalı bir deyimdir.
5.1.7. Döngü Deyimlerinin Karşılaştırılması
İşlevsellik açısından her üç döngü türü de (while, do, for) aynı etkiye sahiptir. Bir türde
yazılmış bir döngünün yaptığı işi yapan başka türde bir döngü her zaman
yazılabilmektedir. Ancak her döngü türünün kullanışlı olduğu bazı durumlar vardır.
Örneğin eğer döngü gövdesindeki komutların en az 1 kez yürütülmesi gerekiyorsa, do
döngüsü while döngüsünden daha kullanışlı olmaktadır çünkü döngü koşulu, komutlar
yürütüldükten sonra yapılır.
Ancak bazı durumlarda önce koşulun sınanıp komutların sonuca göre çalışması
gerekmektedir. Döngüye ilk girişte döngü koşulu yanlış ise döngü gövdesinin hiç
çalıştırılmaması isteniyorsa while döngüsünü kullanmak daha akıllıca olur.
for döngüsü ise, döngü gövdesinin kaç kez çalıştırılacağı biliniyorsa ya da
hesaplanabiliyorsa kullanılmalıdır. Ayrıca for döngüsü, döngü için gerekli ilk işlemlerin ve
her adımdaki işlemlerin yapılmasını, döngünün içinde değil de başlığında yapığı için
yazılan program parçasının daha basit olmasını sağlar.
BÖLÜM-6DİZİLER
6.1. Diziler
Birçok dilde yer alan ve programcıların işini kolaylaştıran dizi yapıları ile veriler
gruplanarak düzenli bir şekilde işlenebilirler. Çok miktarda veri üzerinde işlem yapan
programlar hazırlarken, her veri için bir değişken tanımlamak çok zaman alıcı ve
kullanışsız bir yöntemdir. Bunun yerine, bir dizi içerisinde bütün bu verileri düzenli bir
şekilde yerleştirerek kullanmak verimi çok daha arttırarak program geliştirmeyi
kolaylaştırır.
Dizi yapıları, belirli miktarda verilerden oluşan bir listedir. Her bir veri, listede
numaralandırılmış bir yerde bulunur. Bu numaraların her birine indeks (index) adı
verilir. Bu liste üzerinde herhangi bir veriye erişirken, listedeki sıra numarası olan
indeksi ile ulaşılır.
Yanda, notlar isimli bir dizi örneği verilmiştir. Bu dizide, bir sınıftaki 15 kişinin notları
bulunmaktadır.
Örnekten de görülebileceği gibi C/C++ dillerine benzer olarak Java dilinde de dizilerin
indeksleri 0’ dan başlamaktadır. Dolayısı ile yukarıdaki notlar dizisinin 16 elemanı
olmasına karşın son elemanın indeksi 15 olarak görülmektedir.
Herhangi bir dizi elemanına erişmek için, dizi isminin yanına bitişik olarak köşeli
parantezler içinde erişilecek verinin indeks numarası yazılmalıdır.
Bu dizi bir tamsayı dizisidir, dolayısı ile bu dizideki tüm veriler tamsayıdır. Yukarıda
görüldüğü gibi, dizide yer alan herhangi bir veri, dizi_adi[indeks] şeklideki
kullanımla, tamsayı kabul eden her yerde tamsayı tipinden bir değişken gibi (4.
örnekte olduğu gibi notlar[notlar[9]] = notlar[12]) kullanılabilir.
6.1.1. Dizi Tanımlama ve Kullanma
Java dilinde, C/C++ dilinden farklı olarak diziler birer nesnedir. Bir dizinin tanımlanması
için bu nesneye bir referans tanımlanmalıdır. Daha sonra dizi nesnesi new işleci
kullanılarak oluşturulur ve saklanacak veriler için bellekten yer ayrılır. Aşağıda örnek bir
tanımlama verilmiştir:
“notlar” isimli değişkenin bir tamsayı dizisi olarak tanımlandığı int[] anahtar
sözcüğünden anlaşılır. Bir dizi içersindeki bütün veriler aynı tipte olmak zorundadır.
Aşağıda, başka tiplerde dizi tanımlaması örnekleri bulunmaktadır:
Bir dizi içerisinde ya temel veri tiplerinde veriler ya da belirli bir sınıftan üretilmiş
nesneler saklanabilir. Dizi içerisinde saklanan bu verilere dizi elemanı (array element)
adı verilir.
Dizi tanımlanmasında kullanılan bir diğer yazım ise aşağıdaki gibidir:
Bu iki dizi tanımaması özdeştir. Her ikisi de rahatlıkla kullanılabilir.
Tanımlamada kullanılan dizi tipini belirten sözcükte (int[]) dizi boyu ile ilgili herhangi bir
bilgi yoktur. Aslında bu sadece bir referans tanımlamasıdır. Dizi elemanlarının gerçekte
saklanacakları bellek alanı new işlecinin çalıştırılması ile oluşturulur. Bu aşamada ise
new işlecine hangi tipten kaç tane verinin sığabileceği kadar bir bellek bölgesi ayrılması
gerektiği, new int[15] şeklinde aktarılmaktadır.
Bir dizideki elemanlara erişmek için kullanılan köşeli parantez işaretleri ( [] ) aslında bir
işleçtir. Bu işleç ile dizi elemanın, dizinin başından itibaren kaç sekizli (byte) ötede
olduğu hesaplanarak buradaki veriye ulaşılır. Bu yüzden bütün diğer işleçler gibi dizi
indeksi işlecinin de bir öncelik seviyesi vardır. Ancak dizi işlecinin öncelik seviyesi, bütün
diğer işleçlerden yüksektir.
Dizi elemanı erişim işleci, diğer işleçlerden farklı özel bir işleçtir çünkü bu işleç, erişilmek
istenilen indeksin, dizinin geçerli bir indeksi olup olmadığını da sorgular. Bu özelliğe
otomatik indeks denetimi (automatic index checking) adı verilir. Bir dizi elemanına
erişilmek istendiğinde, bu işleç işlenen olarak belirtilen indeksin sıfırdan büyük olması ve
en büyük indeksten daha büyük olmamasını denetler. Örneğin yukarıda tanıtılan 15
elemanlı “notlar” dizisinin elemanlarına aşağıdaki gibi erişilmek istensin:
Bu örnekler incelendiğinde, -1 indeksinin zaten geçerli bir indeks olmadığı hemen
görülür. 100 numaralı indeksin de dizi boyu olan 15’ den çok fazla olduğu açıktır. Ancak
sık karşılaşılan bir hatalı düşünce ile dizinin 15 elemanı olduğu için 15 numaralı indeksin
var olduğu ve erişimin sorunsuz olduğu düşünülür. Oysa 15 adet eleman olsa dahi ilk
elemanın indeksi 0’ dan başladığı için en büyük indeks numarası 14’ tür.
Eğer dizi üzerinde geçersiz bir erişim yapılmaya çalışılırsa
ArrayIndexOutOfBoundsException isimli bir aykırı durum ilanı yapılır. Aykırı
durumların yönetimi, bölüm 14’ de ele alınacaktır.
Java dilinde diziler nesneler ile gerçeklendiği için diğer dillerde olmayan bazı özellikler
bulunmaktadır. Örneğin herhangi bir dizi nesnesi oluşturulurken, bu dizi nesnesinin bir
length isimli özelliği (attribute) dizinin eleman sayısını gösterecek şekilde ayarlanır. Bu
değer, nesne oluşturulurken atanır ve bir daha değiştirilmez. Dizi sınıfının length özelliği
public olarak tanımlandığı için her yerden rahatlıkla erişilebilir.
6.1.1.1. Örnek
public class Notlar {
public static void main(String[] args) {
int[] notlar = new int[5];
// Diziyi doldur
for (int i=0; I < notlar.length; i++)
notlar[i] = i * 2;
// Diziyi ekrana yazdır
for (int i=0; I < notlar.length; i++)
System.out.println(notlar[i]);
}
}
Yukarıdaki örnekte 5 elemanlı bir dizi oluşturulmuştur. Bu dizinin elemanlarına ilk for
döngüsü ile 0’dan başlayan çift sayılar atanmıştır. İkinci for döngüsü ile dizinin bütün
elemanları üzerinden geçilerek her bir elemanın değeri ekranda bir satıra yazdırılmıştır.
6.1.2. Dizilere İlk Değer Atama
Diziler oluşturulurken aynı zamanda bu dizi elemanlarına birer ilk değer verilebilir. Bu,
temel veri tiplerinden herhangi birinde tanımlama yaparken ilk değerinin hemen atanması
gibi düşünülebilir, ancak dizilere ilk değer verirken doğal olarak bir tane değer değil,
dizinin eleman sayısı kadar veri içeren bir ilk değerler listesi (initializer list) belirtilir.
Bu ilk değer listesindeki veriler, küme parantezi içerisinde yazılır ve her değer birbirinden
virgül ile ayrılır. Bir dizi, ilk değer listesi verilerek tanımlanıyorsa, new işlecinin
kullanılmasına gerek kalmaz. İlk değerleri içeren listenin eleman sayısı, tanımlanan
dizinin boyunu belirler.
Örnek olarak aşağıda “notlar” dizisinin ilk değer listesi ile oluşturulması verilmiştir:
Doğal olarak, ilk değer listesinde yer alan her bir elemanın tipi, dizinin tipi ile aynı olmak
zorundadır.
6.1.2.1. Örnek
public class DizideEnKucuk {
public static void main(String[] args) {
int[] notlar = {56,78,80,24,38,46,100,71,68,12,75,68,19};
int enKucukIndeks = 0;
// Dizideki en kucuk elemani ve bu elemanin indeksini bul
for (int i=1; i < notlar.length; i++)
if (notlar[i] < notlar[enKucukIndeks])
enKucukIndeks = i;
// Bulunan en küçük sayısı ekrana yazdır
System.out.println(“En kucuk sayi=”+notlar[enKucukIndeks]);
System.out.println(“Dizinin ”+enKucukIndeks+”. elemani…”);
}
}
Program Kodu 6.2 – Diziye ilk değer atama ve dizide arama yapma
Örnek program incelenecek olursa notlar isimli bir dizinin ilk değer listesi verilerek
oluşturulduğu görülür. Bu dizide en küçük elemanı bulmak için en küçük elemanın indisini
saklamak üzere enKucukIndeks isimli bir değişken tanımlanmıştır. Dizinin 0. yani ilk
elemanı en küçük olarak kabul edilmiş ve enKucukIndeks=0 yapılmıştır.
Daha sonra bir for döngüsü ile dizinin 1. elemanından başlayarak taramaya başlanır ve
eğer bu indisteki sayıdan daha küçük bir sayıya rastlanırsa, enKucukIndeks isimli
değişken bu yeni en küçük elemanı gösterecek şekilde güncellenmektedir.
6.1.3. Çok Boyutlu Diziler
6.1.3.1. İki Boyutlu Diziler
İki boyutlu dizilerde veriler düz bir liste şeklinde değil ama matris biçimde satırlar ve
sütunlara dağıtılarak saklanırlar. Örnek bir iki boyutlu dizi yerleşimi aşağıda verilmiştir:
Örnekte notlar dizisinde artık 15 öğrencinin sadece 1 tane notu değil 4 tane notu
saklanmaktadır. Her satırda bir öğrencinin notları bulunmaktadır. Her sütun ise bir
sınavın notları göstermektedir.
Bu matris biçiminde düzenlenmiş dizinin iki boyutu olduğu için iki boyu olacaktır. Satır
sayısı ve sütun sayısı. Bu iki boyutlu dizi aşağıdaki gibi tanımlanır:
Görüldüğü gibi new işlecine önce satır sayısı yani 1. boyutun boyu daha sonra sütun
sayısı yani 2. boyutun boyu işlenen olarak verilmiştir.
Aslında Java, iki boyutlu dizileri, elemanları birer diziyi gösteren bir dizi şeklinde
kotarmaktadır. Yani tek boyutlu bir dizinin içindeki elemanlar aslında başka bir tek
boyutlu diziye referanslar şeklindedir.
İki boyutlu bir dizide herhangi bir elemana erişileceği zaman her iki boyutun da
belirtilmesi gerekmektedir. Aşağıda iki boyutlu dizi elemanlarına erişim örnekleri
verilmiştir:
Tek boyutlu bir dizi, ilk değer listesi ile tanımlanabildiği gibi iki boyutlu diziler de bir değer
listesi ile oluşturulabilir. Örneğin yukarıdaki değerleri içeren bir notlar dizisi aşağıdaki ilk
değer listesi doldurulabilir:
Görüldüğü gibi iki boyutlu bir dizinin ilk değer listesi de 15 adet tek boyutlu dizi ilk değer
listesi içermektedir. Örnekteki notlar dizisi yukarıda değinildiği gibi aslında 15 tane farklı
tek boyutlu dizi referansı içeren bir tek boyutlu dizidir. Bu referansların her biri 4
elemanlı bir tek boyutlu listeye işaret etmektedirler.
6.1.3.1.1. Örnek
İki boyutlu bir dizinin elemanları üzerinde işlem yapmak için genellikle iç içe 2 for
döngüsü kullanılır. Örneğin notlar dizisi üzerinde işlem yaparak bütün notların genel
ortalamasını bulan bir program aşağıda verilmiştir:
public class OrtalamaNotlar {
public static void main(String[] args) {
int[][] notlar= { {35,100,90,84},
{40,90,65,65},
{16,90,3,23},
{36,85,75,74},
{50,75,45,52},
{20,65,5,20},
{25,60,50,52},
{38,55,65,58},
{8,45,85,62},
{4,43,15,18},
{5,41,5,12},
{28,41,75,64},
{25,40,80,61},
{11,38,30,30},
{15,38,55,49}
};
double genelOrtalama = 0;
// Ortalamalari hesaplamak icin butun notlari yigmali topla
for (int i=0; i< notlar.length; i++)
for (int j=0; j< notlar[i].length-1;j++)
genelOrtalama += notlar[i][j];
genelOrtalama = genelOrtalama /
(notlar[0].length * notlar.length);
System.out.println("Tum notlarin ortalamasi : " + genelOrtalama);
}
}
Program Kodu 6.3 – İki boyutlu dizi üzerinde ortalama hesaplayan program
Bu programda, öncelikle 15 öğrencinin notlarının bulunduğu iki boyutlu bir dizi
tanımlanmıştır. Öğrencilerin notları, ilk değer listesi ile verilmiştir. Daha sonra ortalama
hesaplama döngüsüne girilmiş ve 15 öğrenciden her biri için döngü yürütülmüştür. Her
döngü adımında da öğrencilerin bütün notları (4 adet not – notlar[i].length ile dinamik
olarak çalıştırılır) yığmalı toplama ile genelOrtalama değişkeni üzerinde toplanır.
Daha sonra bulunan bütün notların toplamı, toplam not sayısına (toplam matris
elemanı sayısı [ satır_adedi x sütun_adedi ] şeklinde notlar[i].length *
notlar.length ifadesi ile bulunur) bölünerek genel ortalama bulunur ve ekrana yazdırılır.
6.1.3.2. Çok Boyutlu Diziler
İki, üç veya daha fazla boyutlu dizilere çok boyutlu diziler (multidimensional arrays) adı
verilir.
İki boyutlu diziler bir matris ile rahatlıkla görselleştirilebilir. Üç boyutlu diziler ise üç
boyutlu bir küp ile görselleştirilebilir. Ancak boyut sayısı üçü aşarsa, dizinin kâğıt üzerinde
görselleştirilmesi olanaksız hale gelmektedir.
Çoğu kez çok boyutlu diziler, çeşitli verilerin değişik gruplamalar altında uygun biçimde
saklanması amacı ile kullanılır. Örneğin iki-boyutlu notlar dizisi sadece 15 öğrencinin bir
ders için aldığı 4 farklı notu saklamak amacı ile kullanılmaktadır. Bu iki-boyutlu diziye
yeni bir boyut eklenerek bu 15 öğrencinin farklı dersler için aldığı 4 farklı not saklanabilir
hale gelir:
Genelde tek ve iki boyutlu diziler yaygın olarak kullanılmasına karşın daha fazla boyutlu
diziler çok sık kullanılmaz çünkü bu dizilerin yönetimi ve işlenmesi boyut sayısı arttıkça
daha zorlaşır.
Çok boyutlu dizilerin diğer bir ilginç özelliği de Java’nın çok boyutlu dizileri, diğer dillerde
olduğundan farklı bir biçimde desteklemesidir. Önceki bölümlerde de anlatıldığı gibi,
aslında iki boyutlu dizilerin her bir elemanı, bir tek boyutlu diziyi referans olarak
göstermektedir. İkiden fazla boyutlarda da bu işlem tekrarlanmaktadır.
Çok boyutlu bir dizide (son boyut dışında) her bir boyut aslında bir başka diziyi işaret
eden bir referans olduğu için işaret edilen her bir dizinin boyunun aynı olması
gerekmemektedir. Yani matris şeklinde iki boyutlu diziler olabileceği gibi aşağıdaki gibi iki
boyutlu diziler de Java tarafından desteklenmektedir:
Böyle bir dizide, her bir satırın sütun sayısı sabit değil farklı olacaktır. Bu durumda her bir
satırın sütun sayısını öğrenmek için notlar[i].length deyimi ile tek notlar[i] tarafından
gösterilen (referans edilen) dizinin length özelliğinden bu dizinin boyutunu öğrenerek iki
boyutlu dizinin i. satırının sütun sayısı elde edilmiş olur.
Bu şekilde tuhaf iki-boyutlu ve çok boyutlu diziler oluşturulabildiği için çok boyutlu diziler
üzerinde işlem yaparken çok dikkatli program yazmak gerekmektedir.
6.1.4. Dinamik Diziler
Java’nın standart sınıf kütüphanesinde yer alan java.util paketinde yer alan ArrayList
sınıfı ile dizilere benzer bir şekilde birden fazla sayıda veri saklanabilir ve bu verilere
indeksler aracılığı ile erişilebilir.
ArrayList ile bir dizi içerisindeki verilerin tamamının aynı tipte olması zorunluluğu kalkar.
ArrayList ile saklanan her bir eleman, Object sınıfından nesnelere referanslardır dolayısı
ile herhangi bir nesne, bu tür dizilerde saklanabilir. Ancak temel veri tiplerindeki veriler,
uygun bir nesne tarafından sarılarak saklanabilir. Dizilerde olduğu gibi ArrayList ile
kurulan dinamik dizilerde de ilk elemanın indeksi 0 olarak belirlenmiştir. Aşağıda bu
sınıfın bazı metotları ve bunların görevleri verilmiştir:
Tablo 6.1 – ArrayList sınıfına ait bazı önemli metodlar
6.1.4.1. Örnek
ArrayList sınıfının kullanılmasına örnek bir program aşağıda verilmiştir.
import java.util.ArrayList;
public class DinamikDizi {
public static void main(String[] args) {
ArrayList ogrenciler = new ArrayList();
ogrenciler.add("rustu sukur");
ogrenciler.add("oguz rencber");
ogrenciler.add("volkan cetin");
ogrenciler.add("hakan bekiroglu");
System.out.println(ogrenciler);
int indeks = ogrenciler.indexOf("volkan cetin");
ogrenciler.remove(indeks);
System.out.println(ogrenciler);
System.out.println("1. sıradaki : " + ogrenciler.get(1));
ogrenciler.add(2,"serkan altintop");
System.out.println(ogrenciler);
System.out.println("Listenin boyu = "+ogrenciler.size());
}
}
Program Kodu 6.4 – Dinamik dizi örneği
Bu örnekte öncelikle dinamik dizi oluşturulmuş daha sonra bu diziye 4 adet karakter
katarı sınıfından eleman eklenmiştir. Liste ekrana yazdırıldıktan sonra “volkan cetin”
isimli öğrencinin indeks numarası indexOf metodu ile öğrenilerek remove metodu ile bu
dizi elemanı silinmiştir.
Daha sonra 1. sıradaki (2. dizi elemanı) eleman get metodu ile öğrenilerek ekrana bu
öğrencinin ismi yazdırılmıştır. Daha sonra add metoduna fazladan 2 parametresi
gönderilerek eklenecek “serkan altintop” isimli öğrencinin 2 indeksli eleman olarak
diziye eklenmesi istenmiştir.
BÖLÜM-7SINIFLAR VE NESNELER
7.1. Sınıf Kavramı Üzerine
Nesneye dayalı programlamanın temel öğeleri sınıflar ve nesnelerdir. Sınıflar, nesneye
dayalı programlamanın en önemli özelliklerinden olan kapçıklama (encapsulation) ve
bilgi gizleme (information hiding) özelliklerinin gerçekleştirilmesinde yapı taşlarını
oluştururlar.
Bu noktada sınıflar ortak özelliklere sahip nesneleri temsil eden soyut veri türleri olarak
tanımlanır. Bütün nesneler için konuşmak gerekirse bir nesne herhangi bir andaki
durumu ve davranışı ile temsil edilebilir. İşte bu durum ve davranışı belirleyecek öğeler
sınıf tanımları ile belirlenmektedir.
Örneğin bir oyun destesini ele alalım. Bu oyun destesinin içinde birçok oyun kartı
nesnesi bulunmaktadır. Bu yapıyı bilgisayarda temsil etmek istediğimizde kafamızda
oluşan nesne biçimi belirlidir. Her bir oyun kartı bir nesnedir ve bir deste belirli sayıda
nesnenin bir araya gelmesinden oluşur. Bu satırları okuduğunuz anda kafanızda oluşan
oyun kartı imgesi esasen nesneye dayalı programlamada sınıf tanımına denk düşer.
Bir oyun kağıdı sınıfı tanımında genel olarak bir oyun kağıdında bulunması gereken
özellikleri içerecektir. Bu da kağıdın hangi sayıya sahip olduğu, ne renk olduğu, hangi
tür kart olduğu gibi özelliklerdir. Bunun üzerine bir de bu kartın çeşitli özel durumlarda
sergileyeceği davranış üzerine de çeşitli fikirler kafamızda oluşur. Örneğin bir kartın ters
yüz edilebilme veya oyunculardan birine dağıtılabilme gibi özellikleri.
Yukarıda sayılan özelliklerle kafamızda bir kart sınıfı oluştururuz. Gerçek dünyada ise
karşımıza bir deste kağıt geldiğinde örneğin bunlardan birini çektiğimiz zaman bir
nesneden söz ediyoruzdur. Bu noktada kafamızda soyut olarak oluşturduğumuz durum
ve davranış özellikleri somut hale gelir. Kafamızda oluşturduğumuz sınıf tanımının bir
gerçekleştirimi elimizdedir.
Nesneye dayalı programlama söz konusu olduğunda da belirli bir dil kullanarak
oluşturduğumuz sınıf tanımları aynen gerçek dünyada olduğu gibi çeşitli özellik ve
metodları içerir. Bu özellik ve metodlar sınıf tanımı üzerinden oluşturulmuş her bir
nesnenin durum ve davranış özelliklerini temsil ederler.
7.2. Java’da Sınıflar ve Nesneler
Basit bir örnek üzerinden devam edelim ve günün saatlerini temsil etmede kullanacağımız
bir sınıf tanımıyla bahsi geçen kavramları örnekleyelim.
public class Zaman { //Zaman.java
private int saat;
private int dakika;
private int saniye;
public Zaman() { belirleZaman( 0, 0, 0 );}
public void belirleZaman(int s, int d, int sn) {
saat = ( (s >= 0 && s < 24) ? s
dakika = ( (d >= 0 && d < 60) ?
saniye = ( (sn >= 0 && sn < 60)
}
public void alZaman()
{
System.out.println(saat + ":" +
}
: 0 );
d : 0 );
? sn : 0 );
dakika + ":" + saniye);
public void alBasitZaman()
{
System.out.println(saat % 12 + ":" + dakika + (saat<12 ? "
AM" : " PM"));
}
}
Örnekte de görülmüş olduğu gibi kapçıklama kavramı gerçekleştirilmiş ve belirli bir
işlevi gerçekleştirmek üzere ilintili tüm kavramlar bir araya getirilmiştir. Bu sınıf
tanımında sınıfın ismi “Zaman” dır. Dikkat edileceği üzere sınıf tanımı, içerisinde
öncelikle sınıfın tanımlandığını bildirmek amacıyla class takısı kullanılır ve {}
parantezleriyle sınıf içerisinde yer alan metod ve özellikler belrlenir. Tanımda da
görüldüğü üzere sınıfın özellikleri değişkenlerle, metodları ise yordam tanımlarıyla
gerçekleştirilmiştir.
Bir adım öteye gidersek artık bu değişken ve fonksiyonlar Zaman sınıfının tanımlı özellik
ve metodları haline gelmiştir ve kullanılabilmesi için başlarında Zaman sınıfı tarafından
oluşturulmuş nesnelerin belirtilmesi gerekmektedir. Örneğin Zaman sınıfı türünden bir
nesne oluturalım:
Bu tanımla Zaman sınıfının saat isimli bir nesnesini oluşturmuş bulunuyoruz. Artık sınıf
tanımıyla tanımlanmış metod ve özelliklere bu nesne üzerinden ulaşabiliriz. Bir başka
deyişle kafamızda bir zaman göstergecinin temel özelliklerinin ve fonksiyonlarının neler
olması gerektiğini düşündük (sınıf tanımı), bunu karşımızdakine anlattık ve bu doğrultuda
üretilen bir saat nesnesini elimize aldık (nesne oluşturmak). Artık bu sınıfın kafamızda
oluşturduğumuz özellik ve fonksiyonlarına ulaşabiliriz. Tabii “masa_saati”ni kullanarak.
7.3. Sınıf Öğeleri
Bu noktada kafamızdaki zaman gösterici tanımı ve masa saati nesnesi üzerine bir miktar
daha fikir yürütelim. Zaman sınıfının metodlarına bakacak olursak daha sonra
değineceğimiz “Zaman()” kurucu metodunu dışarıda tutarak her bir metodun belirli bir
işlevi sağlamakta kullanıldığını görüyoruz.
Aynen bir masa saatinin üzerindeki “saat kurma” ve “gösterim değiştirme” düğmeleri
gibi. Bu metodlar kullanıcı ile nesne arasında bir “arayüz” oluşturmaktadırlar ve nesne
ile etkileşim tamamen bu metodlar üzerinden gerçekleşmektedir. Yani “alZaman()”
düğmesine bastığımızda zamanı saat:dakika:saniye tarzında görürken
“alBasitZaman()” ile saat:dakika tarzında görmekteyizdir ve “belirleZaman()”
düğmesi ile saati kurmaktayızdır.
Bu noktada bir saatten beklediğimiz tüm işlevler yerine getirilmiştir. Bir anlamda saatin
içinde zamanı belirleyen çarkları temsil eden “saat”,“dakika”, “saniye” gibi özelliklere
nesneye kullanan kullanıcının erişmesine ihtiyaç yoktur. Üstelik bu erişim sakıncalı da
olabilir aynen saatin içindeki çarkları kurcalayıp bozmak gibi.
İşte nesneleri kullananların erişimini sınırlandırmak ve bu sayede “bilgi gizleme”
özelliğini gerçekleştirmek için 3 çeşit sınıf öğesi tipi tanımlanmıştır. Unutmamak
gerekir ki nesneyi kullananların olması gerektiğinden ne çok ne de az erişim hakkına
sahip olmaları önemli bir konudur. Bu 3 çeşit öğe;
olarak tanımlanmıştır.
“public” öğeler kullanıcılar tarafından erişilebilen öğelerken “private” öğeler sadece
nesne içi erişime açıktır. Sınıf içindeki herhangi bir öğe “public” veya “private” olarak
belirlenebilir, tüm özelliklerin “private” ve tüm metodların “public” olması bir gereklilik
olmasa da güçlü nesneye yönelik programlama örnekleri oluşturmak için özelliklerin
mümkün olduğunca “private” kullanımı gerekmektedir. “protected” öğelere ileriki
bölümlerde değinilecektir.
Java’ya özgü bir özellik, bir sınıfa ait metodların sınıf içerisinde tanımlanma
zorunluluğudur. Bir başka özellik de “private” ve “public” takılarının her tanımdan önce
kullanılması gerekliliğidir. Bir tanımdan önce herhangi bir takı kullanılmamışsa o tanım
otomatik olarak “public” kullanılacaktır. Fakat bu tarz takısız kullanımlar anlaşılırlığı
düşürdüğünden tavsiye edilmez.
7.4. Nesneler Kullanımda
Yukarıda verilen Zaman sınıfını Zaman.java ismiyle kaydedip başka bir sınıfta bu sınıfı kullanalım.
public class deneme { //deneme.java
public static void main(String[] args) {
Zaman masa_saati=new Zaman();
masa_saati.belirleZaman(22,40,70);
masa_saati.alZaman();
masa_saati.alBasitZaman();
}
}
“deneme.java” ismiyle kaydedeceğimiz dosya ile başta oluşturduğumuz Zaman.java yı derleyip çalıştırd
Satırı ile Zaman sınıfından yeni bir nesne oluşturuyoruz. Bu satır üzerinde bir miktar düşünmekte yarar va
şeklinde yapılsaydı masa_saati türünden bir nesne belirtilmiş olacaktı. Fakat bir nesneyi belirtmek ile olu
Java perspektifinde bu değişken sadece bir referanstır yani gerçek nesneye ulaşmanın bir yoludur. Aynen
yok.
bildirimiyle kumandanın kontrol edeceği televizyon da oluşturulmuş oluyor. Bu noktadan sonra bizim için n
masa_saati nesnesine gönderilen ilk iletinin saati kurma amaçlı olduğunu görebiliyoruz. Saati kurmak içi
üzere saatin içerisinde ele alınıyor ve tasarımcının isteğine göre ele alınıyor.
iletileriyle saatten iki farklı işlevini yerine getirmesi isteniyor. Bu iletileri alan saat o anki nesne durumuna
göstermektedir. Şimdi isterseniz “private” olarak tanımlanmış öğelere erişmeye çalışalım bu amaçla “de
bu komut nesnemizin “private” olarak tanımlanmış bir özelliğine erişmeye çalıştığımız için derleyici tarafı
7.5. Bazı Özel Metodlar ve Özellikler
Bu tarz private olarak belirlenen verilere ulaşmayı sağlamak için kullanılan yöntem jargonda set ve get m
gibi iki metod eklenebilir. Başta anlamsız gibi gözüken bu yöntem aslında son derece mantıklıdır. Zira sını
ulaşmak yerine nesne tarafından belirlenen kurallar çerçevesinde erişmeleri güvenlik ve bütünlük konul
Set ve get metodları dile bağlı bir özellik değildir, unutmamak gerekir ki bu metodların adı da get ve set
Sınıf tanımları içerisinde bazı özel tanımlı özellikler ve metodlar bulunur. Bu metodların kullanımı ile sınıfla
Bu öğelerden ilki sınıf-nesne ayrımında önemli bir noktada bulunan “this” öğesidir. Sınıf tanımında kullan
kolay anlaşım amacıyla “dk” isimli parametre dakika olarak değiştirildi. Fakat bu noktada “dakika” değiş
nesnenin kendi öğesi kullanılmış olur.
dikkat edilecek olursa her set metodunun sonunda nesnenin kendisi döndürülmüştür. Metodların dönüş de
masa_saati.setSaat() metodu çalışmasını bitirdikten sonra this ile refere edilen “masa_saati” nesnesini
benzer bir işlem setDakika() metodu çalıştıktan sonra da gerçekleşecek ve bu 3 metod zincirleme çağırıl
Son değişikliklerimizin ardından Zaman sınıfına bir daha göz atalım.
public class Zaman { //Zaman.java
private int saat;
private int dakika;
private int saniye;
public Zaman() { belirleZaman( 0, 0, 0 );}
public void belirleZaman(int saat, int dakika, int saniye)
{
setSaat(saat).setDakika(dakika).setSaniye(saniye);
}
public void alZaman()
{
System.out.println(saat + ":" + dakika + ":" + saniye);
}
public void alBasitZaman()
{
System.out.println(saat % 12 + ":" + dakika + (saat<12 ? " AM" : "
PM")); }
public Zaman setSaat(int saat)
{ this.saat = ( (saat >= 0 && saat < 24) ? saat : 0 );
eturn this;
}
public Zaman setDakika(int dakika)
{
this.dakika = ( (dakika >= 0 && dakika < 60) ?
dakika : 0 );
return this;
}
public Zaman setSaniye(int dakika)
{
this.saniye = ( (saniye >= 0 && saniye < 60) ?
saniye : 0 );
return this;
}
public int getSaat() {return saat;}
public int getDakika() {return dakika;}
public int getSaniye() {return saniye;}
}
7.6. Kurucu Metodlar
Zaman sınıfının nesnelerini oluşturduğumuz deneme sınıfına bir kez daha göz atalım
public class deneme { //deneme.java
public static void main(String[] args) {
Zaman masa_saati=new Zaman();
masa_saati.alZaman();
masa_saati.alBasitZaman();
masa_saati.belirleZaman(22,40,70);
masa_saati.alZaman();
masa_saati.alBasitZaman();
}
}
Bir önceki gerçekleştirimden farklı olarak saati kurmadan zamanı öğrenmek istediğimizi görebiliyoruz. Saa
Herhangi bir değişkende olduğu gibi nesnelerde de ilk değer ataması yapılmadığı sürece oluşturulan nesne
Kurucu metodlar sınıf adı ile aynı ismi taşırlar ve sınıfa ait herhangi bir nesne oluştuğu anda çağırılırlar. B
yordamların geri dönüş değerleri yoktur ve otomatik olarak çağırılırlar.
7.6.1. Zaman Sınıfının Kurucu Metodu
Şimdi Zaman sınıfımızın kurucu metoduna bir bakalım:
Görüldüğü gibi kurucu metod sınıfın başka bir metodunu çağırarak saati ilk değer olarak “0:0:0” değerine
Kurucu yordamlar örneğimizde olduğu gibi parametresiz olmak zorunda değildir. Belirli parametreler ala
herhangi bir zamanı parametre olarak göndererek yeni bir nesne oluşturabilir hale getirelim.
Bu durumda sınıf tanımındaki kurucu metod tanımını şu hale getirmiş olacağız:
Bu durumda artık kurucu metodumuz nesnelerin sahip olması gereken ilk zamanı parametre olarak almak
yerine
Satırı ile saatin ön tnaımlı olarak 9:25:40 zamanı ile başlamasını sağlamış olduk. Sınıflarımızı derleyip çalı
sonucunu alacağız.
public class Zaman { //Zaman.java
private int saat;
private int dakika;
private int saniye;
public Zaman() { belirleZaman( 0, 0, 0 );}
public Zaman(int saat,int dakika,int saniye) { belirleZaman(
saat, dakika, saniye );}
public void belirleZaman(int saat, int dakika, int saniye)
{
setSaat(saat).setDakika(dakika).setSaniye(saniye);
}
public void alZaman()
{
System.out.println(saat + ":" + dakika + ":" + saniye);
}
public void alBasitZaman()
{
System.out.println(saat % 12 + ":" + dakika + (saat<12 ? " AM" :
" PM"));
}
public void alBasitZaman()
{
System.out.println(saat % 12 + ":" + dakika + (saat<12 ? " AM" :
" PM"));
}
public Zaman setSaat(int saat)
{ this.saat = ( (saat >= 0 && saat < 24) ? saat : 0 );
return this;
}
public Zaman setDakika(int dakika)
{
this.dakika = ( (dakika >= 0 && dakika < 60) ?
dakika : 0 );
return this;
}
public Zaman setSaniye(int dakika)
{
this.saniye = ( (saniye >= 0 && saniye < 60) ?
saniye : 0 );
return this;
}
public void alBasitZaman()
{
System.out.println(saat % 12 + ":" + dakika + (saat<12 ? " AM" :
" PM"));
}
public int getSaat() {return saat;}
public int getDakika() {return dakika;}
public int getSaniye() {return saniye;}
}
Dikkat edilecek olursa komutsal programlamada alışık olmadığımız bir durumla karşılaştığımızı görürüz. Bu
Parametre sayısı ve/veya tipi değişik olduğu sürece farklı isimde iki kurucu metodun bir sınıf içerisinde bul
denilmektedir.
Örneğin bu durumu denemek için deneme sınıfımızı şu şekilde değiştirelim.
public class deneme { //deneme.java
public static void main(String[] args) {
Zaman saat1=new Zaman();
Zaman saat2=new Zaman(9,20,40);
System.out.println("Parametresiz kurucu:");
saat1.alZaman();
saat1.alBasitZaman();
System.out.println("Parametreli kurucu:");
saat2.alZaman();
saat2.alBasitZaman();
}
}
Şimdi deneme programımızı çalıştırdığımızda;
Sonucunu alıyoruz. Görüldüğü gibi kurucu metodlardan hangisinin ne zaman çalışacağı doğru olarak belirl
Bir sınıfın kurucu metodu olmayabilir mi? Cevap evet. Bu durumda kurucu metodu yazılmamış herhangi b
Bu kurucu yordama ön tanımlı kurucu yordam denir.
Fakat unutmamak gerekir ki bir sınıfa parametre alan herhangi bir kurucu metod yazıldığında bu sınıfın ön
Kurucu yordamlarla birlikte karşımıza çıkan başka bir konu bir kurucu metodun içinden bir başka kurucu
getirdiği işlemleri kapsıyorsa kapsayan metod içinden diğer metod çağırılabilir. Örnek olarak Zaman sınıfı
Gördüğümüz gibi this referansını kullanarak bir başka tanımlı kurucu metod çağırılabiliyor. Bu türde bir
mümkün olmadığıdır.
7.7. Etki Alanı Kuralları
Daha önceden söz edildiği gibi tanımlanmış bir varlığın refere edilebeliceği yani bir anlamda içeriğine doğr
Bir sınıf tanımının ‘{’ işareti ile açılmasıyla başlayan ve ‘}’ işareti ile sonlanmasıyla biten bölge içerisinde ta
çalışıldığında “public” olarak tanımlanmamış özellikler uykarıda görüldüğü gibi derleyici hatasına neden
Blok etki alanı ise değişkenin tanımladığı satırla başlar ve bir sonraki ‘}’ işaretine kadar devam eder. Meto
üretilir.
Eğer bir blok etki alanı içerisinde tanımlanmış varlık sınıf etki alanında tanımlanmış varlık ile çakışıyorsa sı
Şimdi etki alanı kavramı ile ilgili bir örnek inceleyelim.
public class Etki //Etki.java
{
int x=1;
public Etki()
{
int x=5;
System.out.println("Kurucudaki x = " + x);
a();
b();
a();
b();
System.out.println("Kurucudaki x = " + x);
}
void a()
{
int x=25;
System.out.println("a metodundaki x giriste = " + x);
++x;
System.out.println("a metodundaki x cikista = " + x);
}
void b()
{
System.out.println("b metodundaki x giriste = " + x);
x*=10;
System.out.println("b metodundaki x cikista = " + x);
}
}
public class deneme { //deneme.java
public static void main(String[] args) {
Zaman saat[]={new Zaman(),new Zaman(9,20,40)};
for(int i=0;i<2;i++)
{
System.out.println("saat"+i);
saat[i].alZaman();
saat[i].alBasitZaman();
}
}
} import java.util.*;
Örnekte de görüldüğü gibi b metodunda blok etki alanında tanımlanmayan ‘x’ değişkeninin sınıf etki alanı
için değerleri eski haline dönmüştür.
7.8. Nesne Dizileri
Nesnelerden oluşan diziler de önceki bölümlerde görülen kurallara uygun olarak oluşturulurlar. Bu noktada
Bu ilke doğrultusunda az önceki örneği yeniden düzenlersek:
public class deneme { //deneme.java
public static void main(String[] args) {
Zaman saat[]={new Zaman(),new Zaman(9,20,40)};
for(int i=0;i<2;i++)
{
System.out.println("saat"+i);
saat[i].alZaman();
saat[i].alBasitZaman();
}
}
}
Bir döngü içinde veya sıradüzensel olarak oluşturduğumuz Zaman sınıfı nesneleri dizisinin nesnlerini o
beklenmez zira
7.9. Finalize ve Anlamsız Veri Toplama
C++ gibi bellek yönetimi kısmen kullanıcı elinde olan dillerde kurucu yordamlarla yapılan bellek ayırma işl
Bu özelliğe ek olarak yine de programcı tarafından kullanılan bellek dışı kaynakların ve benzeri işlemlerin e
programcı geri dönüş değeri olmayan ve parametre alamayan, çoklu kullanılamayan bu metodun içini dold
Çöp toplama işlemi sistem kontrollü bir işlem olduğu için programın çalışması esnasında ne zaman çöp t
zamana dayanan işlemlerin ve bunlar gibi yapılma anı önemli olan herhangi bir işlemin bu metodun içinde
Son olarak programcı, sistemin gerektirdiği durumların dışında kendi isteği doğrultusunda çöp toplama işle
7.10. final Özellikler
Programlama esnasında değeri hiçbir şekilde değiştirilemeyecek sabit değişkenlere ihtiyaç duyulması bekle
halde java derleyicisi tarafından hata alınacaktır. Böyle bir değerin tanımı aşağıdaki gibi yapılabilir.
Bu şekilde tnaımlanan bir değişkenin değeri programın ilerleyen safhalarında değiştirilemez. “final” anaht
7.11. Statik Özellikler
Daha önce bahsedildiği gibi her bir nesne kendisine ait bir durum ve davranış’ı bulunan sınıf gerçekleştirim
olarak bellekte sınıf tanımıyla birlikte yer tutarlar ve o sınıfa ait hiçbir nesne yaratılmamışsa dahi bellektek
Öte yandan statik öğeler ne kadar nesne oluşursa oluşsun kopyalanmazlar ve bellekte yalnızca tek kop
Temelde statik öğeler de bir sınıf öğesidir ve özellik veya metod olarak tanımlanabilecekleri gibi private
Bu nedenle tıpkı normal sınıf öğelerinde olduğu gibi statik öğelerde de değişkenlerin private olarak tanımla
Statik metodların kullanım alanı nesnelerden bağımsız sınıf geneli bilgiyi tutmaktır. Statik öğeler belirli bir
görülebilir.
Örnek olarak basit bir oyun yazmaya başladığımızı düşünelim. Bu oyunda uzaydan gelen marslılar dünyay
olarak gördüğümüzde buna uygun program şu şekilde yazılabilir.
public class Marsli { //Marsli.java
private static int nufus=0;
public Marsli()
{
++nufus;
System.out.println("Marslı sayısı arttı: "+nufus);
}
protected void finalize()
{
--nufus;
System.out.println("Marslı sayısı dustu: "+nufus);
}
public static int getNufus(){return nufus;}
}
public class deneme { //deneme.java
public static void main(String[] args) {
Marsli marslilar[]=new Marsli[6];
for(int i=0;i<6;i++)
{
marslilar[i]=new Marsli();
if(Marsli.getNufus()>=5)
System.out.println("Marslılar saldırıyor");
}
}
}
Programı incelediğimizde marslı sayısının 5’ i geçip geçmediği her seferinde kontrol edilmekte ve 5’ in üze
değişkenimiz nüfusu azaltmak için nesne bellekten çıkarılmadan önceki çağrılan finalize() metodu çoklu ku
Unutulmaması gereken önemli bir nokta statik metodlar içerisinde “this” değişkeninin kullanılamamasıdı
7.12. Metodların Çoklu Kullanımı
Programa dilleri aynı günümüzde kullanılan dillerde olduğu gibi iletişim amacıyla kullanılır. Yazdığımız pro
Örneğin bir nesne oluşturup ona isim vermek, bellekten kullanılmak üzere belirli miktar yer ayırıp onu dah
Günümüzde kullanılan bazı dil özelliklerini programa dillerine de uyarlamak kaçınılmaz bir getiridir. Örneği
dendiğinde gömlekte olduğu gibi köpeği çamaşır makinesine atmayız. Fakat kastedilen yıkama işi temelde
Aynen bunun gibi temelde benzer fakat gerçekleştirimde farklı yordamları birbirinden tamamen farklı isim
Peki bu ilke nasıl gerçekleştirilir? Metod isimleri aynı oluğunda ayrım, parametreler üzerinden yapılır. Me
Aşağıdaki örneği incelersek;
public class deneme { //deneme.java
void karsilastir(int a,int b){
if(a>b)
System.out.println(a+">"+b);
else
System.out.println(b+">"+a);
}
void karsilastir(double a,double b){
if(a>b)
System.out.println(a+">"+b);
else
System.out.println(b+">"+a);
}
void karsilastir(int a,int b,int c){
if(a>b && a>c)
System.out.println(a+"en buyuk");
else if(b>a && b>c)
System.out.println(b+"en buyuk");
else
System.out.println(c+"en buyuk");
}
public static void main(String[] args) {
int a=5;
int b=3;
int c=4;
double f_a=2.3;
double f_b=3.5;
deneme d=new deneme();
d.karsilastir(a,b);
d.karsilastir(f_a,f_b);
d.karsilastir(a,b,c);
}
}
İsimleri aynı 3 metod parametrelerindeki farklılıklar nedeniyle derleyici tarafından ayrılarak doğru şekilde
konusunda önceden kestirim yapılamaz.
Dikkat edilecek olursa 2 parametre alan iki yordam tamamen aynı işi yapmasına rağmen gereksiz kod te
Unutumamak gerekir ki metod çoklu kullanımı tamamen parametrelerdeki farklılıklar üzerinden gerkeçkleş
durumunda derleyicinin hangi çağıracağı f()’ e karar verememesidir. Bu da son derece doğaldır.
7.13. Parametre Aktarımı
Metodlara parametre göndermek sanıldığından daha karmaşık olabilir.
Bunlardan hangisinin kullanılacağına genellikle programcı tarafından karar verilir.
Değer ile parametre gönderirken gönderilen değişkenin bir kopyası oluşturulur ve bu kopya değişkene
yapılan değişikliker orjinal değerleri etkilemez.
Referans ile parametre göndermede orjinal değişkenin bellek adresi parametre olarak gönderilir ve de
değerinin değişmesine olanak verir. Bu sayede örneğin parametre olarak gönderilen büyük nesnelerin ile g
değiştirmesi bir etki alanı(scope) ihlali olarak görülür ve güvenlik açısından sorun yaratabilir.
Java’da programcı değer – referans ile parametre aktarmaya karar verme yetkisine sahip değildir. Jav
7.14. Nesne Bileşimi
Java’ da herşey nesne tabanlıdır. Bunu aklımızda tuttuğumuzda bir nesnenin bir başkasının içine dahil edi
public class Zaman { //Zaman.java
private int saat;
private int dakika;
private int saniye;
public Zaman() { belirleZaman( 0, 0, 0 );}
public Zaman(int saat,int dakika,int saniye)
{ belirleZaman(saat, dakika, saniye );}
public void belirleZaman(int saat, int dakika, int saniye)
{
this.saat=saat;
this.dakika=dakika;
this.saniye=saniye;
}
public void alZaman()
{
System.out.print(saat + ":" + dakika + ":" + saniye);
}
public void alBasitZaman()
{
System.out.print(saat % 12 + ":" + dakika + (saat<12 ? " AM" : " PM"));
}
}
public class Tarih { //Tarih.java
private int gun;
private int ay;
private int yil;
public Tarih() { belirleTarih( 1, 1, 1900 );}
public Tarih(int gun,int ay,int yil)
{ belirleTarih(gun, ay, yil );}
public void belirleTarih(int gun, int ay, int yil)
{
this.gun=gun;
this.ay=ay;
this.yil=yil;
}
public void alTarih()
{
System.out.print(gun + "/" + ay + "/" + yil);
}
public void alTarihABD()
{
System.out.print(ay + "." + gun + "." + yil);
}
}
public class Takvim { //Takvim.java
private Zaman saat;
private Tarih birTarih;
public Takvim() {
saat= new Zaman(21,15,30);
birTarih= new Tarih(19,03,1984);
}
public void bilgiGoster(boolean yerli)
{
if(yerli)
{
birTarih.alTarih();
System.out.print(" ");
vsaat.alZaman();
System.out.println();
}
else
{
birTarih.alTarihABD();
System.out.print(" ");
saat.alBasitZaman();
System.out.println();
}
}
}
public class deneme { //deneme.java
public static void main(String[] args) {
Takvim birTakvim=new Takvim();
System.out.println("Yerliler icin takvim");
birTakvim.bilgiGoster(true);
System.out.println("Yabancilar icin takvim");
birTakvim.bilgiGoster(false);
}
}
Bu kodda önceden yazmış olduğumuz zaman sınıfına ek olarak bir tarih sınıfı oluşturduk ve bu sınıfta tut
bunları iki farklı biçmde zaman ve tarih görüntülemede kullandık.
8.1. Katar Kavramı
Karakterler Java kaynak kodlarının yapı taşlarıdır. Bilgisayarın işlemesi için üretilen dizgeler karakterler
Karakter sabitlerine ulaşabilmek için onları Java’da onları tek tırnak içinde temsil etmek yeterlidir. Örn
Katarlar ardarda sıralanmış bir dizi karakterin tek bir birim gibi davranması sonucu karşımıza çıkarlar.Bir k
Java’da bir katar oluşturmak için aşağıdaki gibi bir tanımlama yapabiliriz:
Bu noktada katarın nasıl ifade edildiğine dikkat edelim. Çift tırnak içine alınmış isim bir katar nesnesidir.
temsil edecek bir referans değişkene ihtiyacımız olacaktır.
Daha önce verilen televizyon ve kumanda örneğini hatırlayınız. Şimdi elimizde bir katar nesnesi var ve bun
8.2. Katar Kurucuları
String sınıfı katar türünden nesnelerin oluşturulması için 7 farklı sunucu metod sağlar. Bu metodların ku
public class deneme { //deneme.java
public static void main(String[] args) {
char charDizisi[]= {'m','e','r','h','a','b','a',' '
,'d','u','n','y','a'};
byte byteDizisi[]= {'m','e','r','h','a','b','a',' '
,'d','u','n','y','a'};
String katar,katar1,katar2,katar3,katar4,katar5,katar6;
katar=new String("Selam");
katar1=new String();
katar2=new String(katar);
katar3=new String(charDizisi);
katar4=new String(byteDizisi);
katar5=new String(charDizisi,8,5);
katar6=new String(byteDizisi,0,7);
System.out.println(katar1);
System.out.println(katar2);
System.out.println(katar3);
System.out.println(katar4);
System.out.println(katar5);
System.out.println(katar6);
}
}
8.2.1. Katar Kurma Metodları
Bir önceki sayfada görülen katar kurma metodlarını inceleyelim.
Bu iki yöntem aslında birbiriyle eştir. İki yöntemde de kurucu yordama hazır olan bir katar nesnesi gönd
Dikkat edilecek olursa bu bölümün başında gösterilen hazır nesneye bir referans atama ile aynı şey değild
aynı nesneden yeni bir tane oluşturulur.
Burada katarın kurucu metodunun boş parametre listesiyle oluşturulmasını görüyoruz. Bu şekilde oluşt
char ve byte türü değişkenlerden oluşmuş iki farklı dizi katar kurucularına parametre olarak aktarılarak y
Karakter dizileri ile kurucu metodlar çağırıldığında kurucu metoda ek iki sayı parametresi ile karakter dizis
başlayarak sayıldığında) gösterir ken ikinci sayı da başlangıç karakterinden itibaren kaç karakterin kopyala
Bunlara ek olarak bir başka kurucu metod çeşidi de StringBuffer sınıfı türünden nesnelerin parametre ola
8.3. Katar Sınıfı Metodları
String sınıfı ile birlikte programcıya çeşitli metodlar da sunulmuştur bunlardan birkaçı aşağıdaki örnekte g
public class deneme { //deneme.java
public static void main(String[] args) {
char charDizisi[]=new char[5];
String katar = new String("merhaba dunya");
String katar1;
System.out.println("\""+katar+"\""+" katarinin uzunlugu:
"+katar.length());
System.out.println("\""+katar+"\""+" katarinin 3.karakteri:
"+katar.charAt(2));
katar.getChars(5,10,charDizisi,0);
katar1=new String(charDizisi);
System.out.println("\""+katar+"\""+" katarindan bir alt katar:
"+katar1);
}
}
Bir String sınıfının length() metodu ile katarın uznluğu tamsayı cinsinden bir değer halinde öğrenilebilir.
charAt() metodu ile metoda parametre olarak gönderilen noktadaki karakter değeri edinilebilir. Bu durum
getChars() metodu ile önceden oluşturulmuş bir karakter dizisinin istenilen bir yerine katarın bir alt parç
gösterir. Üçüncü parametre kopyalanacak char dizisi değişkenidir. Son parametre de kopyalama işleminin
8.3.1. Katar Karşılaştırma
Java’da katar karşılaştırma işlemleri dil kurallarına uygun olarak gerçekleştirilir. Yani örneğin “deniz” ve “
Bu tabloda harfler alfabetik olarak sıralanmıştır, bu nedenle “e” harfinin tamsayı karşılığı “i” harfinden da
beklediğimizden farklı sonuç verebilriler.
Karşılaştırma metodlarının en önde geleni eşitlik kontrol etmedir. Bunun için katar nesneleri için tanımlanm
Bu noktada equals() metodu mantıksal bir karılaştırma yapacak ve değerler birbirine eşitse boolean “tru
dönecektir çünkü büyük küçük harf ayrımı söz konusudur. Bu ayrımı aşmak ve büyük-küçük harf farklı
Eşitlik kontrolünde unutulmaması gereken ayrıntı “==” işlecinin kullanılmasıdır. Temel veri tiplerinde çalış
Bunun nedeni “==” işlecine temel veri tipleri haricinde gelen referans değişkenleri söz konusu olduğunda
komutuyla yaratılan nesnenin birbirinden farklı yorumlanması ve eşit kabul edilmemesi doğaldır.
8.3.1.1. Bir Başka Karşılaştırma Metodu: compareTo()
Bir başka karşılaştırma metodu compareTo() metodudur. Bu metod kullanılarak metodu çağıran katar ile
compareTo() metodu yukarıdaki şekilde çağırılarak bir tamsayı değeri döndürür. Eğer karşılaştırılan kata
pozitif tamsayı sonucu elde edilecektir.
İki karakter katarının belirli alt katarlarını karşılaştırmak için ise regionMatches() metodu kullanılabilir. B
Bu kullanımda ilk parametre katar1’ in (yani metodu çağıran katarın) karşılaştırma yapılacak alt katarının
noktasını, son parametre ise alt katarların uzunluklarını temsil eder. Bu durumda yukarıdaki katarlar
için aşağıdaki gibi çağırmak yeterli olacaktır.
8.3.1.2. Bir Başka Karşılaştırma Metodu: startsWith() ve endsWith()
Bir başka çift önemli katar karşılaştırma metodları ise startsWith() ve endsWith() metodlarıdır. Bu iki m
komutlar “true” değerlerini döndüreceklerdir.
startsWith komutunun bir başka kullanım alanı da bir katarın içinde bir başka katarın belirli bir pozisyond
bulunmadığını araştırır.
Örnekteki çağrı “true” değerini döndürecektir. Dikkat edilmesi gereken nokta bu aramanın sadece alt kat
8.3.2. Özet Bilgi Görüntüleme
Hash tabloları nesnelerin özet bilgilerinin çıkartılarak istiflenmesinin hızlandırılması için üretilebilen karakte
gizlenmesi amacıyla da kullanılabilirler. Hash algoritmaları aynı nesnelere aynı kodları üreteceğinden bir k
Bir katar olarak kullanıcıdan aldığımız parolaları bu halde veritabanında saklarsak bu veritabanına göz gez
hashCode() yordamı ile katarın özet bilgisini kullanabilir ve böylece parola güvenliğini sağlamış oluruz.
Örneğin
public class deneme { //deneme.java
public static void main(String[] args) {
String katar= “merhaba dunya”;
System.out.println(katar.hashCode());
}
}
ifadesi sonucu
Özetini elde etmiş oluruz. Aynı katardan her seferinde aynı özet üretileceğinden özet bilgisi elimizde olan i
8.3.3. Alt Katar ve Karakter Bulma
Herhangi bir katarın indexOf() isimli metoduyla katar altında karakter veya alt katar arayabiliriz. Bu met
örneklerde olduğu gibi.
İlk örnekte katarda ‘c’ harfi aratılırken ikinci örnekte “den” katarı aratılmaktadır. Bu durumda dikkat edil
gibi. indexOf() metodunun bir başka veriyonu da ikinci bir parametre alarak bu parametre ile belirtilen o
Örneğin aşağıdaki iki örnekte de katar1’ in ilk iki karakteri ihmal edilereke istenilen karakter ve katar geri
Bu yordam geri dönüş deği olarak parametrede verilen argümanın geçtiği ilk kısımın katar dizisi içindeki p
Aranan karakter veya katar bulunamadığında ise negatif bir tamsayı değeri geri döndürülür.
indexOf() yordamı benzeri bir yordam da lastIndexOf()’ dur. indexOf() ile aynı çalıştırılma şekline sah
8.3.4. Alt-Katar Elde Etme
Bir katara ait herhangi bir alt-katarı elde etmek için String sınıfının substring() metodunun iki farklı çeşid
public class deneme { //deneme.java
public static void main(String[] args) {
String katar1=”merhaba dunya”;
System.out.println(katar1.substring(5));
System.out.println(katar1. substring(4,9));
}
}
Yukarıdaki satırlar işletildiğinde aşağıdaki sonuçlar alınır.
Kullanılan ilk substring() metodu parametre olarak aldığı tamsayının gösterdiği katar poziysonundan baş
programcıya kopyalanacak alt-katarın başlangıç ve bitiş pozisyonlarını belirtme imkanı sunar.
8.3.5. Katar Birleştirme
İki katarı birleştirmek için String sınıfı tarafından sağlanan concat() metodu kullanılır. Bu metod kullanıld
sonrası orjinal katarlarda bir değişme olmaz. Dönen değer metodu çağıran katarın sonuna parametre ile g
public class deneme { //deneme.java
public static void main(String[] args) {
String katar1="merhaba";
String katar2=" dunya";
System.out.println(katar1.concat(katar2));
System.out.println(katar1);
}
}
System.out.println(katar2);
Sonucunda alınan çıktı;
8.3.6. Çeşitli String Metodları
 replace() metodu
şeklinde çağırılır. Verilen ilk karakter parametrenin, ikinci karakter parametre halinde değiştirilmesine yol
 toUpperCase() metodu
şeklinde çağırılır. Geri dönen yeni nesne çağıran katardaki tüm küçük harflerin büyük harfe dönüştürülmü
 toLowerCase() metodu
şeklinde çağırılır. Geri dönen yeni nesne çağıran katardaki tüm büyük harflerin küçük harfe dönüştürülmü
 trim() metodu
şeklinde çağırılır. Geri dönen yeni nesne çağıran katarın başındaki ve sonundaki tüm görünmez karakterle
olmaz.
 toCharArray() metodu
şeklinde çağırılır. Geriye katardaki karakterleri içeren bir karakter dizisi döner. Dönen bu karakter dizisi aş
değişme olmaz.
8.3.6.1. valueOf() Metodu;
String sınıfının, statik valueOf() metodu ile çeşitli farklı tiplerden değişken katar haline dönüştürülebilir.
türden bir değişkene katar haline çevirmeye yarar.
public class deneme { //deneme.java
public static void main(String[] args) {
char charDizisi[]={'m','e','r','h','a','b','a'};
boolean b=false;
char karakter='T';
int i=42;
long l=42000000;
float f=4.2f;
double d=42.4242;
Object o= "selam";
System.out.println(String.valueOf(charDizisi));
System.out.println(String.valueOf(charDizisi,2,4));
System.out.println(String.valueOf(b));
System.out.println(String.valueOf(karakter));
System.out.println(String.valueOf(l));
System.out.println(String.valueOf(f));
System.out.println(String.valueOf(d));
System.out.println(String.valueOf(o));
}
}
Şeklinde oluşturulan bir sınıfta;
şeklinde bir çıktı alınacaktır. Son olarak Object sınıfından bir değişken ile katar nesnesinin ilişkilendirilme
Bir anlamda Object sınıfı String sınıfını soyut olarak kapsamakta olduğundan bu şekilde bir kullanım serb
Karakter dizilerinin iki farklı şekilde dönüştürülebilmesi de daha önceden açıklanan katar işlemleriyle görü
8.3.6.2. intern() Met
intern() metoduyla kopyası olu
Bu sayede intern() metodunun
gelir.
Örneğin ;
katarları katar1==katar2 şek
bahsetmiştik. Çünkü bu iki nesn
yükümlüdür. Doğru biçimde bu
gerekir. Fakat bu metodlar perf
metodları kullanılarak içeriğe b
Bu şekilde oluşturulmuş katar3
işleci ile karşılaştırılabilirler. Un
olduğudur. katar1-katar2 , ka
hala bellekte farklı nesneler ola
Burada eş nesnelerle bellekte y
8.4. Komut Satırı Par
Daha önce de belirtildiği gibi sın
çalıştırıldığı zaman işlettiği met
Çalıştırılan Java programları he
sahiptirler. Bu parametreler ma
dizinin ilk değişkeni program ad
parametre aktarımını açıklayalı
public class deneme {
public static void ma
int x;
for(x=0;x< args.lengt
System.out.println(ar
}
}
Programımızda bir döngü içeris
dizisi içerisindeki parametreler
desteklenmektedir.
Komut satırından alınan param
parametrelerinin en büyük kulla
giriş-çıkış dosyalarının program
8.5. StringBuffer Sınıfı
String sınıfı katarlar üzerinde iş
eksikliği bir kez değer aldıkları
kapasitesini aşan bir nesne oto
Unutmamak gerekir ki String sı
üstündür. Bu neden herhangi b
gösterilmelidir.
8.5.1. StringBuffer Kurucu Metodları
StringBuffer nesneleri 3 farklı şekilde oluşturulabilirler.
Örnekteki gibi ön tanımlı kurucu metodlar tarafından çağırıldıkl
kapasitesi 16 karakter olarak belirlenmiştir.
Kurucu metoda parametre olarak bir tamsayı gönderildiğinde il
katarın uzunluğu parametrenin değeri kadardır. Örneğimizde
Kurucu metoda parametre olarak bir katar gönderildiğinde bu d
uzunluğuna ek olarak 16 karakter kadardır. Örneğimizde 7 ka
bir katar oluşturulmuştur.
StringBuffer sınıfından bir nesne herhangi bir nedenle String s
methodu toString() kullanılabilir.
8.5.2. StringBuffer Sınıfının Kapasite ve Uzunluk Özellikleri
StringBuffer sınıfı ile bir katarın kapasitesi ve uzunluğu biribirinden ayrılmıştır. Bir katarın uzunluğu o an
belirlenmiştir. Kapasite ise StringBuffer sınıfının dinamik özelliklerinden kaynaklanan yeni bir kavramdır.
uzunluğunun alabileceği en büyük sayı olarak belirlenir.
Bir başka deyişle kapasite, katarın tutabileceği en büyük karakter sayısıdır.
Örneğin StringBuffer sınıfından yeni bir katar oluşturalım:
Şu an bu katar’ın uzunluğu 13 karakter iken, hatırlayacağınız üzere kapasitesi ön tanımlı olarak 16 karak
belirlenmiştir.
Katarın bu özelliklerine aşağıdaki şekllerdeki tanımlı metodlarla ulaşabiliriz.
ve
Bu değerlere ulaştığımız gibi onları değiştirebiliriz de. Örneğin tanımlı kapasiteyi değiştirmek için aşağıdak
gönderilen 65 tamsayı değerine yükseltebiliriz.
Dikkat etmemiz gereken nokta katarın o anki kapasitesini bu yolla düşüremeyeceğimizdir.
Uzunluk değişimi için ise benzer şekilde aşağıdaki gibi bir metod çağrısı yapılabilir.
Eğer bu şekilde belirtilen uzunluk o anki uzunluktan kısaysa katarın sonundaki karakterler göz ardı edilere
örneğimizdeki metodu çağırdıktan sonra katarımız “merhaba du” haline gelir. Tersi bir durumda ise fazla
temsil edilen karakterler) karkaterleri ile doldurulur.
8.5.3. Çeştli StringBuffer Metodları
Aynı String sınıfında olduğu gibi StringBuffer sınıfında da katarlar üzerinde çeşitli işlemler tanımlanmışt
ile parametrede gönderilen pozisyondaki karakter geri döndürülür. setCharAt() metodu ise pozisyon beli
karakteri parametre olarak alır ve katarın belirtilen pozisyonundaki karakteri parametresi ile değiştirir.
public class deneme { //deneme.java
public static void main(String[] args) {
StringBuffer katar=new StringBuffer(“merhaba dunya”);
for(int i=0;i < 3;i++)
katar.setCharAt(i,katar.charAt(katar.length()-(i+1)));
System.out.println(katar);
}
}
Bu metodları kullanırken dikkat edilmesi gereken nokta katarın uzunluğunu geçecek parametreler gönde
derleyici hata vermez fakat çalışma zamanında aykırı durumlar oluşur.
String sınıfındakilere benzer bir başka metod getChars() metodudur ki bu metodla katarın ilk iki param
bitiş noktaları belirlenen bir parçası 3. parametreyle belirlenen bir karakter dizisinin 4. parametreyle
yerleştirilmesi söz konusudur.
reverse() metodu ise katar içeriğini ters çevirmeye yarar. Bütün bu metodlarda dikkat edilmesi gereken
işlemlerin String sınıfı nesnelerinden farklı olarak katarın kendisini etkilemesidir.
8.5.4. Append Metodu
StringBuffer sınıfı ile birlikte sunulan 10 adet çoklu kullanılmış append() metodu ile birlikte StringBuffe
nesnelerine istenilen birçok tipten değişkenin eklenmesi sağlanır. append() metodunun genel kullanım ş
Object o=”merhaba”;
String s=”dunya”;
StringBuffer katar=new StringBuffer();
katar.append(o);
katar.append(‘ ’);
katar.append(s);
boolean , int , long , float , double. Örneğimizde bu türlerin 3 tanesi kullanılmıştır. char dizileri için ek
parametre daha alarak bir alt-katar alma işlemi yapılabilir.
Append metodunun kullanıldığı bir başka yer ise Java derleyicisinin içidir. String sınıfı ile birlikte + ve +=
kullanılırken aslında bu işlemler Java derleyicisi tarafından StringBuffer append() işlemlerine çevrilir. Ör
İşlemi gerçekleştirilirken derleyici tarafından
metodları çağırılır.
8.5.4.1. insert() Metodu
append()’ e benzer bir başka metod insert() metodudur. insert()’ in farkı katarın, metodun ilk
parametresiyle belirttiğimiz pozisyonundan itibaren başlayarak ikinci parametredeki katarı
metodu çağıran katara eklemektir. Bu amaçla 9 farklı insert metodu oluşturulmuştur. insert()
için karakter dizilerinin alt-kümelerini belirleme işlevi gerçekleştirilmez.
Örneğin;
ile katar1 nesnesini “merhaba dunya” katarını taşıyacak şekilde oluşturmuş oluyoruz.
8.6. Karakter Sınıfı
Java temel veri tiplerini sınıf nesneler olarak ele alabilmek için bir dizi sınıf tanımı sunar.
Bunlar Boolean, Character, Double, Float, Byte, Short, Integer ve Long sınıflarıdır.
Bu sınıfların hepsi Number sınıfından türemişlerdir ve kullanıcıya ilgili veri tipi üzerinde
işlem yapabilmek için bir dizi metod sunarlar. Bu tarz sınıflara işlevlerinden ötürü
sarmalayıcı sınıf adı verilir. Katarlarla ilgili olarak Character sınıfının incelenmesinde
yarar vardır.
Birçok Character sınıfı verisi statiktir ve sınıfa sunulan karakter değerinin üzerinde çeşitli
testler yapılmasını sağlar. Öte yandan bu sınıfın statik olmayan metodları da vardır. Bu
metodların kullanılabilmesi için sınıf kurucusuna bir karakter değeri göndererek yeni bir
nesne oluşturmak gereklidir.
Öncelikle birkaç statik Character sınfı metodunu incelyelim. Bütün bu metodların
aşağıdaki değişkeni aracılıyığla yapıldığını farz edelim.
8.7. StringTokenizer Sınıfı
Katar jetonlama işlemi belirli bir katarın içinde geçen belirli parçacıkları sınır olarak
kabul edip alt katarlara ayırma işlemidir. Her bir alt katar bir jeton olarak kabul edilir.
Sözü edilenbelirli parçacıklar genellikle görünmez karakterler olarak anılan boşluk,
sekme ve satırbaşı karakterleridir. Başka karakterler yada katarlar da sınır olarak kabul
edilip işlemler yapılabilir. Bu tür jetonlama işlemlerinde kullanılan Java sınıfı
StringTokenizer sınıfıdır ve bu sınıftan bir nesne aşağıdaki gibi oluşturulabilir.
Kurucu metod bu şekilde çağırıldığında ön tanımlı sınırlama karakterleri olan görünmez
karakterler yani ‘ ‘ , ‘\n’ , ‘\t’ ve ‘\r’ kullanılarak jetonlama işlemi burada
parcalanacak_katar olarak adlandırılmış String nesnesi parametre kullanılarak yapılır.
Bu sınıfa ait diğer iki kurucu metoddan ilki iki parametre alır, bunlardan ilki yukarıdaki ile
aynıdır, ikincisi ise her karakteri sınır kabul edilecek katarı bir String nesnesi aracılığıyla
parametre olarak alır. Üçüncü tür kurucu metodda ise ek olarak gelen üçüncü parametre
bir boolean türü değişkendir. Bu parametre ‘true’ olarak geçirildiğinde katarda belirtilen
sınır karakterleri de jeton olarak kabul edilir.
8.7.1. Örnek
Bir örnek üzerinden jetonlama işlevlerini inceleyelim.
import java.util.*; //deneme.java
public class deneme {
public static void main(String[] args) {
String parcalanacak_katar=new String("merhaba dunya");
StringTokenizer jetonlar= new
StringTokenizer(parcalanacak_katar,"hn",true);
System.out.println(parcalanacak_katar);
while(jetonlar.hasMoreTokens())
System.out.println(jetonlar.nextToken());
}
}
İlk satırdaki import komutu ile StringTokenizer sınıfının içinde bulunduğu paketi
programımıza dahil ediyoruz. Döngü içerisindeki nexToken() metodu ile ayrılmış
jetonlardan ilkinden itibaren sırasıyla her çağırılışında bir jetonu String nesnesi olarak
döndürüyoruz.
Döngü karar kısmında bulunan hasMoreTokens() her seferinde nestToken() ile
aldığımız jetonların sonuna gelip gelmediğimizi kontrol etmede kullanılıyor. Dikkat
edilmesi gereken nokta ikinci parametrede girilen “hn” katarındaki her karakterin bir
sınır karakteri olarak kabul edileceği ve 3. parametrenin ‘true’ olarak verilmesi nedeniyle
sınır değerlerinin de jeton olarak alınacağıdır.
Bu durumda programın çıktısı aşağıdaki şekilde oluşur:
BÖLÜM-9ÖZYİNELEME
9.1. Yordam Çağrımına Yeni Bir Bakış Açısı
Şu ana kadar gördüğümüz metodlar içlerinden başka metodları çağıran ve temel işlevleri
yerine getiren hiyerarşik dizilerdi. Bu noktada ise bir metodun doğrudan veya çağırdığı bir
metodun içerisinden kendisini çağırması anlamına gelen özyinelemeye değineceğiz. Bazı
durumlarda problem çözmeyi kolaylaştırmak amacıyla özyineli yordamları algoritmaya
dahil etmek işimizi kolaylaştırır. Öte yandan bu çeşit metodları anlamak ve etkin
biçimde kullanabilmek için özyinelemeye hakim olmak gerekir.
Özyineleme aslında en çok bilinen problem çözme yöntemlerinden olan parçala-yönetle
benzerlik gösterir. Her özyineli metodun bir taban durumu (değeri) olmalıdır. Taban
değeri metodun en küçük parçasının davranış şeklidir denilebilir. Bu davranış genellikle
giriş değerlerinin en küçüğü için bilinen değerdir. Büyük problem özyineleme ile çözülmek
üzere adım adım daha küçük parçalara ayrılır. Bu parçalama adımlarının her birine
özyineleme denir.
Her özyinelemede problemin daha küçük bir kısmı problem çözücü metoda
gönderilerek çözüm aranır. Problem çözücü metod kendisine gelen küçük parçayı
çözebiliyorsa (taban durumu söz konusu ise) çözer aksi durumda bir parçalama işlemi
daha yaparak kendisini yine çağırır. Bu parçalama işlemleri ağacın dallanması gibi her
seferinde metod kendini çağıracak şekilde ilerler; ta ki tüm parçalar taban durumu
olacak kadar küçülene dek.
Taban durumları son özyinelemenin ardında çözülüp özyinelemenin yapıldığı adıma
sonuçla birlikte geri dönerler. Böylece problemin taban durumundan bir birim daha büyük
parçası için bir değer elde edilmiş olur. Bu değer problem çözümünde kullanılır ve bir
önceki özyinelemeye dönülür. Bu şekilde her adımda taban durumdan büyüyerek ana
problemin çözümüne kadar gidilir.
Dikkat edilecek olursa özyineleme için en önemli noktalar problemin ne şekilde
parçalanacağının belirlenmesi ve özyineli metodun içinde doğru yerlere “return”
komutlarının yerleştirilmesidir. En sık yapılan hata özyineleme ile çağrılan her
metodun sıfırdan başlayarak işlediğidir. Metod kendini özyineli olarak çağırdığında
eski parametreler bir önceki adımda asılı kalmıştır, artık yeni parametreleriyle yeni bir
metod çalışmaktadır.
9.1.1. Örnek
Şimdi iyi bilinen bir matematiksel ifadeyi özyinelemeli olarak çözümleyelim. n! Olarak
gösterilen n sayısının faktöriyeli şu ifade ile elde edilir:
1! = 1 olduğu ve 0! = 1 olduğu ön tanımlı olarak elimizdedir.
Bu yolla örneğin aşağıdaki gibi bir sonuç elde ederiz.
Dikkat edecek olursak faktöriyel ilişkisini şu şekilde özetleyebiliriz.
Problemi nasıl özetlediğimize dikkat edelim. Faktöriyel işlemini iki parçaya ayırdık;
bunlardan biri bir sabiti temsil ederken diğeri problemin aynısının daha küçük
kapsamlısıdır.
Problemi bu şekilde çözümledikten sonra yapılması gereken son derece basit bir işlemdir.
Yazılacak özyineli metod her seferinde kendini bir düşük sayılı parametresi ile
çağıracaktır. Peki taban durumu nedir? Bu yine problemin özünde tanımlanmıştır 1!=1 ve
0!=1. Bu durumda metod eğer parametre olarak 1 sayısını aldıysa geriye 1
döndürecektir.
Bu inceleme doğrultusunda oluşturulacak metod:
Görüldüğü gibi metod sonsuz çağırma döngüsüne girmemek için en başta taban
durumunu ele almış ve parametre aldığı sayının 1 veya 0 olması durumunda sonuç olarak
1 döndürmüştür. Daha sonra ise aynen sorunu çözümlediğimizde yaptığımız gibi
parametre değeri ile parametrenin bir eksiğinin faktöriyelinin çarpımını sonuç olarak
döndürmüşüzdür.
Unutmamak gerekir ki (n-1)! de n! gibi bir faktöriyel hesabıdır ve n! inkine benzer
şekilde (n-1)!= (n-1) . (n-2)! şeklinde çözülebilir. Şimdi n! eşitliğindeki (n-1)! Yerine
yeni bulduğumuz eşitliği yazalım ve çözümlemeyi bu şekilde (n-2)! vs. için de devam
ettirelim. Bu sayede özyinelemeli metodların nasıl çalıştığı konusunda fikir edinilebilir.
Derleyici da aynen bu şekilde işlem yapar.
Bu yordamı kullanan örnek bir program ve çıktısı aşağıda verilmiştir. Statik
metodların içinden sadece statik metodları çağırabildiğimize dikkat ederek örneği
inceleyelim.
public class deneme { //deneme.java
public static void main(String[] args) {
System.out.print("5! = "+faktoriyel(5));
}
public static long faktoriyel(long sayi)
{
if(sayi<=1)
return 1;
else
return sayi*faktoriyel(sayi-1);
}
}
9.2. Doğal Bir Özyineleme Örneği : Fibonacci Serisi
0, 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 şeklinde devam eder. Görüldüğü gibi serinin devam etme
şekli her eleman için kendinden önceki iki elemanın toplamına eşit olması şeklindedir.
Şimdi bu serinin ne şekilde özyineli olarak modellenebileceğine göz atalım. Serinin taban
durumu tanıma uymayan ön tanımlı 0 ve 1 elemanlarıdır. Bu ilk iki eleman için;
Diğer elemanlar için tanım uygundur ve her eleman kendisinden önce gelen iki elemanın
toplamı şeklinde ifade edilebilir.
Şimdi bu çözümlemenin ardından programı yazalım:
9.2.1. Hesaplama
Şimdi bir önceki sayfada metodun örnek olarak 3. fibonacci değerini hesaplarken
gerçekleştirdiği metod çağrılarına bakalIM.
Bu yordamı kullanan örnek bir program ve çıktısı aşağıda verilmiştir. Statik metodların
içinden sadece statik metodları çağırabildiğimize dikkat ederek örneği inceleyelim.
public class deneme { //deneme.java
public static void main(String[] args) {
System.out.print("fib(5) = "+fibonacci(5));
}
public static long fibonacci(long sayi)
{
if(sayi==0 || sayi==1)
return sayi;
else
return fibonacci(sayi-1)+fibonacci(sayi-2);
}
}
Özyinelemeli metodlar kullanılırken dikkat edilmesi gereken nokta birden bire çok fazla
metod çağırımı durumu ile karşı karşıya kalabileceğimizdir. Örneğin serinin 3. değeri için
dahi kendisi dahil 5 adet fibonacci() yordam çağırısı yapılmıştır. Bu noktada son derece
masum gözüken bu sayı 20. eleman için 21891 fibonacci() metodu çağrısı 30. eleman
için ise 2692537 fibonacci() metodu çağrısı sayısına fırlamaktadır. Bu nedenle
özyinelemeli metodlar oluşturulurken dikkatli davranıp olurluk çalışmasını ve
çözümleme kısmını iyi yapmak gerekir.
Bu noktada özyinelemeli problem çözümü ile sıralı işletim (iteratif) ile problem
çözümü arasında bir tercih yapmak gerekirse, eğer performans göz önünde tutulacaksa
tercih açıktır. Özyineli yordamlar çok fazla metod çağrısı yaptığı ve bu nedenle belleği
meşgul ettikleri için her zaman performans problemleriyle karşı karşıyadırlar. Üstelik
özyineli olarak çözülen her yöntem matematiksel yöntemler kullanılarak sıralı işletim ile
çözülecek hale getirilebilir.
O zaman neden özyineli algoritmalar mevcuttur ve kullanılır. Performansın önemli
olduğu durumlarda bazı algoritmaların (iki örneğini az önce gördük) doğasını çok iyi
yansıttığı için özyineleme tercih edilir. Öte yandan her problemin sıralı işletim ile
çözülebilecek çözümü zaman zaman kolay elde edilemeyebilir. Bu gibi durumlardan
dolayı ileride donanımsal gelişimlerin performans problemlerini azaltacağını da göz
önünde tutarak bu sanatsal problem çözme metodolojisi üzerine kafa yormakta yarar
vardır.
BÖLÜM-10KALITIM
10.1. Kalıtım Kavramı
Nesneye dayalı programlamayı gerçek dünyayı modelleme konusunda en güçlü kılan
unsurlardan biri de kalıtım unsurudur. Şimdiye kadar gördüğümüz kapçıklama ve bilgi
gizleme kavramları işlevsel açıdan benzer olan yazılım parçacıklarını bir araya getirerek
yazılımlara düzen kazandırırken, kalıtım ve bir sonraki bölümde görülen çok biçimlilik ile
yazılım parçalarına bir hiyerarşi kazandırmakta bu nedenle de yazılım modellemesi ve
yeniden kullanılabilirlik konularında avantajlar sağlamaktadır.
Gerçek dünyadan bir örnekle kalıtıma örnek verelim. Çarşıya alışverişe çıktığımızda hangi
mağazaya girdiğimizden bağımsız olarak iletişim kurduğumuz insanlar toplumsal
statülerle tanımlanmış “esnaf” niteliğindedir. Bir esnafla iletişim kurarken ondan
beklediğimiz işlevler ve özellikleri belirlidir. Her bir esnaf alışveriş esnasında para alma,
para üstü verme ürün tanıtma gibi çeşitli işlevleri yerine getirmek durumundadır ve
esnafların kasaları dahilinde paraları ve ürün stokları mevcuttur.
Fakat alışveriş durumunda esnaftan esnafa değişen bazı farklı özellikler de
bulunmaktadır. Örneğin konfeksiyon dükkanı olan bir esnaftan ek bir işlev olarak giysi
denemeye izin vermesini isteyebiliriz. İşlev farklılığının ötesinde örneğin bir lokantadan
alışveriş yaparken alışveriş ortamımız “para”dan “yemek bileti” haline dönüşebilir. Her
zaman biçimsel değişiklik zorunlu değildir bazen davranış da değişebilir örneğin her esnaf
ortak özellik olarak ürün tanıtır fakat her esnafın tanıttığı ürünü farklıdır.
Kalıtımda da yukarıda özellikler göz önünde tutularak iki ana bileşen ortaya çıkar. Taban
sınıf ve türeyen sınıf.
Kalıtım geçişli tanımlanmış bir fonksiyon olduğu için alt sevite türetmeler üst seviye
türetmelerin özelliklerini de içerir. Yani eğer “köpek” sınıfı “memeli” sınıfında türemişse
ve “memeli” sınıfı da “hayvan” sınıfından türemişse “köpek” sınıfı hem “memeli”
hem de “hayvan” sınıflarının özelliklerini taşır.
Kısaca “memeli”ler bir “hayvan”sa ve “köpek”ler “memeli”yse “köpek”ler
“hayvan”dır. Şimdi önceki bölümlerde gördüğümüz “sahip olma” ilişkisini hatırlayalım
ve benzer bir ilişki türetmek için kalıtım üzerine düşünelim.
Kalıtım ile altsınıflama ismi verilen kavram gerçekleştirilmektedir. Altsınıflama bir
sınıftan bir başka alt sınıfın oluşturulması temeline dayanır. Oluşturulan alt sınıfların en
önemli özelliği nesnelerinin türedikleri üst sınıfların nesneleri yerine kullanılabilmeleridir.
Çünkü türetilmiş alt sınıflar üst sınıfların tüm özelliklerini de yerine getirirler.
10.2. Kalıtımın Kullanım Amaçları
Kalıtım programcı açısından çeşitli amaçlarla kullanılabilir. Bunlar özetle:
Kalıtımın genel amaçları ve kullanım şekillerini inceledik. Şimdi Java’nın kalıtımla ilgili
sözdizimsel özelliklerini inceleyelim
10.3. Java ve Kalıtım
Java’da bir sınıfı bir diğeri ile kalıtım yolu ile ilişkilendirmek için “extends” takısı
kullanılır. Örnek olarak aşağıdaki çizeneğe göz atalım.
Çizenekteki kutular sınıfları, ok ise kalıtım ilişkisini ifade etmektedir. Buna göre taban
sınıfı olarak “hesap” seçilmiş ve bundan iki adet altsınıf türetilerek “yerli” ve “doviz”
taşıtlar modellenmiştir. Buna göre bir bankada yerli para hesabı ve döviz hesabı olmak
üzere iki farklı tür hesap açılabilir. Şimdi bu sınıfların hiçbir özelliğini ve metodunu
belirlemeden boş tanımlarla bu hiyerarşiyi oluşturalım
public class hesap
{
}
public class yerli extends hesap
{
}
public class doviz extends hesap { }
Bu noktada kafamızdaki hesap kavramı için düşündüğümüz ortak özellikleri ve bazı
işlevleri sınıflara kazandırmalıyız. Bütün hesaplar için ortak özellikleri basitçe düşünecek
olursak, bunlar hesaptaki para miktarı, hesabı belirlemek için bir hesap numarası ve
hesabın sahibinin ismi olmalıdır.
10.3.1. Java ve Kalıtım (Devamı)
Yerli hesaplar için banka tarafından düşünülen sistemde hesap sahibine belirli bir miktara
kadar kredi verme işlevi düşünülmüştür. Bu yüzden yerli hesap üzerinden verilecek
krediler elde tutulmalıdır. Verilen kredi miktarı belirli bir miktarı aştığında hesap bloke
edilecektir. Öte yandan banka sistemine göre sadece yerli hesaplara faiz uygulanmaktadır
ve belirli aralıklarla hesaba sabit faiz miktarı kadar faiz uygulanacaktır. Döviz hesapları
için tutulması gereken özellikler ne tür dözvizin hesapta tutulduğudur. Aynı zamanda
banka, müşterilerine döviz hesaplarındaki miktarın yerli para cinsinden miktarını
görüntüleyebilmektedir. Bütün bu özellikler dışında banka müşterilerine hesap numarası
verilen hesabın bilgilerini de(hesap türü ile birlikte) görüntüleyebilmektedir. Şimdi bu
bilgiler doğrultusunda içleri boş metodlarla kurulmuş hiyerarşiye bakalım.
public class hesap //hesap.java
{
protected long miktar;
protected int hesap_no;
protected String sahip;
protected boolean bloke;
public
public
public
public
}
hesap()
void para_yatir()
void para_cek()
void hesap_bilgisi()
public class yerli extends hesap //yerli.java
{
private int faiz_orani;
private long verilen_kredi;
public yerli()
public void kredi_ver()
public void faiz_islet()
}
public class doviz extends hesap //doviz.java
{
private double endeks;
private String doviz_turu;
public doviz()
public double yerliye_cevir()
public void setEndeks()
}
Hesap sınıfı içerisinde gördüğümüz tüm öğeler kalıtım yoluyla oluşturulan türemiş
sınıflar için de kullanılabilir durumdadır. Bu noktada sınıfların ve nesnelerin öğelere
ulaşım yetkilerini gözden geçirmekte fayda var.
10.4. Etkinlik Alanı Kuralları
Hatırladığımız üzere bir sınıfın nesnesi tarafından public öğelerine ulaşılabilirken private
öğelerine ulaşılamıyordu. Bu noktada o sınıfın private ve public tüm öğelerinin sınıf
içerisinden erişilebildiği doğaldır ve açıkça görülebilir.
Peki sınıf erişimi ile nesne erişimi farkı nedir? Nesne erişimi, bir sınıf üzerinden
tanımlanmış tüm nesnelerin sınıf öğelerine ‘.’ işleci aracılığıyla ulaşması anlamına gelir.
Sınıf erişimi ise sınıfta tanımlanan metotların tanımlanması sırasında sınıfın public ve
private öğelerine ulaşmasıdır.
Bu noktada kalıtım yoluyla erişim de gündeme geldiğinden erişim haklarına ve protected
tür öğelere göz atmakta yarar var. Kalıtımda kullanılan taban sınıfı için hemen hemen
hiçbir değişiklik yoktur. Sınıf ve nesne erişimleri kalıtım öncesindeki gibidir tek değişen
yeni protected öğelerin gelmiş olmasıdır ki bu öğeler sınıftan türetilmiş altsınıf ve
bunların nesneleri haricinde tamamen private öğelermiş gibi davranırlar.
Öte yandan türetilmiş alt sınıflar için durum biraz daha karışıktır. Bu sınıflar taban sınıfları
hariç normal sınıflarınkine benzer erişim haklarına sahiplerdir. Taban sınıflarına karşı ise
altsınıflar, taban sınıfların protected üyeleri hariç yabancı sınıflar gibilerdir.
Taban sınıfların public öğelerine sınıf içinden ve nesneler üzerinden ulaşabilirlerken
private öğelere hiçbir şekilde ulaşamazlar. Taban sınıftaki protected öğeler alt sınıflar
tarafından sınıf bazında ulaşılabilirken nesne bazında ulaşılamazlar. Yani taban sınıfında
protected tanımlanan öğeler alt sınıfların metotları tarafından ‘.’ işleci kullanılmadan
ulaşılabilirler. Altsınıfların nesneleri içinse bu geçerli değildir.
Bu bilgiler doğrultusunda sınıf yapımızı incelediğimizde taban sınıfımız olan hesap
içerisinde tanımlanmış tüm özellik öğelerinin alt sınıflarla paylaşıldığını görebiliyoruz.
yerli ve döviz sınıfları tanım esnasında kendi öğelerini kullanabildikleri gibi sanki taban
sınıf olan hesap sınıfındaki öğeler de alt sınıflara dahilmiş gibi tanımlar yapılabilir.
10.5. Kalıtımda Kurucu ve Yok Edici Yordamlar
Şimdi kurduğumuz sınıf hiyerarşisinde kurucu yordamları yazalım. Üç ayrı sınıfımızın
kurucu yordamlarının yapması gereken işlere baktığımızda taban sınıf olan hesabın
herhangi bir nesnesi oluşturulurken hesapta ilk başta bulunacak para miktarına, bir hesap
numarasına ve hesap açanın ismine ihtiyacımız olduğunu görürüz.
“yerli” sınıfı için gerekli tek parametre hesabın faiz oranıdır. Açılan bir hesap kredi
vermeden bloke olamayacağı için hesap açılırken blokasyon değişken kapalıya
ayarlanacaktır. Öte yandan yerli hesap da bir hesap türü olduğundan hesap için gereken
parametreler yerli sınıfının kurucu metodu için de gerekecektir.
Aynı durum döviz için de geçerlidir. Kurucu metotla birlikte ne tür döviz yatırıldığı ve o
günün döviz kuru ile birlikte hesap sınıfı için gereken parametreler de verilmelidir.
Şimdi bu doğrultuda hazırlanmış kurucu metotları inceleyelim.
public class hesap //hesap.java
{
protected long miktar;
protected int hesap_no;
protected String sahip;
protected boolean bloke;
public hesap(long miktar,int hesap_no,String sahip)
{
bloke=false;
this.miktar=miktar;
this.hesap_no=hesap_no;
this.sahip=new String(sahip);
}
public void para_yatir()
public void para_cek()
public void hesap_bilgisi()
}
public class yerli extends hesap //yerli.java
{
private int faiz_orani;
private long verilen_kredi;
public yerli(long miktar,int hesap_no,String sahip,
int faiz_orani)
{
super( miktar , hesap_no , sahip );
this.faiz_orani=faiz_orani;
}
public void kredi_ver()
public void faiz_islet()
public void hesap_bilgisi()
}
public class doviz extends hesap //doviz.java
{
private double endeks;
private String doviz_turu;
public doviz(long miktar,int hesap_no,String sahip,
double endeks,String doviz_turu)
{
super(miktar,hesap_no,sahip);
this.endeks=endeks;
this.doviz_turu=new String(doviz_turu);
}
public double yerliye_cevir()
public void setEndeks()
public void hesap_bilgisi()
}
Görüldüğü gibi tüm kurucu metodlarda sözü edilen ilk değer atama işlemleri yapılmıştır.
Bunlara ek olarak görülen “super()” metod çağrıları “this()” referansına benzer olarak
taban sınıfı temsil eder ve taban sınıfın kurucu metodunu çağırır. Bu sayede taban sınıfn
kurucusu da taban sınıfın adını bilmeye gerek kalmadan çağırılmış olur. Dikkat edilmesi
gereken nokta super() çağrısının taban sınıfın kurulması için gerekli olduğu ve bu
çağrının alt sınıfın kurucu metodunun ilk satırında yapılmak zorunda olduğudur.
finalize() metodunun alt sınıflar tarafından da kullanılabilmesi için protected
tanımlanması gerekmektedir. finalize() metodu da aynen kurucu metotlar gibi alt
metotlar tarafından gerçkleştirilirken en son olarak super.finalize() şeklinde taban
sınıfının finalize() metodunu çağırmak durumundadır.
10.5.1. Kalıtımda Kurucu ve Yok Edici Yordamlar: Sınıfların
Kullanımı
Şimdi geri kalan metotların da gerekli olanlarının içini doldurup sınıfların kullanımına bir
bakalım.
public class hesap //hesap.java
{
protected long miktar;
protected int hesap_no;
protected String sahip;
protected boolean bloke;
public hesap(long miktar,int hesap_no,String sahip)
{
bloke=false;
this.miktar=miktar;
this.hesap_no=hesap_no;
this.sahip=new String(sahip);
}
public void para_yatir(long ek_miktar)
{if(!bloke) miktar+=ek_miktar;}
public void para_cek(long ek_miktar)
{if(!bloke) miktar-=ek_miktar;}
public void hesap_bilgisi(){}
protected void finalize(){}
}
public class yerli extends hesap //yerli.java
{
private int faiz_orani;
private long verilen_kredi;
public yerli(long miktar,int hesap_no,String sahip,
int faiz_orani)
{
super( miktar , hesap_no , sahip );
this.faiz_orani=faiz_orani;
}
public void kredi_ver()
public void faiz_islet()
public void hesap_bilgisi()
}
public class yerli extends hesap //yerli.java
{
private int faiz_orani;
private long verilen_kredi;
public yerli(long miktar,int hesap_no,String sahip,
int faiz_orani)
{
super( miktar , hesap_no , sahip );
this.faiz_orani=faiz_orani;
}
public void kredi_ver(long miktar)
{
if(verilen_kredi+miktar>10000)
bloke=true;
else verilen_kredi+=miktar;
}
public void faiz_islet()
{miktar=miktar*(faiz_orani+100)/100;}
protected void finalize(){super.finalize();}
}
public class doviz extends hesap //doviz.java
{
private double endeks;
private String doviz_turu;
public doviz(long miktar,int hesap_no,String sahip,
double endeks,String doviz_turu)
{
super(miktar,hesap_no,sahip);
this.endeks=endeks;
this.doviz_turu=new String(doviz_turu);
}
public double yerliye_cevir(){return miktar*endeks;}
public void setEndeks(double endeks){this.endeks=endeks;}
protected void finalize(){super.finalize();}
}
Kodda görülen “döviz_hesabı” ve “yerli_hesap” türünden nesneler üzerinden taban
sınıfı olan “hesap” sınıfının metotlarına ulaşabilmektedir bu da az önce anlatılan ilkelerle
uyumluluk göstermektedir.
10.6. Metod Ezme
Hatırlayacak olursak kalıtımdan söz edilirken türemiş sınıfların, taban sınıfın
davranışlarına sahip olmakla birlikte zaman zaman bu davranışların üzerine ek işlevler
ekleyebileceklerini, zaman zaman da bu davranışları tamamen değiştirip yeni
davranışlar geliştirebilecekleri söylenmişti. Davranış dendiği zaman aklımıza sınıf
metotları geliyor.
Burada anlatılanlar doğrultusunda türeyen sınıf metotlarının taban sınıf metotlarını
kısmen veya tamamıyla yeniden tanımlayabileceklerini anlayabiliyoruz. Bu işlem
nesneye dayalı programlama dahilinde ezme (overriding) olarak adlandırılır. Java
dahil çoğu nesneye dayalı programlama dilinde taban sınıf metotlarına ek işlevler ekleme
dolaylı yoldan gerçekleştirilir. Bu dolaylı yol ezilen metodun içinde taban sınıfın metodunu
çağırmakla gerçekleştirilir. Aynen kurucu sınıflarda “super” referansıyla yapıldığı gibi.
Örnek sınıfımız içinde tanımladığımız hesap bilgisi gösterme metodu üzerinde akıl
yürütelim. Taban sınıfta tanımlanmış hesap bilgisi gösterme metodu erişebildiği özellikler
dahilinde sadece hesabın numarasını , sahibinin ismini ve hesaptaki para miktarını
görüntüleyebilir.
Türetilen sınıflarda gelen ek özelliklere taban sınıf üzerinden erişebilmesi mümkün
değildir. Türeyen sınıflar ise tanımlayacakları bu tarz bir yordam üzerinden taban sınıfa
erişip hesap bilgilerini elde edebilirler. Fakat bu işlem türeyen sınıf sayısı arttıkça kod
tekrarına ve özellikleri hatırlayabilmek için her seferinde yeniden taban sınıfın gözden
geçirilmesine yol açar. Bu olumsuzlukları önleyebilmek için her sınıfın sorumlu olduğu
özellikler üzerinde işlem yapmasını sağlıyor ve işlevler üzerinde kesişen metotları ezerek
sorunu çözüyoruz.
public class hesap //hesap.java
{
protected long miktar;
protected int hesap_no;
protected String sahip;
protected boolean bloke;
public hesap(long miktar,int hesap_no,String sahip)
{
bloke=false;
this.miktar=miktar;
this.hesap_no=hesap_no;
this.sahip=new String(sahip);
}
public void para_yatir(long ek_miktar)
{if(!bloke) miktar+=ek_miktar;}
public void para_cek(long ek_miktar)
{if(!bloke) miktar-=ek_miktar;}
public void hesap_bilgisi()
{
System.out.println("*********************");
System.out.println("Hesap no:"+hesap_no);
System.out.println("Hesap Sahibi:"+sahip);
System.out.print("Para miktari:"+miktar);
}
protected void finalize(){}
}
public class yerli extends hesap //yerli.java
{
private int faiz_orani;
private long verilen_kredi;
public yerli(long miktar,int hesap_no,String sahip,
int faiz_orani)
{
super( miktar , hesap_no , sahip );
this.faiz_orani=faiz_orani;
}
public void kredi_ver(long miktar)
{
if(verilen_kredi+miktar>10000)
bloke=true;
else verilen_kredi+=miktar;
}
public void faiz_islet()
{miktar=miktar*(faiz_orani+100)/100;}
public void hesap_bilgisi()
{
super.hesap_bilgisi();
System.out.println(" YTL");
System.out.println("Verilen kredi miktari:" + verilen_kredi);
System.out.println("*********************");
}
protected void finalize(){super.finalize();}
}
public class doviz extends hesap //doviz.java {
private double endeks;
private String doviz_turu;
public doviz(long miktar,int hesap_no,String sahip,
double endeks,String doviz_turu)
{
super(miktar,hesap_no,sahip);
this.endeks=endeks;
this.doviz_turu=new String(doviz_turu);
}
public double yerliye_cevir(){return miktar*endeks;}
public void setEndeks(double endeks){this.endeks=endeks;}
public void hesap_bilgisi()
{
super.hesap_bilgisi();
System.out.println(" "+doviz_turu);
System.out.println("Yerli para karsiligi:" + yerliye_cevir()+" YTL");
System.out.println("*********************");
}
protected void finalize(){super.finalize();}
}
import java.util.*; //deneme.java
public class deneme {
public static void main(String[] args) {
yerli yerli_hesap=new yerli(15000,15, "sergen yalcin",20);
doviz doviz_hesap=new doviz(3000,20, "umit karan",1.67,"euro");
yerli_hesap.hesap_bilgisi();
doviz_hesap.hesap_bilgisi();
yerli_hesap.para_yatir(5000);
doviz_hesap.para_cek(300);
yerli_hesap.faiz_islet();
yerli_hesap.hesap_bilgisi();
doviz_hesap.hesap_bilgisi();
}
}
10.7. Alt Sınıf – Taban Sınıf Değişimi
Kalıtımdan söz ederken türeyen sınıfların taban sınıflarının tüm özellik ve işlevlerini
destekledikleri için taban sınıfları yerine kullanılabileceğinden bahsedilmişti. Bunun Java’
da gerçekleştirilmesi söz konusudur. Bu amaçla taban sınıf üzerinden oluşturduğumuz
nesnelere türeyen sınıfın nesnelerinin referanslarını atayabiliriz.
Örneğin banka örneğimizde sınırlı miktarda hesap açma kapasitesine sahip olduğumuzu
varsayalım. Varsaydığımız sayıda hesabı kullanıma hazır halde tutmak için bir dizi
değişkeni hazırlayalım. Bu dizi değişkeninde önceden hangi tür hesapların açılıp
tutulacağını bilemeyiz. Bunun için iki hesap türü için ayrı ayrı diziler tutmak bir
çözüm olsa da en kötü senaryoyu göz önünde tutup her iki tür hesap için de açılabilecek
en büyük sayıda hesap kadar yer ayırmamız gerekir. Bu durumda kullanmamız gerekenin
iki katı kadar yer kullanmış oluruz. Oysa ki altsınıf değişimi ile taban sınıfında açtığımız
dizide türeyen sınıfların nesnelerini tutabiliriz.
Gerçekleştirim şu şekilde olacaktır.
import java.util.*; //deneme.java
public class deneme {
public static void main(String[] args) {
yerli yerli_hesap=new yerli(15000,15, "sergen yalcin",20);
doviz doviz_hesap=new doviz(3000,20, "umit karan",1.67,"euro");
hesap hesaplar[]=new hesap[40];
hesaplar[0]=yerli_hesap;
hesaplar[1]=doviz_hesap;
hesaplar[2]=new yerli(1000,25,"erman toroglu",15);
hesaplar[2].hesap_bilgisi();
}
}
Görüldüğü gibi taban sınıfın referans değişkeni üzerinden alt sınıfların metotlarına
ulaşabiliyoruz. Bu şekilde kullanımlarda dikkatli olunması gereklidir çünkü alt sınıfta
tanımlanan bir metodun taban sınıf üzerinden çağırılabilmesi için metodun tanımının
taban sınıfta da yapılmış olması gerekir. Bu özellik bizi soyut sınıflara ve çok biçimlilik
kavramına götürecektir.
10.8. Kalıtımın Yararları, Kalıtım-Bileşim Karşılaştırması
Kalıtım kullanmanın avantajları ve dezavantajlarına kısaca göz atalım.
Kalıtımın avantajları arasında:
Kalıtımın sebep olduğu en önemli dezavantajlar ise:
Kalıtımın benzer olarak görülebileceği bir başka sınıflar arası ilişki türü bileşimdir.
Nesne bileşimini daha önce incelemiş ve “sahip olma” ilişkisi olarak özetlenebileceğini
söylemiştik. Kalıtım ise sahip olma ilişkisinden ziyade bir “türü olma” ilişkisidir. Örneğin
“yerli hesap bir hesap türüdür” veya “bitki bir canlı türüdür” gibi. Bu iki ilişki
birbirine rakip olmamakla birlikte bir arada kullanıldığı da sıkça görülür.
10.9. Çoklu Kalıtım ve Arayüzler
Çoklu kalıtım doğal açıdan kalıtımın bir getirisi gibi gözükse de nesneye dayalı
programlamada kullanıldığı zamanlar bazı sakıncaları görülmektedir. Bu yüzden de Java’
da çoklu kalıtıma izin verilmez. Çoklu kalıtıma izin verilen tek yol arayüz denilen özel
yapılar üzerinden çoklu kalıtım yapılmasıdır. Bu noktada arayüz kavramına ve bunun
Java’ da gerçekleştirilme şekline bakalım.
Arayüzler bilgi gizleme amacını gerçekleştirmeye yönelik, bir sınıfın davranışlarını
özelliklerinden ayıran yapılardır. Bu yapılar sınıfların yeniden kullanımında kullanıcıya
bilmesi gereken sınıf öğelerini açan bir pencere gibidir. Arayüzlere sınıfların belirli işlevleri
dahil edilerek sınıfın kullanımı bu işlevlerle sınırlandırılmış olur. Ayrıca arayüzler, temsil
ettikleri sınıflardan önce yazılarak yazılımın modellenmesi sürecinde etkin
soyutlamalara yol açarlar.
Java’da bir arayüz şu şekilde oluşturulur.
interface hesap_arayuz
{
void para_yatir(long ek_miktar)
void para_cek(long ek_miktar)
void hesap_bilgisi()
}
Arayüz tanımında adı geçen tüm metotlar arayüz üzerinden gerçekleştirildiklerinde public
olarak gerçekleştirilir. Arayüzü gerçekleştiren sınıflar ise şu söz dizim ile gerçekleştirirler.
Class hesap implements hesap_arayuz
{
/*......*/
}
Hesap sınıfının tanımı içerisinde arayüzdeki metotların dışında istenildiği kadar metot
tanmlanabilir ve özellik kullanılabilir.
İşte bu şekilde hazırlanan arayüzler üzerinden bir sınıf çoklu kalıtımı
gerçekleştirebilir. Bu durumda yapılması gereken tek şey gerçekleştirilen arayüz
isimlerinin virgülle ayrılmasıdır.
Örneğin:
class cocuk implements anne,baba
{
/*......*/
}
Arayüz sınıfları normal sınıflar gibi kalıtım hiyerarşisi oluşturabilir. Üstelik çoklu kalıtım
arayüz kalıtım hiyerarşilerinde de serbest bırakılmıştır.
interface arastirmaGorevlisi extends ogrenci,ogretimUyesi
{
/*......*/
}
BÖLÜM-11ÇOK BİÇİMLİLİK
11.1. Çok Biçimliliğin Genel Özellikleri
Çok biçimlilik kavramının ingilizce karşılığı olan polymorphic yunan kökenli bir kelimedir
ve poly=çok ve morphos=biçim kelimelerinin birleşmesinden oluşmuştur. Morphos
kelimesi mitolojik yunan tanrısı Morphus’ tan gelmektedir.
Morphus’ un özelliği insanların karşısına istediği maddenin şeklini alarak çıkabilmesidir,
bu açıdan kendisi tam anlamıyla çok biçimlidir. Kimyada çok biçimli bileşikler en az iki
farklı biçimde kristalleşebilenlerdir. Örneğin grafit ve elmas şeklinde kristalleşebilen
karbon gibi.
Nesneye dayalı programlama gerçek dünyayı modellediğinden ötürü çok biçimlilik
kavramlarına yukarıdaki örneklere benzer şekilde gerçek dünya nesnelerinde de rastlarız.
Çok biçimlilik belirli bir miktar nesneye aynı iletiyi gönderdiğimiz zaman nesnelerin bu
iletiyi kendi yorumları doğrultusunda farklı farklı gerçekleştirecekleridir.
Çok biçimlilik tanım itibariyle çoklu kullanıma benzese de ondan farklı ve çok daha
işlevsel bir kavramdır. Hatta çoklu kullanım ve metodların ezilmesi çok biçimlilik
kavramları arasında bir alt küme olarak görülebilmektedir.
Çoklu kullanım ile çok biçimlilik farkını çoklu kullanımda verilen örnek üzerinden
inceleyelim. Hatırlarsak çoklu kullanımda “köpeği yıka” ve “gömleği yıka” gibi
işlemlerde yıkama fiilinin aynı olduğu fakat yıkama tarzının değiştiğine dikkat çekmiştik.
Çok biçimlilik bu kavramın nesne tabanlı bir bakış açısıyla ele alınmasıdır.
Örneğin iki nesne olarak annemizi ve çamaşır makinesini ele alalım. İki nesneye de
“gömleği yıka” iletisini ulaştırdığımızda ileti aynı olduğu halde yapılan işler nesne
merkezlidir. Çamaşır makinesi bir takım elektromekanik işlemler sonucu çeşitleri
adımları otomatik olarak tekrarlayarak gömleği yıkarken anne nesnesi bir leğen deterjan
ve sıcak su alarak tamamen rastgele bir şekilde gömleği bir süre çitiler ve kendine has bir
yöntemle yıkama işlemini gerçekleştirir.
Çoklu kullanımda parametre merkezli bir işlem söz konusuyken çok biçimlilikte ileti
merkezli bir işlemin söz konusu olacağını gözden kaçırmamak gerekir. Bu noktada ufak
bir ayrıntı da anne nesnesinin gömleği yıka iletisini belli bir süre beklettikten sonra diğer
yıkama iletileriyle birlikte direkt olarak “çamaşır makinesi” nesnesine iletebileceğidir.
Burada nesne bileşimi durumu söz konusudur.
Çoklu kullanım ve çok biçimlilik arasındaki diğer bir önemli fark ise çoklu kullanım
derleyici zamanında gerçekleştirilirken çok biçimliliğin çalışma zamanı işlemleriyle
gerçekleştirilmesidir.
11.2. Alt Sınıf – Taban Sınıf Değişimine Yeniden Bakış
Çok biçimliliğin en doğal karşımıza çıktığı yer kalıtımdır. Kalıtım esnasında incelediğimiz
türetilen sınıfların nesnelerinin taban sınıfları yerine kullanılabileceği olgusu bir çok
biçimlilik ilkesidir.
Bu amaçla gördüğümüz programı yeniden inceleyelim.
import java.util.*; //deneme.java
public class deneme {
public static void main(String[] args) {
hesap hesaplar[]=new hesap[40];
hesaplar[0]=yerli_hesap;
hesaplar[1]=doviz_hesap;
hesaplar[2]=new yerli(1000,25,"erman toroglu",15);
hesaplar[2].hesap_bilgisi();
}
}
Bu noktada gördüğümüz üzere hesap_bilgisi() yordamı taban sınıf üzerinden
oluşturulan nesneler ile çalıştırıldığında çağırıldığı nesnenin gerçekleştirdiği davranışı
göstermektedir. Bu noktada görüldüğü gibi “hesap” sınıfı değişkeni üzerinden çağırılan
bir metod çalışma zamanında kendisine “new” ile bağlanmış sınıfın yordamını
işletmektedir. Bu işlem programlama dillerinde geç bağlanma “late binding” olarak
isimlendirilir. Geç bağlanma sayesinde çok biçimliliğin gerçekleştirilmesi sağlanır.
Şimdi sınıflarımıza bir özellik ekleyelim. Örneğin kredi verme işlevini döviz hesapları için
de geçerli kıldığımızı var sayalım. Fakat döviz hesabından kredi verirken bu sefer sabit bir
değeri geçince bloke olma işlemi farklı şekilde yorumlanacaktır. Her döviz için farklı bir
değer tutmaktansa her değeri kur üzerinden yerli paraya çevirip kontrolü bu değer
üzerinden yapmak daha mantıklı olacaktır. Bu amaçla bu işlevi yerine getiren yordamı
döviz sınıfımıza ekleyelim.
private long verilen_kredi;
public void kredi_ver(long miktar)
{
if(verilen_kredi+(miktar*endeks)>5000)
bloke=true;
else verilen_kredi+=(long)(miktar*endeks);
}
“deneme” sınıfında bu yeni metodu taban sınıf dizimiz üzerinden çalıştırmayı deneyelim.
Bu durumda bir derleyici hatası almamız gerekmektedir. Taban sınıfın yani “hesap” ın bir
metodunu “kredi_ver()” ile çağırmamıza rağmen “hesap” ın böyle bir yordamı
bulunmamaktadır. Bu nedenle bu metod çağrısına derleme zamanında hata almaktayız.
Hatırlayacağımız üzere çalışma zamanında “kredi_ver()” metodunun bağlanma işlemi
yani yapılan yordam çağrısı ile yordamın hangi yordam olduğuna karar verilme işlemi
çalışma zamanında yapıldığından metot çağrısı diziye aktarılan nesne üzerinden yapılır.
Fakat derleme esnasında çağrılan metotlar kontrol edildiğinden “hesap” sınıfına ait
olmayan “kredi_ver()” metoduna derleyici hata verecektir. Bunu önlemek amacıyla
taban sınıfa boş bir “kredi_ver()” metodu eklememiz yeterli olacaktır.
public void kredi_ver(long miktar){}
Şimdi eklediğimiz metodun çalışıp çalışmadığına bir bakalım.
import java.util.*; //deneme.java
public class deneme {
public static void main(String[] args) {
yerli yerli_hesap=new yerli(15000,15, "sergen yalcin",20);
doviz doviz_hesap=new doviz(3000,20, "umit karan",1.67,"euro");
hesap hesaplar[]=new hesap[40];
hesaplar[0]=yerli_hesap;
hesaplar[1]=doviz_hesap;
hesaplar[2]=new yerli(1000,25,"erman toroglu",15);
hesaplar[2].kredi_ver(200);
hesaplar[2].hesap_bilgisi();
}
}
11.3. Soyut Metodlar ve Sınıflar
Nesneye dayalı programlamanın en önemli özelliği programcılara hislerin kullanma
özelliğini vermesidir. “hesap” satırına son eklediğimiz metot eminim birçoğumuza
anlamsız içi boş bir metot gibi gelecektir. Bu tarz aklımızı kurcalayan ve bize neden
olduğunu bilmeden yanlış gelen tasarımlar konusunda daha çok kafa yormakta yarar var
çünkü şu an neden olduğunu bilmesek bile ileride bunu öğrenme olanağımız mutlaka
olacaktır.
Örneğin bu hiyerarşide herhangi bir sebepten dolayı bir şekilde yukarıda eklediğimiz
“hesap” sınıfına ait kredi_ver() yordamını doldurmaya çalışacak olursak uzun süredir
anlattığımız kavramların çoğunu çöpe atmış olacağız. Çünkü bütün yordamları hazır olan
ve normal bir sınıf gibi kullanılabilen bir taban sınıf zaman zaman bizim istemediğimiz bir
özellik olacaktır.
Örneğin yerli para mı döviz mi içerdiği belli olmayan ve sadece para yatırılıp çekilebilen
bir hesap oluşturmak hiçbir anlam ifade etmez. Bu nedenle de taban sınıfımıza ait
nesnelerin oluşturulmasını bir şekilde engellemek durumundayız. Aynen “hesap”
sınıfındaki “kredi_ver()” yordamının bir gövdeye sahip olmasını engellememiz gerektiği
gibi.
Bu tarz bir kullanımı sağlamak için Java tarafından programcıya “abstract” anahtar
kelimesi sağlanmıştır. “abstract” kullanımı ile tam anlamıyla işlevsel bir gövdesi
olmayan metod ve sınıflar yaratıp kullanabiliriz. Bu tür kullanıma soyut sınıf/metod
kullanımı denir.
public abstract class hesap //hesap.java
{
protected long miktar;
protected int hesap_no;
protected String sahip;
protected boolean bloke;
protected final int banka_no=81;
public hesap(long miktar,int hesap_no,String sahip)
{
bloke=false;
this.miktar=miktar;
this.hesap_no=hesap_no;
this.sahip=new String(sahip);
}
abstract void kredi_ver(long miktar);
public void para_yatir(long ek_miktar)
{if(!bloke) miktar+=ek_miktar;}
public void para_cek(long ek_miktar)
{if(!bloke) miktar-=ek_miktar;}
public void hesap_bilgisi()
{
System.out.println("*********************");
System.out.println("Hesap no:"+hesap_no);
System.out.println("Hesap Sahibi:"+sahip);
System.out.print("Para miktari:"+miktar);
}
protected void finalize(){}
}
public final class yerli extends hesap //yerli.java
{
private int faiz_orani;
private long verilen_kredi;
public yerli(long miktar,int hesap_no,String sahip,
int faiz_orani)
{
super( miktar , hesap_no , sahip );
this.faiz_orani=faiz_orani;
}
public void kredi_ver(long miktar)
{
if(verilen_kredi+miktar>10000)
bloke=true;
else verilen_kredi+=miktar;
}
public void faiz_islet()
{miktar=miktar*(faiz_orani+100)/100;}
public void hesap_bilgisi()
{
super.hesap_bilgisi();
System.out.println(" YTL");
System.out.println("Verilen kredi miktari:" + verilen_kredi);
System.out.println("*********************");
}
protected void finalize(){super.finalize();}}
public void hesap_bilgisi()
{
vsuper.hesap_bilgisi();
vSystem.out.println(" "+doviz_turu);
vSystem.out.println("Yerli para karsiligi:"
+ yerliye_cevir()+" YTL");
System.out.println("*********************");
}
protected void finalize(){super.finalize();}
}
Örnekte görüldüğü gibi artık kredi_ver() metodunun bir gövdesi yok ve rahatça alt
sınıflar tarafından ezilebiliyor. Aynı zamanda artık “hesap” sınıfımız da soyut yani artık
bu sınıftan nesne oluşturulamıyor.
Unutmamak gerekir ki içinde “abstract” metodlar barındıran sınıflar da “abstract”
tanımlanmak durumundadır fakat tersi geçerli değildir. “abstract” takısını kulanmak
çoğu zaman gereklidir çünkü programı okuyanlara ve derleyiciye sınıfın ne niyetle
tasarlandığını anlatmanızı sağlar. Şimdi oluşturduğumuz bütün hiyerarşiyi bir deneme
sınıfı ile deneyelim.
11.4. Değiştirilemez Metodlar ve Sınıflar
Son olarak programcının niyetini programa yansıtması endişesinin bir başka yansıması
final metodlar ve sınıflardır. Adından anlaşılacağı gibi final olarak tanımlanan metot ve
sınıflar hiyerarşide son basamağı temsil ederler.
Final olarak tanımlanmış sınıfların bir daha taban sınıfı olarak kullanılması yani sınıflardan
yeni sınıflar türetilmesi derleyici tarafından engellenirken final değişkenlerin atandıkları ilk
değerden itibaren değer değiştirilmeleri engelleniştir. Böylece örneğin “döviz”
sınıfımızdan yeni sınıflar türetilmesini engellemek istiyorsak tanımını aşağıdaki şekilde
yapmalıyız.
public final class doviz extends hesap
{
/* ..... */
}
Örneğin her hesap numarasının başına eklenecek bir de sabit banka kodumuzun
olduğunu düşünelim. Bu banka kodu sabittir ve hesap numarasından bağımsız olarak her
hesap numarasının başına eklenmek durumundadır. Bu nedenle çalışma zamanında
değişmeyecek sabit bir değer yaratmak istiyoruz. Bu durumda final bir değişken
kullanabiliriz.
protected final int banka_no=81;
Bu değişkenin tanımlandığı andan itibaren değiştirilmesi söz konusu değildir ve sabit
değer olarak kullanılabilir.
Metodlar için final takısı kullanıldığında ise ortaya ezilemeyen metodlar çıkarmış oluruz ki
bu da ileride göreceğimiz bazı konular açısından gereklidir.
11.5. Kurucu Yordamlar ve Çok Biçimlilik
Kalıtım ve birbiriyle ilişkili sınıfların oluşturulması söz konusu olduğu zaman bu sınıflar ve
sınıflara ait nesnelerin kurucularının çağırılma sırası da önem kazanıyor. Dikkatli
okuyucular bu sırayı az çok kafalarında oluşturmuşlarsa da çağrılma sırasını bir kez daha
gözden geçirmekte yarar var.
11.5.1. Kurucu Yordamlar ve Çok Biçimlilik (Devamı)
Şimdi işi biraz daha karıştıralım. Çok biçimlilik ve soyut sınıf kavramlarını da kurucu
metotlarla ilişkilendirecek şekilde sınıf hiyerarşisini değiştirelim.
import java.util.*; //deneme.java
abstract class a
{
a() {System.out.println("kurucu a");f();}
abstract void f();
}
class b extends a
{
b() {System.out.println("kurucu b");deger=1;}
private int deger;
void f(){System.out.println(deger);}
}
class c extends a
{
c() {System.out.println("kurucu c");}
void f(){};
}
class d extends c
{
d(b pb) {System.out.println("kurucu d");}
}
public class deneme {
public static void main(String[] args) {
//a n_a = new a();
b n_b = new b();
c n_c = new c();
d n_d = new d(n_b);
}
}
Bu şekilde programı çalıştırdığımızda aşağıdaki gibi bir sonuç alırız
hatayı yapanlar için beklenmedik bir sonuçtur. Zira b sınıfının kurucusu içinde 1 ilk değeri
verilen değişken a’nın kurucusu içinden yazdırıldığında 0 değerini almaktadır. Bunun
nedeni doğal olarka a sınıfının hiyerarşik olarak b’den önce kurulmasıdır. Bu tarz hatalar
derleyici tarafından fark edilmeyen ve ayıklanması zor olanlardır. Bu yüzden mümkün
olduğu kadar kurucu metodların içinden başka metodların çağırılmaması tavsiye edilir.
Tabi bu metodlar final olarak yazılmışsa yani hiyerarşi dışı hareket etme kabiliyeti
olmayan yordamlarsa çağırılmalarında sorun yoktur.
Dikkatimizi çeken son nokta ilk değer atanmayan değişkein 0 değerini almasıdır. Bu bir
tesadüf değildir zira Java’da nesneler için yer bellekten yer ayrılır ayrılmaz tüm öğeleri
0’a kurulur. Bu doğrultuda bir nesnenin oluşması esnasındaki kurulma işlemleri şu 4
adımı içerir.
Bu noktada kurucu metodların ve sınıf oluşumunu da ayrıntılı olarak incelemiş olduk.
BÖLÜM-12OLAY YÖNETİMİ
12.1. “Olay” Nedir?
Olaylar genel programcılık ilkeleridir ve genellikle kulllanıcı arayüzleri ile uğraşmaya
başladığımıza karşımıza çıkarlar. Genel anlamda “olay”, bir programın öngörüsü
haricinde çevresinden gelen ve programı etkilemesi gereken iletilerdir. Bu iletiler insanmakine iletişimi dahilinde gerçekleşebileceği gibi dış kaynaklar tarafından da
oluşturulabilir.
Sıkça karşılaşılan kullanıcı arayüzü kesiminden örnek vermek gerekirse, örneğin bir
düğmeye tıkladığınızda veya bir menü seçtiğinizde programınızın kullanıcıya vereceği
tepkiyi tetikleyen mekanizma olay mekanizmasıdır. Bu mekanzima Java dahilinde
java.awt.event sınıfı dahilinde ele alınmıştır. Bir kullanıcı arayüzü olayı AWTEvent
sınıfından türetilmiş nesneler içerisinde saklanır.
12.1.1. Java’ da Olay
Programlarımızda olayları ele almamız için iki şey yapmalıyız. Önce bir olay dinleyicisine
kayıt olmak ikincisi de bir olay ele alıcı yazmak. Olay dinleyicisi belirli bir olayın olmasını
sürekli ekler ve olay gerçekleştiğinde tanımlanmış olay ele alıcı metodunu çağırır. Olay
dinleyicileri Java’ da java.awt.event sınıfının EventLitener arayüzü üzerinden
gerçekleştirilmektedir.
Arayüz tanımından hatırladığımız üzere içi boş olan çeşitli metodlar içeren dinleyici sınıfını
gerçekleştiren Java sınıfları, o dinleyici sınıf dahilindeki tüm metodların, yani dinleyici
sınıflar açısndan olay ele alıcı metodları ezmek durumundadır. Olay yönetiminde olay
dinleyicilerin kullanılması vekil ile olay yönetimi (delegation event model) ile
adlandırılır. Java.awt.Event arayüzlerinin bir çizeneği aşağıda görebilir. Koyu renkli
elemanlar arayüzleri temsil etmektedir.
Bu bölüm dahilinde olay yönetimini açıklamak ve örneklemek amacıyla tuş takımı ve
fare olay yönetimi örnekleri incelenecektir.
12.2. Uygulamacıklar
Uyulamacıklar Java’nın bilgisayar kullanıcıları tarafından en çok bilinen parçalarıdır. Bir
uygulamacık , İngilizce adıyla “applet” ağ tarayıcısına kendisini içeren bir HTML sayfası
yüklendiğinde çalışan kısıtlı programlardır. Kısıtlı denmesinin sebebi Internet üzerinden
çalıştırılabilir özellikleri olması nedeniyle normal Java uygulamalarının işlevselliğine
ulaşamamalarıdır.
Uygulamacık olarak düzenlenen programlar Java’ da önceden “java.applet” paketinde
tanımlanmış Applet sınıfını türetmek durumundadırlar. Bu sayede yeni yazılan sınıflar bir
HTML sayfasının içine gömülerek kullanılabilir.
Örneğin aşağıdaki basit sınıfta “Applet” sınıfından türeyen basir sınıf içerisinde basit bir
merhaba ietisi yazdırılmaktadır.
import java.applet.Applet; //Merhaba.java
import java.awt.Graphics;
public class Merhaba extends Applet
{
public void paint(Graphics g)
{
g.drawString(“Uygulamacik Ornegi”,25,25);
}
}
Bu örnekte görüldüğü üzere uygulamacıklarda pencere içerisi bir grafik nesnesi gibi
görülmekte ve grafik nesnesinin katar yazdırma yordamı olan drawString() ile bir katar
uygulamacık penceresinin içine yazdırılmaktadır.
Bu uygulamacığı görüntüleyebilmek için bir de deneme.html isimli HTML belgesi
oluşturmamaız gerekiyor. Bu belge en basit hali ile aağıddaki gibi olabilir.
Dikkat edileceği üzere uygulamacığın .class uzantılı sınıf dosyası HTML belgesinin içerisine
“APPLET” etiketi ile yerleştirilmiştir. Bu noktada belgeyi bir ağ tarayıcısı aracılığıyla
görüntülemek yada aşağıdaki komutu komut yorumlayıcısında çalıştırmak yeterlidir.
Gerekli işlem yapıldığında oluşacak çıktı şu şekildedir.
appletviewer deneme.html
Örnek Program Çıktısı
Uygulamacık sınıfları “Applet” sınıfından türetildikleri için, kodlanmaları esnasında bazı
öntanımlı metodları ezerek uygulamacıklardan beklenen işlevleri yerine getirebilirler.
Örneğin paint() isimli metodun içi örneğimizde istediğimiz katarı bastırmak için
ezilmiştir. paint() metodu uygulamacık penceresinin grafik sınıfı tarafından yeniden
ekrana çizdirilmesi esnasında çağırılır, bu yüzden de kalıcı görsel öğelerin bu metodun
ezilmesi ile koda gömülmesi yerinde olacaktır.
Buna benzer bir başka metod da init() metodudur. Bu metod ise uygulamacık hazır hale
getirilip ekrana bastırılmadan önce çağırılır. İsminden de anlaşılacağı üzere
uygulamacıkta kullanılacak öğelerin ilk değerlerinin verilmesi ve uygulamacık öncesi
ayarlamalarının yapılması amacıyla ezilir.
Uygulamacık konusu burada değinilenden daha derin ve yüklü bir konudur ve ileride daha
ayrıntılı olarak değinilecektir. Bu noktada kısa bir bilgilendirme yapılmasının amacı olay
yönetimindeki görsel öğe ihtiyacını karşılayacak alt yapının oluşturulmsıdır. Bölüm
dahilinde olay yönetimi incelenirken yukaıda değinilen konuların bilinmesi yeterli
olacaktır.
12.3. Fare Olay Yönetimi
Java’da fare olaylarını izlemek için MouseListener ve MouseMotionListener adıyla iki adet
arayüz bulunmaktadır. Bu arayüzlerin metodları ezilerek fare olayları izlenebilir. Örnek
üzerinden bu arayüzün metodlarını inceleyelim.
import java.applet.Applet; //Fareci.java
import java.awt.*;
import java.awt.event.*;
public class Fareci extends Applet implements MouseListener ,
MouseMotionListener
{
private int xKor,yKor=-10;
private String katar="";
public void init()
{
addMouseListener(this);
addMouseMotionListener(this);
}
public void paint(Graphics g)
{
g.drawString(katar + "@ [" + xKor + ", " + yKor + "]", xKor ,
yKor);
}
private void setValues(String olay, int x, int y)
{
katar=olay;
xKor=x;
yKor=y;
repaint();
}
public void mouseClicked(MouseEvent olay)
{
setValues("Tiklandi",olay.getX(),olay.getY());
}
public void mousePressed(MouseEvent olay)
{
setValues("Fare tusuna basildi",olay.getX(), olay.getY());
}
public void mouseReleased(MouseEvent olay)
{
setValues("Fare tusu birakildi",olay.getX(), olay.getY());
}
public void mouseEntered(MouseEvent olay)
{ showStatus("Fare applet alaninda"); }
public void mouseExited(MouseEvent olay)
{
showStatus("Fare applet alaninin disinda");
}
public void mouseDragged(MouseEvent olay)
{
setValues("Surukleniyor",olay.getX(),olay.getY());
}
public void mouseMoved(MouseEvent olay)
{
setValues("Hareket halinde",olay.getX(),olay.getY());
}
}
Fare olayları yalnızca Component sınıfından türetilen GUI sınıfları üzerine uygulanabilir.
Bu nedenle bu örneğimiz bir uygulamacık kodu şeklindedir.
Kodda ilk göze çarpan iki yordam addMouseListener() ve addMotionListener()
metotlarıdır. Bu iki yordam ile uygulamacığın hazırlanması aşamasında bu metotlara
parametre olarak kendini referans gönderilerek uygulamacığın kendi üzerindeki fare
olaylarını dinlemesini sağlamış bulunuyoruz.
Örnek program çıktısı
12.3.1. Fare Olay Yönetimi (Dinleyici Arayüzün Metotları)
Bunun dışında gerçekleştirilen olay dinleyici arayüzün metotları sırasıyla şunlardır.
Bu metodtarın ortak bir özelliği de hepsinin bir MouseEvent nesnesini parametre olarak
almasıdır. Bu nesne içerisinde olayın olduğu yerin x ve y koordinatlarını da içinde
barındıran bilgiler tutmaktadır. Bu bilgilere de program dahilinde getX() ve getY()
metodlarıyla ulaşılabilir.
Farenin hangi düğmesine basıldığını anlamak ise fare olay dinleyicisi olan MouseListener’
ın taban sınıfı olan ItemListener sınıfındaki yordam ve sabitler kullanılarak anlaşılabilir. Bu
sınıfa dahil isMetaDown() yordamı sağ düğme ve isAltDown() yordamı 3. düğme
tıklanmışsa ‘true’ değerini döndürerek hangi fare düğmesinin tıklandığına dair fikir verir.
Örneğin tek bir fare hareketini ele alacak olalım. Bu durumda bile tüm arayüz
metotlarının en azından tanımlarını sınıf tanımımıza dahil etmemiz sinir bozucu olabilir.
Bu sorunu aşmak için arayüz yerine gerçek sınıflar olan uyarlayıcı (adapter) sınıflar
Java’da mevcuttur. Bu sınıflardan extends takısı ile yeni sınıflar üretilebilir ve tek bir olay
gerçekleştirilebilir. Bu uyarlayıcı sınıflar ve gerçekledikleri arayüzler şunlardır:
Bu durumda ortaya ufak bir sorun çıkmaktadır. O da Applet sınıfından türeyen ana
sınıfımızın yeni uyarlayıcı sınıftan da türeyememe sorunudur. Bunun nedeni Java’ da
çoklu kalıtımın desteklenmemesidir. Bunu aşmak için olayları dinleyecek sınıf ana sınıftan
ayrı olarak yazılmalı ve ana sınıftaki olay dinleyici ekleme yordamına parametre olarak bu
ayrılan sınıf gönderilmelidir.
Yani sınıf tanımlarımız kayıt olunurken yaratılan yeni uyarlayıcı sınıfın kurucu metoduna
gönderilen ana sınıf nesnesinden kestirilebilir.
public class Fareci extends Applet
{
public void init()
{
addMouseMotionListener( new HareketAlgilayici(this));
}
/*
......Diğer kodlar.......
*/
}
class HareketAlgilayici extends MouseMotionAdapter
{
/*
......Diğer kodlar.......
*/
}
12.4. Tuş Takımı Olay Yönetimi
Java’ da tuş takımı olaylarını izlemek için KeyListener adıyla iki adet arayüz
bulunmaktadır. Bu arayüzün metodları ezilerek klavye olayları izlenebilir. Örnek
üzerinden bu arayüzün metodlarını inceleyelim
import java.applet.Applet; //Tuscu.java
import java.awt.*;
import java.awt.event.*;
public class Tuscu extends Applet implements KeyListener
{
private String satir1="";
private String satir2="";
private String satir3="";
public void init()
{
addKeyListener(this);
requestFocus();
}
public void paint(Graphics g)
{
g.drawString(satir1,25,25);
g.drawString(satir2,25,40);
g.drawString(satir3,25,55);
}
public void keyPressed(KeyEvent olay)
{
satir1 = "Tusa basildi: " +
olay.getKeyText( olay.getKeyCode() );
satir2ve3(olay);
}
public void keyReleased(KeyEvent olay)
{
satir1 = "Tus birakildi: " +
olay.getKeyText( olay.getKeyCode() );
satir2ve3(olay);
}
public void keyTyped(KeyEvent olay)
{
satir1 = "Tus yazildi: " + olay.getKeyChar();
satir2ve3(olay);
}
private void satir2ve3(KeyEvent olay)
{
satir2 = "Bu tus bir fonksiyon tusu" +
(olay.isActionKey() ? "dur!" : "degildir!");
String gecici=olay.getKeyModifiersText(
olay.getModifiers());
satir3 = "Olay tusu basi: " +
(gecici.equals("")?"yok":gecici);
repaint();
}
}
Aynı fare olaylarında olduğu gibi tuş takımı olaylarında da uygulamacığın hazırlanma
yordamı olan init() içerisinde addKeyListener() komutu ile ana sınıfın kendisini kendi
içinde geçen tuş takımı olaylarını dinlemesi için ayarlıyoruz.
Örnek program çıktısı
12.4.1. Tuş Takımı Olay Yönetimi (Dinleyici Arayüzün
Metotları)
Bunun dışında gerçekleştirilen olay dinleyici arayüzün metotları sırasıyla şunlardır.
Tuş takımı olayları için de bir KeyEvent isminde bir sınıf türü ayrılmıştır. Bu sınıfın
nesneleri içerisinde basılan tuşun ismini ve karakter kodunu almaya yarayan metotlar
olduğu gibi basılan tuşun bir olay, fonksiyon (F1,F2,vs.) yada bir karakter tuşu mu
olduğunu bildirmeye yarayan metotlar da bulunur. Bu bilgilere de program dahilinde
getKeyText(), getKeyCode(), isActionKey() ve getKeyModifiersText() metotlarıyla
ulaşılabilir. satir2ve3() yordamında bu metotlardan birkaçının kullanımına örnek
görülebilir.
Tuş takımının bazı özel tuşlarına basıldığını anlamak için tuş takımının olay dinleyicisi olan
KeyListener’ ın taban sınıfı olan ItemListener sınıfındaki yordam ve sabitler
kullanılarak anlaşılabilir. Bu sınıfa dahil isAltDown(), isControlDown() ve
isAltDown() yordamları isimleri içinde geçen tuşa basılmışsa ‘true’ değerini döndürerek
hangi tuşa basıldığına dair fikir verir.
Download