Bölüm 10 Statik ve Anlık Öğeler Statik ve Anlık Öğeler Statik öğeler, bir sınıfta static sıfatıyla nitelendirilen değişkenler ve metotlardır. Bunlara sınıf değişkenleri de denilir. static nitelemesi almayan değişkenler ve metotlar ise anlık (dinamik, statik olmayan) öğelerdir. Örneğin, static int x; static double ÜcretHesapla(){ ... }; deyimleri, sırasıyla, int tipinden static ÜcretHesapla() metodunun bildirimidir. x değişkeni ile double değer alan static Bir sınıfın static öğelerine, o sınıfa ait bir nesne yaratılmadan erişilebilir. Sınıfın herhangi bir nesnesiyle bağlantısı olsun istenmeyen öğelere static nitelemesini vermek ona her yerden erişilebilen bir globallik niteliği verir. Daha önce, Java dilinde global değişken ve global metot olmadığını söylemiş, public nitelemesiyle öğelere bir tür global işlevsellik kazandırılabildiğini belirtmiştik. Öğelere globallik kazandırmanın ikinci etkin bir yolu static nitelemesidir. Statik öğeler, sınıfın bir nesnesi içinde olmadığından, onlara programın her yerinden erişilebilir. Örneğin, main()metodu daima static nitelemesini alır. O nedenle, ait olduğu sınıfın bir nesnesi yaratılmadan doğrudan çalışabilir. Sınıfa ait bir ya da daha çok nesne yaratıldığında, bu nesneler içinde yapılan işler static öğeleri etkilemez. Bunun nedeni, statik bir öğeye, o sınıfa ait bir nesne yaratılmadan ana bellekte bir ve yalnız bir tane bellek adresi ayrılmasıdır. Nesnelerin yaratılıp yokedilmesi, ya da nesneler içindeki değişimler onları etkilemez. Dolayısıyla, örneğin, static bir değişkenin belli bir anda yalnız bir tane değeri olabilir. Daha önemlisi, ana bellekte nesneye bağlı olmadan birer adresleri olduğu için, static öğelere nesne yaratılmadan erişilebilir. Statik olmayan öğelere anlık (dinamik) öğeler diyeceğiz. Öndeğer (default) olarak, bildirim sırasında static nitelemesini almayan her öğe anlık’ tır. Bunlara dinamik denmesinin nedeni, program boyunca ana bellekte kalmazlar, ait oldukları nesne ile birlikte yaratılır ve onunla birlikte ana bellekten silinirler. Örneğin, long y; Bölüm 06: Operatörler 1 float Topla(a,b){ return a+b}; deyimleri, sırasıyla, long tipinden anlık x değişkeni ile float değer alan anlık Topla()metodunun bildirimidir. Bir sınıfın anlık öğelerine, o sınıfın yaratılan her bir nesnesi için, ana bellekte birer adres ayrılır. Başka bir deyişle, bir sınıfın üç ayrı nesnesi yaratılmışsa, sınıftaki anlık bir öğeye her nesnede bir tane olmak üzere üç ayrı bellek adresi ayrılır. Dolayısıyla, anlık değişkenin yaratılan her nesnede ayrı bir değeri vardır. Ama, onların var olabilmeleri için, önce sınıfa ait nesnenin yaratılması gerekir. Neden Nesne Yönelimli Programlama? Bu soruya yanıt vermeden önce aşağıdaki bir dizi programı inceleyeceğiz. Đncelemeyi bitirince, sorunun yanıtı kendiliğinden ortaya çıkacaktır. Örnek programların hepsi aynı işi yapmaktadırlar: Konsoldan brüt geliri okur, hesapladığı gelir vergisini konsola yazar. Đlk program, sözkonusu işi yapan en basit, en kısa ve en kolay programdır. Ama, yapısal programlama açısından asla tercih edilemeyecek kadar kötü bir programdır. Vergi01.java package statik; import java.util.Scanner; public class Vergi01 { public static void main(String[] args) { double brüt; Scanner in = new Scanner(System.in); System.out.println("Brüt Geliri giriniz :"); brüt = in.nextDouble(); in.close(); System.out.println("Gelir Vergisi :"); System.out.println(brüt * 40 / 100); } } Çıktı Brüt Geliri giriniz : 12345,678 Gelir Vergisi : 4938.2712 Şimdi programımıza konsoldan veri okuyan, hesap yapan ve konsola veri yazan VeriOku(), Hesapla() ve VeriYaz() adlı üç ayrı metot ekleyelim. Ama öyle yapalım ki, main()metodu VeriYaz() metodunu çağırsın. VeriYaz() metodu Hesapla() metodunu çağırsın. Hesapla() metodu VeriOku() metodunu çağırsın. Böylece arka arkaya dört metot çalışacaktır. Bir metodun başka bir metodu çağırması, matematikteki bileşik fonksiyon (fonksiyon fonksiyonu) kavramından başka bir şey değildir. Đçteki fonksiyon, dıştaki fonksiyonun parametresidir (değişken). Vergi02.java package statik; import java.util.Scanner; public class Vergi02 { double brüt; 2 t.karaçay : Java ile Nesne Programlama double VeriOku() { Scanner scan = new Scanner(System.in); System.out.println("Brüt Geliri giriniz :"); brüt = scan.nextDouble(); scan.close(); return brüt; } double Hesapla() { return VeriOku() * 40 / 100; } void VeriYaz() { System.out.println(Hesapla()); } public static void main(String[] args) { VeriYaz(); } } Bu program derlenince aşağıdaki hata iletisini gönderir. Cannot make a static reference to the non-static method VeriYaz() … Bu ileti bize, static olmayan VeriYaz() metoduna static bir referans yapılamadığını; yani ona erişilemediğini söylemektedir. Bu mesajı alınca, programda hata ayıklama (debug) işine başlayalım. Đlk aklımıza gelen kolay yol, VeriYaz() metodunu static yapmaktır. 20.satırı şöyle değiştirelim: static void VeriYaz() { Bunu yaptığımızda, derleyici VeriYaz() metoduna erişebilir. Ancak onun çağırdığı Hesapla() metoduna erişemez ve şu hata iletisini yollar: Cannot make a static reference to the non-static method Hesapla() … Gene yukarıdaki çözümü uygulayıp, Hesapla() metodunu static yapalım. Bunun için 16.satırı şöyle değiştirelim: static double Hesapla() { Bunu yaptığımızda, derleyici Hesapla() metoduna erişebilir. Ancak onun çağırdığı VeriOku() metoduna erişemez ve şu hata iletisini yollar: Cannot make a static reference to the non-static method VeriOku() … Artık, bu hatayı nasıl ayıklayacağımızı biliyoruz. Gene yukarıdaki çözümü uygulayıp, VeriOku() metodunu static yapalım. Bunun için 8.satırı şöyle değiştirelim. static double VeriOku() { Bu aşamada programdaki bütün hataları ayıkladığımızı düşünüp, tekrar derlemeyi denersek şu hata iletisini alacağız: Cannot make a static reference to the non-static field brüt … Bu hatayı da ayıklamak için 6. Satırda bildirimi yapılan brüt değişkenini static yapalım: static double brüt; Bu son düzeltmeden sonra programımızda sözdizimi hatası kalmamıştır. Derleyip koşturabiliriz. Vergi03.java package statik; Bölüm 06: Operatörler 3 import java.util.Scanner; public class Vergi03 { static double brüt; static double VeriOku() { Scanner scan = new Scanner(System.in); System.out.println("Brüt Geliri giriniz :"); brüt = scan.nextDouble(); scan.close(); return brüt; } static double Hesapla() { return VeriOku() * 40 / 100; } static void VeriYaz() { System.out.println(Hesapla()); } public static void main(String[] args) { VeriYaz(); } } Çıktı Brüt Geliri giriniz : 12345,678 4938.2712 Yaptığımız hata ayıklama (debug) işlemleriyle, Vergi03.java programını çalışır duruma getirdik. Ancak, bu programda Nesne Yönelimli Programlamanın yeteneklerini hiç kullanmadık. Programdaki bütün değişkenleri ve bütün metotları static yaptık. Bu yöntem büyük projeler için uygun değildir. Bütün değişkenleri ve bütün metotları static yapmak yerine, sınıfa ait bir nesne yaratmak daha uygun olacaktır. Çünkü, yaratılan nesneye ait değişken ve metotların işi bitince, nesne ana bellekten silinir; yeni nesnelere yer açılır. Bu olgu çok sayıda nesne yaratan büyük programlar için önem taşır. Şimdi, değişkenleri ve metotları statik yapmadan programı yeniden yazalım. Sonra, sınıfa ait bir nesne yaratalım. Şimdi de ikinci yöntemi deneyelim. Program03 sınıfından p adlı bir nesne yaratalım. Bunu yapmak için Sınıf_adı nesne_adı = new Sınıf_adı(); nesne yaratıcı (instantiate) deyimini kullanıyoruz. Örneğimizde, nesne_adı p olacaktır. Vergi04.java package statik; import java.util.Scanner; public class Vergi04 { double brüt; double VeriOku() { Scanner scan = new Scanner(System.in); System.out.println("Brüt Geliri giriniz :"); 4 t.karaçay : Java ile Nesne Programlama brüt = scan.nextDouble(); scan.close(); return brüt; } double Hesapla() { return VeriOku() * 40 / 100; } void VeriYaz() { System.out.println(Hesapla()); } public static void main(String[] args) { Vergi04 vergi = new Vergi04(); vergi.VeriYaz(); } } Çıktı Brüt Geliri giriniz : 12345,678 4938.2712 ipucu Anımsayınız, Java uygulama programlarında main() metodunu içeren sınıf adı ile program adı daima aynı olmalıdır. Vergi04.java programı tek sınıftan oluşuyor. Ama büyük programlar çok sayıda sınıftan oluşabilir. Birden çok sınıf olduğunda, onlardan yalnızca birisi main() metodunu içerebilir. main() metodu programın giriş kapısı gibidir; başka yerden programa girilemez. 26.satırdaki Vergi04 vergi = new Vergi04(); deyimi, Vergi04 sınıfına ait vergi adlı bir nesne yaratıyor. Bu deyimdeki new bir anahtar sözcüktür ve daima sınıftan nesne yaratmak için kullanılır. Nesneye vergi yerine istenilen başka bir ad verilebilir. Nesneye verilen ad aynı zamanda nesneyi işaret eden referansın da adıdır. Tabii, verilen adın, yaratılan nesneyi ima eden bir sözcük olması, kaynak programın okunabilirliğini artırır; derleyici için verilen adın önemi yoktur. Vergi04 sınıfındaki VeriOku(), Hesapla(), VeriYaz() metotları ile brüt adlı değişkenin önüne static nitelemesi konulmadığı için, öntanımlı olarak (default) anlık olurlar; yani static değildirler. Dolayısıyla, ancak sınıfa ait bir nesne yaratılınca o nesne içinde ana bellekte kendilerine birer yer ayrılır. Üstlendikleri işlevlerini o nesne içinde yaparlar. Örnekteki nesnenin ana bellekteki adresini işaret eden referansın (işaretçi, pointer) adı vergi’dir. vergi adı aynı zamanda yaratılan nesnenin de adıdır. vergi nesnesine ait VeriYaz() metoduna erişmek için vergi.VeriYaz(); deyimini kullanıyoruz. Nesne adı ile metot arasındaki nokta (.) simgesi, nesnenin öğelerine erişimi sağlayan operatördür. Nesneye ait değişkenler için de aynı operatör kullanılır. Böyle olduğunu görmek için, sınıfa ait p ve q adlarıyla iki farklı nesne yaratalım ve o nesnelerdeki metotlarımızı farklı verilerle çalıştıralım. p ve q nesnelerinden gelen çıktıların farklı olduğunu görebiliriz. Vergi05.java import java.util.Scanner; Bölüm 06: Operatörler 5 public class Vergi05 { double brut; double VeriOku() { Scanner s = new Scanner(System.in); System.out.println("Brüt Geliri giriniz :"); brut = s.nextDouble(); return (brut); } double Hesapla() { return VeriOku() * 40 / 100; } void VeriYaz() { System.out.println(Hesapla()); } public static void main(String[] args) { Demo p = new Demo(); System.out.println("p nesnesi için :"); p.VeriYaz(); System.out.println(); Demo q = new Demo(); System.out.println("q nesnesi için :"); q.VeriYaz(); } } Çıktı p nesnesi için : Brüt Geliri giriniz : 12345,678 4938.2712 q nesnesi için : Brüt Geliri giriniz : 98765,4321 39506.17284 Şimdi başka bir durumu deneyelim. Sınıfta bildirimi yapılan metotların hepsini satic yapalım ve sınıftan bir nesne yaratmayı deneyelim. Vergi06.java import java.util.Scanner; public class Vergi06 { static double VeriOku() { Scanner s = new Scanner(System.in); System.out.println("Brüt Geliri giriniz :"); return (s.nextDouble()); } 6 t.karaçay : Java ile Nesne Programlama static double Hesapla() { return VeriOku() * 40 / 100; } static void VeriYaz() { System.out.println(Hesapla()); } public static void main(String[] args) { Vergi06 vrg = new Vergi06(); vrg.VeriYaz(); } } Bu programı derlemek istersek şu uyarı iletisini verecektir: The static method VeriYaz() from the type Vergi06 should be accessed in a static way Ancak program derlenir ve koşar. Bu demektir ki, static metotlara bir nesnenin referansı ile erişilebilir. Çünkü, statik öğelere her nesneden erişilebilir. Ancak bu yöntem, erişilen öğenin anlık olduğu yanılgısına yol açabileceği için, önerilmez. Statik öğelere erişirken, bir nesneye ait referans (pointer) yerine sınıfın adının kullanılması yeğlenmelidir. Örneğin, yukarıdaki programda 23.satır yerine Vergi06.VeriYaz(); deyimini yazarsak, programın derlendiğini ve koştuğunu görebiliriz. Statik metotlar için önerilen deyim budur. Eğer çağrı aynı sınıf içinden yapılıyorsa, sınıf adını yazmaya gerek olmayabilir. Örneğin, yukarıdaki deyim yerine VeriYaz(); deyimini yazsak da program derlenir ve koşar. Ancak, program birden çok sınıf içeriyorsa, farklı bir sınıftaki bir static metodu çağırmak için, öncekinde olduğu gibi sınıf adını belirtmeliyiz. Değilse, derleyici, metodu hangi sınıftan çağıracağını bilemez. Her durumda, static metotları çağırırken, sınıf adlarını öntakı yapmayı alışkanlık edinmek, büyük programlarda oluşabilecek yanılgıları önler ve gerektiğinde programın güncellenmesi kolaylaşır. Statik metotlar için söylediğimiz bu özelikler statik değişkenler için de geçerlidir. Alıştırmalar Şimdi aşağıdaki durumları ayrı ayrı deneyelim: Metotların üçü de anlık ise, 22. ve 23. satırlardaki deyimlerle programın derlenip koşabildiğini gördük. Metotların üçü de static ise, 23.satırdaki deyim aşağıdakilerden birisi olabilir: vrg.VeriYaz(); Bu durumda statik metot nesne içinden çağrılıyor. VeriYaz(); Bu durumda, aynı sınıftaki statik VeriYaz() metoduna adıyla erişiliyor. VeriYaz() metodu statik Hesapla() metoduna adıyla erişiyor. Sonunda Hesapla() metodu da statik VeriOku() metoduna adıyla erişiyor. Vergi06.VeriYaz(); Bu durumda, statik VeriYaz() metoduna, sınıf adı öntakı yapılarak erişiliyor. Karışıklığı önlemek için, büyük programlarda statik öğelere erişmek için önerilen sözdizimi budur. VeriYaz() metodu anlık, VeriOku() ve Hesapla() metotları statik ise 22. ve 23. satırlardaki deyimlerle program derlenir ve koşar. Bunun nedeni şudur: Anlık VeriYaz() metoduna vrg referansı ile Bölüm 06: Operatörler 7 erişiliyor. Sonra nesne içinden statik Hesapla() metodu çağrılıyor. En sonunda, statik Hesapla() metodu statik VeriOku() metodunu çağırıyor. Bunlar Java’da geçerli eylemlerdir. VeriYaz() ile Hesapla() metotları anlık VeriOku() metodu statik ise 22. ve 23. satırlardaki deyimlerle program derlenir ve koşar. Bunun açıklaması kolaydır. Anlık VeriYaz() metoduna vrg referansı ile erişiliyor. Sonra aynı nesne içindeki anlık Hesapla() metodu çağrılıyor. En sonunda, nesne içinden statik VeriOku() metodunu çağrılıyor. VeriYaz() statik, Hesapla() anlık ise program derlenemez. Çünkü statik metotlar nesne içindeki öğelere erişemezler. O nedenle statik VeriYaz() metodu nesne içindeki anlık Hesapla() metodunu çağıramaz. Aşağıdaki program, farklı sınıftaki statik öğelere adlarıyla erişilemeyeceğini gösteriyor. Demo.java import java.util.Scanner; class Vergi { static double VeriOku() { Scanner s = new Scanner(System.in); System.out.println("Brüt Geliri giriniz :"); return (s.nextDouble()); } static double Hesapla() { return VeriOku() * 40 / 100; } static void VeriYaz() { System.out.println(Hesapla()); } } public class Demo{ public static void main(String[] args) { VeriYaz(); } } Bu program için derleyici şu hata iletisini atar: The method VeriYaz() is undefined for the type Demo Bu ileti, Demo sınıfında VeriYaz() metodunun varolmadığını söylüyor. Hatayı ayıklamak (debug) için, derleyiciye VeriYaz() metodunu hangi sınıfta bulacağını söylemeliyiz. Bunu yapmak için, sınıf adını metodun öntakısı yapmak yetecektir; yani 24.satırdaki deyimi şöyle yazmalıyız: Vergi.VeriYaz(); Bunu yapınca programın derlendiğini ve koştuğunu göreceğiz. Şimdi 15.satırdaki VeriYaz() metodunu anlık yapalım. Demo.java import java.util.Scanner; class Vergi { 8 t.karaçay : Java ile Nesne Programlama static double VeriOku() { Scanner s = new Scanner(System.in); System.out.println("Brüt Geliri giriniz :"); return (s.nextDouble()); } static double Hesapla() { return VeriOku() * 40 / 100; } void VeriYaz() { System.out.println(Hesapla()); } } public class Demo { public static void main(String[] args) { // Vergi p = new Vergi(); Vergi.VeriYaz(); } } Bu program derlenemez ve şu hata iletisini atar: Cannot make a static reference to the non-static method VeriYaz() from the type Vergi Bu ileti, farklı bir sınıftaki anlık metoda erişilemediğini söylüyor. Hatayı ayıklamak için, ya VeriYaz() metodunu statik yapacağız ya da VeriYaz() metodunu içeren sınıfa ait bir nesne yaratacak ve VeriYaz() metoduna referans ile erişeceğiz. Birinci yöntemi, yani metodu statik yapmayı önceden denedik ve çalıştığını gördük. Şimdi VeriYaz() metodunu içeren Vergi sınıfına ait bir nesne yaratalım. Bunun için 23. ve 24. satırlardaki deyimleri şöyle değiştirelim: Vergi vergi = new Vergi(); vergi.VeriYaz(); Bu deyimlerle programın derlendiğini ve koştuğunu görebiliriz. Şimdi de static metotları çağırırken metot adlarının önüne this anahtar sözcüğünün konamayacağını görelim. Bunun için 23.satırdaki deyim yerine this.VeriYaz(); deyimini yazalım. Derleyicinin şu hata uyarısını attığını göreceğiz: Cannot use this in a static context Özet Buraya kadar yaptıklarımızı özetlersek şu kurallar ortaya çıkar: 1. 2. 3. 4. 5. 6. Anlık metot, anlık değişkenlere ve anlık metotlara referanslarıyla erişebilir. Anlık metot, aynı sınıftaki statik değişkenlere ve statik metotlara adlarıyla erişebilir. Anlık metot, farklı sınıftaki statik değişkenlere ve statik metotlara, sınıf adlarını öntakı yaparak erişebilir. Aynı sınıf içindeki statik değişkenlere ve statik metotlara adlarıyla erişilebilir. Farklı sınıf içindeki statik değişkenlere ve statik metotlara, ait oldukları sınıf adları öntakı yapılarak erişebilir. Bu durumda sınıf adı işaretçinin görevini üstlenir. Nesne içinden, aynı sınıfa ait statik değişkenlere ve statik metotlara adlarıyla erişilebilir. Bölüm 06: Operatörler 9 7. Nesne içinden, farklı sınıfa ait statik değişkenlere ve statik metotlara, ait oldukları sınıf adları öntakı yapılarak erişebilir. Bu durumda sınıf adı işaretçinin görevini üstlenir. 8. Statik metot, aynı sınıftaki statik değişkenlere ve statik metotlara adlarıyla erişebilir. 9. Statik metot, farklı sınıftaki statik değişkenlere ve statik metotlara, sınıf adlarını öntakı yaparak erişebilir. 10. Statik metot, anlık değişkenlere ve anlık metotlara adlarıyla erişemez; ancak referans (pointer) kullanarak erişebilir. 11. Statik metot, this anahtar sözcüğünü kullanamaz; çünkü this için işaret edilecek bir nesne yoktur. 10 t.karaçay : Java ile Nesne Programlama