wyklad podstawy wprogramowania Janusz Ratajcza, PWr, Programowanie 1


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 :

  1. Lis M., Praktyczny kurs Java, Helion 2004 (dobry podręcznik do Javy )

  2. Barnes D. J. , Kolling M. , Objects first with Java, Pearson Education Limited, 2006 (skoncentrowany na myśleniu obiektowym)

  3. Wierzbicki M., Java programowanie obiektowe, Helion, 2006 ( zawiera podstawy Javy i zasady programowania obiektowego)

  4. http://www.bluej.org (darmowe środowisko, tutorial, przykłady itp.)

  5. 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

  6. Eckel B., Thinking i Java edycja polska, Helion, 2006, biblia języka Java

i wiele innych....

  1. 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):

  1. analiza problemu

  2. sformułowanie algorytmu

  3. implementacja ( wykonanie) algorytmu

  4. sprawdzenie poprawności wyników

  5. analiza uzyskanego rozwiązania

Formy zapisu algorytmu:

  1. zapis słowny

  2. schemat blokowy

  3. program zapisany w określonym języku programowania.

0x08 graphic
0x08 graphic
Schematy blokowe - elementy składowe:

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

początek algorytmu koniec algorytmu instrukcja wywołanie algorytmu

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
pomocniczego

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

wprowadzanie danych lub rozgałęzienie dwukierunkowe

wyprowadzanie wyników uwaga : InstrukcjaNie może być pusta

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
rozgałęzienie wielokrotne ( można opuścić wariant "inne" - wówczas nic się nie dzieje).

POPRAWNE STRUKTURY ITERACJI (PĘTLI) :

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

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!");

}

}

  1. 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 :

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 Ja
va 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:

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.

  1. 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);}

}

  1. Tablice w Javie.

  1. tablice jednowymiarowe

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;

}

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

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.

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]];

  1. 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) :

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.

  1. 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) {..}

  1. 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 :

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 :

for( E el : c) //c wyrażenie typu kolekcyjnego

instrukcja;

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);

}

}

  1. Wejście/wyjście w Javie.

Problemy :

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 :

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).

  1. Standardowe strumienie znakowe

    1. 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.

printf (String format, Object... args)

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 )

    1. 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 :

void

commentChar(int ch)

definiuje znak będący początkiem komentarza wierszowego

 void

eolIsSignificant(boolean flag)

Określa czy nowa linia jest traktowana jako token

 void

lowerCaseMode(boolean fl)

wymusza zmianę znaków słowa na małe litery

 void

ordinaryChar(int ch)
          wymusza traktowanie znaku jako "zwykłego"

 void

ordinaryChars(int low, int hi)
           znaki z zakresu low <= c <= high mają być traktowane jako

 void

quoteChar(int ch)
          ustal znak oznaczający napis - domyślny ( " )

 void

resetSyntax()
           traktuj wszystkie znaki jako zwykłe

 void

slashSlashComments(boolean flag)
          rozpoznawanie komentarzy w stylu C++ ( // )

 void

slashStarComments(boolean flag)
          rozpoznawanie komentarzy w stylu C ( /* )

 void

whitespaceChars(int low, int hi)
          traktuj znaki z zakresu low <= c <= high jako białe spacje

 void

wordChars(int low, int hi)
          traktuj znaki z zakresu low <= c <= high jako składowe słowa

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

lineno()
zwraca bieżący numer linii          

 void

pushBack()
          zwraca ostatnio odczytany token do strumienia

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');

}

}

  1. Strumienie plikowe

Do czego wykorzystujemy pliki :

    1. przygotowywanie dużych zestawów danych wejściowych

    2. do zapisu obszernych wyników

    3. do przechowywania zgromadzonych danych między kolejnymi uruchomieniami aplikacji

    4. 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 :

Przez wywołanie metody z klasy System :

static void

static void

static void

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);

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

canRead() można czytać z pliku.

 boolean

canWrite() można zapisywać do pliku.

 boolean

createNewFile() utwórz nowy ( pusty ) plik.

static File

createTempFile(String prefix, String suffix)
          Utwórz plik tymczasowy.

 boolean

delete() Skasuj plik.

 boolean

isDirectory() Czy to kartoteka.

 boolean

isFile() czy to plik.

 long

length() wielkość pliku.

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);}

}

  1. 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

--- NulPointerException

--- IndexOutOfBoundsException

--- inne niekontrolowane 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 :

    1. inwariantów wewnętrznych

    2. inwariantów przepływu sterowania

    3. warunków wstępnych (pre) i końcowych ( post) dla metod

    4. inwariantów klasowych

Nie powinno się stosować asercji :

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.

  1. 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();

}

}

}

  1. 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));

}

  1. 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



Wyszukiwarka