Java'da Static Anahtar Kelimesi ve Kullanımı

İbrahim Aslan
İbrahim Aslan 5 yıl önce

Java öğrenmeye çalışanların kafasını karıştıran konulardan birisi de static değişkenler ve metotlardır. Aslında kullanım mantığı çok basit olan static anahtar kelimesi gereksiz yere birçok kişinin kafasını karıştırmaktadır. Bu yazıda Java dilinde static kelimesinin kullanım alanlarını çeşitli örnekler vererek alt başlıklar halinde inceleyeceğiz.

Sınıf ve Nesne Değişkenleri

Bildiğiniz gibi sınıflar içinde bulunan alanlar (değişkenler), o sınıftan yaratılan her nesne için ayrı ayrı oluşturulurlar. Yani ad, soyad ve yaş gibi 3 bilgi içeren bir sınıfımız varsa, bu sınıftan oluşturduğumuz her nesne için farklı farklı ad, soyad ve yaş değişkenleri bellekte oluşturulur. Bu yüzden bu tür değişkenlere nesneye özgü oldukları için "nesne değişkeni" denir. Bir sınıftan oluşturduğumuz her nesne için tanımlı olan nesne değişkenlerinin değerleri de o nesneye ait olacaktır. Ancak Java'da sadece nesneye ait değil, sınıfa ait değişkenler tanımlamak da mümkün. İşte tam burada "static" anahtar kelimesi devreye giriyor. Static anahtar kelimesi kullanılarak oluşturulan değişkenler nesne değişkeni değil "sınıf değişkeni" olarak adlandırılırlar. Bu değişkenler nesneye ait değil, sınıfa ait bilgileri taşırlar. Sınıf değişkenleri içinde tanımlandığı sınıftan hiçbir nesne oluşturulmamış olsa bile bellekte yer kaplarlar. Nesne değişkenleri ise ancak bir nesne tanımlandığında bellekte yer kaplarlar. Bu iki tür değişkenin ayrıldığı bir başka nokta da sınıf değişkenlerinin sadece bir örneğinin olmasıdır. Yani o sınıftan kaç tane nesne oluşturulursa oluşturulsun, bellekte tek bir tane sınıf değişkeni vardır ve ne şekilde erişirsek erişelim, aynı sınıf değişkenine erişiriz. Şimdi bunları bir örnekle pekiştirelim.

package com.seckintozlu.makale;

public class Ogrenci {
	private String isim; 	// Nesne Değişkeni
	private int yas;	// Nesne Değişkeni
	private String ogrenciNo;	// Nesne Değişkeni
	public static int ogrenciSayisi = 0;	// Sınıf Değişkeni

	public Ogrenci(String isim, int yas, String ogrenciNo) {
		this.isim = isim;
		this.yas = yas;
		this.ogrenciNo = ogrenciNo;

		// Her nesne yaratildiginda sinif degiskenini artiriyoruz. ogrenciSayisi++;
	}
}

Burada 3 tane nesne değişkeni, 1 tane de sınıf değişkeni içeren bir sınıfımız var ve bu sınıfın her bir örneği oluşturulduğunda yapıcı (constructor) metot içerisinde sınıf değişkeninin değerinin artırıldığını görüyoruz. Şimdi aşağıdaki koda bakalım.

package com.seckintozlu.makale;

public class Program {
	public static void main(String args[]) {
		System.out.println("Baslangicta ogrenci sayisi: " + Ogrenci.ogrenciSayisi);
	
		// iki tane ogrenci nesnesi olusturuyoruz
		Ogrenci ogrenci1 = new Ogrenci("Obi-Wan Kenobi", 19, "2010001");
		Ogrenci ogrenci2 = new Ogrenci("Anakin Skywalker", 16, "2010002");
	
		int ogrSayi = Ogrenci.ogrenciSayisi;
		System.out.println("Ogrenci sayisi (Sinif uzerinden): " + ogrSayi);

		ogrSayi = ogrenci1.ogrenciSayisi;
		System.out.println("Ogrenci sayisi (Ilk nesne uzerinden): " + ogrSayi);

		ogrSayi = ogrenci2.ogrenciSayisi;
		System.out.println("Ogrenci sayisi (Ikinci nesne uzerinden): " + ogrSayi);
	}
}

Yukarıdaki kod çalıştırıldığında aşağıdaki sonucu üreteceketir.

Baslangicta ogrenci sayisi: 0
Ogrenci sayisi (Sınıf üzerinden): 2
Ogrenci sayisi (İlk nesne üzerinden): 2
Ogrenci sayisi (İkinci nesne üzerinden): 2

Koddan da göreceğiniz gibi static sınıf değişkenlerine hem sınıf adı ile, hem de o sınıftan yaratılmış nesne referansları ile erişmek mümkün. Ancak hangi yolla erişirsek erişelim, sınıf değişkeni tek bir tane olduğu için erişilen bellek alanı aynıdır. Henüz hiçbir nesne oluşturmamışken sınıf adı ile eriştiğimiz sınıf değişkenimiz ilk değeri olan 0'ı verecektir. Her bir nesne oluşturduğumuzda ise sınıf değişkenimiz bir artmaktadır. Sınıf değişkenleri bu örnekte olduğu gibi oluşturulan nesne sayısını tutmak için sıkça kullanılmaktadırlar. Örnekte 2 tane nesne oluşturup sınıf değişkenimizin değerini 2 yapmış oluyoruz. Daha sonra bu sınıf değişkenine hem sınıf adı ile, hem de nesne referansları ile erişip içeriğini yazdırıyoruz ve sonuçta bu 3 erişimde de aynı sonucu alıyoruz. Dikkat ederseniz static değişkeni artıran kod satırı nesneye özgü bir alan alan olan yapıcı metot içerisinde olmasına rağmen artırılan değişken sınıf değişkeni olduğu için yaptığımız değişiklik nesneden bağımsız olmaktadır. Çünkü daha önce de belirttiğim gibi sınıf değişkenleri tek bir defa oluşturulur ve yapılan her türlü erişim bu tek değişkene yapılmış olur.

Static Metotlar

Şimdi ise static anahtar kelimesinin başka bir kullanım alanı olan static metotlara bakalım. Normal şartlarda bir sınıftaki bir metodu çalıştırmak istiyorsak, önce o sınıfı kullanarak bir nesne oluşturmalı, sonra bu nesne referansı üzerinden metodu çağırmalıyız. Ancak değişkenlerde olduğu gibi metotlar için de static kelimesini kullanarak nesnelerden bağımsız sınıf metotları yazabiliriz. Sınıf metotlarını çağırmak için o sınıftan bir nesne oluşturmamız gerekmez. Değişkenlere erişir gibi direk sınıf ismi ile bu metotları çağırmamız mümkündür. Şimdi aşağıdaki kodu inceleyelim.

package com.seckintozlu.makale;

public class Ogrenci {
	private String isim; // Nesne degiskeni
	private int yas; // Nesne degiskeni
	private String ogrenciNo; // Nesne degiskeni
	private static int ogrenciSayisi = 0; // Sinif degiskeni

	public Ogrenci(String isim, int yas, String ogrenciNo) {
		this.isim = isim;
		this.yas = yas;
		this.ogrenciNo = ogrenciNo;
		
		// Her nesne yaratildiginda sinif degiskenini artiriyoruz.
		ogrenciSayisi++;
	}

	public static int getOgrenciSayisi() {
		return ogrenciSayisi;
	}
}

Bu kodun ilk verdiğim Ogrenci sınıfından tek farkı eklenen yeni bir static metot. İlk örnekte public olarak belirttiğimiz sınıf değişkenimize direk ismiyle erişirken, bu sefer sınıf değişkenini private yapıp static bir metot üzerinden erişiyoruz. Bu sınıfı test etmek için aşağıdaki kodu çalıştıracağız.

package com.seckintozlu.makale;

	public class Program {
		public static void main(String args[]) {
			System.out.println("Baslangicta ogrenci sayisi: " + Ogrenci.getOgrenciSayisi());

		// iki tane ogrenci nesnesi olusturuyoruz
		Ogrenci ogrenci1 = new Ogrenci("Obi-Wan Kenobi", 19, "2010001");
		Ogrenci ogrenci2 = new Ogrenci("Anakin Skywalker", 16, "2010002");

		int ogrSayi = Ogrenci.getOgrenciSayisi();
		System.out.println("Ogrenci sayisi: " + ogrSayi);
	}
}

Bu kodun çıktısı aşağıdaki gibi olacaktır.

Baslangicta ogrenci sayisi: 0
Ogrenci sayisi: 2

çıktısını verecektir. Öğrenci sınıfının içindeki getOgrenciSayisi() metodu static olduğu için, bu sınıfın herhangi bir nesnesini oluşturmadan önce bu metodu çağırmamız mümkündür. Bu sınıftan herhangi bir nesne oluşturduysak, o nesne referansı üzerinden de tanımladığımız static metodu çağırabiliriz ancak bu kodun okunabilirliği açısından pek tavsiye edilmez. Aynı şekilde static değişkenlere erişirken de nesne referanslarını kullanabiliyoruz ancak bunlara da sınıf adı ile erişmek kodun okunabilirliğini artırmaktadır.

Static metotlarla ilgili bilinmesi gereken en önemli şey şudur: static metotlar içinden static olmayan bir sınıf öğesine erişemeyiz!! Eğer bizim static metodumuzun içerisindeki ogrenciSayisi değişkeni static olmasaydı program hata verecekti. Static bir metot içerisinde static olmayan değişkenlere erişemediğimiz gibi, static olmayan metotları da çağıramayız! Bu durumu şöyle örnekleyelim.

package com.seckintozlu.makale;

public class Ogrenci {
	private String isim; // Nesne degiskeni
	private int yas; // Nesne degiskeni
	private String ogrenciNo; // Nesne degiskeni
	private static int ogrenciSayisi = 0; // Sinif degiskeni

	public Ogrenci(String isim, int yas, String ogrenciNo) {
		this.isim = isim;
		this.yas = yas;
		this.ogrenciNo = ogrenciNo;

		// Her nesne yaratildiginda sinif degiskenini artiriyoruz. 
		ogrenciSayisi++;
	}

	public int ogrenciSayisiniBul() {
		return ogrenciSayisi;
	}

	public static int getOgrenciSayisi() {
		return ogrenciSayisiniBul();
	}
}

Öğrenci sınıfımıza static olmayan ogrenciSayisiniBul() isimli yeni bir metot ekledik. Bu metot sadece ogrenciSayisi isimli sınıf değişkenimizi döndürüyor. Buraya dikkat çünkü kuralımız şöyleydi: static metot içerisinden static olmayan bir öğeye erişemeyiz. Burada ise static olmayan bir metottan static bir öğeye erişiyoruz. Bu geçerli bir erişimdir. Ancak static metodumuz olan getOgrenciSayisi() metodu static olmayan ogrenciSayisiniBul() isimli metodu çağırıyor. Kuralımıza göre getOgrenciSayisi() static bir metot olduğu için static olmayan ogrenciSayisiniBul() metodunu çağıramaz. Yukarıdaki kodu derlemeye çalıştığımızda "Cannot make a static reference to the non-static method ogrenciSayisiniBul() from the type Ogrenci" hatası alırız ve kodumuz derlenmez. Aynı şekilde getOgrenciSayisi() metodu içerisinden static olmayan bir değişkene (örneğin isim) erişmeye çalışsaydık yine hata alacaktık.

Bunun sebebini şöyle açıklayabiliriz. Static değişkenler ve metotlar nesnelerden bağımsızdır. Yani ortada hiçbir nesne yokken static metotlar çağırılabilir veya static değişkenlere (sınıf değişkenleri) değer atanabilir. Static olmayan sınıf öğeleri ise ancak bir nesne tanımlandığında var olabilir. Bu durumda static bir metot içerisinde static olmayan bir değişkene erişilmeye çalışıldığında hangi nesnenin değişkenine erişilecektir? O anda ortada hiç nesne bile olmayabilir. Aynı durum static metot içerisinde static olmayan bir metot çağırılmaya çalışıldığında da geçerlidir. Bu sebeple static metotlar içerisinden ancak static olan sınıf öğelerine erişebiliriz. Ancak yukarıdaki örnekte yaptığımız gibi static olmayan bir metot içinden static öğelere erişebiliriz. Çünkü static öğeler nesne yaratılmadan daha önce zaten yaratılmıştı.

Static metot içerisinden static olmayan metodu çağıramayınca gidip diğer metodu da static yapmak o an için işe yarayabilir ancak bu son derece kötü bir çözüm yoludur. Hatta çözüm yolu bile değildir, kodu katletmektir. Böyle bir durumda metotlarınızın gerçekten static olup olmaması gerektiğini sorgulamak gerekir. Bu konuya yazının sonunda değineceğim. Şimdi diğer bir static kullanım olan static kod bloklarına göz atalım.

Static Kod Blokları

Static kod blokları static değişkenlere ilişkin ilk değer atamalarını yapmak için kullanılan kod bloklarıdır. Bunlara literatürde "static initializer" denmektedir. Static kod blokları sınıf değişkenleri belleğe yüklendikten hemen sonra işletilirler. JVM, o sınıfa ait bir nesne oluşturulmadan önce static kod bloklarının işletimini garanti eder.

package com.seckintozlu.makale;
import java.util.Random;

public class StaticInitializer {
	private static int dizi[];
	private static int boy = 10;

	static {
		dizi = new int[boy];
		Random rnd = new Random();

		for(int i=0; i < boy; i++)
			dizi[i] = rnd.nextInt(100);
	}
}

Yukarıdaki koda baktığımızda static kod bloğu içerisinde 0 ile 99 arasında rastgele belirlenmiş 10 adet sayıdan oluşan bir dizi oluşturulduğunu görüyoruz. Bu kod bloğu sayesinde, bu sınıfın değişkeni olan dizi değişkeni yaratıldığı anda 10 tane rastgele sayı ile doldurulmaktadır. Static kod blokları içerisinde de, static metotlarda olduğu gibi static olmayan sınıf öğelerine erişmek mümkün değildir. Static kod blokları yaygın olarak kullanılmadığından daha fazla detaya inmeye gerek yok diye düşünüyorum.

Static Import

Static metotların sınıf adı kullanılarak çağırıldığını biliyoruz. Örneğin Java'da "java.lang" paketinde bulunan "Math" sınıfındaki tüm metotlar static metotlardır ve bunları çağırmak için Math.metotAdi() şeklinde bir kod yazmak gerekmektedir. Math sınıfındaki static metotları kodumuza import edip sınıf adını kullanmadan sadece metot adı ile static metotları çağırmak istiyorsak aşağıdaki gibi static import kullanabiliriz. Buradaki tek fark normalde kullandığımız import kelimesinin yanına bir de static eklemektir.

package com.seckintozlu.makale;
import static java.lang.Math.*;

public class Program {
	public static void main(String args[]) {
		System.out.println("Karekok 25: " + sqrt(25));
		System.out.println("Log(100): " + log(100)); 
		System.out.println("Pi Sayisi" + PI);
	}
}

Kodda görüldüğü gibi normalde Math.sqrt(), Math.PI yazarak erişmemiz gereken static metot ve değişkenlere static import kullanarak direk erişme sansına sahip olabiliyoruz.

Static Değişkenler Ne Zaman Kullanılmalı?

Ne zaman static değişkenlerin, ne zaman nesne değişkenlerinin kullanılması gerektiği özellikle Java programlamaya yeni başlamış olan kişiler için kafa karıştırıcı olabilir. Aslında aralarındaki fark çok açık ve net. Sınıf değişkenleri her bir nesne için ortak olan, sadece bir örneği bulunan, başka bir deyişle değeri nesneden nesneye değişmeyen, her nesne için aynı olan değişkenlerdir. Nesne değişkenleri ise zaten adı üzerinde, değeri her nesne için farklı olması gereken değişkenlerdir. Yazının başında vermiş olduğumuz örnekte isim, öğrenci numarası gibi değişkenlerin her birinin her öğrenci nesnesi için farklı olması gerektiği çok açıktır.

Öte yandan öğrenci sayısı değişkeni toplamda yaratılmış öğrenci nesnelerinin sayısını tuttuğu için, tek bir değeri olması gerekmektedir. Yani sınıf değişkeni (static) olması gerekmektedir. Öğrenci sayısı değişkenini nesne değişkeni olarak da tutabiliriz ancak her bir nesne ayrı bir kopya tutacağı için bu değer her değiştiğinde bütün nesneleri gezip her birinde de bu değeri güncellememiz gerekir. Bu da çok anlamsız ve verimsiz bir çözüm olur.

Static Metotlar Ne Zaman Kullanılmalı?

Tanımladığımız metotların sınıf metodu mu yoksa nesne metodu mu olması gerektiği daha zor cevaplanabilecek bir sorudur. Bildiğiniz gibi nesne temelli programlama dillerinde nesnelerin durumları ve davranışları vardır. Tanımladığımız değişkenler (alanlar) nesnelerin durumunu belirlerken, metotlarımız ise davranışları temsil eder. Eğer bir davranış (metot) çalışabilmek için o nesnenin durumuna bağımlı ise ya da o nesnenin o anki durumundan etkileniyorsa, o metod bir nesne metodu olmalıdır. Ancak bir davranışın gerçekleştirmesi o nesnenin durumundan tamamen bağımsız ise, diğer bir deyişle nesnenin o anki durumu metodun üreteceği sonuca etki etmeyecekse, o metod static olmalıdır.

Örnek verecek olursak java.lang.Math sınıfı içerisindeki tüm metotlar sınıf metotlarıdır. Bir sayının karekökünü almak için o sayıyı bir metoda parametre olarak geçmek yeterlidir. Math sınıfından yaratılacak bir nesnenin o anki durumunun karekök alan sqrt() metodu ile hiçbir ilgisi yoktur. O zaman karekök hesaplama için Math sınıfından bir nesne yaratmaya da gerek yoktur. Bu sebeple bu metotlar static olarak tanımlanmıştır. Program geliştirirken nesnenin durumundan bu kadar bağımsız örnekler bulmak zor olabilir. Ancak aradaki farkı kavradığınız zaman bir metodun static olup olmaması gerektiğine kolaylıkla karar verebilirsiniz.

Son Not: public static void main(String args[])

Java'da main metodunun imzası sabittir. Main metodu public ve static olmak zorundadır. Bu metodun public olması gerektmektedir çünkü kodumuzu çalıştıran JVM'nin dışarıdan main metodunu çalıştırabilmesi gerekir. Bu metodun ayrıca static de olması gerekir çünkü JVM kodumuzu çalıştırırken main metodunun yazılı olduğu sınıftan bir nesne oluşturmaz. Ortada nesne yokken bir metodu çalıştırmak için de o metodun static olarak tanımlanmış olması gerekir.


Makale 05 Eki 2010 tarihinde Seçkin TOZLU tarafından seckintozlu.com web adresinde yayınlanmıştır, alıntıdır.


Yorum yapılmamış. İlk yorumu sen yap.
Koyu Tema
Geri Bildirim