Wykład 4
Samochod
, OsobaKontaktowa
.KontoBankowe
zamiast PrzetwarzanieTransakcji
.public
, private
i protected
.private
) aby chronić je przed niepożądanym dostępem z zewnątrz.public
.get
lub set
, a następnie nazwa zmiennej z wielkiej litery, np. getImie()
lub setImie()
./** ... */
) do dokumentowania klas, zmiennych i metod. Dzięki temu inne osoby korzystające z Twojego kodu będą miały łatwy dostęp do opisu funkcjonalności.https://www.oracle.com/java/technologies/javase/codeconventions-fileorganization.html
https://google.github.io/styleguide/javaguide.html#s3.4.2-class-member-ordering
public static final
zmienne).W Javie, jeśli klasa nie ma zdefiniowanego żadnego konstruktora, kompilator dostarcza domyślny konstruktor bezargumentowy (nazywany również konstruktorem domyślnym). Ten konstruktor nie ma żadnego ciała i służy wyłącznie do tworzenia obiektów.
Jeśli jednak zdefiniujesz choć jeden konstruktor dla klasy, kompilator już nie dostarcza domyślnego konstruktora. W takim przypadku, jeśli chcesz mieć konstruktor bezargumentowy, musisz go jawnie zdefiniować.
class Telephone {
private String brand;
private String model;
public void call(String number) {
System.out.println("Calling number " + number + " from " + brand + " " + model);
}
public void sendMessage(String number, String content) {
System.out.println("Sending a message to " + number + " with content: " + content);
}
}
new
Operator new
w Javie odgrywa kluczową rolę w tworzeniu nowych obiektów i jest nieodłącznym elementem programowania obiektowego w tym języku.
new
, system przydziela pamięć dla nowego obiektu w stercie (ang. heap). Sterta jest obszarem pamięci przeznaczonym do alokacji pamięci dla obiektów w czasie działania programu.new
nie tylko alokuje pamięć, ale również inicjuje obiekt poprzez wywołanie odpowiedniego konstruktora klasy.new
zwraca referencję do tego obiektu. Referencję tę można przypisać do zmiennej, która jest zmienną referencyjną odpowiedniego typu.new
, można od razu użyć konstruktora klasy, aby zainicjować obiekt. Na przykład: new Dog("Rex")
używa konstruktora klasy Dog
, który przyjmuje jeden argument typu String
.new
jest również używany do tworzenia tablic w Javie. Na przykład: new int[5]
tworzy tablicę pięciu elementów typu int
.Operator kropki (wyboru składowej) .
jest jednym z najczęściej używanych operatorów i ma wiele zastosowań w kontekście dostępu do składowych klasy lub obiektu.
Zastosowania:
Operator kropki pozwala na dostęp do pól (zmiennych instancji) oraz metod obiektu. Jeśli pole lub metoda są oznaczone jako public
, można do nich bezpośrednio odwołać się za pomocą operatora kropki.
Można użyć operatora kropki do dostępu do statycznych składowych klasy (czyli tych, które są oznaczone słowem kluczowym static
). W tym przypadku odnosimy się bezpośrednio do klasy, a nie do instancji klasy.
Operator kropki jest używany do odwoływania się do klas wewnątrz pakietów.
W sytuacjach, gdy chcemy się odwołać do konkretnej klasy lub składowej w ramach określonego pakietu, używamy operatora kropki.
W kontekście dziedziczenia, jeśli podklasa chce się odwołać do składowej klasy bazowej, która została przesłonięta w podklasie, można użyć słowa kluczowego super
w połączeniu z operatorem kropki.
Jeśli klasa ma zagnieżdżoną klasę wewnętrzną, można odwołać się do niej za pomocą operatora kropki.
this
słowo kluczowe this
odnosi się do bieżącej instancji klasy. Może być używane do odnoszenia się do pól klasy wewnątrz metody, co jest użyteczne, gdy parametr metody ma taką samą nazwę jak pole klasy.
Słowo kluczowe this
można pominąć w Javie, gdy:
this
.Przykład:
Bloki inicjujące w Javie to specjalne bloki kodu, które są wykonywane podczas tworzenia obiektu. Służą one do inicjalizacji zmiennych instancji oraz do wykonywania pewnych operacji przed wywołaniem konstruktora klasy.
Java oferuje dwa rodzaje bloków inicjujących:
Wartości domyślne:
Jeśli zmiennej instancji nie przypiszemy wartości, Java automatycznie przypisuje jej wartość domyślną w zależności od typu zmiennej:
int
, float
, double
): 0
boolean
: false
char
: \u0000
(null)null
public class TestSample {
public static void main(String[] args) {
System.out.println("Main method started");
// Tworzenie obiektu klasy Sample
Sample sample = new Sample();
System.out.println("Instance Variable: " + sample.instanceVar);
}
}
class Sample {
// Zmienna instancji
public int instanceVar;
// Blok inicjujący instancji
{
instanceVar = 10;
System.out.println("Instance block executed");
}
// Konstruktor
public Sample() {
System.out.println("Constructor executed");
}
}
public class TestSample {
public static void main(String[] args) {
System.out.println("Main method started");
Sample sample = new Sample();
System.out.println("Instance Variable: " + sample.instanceVar);
}
}
class Sample {
public int instanceVar = 5;
{
System.out.println("Instance block before - variable: " + instanceVar);
instanceVar = 10;
System.out.println("Instance block after - variable: " + instanceVar);
System.out.println("Instance block executed");
}
public Sample() {
System.out.println("Constructor before - variable: " + instanceVar);
instanceVar = 15;
System.out.println("Constructor after - variable: " + instanceVar);
System.out.println("Constructor executed");
}
}
null
Wartość null
jest specjalną referencją, która wskazuje, że dana zmienna referencyjna nie wskazuje na żaden konkretny obiekt w pamięci.
Wartość domyślna: Gdy deklarujesz zmienną obiektową, ale nie przypisujesz jej żadnej wartości, domyślnie przyjmuje ona wartość null
.
Sprawdzanie istnienia obiektu: Można użyć null
do sprawdzenia, czy dana zmienna została zainicjalizowana (czyli czy wskazuje na jakiś obiekt). To jest przydatne w sytuacjach, gdy nie chcesz ryzykować wystąpienia wyjątku NullPointerException
.
Zwalnianie obiektów: W Javie nie ma bezpośredniej metody na zwolnienie obiektu z pamięci. Zamiast tego Java korzysta z mechanizmu Garbage Collection (GC) do automatycznego usuwania obiektów, które nie są już dostępne. Przypisanie wartości null
do zmiennej obiektowej jest jednym ze sposobów na “odłączenie” obiektu, co pozwala GC na jego ewentualne usunięcie.
Semantyka: null
może być używane do reprezentowania “braku wartości” lub “niezdefiniowanego stanu”. Na przykład, jeśli masz klasę reprezentującą użytkownika, ale nie znasz jeszcze jego adresu e-mail, możesz przypisać pole e-mail wartość null
.
null
Wyjątek NullPointerException
(NPE) pojawia się, gdy program próbuje odwołać się do obiektu lub wywołać metodę na referencji, która jest null
.
Podstawowe zasady (na ten moment)
Inicjalizuj zmienne: - Gdy to możliwe, inicjalizuj zmienne w momencie ich deklaracji. - Unikaj pozostawiania zmiennych referencyjnych bez wartości (niewypełnionych).
Sprawdzaj obiekty przed użyciem: - Zawsze sprawdzaj, czy referencja nie jest null
, zanim użyjesz jej do wywołania metody lub dostępu do pola.
Objects.requireNonNull(T obj)
:
obj
nie jest null
.null
, metoda rzuci wyjątek NullPointerException
.null
, zostanie zwrócony bez zmian.Objects.requireNonNull(T obj, String message)
.null
.Objects.requireNonNullElse(T obj, T defaultObj)
:
obj
nie jest null
.obj
jest null
, zwraca defaultObj
jako wartość domyślną.obj
nie jest null
, zwraca obj
.null
.Modyfikatory dostępu w Javie określają zakres widoczności i dostępności składowych klasy (tj. pól, metod, konstruktorów, itp.). Istnieje cztery główne modyfikatory dostępu:
private
:
private
jest dostępna wyłącznie w obrębie klasy, w której została zdefiniowana.default
(brak modyfikatora):
protected
:
protected
jest dostępna w obrębie jej klasy, w obrębie wszystkich klas w tym samym pakiecie, a także w subklasach (nawet jeśli są w innym pakiecie).public
, ale mniej niż private
i default
.public
:
public
jest dostępna z każdej klasy, niezależnie od pakietu.Hermetyzacja (nazywana także enkapsulacją) to jedno z podstawowych założeń programowania obiektowego. Polega na ukrywaniu wewnętrznych detali i stanu obiektu przed kodem zewnętrznym, udostępniając jednocześnie dobrze zdefiniowany interfejs do interakcji z tym obiektem. Hermetyzacja pomaga w ochronie integralności danych i kontrolowaniu dostępu do nich.
Modyfikatory dostępu
Gettery i settery
Zasada najmniejszego uprzywilejowania: Dobre praktyki hermetyzacji zakładają ograniczenie widoczności i dostępu do zmiennych i metod tak bardzo, jak to tylko możliwe. Dlatego zaleca się, aby pola klasy były zazwyczaj private
, a metody publiczne stanowiły świadomie zaprojektowany interfejs klasy.
Przykład: Stwórzmy klasę Person
z dwoma polami name
i age
.
Przykład: Tablice i listy tablicowe obiektów.
class Book {
private String title;
private double price;
public Book() {
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
if (title != null && !title.isEmpty()) {
this.title = title;
} else {
this.title = "";
}
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
if (price >= 0) {
this.price = price;
} else {
this.price = 0;
}
}
}
Przykład: metoda zwracająca obiekt.
class Book {
private String title;
private double price;
public Book() {
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
if (title != null && !title.isEmpty()) {
this.title = title;
} else {
this.title = "";
}
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
if (price >= 0) {
this.price = price;
} else {
this.price = 0;
}
}
}
Przykład: opisywanie obiektów.
class Book {
private String title;
private double price;
public Book() {
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
if (title != null && !title.isEmpty()) {
this.title = title;
} else {
this.title = "";
}
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
if (price >= 0) {
this.price = price;
} else {
this.price = 0;
}
}
}
Ang. overload
Pakiety w Javie służą do grupowania klas, interfejsów i innych elementów w organizowane jednostki. Pakiety pełnią wiele funkcji, w tym zapewnianie przestrzeni nazw dla klas, ograniczanie dostępu do klas oraz pomaganie w zarządzaniu kodem.
Deklaracja pakietu: W pliku źródłowym Java, deklaracja pakietu powinna znajdować się na samym początku (przed wszystkimi importami i deklaracjami klas). Wygląda to następująco:
Konwencja nazewnictwa: Pakiety powinny być nazywane małymi literami. Zwykle stosuje się odwrotną notację domenową (np. com.example.projectname
). Umożliwia to uniknięcie konfliktów nazw w międzynarodowym środowisku.
com.example.mypackage
, odpowiednia struktura katalogów to com/example/mypackage/
.Organizacja: Klasy o podobnej funkcjonalności lub przeznaczeniu powinny być grupowane w tym samym pakiecie. Pomaga to w organizacji kodu i jego zrozumieniu.
Zachowanie niewielkich pakietów: Zamiast tworzyć jeden duży pakiet z wieloma klasami, lepiej jest stworzyć kilka mniejszych pakietów. Dzięki temu łatwiej zarządzać i rozumieć kod.
Używanie podpakietów: Możesz organizować kody w hierarchii pakietów, tworząc podpakiety. Na przykład com.example.projectname.models
, com.example.projectname.controllers
itp.
Ograniczenie dostępu: Za pomocą modyfikatorów dostępu (public, protected, private, domyślny) oraz pakietów można kontrolować dostępność klas, metod i zmiennych. Dzięki temu możesz ukryć szczegóły implementacji i eksponować tylko potrzebne interfejsy.
Unikanie konfliktów nazw: Dzięki pakietom możesz mieć klasy o tej samej nazwie w różnych pakietach bez konfliktów.
Zachowanie spójności: Jeśli pracujesz w zespole lub nad dużym projektem, dobrze jest przyjąć spójne konwencje nazewnictwa i struktury pakietów, aby kod był łatwo zrozumiały dla wszystkich uczestników.
W Javie nie możemy zadeklarować klas na poziomie najwyższym (top-level) jako private
. Modyfikator private
dla klasy na poziomie najwyższym byłby niezrozumiały, ponieważ nie miałby żadnego zastosowania: nie można by było uzyskać do niej dostępu z żadnego innego miejsca w kodzie.
Jednakże, możemy zadeklarować klasę jako private
, jeśli jest to klasa wewnętrzna (inner class) lub klasa zagnieżdżona (nested class).
Jak czas będzie - to na koniec semestru.
Kiedy przypisujesz jeden obiekt do drugiego, faktycznie przypisujesz jedynie referencję do obiektu, a nie sam obiekt. Oznacza to, że obie zmienne odnoszą się do tego samego miejsca w pamięci (do tej samej instancji obiektu). Zmienienie jednego obiektu wpłynie więc na drugi, ponieważ obie zmienne wskazują na ten sam obiekt.
public class TestPerson {
public static void main(String[] args) {
Person person1 = new Person("John");
Person person2 = person1;
System.out.println(person1.getName());
System.out.println(person2.getName());
person2.setName("Doe");
System.out.println(person1.getName());
System.out.println(person2.getName());
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Konstruktor to specjalny rodzaj metody służącej do inicjalizacji obiektu. Jest on wywoływany podczas tworzenia instancji obiektu, najczęściej przy użyciu słowa kluczowego new
. Konstruktory mają pewne unikalne cechy w porównaniu do standardowych metod:
void
.public class TestVehicle {
public static void main(String[] args) {
// Użycie konstruktorów:
Vehicle vehicle1 = new Vehicle(); // używa konstruktora domyślnego
Vehicle vehicle2 = new Vehicle("Toyota"); // używa konstruktora z jednym argumentem
Vehicle vehicle3 = new Vehicle("Honda", 2); // używa konstruktora z dwoma argumentami
}
}
class Vehicle {
private String brand;
private int wheels;
// Konstruktor domyślny (bezargumentowy)
public Vehicle() {
this.brand = "Unknown";
this.wheels = 4;
}
// Konstruktor z jednym argumentem
public Vehicle(String brand) {
this.brand = brand;
this.wheels = 4;
}
// Konstruktor z dwoma argumentami
public Vehicle(String brand, int wheels) {
this.brand = brand;
this.wheels = wheels;
}
}
Wywołanie jednego konstruktora w innym to technika, która pozwala na ponowne użycie kodu konstruktora w obrębie tej samej klasy. Często jest stosowana, by unikać powtarzania tej samej logiki inicjalizacji w różnych konstruktorach. Aby wywołać jeden konstruktor z innego w Javie, używane jest słowo kluczowe this
z odpowiednimi argumentami.
Wywołanie innego konstruktora za pomocą this
musi być pierwszą instrukcją w konstruktorze.
public class TestLaptop {
public static void main(String[] args) {
// Użycie konstruktorów:
Laptop laptop1 = new Laptop("Dell"); // używa konstruktora z jednym argumentem
Laptop laptop2 = new Laptop("Apple", 16); // używa konstruktora z dwoma argumentami
}
}
class Laptop {
private String brand;
private int memory;
private boolean hasSSD;
// Konstruktor główny
public Laptop(String brand, int memory, boolean hasSSD) {
this.brand = brand;
this.memory = memory;
this.hasSSD = hasSSD;
}
// Konstruktor z jednym argumentem - wywołuje konstruktor główny
public Laptop(String brand) {
this(brand, 8, true); // Wywołuje konstruktor główny z domyślnymi wartościami dla pamięci i SSD
}
// Konstruktor z dwoma argumentami - wywołuje konstruktor główny
public Laptop(String brand, int memory) {
this(brand, memory, true); // Wywołuje konstruktor główny z domyślną wartością dla SSD
}
}
W wielu językach programowania, w których obiekty są tworzone dynamicznie, kluczowym zagadnieniem jest zarządzanie pamięcią i niszczenie obiektów, które nie są już potrzebne. Java korzysta z mechanizmu zwanego “garbage collection” (GC) do automatycznego odzyskiwania pamięci zajmowanej przez obiekty, które nie są już osiągalne.
Automatyczne zwalnianie pamięci: W Javie nie musisz jawnie usuwać obiektów. Mechanizm garbage collection automatycznie wykrywa obiekty, które nie są już osiągalne, i zwalnia pamięć, którą zajmują.
Osiągalność: Obiekt jest uznawany za nieosiągalny, gdy nie istnieją żadne odniesienia do niego. Innymi słowy, jeśli nie ma sposobu dostępu do obiektu z żadnej części programu, staje się on kandydatem do garbage collection.
Metoda finalize()
: Każda klasa w Javie dziedziczy metodę finalize()
z klasy Object
. Możesz nadpisać tę metodę, aby dostarczyć kod, który zostanie wykonany tuż przed usunięciem obiektu przez garbage collector. Jednakże poleganie na finalize()
nie jest zalecane z kilku powodów, w tym z powodu nieterminowości wywołań i dodatkowego obciążenia dla GC.
Sugestia garbage collection: Możesz zasugerować JVM, by uruchomił garbage collection za pomocą System.gc()
, ale nie ma gwarancji, że zostanie on natychmiast wykonany. Z reguły nie jest zalecane ręczne wywoływanie tej metody, ponieważ JVM jest dobrze zoptymalizowany do decydowania, kiedy uruchomić GC.
null
), jeśli wiesz, że obiekt nie będzie już używany, chociaż nie jest to zazwyczaj konieczne.try-with-resources
lub jawnie zamykać te zasoby za pomocą odpowiednich metod (np. close()
).public class TestRetangle {
public static void main(String[] args) {
Rectangle r = new Rectangle(150, -5);
System.out.println(r.getWidth());
System.out.println(r.getHeight());
r.setWidth(50);
r.setHeight(200);
System.out.println(r.getWidth());
System.out.println(r.getHeight());
}
}
class Rectangle {
private double width;
private double height;
public Rectangle(double width, double height) {
if (width < 1) {
this.width = 1;
} else if (width > 100) {
this.width = 100;
} else {
this.width = width;
}
if (height < 1) {
this.height = 1;
} else if (height > 100) {
this.height = 100;
} else {
this.height = height;
}
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
if (width < 1) {
this.width = 1;
} else if (width > 100) {
this.width = 100;
} else {
this.width = width;
}
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
if (height < 1) {
this.height = 1;
} else if (height > 100) {
this.height = 100;
} else {
this.height = height;
}
}
}
public class TestBox {
public static void main(String[] args) {
Box b = new Box(150, -5, 60, new int[]{101, 50, -10, 75});
System.out.println(b.getWidth()); // Wypisze: 100
System.out.println(b.getHeight()); // Wypisze: 1
System.out.println(b.getDepth()); // Wypisze: 60
for (int tag : b.getTags()) {
System.out.print(tag + " "); // Wypisze: 100 50 1 75
}
}
}
class Box {
private double width;
private double height;
private double depth;
private int[] tags = new int[4];
public Box(double width, double height, double depth, int[] tags) {
this.width = (width < 1) ? 1 : (width > 100) ? 100 : width;
this.height = (height < 1) ? 1 : (height > 100) ? 100 : height;
this.depth = (depth < 1) ? 1 : (depth > 100) ? 100 : depth;
for (int i = 0; i < 4; i++) {
if (tags[i] < 1) {
this.tags[i] = 1;
} else if (tags[i] > 100) {
this.tags[i] = 100;
} else {
this.tags[i] = tags[i];
}
}
}
// Gettery i settery
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = (width < 1) ? 1 : (width > 100) ? 100 : width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = (height < 1) ? 1 : (height > 100) ? 100 : height;
}
public double getDepth() {
return depth;
}
public void setDepth(double depth) {
this.depth = (depth < 1) ? 1 : (depth > 100) ? 100 : depth;
}
public int[] getTags() {
return tags.clone();
}
public void setTags(int[] tags) {
for (int i = 0; i < 4; i++) {
if (tags[i] < 1) {
this.tags[i] = 1;
} else if (tags[i] > 100) {
this.tags[i] = 100;
} else {
this.tags[i] = tags[i];
}
}
}
}
import java.util.ArrayList;
public class TestLibraryBook {
public static void main(String[] args) {
ArrayList<String> authorsList = new ArrayList<>();
authorsList.add("J.K. Rowling");
LibraryBook book = new LibraryBook("Harry Potter", authorsList);
System.out.println(book.getTitle()); // Wypisze: Harry Potter
for (String author : book.getAuthors()) {
System.out.print(author + " "); // Wypisze: J.K. Rowling
}
}
}
class LibraryBook {
private String title;
private ArrayList<String> authors = new ArrayList<>();
public LibraryBook(String title, ArrayList<String> authors) {
this.title = (title != null && !title.isEmpty()) ? title : "Unknown Title";
if (authors != null && !authors.isEmpty()) {
this.authors.addAll(authors);
} else {
this.authors.add("Unknown Author");
}
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
if (title != null && !title.isEmpty()) {
this.title = title;
}
}
public ArrayList<String> getAuthors() {
return new ArrayList<>(authors); // Zwrócenie kopii listy, by chronić jej zawartość
}
public void setAuthors(ArrayList<String> authors) {
if (authors != null && !authors.isEmpty()) {
this.authors.clear();
this.authors.addAll(authors);
}
}
}