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:
