SEN1001 · 14. hafta · Case Study

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ı.

case studyproblem decompositionclass designdefensive copyArrayList of objectsaggregate methodstatic helper methodreport formattinghistogramnull checkincremental development

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ıfAlanlarMetotlar
Studentad, not dizisiortalama, en yüksek not, harf notu, toString
GradeBookders 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. %-6s adı 6 karakterlik alana sola yaslar, %6.2f ortalamayı 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: BOS ve Class average: 0.00 basıp topStudent().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üşünceDoğrusu
Önce bütün programı yaz, sonra çalıştırSınıf sınıf yaz, her adımda main’de test et
Kurucuda this.scores = scores; yeterDış 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ğilIndexOutOfBoundsException; önce isEmpty()
find her zaman bir öğrenci döndürürBulamazsa null; çağıran kontrol etmeli
toString içinde printlnreturn ile String döndür
%6.2f 6 ondalık basamakToplam genişlik 6, ondalık 2

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1orta

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
63.333333333333336 D

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.

Soru 2kolay

What is printed?

Student s = new Student("B", new int[]{89, 90, 91});
System.out.println(s.letter() + " " + s.highest());
Cevabı göster
A 91

Ortalama tam 90.0, >= 90 → A. En yüksek not 91.

Soru 3zor

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
30

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.

Soru 4orta

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
75.0 2

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.

Soru 5orta

What is printed?

System.out.println(Student.letterFor(59.99) + Student.letterFor(60)
        + Student.letterFor(89.5) + Student.letterFor(90));
Cevabı göster
FDBA

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.

Soru 6orta

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
90.0

İ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.

Soru 7kolay

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
70.0

find listeyi baştan gezer ve ilk eşleşmede return eder. İkinci Ali'ye hiç bakılmaz.

Soru 8orta

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
0.0 null

İki metot da başta isEmpty() kontrolü yapıyor. Kontrol olmasaydı classAverage 0.0 / 0 = NaN, topStudent ise students.get(0) ile IndexOutOfBoundsException verirdi.

Soru 9zor

What happens when printReport() is called on an empty GradeBook?

Cevabı göster
Prints the course line and Class average: 0.00, then throws NullPointerException

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) ....

Soru 10orta

For the four students of this page (Deniz B, Ece A, Can F, Mert D), what does printHistogram() print?

Cevabı göster
A: *
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ı.

Soru 11kolay

Write the countPassing(double limit) method of GradeBook (field ArrayList<Student> students).

Cevabı göster
for (Student s : students) if (s.average() >= limit) count++; return count;

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.

Soru 12orta

Write the topStudent() method of GradeBook: return the student with the highest average, or null if the list is empty.

Cevabı göster
if (students.isEmpty()) return null; Student top = students.get(0); ... if (s.average() > top.average()) top = s; return top;

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.

Deftere not kartı

Önce tasarla: hangi sınıflar? her sınıfın alanları ve metotları? (UML ile çiz) Student: private final String name; private final int[] scores; Kurucuda diziyi KOPYALA (defensive copy) → dışarıdaki değişiklik içeri sızmaz average: (double) sum / scores.length highest: max = scores[0] letterFor(double) static yardımcı: >=90 A, >=80 B, >=70 C, >=60 D, else F toString: String.format("%-6s %6.2f %s", name, average(), letter()) GradeBook: private ArrayList<Student> students = new ArrayList<>(); Toplu metotlar: classAverage, topStudent, countPassing, find → hepsi for (Student s : students) Boş liste: isEmpty() kontrolü; topStudent/find bulamazsa null döner null dönebilen metodu çağıran: if (x != null) kontrol et, yoksa NullPointerException Histogram: dış döngü kategoriler, iç döngü öğrenciler, eşleşene * Adım adım geliştir: bir sınıf yaz → main'de test et → sonra sıradakine geç