Wykład 12
Cloneable
Interfejs Cloneable
w Javie jest znacznikiem (marker interface), który wskazuje, że klasa zezwala na tworzenie kopii swoich instancji poprzez wywołanie metody clone()
z klasy Object
. W praktyce, implementacja tego interfejsu informuje metodę clone()
z klasy Object
, że jest dozwolone tworzenie polowych kopii (shallow copy) obiektu.
Charakterystyka interfejsu Cloneable
:
Brak Metod: Jako interfejs znacznikowy, Cloneable
nie zawiera żadnych metod do implementacji. Jego jedynym celem jest wskazanie, że klasa implementująca ten interfejs może być bezpiecznie klonowana.
Metoda clone()
z Klasy Object
: Metoda clone()
jest zdefiniowana w klasie Object
i domyślnie wykonuje płytkie kopiowanie obiektu. Oznacza to, że kopiuje wartości pól obiektu, ale nie kopiuje obiektów, na które te pola mogą wskazywać (np. referencje do innych obiektów).
Płytkie Kopiowanie vs Głębokie Kopiowanie: Płytkie kopiowanie (shallow copy) oznacza, że skopiowane są tylko wartości pól, ale jeśli pole jest referencją do innego obiektu, to obie kopie (oryginał i klon) będą wskazywać na ten sam obiekt. Głębokie kopiowanie (deep copy) wymaga dodatkowej logiki, aby skopiować także obiekty, na które wskazują pola.
Nadpisywanie clone()
: Aby umożliwić klonowanie obiektów klasy, która implementuje Cloneable
, zazwyczaj trzeba nadpisać metodę clone()
i zadeklarować ją jako public
. Wewnątrz tej metody, zwykle wywołuje się super.clone()
i dodaje logikę dla głębokiego kopiowania, jeśli jest to potrzebne.
class Example implements Cloneable {
int number;
SomeObject reference;
public Example(int number, SomeObject reference) {
this.number = number;
this.reference = reference;
}
@Override
public Object clone() throws CloneNotSupportedException {
// Płytkie kopiowanie
return super.clone();
// Dla głębokiego kopiowania, dodaj logikę kopiowania obiektów, na które wskazują pola
}
}
Projekt W11, pakiet: example11
package example11;
public class Person implements Cloneable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Name: " + name + ", Age: " + age;
}
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
package example11;
public class TestPerson {
public static void main(String[] args) {
Person p1 = new Person("John", 30);
Person p2 = null;
try {
p2 = p1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println("p1: " + p1);
System.out.println("p2: " + p2);
System.out.println("p1 == p2: " + (p1 == p2));
System.out.println("p1.equals(p2): " + p1.equals(p2));
}
}
Projekt W11, pakiet: example12
package example12;
public class Engine implements Cloneable{
private String type;
public Engine(String type) {
this.type = type;
}
public String toString() {
return "Engine: " + type;
}
@Override
public Engine clone() throws CloneNotSupportedException {
return (Engine) super.clone();
}
}
package example12;
public class Car implements Cloneable{
private Engine engine;
public Car(Engine engine) throws CloneNotSupportedException {
this.engine = engine!= null ? engine.clone() : new Engine("");
}
public Engine getEngine() throws CloneNotSupportedException {
return engine.clone();
}
@Override
public String toString() {
return "Car: " + engine;
}
@Override
public Car clone() throws CloneNotSupportedException {
Car car = (Car) super.clone();
car.engine = this.engine.clone();
return car;
}
}
package example12;
public class TestCar {
public static void main(String[] args) throws CloneNotSupportedException {
Car c1 = new Car(new Engine("V8"));
try {
Car c2 = c1.clone();
System.out.println("c1: " + c1);
System.out.println("c2: " + c2);
System.out.println("c1 == c2: " + (c1 == c2));
System.out.println("c1.equals(c2): " + c1.equals(c2));
System.out.println("c1.getEngine() == c2.getEngine(): " + (c1.getEngine() == c2.getEngine()));
System.out.println("c1.getEngine().equals(c2.getEngine()): " + c1.getEngine().equals(c2.getEngine()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
Projekt W11, pakiet: example12a
package example12a;
public class Shape implements Cloneable {
private int x;
private int y;
public Shape(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public Shape clone() throws CloneNotSupportedException {
//System.out.println("Shape.clone()");
return (Shape) super.clone();
}
}
package example12a;
public class Circle extends Shape {
private int radius;
public Circle(int x, int y, int radius) {
super(x, y);
this.radius = radius;
}
@Override
public Circle clone() throws CloneNotSupportedException {
//System.out.println("Circle.clone()");
return (Circle) super.clone();
}
}
package example12a;
public class TestCircle {
public static void main(String[] args) throws CloneNotSupportedException {
Circle c1 = new Circle(10, 20, 30);
Circle c2 = c1.clone();
System.out.println("c1: " + c1);
System.out.println("c2: " + c2);
System.out.println("c1 == c2: " + (c1 == c2));
System.out.println("c1.equals(c2): " + c1.equals(c2));
Shape c3 = c1.clone();
System.out.println("c3: " + c3);
System.out.println("c1 == c3: " + (c1 == c3));
System.out.println("c1.equals(c3): " + c1.equals(c3));
Shape c4 = new Circle(10, 20, 30);
Circle c5 = (Circle) c4.clone(); // requires cast
System.out.println("c4: " + c4);
System.out.println("c5: " + c5);
System.out.println("c4 == c5: " + (c4 == c5));
}
}
Projekt W11, pakiet: example13
package example13;
import java.util.Arrays;
public class Student implements Cloneable{
private String name;
private double[] grades;
public Student(String name, double[] grades) {
this.name = name;
this.grades = grades!= null ? grades.clone() : new double[0];
}
@Override
public String toString() {
return getClass().getSimpleName() + ": name=" + name + ", grades=" + Arrays.toString(grades);
}
@Override
public Student clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.grades = grades.clone();
return student;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj== null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return name.equals(student.name) && Arrays.equals(grades, student.grades);
}
}
package example13;
public class TestStudent {
public static void main(String[] args) {
Student student1 = new Student("John", new double[]{10, 9, 8});
try{
Student student2 = student1.clone();
System.out.println(student1);
System.out.println(student2);
System.out.println(student1 == student2);
System.out.println(student1.equals(student2));
} catch (CloneNotSupportedException e) {
System.out.println(e.getMessage());
}
}
}
Projekt W11, pakiet: example14
package example14;
import java.util.ArrayList;
public class SportTeam implements Cloneable{
private String name;
private ArrayList<String> players;
public SportTeam(String name, ArrayList<String> players) {
this.name = name;
this.players = players != null ? new ArrayList<>(players) : new ArrayList<>();
}
@Override
public String toString() {
return getClass().getSimpleName() + ": name=" + name + ", players=" + players;
}
@Override
public SportTeam clone() throws CloneNotSupportedException {
SportTeam sportTeam = (SportTeam) super.clone();
sportTeam.players = new ArrayList<>(players);
return sportTeam;
}
}
package example14;
import java.util.ArrayList;
public class TestSportTeam {
public static void main(String[] args) {
ArrayList<String> players = new ArrayList<>();
players.add("Player1");
players.add("Player2");
SportTeam sportTeam1 = new SportTeam("Team1", players);
SportTeam sportTeam2 = null;
try {
sportTeam2 = sportTeam1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(sportTeam1);
System.out.println(sportTeam2);
System.out.println(sportTeam1 == sportTeam2);
System.out.println(sportTeam1.equals(sportTeam2));
}
}
Projekt W11, pakiet: example15
package example15;
public class Person implements Cloneable{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Name: " + this.name + "|Age: " + this.age;
}
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
package example15;
import java.util.Arrays;
public class Team implements Cloneable{
private Person[] team;
public Team(Person[] team) throws CloneNotSupportedException {
//this.team = team!=null ? team.clone() : new Person[0]; //wrong
this.team = new Person[team.length];
for (int i = 0; i < team.length; i++) {
this.team[i] = team[i].clone();
}
}
@Override
public String toString() {
return getClass().getSimpleName() + ": team=" + Arrays.toString(team);
}
@Override
public Team clone() throws CloneNotSupportedException {
Team t = (Team) super.clone();
// t.team = team.clone(); // this is wrong
t.team = new Person[team.length];
for (int i = 0; i < team.length; i++) {
t.team[i] = team[i].clone();
}
return t;
}
}
package example15;
public class TestTeam {
public static void main(String[] args) throws CloneNotSupportedException {
Person[] team = new Person[3];
team[0] = new Person("John", 20);
team[1] = new Person("Mary", 21);
team[2] = new Person("Peter", 22);
Team t1 = new Team(team);
System.out.println(t1);
try {
Team t2 = t1.clone();
System.out.println(t2);
System.out.println(t1==t2);
System.out.println(t1.equals(t2));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
W praktyce, wybór między klasą abstrakcyjną a interfejsem zależy od konkretnego przypadku użycia. Klasy abstrakcyjne są bardziej odpowiednie do modelowania hierarchii dziedziczenia dla obiektów, podczas gdy interfejsy są lepsze do definiowania wspólnych funkcjonalności, które mogą być współdzielone przez różne klasy, niekoniecznie powiązane hierarchią dziedziczenia.
Cloneable
Projekt W11, pakiet: example15a
package example15a;
public class Car implements Cloneable{
private String brand;
private String model;
private Engine engine;
public Car(String brand, String model, Engine engine) {
this.brand = brand;
this.model = model;
this.engine = engine;
}
public String toString() {
return brand + " " + model + " " + engine;
}
public Engine getEngine() {
return engine;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
public Car clone() throws CloneNotSupportedException {
return (Car) super.clone();
}
}
package example15a;
public class TestCar {
public static void main(String[] args) throws CloneNotSupportedException {
Car car1 = new Car("Ford", "Focus", new Engine("Diesel", 1.6));
Car car2 = car1.clone();
System.out.println(car1);
System.out.println(car2);
System.out.println(car1 == car2);
System.out.println(car1.equals(car2));
System.out.println(car1.getEngine() == car2.getEngine());
}
}
Zasady SOLID to zbiór pięciu zasad projektowania oprogramowania w programowaniu obiektowym, które mają na celu zwiększenie czytelności, elastyczności i podatności na rozbudowę kodu.
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 tworzenie własnych klas wyjątków jest stosunkowo proste i pozwala na lepsze zarządzanie wyjątkami specyficznymi dla danej aplikacji. Oto podstawowe kroki do stworzenia własnej klasy wyjątku:
try-catch
lub zadeklarowane w sygnaturze metody za pomocą throws
. Są one dziedziczone z klasy Exception
.RuntimeException
.Exception
lub RuntimeException
Przykład klasy wyjątku sprawdzanego:
Przykład klasy wyjątku niesprawdzanego:
try {
// Kod, który może rzucić MyCheckedException
} catch (MyCheckedException e) {
// Obsługa wyjątku
}
Dobre Praktyki
InvalidUserInputException
.W Javie, najlepszą praktyką jest sprawdzanie poprawności argumentów metody bezpośrednio wewnątrz samej metody. Dzięki temu zapewniamy, że metoda zawsze działa poprawnie, niezależnie od tego, skąd jest wywoływana. W przypadku metody obliczającej silnię, powinniśmy sprawdzić, czy przekazany argument nie jest liczbą ujemną. Jeśli jest, należy wyrzucić wyjątek IllegalArgumentException
, który jest standardowym wyjątkiem systemowym używanym do sygnalizowania nieprawidłowych argumentów wywołania metody.
Projekt W11, pakiet: example16
package example16;
public class FactorialCalculator {
public static long factorial(int number) {
if (number < 0) {
throw new IllegalArgumentException("The number must be greater than or equal to zero.");
}
long result = 1;
for (int i = 1; i <= number; i++) {
result *= i;
}
return result;
}
}
package example16;
import static example16.FactorialCalculator.factorial;
public class TestFactorialCalculator {
public static void main(String[] args) {
try {
System.out.println("Silnia 5: " + factorial(5));
System.out.println("Silnia -1: " + factorial(-1));
} catch (IllegalArgumentException e) {
System.out.println("Wystąpił błąd: " + e.getMessage());
}
}
}
Projekt W11, pakiet: example16a
package example16a;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("The name cannot be null or empty.");
}
if (age < 0) {
throw new IllegalArgumentException("The age must be greater than or equal to zero.");
}
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("The name cannot be null or empty.");
}
this.name = name;
}
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("The age must be greater than or equal to zero.");
}
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
package example16a;
public class TestPerson {
public static void main(String[] args) {
Person person = new Person("Jan", 30);
System.out.println(person);
// ważne: w przypadku błędu, program zostanie przerwany
// kod powinien mieć kod wyjścia zero na egzaminie (!!!)
try {
Person person2 = new Person(null, 30);
System.out.println(person2);
} catch (IllegalArgumentException e) {
System.out.println("Wystąpił błąd: " + e.getMessage());
}
}
}
Programowanie generyczne to koncepcja w programowaniu, która pozwala na pisanie kodu, który może być używany z różnymi typami danych, bez konieczności powtarzania tego samego kodu dla każdego typu danych. Jest to szczególnie przydatne w językach programowania silnie typowanych, takich jak Java, C# czy C++, gdzie typy danych muszą być określone podczas kompilacji. Oto kluczowe aspekty programowania generycznego:
Typy Parametryzowane: Programowanie generyczne umożliwia tworzenie klas, interfejsów i metod, które działają na “typach generycznych”. Te typy generyczne są określone jako parametry, zazwyczaj reprezentowane przez litery, takie jak T, E, K, V itp.
Zwiększona Znacząco Bezpieczeństwo Typów: Dzięki temu, że typy są określone podczas kompilacji, programowanie generyczne pomaga uniknąć błędów związanych z nieprawidłowym rzutowaniem typów, które mogą wystąpić w trakcie działania programu.
Ograniczenia Typów: Możliwe jest narzucenie ograniczeń na typy generyczne, tak aby akceptowały tylko klasy, które spełniają określone wymagania (np. dziedziczenie po konkretnej klasie bazowej lub implementowanie określonego interfejsu).
Kod Współużytkowany: Kod napisany w sposób generyczny może być używany z różnymi typami danych, co zmniejsza redundancję i ułatwia utrzymanie kodu.
Kompilacja Typu Bezpiecznego: Podczas kompilacji, kompilator sprawdza, czy kod generyczny jest używany poprawnie zgodnie z określonymi typami, co zapewnia wyższe bezpieczeństwo typów i pomaga w wykrywaniu błędów na wcześniejszym etapie rozwoju oprogramowania.
Przykładowe Zastosowania:
List<T>
, Map<K,V>
), co pozwala na tworzenie kolekcji, które mogą przechowywać elementy dowolnego typu, jednocześnie zapewniając bezpieczeństwo typów.Przykład w Javie:
Box<T>
, możesz utworzyć jej instancję jako Box<Integer>
lub Box<String>
. Tutaj Box<Integer>
i Box<String>
są typami parametryzowanymi.Typ Generyczny (Generic Type) Typ generyczny to termin ogólnie odnoszący się do klas, interfejsów i metod, które używają typów parametryzowanych. Obejmuje on zarówno definicję klasy generycznej (jak Box<T>
), jak i konkretne typy parametryzowane (jak Box<Integer>
).
Szablon Klas (Class Template) Szablon klas jest pojęciem bardziej związanym z językami programowania takimi jak C++, które stosują “templates” do osiągnięcia podobnych celów, co generyki w Javie. Szablon klasy w C++ jest schematem dla tworzenia klas lub funkcji, które mogą działać z dowolnym typem.
Przykład w C++:
Cay S. Horstmann, Java. Podstawy. Wydanie XII , Wyd. Helion, 2021.
Projekt W11, pakiet: example17
package example17;
// Cay S. Horstmann, Java. Podstawy. Wydanie XII , Wyd. Helion, 2021.
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T newValue) {
first = newValue;
}
public void setSecond(T newValue) {
second = newValue;
}
}
package example17;
//Cay S. Horstmann, Java. Podstawy. Wydanie XII , Wyd. Helion, 2021.
public class ArrayAlg {
public static Pair<String> minmax(String[] a) {
if (a == null || a.length == 0) {
return null;
}
String min = a[0];
String max = a[0];
for (int i = 1; i < a.length; i++) {
if (min.compareTo(a[i]) > 0) {
min = a[i];
}
if (max.compareTo(a[i]) < 0) {
max = a[i];
}
}
return new Pair<>(min, max);
}
}
package example17;
public class TestPair {
public static void main(String[] args) {
Pair<String> p = new Pair<>("Jan", "Kowalski");
System.out.println(p.getFirst() + " " + p.getSecond());
p.setFirst("Adam");
p.setSecond("Nowak");
System.out.println(p.getFirst() + " " + p.getSecond());
String[] words = {"Ala", "ma", "kota", "i", "psa"};
Pair<String> mm = ArrayAlg.minmax(words);
System.out.println("min = " + mm.getFirst());
System.out.println("max = " + mm.getSecond());
}
}
W programowaniu generycznym w Javie stosuje się pewne konwencje dotyczące oznaczeń typów generycznych, aby ułatwić zrozumienie kodu. Oto najczęściej używane oznaczenia:
E - Element: Jest używany głównie w kolekcjach, jak java.util.List<E>
, java.util.Set<E>
, gdzie E oznacza typ elementów w kolekcji.
K - Key: Używany w kontekście map i wpisów mapy, gdzie K reprezentuje typ klucza. Na przykład w java.util.Map<K, V>
.
V - Value: Również używany w mapach, gdzie V oznacza typ wartości. W java.util.Map<K, V>
, K to klucz, a V to wartość.
T - Type: Jest to ogólny typ, który może być używany w dowolnym kontekście. Na przykład, w klasach generycznych jak java.util.ArrayList<T>
, gdzie T oznacza typ przechowywanych elementów.
N - Number: Czasami używany do oznaczania liczbowych typów danych, szczególnie w klasach rozszerzających java.lang.Number
.
S, U, V itd.: Te litery są używane, gdy są potrzebne dodatkowe typy generyczne, i zwykle są stosowane w kolejności alfabetycznej.
https://docs.oracle.com/javase/tutorial/java/generics/types.html
Tworzenie metod generycznych umożliwia pisaniu metod, które mogą operować na różnych typach danych, jednocześnie zapewniając bezpieczeństwo typów w czasie kompilacji. Oto jak możesz tworzyć metody generyczne:
Deklaracja Typu Generycznego: Typ generyczny jest deklarowany przed typem zwracanym metody. Używa się do tego liter jak T, E, K, V, itd., które działają jako zmienne reprezentujące typy.
Przykład:
W tym przykładzie <T>
przed void
oznacza, że metoda printArray
jest generyczna i operuje na typie T
.
Używanie Typów Generycznych w Ciele Metody: Możesz używać tych typów generycznych jako typów zmiennych, parametrów i typów zwracanych w metodzie.
Ograniczenia Typów (Type Bounds): Możesz ograniczyć rodzaje typów, które mogą być używane z danym typem generycznym, używając słowa kluczowego extends
(dla klas i interfejsów) lub super
(dla ograniczeń dolnych).
Przykład:
public <T extends Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (T element : array) {
if (element.compareTo(max) > 0) {
max = element;
}
}
return max;
}
W tym przypadku <T extends Comparable<T>>
oznacza, że typ T
musi implementować interfejs Comparable<T>
.
Wywoływanie Metod Generycznych: Podczas wywoływania metody generycznej, kompilator zazwyczaj jest w stanie wywnioskować typ generyczny na podstawie kontekstu, ale można też jawnie podać typ generyczny.
Przykład:
Projekt W12, example18
package example18;
// //Cay S. Horstmann, Java. Podstawy. Wydanie XII , Wyd. Helion, 2021.
public class ArrayAlg {
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
}
package example18;
public class TestArrayAlg {
public static void main(String[] args) {
String[] words = {"ABC", "DEF", "GHI", "JKL", "MNO"};
String middle = ArrayAlg.getMiddle(words);
System.out.println(middle);
Integer[] numbers = {1, -2, 7, 8, 12};
Integer middle2 = ArrayAlg.getMiddle(numbers);
System.out.println(middle2);
System.out.println(ArrayAlg.getMiddle("ABC", "DEF", "GHI"));
System.out.println(ArrayAlg.getMiddle(3.4, 177.0, 3.14, -5.6, 177.1));
}
}
Projekt W12, example19
package example19;
public class Test19 {
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
Character[] charArray = {'H', 'E', 'L', 'L', 'O'};
print(intArray);
print(doubleArray);
print(charArray);
}
public static <T> void print(T[] array) {
for (T t : array) {
System.out.println(t);
}
}
}
Projekt W12, example20
package example20;
import java.util.Optional;
public class Test20 {
public static void main(String[] args) {
System.out.println(foo("ABC"));
System.out.println(foo(123));
System.out.println(foo(3.14));
System.out.println(foo2(123));
System.out.println(foo2(3.14));
//System.out.println(foo2("ABC"));
System.out.println(Optional.ofNullable(foo3()));
System.out.println(Optional.ofNullable(foo4(0)));
}
public static <T> int foo(T arg){
return arg.hashCode();
}
public static <T> int foo2(T arg) {
if (arg instanceof Number) {
return (int) Math.pow(((Number) arg).doubleValue(), 2);
}
throw new IllegalArgumentException("Arg musi być liczbą");
}
public static <T> T foo3(){
return null;
}
public static <T> T foo4(int arg) {
if (arg == 0) {
return (T) Integer.valueOf(arg);
} else if (arg == 1) {
return (T) "String";
}
return (T) new Object();
}
}
package example21;
// //Cay S. Horstmann, Java. Podstawy. Wydanie XII , Wyd. Helion, 2021.
public class ArrayAlg {
public static <T> T min(T... a) {
if (a == null || a.length == 0) {
return null;
}
T smallest = a[0];
for (int i = 1; i < a.length; i++) {
if (smallest.compareTo(a[i]) > 0) {
smallest = a[i];
}
}
return smallest;
}
}
Czy czegoś tu nie brak?
Projekt W12, example21
package example21;
// //Cay S. Horstmann, Java. Podstawy. Wydanie XII , Wyd. Helion, 2021.
public class ArrayAlg {
public static <T extends Comparable<T>> T min(T... a) {
if (a == null || a.length == 0) {
return null;
}
T smallest = a[0];
for (int i = 1; i < a.length; i++) {
if (smallest.compareTo(a[i]) > 0) {
smallest = a[i];
}
}
return smallest;
}
}
package example21;
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return getClass().getSimpleName() + "[name=" + name + ",age=" + age + "]";
}
}
package example21;
public class TestPerson {
public static void main(String[] args) {
Double[] numbers = {1.0, 12.0, -3.0};
System.out.println(ArrayAlg.min(numbers));
Person[] people = {new Person("Jan", 12), new Person("Anna", 10), new Person("Piotr", 15)};
System.out.println(ArrayAlg.min(people));
}
}
<T extends typ_graniczny>
jest składnią używaną w programowaniu generycznym do określenia górnej granicy dla typu generycznego T
. Oznacza to, że T
musi być podtypem (klasą pochodną) klasy określonej jako typ_graniczny
lub sama być tym typem.
Wymazywanie typów (ang. type erasure) to proces stosowany w Javie w kontekście programowania generycznego, który zapewnia kompatybilność wsteczną z wcześniejszymi wersjami Javy, które nie obsługiwały generyków. Kiedy kod zawierający generyki jest kompilowany, kompilator usuwa (wymazuje) wszelkie informacje o typach generycznych, zastępując je ich ograniczeniami lub, jeśli takie nie istnieją, obiektem najbardziej ogólnym (często Object
).
Jak Działa Wymazywanie Typów?
Object
, jeśli brak jest ograniczeń. Na przykład, dla class Box<T>
, T
zostanie zastąpione przez Object
podczas kompilacji.List<String>
czy List<Integer>
w czasie wykonania.Wymazywanie typów ma kilka konsekwencji: - Brak Możliwości Przeciążania Metod: Metody różniące się jedynie typem generycznym nie mogą być przeciążone, ponieważ po wymazaniu będą miały ten sam sygnaturę.
Konieczność Rzutowania: W czasie wykonania trzeba czasami ręcznie rzutować obiekty na odpowiedni typ, co może prowadzić do błędów ClassCastException
.
Brak Możliwości Sprawdzenia Typu Generycznego w Runtime: Nie można używać refleksji do dokładnego ustalenia typu generycznego w czasie wykonania, ponieważ informacje te są wymazane.
// Cay S. Horstmann, Java. Podstawy. Wydanie XII , Wyd. Helion, 2021.
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T newValue) {
first = newValue;
}
public void setSecond(T newValue) {
second = newValue;
}
}
jest wymazywany na:
public class Pair {
private Object first;
private Object second;
public Pair() {
first = null;
second = null;
}
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
public void setFirst(Object newValue) {
first = newValue;
}
public void setSecond(Object newValue) {
second = newValue;
}
}
public static <T extends Comparable<T>> T min(T... a) {
if (a == null || a.length == 0) {
return null;
}
T smallest = a[0];
for (int i = 1; i < a.length; i++) {
if (smallest.compareTo(a[i]) > 0) {
smallest = a[i];
}
}
return smallest;
}
jest wymazywany na:
Typy proste (ang. primitive types), takie jak int
, double
, char
, itd., nie mogą być używane jako typy generyczne. Wynika to z kilku powodów, głównie związanych z tym, jak generyki są implementowane w Javie oraz jak działają typy proste:
Object
.Object
w procesie wymazywania typów.Integer
dla int
, Double
dla double
itp.List<int>
, używa się List<Integer>
.Projekt W12, example22
package example22;
// Cay S. Horstmann, Java. Podstawy. Wydanie XII , Wyd. Helion, 2021.
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T newValue) {
first = newValue;
}
public void setSecond(T newValue) {
second = newValue;
}
}
Termin “typ wieloznaczny” (ang. wildcard type) odnosi się do typów generycznych, które nie są dokładnie określone, czyli używają symbolu zapytania ?
jako zastępczego oznaczenia typu. Typy wieloznaczne pozwalają na większą elastyczność w definiowaniu i wykorzystywaniu generycznych struktur danych i metod, ponieważ mogą reprezentować szeroki zakres różnych typów.
Rodzaje Typów Wieloznacznych:
?
):
List<?>
może być listą dowolnego typu obiektów.? extends T
):
T
lub dowolnej jej podklasy. Na przykład, List<? extends Number>
może być listą obiektów typu Number
lub dowolnego typu, który jest podklasą Number
(jak Integer
czy Double
).? super T
):
T
lub dowolnej jej nadklasy. Na przykład, List<? super Integer>
może być listą obiektów typu Integer
lub dowolnego typu, który jest nadklasą Integer
(jak Number
czy Object
).Projekt W12, example23
package example23;
// Cay S. Horstmann, Java. Podstawy. Wydanie XII , Wyd. Helion, 2021.
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T newValue) {
first = newValue;
}
public void setSecond(T newValue) {
second = newValue;
}
}
package example23;
public class Animal {
@Override
public String toString() {
return getClass().getSimpleName();
}
}
package example23;
public class Test23 {
public static void main(String[] args) {
Pair<Animal> animals = new Pair<>(new Animal(), new Animal());
printAnimals(animals);
Pair<Dog> dogs = new Pair<>(new Dog(), new Dog());
//printAnimals(dogs);
printAnimalsFix(animals);
printAnimalsFix(dogs);
printAnimalsFix2(animals);
printAnimalsFix2(dogs);
printAnimalsFix3(animals);
printAnimalsFix3(dogs);
}
public static void printAnimals(Pair<Animal> animals) {
System.out.println(animals.getFirst().toString() + " " + animals.getSecond().toString());
}
public static void printAnimalsFix(Pair<? extends Animal> animals) {
System.out.println(animals.getFirst().toString() + " " + animals.getSecond().toString());
}
public static void printAnimalsFix2(Pair<? super Dog> animals) {
System.out.println(animals.getFirst().toString() + " " + animals.getSecond().toString());
}
public static void printAnimalsFix3(Pair<?> animals) {
System.out.println(animals.getFirst().toString() + " " + animals.getSecond().toString());
}
}
Projekt W12, example24
package example24;
// Cay S. Horstmann, Java. Podstawy. Wydanie XII , Wyd. Helion, 2021.
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T newValue) {
first = newValue;
}
public void setSecond(T newValue) {
second = newValue;
}
}
package example24;
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Name cannot be null or blank");
}
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
this.age = age;
}
public void setName(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Name cannot be null or blank");
}
this.name = name;
}
@Override
public String toString() {
return getClass().getSimpleName() + ": name=" + name + ", age=" + age;
}
@Override
public int compareTo(Person o) {
int base = this.name.compareTo(o.name);
if (base != 0) {
return base;
}
return Integer.compare(this.age, o.age);
}
}
package example24;
public class Student extends Person implements Comparable<Person>{
private int studentId;
public Student(String name, int age, int studentId) {
super(name, age);
if (studentId <10000 || studentId > 999999) {
throw new IllegalArgumentException("Wrong student ID");
}
this.studentId = studentId;
}
public int getStudentId() {
return studentId;
}
public void setStudentId(int studentId) {
if (studentId <10000 || studentId > 999999) {
throw new IllegalArgumentException("Wrong student ID");
}
this.studentId = studentId;
}
@Override
public String toString() {
return super.toString() + ", studentId=" + studentId;
}
@Override
public int compareTo(Person o) {
if (o instanceof Student) {
Student student = (Student) o;
int base = super.compareTo(student);
if (base != 0) {
return base;
}
return Integer.compare(this.studentId, student.studentId);
}
return super.compareTo(o);
}
}
package example24;
public class Test24 {
public static void main(String[] args) {
Person[] people = new Person[4];
people[0] = new Person("John", 20);
people[1] = new Person("John", 30);
people[2] = new Person("Adam", 20);
people[3] = new Person("Adam", 16);
System.out.println("Case 1");
Pair<Person> pair = minmaxOld(people);
System.out.println(pair.getFirst());
System.out.println(pair.getSecond());
Student[] students = new Student[4];
students[0] = new Student("John", 20, 125478);
students[1] = new Student("John", 30, 122278);
students[2] = new Student("Adam", 20, 125433);
students[3] = new Student("Adam", 16, 165478);
//Pair<Student> pair2 = minmaxBad(students); // to nie jest możliwe
System.out.println("Case 2");
Pair<Student> pair2 = minmax(students);
System.out.println(pair2.getFirst());
System.out.println(pair2.getSecond());
Person[] people2 = new Person[6];
people2[0] = new Person("John", 20);
people2[1] = new Person("John", 30);
people2[2] = new Person("Adam", 20);
people2[3] = new Student("John", 20, 125478);
people2[4] = new Student("John", 30, 122278);
people2[5] = new Student("Adam", 20, 125433);
System.out.println("Case 3");
Pair<Person> pair3 = minmaxOld(people2);
System.out.println(pair3.getFirst());
System.out.println(pair3.getSecond());
System.out.println("Case 4");
Pair<Person> pair4 = minmax(people2);
System.out.println(pair4.getFirst());
System.out.println(pair4.getSecond());
}
public static <T extends Comparable<T>> Pair<T> minmaxOld(T[] a) {
if (a == null || a.length == 0) {
return null;
}
T min = a[0];
T max = a[0];
for (int i=1; i<a.length; i++) {
if (min.compareTo(a[i]) > 0) {
min = a[i];
}
if (max.compareTo(a[i]) < 0) {
max = a[i];
}
}
return new Pair<>(min, max);
}
public static <T extends Comparable<? super T>> Pair<T> minmax(T[] a) {
if (a == null || a.length == 0) {
return null;
}
T min = a[0];
T max = a[0];
for (int i=1; i<a.length; i++) {
if (min.compareTo(a[i]) > 0) {
min = a[i];
}
if (max.compareTo(a[i]) < 0) {
max = a[i];
}
}
return new Pair<>(min, max);
}
}