
Learning Log #3: Tydzień 12 Zajavka – interfejsy, klasy abstrakcyjne i koniec podstaw Java
Siedemnasty tydzień od startu z kursem Zajavka. Oficjalnie tydzień 12 – ostatni tydzień podstawowej części kursu.
Kawa się parowała. Słuchawki na uszach. Dziecko spokojne. I ja, patrząc na ekran z napisem „Gratulacje! Udało Ci się ukończyć kurs podstawowy!” czułam… ulgę. I dumę. I lekki strach „co dalej?”.
Interfejsy w Java brzmiały dla mnie przez długi czas jak jakaś magiczna zaawansowana rzecz. Dziś wiem, że to jeden z najpotężniejszych narzędzi w OOP. I pokażę Wam dlaczego.
Gdzie jestem w kursie Java?
Oficjalnie: Tydzień 12/12 kursu „Java w 12 tygodni Zajavka” – koniec podstaw ✓
Realnie: 17 tygodni od startu (czyli ~4 miesiące kalendarzowe)
Co dalej: Zajavka ma część zaawansowaną – Spring, bazy danych, REST API. Decyduję czy kontynuować czy przejść na JavaScript.
Czas nauki w tym tygodniu: 10 godzin (wracam do formy!)
Czym są interfejsy w Java? (w końcu rozumiem!)
Analogia która mi pomogła – umowa o pracę:
Wyobraź sobie że zatrudniasz pracownika. Nie obchodzi Cię:
- Jak dokładnie wykonuje pracę
- Jakie narzędzia używa
- Jaki ma proces myślowy
Obchodzi Cię tylko: czy potrafi zrobić to co potrzebujesz.
Interfejs w Java to właśnie taka „umowa” – deklaracja „potrafię to i to” bez szczegółów „jak”.
Przykład z życia – Payment:
java
`// INTERFEJS = umowa public interface Payment { boolean processPayment(double amount); String getPaymentStatus(); }
// IMPLEMENTACJA 1 - Karta kredytowa public class CreditCardPayment implements Payment { private String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public boolean processPayment(double amount) {
System.out.println("Processing " + amount + " PLN via credit card");
// Tutaj logika połączenia z operatorem kart
return true;
}
@Override
public String getPaymentStatus() {
return "Credit card payment successful";
}
}
// IMPLEMENTACJA 2 - BLIK public class BlikPayment implements Payment { private String phoneNumber;
public BlikPayment(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
@Override
public boolean processPayment(double amount) {
System.out.println("Processing " + amount + " PLN via BLIK");
// Tutaj logika BLIK
return true;
}
@Override
public String getPaymentStatus() {
return "BLIK payment successful";
}
}
// UŻYCIE - najważniejsze! public class PaymentProcessor {
public void executePayment(Payment payment, double amount) {
// Nie wiem CZY to karta, BLIK czy coś innego
// Wiem tylko że payment POTRAFI processPayment()
if (payment.processPayment(amount)) {
System.out.println(payment.getPaymentStatus());
}
}
}
// W praktyce: PaymentProcessor processor = new PaymentProcessor();
Payment creditCard = new CreditCardPayment("1234-5678"); Payment blik = new BlikPayment("123456789");
processor.executePayment(creditCard, 100.50); processor.executePayment(blik, 200.00);`
Co mnie zaskoczyło?
PaymentProcessor nie wie, czy dostaje kartę czy BLIK. Wie tylko że dostaje coś co implementuje Payment. I to wystarczy!
To jest cała magia interfejsów – loose coupling, flexibility, extensibility.
Dlaczego interfejsy są lepsze niż klasy abstrakcyjne?
To było moje największe „aha!” tego tygodnia.
Klasa abstrakcyjna:
java
`public abstract class Animal { protected String name;
public Animal(String name) {
this.name = name;
}
public abstract void makeSound();
public void sleep() { // Konkretna implementacja
System.out.println(name + " is sleeping");
}
}
public class Dog extends Animal { public Dog(String name) { super(name); }
@Override
public void makeSound() {
System.out.println("Woof!");
}
}`
Problem: Dog może dziedziczyć tylko z JEDNEJ klasy. Co jeśli Dog ma być też Trainable? Nie można.
Interfejsy – wiele implementacji!
java
`public interface Animal { void makeSound(); }
public interface Trainable { void train(String command); boolean isTraned(); }
public interface Playful { void play(); }
// Jedna klasa, WIELE interfejsów! public class Dog implements Animal, Trainable, Playful { private String name; private boolean trained = false;
@Override
public void makeSound() {
System.out.println("Woof!");
}
@Override
public void train(String command) {
System.out.println("Training: " + command);
trained = true;
}
@Override
public boolean isTrained() {
return trained;
}
@Override
public void play() {
System.out.println("Playing fetch!");
}
}`
Java nie ma multiple inheritance (dziedziczenia wielokrotnego) ale ma multiple implementation (implementacji wielokrotnych)!
To rozwiązuje problem „co jeśli potrzebuję cech z wielu źródeł”.
Kiedy używać czego? (moja ściąga)
| Użyj INTERFEJSU gdy… | Użyj KLASY ABSTRAKCYJNEJ gdy… |
|---|---|
| Określasz „co klasa potrafi” | Definiujesz „czym klasa jest” |
| Potrzebujesz wielu źródeł zachowań | Masz wspólny kod do współdzielenia |
| Chcesz loose coupling | Masz silną relację hierarchiczną |
| Przykład: Flyable, Swimmable, Payable | Przykład: Animal, Vehicle, Shape |
Praktyczna zasada: Domyślnie używaj interfejsów. Klasy abstrakcyjne tylko, gdy naprawdę potrzebujesz współdzielić implementację.
Mój projekt – Therapy Management System
Postanowiłam stworzyć coś użytecznego – system zarządzania terapiami mojego dziecka.
Design z interfejsami:
java
`// INTERFEJSY - umowy public interface Schedulable { LocalDateTime getDateTime(); int getDuration(); boolean reschedule(LocalDateTime newTime); }
public interface Billable { double getCost(); boolean isPaid(); void markAsPaid(); }
public interface Reportable { String generateReport(); Map<String, Object> getStatistics(); }
// IMPLEMENTACJA - terapia ma wszystkie 3 cechy public class TherapySession implements Schedulable, Billable, Reportable { private String therapyType; // "SI", "Logopeda", "Pedagog" private LocalDateTime dateTime; private int durationMinutes; private double cost; private boolean paid; private String notes;
public TherapySession(String type, LocalDateTime dateTime,
int duration, double cost) {
this.therapyType = type;
this.dateTime = dateTime;
this.durationMinutes = duration;
this.cost = cost;
this.paid = false;
}
// Schedulable methods
@Override
public LocalDateTime getDateTime() {
return dateTime;
}
@Override
public int getDuration() {
return durationMinutes;
}
@Override
public boolean reschedule(LocalDateTime newTime) {
this.dateTime = newTime;
System.out.println("Rescheduled to: " + newTime);
return true;
}
// Billable methods
@Override
public double getCost() {
return cost;
}
@Override
public boolean isPaid() {
return paid;
}
@Override
public void markAsPaid() {
this.paid = true;
System.out.println("Marked as paid: " + cost + " PLN");
}
// Reportable methods
@Override
public String generateReport() {
return String.format("Therapy: %s | Date: %s | Duration: %d min | Cost: %.2f PLN | Paid: %s",
therapyType, dateTime, durationMinutes, cost, paid ? "Yes" : "No");
}
@Override
public Map<String, Object> getStatistics() {
Map<String, Object> stats = new HashMap<>();
stats.put("type", therapyType);
stats.put("duration", durationMinutes);
stats.put("cost", cost);
stats.put("paid", paid);
return stats;
}
}
// MANAGEMENT CLASSES - pracują z interfejsami! public class BillingManager {
public double calculateTotalUnpaid(List<Billable> items) {
return items.stream()
.filter(item -> !item.isPaid())
.mapToDouble(Billable::getCost)
.sum();
}
public void processPayments(List<Billable> items) {
items.stream()
.filter(item -> !item.isPaid())
.forEach(item -> {
item.markAsPaid();
});
}
}
public class ScheduleManager {
public List<Schedulable> getUpcoming(List<Schedulable> items) {
LocalDateTime now = LocalDateTime.now();
return items.stream()
.filter(item -> item.getDateTime().isAfter(now))
.sorted(Comparator.comparing(Schedulable::getDateTime))
.toList();
}
}
public class ReportGenerator {
public void generateMonthlyReport(List<Reportable> items) {
System.out.println("=== MONTHLY REPORT ===");
items.forEach(item -> {
System.out.println(item.generateReport());
});
}
}`
Użycie w praktyce:
java
`public class Main { public static void main(String[] args) { // Tworzę sesje terapii List<TherapySession> sessions = new ArrayList<>();
sessions.add(new TherapySession("SI",
LocalDateTime.of(2026, 3, 20, 14, 0), 60, 75.00));
sessions.add(new TherapySession("Logopeda",
LocalDateTime.of(2026, 3, 22, 10, 0), 45, 65.00));
sessions.add(new TherapySession("Pedagog",
LocalDateTime.of(2026, 3, 25, 15, 0), 60, 60.00));
// Billing - obsługuje Billable
BillingManager billing = new BillingManager();
double unpaid = billing.calculateTotalUnpaid(
new ArrayList<>(sessions)); // TherapySession implements Billable!
System.out.println("Total unpaid: " + unpaid + " PLN");
// Schedule - obsługuje Schedulable
ScheduleManager schedule = new ScheduleManager();
List<Schedulable> upcoming = schedule.getUpcoming(
new ArrayList<>(sessions)); // TherapySession implements Schedulable!
System.out.println("Upcoming sessions: " + upcoming.size());
// Reports - obsługuje Reportable
ReportGenerator reports = new ReportGenerator();
reports.generateMonthlyReport(
new ArrayList<>(sessions)); // TherapySession implements Reportable!
}
}
**Output:**
Total unpaid: 200.00 PLN Upcoming sessions: 3 === MONTHLY REPORT === Therapy: SI | Date: 2026-03-20T14:00 | Duration: 60 min | Cost: 75.00 PLN | Paid: No Therapy: Logopeda | Date: 2026-03-22T10:00 | Duration: 45 min | Cost: 65.00 PLN | Paid: No Therapy: Pedagog | Date: 2026-03-25T15:00 | Duration: 60 min | Cost: 60.00 PLN | Paid: No`
Dlaczego to jest piękne?
BillingManager nie wie, że pracuje z TherapySession. Wie tylko, że dostaje Billable.
Jutro mogę dodać DoctorVisit implements Billable i BillingManager będzie działać BEZ ZMIAN!
To jest siła interfejsów – rozszerzalność bez modyfikacji istniejącego kodu.
Default methods w interfejsach (Java 8+)
To mnie totalnie zaskoczyło – interfejsy mogą mieć implementację!
java
`public interface Reportable { String generateReport();
// DEFAULT METHOD - konkretna implementacja!
default void printReport() {
System.out.println("=== REPORT ===");
System.out.println(generateReport());
System.out.println("==============");
}
default String getShortSummary() {
String full = generateReport();
return full.substring(0, Math.min(50, full.length())) + "...";
}
}`
Teraz każda klasa implementująca Reportable automatycznie dostaje printReport() i getShortSummary()!
Kiedy używać:
- Gdy większość implementacji będzie miała tą samą logikę
- Gdy chcesz dodać metodę do interfejsu bez łamania istniejącego kodu
Typowe błędy które popełniłam
1. Zbyt duże interfejsy (zanieczyszczenie interfejsów)
Błąd:
java
public interface TherapyManagement { void schedule(); void reschedule(); void cancel(); double getCost(); void markAsPaid(); String generateReport(); void sendReminder(); void exportToCalendar(); // ... 20 metod później }
Lekcja: To narusza ISP (Interface Segregation Principle – Zasada Segregacji Interfejsów ). Lepiej wiele małych interfejsów niż jeden wielki.
Poprawka: Rozdziel na Schedulable, Billable, Reportable, Notifiable.
2. Interfejs z jedną metodą bez sensu
Błąd:
java
public interface CanExist { boolean exists(); }
Lekcja: Nie każda metoda potrzebuje interfejsu. Czasem po prostu metoda w klasie wystarczy.
3. Mieszanie abstrakcji
Błąd:
java
public interface Payment { boolean processPayment(double amount); void setCardNumber(String card); // Co jeśli to BLIK? Nie ma karty! }
Lekcja: Interfejs powinien opisywać zachowanie wspólne dla WSZYSTKICH implementacji.
Functional Interfaces – bonus
Odkryłam że interfejs z JEDNĄ metodą abstrakcyjną to „functional interface” i może być użyty z lambda!
java
`@FunctionalInterface public interface TherapyCostCalculator { double calculate(int sessions, double pricePerSession); }
// Użycie z lambdą: TherapyCostCalculator standard = (sessions, price) -> sessions * price; TherapyCostCalculator withDiscount = (sessions, price) -> sessions * price * 0.9; // 10% discount
System.out.println("Standard: " + standard.calculate(8, 75)); // 600.0 System.out.println("Discount: " + withDiscount.calculate(8, 75)); // 540.0`
To otwiera drogę do programowania funkcyjnego w Java!
Koniec podstaw Zajavka – co dalej?
Tydzień 12 = koniec fundamentów. Przeszłam przez:
- Podstawy składni Java
- OOP (klasy, obiekty, dziedziczenie)
- Agregacja i kompozycja
- Interfejsy i klasy abstrakcyjne ✓
Co dalej – moje opcje:
Opcja 1: Zajavka – poziom zaawansowany
- Spring Framework
- Hibernate / JPA
- REST APIs
- Microservices
Plusy: Kontynuacja, naturalny postęp, solidna backend-dev ścieżka Minusy: Jeszcze więcej Java, może za ciężkie przy dzieciach
Opcja 2: JavaScript
- Naturalny krok dla freelancerki WordPress
- Synergia z moją pracą
- Frontend interaktywność
Plusy: Praktyczne dla mojej kariery, lżejsze od Springa, już mam podstawy JS Minusy: Restart w kolejnym języku, powtarzanie podstaw
Opcja 3: Konsolidacja + Budowa
- Zatrzymać się na podstawach Java
- Zbudować 2-3 solidne projekty
- Pokazać portfolio
Plusy: Solidne fundamenty, działający kod Minusy: Może za mało dla ryneku pracy
Moja decyzja: Myślę, że… JavaScript. Powody:
- Synergia z WordPress (mogę od razu używać w pracy!)
- Łatwiejszy balans z dziećmi (dużo mniej ciężki niż Spring)
- Frontend umiejętności są wartościowe
- Zawsze mogę wrócić do Java później
Podsumowanie nauki Java – 17 tygodni
Co osiągnęłam:
✅ Podstawy składni Java – solidnie
✅ OOP – rozumiem i używam
✅ Agregacja/kompozycja – odhaczone
✅ Interfejsy – w końcu rozumiem!
✅ 2 projekty robocze (Family system, Therapy Management)
Statystyki:
- 17 tygodni (zamiast 12)
- ~120 godzin nauki całościowo
- ~7h tygodniowo średnio
- 2 pełne projekty
- Setki linii kodu
Czy było warto? TAK. Java nauczyła mnie:
- Myślenia obiektowo
- Projektowania systemów
- Że mogę uczyć się programowania przy dzieciach
- Że 1h dziennie wystarczy
Następny krok: Learning Log #4 będzie o… JavaScript albo o PHP (?!) ! Czekać na dalsze informacje.
PS: Też kończycie kurs/etap nauki? Co dalej planujesz? Zostaw komentarz – ciekawa jestem Waszych ścieżek. A jeśli macie pytania o interfejsy w Java – pytajcie, teraz gdy w końcu rozumiem chętnie pomogę!
Zostaw Komentarz