Geçtiğim hafta boyunca, Java ile ilgili kaynakları
ve dökümanları incelemeye devam ettim. Her programlama dili için olmassa olmaz
gerekli olan bir takım temel bilgileri araştırıyordum. Bunlar bir programcı
için, dilin temel kullanımında ihtiyacı olduğu materyallerdir. Genelde her
programlama dili için bunlar gereklidir. Ancak elbette bunlar programlama
dilleri arasında farklılık gösterebilir. Bahsettiğim konu, değişkenler, koşullu
ifadeler ve döngüler. Bu kahve molasında bunları işlemeye çalışacağım.
Java programlama dilinde değişkenlerin neler
olduğunu bir tablo halinde hazırladım. Genelde programlama dilllerinde bu tip
değişken tipleri hep tablolar halinde sunulur. Değişkenleri sıkça kullandıkça,
bunların alt sınır, üst sınır ve alan büyüklükleri gibi bilgileri zamanla
unutabiliriz. Şahsen ben hep unuturum. Ancak programlarımızı hazırlarken nerede
en uygun değişken kullanılır bunuda bilmek isteriz. Ben oldum olası bu tip
tabloları ezberleyemem. Zaten ezberleme taraftarı değilim. O nedenle bir sürü
not defterim vardır ve taşıdığım çanta genelde ağır olur. Java dilinde
kullanılan değişkenler içinde aynı şeyleri hissediyorum. Sanıyorumki bir tablo
hazırlayacağım ve bunun güzel bir karton baskısını yanımda taşıyacağım.
Esasen Java dilinde, değişkenler, temel veri
tipleri olarak anılırlar. Bu anlamda Java'da iki veri tipi olduğunu
söyleyebiliriz. Değişkenlerin tanımanması için kullanılan Temel Veri
Tipleri(Primitive) ve nesnelerin tanımlanması için kullanılan Referans Tipleri.
C# dilinde'de bu böyledir zaten. Temel veri tipinden elde edilen değişkenler,
bellekte yığın adı verilen bir bölgede tutulurlar. Oysa referans tiplerin
tutuluş şekli daha farklı. Referans tipinden bir nesne, sahip olduğu üyülerin
tutulduğu bellek bölgesindeki alanların başlangıç adresine işaret ederki
bu tip nesneler yığında tutulurken, sahip oldukları içerik öbekte tululur. Java
programlama dilinde Temel Veri Türleri aşağıdaki tabloda olduğu gibidir.
|
Veri Tipi |
Alan Büyüklüğü |
Kategori |
| byte |
8 bit |
Tamsayı Tipleri |
| short |
16 bit |
| int |
32 bit |
| long |
64 bit |
| float |
32 bit |
Kesirli Sayı Tipleri |
| double |
64 bit |
| char |
16 bit |
Karakter Tipi |
| boolean |
- |
Mantıksal Tip (ture/false) |
Görüldüğü gibi temel veri tipleri bunlar. Aslında
bu tabloda birde alt aralık ve üst aralık bilgilerinin olması gerekiyordu. Ancak
bunlar genelde kaynaklarda üstsel bilgi olarak gösterilmiş. Gerçekte, bu veri
tiplerinin en üst ve en alt sınır bilgilerini küçük bir program kodu yazarakta
öğrenebilirim. İşte küçük programım.
|
public class Sinirlar
{
public static void main(String[] args)
{
System.out.println("Integer veri
tipi");
System.out.println("Integer alt sınır
:"+Integer.MAX_VALUE);
System.out.println("Integer ust sınır
:"+Integer.MIN_VALUE);
System.out.println("---");
System.out.println("Double veri
tipi");
System.out.println("Double alt sınır
:"+Double.MAX_VALUE);
System.out.println("Double ust sınır
:"+Double.MIN_VALUE);
System.out.println("---");
System.out.println("Float veri
tipi");
System.out.println("Float alt sınır
:"+Float.MAX_VALUE);
System.out.println("Float ust sınır
:"+Float.MIN_VALUE);
System.out.println("---");
System.out.println("Long veri tipi");
System.out.println("Long alt sınır
:"+Long.MAX_VALUE);
System.out.println("Long ust sınır
:"+Long.MIN_VALUE);
System.out.println("---");
System.out.println("Short veri
tipi");
System.out.println("Short alt sınır
:"+Short.MAX_VALUE);
System.out.println("Short ust sınır
:"+Short.MIN_VALUE);
System.out.println("---");
System.out.println("Byte veri tipi");
System.out.println("Byte alt sınır
:"+Byte.MAX_VALUE);
System.out.println("Byte ust sınır
:"+Byte.MIN_VALUE);
System.out.println("---");
}
} |
Şimdi yazdığım uygulamayı Java Derleyicisi ile
derliyorum. Hayret, her hangibir hata vermeden derledi. Daha sonrada programı
çalıştırıyorum. Sonuç gayet güzel oldu. Tam istediğim gibi, temel veri türlerine
ait alt ve üst sınır değerlerini elde etmeyi başardım. Bunu yaparken, bu
veri tipleri için Java içinde geliştirilmiş hazır sınıfları kullandım. Örneğin
int veri tipi için Integer sınıfını kullandım. Aslında tüm temel veri tipleri
için sınıflar mevcut. Tek yaptığım bu sınıfların MAX_VALUE ve MIN_VALUE
özelliklerinin değerlerini ekrana yazdırmak oldu. Bu kadar basit.

Burada aslında önemli bir noktada her veri tipi
için bir sınıfın var olması. Dolayısıyla bir değişken tanımlamasını iki şekilde
yapma imkanına sahibim. Örneğin aşağıdaki kod parçasında, int (integer-tamsayı)
veri tipinden iki değişkenin farklı şekillerde tanımlandığını görüyoruz.
|
public class DegTan
{
public static void main(String[] args)
{
int deger1=45;
Integer deger2=new Integer(50);
System.out.println("deger1 "+deger1);
System.out.println("deger2 "+deger2);
}
} |
Bu uygulamayı çalıştırdığımda aşağıdaki sonucu
elde ettim.

Sonuçta iki tane integer tipinde değişken
tanımlamıştım, ancak bu tanımlamalar arasında büyük farklar olduğuna inanıyorum.
Şimdi bunu araştırmam gerektiğini düşünüyorum. İlk başta gözüme çarpan, Integer
sınıfını kullanarak, integer tipte veriyi barındıran Deger2 isimli bir nesne
tanımlamamız. Bu durumda deger2 değerinin bellekte tutuluş şekli, deger1'den
farklı olmaktadır. Diğer yandan tüm temel veri tiplerinin birer sınıfı mevcuttur
ve bu sınıflar java.lang adı verilen bir pakette bulunmaktadırlar. Kaynaklardan
edindiğim bilgiye göre, burada adı geçen paket kavramının, C#'taki namespace (ad
alanı) kavramı ile aynı olduğu sonucuna vardım. Bu bilgiye ulaşmak benim için
internette biraz zaman harcamak ile gerçekleşti. Java için herzaman elimizin
altında bulunması gereken yardım dokumantasyonu olan Java 2 Platform Std.Ed.
Documentation v1.3.1' i
http://java.sun.com/j2se/1.3/docs.html
adresinden indirdim. Bu döküman yaklaşık olarak 22
megabyte'lık bir zip dosyası. Açıldığında, html dokumatasyonuna
ulaşabiliyorsunuz. Burada aradığım hemen her konuya ait bilgi mevcut. Ancak bazı
konuların yanında web pages yazılı. Bunlar internetten online olarak
bakılabilecek yada indirilebilecek adreslere işaret ediyor. İşte, java.lang
paketinin içeriğinede bu dokmantasyondan ulaştım. Temel veri tiplerine ait
sınıfları kullanarak pek çok fonksiyonu kullanma şansına sahip olduğumu gördüm.
Örneğin, ilk örneğimizde kullandığımız MAX_VALUE ve MIN_VALUE özellikleri gibi.
Yada integer değeri String'e dönüştürmek için kullanılan toString metodu ve daha
pek çok sayısız metod yer alıyor.
Bir diğer konuda, Java'daki temel veri türünden
değişkenler ile, referans türünden nesneler arasındaki temel farklılıklar.
Burada önemli olan konu, bu iki veri türününde bellekte farklı şekillerde
tutuluyor olması. Temel veri türünden olan değişkenler bellekte kendi isimleri
ile stack(yığın) adı verilen bölgede tutuluyorlar. Referans tipinden olan
nesneler ise, belleğin heap(öbek) adı verilen bölgesinde tutuluyor. Tanımlanan
referans tipindeki nesne, öbekte yer alan verilerine işaret eden ve yığında
tutulan bir değişken adına sahip oluyor. Bu kavramlar aslında karmaşık gibi
görünsede, nesneler arasındaki atamalarda önemli sonuçlar doğuruyor. Bunu
açıklamak için örnekler geliştirmek sanıyorumki en doğrusu olucaktır. Bu örnekte
yapmak istediğim, bir sınıf hazırlamak ve bu sınıf içinde değişkenler
tanımlamak. Sonra bu sınıfa ait bir nesne örneği yaratacak ve onu aynı
sınıftan türetilen başka bir sınıfa atayacağım. Bu işlemlerin gerçekleşmesi
halinde bellekteki oluşucak hareketleri ise şekille göstermeye çalışacağım.
Öncelikle örneğimi geliştireyim. İlk önce nesnelerim için kullanacağım sınıfımı
oluşturuyorum.
|
public class Sinifim
{
public int Deger1;
public int Deger2;
public Sinifim()
{
Deger1=0;
Deger2=0;
}
public Sinifim(int d1,int d2)
{
Deger1=d1;
Deger2=d2;
}
public void Yaz()
{
System.out.println("Deger1
:"+Deger1);
System.out.println("Deger2
:"+Deger2);
}
} |
Doğruyu söylemek gerekirse bu kodlar bize çok şey
söylüyor. Buradaki kodları tamamen C#'taki bilgimi kullanarak yazdım. Örneğin
sınıfı tanımlaması içinde, iki adet yapıcı (constructor) metod var. İlki Deger1
ve Deger2 isimli değişkenlerimize 0 değerlerini atıyan parametre almayan ve
sınıflar için varsayılan olarak kabul edilen yapıcı metodumuz. Diğeri yapıcı
metodumuz ise, bu değişkenlere aldığı parametre değerlerini atıyor. Birde Yaz
isimli bir metodumuz var. Bu metod ise, sınıfımızın Deger1 ve Deger2 isimli
integer değişkenlerini komut satırına yazdırıyor. Yapıcı metodların kullanımı,
sınıflar içindeki yeri gibi kavramlara şimdilik göz ucu ile bakmış oldum. Bu
kavramları ve daha fazlasını diğer kahve molalarımda incelemek istiyorum. Hazır
kahve demişken, kahvemi tazelesem iyi olucak sanırım. Gelelim diğer sınıfımıza.
Bu sınıfımız ise Sinifim sınıfı türünden nesneler üzerinde işlem yapmak için
kullanılıyor.
|
public class Program
{
public static void main(String args[])
{
Sinifim sf1=new Sinifim(10,20);
System.out.println("Sf1 nesnesi
için");
sf1.Yaz();
Sinifim sf2=new Sinifim();
System.out.println("Sf2 nesnesi
için");
sf2.Yaz();
System.out.println("sf1 nesnesi, Sf2
nesnesine aktarılıyor");
sf2=sf1;
sf2.Yaz();
}
} |
Bu kodlarla göstermek istediğim, referans tipleri
arasındaki atamalar sonucu oluşan ilişkidir. Program içinde, önce sf1 isimli bir
nesne örneği oluşturuluyor ve bu nesne içindeki Deger1 ve Deger2 integer
değişkenlerine 10 ile 20 değerleri atanıyor. Bu durumda belleğin durumunun
tasviri aşağıdakine benzer olucaktır.

Daha sonraki adımda ise, sf2 isimli başka bir
Sinifim nesne örneğini oluşturuyoruz. Bu kez nesneyi, Sinifim sınıfının
varsayılan yapıcı metodu ile oluşturuyor ve değişkenlerimize 0 değerini
atıyoruz. Bu halde, belleğin durumu şu şekilde olucaktır. Bellekte Sinifim,
sınıfı türünden iki adet nesne örneği mevcuttur.

Buraya kadar herşey normal. Ancak son adımda, sf1
, nesnesini sf2 nesnesine atıyoruz. İşte bu durumda olay biraz daha değişik bir
hal oluyor. Nitekim, sf1 nesnemiz artık sf2 nesnemize işaret ediyor.

Şimdi örneğimizi biraz değiştirelim ve Sinifim
sınıfına aşağıdaki DegerAta isimli metodu ve kod satırlarını ekleyelim.
|
public void DegerAta(int d1,int d2)
{
Deger1=d1;
Deger2=d2;
} |
Şimdide Program sınıfına aşağıdaki kod satırlarını
ekleyelim.
sf1.DegerAta(1,2);
sf1.Yaz();
sf2.Yaz(); |
Bu haldeyken, Program dosyasını çalıştırdığımda,
her iki nesne örneğininde aynı değerleri yazdırdığını görürüz. Bu şu anlama
geliyor. Atama işleminden sonra, öbekteki aynı bölgeyi işaret eden bu iki
nesneden birisinin içindeki değerlerin değiştirilmesi, diğer nesneninde aynı
değişiklikleri işaret etmesi anlamına gelmektedir. Sonuç aşağıdaki gibi
olucaktır.

Değişkenler arası atamalara gelince. Burada durum
referans tiplere göre daha farklı. Çünkü değişkenler oluşturuldukları isim ile
bellekte tutulur. Bu konuyuda bir örnek üzerinde incelemek taraftarıyım. Kısa ve
basit bir örnek bize yeterli olucaktır sanırım.
|
public class Program2
{
public static void main(String args[])
{
int Deger1=50;
int Deger2;
System.out.println("Deger1
:"+Deger1);
System.out.println("Deger2
:"+Deger2);
Deger2=Deger1;
System.out.println("Deger1
:"+Deger1);
System.out.println("Deger2
:"+Deger2);
Deger1=48;
System.out.println("Deger1
:"+Deger1);
System.out.println("Deger2
:"+Deger2);
}
} |
Kodu derlediğimde hiçte beklemediğim bir hata ile
karşılaştım.

Sanıyorumki hatanın sebebi Deger2 isimli integer
veri tipindeki değişkene ilk değer atamasını yapmamış olmamdı. Bu durumda kodun
bu satırını aşağıdaki gibi değiştirdim ve programın başarılı bir şekilde
derlendiğini gördüm.
int Deger2=0;
Şimdi uygulamamı çalıştırdığımda aşağıdaki
sonuçlar ile karşılaştım.

Şimdi kodu güzelce bir incelemem gerektiğini
düşünüyorum. Kahvemden bir yudum aldım ve Javaca düşünmeye başladım. İlk olarak
Deger1 ve Deger2 değişkenlerimizi tanımlıyor ve bunlara ilk değer olarak
sırasıyla 50 ve 0 değerlerini atıyoruz. Bu noktada belleğin durumu aşağıdaki
tasvirde olduğu gibi olucaktır. İki ayrı integer tipte değişken belleğin yığın
bölgesinde yerlerini almıştır.

Daha sonraki adımda ise, Deger1 değişkeninin
değeri Deger2'ye atanıyor. Bu durumda Deger1'in sahip olduğu 50 değeri, Deger2
değişkenine atanmış ve belleğin görünümü aşağıdaki şekilde olmuş oluyor.

Son adımda ise, Deger1' değişkenine 48 değerini
atıyoruz ve bellekteki Deger1 değişkeninin değerinin değişmesine neden oluyoruz.
Referans tiplerinde olduğu gibi, değişken tanımlamalarında, atamadan sonraki
değişiklikler, değişkenlerin birbirlerini etkilemesine neden olmamaktadır.

Sanıyorumki değişken tipleri ve referans tipleri
arasındaki farkları daha iyi anladık. Evet alet çantamızı doldurmaya devam
edelim. Bizim için gerekli olan diğer önemli ve hatta çok önemli unsurlar
koşullu ifadeler ve döngülerdir. Bu her programlama dili için böyledir.
Öncelikle karşılaştırma ifadelerine bir göz atmak istiyorum. Karşılaştırma
ifadelerimiz bana nedense çok tanıdık geliyor. If ve Switch. Bu ifadelerin
kullanımını örnekler ile incelemek taraftarıyım. Öncelikle if koşul ifadesini
incelemek istiyorum.
|
public class Kosullar
{
public static void main(String args[])
{
int Not;
Not=49;
if(Not>50)
{
System.out.println("Sınıfı geçtin...");
}
else if((Not>45)&&(Not<50))
{
System.out.println("Kanaat kullanmalımıyım bakayım?");
}
else
{
System.out.println("Sınıfta KALDIN?");
}
}
} |
Aslında her şey çok açık ve ortada. If koşulları
parantezler içinde yazılır ve karşılaştırma sonucu true ise hemen izleyen { }
kod bloğu içindeki kodlar çalıştırılır. Eğer bu şart false ile sonuçlanır yani
gerekli koşul sağlanmaz ise, varsa bir sonraki else koşuluna geçilir. Burada
kullanmış olduğumuz bir takım karşılaştırma operatörleride mevcut. Java'da
kullanılan karşılaştırma operatörlerü aşağıdaki tabloda yer almaktadır.
| Karşılaştırma Operatörü |
Örnek |
Açıklama |
| == |
(Deger1==Deger2) |
Deger1, Deger2'ye eşit
ise true, değilse false döndürür. |
| != |
(Deger1!=15) |
Deger1 15'ten farklı ise
true, değilse yani 15'e eşit ise false döndürür. |
| < |
(Deger1<50) |
Deger1 50'den küçükse
true, büyükse false döndürür. |
| <= |
(Deger1<=50) |
Deger1 50'ye eşit veya
küçük ise true, 50'den büyük ise false döndürür. |
| > |
(Deger1>50) |
Deger1 50'den büyükse
true, küçükse false döndürür. |
| >= |
(Deger1>=50) |
Deger1 50'ye eşit veya
büyükse true, 50'den küçükse false döndürür. |
Diğer yandan ifadelerimizin birisinde &&
kullandık. Bu mantıksal VE anlamına gelen bir operatördür. Bu tip mantıksal
operatörleri, iki veya daha fazla koşulun sonuçlarını bir arada değerlendirmek
istediğimizde kullanırız. Bu örneğimizde mantıksal operatörümüz, belirtilen iki
şartında doğru olması şartıyla izleyen kod bloğundaki kodları çalıştırmaktadır.
Java'da kullanılan mantıksal operatörler aşağıdaki gibidir.
|
Birinci Değer |
İkinci Değer |
&& (Ve) |
|| (Veya) |
^ (Yada) |
! (Değili)
(Birinci Değere Göre ) |
| true |
false |
false |
true |
true |
false |
| false |
true |
false |
true |
true |
true |
| false |
false |
true |
false |
false |
|
| true |
true |
true |
true |
false |
|
Kaynaklardan mantıksal operatörlerden && ve ||
için farklı bir kullanım tarzı daha olduğunu öğrendim. Buda & ve | operatörleri.
Bu iki kullanım tarzı arasındaki farkı incelediğimde oldukça işe yarar bir
sonuçla karşılaştım. && ve || operatörleri, her iki koşulunda işleme sokulmasını
sağlar. Ancak bazen ilk koşulun true olması yada false olması sonucu belirlemek
için yeterlidir. Böyle bir durumda her iki koşuluda karşılaştırmaktansa, yani
koşul ifadesindeki tüm kodu çalıştırmaktansa, & ve | operatörlerini kullanarak,
sadece soldaki koşula bakılmasını ve buna göre karar verilmesini sağlayabiliriz.
Gelelim switch koşullu ifadesinin kullanımına. Bunuda yine bir örnekle incelemek
en güzeli.
public class Kosullar
{
public static void main(String args[])
{
int Deger1=3;
switch(Deger1)
{
case 1:
{
System.out.println("Birinci sınıf nesnesi oluştur.");
break;
}
case 2:
{
System.out.println("Ikinci sınıf nesnesi oluştur.");
break;
}
case 3:
{
System.out.println("Ucuncu sınıf nesnesi oluştur.");
break;
}
default:
{
System.out.println("Ana menuye don.");
break;
}
}
}
} |
Aslında buraya kadar herşey çok hızlı gelişti.
Nitekim kullanılan materyaller C dilinden gelmekte ve C# dili içindede aynen
kullanılmakta. Bu nedenle bir C# programcısı için Java'yı öğrenmek veya bir Java
programcısı için, C# dilini öğrenmek hızlı oluyor diyebilirim. Neyse kahvemizi
yudumlamaya devam edelim.
Sırada döngüsel ifadeler var. Bazen bir işlemi
birden fazla sayıda uygulamak isteyeceğimiz durumlar olabilir. Gauss eğer bir
bilgisayara sahip olsaydı inanıyorum ki 1'den 100'e kadar olan sayıların değil
1'den 1000'e kadar olan sayıların 4ncü dereceden kuvvetlerinin toplamını bulan
bir uygulama geliştirir ve bizi Gauss formüllerini ezberlemekten kurtarırdı.
Sanıyorum programlama dillerini geliştirenler Gauss'un çektiklerinden çok, okul
yıllarında matematik sınavlarındaki Gauss formüllerini hatırlayamamaktan
çekmişler. Burada, for döngüsü kullanarak bu işlemin nasıl gerçekleştiğini
incelemeye çalışacağım. Burada üs almak için, Java içinde yer alan Math sınıfına
ait pow isimli metodu kullandım.
|
public class Dongu
{
public static void main(String args[])
{
double Toplam=0;
for(int i=1;i<=1000;i++)
{
Toplam=Toplam+(Math.pow(i,4));
}
System.out.println(Toplam);
}
} |
For döngüsü dışında iki döngü çeşidi daha vardır.
Bunlar while döngüleridir. İki çeşit while döngüsü vardır. Bunlardan birisi, ilk
başta koşulu kontrol eder ve koşul sağlanıyorsa döngü içindeki kodları
çalıştırır. Diğer çeşidinde ise, döngü içindeki kodlar en az bir kere çalışır ve
koşul sonradan kontrol edilir. Her iki döngüde koşullar sağlandığı sürece devam
eder. Örneğin yukarıdaki uygulamamızı while döngüsü kullanarak gerçekleştirelim.
|
Toplam=0;
int i=1;
while(i<=1000)
{
Toplam=Toplam+(Math.pow(i,4));
i++;
}
System.out.println(Toplam); |
While döngümüzün diğer şekli ilede bu döngüyü
yazabiliriz.
|
Toplam=0;
int j=1;
do
{
Toplam=Toplam+(Math.pow(j,4));
j++;
}
while(j<=1000);
System.out.println(Toplam); |
Artık hakiki bir mola vermenin zamanı geldi
sanırım. Bu kahve molasında, alet çantamızı iyice doldurduk. Ancak bu çantaya
doldurmamız gereken daha çok parça olduğu kesin. İlerleyen kahve molalarında
Java'yı incelemeye devam edeceğiz.
Burak Selim ŞENYURT
selim@bsenyurt.com