Java SE Laboratorium nr 2 Temat: Obiektowość 1. Typy proste a. byte 8 bit b. short 16 bit c. int 32 bit d. long 64 bit e. float (32-bitowy standard IEEE 754) f. double (64-bitowy standard IEEE 754) g. boolean h. char 16 bit int zmiennatypuint = 6; 2. Zmienne referencyjne (odnośnikowe, obiektowe) Zmienną typu referencyjnego deklaruje się w sposób podobny do deklaracji zmiennej typu prostego. Utworzona zostanie jednak tylko referencja, do której zostanie przypisana wartość null. Takiej zmiennej po deklaracji należy przypisać odniesienie do utworzonego instrukcją new obiektu. String zmiennareferencyjna; zmiennareferencyjna = new String("napis"); 3. Instrukcje sterujące a. if void applybrakes() { // the "if" clause: bicycle must be moving if (ismoving){ // the "then" clause: decrease current speed currentspeed--; void applybrakes() { if (ismoving) { currentspeed--; else { System.err.println("The bicycle has already stopped!"); class IfElseDemo { 1
int testscore = 76; char grade; if (testscore >= 90) { grade = 'A'; else if (testscore >= 80) { grade = 'B'; else if (testscore >= 70) { grade = 'C'; else if (testscore >= 60) { grade = 'D'; else { grade = 'F'; System.out.println("Grade = " + grade); b. switch public class SwitchDemo { int month = 8; String monthstring; switch (month) { case 1: monthstring = "January"; case 2: monthstring = "February"; case 3: monthstring = "March"; case 4: monthstring = "April"; case 5: monthstring = "May"; case 6: monthstring = "June"; case 7: monthstring = "July"; case 8: monthstring = "August"; case 9: monthstring = "September"; case 10: monthstring = "October"; case 11: monthstring = "November"; case 12: monthstring = "December"; default: monthstring = "Invalid month"; System.out.println(monthString); 2
Instrukcja break nie jest obowiązkowa. Jednak jeśli jej nie będzie wykonane zostaną wszystkie kolejne instrukcje case, aż do napotkania break, lub do końca bloku switch. c. Operator warunkowy warunek? wartość1 : wartość2 Jeśli warunek jest prawdziwy, podstaw za wartość wyrażenia wartość1, w przeciwnym wypadku wartość2. int liczba1 = 10; int liczba2; liczba2 = liczba1 < 0? -1 : 1; System.out.println(liczba2); d. for class ForDemo { public static void main(string[] args){ for(int i=1; i<11; i++){ System.out.println("Count is: " + i); int[] numbers = {1,2,3,4,5,6,7,8,9,10; for (int item : numbers) { System.out.println("Count is: " + item); e. while do while class WhileDemo { public static void main(string[] args){ int count = 1; while (count < 11) { System.out.println("Count is: " + count); count++; class DoWhileDemo { public static void main(string[] args){ int count = 1; do { System.out.println("Count is: " + count); count++; while (count < 11); 3
f. break i continue Instrukcja break przerywa działanie pętli i switcha. Instrukcja continue przerywa wykonywanie bieżącej iteracji i przechodzi do następnej. Isnieją wersje etykietowane tych instrukcji. Etykietę umieszcza się przed instrtukcją pętli. Etykietowane break lub continue może wtedy wychodzić z kilku zagnieżdżonych pętli. class BreakWithLabelDemo { int[][] arrayofints = { { 32, 87, 3, 589, { 12, 1076, 2000, 8, { 622, 127, 77, 955 ; int searchfor = 12; int i; int j = 0; boolean foundit = false; search: //etykieta for (i = 0; i < arrayofints.length; i++) { for (j = 0; j < arrayofints[i].length; j++) { if (arrayofints[i][j] == searchfor) { foundit = true; break search; //etykietowane break if (foundit) { System.out.println("Found " + searchfor + " at " + i + ", " + j); else { System.out.println(searchfor + " not in the array"); 4. Tablice Tablica to prosta struktura danych zawierająca elementy jednego typu. Tablice w Javie są obiektami. Aby utworzyć tablicę należy zadeklarować referencję do danego typu tablicy, a następnie utworzyć obiekt tablicy. class Tablice { int[] tab; tab = new int[2]; tab[0] = 4; // int[] tab = {4,7; //zapis skrócony System.out.println("Pierwszy element tablicy to " + tab[0]); 4
Dzięki temu, że tablice są obiektami, każda z nich posiada właściwość length, która przechowuje rozmiar tablicy. class Tablice { short[] tab = new short[10]; for(short i=0;i<tab.length;i++) tab[i] = i; for(short i=0;i<tab.length;i++) System.out.println("tab[" + i + "] = " + tab[i]); Tablice mogą być wielowymiarowe. Ich struktura jest taka, że każda komórka przechowuje referencję do kolejnej tablicy. W ten sposób można tworzyć tablice nieregularne. class Tablice { int[][] tab = new int[4][]; tab[0] = new int[4]; //W pierwszym wierszu tablica czteroelementowa tab[1] = new int[2]; //W drugim wierszu dwie komórki tab[2] = new int[1]; //... tab[3] = new int[3]; int licznik = 1; for(int i=0;i<tab.length;i++) for(int j=0;j<tab[i].length;j++) tab[i][j] = licznik++; for(int i=0;i<tab.length;i++){ System.out.print("tab[" + i + "] = "); for(int j=0;j<tab[i].length;j++) System.out.print(tab[i][j] + " "); System.out.println(""); 5. Klasy i obiekty Klasy są opisami obiektów. W klasach definiujemy pola i metody. Klasy mogą być: - publiczne - pakietowe - wewnętrzne a. Pola klasy - publiczne - chronione - pakietowe domyślne, gdy nie ma żadnego specyfikatora - prywatne Domyślnie wartości pól ustawiane są na zero (0, 0.0, \0, null, false). 5
class Punkt{ int x; int y; b. Tworzenie obiektów (Klasa Punkt i klasa Main w tym samym pakiecie) public class Main { Punkt punkt = new Punkt(); punkt.x = 10; punkt.y = 20; System.out.println("punkt.x = " + punkt.x); System.out.println("punkt.y = " + punkt.y); class Punkt { int x; int y; c. Metody - publiczne - chronione - pakietowe domyślne, gdy nie ma żadnego specyfikatora - prywatne void wyswietlwspolrzedne(){ System.out.println("punkt.x = " + x); System.out.println("punkt.y = " + y); public class Main { Punkt punkt = new Punkt(); punkt.x = 10; punkt.y = 20; punkt.wyswietlwspolrzedne(); Metody mogą zwracać wartość (metoda w klasie Punkt): int getx() { return x; 6
d. Argumenty metod void setx(int wspx){ x = wspx; void setxy(int wspx, int wspy){ x = wspx; y = wspy; Parametrem metody, a także wartością zwracaną może być obiekt. void setxy(punkt punkt){ x = punkt.x; y = punkt.y; Punkt getpunkt(){ Punkt punkt = new Punkt(); punkt.x = x; punkt.y = y; return punkt; Metody można przeciążać, czyli możemy mieć kilka metod o takiej samej nazwie, ale o różnych parametrach (setxy(int wspx, int wspy) i setxy(punkt punkt)). W przypadku przeciążania metod typ wartości zwracanej nie jest brany pod uwagę. e. Konstruktory Są to specjalne metody wywoływane zawsze po utworzeniu obiektu w pamięci. Służą do inicjacji obiektów. Nigdy nie zwracają żadnego wyniku. Nazwa jest taka sama jak nazwa klasy. Jeśli nie utworzymy żadnego konstruktora, kompilator niejawnie dodaje konstruktor domyślny bezparametrowy. Konstruktory można przeciążać. Punkt() { x = 1; y = 1; Punkt(int wspx, int wspy) { x = wspx; y = wspy; Punkt(Punkt punkt) { x = punkt.x; y = punkt.y; Mając różne konstruktory, możemy w różny sposób tworzyć obiekty: 7
public class Main { Punkt punkt1 = new Punkt(); punkt1.wyswietlwspolrzedne(); Punkt punkt2 = new Punkt(3, 7); punkt2.wyswietlwspolrzedne(); Punkt punkt3 = new Punkt(punkt2); punkt3.wyswietlwspolrzedne(); f. Słowo kluczowe this Słowo kluczowe this to odwołanie do bieżącego obiektu. Możemy je traktować jako referencję do aktualnego obiektu. Pozwala np. odróżnić pola klasy od parametrów metody, czy konstruktora, które są zmiennymi lokalnymi. Punkt(int x, int y) { this.x = x; this.y = y; 6. Dziedziczenie a. Klasa pochodna W Javie nie ma wielodziedziczenie. Możemy dziedziczyć tylko po jednej klasie. Klasą, z której pośrednio lub bezpośrednio dziedziczą wszystkie inne jest klasa Object. Klasa potomna przejmuje właściwości i zachowanie klasy bazowej. Dziedziczenie realizuje się za pomocą słowa extends. Tworzymy klasę Punkt3D, która będzie dziedziczyła po klasie Punkt. Ponieważ będzie ona zawierała wszystkie pola i metody klasy bazowej, należy do niej dodać tylko nową funkcjonalność: pole z, oraz obsługujące je metody. class Punkt3D extends Punkt { int z; void setz(int z){ this.z = z; int getz(){ return z; void setxyz(int x, int y, int z){ this.x = x; this.y = y; this.z = z; 8
b. Metoda super() Aby klasa była w pełni funkcjonalna należy dodać do niej konstruktory. Punkt3D() { x = 1; y = 1; z = 1; Punkt3D(int x, int y, int z) { this.x = x; this.y = y; this.z = z; Punkt3D(Punkt3D punkt) { x = punkt.x; y = punkt.y; z = punkt.z; Porównując je z konstruktorami klasy bazowej widać, że są one bardzo podobne. Są ich rozszerzonymi wersjami. Można zatem wykorzystać już istniejące konstruktory klasy bazowej, wywołać je i nie dublować tego samego kodu. W istocie, to zawsze gdy wywoływany jest konstruktor klasy pochodnej, to niejawnie na początku wywoływany jest domyślny konstruktor klasy bazowej. Czyli najpierw tworzony jest obiekt klasy bazowej, a dopiero później jest rozbudowywany do obiektu klasy pochodnej. Jeśli więc klasa bazowa nie ma konstruktora domyślnego, wystąpi błąd kompilacji. Dlatego też należy pisać konstruktory domyślne bezparametrowe. Jeśli natomiast jawnie wywołamy jakikolwiek konstruktor (w pierwszej linii konstruktora klasy pochodnej i tylko tu!), to konstruktor domyślny nie będzie wywoływany. Ponieważ konstruktor jest specjalną metodą, której nie da się wywołać w zwykły sposób, wykorzystuje się do tego celu specjalną konstrukcję. Jest tometoda super(), którą należy wywołać w pierwszej linii konstruktora klasy pochodnej. Punkt3D() { super(); z = 1; Punkt3D(int x, int y, int z) { super(x,y); this.z = z; Punkt3D(Punkt3D punkt) { super(punkt); z = punkt.z; Ostatni konstruktor wykorzystuje fakt, że obiekt klasy pochodnej może być także traktowany jak obiekt klasy bazowej. Zawiera on w sobie obiekt klasy bazowej więc nie jest to dziwne. Dlatego też pomimo tego, że w klasie bazowej nie mamy konstruktora 9
przyjmującego jako argument obiekt typu Punkt3D, a tylko taki, który przyjmuje obiekt klasy Punkt, to właśnie ten konstruktor jest wykorzystywany przez konstrukcję super(punkt), do której przekazujemy obiekt klasy pochodnej. c. Specyfikatory dostępu W Javie mamy cztery specyfikatory (modyfikatory) dostępu: - publiczny dostęp do składowych klasy bez ograniczeń - chroniony dostęp do składowych z klas pochodnych i klas z tego samego pakietu - pakietowy dostęp do składowych z klas z tego samego pakietu - prywatny dostęp do składowych tylko z wnętrza klasy W poprzednich przykładach wykorzystywany był dostęp domyślny, czyli pakietowy. Należy jednak stosować hermetyzację klas i dostęp do zasobów klasy powierzać tylko publicznemu interfejsowi, czyli publicznym metodom danej klasy. Czyli pola klasy Punkt powinny być chronione (bo są dziedziczone), natomiast metody typu get i set powinny być publiczne. Dlaczego? Np. w przypadku klasy Punkt, jeśli np. z jakiegokolwiek powodu chcielibyśmy zmienić współrzędne punktów z układu kartezjańskiego na biegunowy, to zmienić będziemy musieli dodatkowo tylko metody publicznego dostępu do naszych współrzędnych. Natomiast wszelkie inne klasy (w tym wypadku klasa Main), które korzystają z klasy Punkt nie muszą być zmieniane. Jeśli natomiast inne klasy korzystałyby z bezpośredniego dostępu do współrzędnych punktu, to po ich zmianie, te klasy także musiałyby ulec zmianie. class Punkt { private double sinusalfa; private double r; public int getx(){ double x = r * sinusalfa; return (int) x; Klasy mogą być albo publiczne albo pakietowe. W jednym pliku może się znaleźć tylko jedna klasa o dostępie publicznym, pozostałe muszą mieć dostęp pakietowy. Inne specyfikatory są możliwe w przypadku klas wewnętrznych. d. Przeciążanie i przesłanianie metod Metody mogą być przeciążane (ta sama nazwa, różne parametry) zarówno w klasie bazowej jak i w pochodnej. Dodatkowo w klasie pochodnej mogą być przesłaniane, czyli w klasie pochodnej możemy zdefiniować metodę o takiej samej sygnaturze jak w klasie bazowej, ale wykonującą inne, zmodyfikowane zadanie. W obiektach klasy bazowej będzie obowiązywała metoda oryginalna, w obiektach klasy pochodnej przesłaniająca (zmieniona). 10
Jeśli w klasie pochodnej chcemy odwołać się do przesłoniętej metody z klasy bazowej, musimy znowu posłużyć się konstrukcją super.metodabazowa(); Najczęściej tą konstrukcję stosuje się w metodach przesłaniających w klasie pochodnej do wywołania jej pierwotnej wersji. class Punkt3D extends Punkt { private int z; public void wyswietlwspolrzedne() { super.wyswietlwspolrzedne(); System.out.println("punkt.z = " + z); W podobny sposób można przesłaniać pola, co jest niezalecane. e. Składowe statyczne Pola i metody klasy mogą być statyczne. Oznacza to, że istnieją one nawet bez powoływania obiektu do życia i są wspólne dla wszystkich obiektów tej klasy. Odwoływać się do nich można przez nazwę obiektu lub nazwę klasy. public static void main(string[] args) f. Klasy i składowe finalne Klasa finalna to klasa, z której nie wolno wyprowadzać innych klas, czyli nie można po niej dziedziczyć. Pola finalne to pola, dla których nie możemy zmienić raz przypisanej wartości. Za pomocą tych pól możemy tworzyć stałe: static final double PI = 3.141592653589793; Pola finalne typów referencyjnych nie pozwalają zmienić raz przypisanego obiektu do danego pola, natomiast możemy modyfikować pola obiektu, na który ta referencja wskazuje. final Punkt punkt = new Punkt(); punkt.setx(5); //punkt = new Punkt(3,5); //błąd Finalna metoda oznacza, że jej przesłonięcie w klasie potomnej nie jest możliwe. Finalne mogą być również argumenty metod i oznacza to, że nie mogą być zmienione w ciele metody. 7. Polimorfizm a. Konwersja typów i rzutowanie obiektów 11
Konwersje i rzutowania mogą być albo w dół albo w górę. W górę oznaczają konwersje na typy bardziej ogólne, bazowe i są automatyczne. W dół oznaczają konwersje na typy bardziej wyspecjalizowane, pochodne i muszą być jawnie wywołane przez programistę. Konwersje dotyczą typów prostych. Konwersje w dół na typ mniej pojemny mogą prowadzić do utraty informacji. int x; double y = 3.7; x = (int)y; // x = 3 Typy obiektowe również podlegają konwersjom. Jednak w tym wypadku mamy do czynienia z rzutowaniem typów, które nie powoduje utraty informacji. Obiekt klasy potomnej możemy rzutować na obiekt klasy bazowej, a obiekt klasy bazowej na obiekt klasy pochodnej. Pierwszy przypadek jest oczywisty, obiekt potomny zawiera w sobie obiekt bazowy. Punkt3D punkt3d1 = new Punkt3D(); Punkt punkt = (Punkt)punkt3D1; Punkt3D punkt3d2 = (Punkt3D)punkt; W przypadku tego rzutowania nie utracimy żadnej informacji. Zmienia się tylko perspektywa z jakiej patrzymy na nasz obiekt. W pierwszej linii patrzymy na nowo utworzony obiekt punkt3d1 poprzez referencję typu Punkt3D, więc widzimy i mamy dostęp do wszystkich jego składowych. W drugiej linii rzutujemy go na klasę bazową i patrzymy na niego poprzez referencję klasy bazowej, czyli mamy dostęp do składowych takich jakie występują w klasie bazowej. W trzeciej linii dokonujemy z kolei konwersji obiektu klasy bazowej na klasę pochodną. I znowu mamy dostęp do wszystkich elementów (niezmienionych) klasy pochodnej. W rzeczywistości był tu cały czas jeden obiekt, na który spoglądaliśmy poprzez różne referencje. b. Klasa Object i metoda tostring Wszystkie klasy pośrednio lub bezpośrednio dziedziczą po klasie Object. Jedną z podstawowych metod tej klasy, którą bardzo często się wykorzystuje nawet nie zdając sobie z tego sprawy jest metoda tostring. Jest ona wywoływana zawsze gdy oczekiwanym argumentem jest String a my przekazujemy referencję do jakiegokolwiek obiektu. W większości przypadków należy więc ją przesłonić i zaimplementować w klasie potomnej. Metoda tostring w klasie Punkt: public String tostring() { return "Punkt o współrzędnych (" + x + "," + y +")"; Wywołanie: public class Main { Punkt punkt = new Punkt(3, 8); System.out.println(punkt); // zmienna klasy Punkt 12
Wyświetli w konsoli napis: Punkt o współrzędnych (3,8) c. Wywołania polimorficzne W Javie wszystkie metody są wirtualne, a wszystkie ich wywołania polimorficzne (poza wywołaniami konstruktorów). Polimorfizm oznacza późne wiązanie, wiązanie czasu wykonania. W przypadku rzutowania w dół uniemożliwia wykonanie niedozwolonych operacji, natomiast w przypadku rzutowania w górę wywoływane są metody klas pochodnych (o ile istnieją) nawet jeśli referencje do obiektów są klasy bazowej. Skojarzenie wywołania metody z obiektem następuje w trakcie wykonywania programu, a nie w czasie kompilacji. class Zwierz { public String tostring() { return "To jest zwierzę"; class Kot extends Zwierz { public String tostring() { return "To jest kot"; class Pies extends Zwierz { public String tostring() { return "To jest pies"; public class Main { Zwierz z1 = new Kot(); Zwierz z2 = new Pies(); System.out.println(z1); // To jest kot System.out.println(z2); // To jest pies Jeśli w klasie pochodnej nie będzie przesłoniętej metody klasy bazowej, wywołana zostanie metoda bazowa. 13
8. Interfejsy a. Klasy abstrakcyjne Klasa bazowa często służy tylko do zdefiniowania zestawu metod, którymi będą posługiwały się klasy potomne, oraz udostępnienia wywołań polimorficznych. Nie ma więc potrzeby, a nawet niewskazane jest aby były tworzone w takich przypadkach obiekty klas bazowych. Taką możliwość dają klasy abstrakcyjne. Klasa abstrakcyjna to klasa, w której przynajmniej jedna metoda jest abstrakcyjna. Oznacza się ją słowem kluczowym abstract. Zarówno metodę jak i klasę. Metoda abstrakcyjna zawiera tylko definicję zakończoną średnikiem. Zadeklarowanie metody jako abstrakcyjnej wymusza jej redeklarację w klasie pochodnej. abstract class Zwierz { protected abstract void dajglos(); public String tostring() { return "To jest zwierzę"; class Kot extends Zwierz { protected void dajglos() { System.out.println("miauuuuu"); public String tostring() { return "To jest kot"; class Pies extends Zwierz { protected void dajglos() { System.out.println("hau hau"); public String tostring() { return "To jest pies"; b. Tworzenie interfejsów 14
Interfejs to klasa czysto abstrakcyjna, zawierająca sygnatury metod, które domyślnie są abstrakcyjne. Deklarowany jest ze słowem kluczowym interface. Może być publiczny lub pakietowy. Oprócz metod interfejsy mogą zawierać pola, które zawsze są publiczne, statyczne i finalne, co powoduje, że nie musimy tych specyfikatorów dodawać. public interface Latający { int SKRZYDLA = 2; public void lec(); c. Konflikty nazw Klasa może dziedziczyć tylko po jednej klasie bazowej, lecz może implementować wiele interfejsów. Może jednak dochodzić wtedy do konfliktów nazw, które należy po prostu Eliminować. Jeśli klasa implementuje kilka interfejsów i w dwóch z nich mamy identyczną metodę, to implementujemy tylko jedną metodę. Konflikt zachodzi wtedy, gdy mamy taką samą nazwę metody, takie same parametry, lecz inny typ wyniku zwracanego. class Sokol extends Zwierz implements Latajacy, Atakujacy{ protected void dajglos() { System.out.println("hau hau"); public void lec() { // TODO Auto-generated method stub public void atakuj() { // TODO Auto-generated method stub public String tostring() { return "To jest pies"; Interfejsy można budować stosując mechanizm dziedziczenia. W tym przypadku możliwe jest wielodziedziczenie. Interfejs potomny będzie zawierał wszystkie metody interfejsów bazowych oraz swoje. Ze względu na wielodziedziczenie mogą tu także występować konflikty nazw. W interfejsach mogą także występować metody domyślne (default), oraz statyczne, które to mogą zawierać napisane ciała. Jeśli klasa implementuje dany interfejs, to obiekt tej klasy można rzutować na typ tego interfejsu. Oznacza to, że typ obiektu może być utożsamiany z typem interfejsu, który jest w nim zaimplementowany. 15
9. Klasy wewnętrzne a. Rodzaje klas wewnętrznych b. Obiekty klas wewnętrznych c. Klasy anonimowe d. Klasy zagnieżdżone - statyczne Ćwiczenia samodzielne: 1. Stwórz abstrakcyjną klasę Publication zawierającą pola: author, title, year. 2. Stwórz potomka klasy Publication o nazwie Book. Dodaj pole składowe pages (ilość stron). 3. Stwórz potomka klasy Publication o nazwie Article. Dodaj pole składowe journalname. 4. Zadbaj o enkapsulację. 5. Przygotuj metodę public String tostring() dla stworzonych klas, za pomocą której wypisać będzie można informacje o publikacjach. 6. Stwórz stałą BOOKLET_PAGE_LIMIT, która będzie określała ile maksymalnie stron może mieć wydawnictwo typu Booklet. 7. Stwórz klasę Booklet, dziedziczącą po Book z dodatkowym polem składowym published zmienna typu logicznego informująca czy dana ulotka jest opublikowana czy nie. 8. Przygotuj konstruktory dla wymienionych klas. Zadbaj o to, by każdy Booklet, nie mógł mieć więcej niż BOOKLET_PAGE_LIMIT stron. 9. Przygotuj w f. main program, gdzie powołane zostanie do życia kilka obiektów klas wymienionych powyżej. Zainicjuj ich pola składowe wartościami losowymi. Patrz klasa Random: http://docs.oracle.com/javase/7/docs/api/java/util/random.html 10. Przygotuj interfejs summarizebook, który będzie zawierał jedną metodę. 11. Klasa Book ma implementować interfejs summarizebook, a metoda z tego interfejsu ma generować losowo 20-znakowe streszczenie szyfr książki. 16