Wykład 9
import java.util.Objects;
public class Author {
private String name;
private int age;
public Author(String name, int age) {
this.name = name != null ? name : "";
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name != null ? name : "";
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Author{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Author author = (Author) o;
return age == author.age &&
Objects.equals(name, author.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
import java.util.Objects;
public class Book {
private Author author;
private double price;
public Book(Author author, double price) {
this.author = author != null ? new Author(author.getName(), author.getAge()) : new Author("", 0);
this.price = price;
}
public Author getAuthor() {
return new Author(author.getName(), author.getAge());
}
public void setAuthor(Author author) {
this.author = author != null ? new Author(author.getName(), author.getAge()) : new Author("", 0);
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"author=" + author +
", price=" + price +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return Double.compare(book.price, price) == 0 &&
Objects.equals(author, book.author);
}
@Override
public int hashCode() {
return Objects.hash(author, price);
}
}
public class TestBook {
public static void main(String[] args) {
Author author = new Author("George Orwell", 46);
Book book = new Book(author, 19.99);
System.out.println(book);
Author author1 = book.getAuthor();
author1.setName("Adam Mickiewicz");
System.out.println(book);
Book sameBook = new Book(new Author("George Orwell", 46), 19.99);
System.out.println("Are books equal? " + book.equals(sameBook));
System.out.println("Hashcode of book: " + book.hashCode());
System.out.println("Hashcode of sameBook: " + sameBook.hashCode());
}
}
String
do zmiennej typu int
.try-catch
. Przykłady to NullPointerException
(odwołanie do obiektu, którego wartość jest null
), ArrayIndexOutOfBoundsException
(odwołanie do indeksu tablicy poza jej zakresem), ArithmeticException
(np. dzielenie przez zero).OutOfMemoryError
(brak dostępnej pamięci).=
(przypisanie) zamiast ==
(porównanie) w instrukcji warunkowej.Błędy Semantyczne Są to błędy, które powodują niezgodność kodu z logiką biznesową lub zadanymi wymaganiami, mimo że kod kompiluje się i wykonuje poprawnie.
Błędy Środowiska Obejmują one problemy z konfiguracją środowiska uruchomieniowego Java (np. niewłaściwe ścieżki, brak wymaganych bibliotek) oraz problemy związane z platformą (np. różnice w zachowaniu programu na różnych systemach operacyjnych)
W Javie, wyjątki (exceptions) są klasyfikowane na podstawie różnych kryteriów, w tym ich zachowania w czasie wykonania programu i sposobu ich obsługi.
Główne kategorie to:
IOException
: występuje podczas operacji wejścia/wyjścia, np. odczytu pliku.SQLException
: występuje w trakcie interakcji z bazami danych.ClassNotFoundException
: pojawia się, gdy próbujemy załadować klasę, która nie istnieje.RuntimeException
: Jest to klasa bazowa dla wielu wyjątków związanych z błędami, które pojawiają się podczas działania programu. Przykłady to:
NullPointerException
: występuje, gdy odwołujemy się do metody lub zmiennej obiektu, który jest null
.ArrayIndexOutOfBoundsException
: występuje, gdy próbujemy uzyskać dostęp do elementu tablicy poza jej zakresem.IllegalArgumentException
: wyrzucany, gdy metoda otrzymuje argument, który jest nieprawidłowy lub nieodpowiedni.Error
: Reprezentuje poważne błędy, które zazwyczaj nie powinny być obsługiwane przez aplikację. Są to sytuacje, które zazwyczaj są poza kontrolą programisty, np. OutOfMemoryError
lub StackOverflowError
.W Javie, wszystkie wyjątki i błędy są potomkami klasy Throwable
. Ta klasa ma dwa bezpośrednie podklasy:
Exception
: Klasa bazowa dla wyjątków, które mogą być obsłużone przez programistę.Error
: Reprezentuje poważne problemy, które raczej nie powinny być obsługiwane przez programistę, ponieważ zwykle oznaczają poważne problemy na poziomie systemu.try-catch
lub zadeklarować w sygnaturze metody za pomocą throws
.Exception
, zawsze staraj się łapać najbardziej specyficzny wyjątek.Throwable
, Error
lub RuntimeException
, chyba że jest to absolutnie konieczne i jesteś świadom konsekwencji.try-catch-finally
W Javie, blok try-catch-finally
jest mechanizmem do obsługi wyjątków, umożliwiającym wykrycie i eleganckie zarządzanie błędami, które mogą wystąpić podczas wykonania programu. Każda część tego bloku ma swoją specyficzną rolę:
Blok try
Blok try
zawiera kod, który może wygenerować wyjątek. Jest to kod, który jest “ryzykowny” i może powodować błędy w czasie wykonania, takie jak operacje wejścia/wyjścia, parsowanie danych, itp. Jeśli wewnątrz bloku try
pojawi się wyjątek, wykonanie kodu w tym bloku jest natychmiast przerywane, a kontrola jest przekazywana do odpowiedniego bloku catch
.
Blok catch
Blok catch
służy do przechwycenia i obsługi wyjątków, które mogą zostać zgłoszone w bloku try
. Można mieć wiele bloków catch
po bloku try
, z których każdy może przechwytywać różne typy wyjątków. Blok catch
jest wykonany tylko wtedy, gdy typ wyjątku zgłoszonego w bloku try
pasuje do typu wyjątku zadeklarowanego w bloku catch
.
Blok finally
Blok finally
jest opcjonalny i służy do wykonania kodu, który ma zostać wykonany niezależnie od tego, czy wyjątek został zgłoszony czy nie. Jest to idealne miejsce do umieszczenia kodu czyszczącego lub zamykającego, takiego jak zamknięcie połączenia z bazą danych lub plikiem. Nawet jeśli w bloku try
lub catch
wystąpi instrukcja return
, blok finally
zostanie wykonany przed opuszczeniem metody.
public class DivisionExample {
public static void main(String[] args) {
int numerator = 10;
int denominator = 0;
double result = 0.0;
try {
result = divide(numerator, denominator);
} catch (ArithmeticException e) {
System.out.println("Wystąpił wyjątek: " + e.getMessage());
} finally {
System.out.println("Blok finally został wykonany.");
}
System.out.println("Wynik dzielenia: " + result);
}
public static double divide(int numerator, int denominator) {
return numerator / denominator;
}
}
W Javie, interfejs jest fundamentalnym elementem programowania obiektowego, służącym do definiowania szkieletu metod, które klasa implementująca dany interfejs musi zrealizować. Interfejs jest podobny do klasy, ale z kilkoma kluczowymi różnicami i charakterystycznymi cechami.
Od Javy 8, interfejsy mogą zawierać metody domyślne z implementacją, co pozwala na dodawanie nowych funkcji do interfejsów bez naruszania istniejących implementacji. Mogą także zawierać metody statyczne.
Interfejsy są kluczowym elementem programowania w Javie, umożliwiającym tworzenie bardziej modularnych i elastycznych aplikacji.
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.drive(); // Wywołanie metody abstrakcyjnej
car.turnOnLights(); // Wywołanie metody domyślnej
Vehicle.horn(); // Wywołanie metody statycznej interfejsu
}
}
interface Vehicle {
// Metoda abstrakcyjna
void drive();
// Metoda domyślna
default void turnOnLights() {
System.out.println("Światła włączone.");
}
// Metoda statyczna
static void horn() {
System.out.println("Trąbienie!");
}
}
class Car implements Vehicle {
@Override
public void drive() {
System.out.println("Samochód jedzie.");
}
}
Od Javy 8, interfejsy mogą zawierać także pola. Pola w interfejsie są zawsze statyczne i finalne, co oznacza, że są to stałe. Wartość tych pól musi być przypisana w momencie deklaracji. Nie można ich modyfikować.
interface MyInterface {
// Static and final field
int MAX_AGE = 100;
// Abstract method
void showAge();
// Default method
default void showMaxAge() {
System.out.println("Maximum age is: " + MAX_AGE);
}
}
public class MyClass implements MyInterface {
private int age;
public MyClass(int age) {
this.age = age;
}
@Override
public void showAge() {
System.out.println("Age: " + age);
}
public static void main(String[] args) {
MyClass person = new MyClass(30);
person.showAge();
person.showMaxAge();
System.out.println("Maximum age is: " + MyInterface.MAX_AGE);
}
}
W interfejsach wszystkie metody są domyślnie publiczne, więc deklarowanie ich jako public
jest opcjonalne, ale często stosowane dla lepszej czytelności. Pola w interfejsie są zawsze publiczne, statyczne i finalne, więc nie trzeba ich jawnie oznaczać jako public
, static
czy final
.
Wszystkie metody w interfejsie, które nie są domyślne (default
) ani statyczne, są automatycznie uważane za abstrakcyjne. W Javie 8 wprowadzono możliwość definiowania metod domyślnych i statycznych, które nie są abstrakcyjne. Używanie słowa kluczowego abstract
przy metodach w interfejsie jest więc zbędne i nie jest stosowane.
Od Javy 9 wprowadzono możliwość definiowania prywatnych metod w interfejsach. Prywatne metody w interfejsach umożliwiają enkapsulację wspólnej logiki, która jest wykorzystywana tylko wewnątrz danego interfejsu, nie będąc dostępną dla klas, które go implementują.
public interface MyInterface {
// Prywatna metoda niestatyczna
private void privateMethod() {
// Implementacja metody
System.out.println("To jest prywatna metoda w interfejsie.");
}
// Prywatna metoda statyczna
private static void privateStaticMethod() {
// Implementacja metody
System.out.println("To jest prywatna statyczna metoda w interfejsie.");
}
// Metoda domyślna wykorzystująca prywatną metodę
default void defaultMethod() {
privateMethod();
}
// Metoda statyczna wykorzystująca prywatną statyczną metodę
static void staticMethod() {
privateStaticMethod();
}
}
Unikanie duplikacji kodu: Prywatne metody w interfejsach umożliwiają wyodrębnienie wspólnej logiki używanej przez metody domyślne (default) i/lub statyczne. Dzięki temu można uniknąć powielania tego samego kodu w wielu metodach, co jest zgodne z zasadą DRY (Don’t Repeat Yourself).
Enkapsulacja logiki wewnętrznej: Prywatne metody w interfejsie pozwalają na ukrycie szczegółów implementacyjnych, które nie są istotne dla konsumentów interfejsu. Pozwala to na utrzymanie czystości API i zapewnia lepszą separację odpowiedzialności.
Ułatwienie utrzymania i rozwoju: Kiedy logika jest skoncentrowana w prywatnych metodach, modyfikacje tej logiki są łatwiejsze do przeprowadzenia i mają mniejszy wpływ na klasy implementujące interfejs, co ułatwia utrzymanie i rozwój kodu.
Poprawa czytelności i organizacji kodu: Użycie prywatnych metod pozwala na lepszą organizację kodu w interfejsach, co przekłada się na lepszą czytelność i łatwość zrozumienia struktury interfejsu.
Jednakże, warto pamiętać o kilku ograniczeniach i najlepszych praktykach: