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.