Mój projekt pierwszy z baaardzo obszernym komentarzem.
Krótka instrukcja użycia:
Wymagania co do projektu są tu:
http://www.users.pjwstk.edu.pl/~mtrzaska/Files/MAS/MAS-informacje-stacjonarne.pdf
Jak zrobić 'ekstensje’ (są w projekcie, ale tu macie opis o co chodzi):
http://www.mtrzaska.com/plik/mas/mas-wyklad-nr-04
Projekt składa się z 4 plików:
Main.java
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Date; import java.util.Random; public class Main { public static void main(String[] arg) throws Exception { // jesli wczesniej cos zapisalem [odpalam program po raz kolejny] to wczytaj to co bylo // DEMONSTRACJA DZIALANIA 'TRWALYCH EKSTENSJI' (trwale czyli nie znikaja po zamnieciu programu) if(new File("mojeTajneDane").isFile()) { try { // plik jest w 'workspace' FileInputStream fileIn = new FileInputStream("mojeTajneDane"); ObjectInputStream inStream = new ObjectInputStream(fileIn); // odczytuje wszystko z dysku do pliku 'mojeTajneDane' jedna linijka // 'magia javy' zrobi reszte ObjectPlus.odczytajEkstensje(inStream); inStream.close(); fileIn.close(); } catch(IOException i) { i.printStackTrace(); return; } catch(ClassNotFoundException c) { System.out.println("Klasa nie znaleziona."); c.printStackTrace(); return; } } // utworz nowego Grzegorza o wzroscie miedzy 165, a 195 Random rand = new Random(); Student studencik = new Student("Grzegorz" + rand.nextInt(100), 165 + (rand.nextInt(31))); // na date urodzenia ustawiamy czas jaki jest 'teraz' studencik.ustawDateUrodzenia(new Date()); // wywoluje przeciazana metode z parametrem 'int' studencik.dodajOcene(2 + rand.nextInt(4)); // wywoluje przeciazana metode z parametrem 'Double' studencik.dodajOcene(new Double(2 + ((double)rand.nextInt(7) / 2))); // pokaz wszystkich Grzegorzy jacy istnieja ObjectPlus.pokazEkstensje(Student.class); // przed zamknieciem programu zapisz wszystkie ekstensje do pliku // DEMONSTRACJA DZIALANIA 'TRWALYCH EKSTENSJI' (trwale czyli nie znikaja po zamnieciu programu) try { // plik jest w 'workspace' FileOutputStream fileOut = new FileOutputStream("mojeTajneDane"); ObjectOutputStream outStream = new ObjectOutputStream(fileOut); // zapisuje wszystko na dysk do pliku 'mojeTajneDane' jedna linijka // 'magia javy' zrobi reszte ObjectPlus.zapiszEkstensje(outStream); outStream.close(); fileOut.close(); } catch(IOException i) { i.printStackTrace(); } } }
Osoba.java
import java.util.Date; public abstract class Osoba extends ObjectPlus { /* wymagane do serializacji, w przyszlosci jak cos zmienisz w tej klasie [dodasz/usuniesz atrybut] to zmienisz ta liczbe na inna, dzieki temu mechanizm 'serialize' nie pozwoli zaladowac pliku z dysku z starymi danymi do programu ktory uzywa innej klasy (przeciez moze byc zupelnie inna klasa o nazwie Student i dane takie jak 'oceny' moga w niej nie wystepowac) */ private static final long serialVersionUID = 1L; /* PRZYPOMNIENIE: Co to jest 'protected'? * To takie cos jak 'private' tylko, ze zmienna jest widziana w klasach 'dziedziczacych' (rozszerzajacych). * Dzieki temu bedzie mozna wyswietlac np. 'imie' w studencie ktory dziedziczy po osobie. */ // atrybut prosty, wymagany, pojedynczy, obiektu protected String imie; /* * WYMAGANY: * zawsze kiedy go ustawiamy to sprawdzamy czy ktos nam nie przekazal 'null' */ // atrybut prosty, opcjonalny, pojedynczy, obiektu protected String nazwisko; /* * OPCJONALNY: * zawsze kiedy go wyswietlany to sprawdzany cyz ktos nam nie przekazal 'null' * jesli 'null' to trzeba wyswietlic o tym informacje lub inaczej to 'obsluzyc', * bo proba wyswietlenie 'null' skonczy sie bledem programu */ // atrybut zlozony, opcjonalny, pojedynczy, obiektu protected Date dataUrodzenia; /* * ZLOZONY: * to taki ktory zawiera w sobie wiecej niz jedna informacje * ogolnie chodzi o odniesienie sie do innego obiektu (oprocz 'String') * */ // atrybut prosty, wymagany, pojedynczy, obiektu protected int wzrost = 0; /* * jest tu tylko po to, zeby wyliczac srednia (i miec cos 'pochodnego') */ // atrybut prosty, pojedynczy, wyliczalny (pochodny), klasowy private static double sredniWzrost = 0; /* * KLASOWY: * dotyczy klasy, czyli wszystkich 'Osob', a nie jednej konkretnej * technicznie: zmienna typu static * * POCHODNY: * mozna go wyliczyc, w tym wypadku wystarczylo by pobrac wszystkie Osoby z 'ekstensji' ObjectPlus * pobrac ich wzrost i wyliczyc srednia, ale prosciej to po prostu pamietac * i liczyc tylko raz przy dodawaniu osoby */ // atrybut prosty, pojedynczy, klasowy - potrzebny do liczenia sredniej private static int iloscOsob = 0; // konstruktor nie jest 'pusty', bo imieOsoby jest 'wymagane', wiec musi byc w kazdym konstruktorze Osoba(String imieOsoby, int wzrostOsoby) { /* * BARDZO WAZNE: kazdy konstruktor klasy dziedziczacej po klasie ObjectPlus musi wywolywac 'super()' * Co to jest 'super()'? * Jest to cos co odpali konstruktor w klasie 'wyzej' / 'u rodzica' czyli w ObjectPlus. * A co to robi? * To zapewnia, ze kazdy utworzony obiekt jest na liscie 'ekstensji' w ObjectPlus. * Konstruktor w ObjectPlus zajmuje sie dodawaniem/tworzeniem list ekstensji. */ super(); // tutaj dbamy, aby wymagany atrybut byl ustawiony, a nie wskazywal na 'null' if(imieOsoby == null) { throw new NullPointerException("Osoba musi miec imie"); } this.imie = imieOsoby; // 'int' nie moze byc 'nullem', wiec tego nie sprawdzamy // ale wymagamy podania tego w konstruktorze, wiec kazda osoba bedzie miala wpisany wzrost this.wzrost = wzrostOsoby; // to nam sie przyda do liczenia sredniej iloscOsob++; // a tu troche Drabikologi i mamy srednia sredniWzrost += (this.wzrost - sredniWzrost) / iloscOsob; } /* * PRZESLANIANIE METOD: * w nadklasie Osoba metoda ta zwraca tylko imie, * a w Student imie z dodanym na poczatku tekstem 'Student' * jaki tego sens? trudno mi wymyslic jakas sensowna przeslaniana funkcje * * technicznie: w klasie dziedziczacej jest metoda o takiej samej nazwie, takich samych argumentach, * ale zwraca cos innego */ public String getImie() { return this.imie; } /* * METODA KLASOWA: * metoda operujaca na zmiennych klasowych, a nie obiekcie * technicznie: typu static */ public static double getSredniWzrost() { return sredniWzrost; } /* * demonstracja roznicy w poslugiwaniu sie atrybutami 'wymaganymi' i 'opcjonalnymi' */ public void ustawImie(String imie) { if(imie == null) // uwaga wymagany! { throw new NullPointerException("Osoba musi miec imie"); } this.imie = imie; } public void ustawNazwisko(String nazwisko) { this.nazwisko = nazwisko; } public void ustawDateUrodzenia(Date dataUrodzenia) { this.dataUrodzenia = dataUrodzenia; } public String toString() { String ret = new String(); ret += "Imie: " + getImie(); ret += "\nNazwisko: "; if(nazwisko != null) // uwaga opcjonalny! ret += nazwisko; else ret += "bez nazwiska"; ret += "\nWzrost: " + wzrost; ret += "\nData urodzenia: "; if(dataUrodzenia != null) // uwaga opcjonalny! ret += dataUrodzenia; else ret += "nie znana"; return ret; } }
Student.java
import java.util.Vector; public class Student extends Osoba { /* wymagane do serializacji, w przyszlosci jak cos zmienisz w tej klasie [dodasz/usuniesz atrybut] to zmienisz ta liczbe na inna, dzieki temu mechanizm 'serialize' nie pozwoli zaladowac pliku z dysku z starymi danymi do programu ktory uzywa innej klasy (przeciez moze byc zupelnie inna klasa o nazwie Student i dane takie jak 'oceny' moga w niej nie wystepowac) */ private static final long serialVersionUID = 1L; // atrybut prosty, powtarzalny, obiektu private Vector<Double> oceny = new Vector<Double>(); /* * POWTARZALNY: * obiekt zawiera 0 lub wiecej takich 'elementow' * technicznie: lista/tablica */ Student(String imieOsoby, int wzrostOsoby) { /* * 'super', bo dziedziczy po klasie ktora dziedziczy po ObjectPlus * z parametrem 'imieOsoby' i 'wzrostOsoby', bo takie atrybuty sa wymagana w Osoba */ super(imieOsoby, wzrostOsoby); } /* * PRZESLANIANIE METOD: * w nadklasie Osoba metoda ta zwraca tylko imie, * a w Student imie z dodanym na poczatku tekstem 'Student' * jaki tego sens? trudno mi wymyslic jakas sensowna przeslaniana funkcje * * technicznie: w klasie dziedziczacej jest metoda o takiej samej nazwie, takich samych argumentach, * ale zwraca cos innego */ public String getImie() { return "Student " + this.imie; } /* * PRZECIAZANIE METOD: * metody maja taka sama nazwe, ale przyjmuja rozne parametry * w tym przypadku jedna 'Double', a druga 'int' */ public void dodajOcene(Double ocena) throws Exception { if(ocena == null || ocena < 2 || ocena > 5) { throw new Exception("Ocena musi byc pomiedzy 2, a 5"); } oceny.add(ocena); } public void dodajOcene(int ocena) throws Exception { if(ocena < 2 || ocena > 5) { throw new Exception("Ocena musi byc pomiedzy 2, a 5"); } oceny.add(new Double(ocena)); } public String toString() { // na poczatek opisu studenta wklejamy opis z Osoby (imie, nazwisko..) wywolujac metode z klasy // z ktorej dziedziczymy, robimy to uzywajac 'super' String ret = super.toString(); ret += "\nOceny: "; if(oceny.size() > 0) { for(Double ocena : oceny) { ret += ocena + ","; } } else { ret += "brak ocen"; } return ret; } }
ObjectPlus.java
import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Hashtable; import java.util.Vector; /* * To jest klasa z wykladu nr 4 z drobnymi zmianami, zeby eclipse nie wywalal 'ostrzezen' * -------------------- * klasa zrobina przez wykladowce!, wiec raczej nie powinien sie czepiac * ale trzeba rozumiec KAZDY szczegol jej dzialania! * */ public class ObjectPlus implements Serializable { /* wymagane do serializacji, w przyszlosci jak cos zmienisz w tej klasie [dodasz/usuniesz atrybut] to zmienisz ta liczbe na inna, dzieki temu mechanizm 'serialize' nie pozwoli zaladowac pliku z dysku z starymi danymi do programu ktory uzywa innej klasy (przeciez moze byc zupelnie inna klasa o nazwie Student i dane takie jak 'oceny' moga w niej nie wystepowac) */ private static final long serialVersionUID = 1L; /* (w tym tlumaczeniu bede uzywal slowa 'tablica' zamiast 'lista', roznica? lista [z ktorej korzystamy pod nazwa Hashtable!] sama 'rosnie' jak do niej dodajemy obiekty, a tablice maja staly rozmiar np. 'new Integer[5]' - 5 elementow) Tlumaczenie typow: 1. Class<? extends ObjectPlus> - obiekt typu Class pobrany z obiektu ktory rozszerza ObjectPlus, czyli np. Osoba lub Student, String czy Integer nie rozszerzaja ObjectPlus, wiec nie zostana zaakceptowane 2. Vector<ObjectPlus> - lista/kontener obiektow typu ObjectPlus, do takiej listy mozna wsadzic dowolny obiekt ktory rozszerza ta klase, czyli np. Student czy Osoba Opis: To jest 'tablica tablic', a dokladniej tablica ktora jako klucz nie ma liczby, a.. KLASE (Class)! Class to o dziwo w Javie klasa ktora przechowuje informacje o klasie, mozna pobrac obiekt typu Class z kazdego obiektu w Javie uzywajac: Class klasaObiektu = mojObiekt.getClass(); No wiec mamy juz tablice ktora uzywa jako klucza Class, a jakie dane przechowuje? Przechowuje Vector<ObjectPlus>, czyli tablice obiektow typu ObjectPlus: 1. Osoba rozszerza ObjectPlus [extends], wiec tez moga ja wrzucic do tej tablicy 2. Student rozszerza Osoba ktora rozszerza ObjectPlus, wiec tez moge ja tam wrzucic A po co mi wogole tablica tablic zamiast zwyklej tablicy? Bo ObjectPlus w projekcie docelowym moze byc rozszerzany przez wiele obiektow roznych klas jak np. 'Wplata', 'Ocena', 'Przedmiot' itd., a nie chce mi sie pisac nowego ObjectPlus dla kazdej z nich Nie chce tez przechowywac wszystkich obiektow w jednej tablicy [typu Object, wtedy moge tam wrzucic wszystko], bo wtedy 'pobranie wszystkich 'Student' wymagalo by przegladania wszystkich elementow tablicy i sprawdzania ktorzy to Studenci. */ private static Hashtable<Class<? extends ObjectPlus>, Vector<ObjectPlus>> ekstensje = new Hashtable<Class<? extends ObjectPlus>, Vector<ObjectPlus>>(); /** * Konstruktor. */ public ObjectPlus() { Vector<ObjectPlus> ekstensja = null; Class<? extends ObjectPlus> klasa = this.getClass(); if(ekstensje.containsKey(klasa)) { // Ekstensja tej klasy istnieje w kolekcji ekstensji // jest juz jakis np. Student, wiec jest i lista studentow, nie chce tworzyc nowej listy, // tylko dodac nowego studenta do listy juz istniejacej ekstensja = ekstensje.get(klasa); } else { // Ekstensji tej klasy jeszcze nie ma -> dodaj ją // to wystepuje kiedy pierwszy raz dodajemy obiekt danego typu (np. Student) do ekstensji ekstensja = new Vector<ObjectPlus>(); ekstensje.put(klasa, ekstensja); } // a tu wreszcie dodaje studenta do listy nowej LUB istniejacej ekstensja.add(this); } /** * Zapisuje wszystkie ekstensje do podanego strumienia (wersja z serializacja). * Metoda klasowa. * @throws IOException */ public static void zapiszEkstensje(ObjectOutputStream stream) throws IOException { /* 'magia javy' zamienia obiekt (Hashmape) zawierajacy inne obiekty w tekst, wiec jak zawiera liste Studentow to Ci studenci maja np. atrybut 'Date dataUrodzenia' i to tez sie zapisze (Date jest 'serializable' jak wiekszosc klas uzywanych na codzien w javie) (wszystkie obiekty jakie zawiera ekstensja musza rozszerzac interfejs 'Serializable') java znajdzie wszystkie powiazane obiekty z tymi obiektami ktore mamy w hashmap 'ekstensje' i wszystkie powiazane z ich powiazaniami itd. i wszystko zapisze jako tekst! a potem jeszcze moze z tego tekstu odtworzyc ten obiekt [i obiekty powiazane], magia! UWAGA: to zapisze tylko zmienne 'obiektu', zmienne 'klasowe' (te z 'static') nie zostana zapisane i to jeden z problemow z takim prostym zapisem, ale mozna samemu dopisac zapis 'static' na szczescie to nie jest wymagane w MP_1, a jedynie w projekcie semestralnym */ stream.writeObject(ekstensje); } /** * Odczytuje wszystkie ekstensje z podanego strumienia (wersja z serializacja). * Metoda klasowa. * @throws IOException * @throws ClassNotFoundException */ @SuppressWarnings("unchecked") public static void odczytajEkstensje(ObjectInputStream stream) throws IOException, ClassNotFoundException { /* tutaj 'magia javy' odtwarza dane zapisane jako tekst do pliku (zapisane w funkcji wyzej) */ ekstensje = (Hashtable<Class<? extends ObjectPlus>, Vector<ObjectPlus>>) stream.readObject(); } /** * Wyswietla ekstensje. * Metoda klasowa. * @throws Exception */ public static void pokazEkstensje(Class<? extends ObjectPlus> klasa) throws Exception { /* wystarczy gdzies w programie wywolac ta funkcje tak: ObjectPlus.pokazEkstensje(Student.class); aby wyswietlic wszystkie obiekty typu 'Student' istniejace w programie */ Vector<ObjectPlus> ekstensja = null; if(ekstensje.containsKey(klasa)) { // Ekstensja tej klasy istnieje w kolekcji ekstensji ekstensja = ekstensje.get(klasa); } else { throw new Exception("Unknown class " + klasa); } System.out.println("Ekstensja klasy: " + klasa.getSimpleName()); for(Object obiekt : ekstensja) { System.out.println(obiekt + "\n"); } } }
Mam nadzieję, że będziecie przestrzegać instrukcji.
Wszystko w .zip: