Bölüm 03

Sınıflar ve Nesneler

Sınıf Nedir?

Sınıf  Bildirimi

Sınıf ve Nesne

new Operatörü ile Nesne Yaratmak

Nesnenin Öğelerine Erişim

 

Sınıf (class) Nedir?

Sınıf (class) soyut bir veri tipidir. Nesne (object) onun somutlaşan bir cismidir.

Java’da sınıf (class)  kavramını doğada cins isimlere benzetebiliriz. Bir cins kendi başına belirli bir nesne değildir; ancak belirli türden nesnelerin ortak özelliklerini belirten soyut bir kavramdır. Örneğin, ağaç bir cins isimdir. Ama bahçedeki bir elma-ağacı ya da sokaktaki bir çınar-ağacı belirli varlıklardır. Onlar, ağaç sınıfının birer nesnesidir (üyesidir). Cinsler alt-cinslere ayrılabilir. Alt-cinsler, üst-cinslerin özelikleri yanında kendilerine has başka özelikler de taşırlar. Örneğin, memeli-hayvanlar geniş bir sınıftır. Filler, kaplanlar, şempanzeler, balinalar, insanlar vb. memeliler sınıfının (üst-sınıf) birer alt-sınıfıdır. Alt-sınıftakiler, üst-sınıfın özeliklerini taşımakla birlikte, birbirlerinden kesinlikle farklıdırlar.  Her cinsin ve her alt-cinsin kendine özgü özelikleri (nitelikler ve davranışlar) vardır. Bu özelikler, onları diğer cinslerden ayırır.

Java’da sınıfları doğadaki cinsler gibi düşürsek, konuyu kavramamız kolaylaşacaktır. Java sınıfı, tıpkı bir cinste olduğu gibi, ortak özelikleri belirlenmiş bir topluluğun adıdır. Bir java sınıfının niteliklerini değişkenlerle (attributes, fields), davranışlarını metotlarla (fonksiyon, procedure) belirleriz. Başka bir deyişle, istediğimiz özeliklerini belirterek bir sınıf (cins-isim) tanımlarız. Bu sınıfın nitelikleri  değişkenlerle (attributes, fields) ve davranışları metotlarla (fonksiyon, procedure) ortaya konulur. Kısaca, bir java sınıfı kendi özniteliklerini belirleyen değişkenleri ve fonksiyonları içeren bir birimdir. Bu nedenle, Pascal ve C gibi yapısal dillerdeki yapı (record, struct) kavramının bir genellemesidir. Record ve struct yalnızca değişkenler içerebilir. Fonksiyon ve procedure’ler onların dışında kalır. Java sınıfı ise, değişkenler yanında fonksiyonları da içeren daha genel bir yapıya sahiptir. Bir sınıf içindeki fonksiyonlar o sınıfın değişkenlerine erişebildiği gibi, farklı sınıflar arasında da iletişim sağlanabilmektedir.

Sınıf ve alt-sınıfların tanımı, bütünüyle programcının gereksemelerine göre yapılabilir. Alt-sınıflardan başka alt-sınıflar üretilebilir. Böylece en üst-sınıftan başlayarak en alt-sınıfa ulaşan hiyerarşik bir yapıya sahip olurlar. En alttaki sınıf, kendisinin üstünde olan bütün sınıfların özniteliklerini taşır. Buna kalıtım (inheritance) özeliği diyoruz. Bu özelik, Java’nın üstün özeliklerinden birisidir ve ilerideki konularda nasıl işe yaradığını göreceğiz.

Java programları sınıf (class) lardan oluşur. Bütün sınıflar aynı yapıya sahiptirler. Aralarındaki farkı yaratan şey, içerdikleri değişkenler ve metotlardır. Bazıları hiç değişken ve/veya metot içermez, bazıları az, bazıları çok değişken ve  metot içerebilir.

Uyarı:     Bir sınıftaki değişkenler o sınıfın niteliklerini, metotlar ise o sınıfın davranışlarını belirler. Bu nedenle, Sun da dahil olmak üzere bazı kaynaklarda değişken terimi yerine nitelem (attribute), fonksiyon ve procedure yerine de metot terimi kullanılır. Biz de bu derslerde genellikle bu geleneğe uyacağız. Ancak, özellikle alışkanlıklara hitap etmek gerektiğnde, nitelem yerine değişken terimini ve metot yerine fonksiyon terimini kullanarak gelenekten biraz sapabileceğiz.

Sınıf  Yapısı

Java’da sınıf (class) yapısı için sözdizimi şöyledir:

    [Erişim_nitelemi] class ad {

            Class’ın tanımı

    }

Yapının açıklaması:

 [Erişim_belirteci](Access-modifier):     Class’a kimlerin erişebileceğini belirten bir nitelemdir. Bu nitelemleri ve yaptıkları işleri yeri geldikçe örnekler üzerinde açıklayacağız.

class               Sınıf tanımında mutlaka kullanılması gereken anahtar bir sözcüktür; derleyiciye bir class tanımı yapıldığını bildirir.

Ad                   Her class’a bir ad verilir. Class adlarını büyük harfle başlatmak bir gelenektir. Buna karşılık, değişken adları, paket adları ve interface adları küçük harfle başlatılır. Böylece, kaynak programa bakan herkes, neyin class olduğunu hemen anlayabilir. Bu java programcıları arasındaki bir sözleşmedir ve bu sözleşmenin java derleyicisi bakımından bir anlamı yoktur. Sözleşme, yalnızca programcıların kaynak programı okuyup anlamasını kolaylaştırır.

{ }                    Class’ın yapısı bu parantezler içinde kurulur; yani class’ın nitelemleri ve metotları tanımlanır. Bunların   nasıl yapıldığını adım adım göreceğiz.

