Java ile Nesne Yönelimli Programlama · Ders Notu
Bölüm 2.1: Programlama
Bir algoritma kâğıt üzerinde ne kadar açık yazılmış olursa olsun, bilgisayar onu ancak bir programlama diliyle yazıldığında çalıştırabilir. Programlama dili, insanın düşündüğü çözüm ile makinenin yaptığı işlem arasındaki köprüdür. Bu not o köprünün nasıl kurulduğunu anlatır. İlk kesimler dilleri soyutlama düzeyine, uygulama alanına, yürütme biçimine ve paradigmaya göre sınıflandırır. Java'nın kaynak kodu nasıl bytecode'a derlediği ve sanal makinede nasıl çalıştırdığı da burada yer alır. Ardından her dilin üç katmanı ele alınır: söz dizimi, statik anlam ve anlam. Bu katmanlar derleme, çalışma zamanı ve mantık hatalarını ayırmanın da temelidir. Sonraki kesimler ifadelerin işlem önceliğiyle nasıl değerlendirildiğini, tam sayı bölmesini, statik ve dinamik tiplemeyi, tipler arası işlemleri ve dizgi birleştirmeyi işler. Son kesim bir algoritmanın Java programına dönüşmesini, ifade ile deyim ayrımını ve deyim sırasının sonucu nasıl belirlediğini gösterir.
Öğrenme çıktıları
- Programlama dilleri
- Programlama dilini tanımlamak; dilleri soyutlama düzeyi, uygulama alanı, yürütme biçimi ve paradigmaya göre sınıflandırmak.
- Java'nın çalışması
- Kaynak kodun
javacile bytecode'a derlenip JVM'de çalıştırılmasını açıklamak. - Söz dizimi ve anlam
- Söz dizimi, statik anlam ve anlam kavramlarını ayırmak; derleme, çalışma zamanı ve mantık hatalarını tanımak.
- İfadeler
- İşlem önceliği, soldan sağa değerlendirme ve tam sayı bölmesiyle bir ifadenin değerini bulmak.
- Değişkenler ve tipler
- Değişken tanımlamak, atamayı iki adımda izlemek; statik ve dinamik tiplemeyi, tipler arası işlemleri açıklamak.
- Algoritmadan programa
- İfade ile deyimi ayırt etmek; bir algoritmayı Java programına çevirmek ve deyim sırasının önemini göstermek.
2.1.1Programlama dilleri
İnsanlar birbirleriyle doğal dillerle anlaşır. Türkçe, İngilizce ya da Almanca zengin dillerdir; aynı düşünce pek çok biçimde söylenebilir ve dinleyen kişi eksik kalan yerleri bağlamdan tamamlar. Bilgisayar böyle bir tamamlama yapmaz. Ona verilen her talimat tek bir biçimde okunmalı ve tek bir anlama gelmelidir. Programlama dilleri bu ihtiyaçtan doğmuştur. Bu kesim programlama dilini tanımlar ve dilleri dört ölçüte göre sınıflandırır: soyutlama düzeyi, uygulama alanı, yürütme biçimi ve paradigma. Paradigmalar ayrı bir kesimde işlenir.
2.1.1.1Programlama dili nedir?
Programlama dili (programming language), bir makinenin yapabileceği hesaplamaları ifade etmek için tasarlanmış yapay bir dildir. Bu dille yazılan metne program, programın yazıldığı hâline de kaynak kod (source code) denir.
Tanımdaki “yapay” sözcüğü önemlidir. Doğal diller yüzyıllar içinde kendiliğinden gelişir; kuralları sonradan, gözlemle yazılır. Programlama dilleri ise baştan tasarlanır. Hangi sözcüklerin kullanılacağı, bu sözcüklerin hangi sırayla dizileceği ve her dizilişin ne anlama geldiği bir belgeyle kesin olarak belirlenir. Java için bu belge Java Dil Belirtimi'dir (The Java Language Specification).
Programlama dili iki amaçla kullanılır. Birincisi, makinenin davranışını denetleyen programlar yazmaktır. Bunun için algoritmaların açık ve kesin biçimde ifade edilmesi gerekir. İkincisi daha az fark edilir: program, programcılar arasında bir iletişim aracıdır. Yazılan kodu yıllar sonra başka biri okuyacak, değiştirecek ya da hatasını arayacaktır. İyi bir program yalnız doğru çalışmaz, aynı zamanda kolay okunur.
Bilgisayarlar, telefonlar ve gömülü cihazlar işlevlerini programlardan alır. Bir çamaşır makinesinin yıkama adımları, bir arabadaki fren denetim sisteminin kararları ya da bir telefonun ekranındaki her dokunuşa verilen yanıt, bir programlama diliyle yazılmış talimatlardır. Programlama dili bu açıdan algoritmaların ve mantıksal süreçlerin yazılı ifadesidir.
Şekil 2.1.1 bu yolun dört durağını gösterir. Her geçişin kendi zorluğu vardır. Problemden algoritmaya geçişte belirsizlikler giderilir ve çözüm kesin adımlara bölünür. Algoritmadan programa geçişte bu adımlar bir dilin kurallarına göre yazılır. Programdan makineye geçişi ise derleyici ya da yorumlayıcı denen yardımcı programlar üstlenir. Bu notun büyük bölümü ikinci geçişle ilgilidir: bir algoritma Java'da nasıl doğru yazılır ve yazılan kod gerçekte ne anlama gelir?
Örnek dil sayısı çok fazladır: C, C++, Java, Python, Prolog, Haskell, Scala ve yüzlercesi. Hiçbiri her iş için en iyisi değildir. Dil seçimi projenin ihtiyaçlarına göre yapılır. Hız, güvenilirlik, geliştirme süresi, çalışılacak donanım ve ekibin deneyimi bu seçimi etkiler. Sonraki alt kesimler bu seçimde işe yarayan sınıflandırmaları verir.
2.1.1.2Soyutlama düzeyleri
İşlemci yalnız kendi komut kümesini anlar. Bu komutlar ikili sayılarla kodlanmıştır. Her biri çok küçük bir iş yapar: bir yazmaçtan ötekine değer taşır, iki yazmacı toplar ya da bellekten bir sözcük okur. İşlemcinin doğrudan çalıştırdığı bu biçime makine dili (machine language) ya da makine kodu denir. Makine dili bir işlemci ailesine özgüdür; bir Intel işlemcinin makine kodu bir ARM işlemcide çalışmaz.
Düşük seviyeli dil (low-level language), donanıma yakın dildir. Bu dilde yazılan her satır işlemcinin bir ya da birkaç komutuna karşılık gelir. Makine dili ve assembly dili (assembly language) bu gruptadır. Yüksek seviyeli dil (high-level language) ise donanımın ayrıntısını gizler; programcı değişkenler, ifadeler, döngüler ve metotlarla düşünür. Java, Python ve C# yüksek seviyeli dillerdir.
Assembly dili makine komutlarına okunabilir kısaltmalar verir. ADD, MOV, LOAD gibi adlar ikili kodların yerini alır. Yine de her satır hâlâ tek bir makine komutuna karşılık gelir. a = b + c gibi basit bir işlem assembly'de kabaca üç satırdır: b'yi bir yazmaca yükle, c'yi ekle, sonucu a'nın bellek adresine yaz. Java'da ise aynı iş tek bir satırdır ve hangi yazmacın kullanılacağı programcıyı ilgilendirmez.
Soyutlama düzeyi yükseldikçe program kısalır, okunur hâle gelir ve başka donanımlara taşınması kolaylaşır. Buna karşılık programcı donanım üzerindeki denetimin bir kısmını bırakır. Elektrik-elektronik mühendisliğinde iki ucun da yeri vardır. Bir mikrodenetleyicinin kesme yordamı ya da çok sıkı zaman kısıtı olan bir sürücü bazen assembly ya da C ile yazılır. Ölçüm verisini işleyen, arayüzü olan ya da ağda haberleşen bir uygulama ise çoğunlukla Java ya da Python ile yazılır.
| Düzey | Örnek | Bir satır neye karşılık gelir? | Taşınabilirlik |
|---|---|---|---|
| Makine dili | ikili komut kodları | tek bir işlemci komutu | yalnız o işlemci ailesi |
| Assembly | ADD, MOV | tek bir işlemci komutu (okunur adla) | yalnız o işlemci ailesi |
| Yüksek seviye | Java, Python, C# | çok sayıda işlemci komutu | derleyici ya da sanal makine olan her yer |
C dili bu sınıflandırmada ara bir yerde durur. Yüksek seviyeli bir dildir ama belleğe adresle erişmeye ve donanıma yakın kod yazmaya izin verir. Bu yüzden gömülü sistemlerde çok yaygındır. Java ise programcıyı bellek adreslerinden tümüyle uzak tutar; bellek yönetimini de çöp toplayıcı denen bir bileşene bırakır.
2.1.1.3Genel amaçlı ve hedefe odaklı diller
Dilleri sınıflandırmanın ikinci yolu uygulama alanına bakmaktır.
Genel amaçlı dil (general-purpose language), çok çeşitli uygulamaları destekleyen geniş bir temel komut kümesi sunar. Hedefe odaklı dil (domain-specific language) ise belirli bir uygulama türüne odaklanır ve o türdeki işlemleri kolaylaştırır.
Java, C, C++ ve Python genel amaçlı dillerdir. Aynı dille bir masaüstü programı, bir web sunucusu, bir mobil uygulama ya da bir gömülü sistem yazılabilir. Hedefe odaklı dillerin en tanıdık iki örneği MATLAB ve SQL'dir. MATLAB sayısal hesaplama için tasarlanmıştır; matris ve vektör işlemleri dile gömülüdür ve hızlı çalışacak biçimde eniyilenmiştir. SQL (Structured Query Language, yapılandırılmış sorgu dili) veri tabanından veri istemek ve veri tabanını güncellemek içindir.
Farkı en iyi aynı iş gösterir. İki n × n matrisin çarpımı MATLAB'de tek satırdır:
C = A * B; % tek satırJava'da aynı iş elle yazılan üç iç içe döngüyle yapılır:
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
C[i][j] += A[i][k] * B[k][j];Java kodunda en içteki satır Cij = Σk Aik · Bkj toplamını adım adım kurar. Dış iki döngü sonuç matrisinin her hücresini dolaşır, en içteki döngü o hücre için çarpımları toplar. C[i][j] += … yazımı “C[i][j]'ye ekle” anlamına gelir; C dizisinin başlangıçta sıfırlarla dolu olduğu varsayılır. Java'da yeni oluşturulan bir double dizisinin öğeleri zaten 0.0'dır.
İki yazımın maliyeti de farklıdır. Üç iç içe döngü her biri n tur döndüğü için toplam n3 çarpma ve toplama yapar. MATLAB'deki A * B da sonuçta benzer sayıda işlem yapar, ama bu işlemleri önbelleği iyi kullanan, çoğu zaman paralel çalışan hazır bir kitaplığa bırakır. Hedefe odaklı dil bu yüzden hem daha kısa hem de o alanda çoğu zaman daha hızlıdır. Bedeli esnekliktir: MATLAB ile bir web sunucusu yazmak, Java ile yazmaktan çok daha zordur.
MATLAB alışkanlığıyla Java'da C = A * B; yazmak. Java'da * operatörü yalnız sayılar için tanımlıdır; iki dizinin çarpımını bilmez. Derleyici bad operand types for binary operator '*' iletisiyle satırı reddeder. Java'da matris çarpımı ya döngülerle ya da bir kitaplık metoduyla yapılır.
2.1.1.4Derlenen ve yorumlanan diller
Üçüncü sınıflandırma, kaynak kodun nasıl çalıştırıldığına bakar. İşlemci kaynak kodu doğrudan anlamaz; kodun önce makine diline çevrilmesi gerekir. Bu çeviri iki temel yolla yapılır.
Derleyici (compiler), kaynak kodun tamamını program çalıştırılmadan önce başka bir dile, çoğunlukla makine koduna çeviren programdır. Yorumlayıcı (interpreter) ise kaynak kodu okur ve komutları sırayla, o anda çalıştırır; önceden bütün bir çeviri üretmez. Bu iki yola göre diller derlenen (compiled) ve yorumlanan (interpreted) diye ayrılır.
Şekil 2.1.2'de iki yol yan yana durur. Derlenen bir dilde çeviri bir kez yapılır ve sonucu bir dosyaya yazılır. Windows'taki bir .exe dosyası böyle bir çıktıdır. Program her çalıştırıldığında yeniden çevrilmez; makine kodu doğrudan işlemciye verilir. Derleyici bütün programı birden gördüğü için kodu eniyileyebilir: gereksiz işlemleri atar, sık kullanılan değerleri yazmaçlarda tutar. Bu yüzden derlenen programlar genellikle hızlıdır.
Yorumlanan bir dilde ayrı bir çeviri dosyası yoktur. Yorumlayıcı programın yanında sürekli çalışır, her komutu sırası geldiğinde çözümler ve yerine getirir. Bu yaklaşım esneklik sağlar: kod yazılır yazılmaz denenebilir, program çalışırken bile yeni komutlar verilebilir. Bedeli hızdır. Aynı döngü bin kez dönüyorsa yorumlayıcı aynı satırı bin kez çözümler.
| Derlenen | Yorumlanan | |
|---|---|---|
| Nasıl? | Tüm kaynak kod, çalıştırılmadan önce derleyiciyle nesne kodu (object code) ya da makine koduna çevrilir. | Yorumlayıcı kaynak kodu okur ve komutları sırayla o anda çalıştırır. |
| Hız | Hızlı ve eniyilenmiş yürütme | Genellikle daha yavaş |
| Hatalar | Birçoğu derleme sırasında bulunur | Çoğu ancak o satır çalışınca görülür |
| Dağıtım | Her işletim sistemi ve işlemci için ayrı derleme gerekir | Yorumlayıcısı olan her yerde aynı kaynak kod çalışır |
Tablonun üçüncü satırı programcı için belki de en önemli farktır. Derleyici programı çalıştırmadan önce bütünüyle okur. Yanlış yazılmış bir sözcük, eksik bir parantez ya da uyumsuz bir işlem bu okumada yakalanır ve program hiç çalışmaz. Yorumlanan bir dilde ise hatalı bir satır, programın akışı o satıra ulaşana kadar fark edilmez. Bir Python programı bir saat sorunsuz çalışıp ancak nadiren girilen bir if dalında yazım hatası yüzünden durabilir.
2.1.1.5Java'nın yolu: bytecode ve JVM
Java iki yaklaşımı birleştirir. Kaynak kod önce derlenir, ama doğrudan bir işlemcinin makine koduna değil, bytecode denen ara bir koda. Bu ara kodu da Java Sanal Makinesi (Java Virtual Machine, JVM) çalıştırır.
Bytecode (ara kod), Java derleyicisi javac'ın ürettiği ve .class dosyalarında saklanan, platformdan bağımsız komutlardır. JVM, bytecode'u yorumlayan ve sık çalışan kısımlarını JIT derleyici (just-in-time compiler, tam zamanında derleyici) ile çalışma sırasında makine koduna çeviren sanal makinedir.
Bir Java programı iki komutla çalıştırılır. İlki derleme, ikincisi çalıştırmadır:
> javac Paradigma.java (Paradigma.class oluşur)
> java Paradigma
Buyruksal: 15
Fonksiyonel: 15javac Paradigma.java komutu kaynak dosyayı okur. Hata yoksa aynı klasörde Paradigma.class dosyasını oluşturur. java Paradigma komutu ise JVM'i başlatır ve ona bu sınıfı yükletir. Dikkat edilirse ikinci komutta dosya uzantısı yazılmaz; JVM'e bir dosya adı değil, bir sınıf adı verilir.
Bytecode'un neye benzediği JDK ile gelen javap -c aracıyla görülebilir. İki tam sayıyı toplayan küçük bir metot:
static int topla(int a, int b) {
return a + b;
}javac bu metodu şu dört bytecode komutuna çevirir:
static int topla(int, int);
Code:
0: iload_0
1: iload_1
2: iadd
3: ireturnKomutlar bir yığın üzerinde çalışır. iload_0 birinci parametreyi (a), iload_1 ikinci parametreyi (b) yığına koyar. iadd yığının üstündeki iki tam sayıyı alır, toplar ve sonucu yığına geri koyar. ireturn yığının üstündeki değeri metodun sonucu olarak döndürür. Baştaki i harfi her komutun int ile çalıştığını gösterir. Bu komutlar hiçbir gerçek işlemciye ait değildir; JVM için tanımlanmış soyut bir komut kümesidir.
Bu ara katmanın büyük bir kazancı vardır. Aynı .class dosyası Windows'ta, Linux'ta ya da macOS'ta, Intel ya da ARM işlemcide değiştirilmeden çalışır. Her platform için ayrı bir JVM yazılmıştır; program ise tek bir kez derlenir. Java'nın tanıtımında kullanılan “bir kez yaz, her yerde çalıştır” (write once, run anywhere) sözü bu özelliği anlatır.
Hız sorununu JIT derleyici çözer. JVM programı önce yorumlayarak çalıştırır ve hangi metotların sık çağrıldığını sayar. Sık çalışan bir metot, çalışma sırasında o makinenin gerçek makine koduna derlenir; sonraki çağrılar yorumlama olmadan doğrudan işlemcide yürür. Bu yüzden uzun süre çalışan Java programları derlenen dillere yakın bir hıza ulaşır. Kısa programlarda ise JVM'in açılış süresi hissedilir.
Java'nın derleme adımı, derlenen dillerin en önemli avantajını da korur. javac bütün programı okur, tipleri denetler ve hatalı programın .class dosyasını hiç üretmez. Yanlış yazılmış bir değişken adı ya da bir sayıya bir dizginin atanması program çalışmadan yakalanır. Kotlin ve Scala gibi başka diller de JVM bytecode'una derlenir; bu diller aynı sanal makineyi ve Java'nın kitaplıklarını paylaşır.
Programı java Paradigma.class ya da sınıf adından farklı adlı bir dosyayla çalıştırmaya çalışmak. java komutu bir sınıf adı bekler; .class uzantısı yazılırsa JVM Paradigma.class adlı bir sınıf arar ve bulamaz. Ayrıca public bir sınıfın kaynak dosyası sınıfla aynı adı taşımalıdır: public class Paradigma yalnız Paradigma.java adlı dosyada derlenir.
2.1.2Programlama paradigmaları
İki programcı aynı problemi aynı dille bile çok farklı biçimlerde çözebilir. Dillerin kendisi de belli bir çözüm biçimini kolaylaştıracak şekilde tasarlanır. Bir dilin hangi düşünme biçimini öne çıkardığı, o dilin paradigmasıyla anlatılır. Bu kesim dört temel paradigmayı tanıtır ve her birini kısa bir programla gösterir.
2.1.2.1Paradigma kavramı
Programlama paradigması (programming paradigm), bir problemi çözerken izlenen temel yaklaşımdır. Paradigma, programın neyden oluştuğunu (komutlar, fonksiyonlar, kurallar ya da nesneler) ve hesaplamanın nasıl ilerlediğini belirler.
Dört paradigma bu derste sık anılır. Tablo 2.1.3 bunları özetler.
| Paradigma | Temel fikir | Örnek diller |
|---|---|---|
| Buyruksal (imperative) | Programcı makineye, durumunu nasıl değiştireceğini adım adım söyler. | FORTRAN, BASIC, Pascal, C |
| Fonksiyonel (functional) | Hesaplama, matematiksel fonksiyonların değerlendirilmesidir; durum değiştirmekten kaçınılır. | Lisp, Scheme, Haskell |
| Mantıksal (logic) | Hesaplamanın mantığı olgular ve kurallarla açıklanır; kontrol akışı ayrıntılandırılmaz. | Prolog |
| Nesne yönelimli (object-oriented) | İlişkili veri ve metotlar bir nesne altında toplanır; komutlar, üzerinde çalıştıkları durumla birlikte gruplanır. | C++, Java, C#, Python |
Tablodaki “durum” (state) sözcüğü paradigmaları ayırmanın anahtarıdır. Durum, programın belli bir andaki değişkenlerinin değerleridir. Buyruksal bir programda bu değerler her adımda değişir ve sonuç bu değişikliklerin toplamıdır. Fonksiyonel bir programda ise değerler bir kez hesaplanır ve değiştirilmez; sonuç, fonksiyonların birbirine bağlanmasıyla elde edilir. Mantıksal programda programcı ne adımları ne de fonksiyonları yazar; yalnız neyin doğru olduğunu söyler. Nesne yönelimli programda durum ortadan kalkmaz, nesnelerin içine yerleşir.
Birden çok paradigmayı destekleyen dillere çok paradigmalı diller denir. Java temelde nesne yönelimli ve buyruksaldır. Java 8'den bu yana lambda ifadeleri ve akışlar (streams) gibi fonksiyonel öğeler de içerir. Python ve C# da çok paradigmalıdır. Paradigma bu yüzden bir dilin değil, çoğu zaman bir programın ya da programın bir parçasının özelliğidir.
2.1.2.2Buyruksal ve fonksiyonel: aynı problem
Problem basittir: 1'den 5'e kadar sayıların toplamı bulunsun. Aşağıdaki Java programı bu toplamı iki yolla hesaplar.
import java.util.stream.IntStream;
public class Paradigma {
public static void main(String[] args) {
// Buyruksal: adım adım durum değiştir
int toplam = 0;
for (int i = 1; i <= 5; i++) {
toplam = toplam + i;
}
System.out.println("Buyruksal: " + toplam);
// Fonksiyonel (Java 8+): ne istediğini söyle
int sonuc = IntStream.rangeClosed(1, 5).sum();
System.out.println("Fonksiyonel: " + sonuc);
}
}Program çalıştırıldığında iki yol da aynı sonucu verir:
Buyruksal: 15
Fonksiyonel: 15Buyruksal parça, toplamın nasıl bulunacağını yazar. toplam adlı bir değişken 0 ile başlar. for döngüsü i'yi 1'den 5'e kadar sayar ve her turda toplam = toplam + i deyimiyle toplam'ı günceller. Sonuç, beş güncellemenin ardından değişkende kalan değerdir. Programcı burada makinenin yerine düşünür: hangi değişken, hangi sırayla, nasıl değişecek?
Fonksiyonel parça ise ne istendiğini yazar. IntStream.rangeClosed(1, 5) 1, 2, 3, 4 ve 5 sayılarından oluşan bir akış üretir; closed sözcüğü üst sınırın da dahil olduğunu belirtir. .sum() bu akıştaki bütün sayıları toplar. Kodda adım adım güncellenen bir değişken yoktur; sonuc değişkeni yalnız bir kez, son değeriyle atanır. Toplamanın hangi sırayla yapılacağı kitaplığa bırakılmıştır.
Programın ilk satırındaki import java.util.stream.IntStream; satırı derleyiciye IntStream sınıfının nerede olduğunu söyler. Bu satır olmasa derleyici IntStream adını tanımaz ve cannot find symbol hatası verir. System ve String gibi sınıflar java.lang paketinde olduğu için onları ayrıca içe aktarmak gerekmez.
2.1.2.3Kod izleme: buyruksal toplam
Buyruksal yaklaşımda durumun nasıl değiştiğini görmenin en iyi yolu kodu satır satır izlemektir. Tablo 2.1.4 döngünün her turunda koşulun değerini ve toplam'ın yeni değerini verir.
i | i <= 5 | Gövde | toplam |
|---|---|---|---|
| — | — | başlangıç | 0 |
| 1 | doğru | 0 + 1 | 1 |
| 2 | doğru | 1 + 2 | 3 |
| 3 | doğru | 3 + 3 | 6 |
| 4 | doğru | 6 + 4 | 10 |
| 5 | doğru | 10 + 5 | 15 |
| 6 | yanlış | döngü biter | 15 |
toplam durum değişkeni 0 ile başlar. (b) i = 2: 1 + 2 = 3; birinci tur yeşille işaretlenmiştir. (c) i 6 olunca 6 <= 5 yanlıştır ve döngü biter; beş turda biriken toplam 15'tir. (d) Fonksiyonel yol: rangeClosed(1, 5) beş sayıyı birlikte üretir, sum() hepsini toplar; hiçbir değişken adım adım güncellenmez.İzlemede üç nokta dikkat çeker. Birincisi, koşul altı kez denetlenir ama gövde yalnız beş kez çalışır; son denetim döngüyü bitiren denetimdir. İkincisi, döngü bittiğinde i 6'dır, 5 değil. i döngünün başlığında tanımlandığı için döngüden sonra zaten kullanılamaz; yine de bu ayrıntı sınır hatalarını anlamak için önemlidir. Üçüncüsü, toplam'ın her ara değeri bir sonraki turun girdisidir. Bir turdaki hata sonraki bütün turlara taşınır.
İki yolun maliyeti aynıdır. Her ikisi de beş toplama yapar; n sayı için n toplama gerekir. Fark yazımdadır, iş miktarında değil. Şekil 2.1.3'ün son karesi bu farkı özetler. İki yaklaşım aynı sonucu verir. Fark, nasıl yapılacağını mı yoksa ne istendiğini mi yazdığımızdadır.
3'ten 6'ya kadar sayıların toplamı iki yolla yazılsın.
Buyruksal: int t = 0; for (int i = 3; i <= 6; i++) t = t + i;. Turlar sırasıyla t'yi 3, 7, 12 ve 18 yapar; i 7 olunca koşul yanlış çıkar.
Fonksiyonel: IntStream.rangeClosed(3, 6).sum(). Akış 3, 4, 5, 6 sayılarını üretir; toplamları 18'dir.
İki yol da 18 verir. rangeClosed(3, 6) yerine range(3, 6) yazılsaydı üst sınır dahil olmaz, sonuç 3 + 4 + 5 = 12 olurdu.
Toplam değişkenine ilk değer vermeyi unutmak. int toplam; yazıp döngüde toplam = toplam + i; denirse Java variable toplam might not have been initialized iletisiyle derlemeyi durdurur. Yerel değişkenlerin varsayılan değeri yoktur. Başka dillerde böyle bir değişken rastgele bir değerle başlayabilir ve yanlış bir toplam sessizce üretilir; Java bu hatayı derlemede yakalar.
2.1.2.4Mantıksal programlama: Prolog
Mantıksal paradigmanın bilinen örneği Prolog'dur. Prolog programı adımlardan değil, olgulardan (facts) ve kurallardan (rules) oluşur. Programcı neyin doğru olduğunu yazar; bir soru sorulduğunda cevabı Prolog kendisi arar.
% olgular
ebeveyn(ali, ayse).
ebeveyn(ayse, can).
% kural
dede(X, Z) :- ebeveyn(X, Y),
ebeveyn(Y, Z).
% sorgu
?- dede(ali, can).
true.İlk iki satır iki olgu bildirir: Ali, Ayşe'nin ebeveynidir; Ayşe de Can'ın ebeveynidir. Prolog'da küçük harfle başlayan adlar sabittir, büyük harfle başlayanlar değişkendir. Kural satırı “X, Z'nin dedesidir, eğer X bir Y'nin ebeveyniyse ve Y de Z'nin ebeveyniyse” diye okunur. :- işareti “eğer”, virgül “ve” anlamındadır.
Sorgu ?- dede(ali, can). “Ali, Can'ın dedesi midir?” sorusudur. Prolog kuralı kullanır ve X yerine ali, Z yerine can koyar. Ardından ebeveyn(ali, Y) olgusunu sağlayan bir Y arar ve ayse'yi bulur. Son olarak ebeveyn(ayse, can) olgusunun var olup olmadığına bakar. Olgu vardır, cevap true olur.
Bu arama sırasının hiçbiri programda yazılı değildir. Programcı döngü kurmamış, hangi olgunun önce deneneceğini söylememiştir. Mantıksal paradigmada kontrol akışı dilin kendi arama yöntemine bırakılır. Aynı program başka sorulara da cevap verir: ?- dede(ali, X). sorgusu “Ali kimin dedesidir?” anlamına gelir ve Prolog X = can cevabını bulur. Buyruksal bir dilde bu iki soru için iki ayrı kod yazmak gerekirdi.
2.1.2.5Nesne yönelimli programlama: Dikdortgen
Nesne yönelimli paradigma, bu dersin asıl konusudur. Temel fikir, birbirine ait veri ile o veri üzerinde çalışan işlemleri tek bir birimde toplamaktır. Bu birime nesne denir; nesnelerin kalıbı da sınıftır.
Nesne yönelimli programlama (object-oriented programming, OOP), programı durumu (veri) ve davranışı (metotlar) birleştiren nesneler üzerine kurar. Nesnenin verisini tutan değişkenlere alan (field), davranışını tanımlayan işlemlere metot (method) denir.
Aşağıdaki sınıf bir dikdörtgeni modeller. Java'da tanımlayıcılarda Türkçe karakter kullanılabilir, ama derste dosya adlarıyla sorun çıkmasın diye Dikdortgen yazımı seçilmiştir.
public class Dikdortgen {
double en, boy; // veri (durum)
Dikdortgen(double en, double boy) {
this.en = en;
this.boy = boy;
}
double alan() { // davranış
return en * boy;
}
public static void main(String[] args) {
Dikdortgen d = new Dikdortgen(3, 4);
System.out.println("Alan: " + d.alan());
}
}Alan: 12.0Sınıfın dört parçası vardır:
double en, boy;satırı iki alan tanımlar. HerDikdortgennesnesi kendienveboydeğerlerini taşır; bu iki değer nesnenin durumudur.Dikdortgen(double en, double boy)bir yapıcıdır (constructor). Yapıcının adı sınıfın adıyla aynıdır ve dönüş tipi yoktur. Nesne oluşturulurken bir kez çağrılır ve alanlara ilk değerlerini verir.double alan()bir metottur. Nesnenin kendi alanlarını kullanarak alanı hesaplar vedoublebir değer döndürür.mainmetodu programın giriş noktasıdır. Bir nesne oluşturur, nesneninalan()metodunu çağırır ve sonucu yazdırır.
Yapıcının içindeki this.en = en; satırı ilk bakışta tuhaf görünür. Burada iki ayrı en vardır. Sağdaki en, yapıcının parametresidir ve çağrıda verilen değeri taşır. Soldaki this.en ise nesnenin alanıdır. this anahtar kelimesi “şu anda üzerinde çalışılan nesne” anlamına gelir. Parametre ile alan aynı adı taşıdığında parametre alanı gölgeler; alana ulaşmak için this. yazmak gerekir.
alan() metodunda ise this yazılmamıştır: return en * boy;. Metodun içinde en adında bir parametre ya da yerel değişken olmadığı için bu ad doğrudan alana gider. return this.en * this.boy; yazmak da aynı anlama gelir.
Yapıcıda this yazmayı unutmak: en = en;. Bu satır derlenir, ama parametreyi kendisine atar; alan değişmez. Nesnenin en ve boy alanları varsayılan 0.0 değerinde kalır ve program Alan: 0.0 yazar. Derleyici bu durumda hata vermez, çünkü satır dil açısından geçerlidir. Bu bir mantık hatasıdır.
2.1.2.6Kod izleme: Dikdortgen nesnesi
Nesne yönelimli bir programı izlerken bellekte iki bölge birlikte düşünülür. Yığın (stack), metot çağrılarının çerçevelerini ve yerel değişkenleri tutar. Heap ise new ile oluşturulan nesnelerin yaşadığı bölgedir. Yerel değişken d yığındadır; gösterdiği Dikdortgen nesnesi heap'tedir.
main başlar; yığında main çerçevesi vardır, d henüz bir nesneyi göstermez. (b) new heap'te nesneye yer ayırır, alanlar varsayılan 0.0'dır; yapıcı çerçevesinde this nesneyi gösterir, parametreler 3.0 ve 4.0'dır. (c) Yapıcı bitti, alanlar 3.0 ve 4.0 oldu; d artık nesneyi gösterir. (d) d.alan() çağrısında this ile d aynı nesneyi gösterir.Şekil 2.1.4 programın akışını adım adım verir. Ayrıntılar şöyledir:
mainçalışmaya başlar. Yığındamainçerçevesi açılır,ddeğişkenine yer ayrılır ama henüz bir nesne yoktur.new Dikdortgen(3, 4)ifadesi değerlendirilir.newheap'te nesneye yer ayırır ve alanlara varsayılan değer 0.0'ı verir. Ardından yapıcı çağrılır. Çağrıdaki 3 ve 4 tam sayıdır, parametreler isedouble'dır; bu yüzden 3 ve 4 otomatik olarak 3.0 ve 4.0'a genişletilir.this.en = en;çalışır: nesneninenalanı 3.0 olur.this.boy = boy;çalışır:boyalanı 4.0 olur. Yapıcı biter ve çerçevesi yığından kalkar.newifadesinin değeri nesnenin referansıdır, yani heap'teki yerini gösteren değerdir. Bu referansd'ye atanır.d.alan()çağrılır. Yeni çerçevedethis,dile aynı nesneyi gösterir.en * boy= 3.0 × 4.0 = 12.0 hesaplanır ve döndürülür.- Dönen 12.0,
"Alan: "dizgisiyle birleştirilir veAlan: 12.0yazılır.
Çıktıda 12 değil 12.0 yazmasının nedeni tiptir. alan() bir double döndürür. Java bir double değeri dizgiye çevirirken, değer tam sayı olsa bile en az bir ondalık basamak yazar.
Bu küçük program nesne yönelimli paradigmanın özünü taşır. Veri (en, boy) ve davranış (alan) aynı nesnede durur. main metodu alanın nasıl hesaplandığını bilmez; yalnız nesneden alanını ister. Hesaplama yöntemi değişse bile, örneğin alanlar santimetre yerine milimetre tutulsa, main'deki çağrı aynı kalır.
main'e şu iki satır eklensin: Dikdortgen k = new Dikdortgen(2, 5); ve System.out.println(k.alan() + k.en);.
Heap'te ikinci bir nesne oluşur; alanları 2.0 ve 5.0'dır. İlk nesne bundan etkilenmez, çünkü her nesnenin alanları ayrıdır. k.alan() 10.0 döndürür; k.en 2.0'dır. İki double toplanır ve 12.0 yazılır.
Satır "Alan: " + k.alan() + k.en olsaydı sonuç farklı olurdu. Soldaki işlenen dizgi olduğu için iki + da birleştirme yapar ve Alan: 10.02.0 yazılır.
2.1.3Söz dizimi ve anlam
Programlama dilleri ile doğal diller birçok ortak yön paylaşır. Her ikisinde de temel yapılar vardır, bu yapıların nasıl dizileceğini belirleyen kurallar vardır ve doğru dizilmiş bir metnin bir anlamı vardır. Bir dili incelemenin üç katmanı bu gözleme dayanır: söz dizimi, statik anlam ve anlam. Bu ayrım yalnız kuramsal değildir. Programdaki bir hatanın katmanı, hatayı kimin ve ne zaman bulacağını belirler: derleyici mi, JVM mi, yoksa test eden kişi mi?
2.1.3.1Temel yapılar ve söz dizimi
Türkçede temel yapılar kelimeler ve noktalama işaretleridir. Java'da ise sayılar, dizgiler, basit operatörler (+ - * /) ve anahtar kelimelerdir. int, class, if ve return anahtar kelimelere örnektir; dil bu sözcükleri kendine ayırmıştır.
Söz dizimi (syntax), bir dilde hangi karakter ve sembol dizilerinin düzgün olduğunu belirleyen biçim kurallarıdır. Bu kurallara uymayan bir metin, içindeki sözcükler tek tek doğru olsa bile geçersizdir.
“kedi köpek çocuk” dizisini oluşturan kelimelerin her biri Türkçedir, ama ortada bir cümle yoktur; özne, nesne ve yüklem kurulmamıştır. Java'da da durum aynıdır. int x = 5; geçerli bir satırdır: önce tip, sonra ad, sonra atama işareti, sonra değer, en sonda noktalı virgül gelir. Aynı parçalar başka bir sırayla dizilirse satır geçersiz olur:
int = x 5;Derleyici bu satırı reddeder. JDK 21'in tam iletisi şudur:
Hata.java:3: error: not a statement
int = x 5;
^
Hata.java:3: error: ';' expected
int = x 5;
^
Hata.java:3: error: not a statement
int = x 5;
^
Hata.java:3: error: ';' expected
int = x 5;
^
4 errorsTek bir satırdaki tek bir sıralama yanlışı dört hata iletisine yol açmıştır. Bunun nedeni derleyicinin hatadan sonra durmaması, satırın geri kalanını anlamlandırmaya çalışmasıdır. int'ten sonra bir ad beklerken = görür ve “bu bir deyim değil” der. Ardından bir ; bekler, bulamaz. Kalan x ve 5 parçalarını da ayrı ayrı yorumlamaya çalışır ve iki ileti daha üretir. Sonraki iletiler ilk hatanın yan etkisidir. Bu yüzden birden çok hata listelendiğinde düzeltmeye ilkinden başlanır; ilk hata düzelince çoğu zaman ötekiler de kaybolur.
Söz dizimi dilin anlaşılırlığını da etkiler. Kuralları kesin olan bir dilde bir satırın nasıl okunacağı konusunda tartışma çıkmaz. Söz dizimi hataları bu yüzden en kolay bulunan hatalardır: kurallar açıkça tanımlanmıştır ve derleyici her satırı bu kurallara göre denetler.
2.1.3.2Statik anlam
Söz dizimi doğru olan her metin anlamlı değildir. İngilizcedeki “I are big” cümlesi biçim olarak <isim> <fiil> <isim> kalıbına uyar. Ama “I” tekil, “are” çoğuldur; cümle dil açısından geçersizdir. Bu tür bir uyumsuzluk cümlenin dizilişinde değil, parçaların birbirine uygunluğundadır.
Statik anlam (static semantics), söz dizimi doğru olan bir ifadenin anlamlı olup olmadığını program çalışmadan önce denetleyen kurallardır. Java'da bu kuralların en önemlisi tip uyumudur.
Java'da <değer> <işleç> <değer> geçerli bir kalıptır. 2.3 / 4 bu kalıba uyar ve anlamlıdır. Aşağıdaki satır da aynı kalıba uyar, ama bir sayıyı bir dizgiye bölmenin anlamı yoktur:
double sonuc = 2.3 / "abc";error: bad operand types for binary operator '/'
first type: double
second type: Stringİleti, / operatörünün bu iki tiple kullanılamayacağını söyler ve iki işlenenin tipini ayrı ayrı yazar: birincisi double, ikincisi String. Aynı türden bir başka hata, bir değişkene tipine uymayan bir değer atamaktır:
int x = "beş";error: incompatible types:
String cannot be converted to intBu iki satırın söz diziminde bir kusur yoktur. Hata, derleyicinin tip denetimi adımında bulunur. Java statik tipli bir dildir: derleyici her değişkenin ve her ifadenin tipini bilir, her operatörün işlenenlerinin tipini denetler. Bu yüzden bu tür hatalar program hiç çalışmadan yakalanır.
int = x 5; burada takılır), sonra statik anlam denetiminden (int x = "beş"; burada takılır) geçer; ikisi de derleme zamanındadır. Anlam hataları ((a + b) / 2 ile ortalama) ise ancak program çalışırken ortaya çıkar.Şekil 2.1.5 üç katmanı bir boru hattı gibi gösterir. Her katman bir öncekinden geçmiş kodu alır. Söz dizimi hatası olan bir satır tip denetimine hiç ulaşmaz. Tip denetiminden geçen kod derlenir ve çalıştırılabilir. Son kutudaki hatalar ise derleyicinin göremeyeceği türdendir.
Statik anlam denetiminin gücü dilden dile değişir. Bu denetimi zayıf olan dillerde yakalanmayan hatalar programın davranışını öngörülemez kılar. C dilinde bir dizinin sınırı dışına yazmak derlemede yakalanmaz ve program belleğin başka bir yerini sessizce bozabilir. Java bu tür hataların bir kısmını derlemede, kalanını çalışma zamanında bir istisnayla yakalar; hiçbirini sessiz bırakmaz.
2.1.3.3Anlam
Anlam (semantics), söz dizimi doğru bir ifadenin ne anlama geldiğini, yani çalıştığında ne yaptığını belirler.
Doğal dillerde bir cümlenin birden fazla anlamı olabilir. “They saw the man with the telescope.” cümlesi iki biçimde okunur: adamı teleskopla gördüler, ya da teleskobu olan adamı gördüler. Dinleyen kişi doğru anlamı bağlamdan çıkarır. Programlama dillerinde böyle bir çok anlamlılık yoktur. Her geçerli ifadenin tek bir anlamı vardır ve bu anlam dil belirtiminde yazılıdır.
Ancak bu tek anlam, programcının beklediği şey olmayabilir. Aşağıdaki kod 7 ile 8'in ortalamasını üç farklı biçimde hesaplamaya çalışır:
int a = 7, b = 8;
int ortalama = (a + b) / 2;
System.out.println(ortalama); // 7.5 beklenirken
System.out.println(a + b / 2); // öncelik
System.out.println((a + b) / 2.0); // doğru7
11
7.57 ile 8'in ortalaması 7,5'tir. Yalnız üçüncü satır bu değeri yazar. İlk iki satır derlenir, çalışır ve bir sayı yazar; ama sayı yanlıştır. Derleyici bu satırlarda bir hata görmez, çünkü dil açısından ortada bir hata yoktur. Her satır Java'nın kurallarına göre tam olarak ne yazılmışsa onu yapar.
(a + b) / 2: iki işlenen deintolduğu için Java tam sayı bölmesi yapar. 15 / 2'nin kesirli kısmı atılır ve 7 bulunur.a + b / 2: parantez yoktur ve/işleminin önceliği+'dan yüksektir. Önceb / 2= 4 hesaplanır, sonra 7 + 4 = 11 bulunur.(a + b) / 2.0:2.0birdoublesabittir. 15 dedouble'a genişletilir ve gerçek bölme yapılır: 7.5.
2.1.3.4Kod izleme: tam sayı bölmesi
Bir ifadenin nasıl değerlendirildiği, ifade ağacıyla açıkça görülür. Ağaçta her iç düğüm bir operatör, her yaprak bir değer ya da değişkendir. Bir operatör ancak alt düğümlerinin değerleri bulunduktan sonra uygulanır; bu yüzden değerlendirme yapraklardan köke doğru ilerler.
(a + b) / 2: önce parantez, a + b = 15. (b) 15 / 2: iki işlenen de int, tam sayı bölmesiyle 7. (c) a + b / 2: parantez olmayınca bölme alta iner; önce b / 2 = 4, sonra 7 + 4 = 11. (d) (a + b) / 2.0: 2.0 double olduğu için sonuç 7.5.Şekil 2.1.6'daki (a) ve (c) ağaçları aynı dört parçadan (a, b, +, /) ve aynı 2 sabitinden oluşur. Yalnız biçimleri farklıdır. Parantez + düğümünü alta, yani önce yapılacak yere taşır. Parantez olmadığında öncelik kuralı /'yi alta koyar. Aynı karakterlerle yazılan iki ifadenin farklı sonuç vermesinin nedeni budur.
| Satır | Hesap | Değişkenler | Çıktı |
|---|---|---|---|
| 1 | — | a = 7, b = 8 | |
| 2 | (7 + 8) / 2 = 15 / 2 = 7 | ortalama = 7 | |
| 3 | — | 7 | |
| 4 | 7 + 8 / 2 = 7 + 4 = 11 | 11 | |
| 5 | (7 + 8) / 2.0 = 15.0 / 2.0 = 7.5 | 7.5 |
Programın ikinci satırı bu notun en sık rastlanacak tuzağını gösterir. ortalama değişkeni int olarak tanımlanmıştır. Sağ taraf 7.5 üretseydi bile Java bu değeri int'e atamaya izin vermezdi. Değişken double yapılsa da sorun çözülmez; sağ taraf yine tam sayı bölmesiyle 7 üretir, atama yalnız 7'yi 7.0'a çevirir.
Sonucu double bir değişkene yazmanın bölmeyi ondalıklı yaptığını sanmak. double ort = (a + b) / 2; satırı 7.5 değil 7.0 verir. Java önce sağ tarafı kendi tipleriyle hesaplar: iki int'in bölümü int'tir. Atama ancak bu hesap bittikten sonra yapılır ve kaybolan kesir geri gelmez. Doğrusu işlenenlerden birini double yapmaktır: (a + b) / 2.0.
2.1.4Hata türleri ve hata iletileri
Program yazmak, hata yapmayı ve hatayı bulmayı da içerir. Deneyimli programcılar bile ilk denemede hatasız kod yazmaz. Fark, hatanın türünü hızla tanımak ve onu nerede arayacağını bilmektir. Söz dizimi, statik anlam ve anlam ayrımı burada doğrudan işe yarar.
2.1.4.1Dört hata türü
| Hata türü | Ne zaman fark edilir? | Java örneği | Belirti |
|---|---|---|---|
| Söz dizimi | derlemede (derleyici) | System.out.println("Merhaba") | ';' expected |
| Statik anlam | derlemede (tip denetimi) | int x = "beş"; | incompatible types |
| Çalışma zamanı | program çalışırken | a / b (b sıfırken) | çökme: ArithmeticException |
| Mantık | derleyici fark etmez; ancak testle | (a + b) / 2 ile ortalama | yanlış sonuç, sonsuz döngü |
İlk iki tür derleme hatası diye birlikte anılır. İkisi de program çalışmadan bulunur ve hata varken .class dosyası oluşmaz. Söz dizimi hataları yazım hataları gibidir: açıkça tanımlanmış kurallara aykırılıktır ve derleyici bunları kolayca bulur. Statik anlam hataları ise dile özgü denetimlerle aranır; Java'da bu denetimin merkezinde tipler vardır.
Çalışma zamanı hatası (runtime error), derlenmiş bir program çalışırken ortaya çıkan ve Java'da bir istisnaya (exception) yol açan hatadır. Mantık hatası (logic error) ise programın çalışıp yanlış sonuç üretmesidir; ne derleyici ne de JVM bunu fark eder.
Tablonun son satırı en tehlikeli durumu anlatır. Söz dizimi ve statik anlam açısından kusursuz bir program bile beklenen sonucu üretmeyebilir. Program çökebilir, sonsuz döngüye girebilir ya da hiçbir uyarı vermeden yanlış bir sonuç verebilir. Bu sonuncusu en zor bulunanıdır, çünkü bir şeylerin yanlış gittiğini gösteren bir belirti yoktur.
2.1.4.2Derleme hatası iletisini okumak
Derleyicinin hata iletisi korkutucu görünebilir, ama düzenli bir yapısı vardır. Aşağıdaki programda bir noktalı virgül eksiktir:
public class Selam {
public static void main(String[] args) {
System.out.println("Merhaba")
}
}Selam.java:3: error: ';' expected
System.out.println("Merhaba")
^
1 errorİleti dört parçadan oluşur:
Selam.java:3: hatanın bulunduğu dosya ve satır numarası.error: ';' expected: derleyicinin ne beklediği. Burada bir noktalı virgül bekleniyordu.- Satırın kopyası ve altındaki
^işareti: hatanın satır içindeki konumu. İşaret, kapanan parantezden hemen sonrasını, yani;'ün gelmesi gereken yeri gösterir. 1 error: toplam hata sayısı.
Hata varsa .class dosyası oluşmaz ve program hiç çalışmaz. Önceki bir derlemeden kalmış eski bir .class dosyası varsa, java komutu yanlışlıkla o eski sürümü çalıştırabilir. Kodda yapılan değişikliğin etkisinin görülmemesi çoğu zaman bu yüzdendir: derleme başarısız olmuştur ama ileti gözden kaçmıştır.
Noktalı virgül eksikse javac hatayı, ;'ün eksik olduğu satırın sonunda gösterir. Aşağıdaki parçada ilk satırın sonundaki ; unutulmuştur:
int x = 5
int y = 6;
System.out.println(x + y);Semi.java:3: error: ';' expected
int x = 5
^
1 errorİşaret ikinci satıra değil, 5'in hemen arkasına konmuştur. Derleyici 5'ten sonra gelen sözcüğü (int) görünce deyimin bitmesi gerektiğini anlar ve eksikliği bir önceki parçanın sonuna yazar. Dosyanın 3. satırı, kod parçasının ilk satırıdır; parçanın üstünde sınıf ve main başlıkları vardır.
Bir programda int toplam = 0; tanımlanmış, yedinci satırda ise System.out.println(Toplam); yazılmıştır. Derleyici şu iletiyi verir: Hesap.java:7: error: cannot find symbol, altında da symbol: variable Toplam.
İleti okunur: hata 7. satırdadır; derleyici Toplam adında bir değişken bulamamıştır. Tanımlanan ad toplam'dır. Java büyük ve küçük harfi ayırır; bu iki ad farklıdır. Bu bir derleme (statik anlam) hatasıdır. Toplam yerine toplam yazılınca program derlenir.
2.1.4.3Çalışma zamanı hatası ve istisna
Aşağıdaki program sorunsuz derlenir. Hata ancak çalışırken ortaya çıkar:
public class Bolme {
public static void main(String[] args) {
int a = 10, b = 0;
System.out.println(a / b);
}
}Exception in thread "main"
java.lang.ArithmeticException: / by zero
at Bolme.main(Bolme.java:4)Derleyici a / b ifadesinde bir sorun görmez: iki int'in bölümü geçerli bir ifadedir. b'nin o anda 0 olduğu ancak program çalışırken bilinir. Tam sayılar sıfıra bölünemez; JVM bu durumda bir istisna fırlatır. İletinin parçaları şöyledir:
Exception in thread "main": istisnamainiş parçacığında oluştu.java.lang.ArithmeticException: istisnanın türü, yani sınıfının tam adı./ by zero: istisnanın açıklaması; tam sayı sıfıra bölünmeye çalışıldı.at Bolme.main(Bolme.java:4): istisnanın oluştuğu metot, dosya ve satır.
Gerçek çıktıda ilk iki parça tek satırda yazılır; sunumda sayfaya sığsın diye bölünmüştür. İstisna bir try-catch bloğuyla yakalanmazsa program bu satırda sonlanır ve sonraki satırlar hiç çalışmaz.
Sıfıra bölmenin davranışı tipe bağlıdır. 10 / 0 istisna fırlatır. 10.0 / 0 ise istisna fırlatmaz ve Infinity değerini üretir; 0.0 / 0 da NaN (not a number, sayı değil) verir. Kayan noktalı sayıların bu özel değerleri vardır, tam sayıların yoktur. Bu yüzden double ile yapılan bir sıfıra bölme programı çökertmez, ama sonucu sessizce anlamsız bir değere dönüştürür.
Bir başka sık çalışma zamanı hatası, dizinin sınırı dışına çıkmaktır. int[] a = new int[3]; üç öğeli bir dizi oluşturur; geçerli indisler 0, 1 ve 2'dir. a[3] = 1; derlenir, ama çalışırken ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 istisnasını fırlatır. İndisin değeri çoğu zaman bir hesabın sonucudur ve derleyici onu önceden bilemez.
2.1.4.4Mantık hatası ve test
Mantık hatasında program derlenir, çalışır ve bir sonuç verir; yalnız sonuç yanlıştır. Ortalamayı (a + b) / 2 ile almak, dairenin alanı için 2 * pi * r yazmak (bu çevredir), bir döngünün sınırını bir eksik ya da bir fazla kurmak bu türdendir. Dil açısından bu satırların hiçbirinde kusur yoktur.
Mantık hatalarını bulmanın yolu testtir: programı, sonucu önceden bilinen girdilerle çalıştırıp çıktıyı beklenen değerle karşılaştırmak. 7 ile 8'in ortalamasının 7,5 olduğu bilinir; program 7 yazıyorsa bir hata vardır. İyi seçilmiş birkaç test, hatanın yerini de daraltır. 6 ile 8 için program doğru sonucu (7) verir, 7 ile 8 için yanlış sonucu verir; bu fark, kesirli sonuçların kaybolduğunu düşündürür.
İstisna mekanizması mantık hatalarını yakalamaz. İstisna, geçersiz bir işlem yapıldığında fırlatılır; mantık hatasında ise yapılan her işlem geçerlidir. Bu ayrım önemlidir: programın istisna fırlatmadan çalışması, doğru çalıştığını göstermez.
Aşağıdaki dört satırın her biri ayrı bir programda olsun. Hata türleri bulunsun.
(a) String s = 5; Statik anlam hatası. Derleyici incompatible types: int cannot be converted to String der. Satırın biçimi doğrudur, tipler uyuşmaz.
(b) System.out.println("Merhaba"; Söz dizimi hatası. Kapanan parantez eksiktir; derleyici ')' or ',' expected der.
(c) int k = 0; System.out.println(10 % k); Çalışma zamanı hatası. Kalan işlemi de bölme gibi sıfıra yapılamaz; ArithmeticException: / by zero fırlatılır.
(d) double cevre = pi * r * r; Mantık hatası. Kod derlenir ve çalışır, ama çevre yerine alanı hesaplar. Doğrusu 2 * pi * r'dir.
2.1.5İfadeler ve işlem önceliği
Bir programın hesap yapan her parçası bir ifadedir. Bir ifadenin değerini doğru bulmak için iki şey bilinmelidir: işlemlerin hangi sırayla yapıldığı ve her işlemin hangi tiple sonuç verdiği. Bu kesim birinciyi, yani öncelik kurallarını işler; tipler de bu kuralların içine yer yer girer.
2.1.5.1İfade, operatör, işlenen
İfade (expression), değerlendirilince bir değer üreten yapıdır: 3 + 4, x > 0, pi * r * r. İfadedeki işlem işaretlerine operatör (operator), operatörün üzerinde çalıştığı değerlere işlenen (operand) denir.
3 + 4 ifadesinde + operatör, 3 ve 4 işlenendir; ifadenin değeri 7'dir. İşlenenler de birer ifade olabilir. (a + b) / 2 ifadesinde / operatörünün sol işleneni (a + b) ifadesidir. İfadeler böylece iç içe geçer ve bir ağaç oluşturur.
Java'daki temel aritmetik operatörler beştir: toplama +, çıkarma -, çarpma *, bölme / ve kalan %. Kalan operatörü, bölmenin kalanını verir: 7 % 3 = 1, çünkü 7 = 2 · 3 + 1'dir. Bunlara ek olarak karşılaştırma operatörleri (<, >, <=, >=, ==, !=) iki değeri karşılaştırır ve boolean bir değer, yani true ya da false üretir.
2.1.5.2Öncelik ve soldan sağa kuralı
İşlem önceliği (operator precedence), bir ifadede operatörlerin hangi sırayla uygulanacağını belirleyen kuraldır. Aynı öncelikteki aritmetik operatörler soldan sağa uygulanır.
Java'nın aritmetik öncelikleri okulda öğrenilen matematik kurallarıyla aynıdır. Tablo 2.1.7 bu notta kullanılan operatörleri yüksek öncelikten düşüğe doğru sıralar.
| Düzey | Operatörler | Yön |
|---|---|---|
| 1 | parantez ( ) | içten dışa |
| 2 | * / % | soldan sağa |
| 3 | + - (toplama, çıkarma, dizgi birleştirme) | soldan sağa |
| 4 | < > <= >= | soldan sağa |
| 5 | == != | soldan sağa |
| 6 | = (atama) | sağdan sola |
Tablodaki iki ayrıntı sık gözden kaçar. Birincisi, *, / ve % aynı düzeydedir. Biri ötekinden önce gelmez; hangisi soldaysa o önce yapılır. 12 / 3 * 2 ifadesi 12 / 6 = 2 değil, (12 / 3) · 2 = 8 verir. İkincisi, atama en düşük önceliklidir ve sağdan sola işler. Bu yüzden x = y = 7; önce y'ye 7 atar, sonra bu atamanın değerini (7) x'e atar.
Aynı öncelikteki işlemlerin soldan sağa yapılması, çıkarmada sonucu doğrudan etkiler. 10 - 4 - 3 ifadesi (10 − 4) − 3 = 3 diye hesaplanır. Sağdan başlansaydı 10 − (4 − 3) = 9 bulunurdu. Toplama ve çarpmada sıra sonucu değiştirmez, ama tam sayı bölmesi devreye girince değiştirir: 7 / 2 * 2 = 3 · 2 = 6, oysa 7 * 2 / 2 = 14 / 2 = 7'dir.
10 - 2 * 3 + 8 / 4 % 3 ifadesinin değeri bulunsun.
En yüksek öncelikli operatörler *, / ve %'dir. Soldan sağa: 2 * 3 = 6. Sonra 8 / 4 = 2, ardından 2 % 3 = 2. İfade 10 - 6 + 2 olur.
- ile + aynı düzeydedir, soldan sağa: 10 − 6 = 4, 4 + 2 = 6. Sonuç 6'dır ve tipi int'tir.
2.1.5.3İfade değerlendirme adım adım
Bilgisayar bir ifadeyi tek seferde değil, öncelik kurallarına göre adım adım sadeleştirir. Her adımda en yüksek öncelikli işlemlerden en soldakini yapar ve onun yerine sonucunu koyar. Fahrenhayttan Celsius'a dönüşüm formülünden alınan şu ifade bunu iyi gösterir: (72 - 32) / 9 * 5. Matematikte bu ifadenin değeri 40 · 5 / 9 ≈ 22,22'dir.
| Adım | İfade | Yapılan işlem |
|---|---|---|
| 1 | (72 - 32) / 9 * 5 | önce parantez: 72 − 32 = 40 |
| 2 | 40 / 9 * 5 | / ile * aynı düzeyde, soldan sağa: önce 40 / 9 |
| 3 | 4 * 5 | iki işlenen int: tam sayı bölmesi, 4,44… değil 4 (kalan 4 atıldı) |
| 4 | 20 | sonuç 20; beklenen 22,22… idi |
| 5 | (72.0 - 32) / 9 * 5 | double − int: 32, 32.0'a genişletilir; sonuç 40.0 |
| 6 | 40.0 / 9 * 5 | gerçek bölme: 4.444444444444445 |
| 7 | 4.444444444444445 * 5 | double × int → double |
| 8 | 22.22222222222222 | doğru sonuç |
(72 - 32) / 9 * 5 ifadesinin ağacı. Alttaki işlem önce yapılır; numaralar işlem sırasıdır: 1. çıkarma, 2. bölme, 3. çarpma.Şekil 2.1.7 ifadenin yapısını gösterir. Kökte çarpma vardır, çünkü en son o yapılır. Çarpmanın sol işleneni bölmedir, bölmenin sol işleneni de çıkarmadır. Soldan sağa kuralı ağaçta şu biçimde görünür: / ile * aynı öncelikte olduğu için soldaki (/) alta iner ve önce yapılır.
Tablo 2.1.8'in ilk dört adımı, öncelik kurallarının doğru uygulandığı hâlde sonucun yanlış çıkabileceğini gösterir. Hata öncelikte değil, tiptedir. Üçüncü adımda iki int bölünür ve kesir kaybolur. Bu kayıp sonraki çarpmayla büyür: 0,44'lük kayıp beşle çarpılır ve sonuç 2,22 eksik çıkar. Bu bir anlam (mantık) hatasıdır; derleyici uyarmaz.
Düzeltme için tek bir sabitin double yapılması yeterlidir. 72.0 yazılınca çıkarmanın sonucu double olur ve bu tip ifadenin geri kalanına yayılır. Bir işlemin işlenenlerinden biri double ise öteki de double'a genişletilir ve sonuç double olur. Ondalıklı sonuç gerekiyorsa işlenenlerden en az biri double olmalıdır; bu genişletme de bölmeden önce gerçekleşmelidir.
Son adımdaki 22.22222222222222'nin son basamağı da dikkat çekebilir. Matematikte 200 / 9 = 22,222… sonsuza kadar sürer. double yaklaşık 15–17 anlamlı basamak tutar; Java bu değeri yazarken onu tek başına belirleyen en kısa basamak dizisini seçer. Kayan noktalı sayılar bu yüzden her zaman yaklaşık değerdir.
Aynı hesap işlem sırası değiştirilerek (72 - 32) * 5 / 9 diye yazılsın.
Parantez: 40. Soldan sağa, önce çarpma: 40 · 5 = 200. Sonra bölme: 200 / 9 = 22 (tam sayı bölmesi, kalan 2 atılır). Sonuç 22'dir.
Kayıp küçülmüştür: tam sayı bölmesi en sona kaldığı için yalnız son kesir (0,22…) atılır. Çarpmayı bölmeden önce yapmak tam sayılarla çalışırken hatayı azaltır, ama ortadan kaldırmaz. Tam sonuç için yine double gerekir: 200 / 9.0 = 22.22222222222222.
2.1.5.4Tam sayı bölmesi ve kalan
Tam sayı bölmesi (integer division), iki tam sayının bölümünde kesirli kısmın atılmasıdır. Java'da / operatörünün iki işleneni de tam sayı tipindeyse (int, long gibi) sonuç da tam sayıdır. Sonuç sıfıra doğru kesilir: 7 / 2 = 3, -7 / 2 = −3.
“Sıfıra doğru kesilir” ifadesi negatif sayılarda önem kazanır. −7 / 2 = −3,5'tir. Kesir atılınca −3 kalır; sonuç sıfıra yaklaşır. Bazı diller (örneğin Python'un // operatörü) aşağı yuvarlar ve −4 verir. Java her zaman sıfıra doğru keser.
Kalan operatörü %, bölmeyle tutarlı çalışır. Her a ve sıfırdan farklı b için şu eşitlik sağlanır:
(a / b) * b + a % b == a
Bu eşitlikten kalanın işaretinin bölünenin işaretiyle aynı olduğu çıkar. -7 % 2 = −1'dir, çünkü (−3) · 2 + (−1) = −7'dir. -17 % 5 = −2'dir. Buna karşılık 7 % -2 = 1'dir; bölenin işareti kalanı etkilemez. Bir sayının çift olup olmadığı n % 2 == 0 ile sınanır. Negatif tek sayılarda kalan −1 olduğu için tek sayı sınaması n % 2 == 1 değil, n % 2 != 0 ile yapılmalıdır.
| İfade | Sonuç | Tip | Açıklama |
|---|---|---|---|
7 % 3 | 1 | int | 7 = 2 · 3 + 1 |
17 / 5 | 3 | int | 3,4'ün kesri atılır |
17 % 5 | 2 | int | 17 = 3 · 5 + 2 |
-7 / 2 | -3 | int | sıfıra doğru kesilir |
-7 % 2 | -1 | int | kalan bölünenin işaretini alır |
1 / 3 * 3 | 0 | int | 1 / 3 = 0, sonra 0 · 3 |
1.0 / 3 * 3 | 1.0 | double | gerçek bölme |
17 / 5.0 | 3.4 | double | bir işlenen double |
Tam sayı bölmesi her zaman bir hata değildir. Birçok algoritma onu bilerek kullanır. Saniyeyi dakika ve saniyeye ayırmak bunun tipik örneğidir: 135 saniye için 135 / 60 = 2 dakika, 135 % 60 = 15 saniye. Bir sayının basamaklarını ayırmak da aynı yolla yapılır: n % 10 son basamağı verir, n / 10 son basamağı atar. Sorun, ondalıklı bir sonuç beklenen yerde tam sayı bölmesinin farkında olmadan yapılmasıdır.
Bir öğrencinin üç sınav notu 70, 85 ve 90'dır. Ortalama üç biçimde hesaplansın:
int n1 = 70, n2 = 85, n3 = 90;
System.out.println((n1 + n2 + n3) / 3); // 81
System.out.println((n1 + n2 + n3) / 3.0); // 81.66666666666667
double ort = (n1 + n2 + n3) / 3;
System.out.println(ort); // 81.0Toplam 245'tir. İlk satır 245 / 3'ü tam sayı bölmesiyle 81 bulur. İkinci satırda 3.0 bir double olduğu için gerçek bölme yapılır ve 81.66666666666667 yazılır. Üçüncü satır en yanıltıcı olanıdır: değişken double olsa da sağ taraf önce 81 olarak hesaplanır; atama bunu 81.0'a çevirir.
2.1.5.5Üs alma
Java'da üs alma operatörü yoktur. Matematikte 23 diye yazılan işlem, Math.pow(2, 3) metoduyla yapılır ve sonuç double'dır: 8.0. Math.pow(2, 10) = 1024.0, Math.pow(2, 0.5) = 1.4142135623730951'dir. Küçük tam sayı kuvvetleri için çarpma da kullanılır: x * x, x2'dir.
Başka dillerden gelen iki alışkanlık burada sorun çıkarır. Python'daki ** operatörü Java'da yoktur; 2 ** 3 yazılırsa derleyici illegal start of expression der. Daha sinsi olanı ^ işaretidir. Java'da ^ geçerli bir operatördür, ama üs değil, bit düzeyinde dışlayıcı veya (XOR) işlemidir:
System.out.println(2 ^ 3); // 1: XOR
System.out.println(Math.pow(2, 3)); // 8.02'nin ikili gösterimi 010, 3'ünki 011'dir. XOR, iki sayının farklı olduğu bitlerde 1 üretir: 001, yani 1. Kod derlenir ve çalışır; yalnız sonuç yanlıştır. Derleme hatası vermediği için ^ ile üs almaya çalışmak bir mantık hatasıdır.
2.1.6Değişkenler, atama ve tipleme
Bir programın hesapladığı değerlerin bir yerde saklanması gerekir. Bu yer değişkendir. Değişkenin tipi, içinde ne tür bir değer saklanacağını ve o değerle hangi işlemlerin yapılabileceğini belirler.
2.1.6.1Değişken ve atama
Değişken (variable), bir ada bağlı, değeri değişebilen bellek alanıdır. Java'da bir değişken kullanılmadan önce tipi ve adıyla bildirilir: int sayac;. Atama (assignment) ise = işaretiyle, sağdaki ifadenin değerini soldaki değişkene yazmaktır.
Atama iki adımda gerçekleşir ve bu iki adımın sırası önemlidir:
- Sağ taraftaki ifade, değişkenlerin şu anki değerleriyle hesaplanır.
- Bulunan değer soldaki değişkene yazılır; değişkenin eski değeri silinir.
Bu sıra, matematikte anlamsız görünen x = x + 1 satırını anlamlı kılar. Matematikte bu bir denklemdir ve çözümü yoktur. Java'da ise bir komuttur: “x'in şimdiki değerine 1 ekle, sonucu x'e yaz.” x 5 ise önce 5 + 1 = 6 hesaplanır, sonra 6 yazılır.
Şu dört satır çalıştıktan sonra a ve b kaçtır? int a = 4; int b = a * 3; a = b - a; b = b / a;
1. a = 4. 2. Sağ taraf 4 · 3 = 12, b = 12. 3. Sağ taraf a'nın eski değeriyle hesaplanır: 12 − 4 = 8; sonra a = 8 olur. 4. 12 / 8 = 1 (tam sayı bölmesi, 0,5 atılır); b = 1.
Sonuç: a = 8, b = 1. Üçüncü satırda a sağda da solda da geçer. Sağdaki a okunurken değeri hâlâ 4'tür; yeni değer ancak hesap bittikten sonra yazılır.
Değişken adlarına tanımlayıcı (identifier) denir ve birkaç kurala uymaları gerekir. Ad bir harf, alt çizgi (_) ya da dolar işaretiyle ($) başlar; sonraki karakterler bunlara ek olarak rakam olabilir. Ad boşluk içeremez ve bir anahtar kelime olamaz. toplam2, _sayac, $fiyat ve ortalamaNot geçerlidir. 2toplam rakamla başladığı, ogrenci sayisi boşluk içerdiği, int de anahtar kelime olduğu için geçersizdir. Java geleneğinde değişken adları küçük harfle başlar ve sonraki sözcükler büyük harfle bitiştirilir: ortalamaNot, ogrenciSayisi.
Java büyük ve küçük harfi ayırır. Sayi ile sayi iki farklı addır:
int Sayi = 4;
System.out.println(sayi);Harf.java:4: error: cannot find symbol
System.out.println(sayi);
^
symbol: variable sayi
location: class Harf
1 errorİleti, derleyicinin sayi adlı bir değişken bulamadığını söyler. symbol satırı aranan adı, location satırı aramanın yapıldığı sınıfı verir. Aynı ileti, değişkeni hiç tanımlamamak ya da adını yanlış yazmak durumunda da çıkar.
Bir yerel değişken, değer verilmeden okunamaz:
int s;
System.out.println(s);IlkDeger.java:4: error: variable s might not have been initialized
System.out.println(s);
^
1 errorDerleyici, s'ye ulaşan bütün yolları inceler ve hiçbirinde atama bulamaz. Bu denetim, rastgele bir bellek değerinin sessizce kullanılmasını önler. Bir nesnenin alanları ise (Dikdortgen'in en ve boy'u gibi) otomatik olarak varsayılan değer alır: sayılar için 0, boolean için false. Yerel değişkenler için böyle bir varsayılan değer yoktur.
2.1.6.2Atama ile karşılaştırma
Java'da = atamadır, == karşılaştırmadır. Matematikteki eşittir işareti her iki anlamda da kullanıldığı için bu iki operatör sık karıştırılır. Bir koşulun içine yanlışlıkla = yazmak tipik bir hatadır:
int x = 100;
if (x = 100) {
System.out.println("yüz");
}EsitMi.java:4: error: incompatible types: int cannot be converted to boolean
if (x = 100) {
^
1 errorif deyiminin parantezi içinde bir boolean değer beklenir. x = 100 ise bir atamadır ve değeri atanan değer, yani int türünde 100'dür. Java bir int'i boolean'a çevirmez, bu yüzden satır derlenmez. Doğrusu if (x == 100)'dür. C dilinde tam sayılar koşul olarak kullanılabildiği için aynı hata derlenir ve koşul her zaman doğru çıkar; Java'nın tip denetimi bu hatayı derlemede yakalar.
Karşılaştırma operatörlerinin ürettiği değerler boolean değişkenlerde saklanabilir: boolean gecti = not >= 50;. Bu satırda önce not >= 50 karşılaştırması yapılır (öncelik tablosunda >=, ='den yüksektir), sonra sonucu gecti'ye atanır. Böyle bir değişken bir if deyiminde doğrudan kullanılabilir: if (gecti).
2.1.6.3Statik ve dinamik tipleme
Her programlama dili değerlerin tipleriyle ilgilenir; diller arasındaki fark, tiplerin ne zaman denetlendiğindedir.
Statik tipli dillerde (Java, C, C#) değişkenin tipi açıkça belirtilir, değişmez ve tip uyumu derleme zamanında denetlenir. Dinamik tipli dillerde (Python, JavaScript) değişkenin tipi önceden belirtilmez; değer atanınca belirlenir ve çalışma zamanında değişebilir. Bu iki yaklaşıma statik tipleme ve dinamik tipleme (static / dynamic typing) denir.
Farkı aynı fikrin iki dildeki yazımı gösterir. Python'da bir değişkene önce sayı, sonra dizgi atanabilir:
x = 5 # x şimdi int
x = "beş" # artık str: sorun yok
print(x + 1) # çalışırken:
# TypeError: can only concatenate
# str (not "int") to strİlk iki satır sorunsuz çalışır. Hata ancak üçüncü satır çalıştırılınca, bir dizgiye sayı eklenmeye çalışılınca ortaya çıkar. Bu satır programın sonunda ya da nadiren girilen bir dalda olsaydı, hata belki haftalarca fark edilmezdi. Java'da aynı fikir derlenmez:
int x = 5;
x = "beş"; // derlenmez:
// error: incompatible types:
// String cannot be converted to intx, int olarak bildirilmiştir ve bu tip değişmez. İkinci satırdaki atama derlemede reddedilir; program hiç çalışmaz.
| Dinamik tipli (Python, JavaScript) | Statik tipli (Java, C, C#) | |
|---|---|---|
| Tip bildirimi | Önceden belirtilmez; değer atanınca belirlenir | Açıkça belirtilir ve değişmez |
| Tip değişebilir mi? | Çalışma zamanında değişebilir | Hayır |
| Uyumsuz işlem | Çalışırken hata (TypeError) | Derlemede hata |
| Kazanç | Esneklik, kısa kod | Güvenlik; derleyici hızlı kod üretebilir |
Statik tiplemenin bir kazancı da hızdır. Derleyici a + b ifadesindeki iki değişkenin int olduğunu bildiği için doğrudan bir tam sayı toplama komutu üretir. Dinamik tipli bir dilde ise her toplamada işlenenlerin o anki tipine bakılması gerekir: iki sayı mı, iki dizgi mi, yoksa uyumsuz bir çift mi?
Java 10'dan bu yana yerel değişkenler var ile de bildirilebilir. Bu, Java'yı dinamik tipli yapmaz:
var adet = 3; // tip: int
var oran = adet / 2; // tip: int, değer 1
var ad = "Ali"; // tip: String
adet = "üç"; // derlenmez: String cannot be converted to intvar ile tip yazılmaz, ama derleyici tipi sağ taraftan çıkarır ve derlemede sabitler. adet'in tipi int'tir; ona daha sonra bir dizgi atanamaz. var yalnız yazımı kısaltır; tip denetimi aynen sürer. Bu yüzden var kullanılan bir değişkene ilk değer vermek zorunludur: var x; derlenmez, çünkü derleyici tipi çıkaracağı bir değer bulamaz.
var'ın değişkeni “her tipten değer alabilir” yaptığını sanmak. var oran = adet / 2; satırında adet 3 ise oran'ın tipi int, değeri 1'dir. var ondalıklı bir tip seçmez; sağ taraf ne üretiyorsa onu alır. Ondalıklı sonuç isteniyorsa ya double oran = adet / 2.0; yazılır ya da sağ taraf double üretecek biçimde düzenlenir.
2.1.7Dizgiler ve tipler
Bir program yalnız sayılarla çalışmaz. Kullanıcıya gösterilen her ileti, okunan her ad, ekrana yazılan her sonuç bir dizgidir. Sayılar ile dizgiler aynı ifadede buluştuğunda Java'nın kuralları kesin ama ilk bakışta şaşırtıcıdır. Bu kesim o kuralları işler.
Dizgi (string), karakterlerden oluşan bir metindir. Java'da dizgiler String sınıfının nesneleridir ve kaynak kodda çift tırnak içinde yazılır: "Merhaba". Tek tırnak içindeki tek bir karakter ('a') ise dizgi değil, char tipinde bir değerdir.
2.1.7.1Farklı tipler karşılaştırılmaz
Farklı veri tiplerini doğrudan karşılaştırmak hataya yol açar. Bir tam sayı ile bir dizgi karşılaştırılamaz; 5 ile "5" insan gözüne aynı görünse de biri bir sayı, öteki bir karakter dizisidir.
int sayi = 5;
String metin = "5";
boolean b = sayi == metin;error: bad operand types for binary operator '=='
first type: int
second type: StringDiller, tiplerin karşılaştırılması için kurallar ve tip dönüşümü (type conversion) yolları tanımlar. Karşılaştırma ancak iki değer aynı tipe getirildikten sonra anlamlıdır. Bunun iki yönü vardır: dizgi sayıya çevrilir ya da sayı dizgiye çevrilir.
int sayi = 5;
String metin = "5";
System.out.println(Integer.parseInt(metin) == sayi);
System.out.println(metin.equals(String.valueOf(sayi)));
System.out.println(metin.equals(sayi));true
true
falseInteger.parseInt(metin),"5"dizgisiniinttüründeki 5'e çevirir. İkiintartık==ile karşılaştırılabilir:true.String.valueOf(sayi), 5 sayısını"5"dizgisine çevirir. İki dizgiequalsmetoduyla karşılaştırılır:true.metin.equals(sayi)derlenir, ama her zamanfalsedöndürür.equalsher türden nesneyi parametre olarak kabul eder;sayiburada otomatik olarak birIntegernesnesine sarılır. BirStringhiçbir zaman birInteger'a eşit değildir.
Üçüncü satır en tehlikeli olanıdır, çünkü derleyici uyarmaz. Program çalışır ve yanlış bir cevap verir; bu bir mantık hatasıdır. parseInt da dikkat ister: dizgi bir tam sayı değilse, örneğin "5a" ya da "5.0" ise, çalışma zamanında NumberFormatException fırlatır.
Dizgilerin karşılaştırılmasında bir kural daha vardır: dizgiler == ile değil equals ile karşılaştırılır. == iki referansın aynı nesneyi gösterip göstermediğine bakar; equals ise iki dizginin karakterlerinin aynı olup olmadığına. İki ayrı nesne aynı metni taşıyabilir. Bu durumda == false, equals true verir. Kaynak kodda yazılan aynı sabit dizgiler Java'da tek bir nesne olarak saklandığı için == bazen yanlışlıkla doğru sonuç da verir. Bu tesadüfe güvenilmemelidir.
2.1.7.2Farklı tiplerde işlemler
Aynı operatör farklı tipler üzerinde farklı davranır. En belirgin örnek +'dır: sayılar üzerinde toplama, dizgiler üzerinde birleştirmedir.
Dizgi birleştirme (string concatenation), + ile dizgileri uç uca eklemektir. +'nın işlenenlerinden biri dizgiyse öteki de dizgiye çevrilir ve sonuç bir dizgidir. Genişletme dönüşümü (widening conversion) ise küçük bir sayı tipinin daha büyük bir tipe otomatik çevrilmesidir: int → double.
| İfade | Java sonucu | Tip | Açıklama |
|---|---|---|---|
3.0 + 4.0 | 7.0 | double | iki ondalıklı sayı |
3 + 4 | 7 | int | iki tam sayı |
3 + 4.0 | 7.0 | double | int otomatik olarak double'a genişletilir |
"3" + "4" | "34" | String | birleştirme |
3 + "4" | "34" | String | bir taraf dizgiyse öteki dizgiye çevrilir; hata değildir |
3 + true | derlenmez | — | bad operand types for binary operator '+' |
'a' + 1 | 98 | int | char bir sayısal koddur ('a' = 97) |
Tablonun her satırı bir kuralı gösterir. İki int'in toplamı int'tir. Bir int ile bir double toplanırken int önce double'a genişletilir; genişletmede bilgi kaybolmaz, çünkü her int değeri bir double'da tam olarak yazılabilir. Ters yön otomatik değildir. int n = 4.5; satırı derlenmez ve derleyici possible lossy conversion from double to int der: double'dan int'e geçişte kesir kaybolabilir ve Java bu kaybı programcının açıkça istemesini bekler.
3 + "4" satırı dillerin farklı tercihlerini gösterir. Java bunu hata saymaz ve 3'ü "3"'e çevirip birleştirir. Python'da 3 + "4" bir TypeError fırlatır; Python sayıyı kendiliğinden dizgiye çevirmez. JavaScript ise Java gibi birleştirir. Hiçbiri yanlış değildir; her dilin kuralı farklıdır ve programcı kullandığı dilin kuralını bilmelidir.
3 + true satırı Java'nın bir başka tercihidir: boolean bir sayı değildir. C'de doğru değeri 1 sayılır ve bu toplam 4 verir; Java ise satırı derlemez. Son satırdaki 'a' + 1 ise şaşırtıcı görünse de kurala uyar. char tipi bir karakteri Unicode kod numarasıyla saklar; 'a''nın kodu 97'dir. Aritmetik işlemde char, int'e genişletilir ve 97 + 1 = 98 bulunur. Sonucu karakter olarak görmek için açık dönüşüm gerekir: (char) ('a' + 1) ifadesi 'b' verir.
Farklı tipler arasında işlem, dilin kuralları bilinmiyorsa beklenmeyen sonuçlara ya da hatalara yol açar. Java'da bu kurallar kesindir ve derleyici onları her ifadeye aynı biçimde uygular. Kuralları bilen bir programcı için Java'nın davranışı her zaman öngörülebilirdir.
2.1.7.3Birleştirme soldan sağa
+ operatörü soldan sağa işler ve her adımda yalnız o adımın iki işlenenine bakar. Bir ifadede hem sayılar hem dizgiler varsa sonuç, dizginin hangi konumda olduğuna bağlıdır:
| İfade | Adımlar | Çıktı |
|---|---|---|
"Toplam: " + 2 + 3 | "Toplam: 2" + 3 | Toplam: 23 |
"Toplam: " + (2 + 3) | parantez önce: 5; "Toplam: " + 5 | Toplam: 5 |
2 + 3 + " toplam" | 2 + 3 = 5; 5 + " toplam" | 5 toplam |
1 + 2 + "3" + 4 + 5 | 3; "33"; "334"; "3345" | 3345 |
"" + 'A' + 'B' | "A"; "AB" | AB |
'A' + 'B' | 65 + 66 | 131 |
İlk satır, sık yapılan bir hatanın kaynağıdır. Programcı “Toplam: 5” yazdırmak ister, ama Java soldan başlar: "Toplam: " + 2 bir dizgidir. Bu dizgiye 3 eklenince yine birleştirme yapılır ve “Toplam: 23” çıkar. Sayıların önce toplanması için parantez gerekir. Üçüncü satırda ise dizgi en sondadır. Soldaki iki int toplanır ve 5 bulunur; birleştirme ancak son adımda yapılır.
Dördüncü satır kuralın genel hâlini gösterir. İlk dizgi görülene kadar sayılar toplanır; ilk dizgiden sonra her + birleştirmedir. Son iki satır char için aynı kuralı uygular. 'A' + 'B' ifadesinde dizgi yoktur; iki karakter sayı olarak toplanır ve 65 + 66 = 131 bulunur. Başa boş bir dizgi ("") eklemek ifadeyi en baştan birleştirmeye çevirir.
2.1.7.4Girdide ondalık ayırıcı
Tipler arası dönüşümün bir yüzü de kullanıcıdan okunan girdidir. Klavyeden gelen her şey önce metindir; Scanner sınıfı bu metni istenen tipe çevirir. nextDouble() metodu bir ondalıklı sayı okurken bilgisayarın yerel ayarını (locale) kullanır. Türkçe yerel ayarda ondalık ayırıcı virgüldür.
Scanner giris = new Scanner(System.in); // yerel ayar: Türkçe
double boy = giris.nextDouble();
// girdi "1,75" -> 1.75
// girdi "1.75" -> java.util.InputMismatchExceptionTürkçe bölge ayarlı bir bilgisayarda kullanıcı 1,75 yazarsa değer 1.75 olarak okunur. 1.75 yazarsa Scanner bunu bir ondalıklı sayı olarak tanımaz ve InputMismatchException fırlatır; program durur. Kaynak koddaki sabitler (65.2 gibi) ve println'ın çıktısı ise yerel ayardan etkilenmez; her zaman nokta kullanılır. Bu yüzden aynı program girdide virgül ister, çıktıda nokta yazar.
Sorunu çözmenin iki yolu vardır. Birincisi, kullanıcıya gösterilen istemdeki örneği yerel ayara uydurmaktır: “Boy (örn: 1,75)”. İkincisi, Scanner'ın yerel ayarını sabitlemektir: giris.useLocale(Locale.US); satırından sonra girdi her bilgisayarda noktayla okunur. Bu ayrıntı, aynı programın bir bilgisayarda çalışıp ötekinde çökmesinin sık nedenlerinden biridir.
2.1.8Algoritmadan programa
Önceki kesimler bir programın yapı taşlarını tek tek ele aldı: değerler, tipler, ifadeler, değişkenler. Bu kesim onları bir araya getirir. Bir program bu yapı taşlarından kurulmuş bir talimatlar dizisidir; talimatların sırası da en az kendileri kadar önemlidir.
2.1.8.1Program bir talimatlar dizisidir
Bilgisayar talimatları birer birer ve sırayla işler. Bir talimat bittikten sonra bir sonraki başlar ve her talimat bir öncekinin bıraktığı durumdan devam eder. Bu yüzden işlem sırasını doğru belirlemek önemlidir. Klasik bir örnek, iki değişkenin değerlerini yer değiştirmektir; buna takas denir.
int a = 3, b = 5;
a = b;
b = a;
System.out.println(a + " " + b);
int c = 3, d = 5;
int gecici = c;
c = d;
d = gecici;
System.out.println(c + " " + d);Program iki satır yazar: 5 5 ve 5 3. İlk takas başarısızdır, ikincisi başarılıdır. Tablo 2.1.13 iki parçayı satır satır izler.
| Satır | Deyim | Değişkenler | Çıktı |
|---|---|---|---|
| 1 | int a = 3, b = 5; | a = 3, b = 5 | |
| 2 | a = b; | a = 5, b = 5 (eski 3 kayboldu) | |
| 3 | b = a; | a = 5, b = 5 | |
| 4 | println | 5 5 | |
| 6 | int c = 3, d = 5; | c = 3, d = 5 | |
| 7 | int gecici = c; | c = 3, d = 5, gecici = 3 | |
| 8 | c = d; | c = 5, d = 5, gecici = 3 | |
| 9 | d = gecici; | c = 5, d = 3, gecici = 3 | |
| 10 | println | 5 3 |
a = b: a'ya 5 yazılır, eski değer 3 kaybolur. (b) b = a: b'ye a'nın şimdiki değeri (5) yazılır; takas başarısızdır. (c) Doğru yöntem: c'nin değeri önce gecici'de saklanır. (d) d'ye saklanan eski c değeri (3) yazılır; takas başarılıdır.İlk parçanın hatası, atamanın eski değeri silmesindedir. a = b; çalıştığında a'nın 3 değeri kaybolur ve hiçbir yerde saklanmamıştır. Sonraki b = a; satırı a'nın şimdiki değerini, yani 5'i kopyalar. Şekil 2.1.8'in (a) ve (b) kareleri bu kaybı gösterir. Satırların sırası değiştirilse de sonuç düzelmez: önce b = a; yazılırsa bu kez b'nin değeri kaybolur ve çıktı 3 3 olur.
Doğru yöntem, üzerine yazılacak değeri önceden üçüncü bir geçici değişkende saklamaktır. gecici = c satırı 3'ü güvene alır. Ardından c'nin üzerine güvenle yazılabilir. Son satır, saklanan değeri d'ye taşır. Takas üç atamayla yapılır ve sıraları değiştirilemez. Maliyeti sabittir: kaç değer olursa olsun bir takas üç atama ve bir ek değişken gerektirir.
Geçici değişken olmadan da tam sayılar takas edilebilir. a = 4, b = 9 için şu üç satır izlensin: a = a + b; b = a - b; a = a - b;
1. a = 4 + 9 = 13, b = 9. 2. b = 13 − 9 = 4. 3. a = 13 − 4 = 9.
Sonuçta a = 9, b = 4'tür. Yöntem çalışır, ama iki sakıncası vardır. Okunması zordur ve a + b toplamı int'in sınırını aşarsa taşma olur. Geçici değişkenli yöntem her tip için çalışır ve niyeti açıkça gösterir.
2.1.8.2İfadeler, deyimler ve programlar
Bir programın parçaları iki türlüdür: değer üretenler ve etki yaratanlar.
İfade bir değeri hesaplar ve sonucunu döndürür: 3 + 4 ifadesinin değeri 7'dir. Deyim (statement) ise bir etki yaratır: pi = 3.14159; bir değişkene değer atar, System.out.println(pi); ekrana yazar. Java'da deyimler genellikle ; ile biter. Bir program, sırayla çalışan deyimlerden oluşur.
İfadeler iç içe geçebilir ve deyimlerin içinde yer alabilir. double alan = pi * r * r; bir deyimdir; içinde pi * r * r ifadesi vardır, o ifade de pi * r ifadesini içerir. Tersi geçerli değildir: bir deyim, bir ifadenin içinde yer alamaz. println ekrana yazar, ama bir değer döndürmez; dönüş tipi void'dir. Bu yüzden onu bir hesabın içinde kullanmak anlamsızdır:
double d = 3 + System.out.println(pi);error: 'void' type not allowed hereTersine, değer üreten ama hiçbir etki yaratmayan bir ifade de tek başına deyim olamaz:
3 + 4; // değer üretir ama etkisi yokerror: not a statementHer ifade sonuna ; konarak deyime dönüşmez. Java buna yalnız dört ifade türünde izin verir: atama (x = 5;), artırma ve azaltma (i++;), metot çağrısı (println(x);) ve nesne oluşturma (new Dikdortgen(1, 2);). Bunların ortak yanı bir etki yaratmalarıdır. 3 + 4;, x * 2; ya da x > 3; hesaplanır ve sonuç hemen atılır; bu boşa yapılmış bir işlemdir ve derleyici onu bir yazım yanlışı sayar.
Burada ince bir nokta vardır. Java'da atama da bir değer üretir: atanan değeri. Bu yüzden şu satır derlenir:
int y = (x = 4) + 1; → x = 4, y = 5
Parantezdeki atama önce yapılır ve değeri 4 olur; buna 1 eklenir. Atamanın değer üretmesi, x = y = 7; gibi zincirleme atamaları da mümkün kılar. Yine de atamayı bir ifadenin ortasına gömmek okunmayı zorlaştırır ve tercih edilmez. Programın her satırında tek bir etki olması, izlemeyi de hata aramayı da kolaylaştırır.
println'ın yazdırdığı değeri bir değişkene almaya çalışmak: int s = System.out.println(5);. println ekrana yazar ama bir değer döndürmez; derleyici incompatible types: void cannot be converted to int iletisini verir. Bir hesabın sonucu hem kullanılacak hem yazdırılacaksa önce değişkene atanır, sonra yazdırılır.
2.1.8.3Fahrenhayttan Celsius'a
Bir program yazmanın sırası bellidir: önce algoritma, sonra kod. Algoritma problemi kesin adımlara ayırır; kod bu adımları dilin kurallarıyla yazar. Fahrenhayt cinsinden verilen bir sıcaklığı Celsius'a çeviren bir program bu sırayla kurulsun. Dönüşüm formülü C = (F − 32) · 5 / 9'dur.
Algoritma üç adımdır:
1. Fahrenhayt değeri f'yi oku.
2. c = (f − 32) · 5 / 9 değerini hesapla.
3. c'yi yazdır.Her adımın Java'da bir karşılığı vardır. Okuma için Scanner, hesap için bir atama deyimi, yazdırma için println kullanılır:
import java.util.Scanner;
public class Fahrenhayt {
public static void main(String[] args) {
Scanner giris = new Scanner(System.in);
System.out.print("Fahrenhayt: ");
double f = giris.nextDouble();
double c = (f - 32) * 5 / 9;
System.out.println("Celsius: " + c);
}
}Kullanıcı 100 girdiğinde ekranda şu görünür:
Fahrenhayt: 100
Celsius: 37.77777777777778Kod satır satır şöyle çalışır. import java.util.Scanner; satırı Scanner sınıfını kullanılabilir kılar. new Scanner(System.in) klavyeden okuyan bir nesne oluşturur. print, println'dan farklı olarak satır sonuna geçmez; bu yüzden kullanıcının girdiği 100 aynı satırda görünür. giris.nextDouble() bir sayı okur ve f'ye atar. Hesap satırında önce f - 32 = 68.0 bulunur; f double olduğu için bu sonuç da double'dır. Ardından 68.0 · 5 = 340.0 ve 340.0 / 9 = 37.77777777777778 hesaplanır.
Formülün yazılış biçimi burada da önemlidir. Aynı formül 5 / 9 * (f - 32) diye yazılırsa sonuç 0.0 olur. Öncelik kuralları değişmemiştir: 5 / 9 soldadır ve önce yapılır. İki işlenen de int olduğu için tam sayı bölmesi 0 verir ve 0 · 68.0 = 0.0 bulunur. Derleyici bunu bir hata olarak görmez; program 100 °F için 0 °C yazar. Düzeltmenin iki yolu vardır: bölmeyi ondalıklı yapmak (5.0 / 9 * (f - 32)) ya da çarpmayı öne almak ((f - 32) * 5 / 9). İkincisi, f double olduğu sürece doğru çalışır; f - 32 double ürettiği için sonraki bölme de gerçek bölmedir.
| Yazılış | f double | f int |
|---|---|---|
(f - 32) * 5 / 9 | 37.77777777777778 | 37 (340 / 9, kesir atılır) |
5 / 9 * (f - 32) | 0.0 | 0 |
5.0 / 9 * (f - 32) | 37.77777777777778 | 37.77777777777778 |
Tablonun son sütunu, tipin sonucu nasıl değiştirdiğini gösterir. f int olsa bile 5.0 ifadeye bir double soktuğu için sonuç doğru çıkar. Program yazmanın sırası bu küçük örnekte bile görünür: algoritma doğrudur, Java koduna çeviri sırasında yapılan küçük bir yazım farkı sonucu bozar. Bu tür hataları bulmanın yolu, sonucu bilinen bir girdiyle denemektir. 212 °F'nin 100 °C, 32 °F'nin 0 °C olduğu bilinir; program bu iki değeri doğru çeviriyorsa formül büyük olasılıkla doğru yazılmıştır.
2.1.9Popüler programlama dilleri
Programlama dilleri farklı görevler için farklı avantajlar sunar ve programcılar projenin ihtiyacına göre en uygun dili seçer. Dillerin ne kadar kullanıldığı her yıl anketler ve dizinlerle izlenir. Stack Overflow'un geliştirici anketi, TIOBE dizini ve IEEE Spectrum'un sıralaması bunların bilinenleridir. Her biri farklı bir ölçüt kullanır: ankete katılanların beyanı, arama motoru sonuçları ya da iş ilanları. Bu yüzden sıralamalar birbirinden biraz farklıdır, ama ilk sıralarda hep aynı diller görülür.
| Dil | Öne çıktığı alan |
|---|---|
| Python | veri analizi, yapay zekâ, web geliştirme |
| Java | büyük ölçekli kurumsal uygulamalar, Android |
| JavaScript | web tarayıcıları, web geliştirme |
| C# | Windows uygulamaları, oyun geliştirme |
| C++ | oyun, sistem programlama, performans odaklı yazılım |
| Rust | güvenli ve hızlı sistem programlama |
| Swift | Apple platformlarında uygulama geliştirme |
| Kotlin | Android uygulama geliştirme (JVM üzerinde çalışır) |
| Go (Golang) | basit ve etkili; ağ ve bulut uygulamaları |
Tablo önceki kesimlerdeki sınıflandırmalarla birlikte okunabilir. C, C++, Rust ve Go doğrudan makine koduna derlenir ve performansın önemli olduğu yerlerde öne çıkar. Java, Kotlin ve C# bir ara koda derlenip bir sanal makinede çalışır; taşınabilirlik ile hızı dengeler. Python ve JavaScript genellikle yorumlanır ve hızlı geliştirme gereken yerlerde tercih edilir. Kotlin'in Java ile aynı JVM'de çalışması, iki dilin aynı projede birlikte kullanılabilmesini sağlar.
Mühendislik açısından tek bir “en iyi dil” yoktur. Bir mikrodenetleyicinin yazılımı için C, bir sinyal işleme prototipi için MATLAB ya da Python, bir masaüstü ölçüm uygulaması için Java uygun düşebilir. Bir dilde öğrenilen kavramlar, yani tipler, ifadeler, öncelik, atama ve deyim sırası, öteki dillere büyük ölçüde taşınır. Yeni bir dil öğrenmek çoğu zaman bu kavramların o dildeki yazılışını ve küçük kural farklarını öğrenmektir.
2.1.10Sık yapılan hatalar
Programlamaya yeni başlayanların hataları büyük ölçüde aynı birkaç kaynaktan gelir. Aşağıdaki sekiz hata, bu notta ayrıntılı ele alınan kuralların özetidir. Her birinde derleyicinin ya da JVM'in tepkisi JDK 21 ile alınmıştır.
| Hata | Örnek | Belirti | Düzeltme |
|---|---|---|---|
| Noktalı virgülü unutmak | System.out.println("Merhaba") | ';' expected; ^ satırın sonunu gösterir | deyimin sonuna ; |
= ile =='yi karıştırmak | if (x = 100) | int cannot be converted to boolean | if (x == 100) |
| Tam sayı bölmesi | (a + b) / 2 ile 7 ve 8'in ortalaması | 7 (beklenen 7,5); derleyici uyarmaz | (a + b) / 2.0 |
Üs için ** ya da ^ | 2 ** 3; 2 ^ 3 | illegal start of expression; ^ derlenir ama XOR'dur (1) | Math.pow(2, 3) |
| Dizgi + sayı sırası | "Toplam: " + 2 + 3 | Toplam: 23 | "Toplam: " + (2 + 3) |
| Büyük/küçük harf | Sayi tanımlayıp sayi kullanmak | cannot find symbol | adı tanımlandığı gibi yazmak |
| Değer atamadan kullanmak | int s; System.out.println(s); | variable s might not have been initialized | bildirimde ilk değer: int s = 0; |
| Ondalık ayırıcı | Türkçe yerel ayarda nextDouble()'a 7.5 girmek | InputMismatchException | 7,5 girmek ya da useLocale(Locale.US) |
Tablodaki hatalar üç gruba ayrılır. Noktalı virgül, büyük/küçük harf, ilk değer ve =/== hataları derleme hatasıdır; derleyici yakalar ve iletisi hatanın yerini gösterir. Ondalık ayırıcı hatası bir çalışma zamanı hatasıdır; program derlenir ama belli bir girdiyle çöker. Tam sayı bölmesi, ^ ile üs ve birleştirme sırası ise mantık hatasıdır. Bu sonuncular en tehlikeli olanlardır, çünkü program hiçbir belirti göstermeden yanlış bir sonuç üretir. Onları bulmanın tek yolu, sonucu bilinen girdilerle denemektir.
Derleme hatalarıyla ilgili genel bir alışkanlık da işe yarar: ileti dikkatle okunur ve düzeltmeye ilk hatadan başlanır. int = x 5; örneğinde olduğu gibi, tek bir yanlış bir dizi iletiye yol açabilir. İlk hata düzelince ötekiler çoğu zaman kendiliğinden kaybolur. Hata iletisinin gösterdiği satırda bir sorun görünmüyorsa bir önceki satıra da bakılır; özellikle kapanmamış bir parantez ya da tırnak, hatayı birkaç satır ileriye taşıyabilir.
2.1.11Özet
- Programlama dili, bir makinenin yapabileceği hesaplamaları ifade etmek için tasarlanmış yapay bir dildir. Program hem makineye talimat hem de programcılar arasında bir iletişim aracıdır.
- Diller dört ölçüte göre sınıflandırılır: soyutlama düzeyi (düşük ya da yüksek seviye), uygulama alanı (genel amaçlı ya da hedefe odaklı), yürütme biçimi (derlenen ya da yorumlanan) ve paradigma.
- Java'da
javackaynak kodu bytecode'a (.class) derler; JVM bytecode'u yorumlar ve sık çalışan kısımları JIT ile makine koduna çevirir. Aynı.classdosyası JVM olan her platformda çalışır. - Dört temel paradigma buyruksal, fonksiyonel, mantıksal ve nesne yönelimlidir. Java temelde nesne yönelimli ve buyruksaldır; Java 8'den bu yana fonksiyonel öğeler de içerir.
- Söz dizimi biçim kurallarıdır. Statik anlam, söz dizimi doğru kodun derlemede denetlenen anlam kurallarıdır (tip uyumu). Anlam ise kodun ne yaptığıdır; tek ve kesindir, ama programcının beklediği olmayabilir.
- Hatalar dört türlüdür: söz dizimi ve statik anlam hataları derlemede, çalışma zamanı hataları çalışırken istisnayla, mantık hataları ancak testle bulunur.
- İfadeler önce parantez, sonra
* / %, sonra+ -sırasıyla, aynı düzeyde soldan sağa değerlendirilir. İki tam sayının bölümü tam sayıdır ve sıfıra doğru kesilir. Java'da üs operatörü yoktur;Math.powkullanılır. - Atama iki adımdır: önce sağ taraf şu anki değerlerle hesaplanır, sonra soldaki değişkene yazılır.
=atama,==karşılaştırmadır. - Java statik tiplidir: tipler derlemede denetlenir ve değişmez.
varbile tipi derlemede sabitler.intiledouble'ın işlemidouble'dır. Bir işlenen dizgiyse+birleştirmedir ve soldan sağa işler. - Dizgiler
equalsile karşılaştırılır. Farklı tipler önce aynı tipe dönüştürülür:Integer.parseInt,String.valueOf. - Program sırayla çalışan deyimlerden oluşur. İfade değer üretir, deyim etki yaratır. Deyimlerin sırası sonucu belirler; takas üçüncü bir geçici değişkenle yapılır.
- Bir program önce algoritma olarak tasarlanır, sonra koda çevrilir. Çeviride tip ve öncelik ayrıntıları sonucu değiştirebilir.
2.1.12Kavram listesi
- programlama dili programming language
- kaynak kod source code
- assembly dili assembly language
- derleyici compiler
- yorumlayıcı interpreter
- bytecode (ara kod) bytecode
- Java Sanal Makinesi (JVM) Java Virtual Machine
- JIT (tam zamanında) derleyici just-in-time compiler
- programlama paradigması programming paradigm
- nesne yönelimli programlama object-oriented programming
- alan field
- yapıcı constructor
- referans reference
- söz dizimi syntax
- statik anlam static semantics
- anlam semantics
- çalışma zamanı hatası runtime error
- mantık hatası logic error
- istisna exception
- ifade expression
- işlenen operand
- işlem önceliği operator precedence
- tam sayı bölmesi integer division
- değişken variable
- atama assignment
- tanımlayıcı identifier
- statik / dinamik tipleme static / dynamic typing
- genişletme dönüşümü widening conversion
- tip dönüşümü type conversion
- dizgi string
- dizgi birleştirme string concatenation
- deyim statement
2.1.13Kendini Dene
17 / 5 * 5 + 17 % 5ve17 / 5.0ifadelerinin değerleri ve tipleri nedir?Cevap
Birincisi:
17 / 5= 3 (tam sayı bölmesi), 3 · 5 = 15,17 % 5= 2, toplam 17, tipiint'tir. Bu sonuç rastlantı değildir:(a / b) * b + a % bher zamana'ya eşittir. İkincisi: 5.0doubleolduğu için gerçek bölme yapılır; 3.4, tipidouble'dır.System.out.println(2 * 3 + "x" + 2 * 3);ne yazdırır?Cevap
6x6.*operatörünün önceliği+'dan yüksektir; iki çarpma, birleştirmelerden önce yapılır ve ifade6 + "x" + 6olur. Soldan sağa:6 + "x"="6x", ardından"6x" + 6="6x6". Çarpmalar parantezsiz de doğru çalışır; sorun yalnız+ile toplama yapılmak istendiğinde çıkar.Her satır ayrı bir programda olsun. Hata türü (söz dizimi, statik anlam, çalışma zamanı, mantık) nedir? (a)
boolean b = 1;(b)int n = 5; int y = 10 / (n - 5);(c) Dairenin çevresi içindouble cevre = 2 * pi + r;Cevap
(a) Statik anlam:
incompatible types: int cannot be converted to boolean. Java'dabooleanbir sayı değildir. (b) Çalışma zamanı: kod derlenir, aman - 5= 0 olduğu içinArithmeticException: / by zerofırlatılır. (c) Mantık: kod derlenir ve çalışır, ama formül yanlıştır; doğrusu2 * pi * r'dir.Şu satırlar çalıştıktan sonra
xveykaçtır?int x = 6; int y = x / 4; x = x * y + x % 4; y = x - y;Cevap
y= 6 / 4 = 1 (tam sayı bölmesi). Üçüncü satırın sağ tarafı eskix(6) ile hesaplanır: 6 · 1 + 6 % 4 = 6 + 2 = 8;x= 8. Son satır: 8 − 1 = 7. Sonuçx= 8,y= 7.a= 1,b= 2,c= 3 iken değerler sağa döndürülmek isteniyor:a←c,b←a,c←b. Geçici bir değişkenle hangi sırayla yazılır? Sonuç ne olur?Cevap
Önce üzerine ilk yazılacak değer saklanır.
int g = c; c = b; b = a; a = g;sırası doğrudur:g= 3,c= 2,b= 1,a= 3. Sonuç 3 1 2'dir. Her atama, değeri daha önce başka bir yere kopyalanmış bir değişkenin üzerine yazar; hiçbir değer kaybolmaz.IntStream.range(2, 6).sum()ne verir? Aynı hesabın buyruksal karşılığı nasıl yazılır?Cevap
rangeüst sınırı dahil etmez: 2, 3, 4, 5 sayıları toplanır ve 14 bulunur. Buyruksal karşılığı:int t = 0; for (int i = 2; i < 6; i++) t = t + i;. Döngü koşulunda<=değil<kullanılır;rangeClosed(2, 6)olsaydı 6 da dahil olur ve 20 bulunurdu.int c = 37;ikenc * 9 / 5 + 32,9 / 5 * c + 32vec * 9 / 5.0 + 32ifadeleri ne verir?Cevap
Birincisi: 37 · 9 = 333, 333 / 5 = 66 (kesir atılır), 66 + 32 = 98. İkincisi: 9 / 5 = 1 (tam sayı bölmesi), 1 · 37 + 32 = 69; büyük bir hata. Üçüncüsü: 333 / 5.0 = 66.6, 66.6 + 32 = 98.6 (
double), doğru değer budur.xbirintdeğişken olsun. Aşağıdakilerden hangileri tek başına bir deyim olarak derlenir?x++;,x + 1;,Math.pow(x, 2);,x == 3;Cevap
x++;derlenir:x'i bir artırır, yani bir etki yaratır.Math.pow(x, 2);de derlenir: metot çağrıları deyim olabilir, döndürülen değer atılır.x + 1;vex == 3;derlenmez ve ikisi denot a statementiletisini verir; yalnız bir değer üretirler, etkileri yoktur.
2.1.14İleri okuma
- Oracle, The Java Tutorials, “Language Basics” bölümü: değişkenleri, operatörleri, öncelik tablosunu, ifadeleri ve deyimleri kısa örneklerle anlatır. Java'nın resmî ve ücretsiz başlangıç kaynağıdır.
- A. B. Downey ve C. Mayfield, Think Java (2. baskı, 2019), Bölüm 1–2: programlama dilini, derleme ve yorumlamayı, hata türlerini ve hata iletilerini okumayı anlatır. Anlatım yeni başlayan okura göredir.
- J. V. Guttag, Introduction to Computation and Programming Using Python (3. baskı, 2021), Bölüm 2: söz dizimi, statik anlam ve anlam ayrımının kaynağıdır. Ayrımı Python örnekleriyle verdiği için dinamik tiplemeyle karşılaştırmaya da elverişlidir.
- J. Gosling ve diğerleri, The Java Language Specification, Java SE 21 Edition, Bölüm 15: ifadelerin değerlendirme sırasını, tam sayı bölmesini ve dizgi birleştirmeyi kesin kurallarla tanımlar. Bir ifadenin değeri konusunda son söz bu belgededir.
2.1.15Kaynaklar
- P. Deitel ve H. Deitel, Java: How to Program, Early Objects, 11. baskı, Pearson, 2017 (Bölüm 1–2).
- A. B. Downey ve C. Mayfield, Think Java: How to Think Like a Computer Scientist, 2. baskı, O'Reilly, 2019 (Bölüm 1–2).
- J. Gosling ve diğerleri, The Java Language Specification, Java SE 21 Edition, Oracle, 2023 (Bölüm 3: Sözcük yapısı, Bölüm 15: İfadeler).
- J. Guttag, Introduction to Computation and Programming Using Python, 3. baskı, MIT Press, 2021 (Bölüm 2: söz dizimi, statik anlam ve anlam).
- C. S. Horstmann, Core Java, Volume I: Fundamentals, 12. baskı, Pearson, 2022 (Bölüm 2–3).
- Oracle, The Java Tutorials: “Language Basics” (değişkenler, operatörler, ifadeler, deyimler ve bloklar).
- K. Sierra ve B. Bates, Head First Java, 3. baskı, O'Reilly, 2022.
İlgili sayfalar: Bölüm 2.1 sunumu · Çalışma soruları · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.