Vaka çalışması: adım adım not defteri (GradeBook)
Dönemin bütün parçalarını tek programda birleştiriyoruz: Student sınıfı (dizi alanı, ortalama, harf notu), GradeBook sınıfı (ArrayList, en iyi öğrenci, geçen sayısı, arama), printf raporu ve yıldızlı histogram. Final yazma sorularının provası.
Son hafta yeni bir konu yok. Dönem boyunca öğrendiğin her şeyi tek, gerçekçi bir programda birleştiriyoruz: bir dersin not defteri. Program küçük ama içinde sınıf, kurucu, private alan, dizi alanı, static yardımcı metot, ArrayList, toString, printf, iç içe döngü ve null kontrolü var. Final sınavındaki uzun yazma sorusu tam olarak böyle görünür: “Şu sınıfı yaz, sonra onu kullanan şu sınıfı yaz.”
Strateji: programı bir kerede yazmaya çalışma. Küçük bir parça yaz, main’de dene, çalıştığını gör, sonra büyüt (incremental development).
Adım 0: Problemi parçala
İstek: “Bir dersin öğrencilerini ve notlarını tut. Her öğrencinin ortalamasını ve harf notunu hesapla. Sınıf ortalamasını, en başarılı öğrenciyi ve geçen öğrenci sayısını bul. Bir rapor ve bir harf notu histogramı yazdır.”
İsimler (noun) sınıf ve alan adaylarıdır, fiiller (verb) metot adaylarıdır:
| Sınıf | Alanlar | Metotlar |
|---|---|---|
Student | ad, not dizisi | ortalama, en yüksek not, harf notu, toString |
GradeBook | ders adı, öğrenci listesi | öğrenci ekle, sınıf ortalaması, en iyi öğrenci, geçenleri say, ara, rapor, histogram |
+-------------------------------------+ +----------------------------------------+
| Student | | GradeBook |
+-------------------------------------+ +----------------------------------------+
| - created : int (static) | | - courseName : String |
| - name : String (final) | 0..* | - students : ArrayList<Student> |
| - scores : int[] (final) |<------| |
+-------------------------------------+ +----------------------------------------+
| + Student(name : String, | | + GradeBook(courseName : String) |
| scores : int[]) | | + addStudent(s : Student) : void |
| + getName() : String | | + classAverage() : double |
| + setScore(i : int, v : int) : void | | + topStudent() : Student |
| + average() : double | | + countPassing(limit : double) : int |
| + highest() : int | | + find(name : String) : Student |
| + letterFor(avg : double) : String | | + printReport() : void |
| (static) | | + printHistogram() : void |
| + letter() : String | +----------------------------------------+
| + toString() : String |
+-------------------------------------+
GradeBook bir öğrenci listesine sahip (composition). Ok, GradeBook’un Student’ları kullandığını gösteriyor.
Adım 1: Student sınıfı
class Student {
private static int created = 0; // kaç öğrenci oluşturuldu
private final String name;
private final int[] scores;
public Student(String name, int[] scores) {
this.name = name;
this.scores = new int[scores.length]; // kendi dizisi
for (int i = 0; i < scores.length; i++) this.scores[i] = scores[i];
created++;
}
public String getName() { return name; }
public static int getCreated() { return created; }
public void setScore(int index, int value) {
if (index >= 0 && index < scores.length && value >= 0 && value <= 100) {
scores[index] = value;
}
}
public double average() {
int sum = 0;
for (int sc : scores) sum += sc;
return (double) sum / scores.length; // int bölme tuzağı yok
}
public int highest() {
int max = scores[0];
for (int sc : scores) if (sc > max) max = sc;
return max;
}
public static String letterFor(double avg) { // nesneye ihtiyaç yok
if (avg >= 90) return "A";
else if (avg >= 80) return "B";
else if (avg >= 70) return "C";
else if (avg >= 60) return "D";
else return "F";
}
public String letter() { return letterFor(average()); }
@Override
public String toString() {
return String.format("%-6s %6.2f %s", name, average(), letter());
}
}
Tasarım kararları:
- Alanlar
private final. Ad ve not dizisinin referansı bir kez atanır. (final dizinin elemanları yine değişebilir; setScore bunu kontrollü yapıyor.) - Kurucu diziyi kopyalıyor.
this.scores = scores;yazsaydık dışarıdaki dizi ile aynı diziyi paylaşırdık ve dışarıda yapılan her değişiklik öğrencinin notunu değiştirirdi (13. haftadaki getter tuzağının kurucu hali). Buna savunmacı kopya (defensive copy) denir. - setScore iki şeyi kontrol ediyor: indeksin geçerli olması (yoksa
ArrayIndexOutOfBoundsException) ve notun 0..100 arasında olması. - letterFor static. Harf hesaplamak için bir öğrenci nesnesine gerek yok; sadece bir sayı yeter. letter() ise örnek metodu ve kendi ortalamasını letterFor’a veriyor.
- toString printf biçimiyle.
%-6sadı 6 karakterlik alana sola yaslar,%6.2fortalamayı sağa yaslı ve 2 basamaklı yazar. Rapor böylece hizalı çıkar.
Test et:
Student s = new Student("Deniz", new int[]{70, 85, 90});
System.out.println(s.getName() + " " + s.average() + " " + s.highest() + " " + s.letter());
System.out.println(s);
Deniz 81.66666666666667 90 B
Deniz 81.67 B
new int[]{70, 85, 90} dizi değişkeni açmadan dizi oluşturup doğrudan argüman olarak vermenin yolu.
Adım 2: GradeBook sınıfı
class GradeBook {
private String courseName;
private ArrayList<Student> students = new ArrayList<>();
public GradeBook(String courseName) { this.courseName = courseName; }
public void addStudent(Student s) { students.add(s); }
public int size() { return students.size(); }
public double classAverage() {
if (students.isEmpty()) return 0.0;
double total = 0;
for (Student s : students) total += s.average();
return total / students.size();
}
public Student topStudent() {
if (students.isEmpty()) return null;
Student top = students.get(0);
for (Student s : students) {
if (s.average() > top.average()) top = s;
}
return top;
}
public int countPassing(double limit) {
int count = 0;
for (Student s : students) if (s.average() >= limit) count++;
return count;
}
public Student find(String name) {
for (Student s : students) {
if (s.getName().equals(name)) return s;
}
return null;
}
}
Dikkat: GradeBook hiçbir yerde not dizisine dokunmuyor. Ortalamayı öğrencinin kendisine soruyor (s.average()). Her sınıf kendi verisinden sorumlu; buna kapsülleme demiştik.
Test et:
GradeBook book = new GradeBook("SEN1001");
book.addStudent(new Student("Deniz", new int[]{70, 85, 90}));
book.addStudent(new Student("Ece", new int[]{95, 88, 92}));
book.addStudent(new Student("Can", new int[]{40, 55, 49}));
book.addStudent(new Student("Mert", new int[]{60, 72, 66}));
System.out.println(book.size());
System.out.printf("%.2f%n", book.classAverage());
System.out.println(book.topStudent().getName());
System.out.println(book.countPassing(60));
System.out.println(book.find("Ece").letter() + " " + book.find("Zeynep"));
4
71.83
Ece
3
A null
Bulunamayan Zeynep için find null döndürdü; String birleştirmede null yazısı olarak göründü. book.find("Zeynep").letter() yazsaydın NullPointerException alırdın.
Adım 3: Rapor ve histogram
public void printReport() {
System.out.println("Course: " + courseName);
for (Student s : students) System.out.println(s); // toString otomatik
System.out.printf("Class average: %.2f%n", classAverage());
System.out.println("Top: " + topStudent().getName());
}
public void printHistogram() {
String[] labels = {"A", "B", "C", "D", "F"};
for (String lab : labels) { // dış: harfler
System.out.print(lab + ": ");
for (Student s : students) { // iç: öğrenciler
if (s.letter().equals(lab)) System.out.print("*");
}
System.out.println();
}
}
Course: SEN1001
Deniz 81.67 B
Ece 91.67 A
Can 48.00 F
Mert 66.00 D
Class average: 71.83
Top: Ece
A: *
B: *
C:
D: *
F: *
TUZAK · null dönebilen metodu körlemesine kullanmak. printReport boş bir defterde
Course: BOSveClass average: 0.00basıptopStudent().getName()satırında NullPointerException ile çöktü. Null dönebilecek bir sonucu önce bir değişkene al ve kontrol et:Student top = topStudent(); if (top != null) System.out.println("Top: " + top.getName());.
Bu programda dönemin hangi konusu nerede?
| Konu (hafta) | Programdaki yeri |
|---|---|
| printf, String.format (2) | toString ve rapor hizalaması |
| if / else if zinciri (3) | letterFor |
| Döngüler, iç içe döngü (4) | average, highest, histogram |
| static metot (5) | letterFor, getCreated |
| Diziler, kopyalama (6-7) | scores alanı, savunmacı kopya |
| ArrayList (7) | students listesi |
| Sınıf, private, get/set (10) | iki sınıfın tamamı, setScore doğrulaması |
| Kurucu, UML, nesne listesi (11) | kurucular, diyagram, ArrayList<Student> |
| this, static alan, final, toString (12) | this.name, created, final alanlar, toString |
| Composition, null, referanslar (13) | GradeBook has-a Student, find/topStudent null |
Bir final sorusunda bu tablonun herhangi bir satırı tek başına sorulabilir. Hepsini birlikte kurabiliyorsan dersin hedefine ulaştın.
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
| Önce bütün programı yaz, sonra çalıştır | Sınıf sınıf yaz, her adımda main’de test et |
Kurucuda this.scores = scores; yeter | Dış dizi paylaşılır; kopyala |
Ortalama sum / scores.length | (double) sum / scores.length |
| letterFor örnek metodu olmalı | Sadece sayıya bağlı; static yeterli |
| GradeBook not dizisine doğrudan erişir | Öğrenciye sorar: s.average() |
Boş listede students.get(0) sorun değil | IndexOutOfBoundsException; önce isEmpty() |
| find her zaman bir öğrenci döndürür | Bulamazsa null; çağıran kontrol etmeli |
| toString içinde println | return ile String döndür |
%6.2f 6 ondalık basamak | Toplam genişlik 6, ondalık 2 |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
Using the Student class of this page, what is printed?
Student s = new Student("A", new int[]{50, 60, 80});
System.out.println(s.average() + " " + s.letter());Cevabı göster
Toplam 190, (double) 190 / 3 = 63.333... double tam haliyle basılır (son basamaktaki 6 kayan nokta yuvarlamasından). 63.3 ≥ 60 ama < 70 → D.
What is printed?
Student s = new Student("B", new int[]{89, 90, 91});
System.out.println(s.letter() + " " + s.highest());Cevabı göster
Ortalama tam 90.0, >= 90 → A. En yüksek not 91.
The Student constructor copies the array it receives into its own new array. What is printed?
int[] marks = {10, 20, 30};
Student s = new Student("C", marks);
marks[0] = 100;
System.out.println(s.highest());Cevabı göster
Kurucu this.scores = new int[...] ile kendi dizisini açıp elemanları tek tek kopyaladı (defensive copy). marks'ı sonradan değiştirmek öğrencinin notlarını etkilemez. Kopyalamasaydı (this.scores = scores;) cevap 100 olurdu.
What is printed?
GradeBook g = new GradeBook("X");
g.addStudent(new Student("P", new int[]{100}));
g.addStudent(new Student("Q", new int[]{0, 100}));
System.out.println(g.classAverage() + " " + g.countPassing(50));Cevabı göster
P'nin ortalaması 100.0, Q'nunki 50.0. Sınıf ortalaması öğrenci ortalamalarının ortalaması: (100 + 50) / 2 = 75.0. Geçme sınırı >= 50 olduğu için Q da geçer: 2.
What is printed?
System.out.println(Student.letterFor(59.99) + Student.letterFor(60)
+ Student.letterFor(89.5) + Student.letterFor(90));Cevabı göster
letterFor static, sınıf adıyla çağrılır. 59.99 < 60 → F. 60 → D (sınır dahil). 89.5 < 90 → B; yuvarlama yok. 90 → A. Dört String birleşir.
setScore(index, value) changes a score only if the index is valid and the value is between 0 and 100. What is printed?
Student s = new Student("D", new int[]{80, 80});
s.setScore(1, 100);
s.setScore(5, 100);
s.setScore(0, -10);
System.out.println(s.average());Cevabı göster
İlk çağrı geçerli: {80, 100}. İndeks 5 yok, reddedilir (exception da atılmaz, çünkü önce kontrol ediliyor). -10 geçersiz değer. Ortalama (80 + 100) / 2 = 90.0.
Two students have the same name. What is printed?
GradeBook h = new GradeBook("Y");
h.addStudent(new Student("Ali", new int[]{70}));
h.addStudent(new Student("Ali", new int[]{90}));
System.out.println(h.find("Ali").average());Cevabı göster
find listeyi baştan gezer ve ilk eşleşmede return eder. İkinci Ali'ye hiç bakılmaz.
What do classAverage() and topStudent() return for a GradeBook with no students?
GradeBook empty = new GradeBook("BOS");
System.out.println(empty.classAverage() + " " + empty.topStudent());Cevabı göster
İki metot da başta isEmpty() kontrolü yapıyor. Kontrol olmasaydı classAverage 0.0 / 0 = NaN, topStudent ise students.get(0) ile IndexOutOfBoundsException verirdi.
What happens when printReport() is called on an empty GradeBook?
Cevabı göster
printReport topStudent().getName() çağırıyor. Boş defterde topStudent null döner, null üzerinden getName çağrısı çöker. Doğrulamada önce Course: BOS ve Class average: 0.00 basıldı, sonra NullPointerException. Düzeltme: Student top = topStudent(); if (top != null) ....
For the four students of this page (Deniz B, Ece A, Can F, Mert D), what does printHistogram() print?
Cevabı göster
B: *
C:
D: *
F: *
Dış döngü harfleri, iç döngü öğrencileri geziyor. Her harf için o harfi alan öğrenci başına bir yıldız. C alan yok; satırda sadece C: var. İç içe döngünün tipik kullanımı.
Write the countPassing(double limit) method of GradeBook (field ArrayList<Student> students).
Cevabı göster
Sayaç kalıbı, bu sefer nesne listesi üzerinde. Her öğrencinin ortalamasını kendi metodu hesaplıyor.public int countPassing(double limit) { int count = 0; for (Student s : students) { if (s.average() >= limit) count++; } return count;}Sayfadaki dört öğrenci için countPassing(60) → 3.
Write the topStudent() method of GradeBook: return the student with the highest average, or null if the list is empty.
Cevabı göster
Max kalıbının nesne hali, artı boş liste kontrolü.public Student topStudent() { if (students.isEmpty()) return null; Student top = students.get(0); for (Student s : students) { if (s.average() > top.average()) top = s; } return top;}Sayfadaki dört öğrenci için Ece (91.67) döner.
Doğrulama: Programın tamamı JDK 26 ile çalıştırıldı (verify/SEN1001/W14.java). Boş defterde printReport davranışı verify/SEN1001/W14b.java ile ayrıca denendi. Rapor ve histogram çıktısındaki boşluklar cat -A ile kontrol edildi.