UWAGA:
Podany niżej materiał nie jest namiastką podręcznika. Nie zastąpi również uczestnictwa w wykładach. Jego celem jest ułatwienie przygotowywania się do kolejnych zajęć i zwolnienie z konieczności przepisywania omawianych przykładowych programów z tablicy - w związku z tym znajdują się tu tylko przykłady i niektóre ( przydatne moim zdaniem) informacje.
Literatura :
Lis M., Praktyczny kurs Java, Helion 2004 (dobry podręcznik do Javy )
Barnes D. J. , Kolling M. , Objects first with Java, Pearson Education Limited, 2006 (skoncentrowany na myśleniu obiektowym)
Wierzbicki M., Java programowanie obiektowe, Helion, 2006 ( zawiera podstawy Javy i zasady programowania obiektowego)
http://www.bluej.org (darmowe środowisko, tutorial, przykłady itp.)
http://wazniak.mimuw.edu.pl/index.php?title=Programowanie_obiektowe ( dobry bryk z Javy) - na tej stronie można znaleźć również materiały do innych przedmiotów
Eckel B., Thinking i Java edycja polska, Helion, 2006, biblia języka Java
i wiele innych....
Proces rozwiązywania zadania, nieformalna specyfikacja ( wejściowo wyjściowa ) problemu, algorytm, formy zapisu algorytmu, język programowania, budowa programu w języku Java.
Algorytm programisty ( fazy procesu rozwiązywania zadania):
analiza problemu
sformułowanie algorytmu
implementacja ( wykonanie) algorytmu
sprawdzenie poprawności wyników
analiza uzyskanego rozwiązania
Formy zapisu algorytmu:
zapis słowny
schemat blokowy
program zapisany w określonym języku programowania.
Schematy blokowe - elementy składowe:
początek algorytmu koniec algorytmu instrukcja wywołanie algorytmu
pomocniczego
wprowadzanie danych lub rozgałęzienie dwukierunkowe
wyprowadzanie wyników uwaga : InstrukcjaNie może być pusta
rozgałęzienie wielokrotne ( można opuścić wariant "inne" - wówczas nic się nie dzieje).
POPRAWNE STRUKTURY ITERACJI (PĘTLI) :
Prosta aplikacja w języku Java :
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
W środowisku BlueJ możemy uruchamiać każdą klasę, więc nie musimy tworzyć klasy uruchomieniowej ( zawierającej metodę main). Nasz pierwszy program może zostać uproszczony.
public class HelloWorld {
public void hello() {
System.out.println("Hello world!");
}
}
Podstawowe elementy języka :, zmienna, stała, operator, wyrażenie, instrukcja. Typy danych języka Java i związane z nimi operatory.
Identyfikator (nazwa).
Nazwa może być zbudowana z liter, cyfr i znaku "_" (jest traktowany jako litera). Można używać polskich liter, ale ze względu na przenośność nie jest to zalecane. Nie ma ograniczeń na długość nazwy. Nazwa musi być różna od słów : null, false, true i wszystkich słó kluczowych.
Słowa kluczowe języka Java :
abstract |
continue |
for |
new |
switch |
assert |
default |
if |
package |
synchronized |
boolean |
do |
goto |
private |
this |
break |
double |
implements |
protected |
throw |
byte |
else |
import |
public |
throws |
case |
enum |
instanceof |
return |
transient |
catch |
extends |
int |
short |
try |
char |
final |
interface |
static |
void |
class |
finally |
long |
strictfp |
volatile |
const |
float |
native |
super |
while |
Typy dzielą się na :
proste ( pierwotne)
logiczny
całkowitoliczbowe ( byte, short, int, long, char )
zmiennopozycyjne (float, double)
obiektowe ( referencyjne, odnośnikowe)
klasowe
interfejsowe
tablicowe
Literały ( wartości bezpośrednie) :
znaki - 'A' '+' '\n'
napisy - "To jest napis"
liczby całowite - 1 123 -17
długie liczby całkowite - 132789 -2000000 1L
liczby rzeczywiste - 1.009 -123.0 2F 1.203e-6 1e3
liczby całkowite w zapisie szesnastkowym - 0x10 0xF
liczby całkowite w zapisie ósemkowym - 011 07
wartości logiczne - false true
pusta wartość referencyjna - null
Znaki specjalne używane w wydrukach ( wpisywane bezpośrednio w tekst lub wysyłane jako oddzielny znak ).
Znak |
Interpretacja |
\a |
alarm (sygnał dźwiękowy , "beep" ) |
\b |
cofnięcie o jeden znak (backspace) |
\f |
przejście na początek następnej strony (formfeed) |
\n |
przejście na początek następnej strony (newline) |
\r |
przejście na początek bieżącej linii ( powrót karetki) |
\t |
przejście do następnej pozycji tabulacji w linii |
\v |
przejście do następnej pozycji tabulacji pionowej |
\\ |
znak \ (backslash) |
\' |
znak ' (apostrof) |
\" |
znak " (cudzysłów) |
\0 |
znak pusty (null) |
\ooo |
znak o kodzie podanym jako liczba ósemkowa ( \241) |
\uoooo |
znak o kodzie podanym jako liczba szesnastkowa ( \ua241) |
Zakresy wartości dla podstawowych typów liczbowych:
Typ danych |
zakres wartości |
byte |
-128 : 127 |
short |
-32768 : 32767 |
int |
-2147483648 : 2147483647 |
long |
-9223372036854775808L : 9223372036854775807L |
float |
1.401298464324817E-45f : 3.4028234663852886E38f |
double |
4.9E-324 : 1.7976931348623157E308 |
Zmienne.
Deklaracje zmiennych prostych :
int m = 5; lub int m;
m=5;
Deklaracje zmiennych referencyjnych :
Czas czas; // nie tworzy obiektu
Czas czas = new Czas(parametry_konstruktora) ; // tworzy obiekt
Deklaracje zmiennych tablicowych :
int [] tablica; lub int tablica[]; // nie tworzy tablicy
int [] tablica = new int[liczba_elementów];
Uwaga : zmienne są widoczne tylko w bloku w którym została zadeklarowana i wszystkich blokach wewnętrznych ( na zewnątrz nie).
Operator realizuje pewną funkcję (działanie) na związanych z nim argumentach.
W języku Java występują operatory jedno-, dwu- i trójargumentowe.
Operator może być zapisany przed swoimi argumentami - notacja prefiksowa (przedrostkowa), za argumetami - notacja postfiksowa (przyrostkowa) lub w przypadku dwu argumentów pomiędzy nimi - notacja infiksowa (wrostkowa).
Jeżeli argument jest w zasięgu działania kilku operatorów o kolejności wykonywania działań decyduje ustalony priorytet tych operatorów (priorytety operatorów w językach programowania zachowują konwencje stosowane w matematyce).
W przypadku, gdy operatory mają taki sam priorytet, działania realizowane są w kolejności od lewej do prawej lub odwrotnie, w zależności od wiązania prawego (prawostronnego) lub lewego (lewostronnego).
Poniższa tabela przedstawia operatory stosowane w języku Java.
Priorytet |
Wiązanie |
Operator |
Argumenty |
Notacja |
Nazwa |
Typ |
1 |
Prawe |
! |
1 |
Prefiksowa |
Negacja |
Logiczny |
1 |
Prawe |
~ |
1 |
Prefiksowa |
Dopełnienie do 1 |
Bitowy |
1 |
Prawe |
+ |
1 |
Prefiksowa |
Jednoargumentowy + |
Arytmetyczny |
1 |
Prawe |
- |
1 |
Prefiksowa |
Jednoargumentowy - |
Arytmetyczny |
1 |
Prawe |
++ |
1 |
Prefiksowa, postfiksowa |
Zwiększenie o 1 |
Arytmetyczny |
1 |
Prawe |
-- |
1 |
Prefiksowa, postfiksowa |
Zmiejszenie o 1 |
Arytmetyczny |
1 |
Prawe |
(typ) |
1 |
Prefiksowa |
Konwersja (rzutowanie) |
Specjalny |
2 |
Lewe |
* |
2 |
Infiksowa |
Mnożenie |
Arytmetyczny |
2 |
Lewe |
/ |
2 |
Infiksowa |
Dzielenie |
Arytmetyczny |
2 |
Lewe |
% |
2 |
Infiksowa |
Reszta |
Arytmetyczny |
3 |
Lewe |
+ |
2 |
Infiksowa |
Dodawanie, konkatenacja |
Arytmetyczny |
3 |
Lewe |
- |
2 |
Infiksowa |
Odejmowanie |
Arytmetyczny |
4 |
Lewe |
<< |
2 |
Infiksowa |
Przesunięcie w lewo |
Bitowy |
4 |
Lewe |
>> |
2 |
Infiksowa |
Przesunięcie w prawo |
Bitowy |
4 |
Lewe |
>>> |
2 |
Infiksowa |
Przesunięcie w prawo bez znaku |
Bitowy |
5 |
Lewe |
< |
2 |
Infiksowa |
Mniejsze |
Relacyjny |
5 |
Lewe |
<= |
2 |
Infiksowa |
Mniejsze równe |
Relacyjny |
5 |
Lewe |
> |
2 |
Infiksowa |
Większe |
Relacyjny |
5 |
Lewe |
>= |
2 |
Infiksowa |
Wieksze równe |
Relacyjny |
5 |
Lewe |
instanceof |
2 |
Infiksowa |
Stwierdzenie typu |
Specjalny |
6 |
Lewe |
== |
2 |
Infiksowa |
Równość |
Relacyjny |
6 |
Lewe |
!= |
2 |
Infiksowa |
Nierówność |
Relacyjny |
7 |
Lewe |
& |
2 |
Infiksowa |
Koniunkcja |
Bitowy |
8 |
Lewe |
^ |
2 |
Infiksowa |
Albo |
Bitowy |
9 |
Lewe |
| |
2 |
Infiksowa |
Alternatywa |
Bitowy |
10 |
Lewe |
&& |
2 |
Infiksowa |
Koniunkcja |
Logiczny |
11 |
Lewe |
|| |
2 |
Infiksowa |
Alternatywa |
Logiczny |
12 |
Prawe |
?: |
3 |
|
Warunek (if-else) |
Specjalny |
13 |
Prawe |
= |
2 |
Infiksowa |
Przypisanie |
Przypisanie |
13 |
Prawe |
*= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
13 |
Prawe |
/= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
13 |
Prawe |
%= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
13 |
Prawe |
+= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
13 |
Prawe |
-= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
13 |
Prawe |
&= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
13 |
Prawe |
^= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
13 |
Prawe |
|= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
13 |
Prawe |
<<= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
13 |
Prawe |
>>= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
13 |
Prawe |
>>>= |
2 |
Infiksowa |
Przypisanie z operacją |
Przypisanie |
|
|
new |
1 |
Prefiksowa |
Konkretyzacja |
Specjalny |
|
|
. |
|
|
Kwalifikator, selektor |
Specjalny |
|
|
[] |
|
|
Indeks |
Specjalny |
|
|
, |
|
|
Separator list (dekl. param.) |
Specjalny |
|
|
() |
|
|
Ogranicznik list parametrów |
Specjalny |
Wyrażenia.
Wyrażenia buduję się ze zmiennych, stałych, operatorów i nawiasów ( '(' i ')' ) służących do zmiany kolejności obliczeń.
Instrukcje jezyka Java.
Instrukcja jest elementarną jednostką wykonywania programu.
W programach w języku Java każda instrukcja zakończona jest średnikiem ";" (średnik jest terminatorem instrukcji - nie separatorem).
1. Instrukcja pusta
Instrukcja pusta jest pustym ciągiem znaków zakończonym średnikiem.
2. Instrukcja deklaracji
Instrukcjami deklaracji są deklaracje przynależności do pakietu, deklaracje importu, deklaracje klas, interfejsów, składowych oraz zmiennych lokalnych.
3. Instrukcja wyrażenia
Instrukcjami wyrażenia mogą być zakończone średnikiem wyrażenia poniższego rodzaju:
wyrażenia przypisania,
prefiksowe oraz postfiksowe wersje operatorów ++ i --,
wywołania metod niezależnie od typu wyniku,
wyrażenia tworzące nowe obiekty za pomocą operatora new.
4. Instrukcja grupująca - blok
Składnia:
{ instrukcja1 instrukcja2 ... instrukcjaN }
Opis:
Instrukcje mogą być zgrupowane w blok instrukcji za pomocą nawiasów klamrowych. Konstrukcje taką stosuje się wszedzie tam, gdzie składnia języka przewiduje jedną instrukcję, a chce się wykonać więcej niż jedną instrukcję.
INSTRUKCJE STERUJĄCE
Instrukcje decyzyjne (warunkowe)
5. Instrukcja if
Składnia:
if (wyrażenie-logiczne)
instrukcja
Opis:
Instrukcja if służy do warunkowego wykonania innej instrukcji. Najpierw obliczane jest wyrażenie-logiczne i jeśli jego wartością jest true, to wykonywana jest instrukcja w przeciwnym razie wykonywana jest następna instrukcja po instrukcji if.
6. Instrukcja if-else
Składnia:
if (wyrażenie-logiczne)
instrukcja1
else
instrukcja2
Opis:
Instrukcja if-else służy do warunkowego wykonania jednej z dwu instrukcji. Najpierw obliczane jest wyrażenie-logiczne i jeśli jego wartością jest true, to wykonywana jest instrukcja1, a jeśli false to wykonywana jest instrukcja2.
Uwaga: klauzula else należy zawsze do ostatniej instrukcji if, która nie miała swojego else.
7. Instrukcja switch
Składnia:
switch (wyrażenie wyboru) {
case wartość1: instrukcja1
case wartość2: instrukcja2
...
case wartośćN: instrukcjaN
default: instrukcjaDomyślna
}
Opis:
Instrukcja switch służy do warunkowego wykonania jednej z zestawu instrukcji w zależności od wartości wyliczonego wyrażenia nazywanego selektorem, który musi być wyrażeniem całkowitym. Obliczona wartość poszukiwana jest kolejno wśród wyrażeń całkowitych stałych podanych po słowie case i jeśli zostanie odnaleziona, to wykonywana jest odpowiednia instrukcja podana po dwukropku. W przypadku nieodnalezienia odpowiedniego przypadku wykonywana jest instrukcja z etykietą default lub następna instrukcja po instrukcji switch, gdy opcjonalna klauzula default nie wystąpiła.
Uwaga 1: wykonanie instrukcji po etykiecie case lub default nie kończy wykonania instrukcji switch ani nie powoduje pominięcia instrukcji, które po niej następują, a gdy taka konieczność zachodzi należy zastosować instrukcję break (patrz niżej).
Uwaga 2: etykiety case mogą być grupowane tj. może ich wystąpić kilka jedna po drugiej.
switch (wyr) {
case w1: case w2:
case w3: case w4:
ins1
case w5:
ins2
}
Uwaga 3: klauzula default może wystąpić tylko raz.
Instrukcje iteracyjne (pętle)
8. Instrukcja while (podstawowa wersja iteracji)
Składnia:
while (wyrażenie-logiczne)
instrukcja
Opis:
Pętla while powoduje cykliczne wykonywanie instrukcji dopóki spełniony jest pewien warunek. Najpierw obliczane jest wyrażenie-logiczne i jeśli jego wartością jest true, to wykonywanie instrukcji powtarzane jest tak długo, aż kolejne obliczenie wartości wyrażenia-logicznego da wartość false.
Uwaga: jeśli wyrażenie-logiczne przyjmie wartość false już przy pierwszym obliczeniu, to instrukcja pętli while nie wykona się ani razu.
9. Instrukcja do-while
Składnia:
do
instrukcja
while (wyrażenie-logiczne);
Opis:
Pętla do-while podobnie jak pętla while powoduje cykliczne wykonywanie instrukcji dopóki spełniony jest pewien warunek. Najpierw wykonywana jest instrukcja, a następnie obliczne jest wyrażenie-logiczne. Jeśli ma ono wartość true, cały proces powtarzany jest ponownie. Wyjście z pętli następuje, gdy wyrażenie-logiczne przyjmie wartość false.
Uwaga: instrukcja w pętli do-while wykonana jest przynajmniej raz.
10. Instrukcja for
Składnia:
for (wyrażenie-inicjujące; wyrażenie-logiczne; modyfikator)
instrukcja
Opis:
Instrukcja for służy (na ogół) do cyklicznego wykonania pewnej instrukcji określoną ilość razy. Efekt ten uzykuje się poprzez zastosowanie zmiennej sterującej i odpowiednią konstrukcję wyrażeń z jej użyciem w nagłówku pętli. Najpierw obliczane jest wyrażenie-inicjujące. Następnie w pętli wykonywane są kolejno czynności: obliczane jest wyrażenie-logiczne i jeśli ma wartość true, to wykonywana jest instrukcja, po czym wykonywana jest instrukcja modyfikatora.
Uwaga 1: wszystkie wyrażenia w nagłówku pętli tj. w obrębie nawiasów okrągłych po słowie for, są opcjonalne ( średniki muszą pozostać) .
Uwaga 2: wyrażenia w nagłówku pętli mogą mieć postać list wyrażeń rozdzielonych przecinkiem.
Instrukcje przerwania (break i continue to instrukcje niestrukturalne, nie należy ich nadużywać)
11. Instrukcja break
Składnia:
break ;
Opis:
Instrukcja break pozwala na przerwanie każdego bloku chociaż najczęściej stosowana jest w połączeniu z instrukcją switch lub z instrukcjami iteracyjnymi ( tylko w bardzo uzasadnionych przypadkach). Jej użycie powoduje zakończenie wykonywania najwęższej obejmującej ją instrukcji iteracyjnej (while, do-while, for) lub decyzyjnej (switch).
12. Instrukcja continue
Składnia:
continue;
Opis:
Zastosowanie instrukcji continue powoduje zaniechanie w bieżącym cyklu wykonywania instrukcji objętych iteracją i kontynuowanie wykonywania najwęższej obejmujacej ją instrukcji iteracyjnej.
13. Instrukcja return
Składnia:
return [wyrażenie];
Opis:
Instrukcja return kończy wykonanie metody i powoduje powrót do miejsca jej wywołania. Jeśli metoda nie deklaruje wartości powrotnej, to po słowie return nie podaje się żadnego wyrażenia. W przeciwnym przypadku w instrukcji powinno wystąpić wyrażenie o typie zgodnym pod względem przypisania z typem wyniku deklarownym przez metodę.
Instrukcje obsługi wyjątków
14. Instrukcja throw
Składnia:
throw obiektWyjątku;
Opis:
Instrukcja throw wyrzuca (zgłasza, wysyła, generuje) wyjątek. Zostanie omówiona w dalszej części wykładu.
15. Instrukcja try-catch-finally
Składnia:
try {
instrukcje
}
catch (typWyjątku identyfikator) {
instrukcje
}
catch (typWyjątku identyfikator) {
instrukcje
}
...
finally {
instrukcje
}
Opis:
Instrukcja try-catch-finally służy do wychwytywania i obsługi wyjątków. Zostanie omówiona w dalszej części wykładu.
Definicja klasy, tworzenie obiektów. Porównanie i kopiowanie obiektów.
// Przykład ilustruje różne możliwości występujące przy definiowaniu klasy
public class Czas
{
// definicje pól
private int godz;
private int min;
private int sek;
//konstruktory ; modyfikator dostępu konstruktora powinien być taki sam jak całej klasy
// konstruktor podstawowy
public Czas()
{ godz=min=sek = 0; }
// przeciążenie konstruktora
public Czas(int godz)
{
// przesłanianie pola godz
this.godz=godz;
min=sek = 0;
}
//użycie konstruktora w konstruktorze
public Czas(int godz, int min)
{ this(godz,min,0); }
//wykorzystanie innej metody ( nie konstruktora)
public Czas(int godz, int min, int sek)
{ ustawGodzMinSek(godz,min,sek); }
//konstruktor przydatny do kopiowania
public Czas( Czas czas)
{ godz=czas.godz; min=czas.min; sek=czas.sek;}
//metody odczytujące stan obiektu (getters)
public int dajGodz()
{ return godz;}
public int dajMin()
{ return min; }
public int dajSek()
{ return sek; }
// metody ustawiające pola obiektu (setters)
public void ustawGodz(int godz)
{ this.godz=godz;
min=sek = 0;
}
public void ustawGodzMin(int godz, int min)
{ this.godz=godz;
this.min=min;
sek = 0;
}
public void ustawGodzMinSek(int godz, int min, int sek)
{ this.godz=godz;
this.min=min;
this.sek = sek;
}
// inne metody
//przesunięcie czasu o 1 sekundę
public void plusSek()
{
if(sek<59) sek++;
else {sek=0;
if(min<59) min++;
else { min=0;
if(godz<23) godz++;
else godz=0;
// lub godz=++godz%24;
}
}
}
// jaki będzie czas za 1 sekundę
public Czas zaSek()
{ Czas wynik= new Czas();
if(sek<59) {wynik.sek=sek+1;wynik.min=min;wynik.godz=godz;}
else {wynik.sek=0;
if(min<59) {wynik.min=min+1;wynik.godz=godz;}
else { wynik.min=0;
wynik.godz=(godz+1)%24;
}
}
return wynik;
}
//porównanie wartości dwóch czasów ; niestety zwykły operator == nie działa
public boolean equals(Czas czas)
{ return this.godz==czas.godz && this.min == czas.min && this.sek==czas.sek; }
// do pól prywatnych argumentu mamy dostęp ponieważ jesteśmy wewnątrz klasy Czas
public boolean less(Czas czas)
{ if(godz != czas.dajGodz())
return godz<czas.dajGodz();
else if(min!=czas.min) return min<czas.min;
else return sek<czas.sek;
}
// można prościej
public boolean less1(Czas czas)
{
return (godz<czas.godz)||(godz==czas.godz)&&(min<czas.min) || (godz==czas.godz)&&(min==czas.min)&&(sek<czas.sek);
}
// metoda pozwalająca bezpośrednio drukować obiekt
// np. System.out.println( "Jest "+czas);
public String toString()
{ return godz+" : "+min+" : "+sek; }
// tworzy kopię obiektu; operator = nie działa
public Czas copy()
{ return new Czas(this);}
}
Tablice w Javie.
tablice jednowymiarowe
z elementami typu prostego
definicja zmiennej tablicowej :
int[] tab; // nie tworzy tablicy, a tylko zmienną referencyjną (tablica jest traktowana jako obiekt, tylko nie ma określonej nazwy klasy).
tworzenie tablicy o liczbie elementów "n" :
tab = new int[n];
lub int[] tab = new int[n];
lub int [] tab= {1,2,3,4,5};
odwołanie do elementu tablicy :
tab[i] // wyrażenie indeksowe musi mieć wartość od 0 do liczba elementów-1
Uwaga : jeśli tablica nie jest wykorzystana w pełni musimy pamiętać liczbę zapisanych elementów, w przypadku pełnej tablicy możemy wykorzystać atrybut length ( tab.length).
Przetwarzanie tablic :
- typowa pętla przechodząca przez wszystkie elementy tablicy na przykładzie obliczania sumy wartości elementów tablicy :
int sumaElementów()
{ int suma=0;
for(int i=0; i<n; i++)
suma+=tab[i];
return suma; }
- wyszukiwanie elementu w tablicy ( przykład pętli która może się kończyć wcześniej )
boolean jest( int szukany)
{ int i=0;
while ( i<n && a[i]!=szzukany) // ważna kolejność warunków
i++;
return i<n; // nie a[i]==szukany
}
Uwaga: ta porządna strukturalna wersja wcale nie jest bardziej złożona od wersji niestrukturalnej ( czytaj nieporządnej ) - niestety często spotykanej w książkach :
boolean jest( int szukany)
{ for(int i=0; i<n ;i++)
if(a[i]== szukany ) return true;
return false;
}
- sprawdzanie różnowartościowości tablicy ( przykład na to, że nie zawsze wystarczy pojedyncza pętla do przetworzenia tablicy jednowymiarowej) :
boolean roznowartosciowa( )
{ boolean jest = true; // jest różnowartościowa
for( int i =0 && jest ; i< n; i++)
for( int j=i+1; j < n && jest ; j++)
jest=a[i] != a[j];
return jest;
}
z elementami klasowymi
załóżmy, że mamy klasę : class Osoba { String nazwisko; ...........};
definicja zmiennej tablicowej :
Osoba [ ] tab;
definicja tablicy n- elementowej :
Osoba [ ] tab = new Osoba[n] ; // tworzy tylko tablicę pustych ( null) referencji
tworzenie n elementowej tablicy wypełnionej pustymi obiektami :
Osoba [ ] tab = new Osoba[n] ;
for(int i =0; i < n ; i++)
tab[i]=new Osoba();
przetwarzanie tablicy :
a. jeśli elementy są dopisywane kolejno ( bez żadnych dziur ) - np. osoby zapisane na wycieczkę :
int ileOsob( )
{ int i=0;
while( tab[i++]!= null ) ; // typowy skrócony zapis to : while(tab[i++]);
return i;
}
b. jeśli elementy są dopisywane na wybrane pozycje ( mogą być przerwy ) - np. kino z numerowanymi miejscami, ale bez wyróżnionych rzędów :
int ileOsob( )
{ ile=0;
for(int i=0; i< tab.length; i++)
if(tab[i]!=null) ile++;
return ile;
}
B. tablice dwuwymiarowe
regularne ( macierze)
definicja zmiennej tablicowej :
int [ ] [ ] mac;
definicja macierzy o m wierszach i n kolumnach :
int [ ] [ ] mac= new [ m] [n] ;
odwołania :
mac - cała macierz
mac[i] - i-ty wiersz macierzy
mac[i][j] - j-ty element i-tego wiersza , czyli element ai,j
przykład przetwarzania ( typowa podwójna pętla ) :
int suma Elementow( )
{ int suma=0;
for( int i=0; i<m; i++ )
for( int j=0; j<n; j++ )
suma+=mac[i][j];
return suma;
}
Żeby się przekonąć, że nie zawsze wystarczy podwójna pętla wystarczy spróbować zapisać metodę sprawdzającą różnowartościowość macierzy.
nieregularne ( o wierszach różnej długości)
tworzenie tablicy na przykładzie szkoły w której każda klasa ma inny limit uczniów (zapisany w tablicy klasa) :
int[ ] klasa ={ .......};
Osoba [ ][ ] szkola = new Osoba[klasa.length][ ] ;
for( int i=0; i< klasa.length; i++ )
szkola[i]=new Osoba[klasa[i]];
Hierarchia klas. Dziedziczenie. Polimorfizm.
public class Ksiazka
{ String tytul;
String autor;
int rok; // rok wydania
String wydawnictwo;
Public Ksiazka( String tytul, String autor, int rok, String wydawnictwo)
{this.tytul= new String(tytul); // żeby utworzyć kopię napisu
.......}
String dajTytul( ) {...}
......
String toString( )
{return "Tytul : "+tytul+" autor : "+autor+" rok wydania : "+rok +" wydawnictwo : "+wydawnictwo; }
......
}
public class Artykul
{ String tytul;
String autor;
int rok; // rok wydania
String czasopismo;
Public Artykul( String tytul, String autor, int rok, String czasopismo)
{this.tytul= new String(tytul); .......}
String dajTytul( ) {...}
......
String toString( )
{return "Tytul : "+tytul+" autor : "+autor+" rok wydania : "+rok +" w czasopismie : : "+czasopismo ; }
......
}
Widać wyraźnie powtórzenia zarówno pól jak i metod. Lepsze rozwiązanie wykorzystujące dziedziczenie.
public class Publikacja
{ String tytul;
String autor;
int rok; // rok wydania
Public Publikacja( String tytul, String autor, int rok)
{this.tytul= new String(tytul); .......}
String dajTytul( ) {...}
......
String toString( )
{return "Tytul : "+tytul+" autor : "+autor+" rok wydania : "+rok ; }
......
}
public class Ksiazka extends Publikacja
{ String wydawnictwo;
Public Ksiazka( String tytul, String autor, int rok, String wydawnictwo)
{ super(tytul,autor,rok); // wywołanie konstruktora nadklasy
this.wydawnictwo= new String(wydawnictwo);}
......
String toString( )
{return (super.toString())+ // dostęp do przesłoniętej metody z nadklasy
" wydawnictwo : "+wydawnictwo; }
......
}
public class Artykul extends Publikacja
{ String czasopismo;
Public Artykul( String tytul, String autor, int rok, String czasopismo)
{super(tytul,autor,rok);
this.czasopismo= new String(czasopismo);}
......
String toString( )
{return (super.toString())+" w czasopismie : : "+czasopismo ; }
......
}
Klasa potomna dziedziczy ( a więc ma do nich dostęp bezpośredni) wszystkie atrybuty z wyjątkiem prywatnych. Nadklasa nic nie wie o swoich podklasach.Każda klasa może ieć najwyżej jedną nadklasę ( nie ma wielodziedziczenia).
Główne korzyści z dziedziczenia ( możliwości tworzenia hierarchii klas) :
pokazanie powiązań międzyklasowych
unikanie powtórzeń
możliwość rozbudowy aplikacji bez modyfikacji klas już działających.
public class Jeden
{ protected int xJeden=1;
public Jeden(int a)
{ xJeden = a;}
public String toString()
{ return " "+ xJeden+" ";}
public void metodaJeden()
{ System.out.println("metodaJeden " + this);}
}
public class Dwa extends Jeden
{ protected int xDwa=2;
public Dwa(int a, int b)
{ super(a); // wywołanie konstruktora klasy nadrzędnej
xDwa = b;}
public String toString( ) // przesłonięcie metody z nadklasy
{ return " "+xDwa+" ";}
public void metodaDwa()
{ System.out.println("metodaDwa " + this);}
public void test()
{ Jeden j=new Jeden(111);
j.metodaJeden(); // dostęp do metody z nadklasy
j.xJeden=111111; // bezpośredni dostęp do pola nadklasy - niezalecany
j.metoda();
Jeden j1=new Dwa(111,222); //użycie obiektu podklasy w miejsce obiektu nadklasy
// rzutowanie w górę - bezpieczne
((Dwa)j1).xDwa=22222222; // przykłady rzutowania w dół - ryzykowne
((Dwa)j1).metodaDwa();
// sprawdzenie rzeczywistego typu obiektu :
// if ( j1 instanceof Dwa ) .......
}
}
public class Trzy extends Dwa
{ protected int xTrzy=3;
public Trzy(int a, int b,int c) //początek łańcucha konstruktorów
{ super(a,b);
xTrzy = c;}
public String toString()
{ return " "+ xTrzy +" ";}
public void metodaTrzy()
{ System.out.println("metodaTrzy " + this);}
public void test()
{ ... }
}
Klasy mogą być pisane przez różnych autorów, więc może się zdarzyć następująca sytuacja :
public class Jeden
{ protected int x=1;
public Jeden(int a)
{ x = a;}
public String toString()
{ return " "+ x+" ";}
public void metoda()
{ System.out.println("metodaJeden " + this);}
}
public class Dwa extends Jeden
{ protected int x=2; //przesłonięcie pola
public Dwa(int a, int b)
{ super(a); // wywołanie konstruktora klasy nadrzędnej
x = b;}
public String toString( ) // przesłonięcie metody z nadklasy
{ return " "+x+" ";}
public void metoda() //j.w.
{ System.out.println("metodaDwa" + this);}
public void test()
{ }
}
public class Trzy extends Dwa
{ protected int x=3;
public Trzy(int a, int b,int c) //początek łańcucha konstruktorów
{ super(a,b);
x = c;}
public String toString()
{ return " "+ x +" ";}
public void metodaTrzy()
{ System.out.println("metoda " + this);}
public void test() // niezalecany dostęp do pól nadklas
{ System.out.println(a //pole klasy Trzy
+" "+super.a //pole klasy Dwa
+" "+((Jeden)this).a);} } //pole klasy Jeden
}
public class Test
{ public Test() { ...}
public void test()
{ Trzy b = new Trzy(11,22,33);
Jeden c=b;
System.out.println(b+" "+b.x); //pole klasy Trzy
System.out.println(b+" "+c.x); //pole klasy Jeden ( wczesne wiązanie)
b.metoda(); // metoda klasy Trzy
c.metoda();// metoda klasy Trzy ( późne wiązanie - polimorfizm )
}
}
Atrybuty statyczne.
Są to pola i metody związane z klasą, a nie z konkretnym obiektem ( static) .
class A
{ static int x;
...
static int f() {...}
void metoda( )
{ x=5; //odwołania wewnątrz definicji klasy
f();
..
}
}
A ob= new A( );
int i=ob.x; //tradycyjne odwołanie
ob.f( );
int j=A.x; // odwołania przez nazwę klasy, bez tworzenia obiektu
A.f( );
Modyfikator final.
- dla klasy public final A {...} // nie wolno tworzyć podklas
- dla pola ( po pierwszym nadaniu wartości nie wolno zmieniać watości pola)
final int x=1; // dla zwykłych pól są dwie możliwości
final int y;
...
y=2;
static final MAX_WART=20; // jest to definicja stałej, wolno tylko tak
- dla parametru metody
void f( final int n) //nie wolno zmieniać wartości parametru
- dla metody
final void f( ) // metoda nie może być przedefiniowana w podklasach
Uwaga: w przypadku zmiennych referencyjnych zakaz dotyczy zmiany referencji, ale pozwala na zmiany wartości pól wskazywanego obiektu.
Klasy i metody abstrakcyjne, interfejsy. Typy uogólnione.
Klasa abstrakcyjna - klasa która jest zadeklarowana jako abstract .....
Nie wolno tworzyć biektów takiej klasy, ale wolno po niej dziedziczyć.
Klasa abstrakcyjna może ( ale nie musi) zawierać metodyabstrakcyjne.
Metoda abstrakcyjna - metoda bez implementacji ( sam nagłówek). Stosowana gdy chcemy wymusić aby wszystkie podklasy zawierały definicję metody, a nie możemy jej zrealizować w nadklasie. Np.
public abstract class Publikacja // klasa abstrakcyjna zawiera metodę dajPunkty, a nie da się
// policzyć punktów za publikację jako taką
{ String tytul;
String autor;
int rok; // rok wydania
....
abstract int dajPunkty(); //metoda abstrakcyjna
......
}
public class Ksiazka extends Publikacja
{ String wydawnictwo;
static private int punkty=10;
......
int dajPunkty( )
{return punkty;} //implementacja metody abstrakcyjnej
......
}
public class Artykul extends Publikacja
{ String czasopismo;
static private int punkty =5;
......
int dajPunkty( )
{return punkty;} //implementacja metody abstrakcyjnej
......
}
Klasę czysto abstrakcyjną (zawiera tylko metody abstrakcyjne i ewentualnie pola stałe) nazywamy interfejsem. W przeciwieństwie do klas możliwe jest wielodziedziczenie.
public interface Interface extends Interface1,interface2,...,InterfaceN
{ // definicje stałych ( modyfikatory domyślne : public, static, final - można opuścić
// sygnatury metod ( domyślny modyfikator public można opuścić)
}
Można tworzyć zmienne typu interfejsowego, wartością takiej zmiennej może być referencja do obiektu dowolnej klasy implementującej dany interfejs.
public interface Interface
{...}
public class Klasa implements Interface
{ ....}
Klasa obiekt = new Klasa();
Inteface x=obiekt;
Jest to swego rodzaju umowa określająca minimalny zestaw metod który musi realizować każda klasa implementująca interfejs. Każda klasa może implementować dowolną liczbę interfejsów.
class A implements Interfejs1,Interfejs1,.....,IntefejsN
Jeśli klasa nie implementuje wszystkich metod interfejsu to musi być klasą abstrakcyjną.
Typy uogólnione ( typy abstrakcyjne, klasy sparametryzowane, klasy generyczne, szablony klas, wzorce klas itp.).
Chcemy zdefiniować klasę Para umożliwiającą przechowywanie dwóch obiektów dowolnych typów. Można wykorzystać fakt, że wszystkie klasy dziedziczą po klasie Obiekt.
class Para
{ Obiekt pierwszy;
Obiekt Drugi;
Para(Obiekt Pierwszy, Obiekt drugi)
{ this.pierwszy= pierwszy;
this drugi= drugi; }
Obiekt dajPierwszy( )
{ return pierwszy;}
.....
}
Możemy utworzyć parę zawierającą np. dane osoby i napis zawierający komentarz.
Osoba os1=new Osoba("Nowak");
Para para=new Para(os1, "Opis"); // tu jest OK - rzutowanie w górę
Osoba os2= para.dajPierwszy( ); // błąd kompilacji, trzeba użyć rzutowania w dół
Osoba os2 = (Osoba) para.dajPierwszy( ); // kompilator traci możliwość kontroli typów
Możemy jednak zastosować rozwiązanie bezpieczne tworząc klasę sparametryzowaną :
class Para< T1, T2>
{ T1 pierwszy;
T2 Drugi;
Para(T1 Pierwszy, T2 drugi)
{ this.pierwszy= pierwszy;
this drugi= drugi; }
T1 dajPierwszy( )
{ return pierwszy;}
.....
}
Użycie takiej pary :
Osoba os1=new Osoba("Nowak");
Para<Osoba,String> para=new Para<Osoba,String>(os1, "Opis");
Osoba os2= para.dajPierwszy( );
Typy będące parametrami typów uogólnionych nie mogą być typami prostymi. Nie jest to problem ponieważ każdy typ prosty ma zdefiniowany odpowiadający mu typ obiektowy.
int - Integer; long - Long; float - Float; double - Double; char - Char; boolean - Boolean.
Kompilator przyjmuje, że typy będące parametrami wzorca mogą być dowolną podklasą klasy Obiekt. Dla poprawy funkcjonalności należałoby przeciążyć podstawowe ( clone, toString, equals) metody klasy Obiekt.
Dzięki mechanizmowi automatycznego opakowania i rozpakowania można ich używać zamiennie .
int a=5;
Integer b =a;
Integer b=10; // zamiast new Integer(10);
a=b;
Możliwości dziedziczenia :
class ParaInt extends Para<Integer,Integer>
{ public ParaInt( Integer pierwszy, Integer drugi)
{super(pierwszy,drugi); }
}
class ParaJednorodna<T> extends Para<T,T>
{ public ParaJednorodna(T pierwszy, T drugi)
{super(pierwszy, drugi);}
}
Parametry typu uogólnionego mogą zostać zastąpione dowolną klasą, możemy jednak ograniczyć możliwości zastępowania.
class Para1< T implements Cloneable> extends ParaJednorodna<T>
{...} // parametr t musi być klasą implementującą interface Cloneable
class Para2< T implements Cloneable&Serializable> extends ParaJednorodna<T>
{...} // parametr T musi być klasą implementującą interfejsy Cloneable i Serializable
class Para3<T1, T2 extends T1> {..} // T2 musi być podklasą T1
Użycie dżokerów :
Para<?,?> a = .....
Para<?,A> b=
Para<A, ? extends A> c =
Para <A, ? super A> d =
Para< A, ? extends Cloneable> e =
Można również parametryzować metody ( również w zwykłych klasach ), należy przy tym stosować zasady obowiązujące przy przeciążaniu metod.
<T> T metoda(T parametr) {..}
Kolekcje w Javie.
Kolekcja - obiekt umożliwiający przechowywanie i przetwarzanie wielu obiektów ( elementów kolekcji). W przeciwieństwie do tablic jej rozmiar może się zmieniać i jej elementami mogą być tylko obiekty ( referencje).
Pełny opis hierarchii klas kolekcyjnych można znaleźć w helpie ( Java Collections Framework - JFC). Opis ten jest zbudowany na trzech poziomach abstrakcji :
interfejsy ( wymagany zestaw metod)
klasy abstrakcyjne ( częściowa implementacja metod wspólnych)
klasy konkretne ( implementacja)
Ja przedstawiam tylko minimalny zestaw informacji umożliwiający wykorzystanie podstawowych rodzajów kolekcji.
Definicje klas kolekcyjnych znajdują się w pakiecie java.util ( trzeba dodać import java.util.*;).
Java definiuje dwa drzewa interfejsów kolekcyjnych :
Iterable<E>
Collection<E>
List<E>
Set<E>
SortedSet<E>
i Map<K,V>
SortedMap<K,V>
Iterable - zawiera tylko konstruktor klasy Iterator<E>
Collection<E> - metody wspólne dla wszystkich kolekcji np.
Boolean isEmpty();
int size();
boolean add(E e); // true jeśli wstawiono element
boolean addAll( Collection <? extends E> c) // true jeśli dodano choć jeden element
boolean contains(Object o) // true gdy istnieje taki e , że o==null? e==null: o.equals(e)
boolean containsAll(Collection <?> c )
boolean remove(Object o) //usuwa tylko jedno wystąpienie
boolean removeAll(Collection<?> c) //usuwa wszystkie wystąpienia każdego obiektu
void clear()
boolean retainAll(Collection<?> c) //część wspólna kolekcji
E[] toArray() // zapis elementów kolekcji w tablicy
Przetwarzanie wszystkich elementów kolekcji :
pętla foreach
for( E el : c) //c wyrażenie typu kolekcyjnego
instrukcja;
użycie iteratora // można stosować również do pełnych tablic
public interface Iterator<E>
{ boolean hasNext(); // czy jest następny element
E next(); // daj następny element
void remove( ); //usuń element zwrócony ostatni przez iterator
}
Realizacja foreach za pomocą iteratora :
for(Iterator<E> it = c.iterator(); it.hasNext();)
{ E el=it.next();
instrukcja;
} // można użyć while
List<E> - sekwencja, ciąg; wartości elementów mogą się powtarzać , elementy są uporządkowane ( ale nie muszą być posortowane). Jest to odpowiednik dynamicznej tablicy.
E get(int index)
List<E> subList(int poc, int kon) //od poc do kon-1
int indexOf(Object O)
int lastIndexOf(Object O)
void add(int indeks, E elem) // bez indeksu - na koniec
E set(int indeks,E elem) // zamień, wynik - stary element
E remove(int indeks)
ListIterator<E> listIterator( )
Set<E> - odpowiednik zbioru matematycznego; elementy się nie powtarzają i nie są uporządkowane
nie udostępnia nowych metod
SortedSet<E> - elementy nie powtarzają się i są posortowane; kolejność naturalna - wyznaczona przez compareTo lub odpowiedni komparator - elemety muszą realizować interfejs Comparable lub być akceptowane przez odpowiedni Comparator;
E first() // zwraca pierwszy ( najmniejszy element)
SortedSet<E> headSet( E to Element ) // elementy mniejsze od wskazanego
E last() // ostatni
SortedSet<E> subSet(E fromElement, E toElement)
SortedSet<E> tailSet(E fromElement) // większe lub równe
Map<K,V> - odwzorowanie,słownik, tablica asocjacyjna, mapa. Zbiór par klucz ( typu K ) i warość (typu V). Może być traktowane jako tablica indeksowana kluczami.
Oczywiście klucze nie mogą się powtarzać.
boolean containsKey(Object key)
boolean containsValue(Object value)
V get(Object key)
Set<Map.Entry<K,V>> entrySet( )//zbiór par
Set<K> keySet()
Collection<V> values()
V put( K key, V value) // wstaw maplet, wynik - stara wartość lub null
void putAll(Map<? extends K, ? extends V> m) // złożenie odwzorowań
SortedMap<K,V> - odwzorowaniowy odpowiednik zbioru uporządkowanego według wartości kluczy;
K firstKey()
SortedMap<K,V> headMap(K toKey)
K lastKey()
SortedMap<K,V> subMap(K fromKey, K toKey)
SortedMap<K,V> tailMap(K fromKey)
Klasy abstrakcyjne ( zawierają implementacje niektórych ogólnych metod )
Szczegóły są zawarte w dokumentacji.
Klasy konkretne :
ArrayList<E> - podstawowa realizacja listy - odpowiednik tablicy dynamicznej ( o zmiennym rozmiarze)
void trimToSize() //dopasowuje rozmiar tablicy do rozmiaru listy
LinkedList<E> - realizacja listy w postaci dwukierunkowej prostej listy dowiązaniowej; dodano wersje metod : get, set i remove działające na początku (First) i końcu (Last) listy.
HashSet<E> - szybka realizacja zbioru w postaci tablicy haszowanej, elementu są nieuporządkowane, null może być elementem zbioru
Object clone( ) //tworzy kopię zbioru, elementy nie są kopiowane
TreeSet<E> - realizacja zbioru w postaci tablicy drzewa czerwono-czarnego, elementu są posortowane
Object clone( ) //tworzy kopię zbioru, elementy nie są kopiowane
HashMap<K,V> - podstawowa realizacja odwzorowania w postaci tablcy haszowanej; nie ma dodatkowych metod
TreeMap<K,V> - realizacja w postaci drzewa czerwono-czarnego uporządkowanego według wartości kluczy; nie ma dodatkowych metod.
Przykład przetwarzania kolekcji i przeciążeniach odpowiednich metod dla elementów kolekcji :
import java.util.*;
import java.io.*;
public class ComparatorPublikacji implements Comparator<Publikacja>,Serializable
{ public int compare(Publikacja p1,Publikacja p2)
{ return p1.autor.compareTo(p2.autor)!=0 ? p1.autor.compareTo(p2.autor)
: p1.tytul.compareTo(p2.tytul);}
}
import java.io.*;
public abstract class Publikacja implements Comparable, Cloneable, Serializable
{ String tytul;
String autor;
int rok;
Publikacja(String tytul, String autor, int rok)
{this.tytul=tytul; this.autor=autor;this.rok=rok;
}
public Publikacja clone()
{ try{ Publikacja p=(Publikacja)super.clone();
p.tytul= new String(tytul);
p.autor=new String(autor);
return p;
} catch(CloneNotSupportedException e){return null;}
}
public String toString()
{ return tytul+" "+ autor+" " +rok +" "; }
public boolean equals(Publikacja p)
{ return p!=null && tytul.equals(p.tytul) && autor.equals(p.autor) ; }
public int hashCode()
{ return tytul.hashCode() & autor.hashCode(); }
public int compareTo(Object p1)
{ Publikacja p=(Publikacja)p1;
return (tytul.compareTo(p.tytul)!=0 ? tytul.compareTo(p.tytul)
: autor.compareTo(p.autor));
}
}
public class Ksiazka extends Publikacja
{ String wydawnictwo;
public Ksiazka(String tytul, String autor,int rok, String wydawnictwo)
{ super(tytul,autor,rok);
this.wydawnictwo=wydawnictwo;
}
public String toString()
{ return super.toString() + wydawnictwo;}
}
public class Artykol extends Publikacja
{ String czasopismo;
public Artykol(String tytul, String autor,int rok, String czasopismo)
{super(tytul,autor,rok);
this.czasopismo=czasopismo;
}
public String toString()
{ return super.toString() + czasopismo;}
}
import java.util.*;
public class test
{ Publikacja[] pub ={new Ksiazka("Podstawy","Jan",1980,"Iskry"),
new Artykol("Java","Tom",2008,"Algorytmy")};
SortedSet<Publikacja> s = new TreeSet<Publikacja>(Arrays.asList( pub));
public void druk(Collection<Publikacja> s)
{ for(Publikacja el:s)
System.out.println(el+" , ");
System.out.println();
}
public void druk1(Iterator<Publikacja> it)
{ while(it.hasNext())
System.out.println(it.next()+" , ");
System.out.println();
}
public void test()
{ druk(s);
druk1(s.iterator());
System.out.println(s);
Ksiazka k1=new Ksiazka("Podstawy","Jan",1980,"Iskry");
Ksiazka k2=new Ksiazka("Podstawy","Jan",1999,"Iskry2");
Ksiazka k3=new Ksiazka("Podstawy","Janusz",1980,"Iskry");
System.out.println( s.add(k1));
System.out.println( s.contains(k1));
System.out.println( s.add(k2));
System.out.println( s.contains(k2));
System.out.println( s.contains(k3));
System.out.println( s.add(k3));
druk(s);
System.out.println(k1);
Comparator<Publikacja> comp=new ComparatorPublikacji();
SortedSet<Publikacja> s1 = new TreeSet<Publikacja>(comp);
s1.addAll(Arrays.asList(pub));
druk(s1);
}
}
Wejście/wyjście w Javie.
Problemy :
rozmaitość obsługiwanych żródeł danych i celów ( konsola klawiatura, inne urządzenia, pliki, pamięć, łącza itp.)
konieczność zachowania przenośności na różne platformy
System wejścia/wyjścia jest oparty na pojęciu strumienia (znakowego lub binarnego ) .
Realizowany przez hierarchię klas - na pierwszy rzut oka bardzo rozbudowaną, ale i tak w miarę prosty w wyniku wykorzystania jednego z rodzajów wzorców projektowych - dekoratorów.
Na szczęście większość z nich jest przeznaczona do bardziej złożonych działań - wykorzystanie systemu do wykonywania podstawowych zadań ( drukowanie wyników , wczytywanie danych, współpraca z plikami ) jest stosunkowo proste.
Podstawowe klasy hierarchii to : InputStraem ( wejście binarne), OutputStream ( wyjście binarne), Reader(wejście znakowe) i Writer (wyjście znakowe). Podstwowe metody to : read - dla wejścia i write - dla wyjścia.
Dla ułatwienia podzieliłem wykład na dwie części :
strumienie standardowe ( tekstowy wydruk wyników na ekranie i wczytywanie danych z klawiatury)
wykorzystanie plików ( tekstowy i binarny zapis/odczyt).
Zgodnie z przyjętym założeniem przekazuję tylko podstawowe informacje umożliwiające efektywne wykorzystanie strumieni ( większość z tych działań można zrealizować na wiele sposobów - zazwyczaj będę ograniczał się do jednego).
Standardowe strumienie znakowe
Drukowanie wyników
Przy prostych wydrukach wystarczy wykorzystanie standardowego strumienia out ( obiekt klasy PrintStream) z klasy System z podstawowymi metodami : print, println i printf ( wydruk z formatowaniem). Jeśli zależy nam na efektywności i przenośności lepiej jest definiować własny strumień klasy PrintWriter , np. :
PrintWriter wyj= new PrintWriter( new BufferedWriter(new OutputStreamWriter (System.out)),true);
Do bardziej złożonych wydruków ( np. tabele, wykazy) należy stosować metodę printf.
Parametr format zawiera bezpośredni tekst i opis sposobu drukowania argumentów.
Ogólna postać formatu dla pojedynczego argumentu :
%[argument_index$][flags][width][.precision]conversion
Elementy w nawiasach [] są opcjonalne.
argument_index numer argumentu ( od 1) , jeśli jest opuszczony drukuje kolejny (liczone są tylko nieindeksowane) element, indeks < oznacza poprzedni argument ( umożliwia wielokrotny wydruk jednego argumentu)
flags ciąg znaków modyfikujących sposób wydruku ( znaczenie zależy od typu drukowanego elementu)
width minimalna szerokość pola wydruku
precision górne ograniczenie szerokości pola wydruku ( znaczenie zależy od typu drukowanego elementu)
conversion sposób formatowania elementu ( zbiór poprawnych konwersji zależy od typu elementu)
Formaty dla róznych typów argumentów :
- boolean
%[argument_index$][flags][width][.precision]conversion
konwersja : b lub B (wydruk wielkimi literami)
szerokość - minimalna szerokość pola ( uzupełniana spacjami)
precyzja - maksymalna szerokość pola (brak oznacza brak ograniczeń)
flaga - '-' wyrównanie do lewego brzegu pola ( domyślne wyrównanie do prawego)
- char
%[argument_index$][flags][width]conversion
konwersja : c lub C (wydruk wielkimi literami)
szerokość - minimalna szerokość pola ( uzupełniana spacjami)
flaga - '-' wyrównanie do lewego brzegu pola ( domyślne wyrównanie do prawego)
Uwagi : aby wydrukować znak procentu podajemy %% - format bezargumentowy
%n - oznacza niezależny od platformy znak nowej linii - bezargumentowy
- typy całkowite ( byte, short,int,long)
%[argument_index$][flags][width]conversion
konwersja : d
szerokość - minimalna szerokość pola ( uzupełniana spacjami)
flagi: '-' wyrównanie do lewego brzegu pola ( domyślne wyrównanie do prawego)
'0' uzupełnianie pola 0 a nie spacją
'+' znak jest zawsze drukowany
' ' dla liczb dodatnich zamiast znaku drukowana spacja
'(' liczby ujemne są drukowane w ( ) ale bez znaku -
- typy rzeczywiste (float, double)
%[argument_index$][flags][width][.precision]conversion
konwersja : e lub E postać naukowa (wykładnicza)
f postać stałoprzecinkowa
szerokość - minimalna szerokość pola ( uzupełniana spacjami)
precyzja - liczba miejsc po kropce dziesiętnej uzupełniana 0 lub obcinana przez zaokrąglenie ( domyślnie 6)
flaga - '-' wyrównanie do lewego brzegu pola ( domyślne wyrównanie do prawego)
'#' wymusza druk kropki dziesiętnej nawet jeśli część ulamkowa jest 0
- napisy (również obiekty ze zdefiniowaną metodą toString)
%[argument_index$][flags][width][.precision]conversion
konwersja : s lub S (wydruk wielkimi literami)
szerokość - minimalna szerokość pola ( uzupełniana spacjami)
precyzja - maksymalna szerokość pola (brak oznacza brak ograniczeń)
flaga - '-' wyrównanie do lewego brzegu pola ( domyślne wyrównanie do prawego)
- data/czas (klasy Calendar i Date)
%[argument_index$][flags][width][.precision]conversion
konwersja : t lub T (pierwszy znak , konwersja jest zawsze dwuznakowa)
po szczegółowy opis znaczenia drugiego znaku odsyłam do helpa
szerokość - minimalna szerokość pola ( uzupełniana spacjami)
precyzja - maksymalna szerokość pola (brak oznacza brak ograniczeń)
flaga - '-' wyrównanie do lewego brzegu pola ( domyślne wyrównanie do prawego)
Uwagi :
- jeżeli wydruk nie kończy się znakiem nowej linii należy użyć metody flush( ) do natychmiastowego wypisania na ekranie
- podobne metody ma metoda String.format(...) szczególnie przydatna do przeciążania metody toString dla obiektów ( ale nie tylko )
Wczytywanie danych
W wielu wypadkach do wczytywania danych wystarczą proste klasy ( InputStream lub Reader ) jednak wygodniejsze ( i szybsze ) jest użycie buforowanego wejścia ( BufferedReader), a najbardziej elastyczne jest wykorzystanie klasy dzielącej zawartość strumienia na proste elementy tzw. tokeny ( StreamTokenizer ).
Deklaracja zmiennych :
BufferedReader wej= new BufferedReader( new InputStreamReader(System.in));
StreamTokenizer st = new StreamTokenizer(wej); // argument typu Reader
Uwaga : praktycznie wszystkie metody wejściowe zgłaszają wyjątek IOException, który należy obsłużyć lub przekazać do metody wywołującej.
Podstawowe metody klasy BufferedReader :
public int read()throws IOException
wczytanie pojedynczego znaku ( -1 gdy koniec strumienia)
public int read(char[] cbuf,int off,int len)throws IOException
wczytanie ciągu znaków do fragmentu ( można opuścić off i len) tablicy znakowej ( wynik - liczba wczytanych znaków lub -1 - próba czytania na końcu strumienia )
public String readLine()throws IOException
wynikiem jest zawartość wczytanej linii tekstu
public long skip(long n) throws IOException
pomiń n znaków
public boolean ready()throws IOException
true jeśli w strumieniu są jakieś znaki
public int available()throws IOException
liczba znaków oczekujących w strumieniu ( jest to metoda klasy InputStream dostępna dzięki użyciu strumienia System.in)
Aby wczytać wartość liczbową używamy metody readln, a następnie odpowiedniej metody parse ( Boolean, Int , Double ...). Niestety metody te są wrażliwe ( zgłaszają NumberFormatException) na niepotrzebne znaki ( nawet spacje) więc trzeba je samemu usuwać . Tej wady nie ma StreamTokenizer.
Uwaga: warto przeanalizować możliwości klasy Scanner.
Klasa ta może rozpoznawać : nazwy(słowa), liczby, stringi i komentarze. Typowy sposób wykorzystania to :
ustawienie parametrów do analizy strumienia
void |
definiuje znak będący początkiem komentarza wierszowego |
void |
Określa czy nowa linia jest traktowana jako token |
void |
wymusza zmianę znaków słowa na małe litery |
void |
|
void |
|
void |
|
void |
|
void |
|
void |
|
void |
|
void |
odczytywanie i przetwarzanie kolejnych tokenów, przez wywołanie :
wej.nextToken( )
wynikiem jest typ tokena ( można go również odczytać z pola wej.ttype )
TT_EOF - koniec strumienia
TT_EOL - koniec linii ( tylko gdy ustawiony jest .....)
TT_NUMBER - wartość liczbowa, umieszczona w polu val (typu double)
TT_WORD - nazwa(napis) , umieszczony w polu sval ( typu String).
Dodatkowe metody :
int |
|
void |
Uwaga: nie trzeba zamykać strumieni zbudowanych na bazie strumieni standardowych ( out, in, err).
W przypadku prowadzenia analizy danych zapisanych w strumieniu przez wiele różnych metod bardzo przydatna jest możliwość zwrotu (lub wręcz wstawienia ) do strumienia ostatnio odczytanego znaku( lub ciągu znaków). Taką możliwość daje metoda unread( ..) klasy PushBackReader.
Przykład :
import java.util.*;
import java.io.*;
public class ComparatorPublikacji implements Comparator<Publikacja>,Serializable
{ public int compare(Publikacja p1,Publikacja p2)
{ return p1.autor.compareTo(p2.autor)!=0 ? p1.autor.compareTo(p2.autor) : p1.tytul.compareTo(p2.tytul);}
}
// interfejs utworzony tylko po to, żeby przekazywać strumieni jako parametry metod
import java.io.*;
public interface Strumien
{ PrintWriter wyj= new PrintWriter( new BufferedWriter(new OutputStreamWriter(System.out)),true);
BufferedReader wej= new BufferedReader( new InputStreamReader(System.in));
StreamTokenizer st = new StreamTokenizer(wej); // argument typu Reader
}
import java.io.*;
public abstract class Publikacja implements Comparable, Cloneable, Serializable, Strumien
{ String tytul;
String autor;
int rok;
Publikacja()
{ wyj.print(" Podaj Tytul Autora i Rok "); wyj.flush();
try {st.nextToken();
tytul=st.sval;
st.nextToken();
autor=st.sval;
while(st.nextToken()!=st.TT_NUMBER);
rok=(int)st.nval;
}catch(IOException e) {System.out.println("Publikacja Blad wczytywania");}
}
public Publikacja clone()
{ try{ Publikacja p=(Publikacja)super.clone();
p.tytul= new String(tytul);
p.autor=new String(autor);
return p;
} catch(CloneNotSupportedException e){return null;}
}
public String toString()
{ return String.format("%-30s %-15s %6d ", tytul,autor,rok); }
public boolean equals(Publikacja p)
{ return p!=null && tytul.equals(p.tytul) && autor.equals(p.autor) ; }
public int hashCode()
{ return tytul.hashCode() & autor.hashCode(); }
public int compareTo(Object p1)
{ Publikacja p=(Publikacja)p1;
return (tytul.compareTo(p.tytul)!=0 ? tytul.compareTo(p.tytul) : autor.compareTo(p.autor));
}
}
import java.io.*;
public class Ksiazka extends Publikacja
{ String wydawnictwo;
public Ksiazka()
{ super();
try{ wyj.print(" Podaj wydawnictwo ");wyj.flush();
st.nextToken();
wydawnictwo=st.sval;
} catch(IOException e){ System.out.println(" Ksiazka Blad wejscia ");}
}
public String toString()
{ return super.toString() + String.format(" Ksiazka wydana przez %s ",wydawnictwo);}
}
import java.io.*;
public class Artykol extends Publikacja
{ String czasopismo;
public Artykol()
{super();
wyj.print(" w czasopismie ");wyj.flush();
try{ st.nextToken();
czasopismo=st.sval;
}catch(IOException e) { System.out.println(" Artykol Blad wejscia ");}
}
public String toString()
{ return super.toString() + String.format(" Artykol w : %s",czasopismo);}
}
import java.util.*;
import java.io.*;
public class test implements Strumien
{
SortedSet<Publikacja> s = new TreeSet<Publikacja>();
public void druk(Collection<Publikacja> s)
{ for(Publikacja el:s)
wyj.printf("%s %n" ,el);
wyj.println();
}
public void nowaPublikacja()
{ String odp="";
while(!(odp.equals("Ksiazka")||odp.equals("Artykol")))
{ wyj.println(" Ksiazka czy Artykol ");
try{ st.nextToken();
odp=st.sval;
} catch(IOException e) { System.out.println(" nowa Publikacja Blad wejscia ");return;}
}
s.add(odp.equals("Ksiazka")? new Ksiazka() : new Artykol());
}
public void druk1(Iterator<Publikacja> it)
{ while(it.hasNext())
System.out.println(it.next()+" , ");
System.out.println();
}
public void test()
{ char odp = ' ';
do { wyj.println(" Co chcesz robic : Dodaj publikacje, Wypisz liste, Koniec ");
try { st.nextToken(); }
catch(IOException e){ System.out.println(" Blad menu "); return;}
odp=st.sval.toLowerCase().charAt(0);
switch(odp)
{case 'd' : nowaPublikacja();break;
case 'w' : druk(s); break;
case 'k' : break;
default : wyj.printf(" Nie ma takiej opcji %n");
}
} while (odp!='k');
}
}
Strumienie plikowe
Do czego wykorzystujemy pliki :
przygotowywanie dużych zestawów danych wejściowych
do zapisu obszernych wyników
do przechowywania zgromadzonych danych między kolejnymi uruchomieniami aplikacji
do pracy z wielkimi ( nie mieszczącymi się w pamięci operacyjnej ) zbiorami danych.
1 i 2 - pliki tekstowe
3 i 4 - pliki binarne ( do 4 najlepsze są pliki o dostępie bezpośrednim).
Uwaga : strumienie plikowe trzeba po wykorzystaniu zamykać używając metody close( ).
Przy wykorzystaniu plików tekstowych mamy dwa rozwiązania :
przekierowanie strumieni standardowych
Przez wywołanie metody z klasy System :
static void |
setErr(PrintStream err) |
static void |
setIn(InputStream in) |
static void |
setOut(PrintStream out) |
System.setIn("dane.txt");
System.setOut("wyniki.txt");
PrintWriter wyj= new PrintWriter( new BufferedWriter(new OutputStreamWriter(System.out)),true);
BufferedReader wej= new BufferedReader( new InputStreamReader(System.in));
StreamTokenizer st = new StreamTokenizer(wej);
wykorzystanie klas : FileReader i FileWriter
Konstruktory ( analogiczne dla FileWriter):
public FileReader(String fileName) throws FileNotFoundException
public FileReader(File file) throws FileNotFoundException
public FileWriter(String fileName) throws FileNotFoundException
public FileWriter(File file) throws FileNotFoundException
public FileWriter(String fileName, boolean append) throws FileNotFoundException
public FileWriter(File file, boolean append) throws FileNotFoundException
np:
PrintWriter wyj= new PrintWriter( new BufferedWriter(new FileWriter("wyniki.txt")),true);
lub prościej
PrintWriter wyj= new PrintWriter("wyniki.txt");
BufferedReader wej= new BufferedReader( new FileReader("dane.txt"));
StreamTokenizer st = new StreamTokenizer(wej);
Wykorzystanie tak jak to opisano w poprzednim wykładzie.
Przy plikach binarnych wykorzystujemy klasy : FileInputStream i FileOutputStream ( dla prostych danych ) lub ObjectInputStream i ObjectOutputStream ( dla obiektów).
Konstruktory ( analogiczne dla FileOutputStream + opcjonalny parametr append):
public FileInputStream(String fileName) throws FileNotFoundException
public FileInputStream(File file) throws FileNotFoundException
Np.
FileInputString wej = new FileInputString("baza.dta");
FileOutputString wyj = new FileOutputString("baza.dta");
Podstawowe metody raead/write są przeciążone dla wszystkich typów prostych i ciągów bajtów.
Do pracy z obiektami powinniśmy korzystać z klas ObjectInputStream i ObjectOutputStream.
np.
ObjectInputStream wejOb= new(ObjectInputStream(wej));
ObjectOutputStream wyjOb= new(ObjectOutputStream(wyj));
Dodatkowe metody to :
public final Object readObject( ) throws IOException,ClassNotFoundException
public final void writeObject(Object obj) throws IOException
Uwaga: jeśli obiekt ma pola typu obiektowego należy wykorzystać mechanizm serializacji\deserializacji. Każda z klas występujących w grafie zapisywanych obiektów musi implementować interfejs Serializable. Po zapisie i odczycie mamy odtworzony cały graf obiektów ( identyczne obiekty składowe nie są powielane).
Przy pracy bezpośrednio z plikiem ( plik jako wielka tablica w pamięci zewnętrznej ) wykorzystujemy klasę RandomAccesFile ( nie jest to podejście strumieniowe).
Konstruktory:
public RandomAccessFile(String name,String mode)throws FileNotFoundException
public RandomAccessFile(File file,String mode)throws FileNotFoundException
Tryby dostępu to :
r - tylko odczyt
rw - odczyt i zapis
rws, rwd - fizyczny zapis zmian po każdej modyfikacji
Podstawowe metody to przciążone read/write i close.
Dodatkowe metody udostępniane przez klasę :
long getFilePointer( ) - daj bieżącą pozycję kursora pliku
long length( ) - długość (wielkość) pliku
void seek ( long pos) - ustaw kursor na pozycję
void setLength(long newLength) - zmień wielkość pliku
void skipBytes(int n ) - przesuń kursor o n bajtów
Jeśli potrzebujemy odczytać informacje o pliku , manipulować plikami lub kartotekami powinniśmy wykorzystać klasę File.
Podstawowy konstruktor : File( String nazwa)
Wybrane ( proste metody) :
boolean |
|
boolean |
|
boolean |
|
static File |
|
boolean |
|
boolean |
|
boolean |
|
long |
|
boolean
|
renameTo(File dest) zmień nazwę pliku. |
Przykład wykorzystania plików ( pomijam klasy, które nie uległy zmianie ).
import java.util.*;
import java.io.*;
public class test implements Strumien
{
public void test() throws IOException,ClassNotFoundException
{ PrintWriter plikWyj= new PrintWriter("wyniki.txt");
Spis spis=new Spis();
spis.wczytaj();
char odp = ' ';
do { wyj.println(" Co chcesz robic : Dodaj publikacje, Wypisz liste, Save, Restore, Koniec ");
st.nextToken(); }
odp=st.sval.toLowerCase().charAt(0);
switch(odp)
{case 'd' : spis.nowaPublikacja(st,true);break;
case 'w' : plikWyj.printf("%s %n",spis); break;
case 's' : spis.save(); break;
case 'r' : spis=spis.restore(); break;
case 'k' : break;
default : wyj.printf(" Nie ma takiej opcji %n");
}
} while (odp!='k');
if(plikWyj != null) plikWyj.close();
}
}
import java.util.*;
import java.io.*;
public class Spis implements Strumien,Serializable
{ SortedSet<Publikacja> s = new TreeSet<Publikacja>();
public String toString()
{ String wynik=new String();
for(Publikacja el:s)
wynik=wynik+String.format("%s %n" ,el);
return wynik;
}
public void nowaPublikacja(StreamTokenizer st,boolean standard)throws IOException
{ String odp="";
while(!(odp.equals("Ksiazka")||odp.equals("Artykol")))
{ if(standard) {wyj.println(" Ksiazka czy Artykol ");}
st.nextToken();
odp=st.sval;
}
s.add(odp.equals("Ksiazka")? new Ksiazka(st,standard) : new Artykol(st,standard));
}
public void save()throws IOException
{ ObjectOutputStream plik = new ObjectOutputStream(new BufferedOutputStream( new FileOutputStream("baza.dta")));
plik.writeObject(this);
plik.close();
}
public Spis restore()throws IOException, ClassNotFoundException
{ ObjectInputStream plik = new ObjectInputStream(new BufferedInputStream( new FileInputStream("baza.dta")));
Spis tmp=(Spis)plik.readObject();
plik.close();
return tmp;
}
public void wczytaj() throws IOException
{ StreamTokenizer plikWej=new StreamTokenizer(new FileReader("dane.txt"));
while(plikWej.nextToken()!=plikWej.TT_EOF)
{plikWej.pushBack();
nowaPublikacja(plikWej,false);
}
}
}
import java.io.*;
public abstract class Publikacja implements Comparable, Cloneable, Serializable, Strumien
{ String tytul;
String autor;
int rok;
Publikacja(StreamTokenizer st,boolean standard)throws IOException
{ if(standard){wyj.print(" Podaj Tytul Autora i Rok "); wyj.flush();}
st.nextToken();
tytul=st.sval;
st.nextToken();
autor=st.sval;
while(st.nextToken()!=st.TT_NUMBER);
rok=(int)st.nval;
}
public Publikacja clone()
{ try{ Publikacja p=(Publikacja)super.clone();
p.tytul= new String(tytul);
p.autor=new String(autor);
return p;
} catch(CloneNotSupportedException e){return null;}
}
public String toString()
{ return String.format("%-30s %-15s %6d ", tytul,autor,rok); }
public boolean equals(Publikacja p)
{ return p!=null && tytul.equals(p.tytul) && autor.equals(p.autor) ; }
public int hashCode()
{ return tytul.hashCode() & autor.hashCode(); }
public int compareTo(Object p1)
{ Publikacja p=(Publikacja)p1;
return (tytul.compareTo(p.tytul)!=0 ? tytul.compareTo(p.tytul) : autor.compareTo(p.autor));
}
}
import java.io.*;
public class Ksiazka extends Publikacja
{ String wydawnictwo;
public Ksiazka(StreamTokenizer st, boolean standard) throws IOException
{ super(st,standard);
if(standard) {wyj.print(" Podaj wydawnictwo ");wyj.flush();}
st.nextToken();
wydawnictwo=st.sval;
}
public String toString()
{ return super.toString() + String.format(" Ksiazka wydana przez %s ",wydawnictwo);}
}
import java.io.*;
public class Artykol extends Publikacja
{ String czasopismo;
public Artykol(StreamTokenizer st, boolean standard) throws IOException
{super(st,standard);
if( standard) {wyj.print(" w czasopismie ");wyj.flush();}
st.nextToken();
czasopismo=st.sval;
}
public String toString()
{ return super.toString() + String.format(" Artykol w : %s",czasopismo);}
}
Wychwytywanie błędnych zachowań programu - asercje , wyjątki.
Pojęcia :
serwer - klasa udostępniająca jakąś usługę
klient - korzystający z usług serwera ( klasa, aplikacja, człowiek).
bład - fizyczny błąd występujący w oprogramowaniu ( error)
błędne wykonanie ( zachowanie) - objaw występowania błędu ( fault ).
Rodzaje błędów :
syntaktyczne - składniowe
logiczne
błędne zachowania klienta, sytuacje awaryjne w otoczeniu serwera.
Sposoby sygnalizacji błędnych zachowań :
wydruki kontrolne
kody powrotu z metod
zgłaszanie wyjątków
asercje
Wyjątki :
Wyjątki - obiekty służące do przekazywania informacji diagnostycznej .
Zgłaszanie wyjątku :
void f ( )
{ .....
if ( o = = null) throw new NullPointerException( " Nie przekazano obiektu ");
....
}
Postać instrukcji throw :
throw new TypWyjatku(" opcjonalny komunikat diagnostyczny")
lub :
TypWyjatku e = new TypWyjatku("komunikat");
throw e;
Hierarchia klas ziązanych z obsługą wyjątków :
Throwable
Error
VirtualMachineError
AssertionError
Exception
RuntimeException
--- NulPointerException
--- IndexOutOfBoundsException
--- inne niekontrolowane wyjątki
IOException
inne kontrolowane wyjątki
Wyjątki klasy error sygnalizują poważne błędy systemowe, zazwyczaj nie są przechwytywane i nie powinno się wymuszać ich obsługi przez klienta.
Wyjątki klasy RuntimeException sygnalizują błędy programisty, czasami ich obsługa ma sens i wtedy je przechwytujemy, ale wymuszanie ich obsługi nie jest wskazane.
Wyjątki dziedziczące po klasie Exception sygnalizują błędne zachowanie otoczenia serwera na które serwer powinien być przygotowany, zazwyczaj ich obsługa jest możliwa i najczęściej jest wymuszana przez serwer.
Do wymuszania obsługi wyjątku służy klauzula throws.
void f ( ) throws NullPointerException
{ .....
if ( o = = null) throw new NullPointerException( " Nie przekazano obiektu ");
....
}
Jeśli mamy sensowną możliwość reakcji (np. poprawa wartości argumentu, doprecyzowanie informacji diagnostycznej itp. ) na wystąpienie wyjątku powinniśmy go przechwycić i obsłużyć - służą do tego instrukcje try ... catch.
void f ( )
{ ...
try {
// instrukcje mogące wygenerować wyjątek
} catch(Exception e) {
// obsługa wyjątku
}
...
// pozostałe instrukcje - pomijane w przypadku wystąpienia wyjątku
}
Jeśli instrukcje bloku try mogą wygenerować więcej rodzajów wyjątków to możemy użyć kilku instrukcji catch. Ponieważ sprawdzane są kolejno więc ustawiamy je od od najbardziej szczegółowego wyjątku do najbardziej ogólnego.
void f ( )
{ ...
try {
// utwórz strumień plikowy , odczytaj i zamknij go
} catch(FileNotFoundException e) {
// nie odnaleziono pliku o podanej nazwie
}
catch(IOException e ) {
// obsługa pozostałych błędów wejścia wyjścia
}
...
// pozostałe instrukcje - pomijane w przypadku wystąpienia wyjątku
}
Jeśli pewne instrukcje muszą być wykonane niezależnie od tego czy wystąpił wyjątek czy nie do bloków try catch dodajemy klauzulę finally.
void f ( )
{ ...
try {
// utwórz strumień plikowy i odczytaj go
} catch(FileNotFoundException e) {
// nie odnaleziono pliku o podanej nazwie
}
catch(IOException e ) {
// obsługa pozostałych błędów wejścia wyjścia
}
finally {
// instrukcje wykonywane zawsze np. zamknięcie pliku ( jeśli został otwarty )
}
...
// pozostałe instrukcje - pomijane w przypadku wystąpienia wyjątku
}
Jeśli nie wiemy co zrobić w przypadku wystąpienia wyjątku kontrolowanego nie powinniśmy przechwytywać go ( i dawać pustą obsługę, lub powielać systemową informację diagnostyczną - jak to się często robi ), ale przekazać go "wyżej" - do klienta ( może on będzie wiedział co z tym zrobić).
void f ( ) throws IOException
{
// instrukcje mogące wygenerować wyjątek IOException
}
Uwaga: możemy również zgłaszać nowe wyjątki w bloku catch.
Asercje :
Asercja jest to warunek logiczny sprawdzający czy stan programu ( wartości zmiennych ) jest zgodny z przyjętymi założeniami (ułatwia wykrywanie błędów logicznych programu). Stosowana przede wszystkim na etapie uruchamiania programu. Domyślnie java wyłącza sprawdzanie asercji. Włączenie wymaga ustawienia odpowiednich opcji kompilatora i loadera :
javac -source 1.4 MyClass.java
java -ea MyClass
W środowisku BlueJ trzeba ustawić opcję -ea ( i -eas jeśli chcemy objąć asercjami klasy systemowe ) dla maszyny wirtualnej :
czyli w pliku bluej.defs należy do wiersza bluej.vm.args dopisać -ea ( i ewentualnie usunąć znak komentarza '#' ), wiersz po zmianie :
bluej.vm.args=-server -Xincgc -ea -esa
Postać instrukcji assert :
assert warunek [ : wyrażenie]
jeśli warunek nie jest spełniony generowany jest wyjątek AssertionError z dodatkową informacją ( jeśli jest) przekazaną przez opcjonalne wyrażenie.
Asercje stosyjemy do sprawdzania :
inwariantów wewnętrznych
inwariantów przepływu sterowania
warunków wstępnych (pre) i końcowych ( post) dla metod
inwariantów klasowych
Nie powinno się stosować asercji :
do sprawdzania poprawności argumentów metod publicznych ( należy zgłosić wyjątek podklasy RuntimeException)
powodujących efekty uboczne ( są jednak możliwe wyjątkowe sytuacje )
np. // Broken! - action is contained in assertion
assert names.remove(null);
należy zrealizować tak :
// Fixed - action precedes assertion
boolean nullsRemoved = names.remove(null);
assert nullsRemoved; // Runs whether or not asserts are enabled
Ad. a
if (i % 3 == 0) {
...
} else if (i % 3 == 1) {
...
} else {
assert i % 3 == 2 : i;
...
}
Pozornie niemożliwa sytuacja.
Podobnie :
switch(suit) {
case Suit.CLUBS:
...
break;
case Suit.DIAMONDS:
...
break;
case Suit.HEARTS:
...
break;
case Suit.SPADES:
...
}
Przyjęliśmy założenie, że kolor karty może mieć tylko jedną z czterech wartości. Dla sprawdzenia tego możemy dodać :
default:
assert false : suit;
Inne ( nawet lepsze ) rozwiązanie to :
default:
throw new AssertionError(suit);
Ad. b
Zasada : wstawiaj asercje wszędzie tam gdzie nigdy nie powinno się dojść ( assert false).
Ad. c
Warunki wstępne ( poprawność argumentów wywołania metod ) sprawdzamy tylko dla metod prywatnych.
Warunki końcowe - sprawdzają czy efekt działania metody ( wynik i zmiana pól ) spełnia założenia.
Ad. d
Warunki jakie muszą być spełnione przez wartości pól aby obiekt był uznany za poprawny ( patrz np. Data). Powinniśmy sprawdzać w każdej metodzie modyfikującej pola.
Rekurencja.
Tym razem tylko przykłady, wprowadzenie mam w pamięci operacyjnej.
Wersja trochę niedydaktyczna - tak na prawdę jest to przykład programowania strukturalnego , a nie obiektowego.
import java.io.*;
public class Kalkulator
{StreamTokenizer wej=new StreamTokenizer(new BufferedReader(new InputStreamReader
(System.in)));
void oblicz() throws IOException
{ // ustawiamy analizator tak aby nowa linia (EOL) '/' i '-' były traktowane jako tokeny
// pod pojęciem liczby rozumiemy liczbę bez znaku
wej.eolIsSignificant(true);
wej.ordinaryChar('/');
wej.ordinaryChar('-');
System.out.println(" Wprowadz wyrażenie ");
wej.nextToken();
// każda metoda startuje z wczytanym piewrszym tokenem
System.out.println(" = " + wyrazenie());
}
double wyrazenie()throws IOException
{ double suma=0;
int oper=wej.ttype;
if(oper!='+' && oper!='-') // liczba lub wyrazenie w ( ) - czyli skladnik
{suma=skladnik(); oper=wej.ttype;}
while(oper=='+' || oper=='-')
{ wej.nextToken();
switch(oper)
{ case '+': suma+=skladnik(); break;
case '-': suma-=skladnik();
}
oper=wej.ttype;
}
return suma;
}
double skladnik() throws IOException
{double iloczyn=czynnik();
int oper=wej.ttype;
while(oper=='*' ||oper=='/')
{ wej.nextToken();
switch(oper)
{ case '*': iloczyn*=czynnik(); break;
case '/': iloczyn/=czynnik();
}
oper=wej.ttype;
}
return iloczyn;
}
double czynnik()throws IOException
{ double wart;
if(wej.ttype==wej.TT_NUMBER) wart=wej.nval;
// jeśli nie liczba to musi być wyrażenie w nawiasach
else {wej.nextToken(); wart=wyrazenie();}
wej.nextToken();
return wart;
}
}
Tym razem wersja wykorzystująca klasy wewnętrzne. Moim zdaniemwprowadzone nieco na siłę. Dodana jest również częściowa kontrola poprawności zapisu wyrażenia.
import java.io.*;
public class Kalkulator
{StreamTokenizer wej= new StreamTokenizer(new BufferedReader(new InputStreamReader
(System.in)));
void oblicz() throws IOException
{ // ustawiamy analizator tak aby nowa linia (EOL) '/' i '-' były traktowane jako tokeny
// pod pojęciem liczby rozumiemy liczbę bez znaku
wej.eolIsSignificant(true);
wej.ordinaryChar('/');
wej.ordinaryChar('-');
System.out.println(" Wprowadz wyrażenie ");
wej.nextToken();
// każda metoda startuje z wczytanym piewrszym tokenem
System.out.println(" = " + (new Wyrazenie()).dajWart());
}
class Wyrazenie
{ double lewy=0.0, // pierwszy argument i jednocześnie wynik
prawy=0.0; // drugi argument operatora dodawania
Wyrazenie() throws IOException
{ wyrazenie();}
double dajWart()
{ return lewy;}
void wyrazenie()throws IOException,RuntimeException
{ int oper=wej.ttype;
if(oper!='+' && oper!='-') // liczba lub wyrazenie w () - czyli skladnik
if(oper==wej.TT_NUMBER ||oper=='(') {lewy=(new Skladnik()).dajWart(); oper=wej.ttype;}
else throw new RuntimeException("Oczekiwany ( lub liczba ");
while(oper=='+' || oper=='-')
{ wej.nextToken();
prawy=(new Skladnik()).dajWart();
switch(oper)
{ case '+': lewy+=prawy ; break;
case '-': lewy-=prawy; break;
default : assert false : "nieoczekiwany znak "+(char) oper;
}
oper=wej.ttype;
}
}
}
class Skladnik
{ double lewy=0.0,
prawy=0.0;
Skladnik() throws IOException
{skladnik();}
double dajWart()
{return lewy;}
void skladnik() throws IOException
{ lewy=(new Czynnik()).dajWart();
int oper=wej.ttype;
while(oper=='*' ||oper=='/')
{ wej.nextToken();
prawy=(new Czynnik()).dajWart();
switch(oper)
{ case '*': lewy*=prawy; break;
case '/': lewy/=prawy; break;
default : assert false : "Nieoczekiwany znak "+ (char)oper;
}
oper=wej.ttype;
}
}
}
class Czynnik
{ double wart;
Czynnik() throws IOException
{czynnik();}
double dajWart()
{return wart;}
void czynnik()throws IOException, RuntimeException
{ if(wej.ttype==wej.TT_NUMBER) wart=wej.nval;
// jeśli nie liczba to musi być wyrażenie w nawiasach
else if(wej.ttype=='(')
{wej.nextToken(); wart=(new Wyrazenie()).dajWart();
if(wej.ttype!=')')
throw new RuntimeException("Oczekiwany ) "); }
else throw new RuntimeException("Oczekiwany ( ");
wej.nextToken();
}
}
}
Typy wyliczeniowe w Javie.
Stosowane wtedy gdy mamy do czynienia z zestawami stałych.
Najprostsza definicja :
public enum Kolor
{ trefl,karo,kier,pik }
Możliwe działania :
public class TestKolor
{
void test()
{ Kolor kolor= Kolor.trefl;
System.out.println(kolor+" "+kolor.name()+" "+kolor.ordinal());
kolor= Kolor.valueOf("karo");
System.out.println(kolor+" "+kolor.name()+" "+kolor.ordinal());
kolor= Enum.valueOf(Kolor.class,"kier");
System.out.println(kolor+" "+kolor.name()+" "+kolor.ordinal());
kolor= Kolor.values()[3];
System.out.println(kolor+" "+kolor.name()+" "+kolor.ordinal());
switch(kolor)
{ case trefl: System.out.println("wybraleś trefl "); break;
case karo: System.out.println("wybraleś karo "); break;
case kier: System.out.println("wybraleś kier "); break;
case pik: System.out.println("wybraleś pik "); break;
}
for(Kolor k:Kolor.values())
System.out.println(k);
}
}
Możliwe jest definiowanie własnych metod ( przykład kolorów przy grze w tysiąca ) :
public enum Kolor
{
trefl("zoledz",60),
karo("dzwonek",80),
kier("czerwien",100),
pik("wino",40);
private String alias;
private int meldunek;
Kolor(String alias,int meldunek)
{this.alias=alias; this.meldunek=meldunek;}
String dajAlias()
{ return alias; }
int dajMeldunek()
{return meldunek;}
public static Kolor valueOfAlias( String s )
{ try { return valueOf( s ); }
catch ( IllegalArgumentException e )
{ for ( Kolor k : Kolor.values() )
if ( k.dajAlias().equals(s))
return k;
throw new IllegalArgumentException( " nieznany kolor : " + s );
}
}
}
public class TestKolor
{ void test()
{ Kolor kolor= Kolor.trefl;
System.out.println(kolor+" "+kolor.dajAlias()+" "+kolor.dajMeldunek());
kolor= Kolor.valueOfAlias("dzwonek");
System.out.println(kolor+" "+kolor.name()+" "+kolor.ordinal());
kolor= Kolor.valueOfAlias("kier");
System.out.println(kolor+" "+kolor.name()+" "+kolor.ordinal());
}
}
Można również definiować różne metody dla każdej z wartości.
public enum Operation {
PLUS { double eval(double x, double y) { return x + y; } },
MINUS { double eval(double x, double y) { return x - y; } },
TIMES { double eval(double x, double y) { return x * y; } },
DIVIDE { double eval(double x, double y) { return x / y; } };
// Do arithmetic op represented by this constant
abstract double eval(double x, double y);
}
public void test ( ) {
double x = 5.0;
double y = 10.0);
for (Operation op : Operation.values())
System.out.printf("%f %s %f = %f%n", x, op, y, op.eval(x, y));
}
Wykrywanie i lokalizacja błędów - testowanie i debugowanie.
1
1
nazwa algorytmu i parametry
działanie
STOP
START
N
T
warunek
InstrukcjaNie
InstrukcjaTak
Ins_n
Ins_inne
......
Ins_2
Ins_1
wyrażenie
W1 W2 ........ Wn Inne
Instrukcje pętli
Inicjacja
N
Inicjacja
N
T
warunek
T
Instrukcje pętli
warunek