Java dili büyük-küçük harf ayrımına duyarlıdır. Anahtar sözcüklerde bu ayrıma dikkat edilmezse, program derlenemez. Bunun yanında, kaynak programın, okur tarafından kolay anlaşılması için konulmuş bazı kurallar vardır. Bir tür gelenek halini alan bu kurallar, derleyici açısından zorunlu olmasa bile, program yazanların bunlara uyması istenir. Bunlar, java programcıları arsındaki yazısız bir antlaşma hükümleridir.  Böylece, kaynak programı okuyan herkes, programın yapısını daha kolay kavrayabilir. Bu kuralları, yeri geldikçe örnekler üzerinde açıklayacağız. Şimdi, basit birkaç kuralı söylemek gerekiyor.

Uyarı

1.                   Java’da adlar (değişken, metot, class, interface, paket adları) bir karakterle başlar, karakter veya rakamlarla devam edebilir. Her ad tek sözcükten oluşur; yani ad olan sözcük boşluk içeremez. Örneğin, Yolcu 12 Treni  bir ad olamaz. Bunu Yolcu_12_Treni ya da Yolcu12Treni gibi yazmak gerekir.

2.                   İki ya da daha çok sözcüğün birleştirilmesiyle bir ad yaratılıyorsa, adlar arasına altçizgi (_) simgesi konulur ya da bitişik yazılıp sözcüklerin her birisi büyük harfle başlatılabilir. Örnekler: Mavi_Yolcu_Treni, MaviYolcuTreni.

3.                   Class_adları büyük harfle başlar, küçük harflerle ya da rakamlarla devam eder. Örnekler:  Tren, Mavi_Tren, Mavi_Yolcu_Treni, MaviYolcuTreni.

4.                   Değişken, metot, interface ve paket adları küçük harfle başlar.  Örnekler:  renk, metro_bileti, metroBileti, gidiş_dönüş, gidişDönüş,  hızlı_tren, hızlıTren.

5.                   Sabitlenmiş değişkenler (final) büyük harflerle yazılır. Örnek: PI=3.14159.

 

Hemen belirtelim ki, java dili, bir programcının gerekseme duyacağı genel türden sınıfları (class) ön-tanımlı olarak içerir. Java’nın içerdiği ve Java API (Application Programmer’s Interface) denilen bu sınıfların dökümanları  http://download.oracle.com/javase/6/docs/api/ web sitesinden her an görülebilir. Programcı bu sınıfları doğrudan kullanabildiği gibi, dilerse onların alt-sınıflarını üretebilir ya da kendi istediği sınıfları serbestçe tanımlayabilir. Bu durumuyla, java dili, programcıya olağanüstü kolaylık ve özgürlük sunmaktadır.

Aşağıdaki sınıf, programcının yazdığı basit bir sınıftır. Bu örnek, aslında java dilinin özelliklerini göstermekten daha çok, o özelikleri saklamaktadır. Ama, her dil öğretiminde olduğu gibi, konuya sistematik yaklaşmak yerine pedagojik yaklaşmak daha verimlidir. Bu nedenle, konuyu, basitten karmaşığa giden adımlarla izleyerek, sonunda asıl sistematik yapıya ulaşacağız.

Jprog01 sınıfı, java API kütüphanesinde yer alan java.lang paketi içindeki System class’ını ve java.io paketi içindeki PrintStream altsınıflarını kullanmaktadır. Aşağıdaki açıklamaları izleyebilmeleri için, öğrencilerin java API dokümanlarına bakmaları önerilir.  

Jprog01 class’ı içinde programcının tanımladığı hiçbir değişken ya da metot yoktur. Bunun yerine java API ‘den alınan System class’ını ve main() metodunu kullanmaktadır. Ayrıca main() metodunun çağırdığı System.out.println() fonksiyonu, parametre olarak bir metin (string) içerdiği için, String class’ını çağırmaktadır.  Bütün bu işleri, java kendiliğinden yapmaktadır. Programcıya düşen tek iş aşağıdaki basit programı yazmaktır.  Bu program, aslında bir sınıf (class) yazmaktan ibarettir.

class Jprog01

{

  public static void main(String[] args)

    {

         System.out.println("Ankara Türkiye\’nin başkentidir.");

    }

}

Bu programın çıktısı şudur:

    Ankara Türkiye’nin başkentidir.

Çok basit görünen bu programın açıklanması zor değilse bile, çok uzundur ve java programının bir çok özeliklerini taşır. Bir java programının (sınıfının) nasıl çalıştığını göstereceği için bu uzun açıklamaya girmekte yarar görüyoruz. Java'ya yeni başlayan öğrenciler bu açıklamayı anlayamazlarsa üzülmesinler. Ama dersler ilerledikten sonra, geri dönüp mutlaka okumalı ve her satırını anlamalıdırlar. Çünkü, bu açıklamaları kavrayamayan öğrenci, javayı henüz kavrayamamış demektir.

1.  Satır: Her java programı bir sınıf (class) dır. O nedenle, hepsi class anahtar sözcüğünü izleyen bir ad ile başlar.  (örneğimiz için  class Jprog01 dir). Bazan  class anahtar sözcüğünün önüne belirteçler gelebilir. Onları ileride yeri geldikçe açıklayacağız.

2.  Satır: Java sınıfının tanımı { } parantezi içinde yer alan ana blok içine konur. 2-inci satırda açılan { parantezi 7-inci satırda kapanmaktadır. Bu ana bloktur. Bu blokun içine başka bloklar girebilir. Her blokun kendine özgü { } parantezleri vardır. Açılan her parantez kapatılmalıdır. İç-içe bloklar oluşturulduğunda, parantezler en içten başlayarak dışa doğru karşılıklı olarak eşleşirler. Örnekteki 3-üncü satırda başlayan iç blok 6-ıncı satırda bitmektedir. 3 ile 6-ıncı satırlarda yer alan { } parantezler birbirini eşler ve bir iç-blok oluşturur. 2-inci ve 7-inci satırlardaki { } parantezleri birbirine eştir ve en dış bloku (ana blok) oluşturur. Bir sınıf içinde istenildiği kadar iç-içe bloklar oluşturulabileceği gibi, birbirinin içinde olmayan bloklar da oluşturulabilir. Ama bütün bloklar, sınıfın sınırlarını belirleyen ana blok içinde olmak zorundadır.

3.  Satır:  Bu satır main() adlı bir metot (fonksiyon) tanımlamaktadır. public saklı sözcüğü, bir erişim belirtkesidir, bu metoda her sınıfın ulaşabileceğini belirtir. Gündelik konuşmamızdaki halka açık deyimine benzer bir işleve sahiptir. Herkes ona erişebilir.  static saklı sözcüğünün java'da çok işlevsel bir anlamı vardır. Şimdilik, bu belirtkenin, main() metodu için ana bellekte bir yer ayırdığını ve onun başka bir kopyasının yapılmasına izin vermediğini söylemekle yetinelim.  static saklı sözcüğünün yaptığı işin ayrıntılarını zamanla daha iyi kavrayacağız.  void saklı sözcüğü  main() metodunun (fonksiyonunun) değer bölgesinin olmadığını (boş küme) belirtir. Bu metot bir iş yapar, ama bir değer almaz. Bazı java metotlarının değer bölgesi vardır, bazılarının yoktur (boştur). Son olarak  main(String[] args) simgelerini açıklayalım. Her dilde olduğu gibi, java'da da sınıfın her üyesinin bir adı vardır.  3-üncü satır bir metot tanımlamaktadır. Bu metot elbette sınıfın bir üyesi olmaktadır ve bir adı olmalıdır. Bu metodun adı  main()'dir. Java'da bütün uygulama programları bir main() metodu ile çalışır. Her uygulama programında yalnızca bir tane main() metodu vardır ve daima 3-üncü satırdaki gibi yazılır. Tabii, her uygulama programı farklı işler yapacağından, hepsinin main() metodunun tanımı farklı olacaktır. Bu metodun tanımı main(){...}'blokuna ait {...}bloku içinde yapılır. Ama bütün main() metotlarının adları, nitelemleri ve parametre türü hepsinde aynıdır. Parantez içindeki  String[] args ifadesi bir değişken (parametre) bildirimidir. Metotların değişkenlerine parametre diyoruz. Böylece, bu değişkenleri sınıf ve blok içlerinde tanımlanan öteki değişkenlerden ayırmamız kolaylaşıyor. Değişkenin adı args , tipi ise String[]'dir. String java'da metin (text) işlemlerini yapmamızı sağlayan kullanışlı bir sınıf (class) dır. Bununla çok sık karşılaşacak ve onun bir çok hünerini zamanla öğreneceğiz. Şimdilik, Pascal ve C gibi dillerde karakter arraylari olarak yapılan metin (string) işlemlerini java'da String sınıfı ile yaptığımızı söyleyelim. String 'in sonuna [] köşeli parantezleri eklenince, elde edilen String[] simgesi, öğeleri  String tipinden olan bir dizi (array) belirtir.  Demek ki  args değişkeni öğeleri String tipinden olan bir array'dir (dizi).  Bunu daha çok açıklamak için, gerekli ön bilgilere sahip olmayı sabırla beklemeliyiz.   

4.  Satır:   Bu satırda  metodunun bloku {  parantezi ile başlamaktadır. Blok 6-ıncı satırda bitmektedir, bir iç bloktur. Bu iç blokta main metodunun tanımı yapılmaktadır.

5.  Satır: Bu satırın açıklanması için gerekli olan bilgileri java API dökümanlarından alabiliriz. Dökümanlarda java.lang paketinin içine bakarsak, System sınıfını (class) görebiliriz.

java.lang.Object

  java.lang.System

 

public final class System

extends Object

 

Dördüncü satırdaki public erişim belirteci System sınıfına her sınıfın erişebileceğini söylüyor. final nitelemesi ise, System sınıfının değiştirilemez (sabit) olduğunu söylüyor. extends Object ifadesi, System sınıfının Object sınıfından üretildiğini söylüyor; yani onun bir alt sınıfı olduğunu söylüyor. Object sınıfı bütün sınıfların üst-sınıfıdır.

Açıklamayı sürdürebilmek için, önce programın üçüncü satırında yer alan out ‘un ne olduğunu sonra da  println() metodunun tanımını bulmalıyız. 

Bunun için, API dökümanında java.lang paketi içinde yer alan System sınıfının değişkenlerine (attributes, fields) bakalım. System sınıfının PrintStream tipinden err ve out adlı iki değişkeni ile InputStream  tipinden in adlı bir değişkeni olduğunu görürüz. Adlarından da anlaşılacağı üzere, out değişkeni çıkış akımlarını, in değişkeni giriş akımlarını düzenler. err değişkeni ise, oluşabilecek hataları bildirir. API dökümanında out değişkenine tıklarsak, onun açık tanımını görebiliriz: 

public static final PrintStream out

Bu değişkenin aldığı public static final nitemleri , sırasıyla, şu anlamlara gelir:

public  out         değişkeni herkese açıktır. Programdaki her class bu değişkene ulaşabilir.

static  out         değişkeni için sistem ana bellekte bir yer açmıştır ve kullanılmaya hazırdır. Onu ayrıca programcının yaratmasına gerek yoktur. Doğrudan kullanılabilir durumdadır.

final  out    değişkeni sabittir, program içinde değeri değiştirilemez. 

System sınıfı içindeki out değişkeni static olduğundan, ona erişmek için

System.out                                              (1)

yazmak yeterlidir. Son olarak, programın üçüncü satırında yer alan out.println() metodunun tanımını aramalıyız.  Bunun için out değişkeninin ait olduğu PrintStream  sınıfını bulmalıyız. Bu sınıf java.io paketi içindedir ve hiyerarşik tanımı şöyledir:

java.lang.Object

  java.io.OutputStream

      java.io.FilterOutputStream

          java.io.PrintStream

 

Bu hiyerarşiden anlaşılacak şey, PrintStream  sınıfının Object sınıfından türetilen üçüncü aşamadaki bir alt-sınıf olduğudur. Bu sınıfın tanımı, hiyerarşik olarak şu tanımlardan çıkar:

public abstract class OutputStream

extends Object

 

public class FilterOutputStream

extends OutputStream

public class PrintStream

extends FilterOutputStream

API dökümanında  PrintStream sınıfının metotları arasında

        public void println(String x)

adlı bir metodu olduğunu görürüz. Bu metot, parametre olarak aldığı bir x metnini standart çıkışa (ekran) yazar. Parametre olarak çift tırnak (" ") içinde yazdığımız

"Ankara Türkiye\’nin başkentidir."

metni, java için String tipinden bir parametredir (String tipinden x değişkenidir). println() metodunun görevi bu parametreyi (nesneyi) byte akışı biçiminde standart çıkışa (ekran) göndermektir.  Böylece,

System.out.println("Ankara Türkiye\’nin başkentidir.")

deyimi bizim için bir anlam kazanmıştır. 

Sınıf  Bildirimi

Bu kesimde Java dilinde sınıf bildirimini, new operatörü ile sınıftan nesne yaratmayı ve sınıfın öğelerine erişimin nasıl olduğunu öğreneceğiz.

Java dilinde sınıf bildirimi (tanımı) çok kolaydır. Örneğin,

class Otomobil

{

 

}

deyimi bir sınıf bildirir. Burada class anahtar sözcüktür. Otomobil sınıfın adıdır. İstediğiniz adı verebilirsiniz. { } blokuna  sınıfın gövdesi ya da sınıf bloku diyeceğiz.

Java  derleyicisi, kaynak programdaki birden çok ardışık boşluk, tab ve satırbaşını tek bir boşluk olarak algılar. Dolayısıyla, yukarıdaki deyimi

class    Otomobil { }      ;

biçiminde de yazabiliriz. Ama kaynak programımızın kolay okunup algılanabilmesi için her deyimi ayrı satıra yazmaya, iç-içe blokları tab ile görünür biçime getirmeye özen göstereceğiz.

Sınıf gövdesi yukarıdaki gibi boş olabilir. Ama boş gövdeli sınıfın bir işe yaramayacağı açıktır. Onun işe yarar olabilmesi için içine nitelem (değişken) ve metot (fonksiyon) bildirimleri (tanımları) koyacağız. Onlara sınıfın öğeleri (class member) denilir.

Şimdi  Otomobil sınıfımızı işe yarar hale getirmeye çalışalım. Bir Otomobil ile ilgili bilgileri tutan değişkenleri ve o bilgilerle işlem yapan fonksiyonları Otomobil sınıfının gövdesine yerleştireceğiz. Örneğin, bir Otomobil’in markası, modeli, tipi, silindirHacmi, rengi, döşemeleri, motor_numarası, şasi_numarası, göstergeleri, vergisi, fiyatı, vb bilgilerden istediklerimizi sınıfın gövdesine koyabiliriz. Ayrıca yürürlükte olan mali mevzuata göre otomobilin vergisini, sigorta bedelini ve sigorta primlerini hesaplayan metotları tanımlayıp { } sınıf bloku içine yerleştirebiliriz. Konunun basitliğini korumak için, başlangıçta yalnızca markasını belirten marka nitelemi ve silindir hacmini tutan silindirHacmi nitelemi (değişken) ile yıllık taşıt vergisini hesaplayan vergiHesapla() metodunu içeren bir sınıf bildirimi yapalım. Her otomobilin markası ve silindir hacmi belirlidir. Marka Ferrari, ford, renault, hyundai gibi bir addır. Java dilinde string (metin) veri tipindendir. Silindir hacimleri ve vergiler için tam sayılar kullanılır. Java dilinde tamsayılar byte, short, int veya long tipinden olabilir. Silindir hacmi için short tipini, vergi için int tipini seçelim. Sınıfımız şöyle olacaktır.

Otomobil.java

class Otomobil

{

    public String  marka;

    public short silindirHacmi  ;

    public int  vergiHesapla(short sHacmi)

    {

         int  vergi = 0;

         sHacmi = silindirHacmi;

         if (sHacmi <= 1300) vergi = 405 ;

    else

         if (sHacmi <= 1600) vergi = 648 ;

    else

         if (sHacmi <= 1800) vergi = 1140 ;

    else

         if (sHacmi <= 2000) vergi = 1793 ;

    else

         if (sHacmi <= 2500) vergi = 2690 ;

    else

         if (sHacmi <= 3000) vergi = 3750 ;

    else

         if (sHacmi <= 3500) vergi = 5711 ;

    else

         if (sHacmi <= 4000) vergi = 8976 ;

    else

         if (sHacmi > 4000) vergi = 14689 ;

    return vergi;

    }

}

 

 

{ } sınıf bloku içindeki ilk iki deyim 

    public String  marka ;

    public short silindirHacmi ;

birer nitelemedir (attribute). Üçüncü deyim olan

    public int  vergiHesapla(short sHacmi)

{

     

    };

ise bir metottur. Metodun adına son takı olarak ( ) parantezlerinin eklendiğine ve onun içine int tipinden sHacmi adlı metot parametresinin yazıldığına dikkat ediniz. Metot bildirimindeki { } metot bloku içine, Maliye Bakanlığının 2010 yılında uyguladığı taşıt vergisi hesabını yapan deyimler konulmuştur. Bu deyimlerin ne yaptığını sözcük anlamlarından algılayabilirsiniz. Gerçekten bu metot aşağıdaki tabloya uyan vergi hesabı yapmaktadır:

Motor Silindir Hacmi (cm3)

2010 yılı Motorlu Taşıt Vergisi (TL)

1301   den küçük

405

1301 -1600

648

1601 – 1800

1140

1801 – 2000

1793

2001 – 2500

2690

2501 – 3000

3750

3001 – 3500

5711

3501 – 4000

8976

4001 – 10000

14689

2010 yılı motorlu taşıt vergileri

 

Bu blok içinde son deyim olan

return vergi;

deyimi, metot bloku içinde hesaplanan vergi değerini fonksiyonun değeri olarak belirler. Fonksiyon çağrıldığında, hesaplanan bu değeri verecektir. Şimdilik bunların nasıl olduğunu fazla düşünmeyiniz; çünkü o deyimleri ileride daha ayrıntılı göreceğiz.  Java dilinde deyimler birbirlerinden noktalı virgül (;) ile ayrılır. Bu sınıfın içine gerekli görülecek başka öğeleri (nitelem, metot) koymak  istersek, her yeni nitelem ve her yeni metot için gerekli deyimleri yukarıdakilere benzer biçimde yazmalıyız.

Şimdi yukarıdaki Otomobil sınıfını NotePad gibi basit bir editörle yazıp, örneğin, C:\jnp  adlı dizine Otomobil.java adıyla kaydediniz. Sonra DOS Komut İstemini (Command Prompt) açınız. Komut istemi başka sürücüde ise, C: yazıp Entere basınız.  C: sürücüsüne geçmiş olursunuz. cd \jnp yazıp Entere basınız. Komut istemi C:\jnp dizinine geçecektir. İkinci Bölümde anlatıldığı gibi çevre değişkenlerini ayarladığınızı varsayıyoruz. Eğer o ayarları yapmamış iseniz, hemen onları yapınız.

Otomobil sınıfını derlemek için DOS Komut İsteminde

javac  Otomobil.java                               (9)

yazıp Entere basmanız yetecektir. Java derleyicisi javac.exe, Otomobil.java adıyla kayıtlı kaynak dosyayı (sınıfı) derleyecek ve java byte koduna dönüştürecektir. Böyle olduğunu  C:\jnp  adlı dizinde yaratılan Otomobil.class adlı dosyadan anlayabilirsiniz. Eğer, kaynak programınızda bir hata varsa, java derleyicisi hatanın hangi satırda olduğunu size söyleyecektir. O durumda, kaynak programı düzeltmeniz (debug) gerekir. Byte koduna dönüşmüş Otomobil.class programını yürütmek (icra) için

java Otomobil                                      (10)

komutunu vermemiz gerektiğini anımsayınız. Ancak, bu komut bize

Error: Main method not found in class Otomobil, please define the main method as:

   public static void main(String[] args)

java.lang.RuntimeException: Main method not found in Otomobil

        at sun.launcher.LauncherHelper.signatureDiagnostic(LauncherHelper.java:214)

        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:202)

Exception in thread "main"

 

hata iletisini gönderir. Bu ileti, sözkonusu sınıfı çalıştıracak bir  main() metodu olmadığını söylüyor. Bu eksikliği bundan sonraki kesimde gidereceğiz.

Sınıf ve Nesne

Önce yapacağımız işin gerçek yaşamda neye benzediğini bir örnekle açıklamaya çalışalım. Bir otomobil fabrikası önce otomobilin mimari tasarımını yapar. O tasarım otomobilin motorunu, şasisini, kaportasını, döşemelerini, göstergelerini, tekerlerini, aktarma organlarını vb niteliklerini belirler. Bunları Java dilinde birer nitelem (değişken) ile belirtiriz. Tasarımda otomobilin hızını, motor sıcaklığını, kabin ısısını vb ölçecek cihazlar da yer alır. Bunları Java dilinde birer metot (fonksiyon) ile belirtiriz.  Bu tasarım, lüks bir otomobil için uzmanların tasarladığı ve çizdiği çok fazla öğe içeren bir mimari proje olabileceği gibi, daha az öğe içeren bir tasarım da olabilir. İster çok, ister az öğe içersin, önce ortada bir tasarım vardır. İster kağıt üzerine çizilsin, ister birisinin kafasında tasarlanmış olsun, o tasarım somut bir nesne, bir otomobil değildir. O tasarımın içine girip oturamaz, direksiyona geçip süremeyiz. O işi yapabilmemiz için, her şeyden önce, tasarımı yapılan otomobilin inşa edilmesi gerekir. 

Java dilinde bir sınıf bir tasarımdır. Onu somut olarak göremez, kullanamayız. Onun için sınıftan somut nesne(ler) kurmalıyız. Otomobil’in mimari tasarımını, Java  dilinde bir sınıfa (class) benzetebiliriz. Mimari tasarımdan inşa edilen somut bir otomobili de Java dilinde bir sınıftan yaratılan bir nesneye (object) benzetebiliriz. Bir mimari tasarımdan aynı tipte istediğimiz kadar otomobil inşa edebiliriz. Örnekse, bir fabrikanın belli bir üretim bandından çıkan otomobillerin hepsi bir tek tasarımdan üretilir. Ancak, o otomobillerin her birisi kendi başına somut bir varlıktır, herbiri uzayda farklı bir yer işgal eder. O otomobiller bazı ortak özeliklere sahiptirler, ama her bir otomobilin markası, modeli, boyası, motor_numarası, şasi_numarası, trariğe çıkınca plakası, sahibi vardır. Bu öznitelikler, bir otomobili başkalarından ayırır.

Benzer olarak, bir sınıftan istediğimiz kadar nesne kurabiliriz.  O nesnelerin bazı özelikleri ortaktır, ama daima birini ötekinden ayıran öznitelikleri vardır. 

new Operatörü ile Nesne Yaratmak

Şimdi Otomobil sınıfından bir nesne yaratacak (kuracak) olan deyimleri açıklayalım.

Otomobil  oto_1 ;                                    (1)

(1) deyimi, derleyiciye Otomobil sınıfına ait bir nesnenin ana bellekteki adresini gösterecek bir referans bildirimidir. Referansın adı oto_1 dir. Çeşitli kaynaklarda buna pointer, gösterici, işaretçi, referans gibi adlar verilir. C/C++ dilindeki pointer teriminin karşılığıdır. Java dili, C/C++ dillerindeki gibi pointerin programcı tarafından kullanılmasına izin vermez; o nedenle pointer yerine   'reference'  sözcüğünü kullanır. Biz de ona uyarak 'referans' veya 'işaretçi' sözcüklerini eşanlamlı olarak kullanacağız. Örneğimizdeki oto_1 referansının tek ve önemli görevi Otomobil sınıfına ait bir nesnenin bellekteki adresini işaret etmektir.

                              

(1) bildirimi yapıldığında, henüz işaret edilecek somut bir Otomobil yoktur. Dolayısıyla, o aşamada, oto_1  işaretçisi bellekte hiç bir yeri işaret etmez. Hiç bir yeri yeri işaret etmediğini belirtmek için, işaretçiye null işaret ediyor deriz.

Şimdi işaretçimizin işaret edeceği somut bir Otomobil inşa etmeliyiz. Somut bir Otomobil'i inşa etmek demek, ana bellekte Otomobil'in bütün öğelerine verilecek değerlerin sığacağı bir yeri (bellek adresi) tahsis etmek demektir. Bu yere Otomobil'in bir nesnesi (object, instance) denir. Yapılan bu işe de sınıfa ait bir nesne yaratmak (instantiate) denilir. Bellekte sınıfa ait bir nesneyi yaratırken, fabrikadaki ustabaşı kadar uğraşmayacağız. Şu basit deyimi yazmamız yetecektir.

Oto_1  =  new  Otomobil() ;                                 (2)

(2) deyimi ana bellekte aşağıdaki şeklin gösterdiği olayı gerçekleştirir. Elbette ana bellekte böyle geometrik şekiller oluşmuyor. Ama şekiller ve resimler soyut kavramları algılamamızı kolaylaştırır. O nedenle, şeklimize bakmaya devam edelim.

Otomobil sınıfının üç öğesini tanımlamıştık:  marka, silindirHacmi ve vergiHesabı. Bellekte yaratılan  nesneye bakarsak, orada marka, silindirHacmi ve vergiHesabı için ayrı ayrı yerler açılmış olduğunu görüyoruz. Bir sınıfa ait nesne (object)  yaratmak demek, ana bellekte sınıfa ait static olmayan bütün öğelere ana bellekte birer yer ayırmak demektir. Ayrılan bu yerlere başka değerler yazılamaz; o nesne bellekte durduğu sürece, yalnızca ait oldukları öğelerin değerleri girebilir. Bunu, bellekte yer kiralama gibi düşünebiliriz. Kiralanan yerde ancak kiracı oturabilir. Henüz yaratılan nesnenin öğelerine değer atanmamıştır; yani kiracı henüz taşınmamıştır. Bize göre marka,  silindirHacmi ve vergiHesabı() öğeleri için ayrılan hücreler boştur. Ama, Java   bellekte yaratılan her sınıf değişkenine kendiliğinden bir öndeğer (default value) atar. Öndeğer veri tipine göre değişir. Aşağıdaki tablo başlıca veri tiplerine atanan öndeğerleri göstermektedir.

Veri Tipi

Değer

byte, short, Int, long

0

float, double

0,0

boolean

False

char

'\0' (null karakter)

string

" " (boş string)

nesne (object)

null

Veri tiplerinin öndeğerleri (default values)

 

marka değişkeni String veri tipinden olduğu için öndeğeri boş string  (“ “) dir. silindirHacmi değişkeni short veri tipinden, vergiHesabı() metodu int veri tipinden olduğu için, her ikisi için  öndeğerler 0 dır. Olayın basitliğini korumak için, öndeğerleri şekle yazmıyoruz. Zaten, birazdan onların gerçek değerlerini atayacağız.  

İstersek (1) ve (2) deyimlerini birleştirip, iki işi tek adımda yapabiliriz. 

Otomobil oto_1 = new Otomobil();            (3)

 

(3) nolu deyim, ilk iki deyimin yaptıklarına denk iş yapar.

İçinde new anahtar sözcüğü olan bu tür deyimlere nesne yaratıcı (instantiate) diyoruz. Sözdizimine bakınca ne yaptığını anlamak mümkündür. Bu deyimde yer alan sözcüklerin işlevlerini soldan sağa doğru şöyle açıklayabiliriz:

 

Otomobil

Yukarıda tasarladığımız Otomobil sınıfıdır;

Oto_1

Tasarımdan üretilecek olan somut otomobilin ana bellekteki adresini işaret eder. Bu nedenle, oto_1'i işaret ettiği nesnenin (otomobilin) adıymış gibi de düşünebiliriz. Bundan böyle işaretçi (referans) adı ile işaret ettiği nesneyi aynı adla anacağız. Söylemlerimizde, kastedilen şeyin  referans mı, nesne mi olduğu belli olacaktır. Ancak çok gerektiğinde, referans oluşuna ya da nesne oluşuna vurgu yapacağız.

=

Atama operatörü

new

Sınıftan nesne yaratan operatör (nesne yaratıcı) 

Otomobil()

Yaratılacak nesnenin tasarımı

 

Bir sınıftan bir nesne yaratan genel sözdizimi (syntax) şöyledir:

sınıf_adı    nesne_adı = new     sınıf_adı();      (4)

 

main()  metodu

Java uygulamalarının mutlaka  main() metodu (fonksiyon) tarafından başlatıldığını söylemiştik.

    public static void main(String[] args)

    {

    }

metoduna yaptırmak istediğimiz ilk iş Otomobil sınıfına ait bir nesne yaratan (3) deyimini çalıştırmasıdır. Bunu yaptırmak için main() metodunun { } bloku içine (3) deyimini yazmak yetecektir. Bunu yapınca  main() metodu şöyle olur:

public static void main(String[] args)

    {

Otomobil oto_1 = new  Otomobil();

    }

main() metodu kendi başına ortalıkta duramaz; o da bütün Java öğeleri gibi, mutlaka bir sınıf içinde olmalıdır. Onu istersek mevcut Otomobil sınıfı içine koyabiliriz, istersek ayrı bir sınıf yaratıp, o sınıfın içine koyabiliriz.

Nesne yönelimli programlamada, mümkün olduğu kadar farklı işleri farklı sınıflar içinde yapmayı tercih ederiz. Böylece hem programımız kolay yönetilir, hem bir programda yaratılan bir sınıf gerektiğinde benzer işi yapsın diye başka programlara monte edilebilir. Buna modüler programlama diyoruz.

İkinci seçeneğe uyalım; yani  main() metodunu içeren Uygulama adlı yeni bir sınıf yaratalım:

class Uygulama

{

}

Tabii, amacımız bu sınıfın gövdesine  yukarıda yazdığımız main() metodunu yerleştirmektir:

class Uygulama

{

    public static void main(String[] args)

    {

        Otomobil oto_1 = new  Otomobil();

    }

}

 Şu ana kadar iki sınıf tanımladık. Otomobil adlı sınıf bir otomobilin markasını, silindir_hacmini ve taşıt_vergisini  tutacak üç öğeye sahiptir. Ama Otomobil sınıfı bir tasarımdır, kendi başına bir iş yapamaz. Onu işlevsel hale getirecek olan Uygulama sınıfıdır.

Uygulama adlı sınıfta main() metodu tanımlıdır ve bu metot Otomobil sınıfının bir nesnesini yaratacak olan nesne kurucuyu çalıştırmaktadır. Nesne yaratıldıktan sonra, onun öğeleriyle ilgili işleri yapmaya başlayabiliriz.

Nesnenin Öğelerine Erişim

Yarattığımız oto_1 nesnesi bellekte duruyor. Onunla ilgili işler yapabilmemiz için, onun öğelerine erişebilmeliyiz; yani nesnenin öğelerine değer atayabilmeli, atanan değerlerle işlem yapabilmeli ve onların değerlerini tekrar okuyabilmeliyiz. Öyleyse ilk işimiz oto_1  nesnenin öğelerine (nitelem, metot) birer değer atamak olmalıdır. oto_1 nesnesi içindeki marka ve silindirHacmi bileşenleri birer nitelemdir. Dolayısıyla, değişkenlerle yapılan her iş ve işlem, onlara da uygulanabilecektir. Birincisi string tipinden, ikincisi short veri tipinden olduğuna göre, onlara tiplerine uygun birer değer atayabiliriz:

oto_1.marka = “Ford” ;                             (5)

oto_1.silindirHacmi = 2000 ;                       (6)

Bu atamalardan sonra  oto_1 nesnesinin ana bellekteki durumu aşağıdaki şekildeki gibidir.

Son öğe olan vergiHesapla() bir metottur. Ona vergiyi hesaplatmak için short veri tipinden olan sHacmi adlı parametresine bir değer atayarak, metodu çağıracağız. Bu iş oldukça kolaydır:

Oto_1.vergiHesapla(2000) ;                         (7)

deyimi istenen işi yapacaktır. Bu metodun tanımına bakarsak, silindir hacmi 2000 olan otomobilin vergisinin  1793 TL olarak hesaplanacağını görürüz. Başka bir deyişle (7) deyimi çalışınca,

vergiHesapla(2000) == 1793 ;                       (8)

olacaktır.

Sonuncu değer olan (8) değeri ait olduğu bileşene yerleşince, oto_1 nesnesinin ana bellekteki durumu aşağıdaki şekilde görüldüğü gibi olacaktır.

Burada hemen aklımıza takılması gereken bir durum var. Java dilinde her öğe ve her deyim mutlaka bir sınıfa ait olmalıdır. Ayrıca (5), (6) ve (7) deyimleri yürütülmesi (icra edilmesi) gereken deyimlerdir. Dolayısıyla onları ya main() metodunun içine koyacağız ya da başka sınıfa konulurlarsa, o sınıftan main() vasıtasıyla çağrılacaklardır.  Şimdi bu sorunu nasıl çözeceğimize bakalım. Kısa yolu seçerek (5), (6) ve (7) deyimlerini main() metodu içine koyalım. Hemen belirtmeliyiz ki, bu kısa yol her zaman en iyi yol değildir. Çünkü, Otomobil sınıfından 100 farklı nesne yaratırsak, her birisi için benzer işi yapmak istediğimizde,  main() metodu içine benzer deyimleri yüzer kez yazmak zorunda kalacağız. Bunun üstesinden nasıl gelineceğini ileride göreceğiz. Şimdilik, (5), (6) ve (7) deyimlerini  main() metodu içine koyduğumuzda Uygulama sınıfının alacağı biçimi görelim:

class Uygulama

{

    public static void main(String[] args)

    {

        Otomobil oto_1 = new  Otomobil();

         oto_1.marka = "Ford" ;

         oto_1.silindirHacmi = 2000 ;

         System.out.println(oto_1.vergiHesapla(oto_1.silindirHacmi));

    }

}

Nesne içindeki değişkenlere yapılan atama deyimlerinde, değişken adlarının önüne nesne adını yazıyoruz. Bunun nedeni açıktır. Diyelim ki,

Otomobil oto_2 = new Otomobil();

nesne kurucusunu kullanarak oto_2  adlı başka bir nesne yarattık. O zaman, bellekte iki nesne ve her birinde marka, silindirHacmi, vergiHesapla() bileşenleri ayrı ayrı yer alacaktır.

O durumda, yalnızca,

marka = “Ferrari” ;

silindirHacmi = 4000;

atama deyimlerini yazarsak, derleyici, bu değerleri oto_1 ya da oto_2 nesnelerinden (otomobillerden) hangisine atayacağını bilemez, hata iletisi verir.  Bu nedenle, önce nesneyi, sonra değişkeni yazıyoruz. Tabii, nesne adı ile değişken adı arasına (.) koymayı unutmuyoruz.

oto_1.marka = “Ford” ;

oto_1.silindirHacmi = 2000;

deyimleri, oto_1 nesnesi içindeki değişkenlerin adıdır. Benzer şekilde,

oto_2.marka = “Ferrari” ;

oto_2.silindirHacmi = 4000;

deyimleri, oto_2 nesnesi içindeki değişkenlerin adıdır. Görüldüğü gibi, farklı nesneler içinde aynı adı taşıyan değişkenlere farklı değerler atanabiliyor. Değişken önüne konulan nesne adları, aynı adlı değişkenleri birbirlerinden ayırır. Zaten, bir sınıftan istediğimiz kadar farklı nesne yaratabiliyor olmamızın nedeni budur. Her nesne kendi başına bir varlıktır.

Şimdi Uygulama sınıfını derleyip çalıştıralım. Otomobil sınıfı için yaptıklarımızı Uygulama sınıfı için de yapacağız. Önce, Uygulama.java adıyla programı C:\jprg\src dizinine kaydedelim. Sonra DOS Komut İstemci penceresini açıp

javac Uygulama.java

komutunu yazıp Entere basalım. Uygulama.class sınıfı yaratılacaktır.  Şimdi

java Uygulama

komutunu verirsek, ekrana

1793

sayısının yazıldığını göreceğiz. 

Şimdi başka bir yöntemler deneyelim.

Yöntem 1

 Otomobil sınıfı ile Uygulama sınıfını alt alta yazıp tek program haline getirelim ve programın adına gene Otomobil.java diyelim.

Otomobil.java

 

class Otomobil

{

    public String  marka;

    public short silindirHacmi  ;

    public int  vergiHesapla(short sHacmi)

    {

         int  vergi = 0;

         sHacmi = silindirHacmi;

         if (sHacmi <= 1300) vergi = 405 ;

    else

         if (sHacmi <= 1600) vergi = 648 ;

    else

         if (sHacmi <= 1800) vergi = 1140 ;

    else

         if (sHacmi <= 2000) vergi = 1793 ;

    else

         if (sHacmi <= 2500) vergi = 2690 ;

    else

         if (sHacmi <= 3000) vergi = 3750 ;

    else

         if (sHacmi <= 3500) vergi = 5711 ;

    else

         if (sHacmi <= 4000) vergi = 8976 ;

    else

         if (sHacmi > 4000) vergi = 14689 ;

    return vergi;

    }

}

 

class Uygulama

{

    public static void main(String[] args)

    {

        Otomobil oto_1 = new  Otomobil();

         oto_1.marka = "Ford" ;

         oto_1.silindirHacmi = 2000 ;

         System.out.println(oto_1.vergiHesapla(oto_1.silindirHacmi));

    }

}

 

Bu programı

javac  Otomobil.java

yazıp derlemek istersek hatasız derlenir. Gerçekten c:\jprg\src dizinimizin içinde Otomobil.class ve Uygulama.class adlarıyla her iki sınıfa ait bytekod'ların yaratıldıklarını görebiliriz. Çünkü program içindeki bütün sınıflar derlenir ve kendi bytekodları yaratılır. Ancak

java Otomobil

yazıp yazıp yorumlamak istersek, aşağıdaki hata iletisini alırız.

C:\jprg\src>javac Otomobil.java

C:\jprg\src>java Otomobil

Error: Main method not found in class Otomobil, please define the main method as :

   public static void main(String[] args)

Exception in thread "main" java.lang.RuntimeException: Main method not found in Otomobil

        at sun.launcher.LauncherHelper.signatureDiagnostic(LauncherHelper.java:214)

        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:202)

C:\jprg\src>

Bunun nedeni, Otomobil sınıfı içinde main() metodunun  olmayışıdır. Öyleyse, yukarıda yaptığımız gibi

java Uygulama

komutunu verirsek, ekrana

1793

sayısının yazıldığını göreceğiz. 

Yöntem 2

Önce içindeki bütün dosyaları silerek c:\jprg\src dizinini temizleyelim. Sonra Otomobil sınıfı ile Uygulama sınıfını ayrı ayrı programlar haline getirelim ve Otomobil.java ve Uygulama.java adlarıyla kaydedelim.

Şimdi Otomobil.java programını derlemeden, yalnızca Uygulama.java programını derleyelim:

Javac  Uygulama.java

 Dizinimizin içine bakarsak her iki sınıfın derlendiğini ve bytekodlarının yaratıldığını görebiliriz. Gene, yukarıda yaptığımız gibi

java Uygulama

komutunu verirsek, ekrana

1793

sayısının yazıldığını göreceğiz. 

 Özellikle derlemediğimiz Otomobil.java programının kendiliğinden derleniyor olmasının nedeni, Uygulama sınıfından onun çağrılıyor oluşudur.

Uyarı

Erişimi kısıtlanmayan sınıflar aynı program içindeki başka sınıflar tarafından çağrılabilirler ve bytekodları yaratılabilir. Erişim kısıtlarını ileride göreceğiz.