Programowanie Obiektowe (Java) 1

Transkrypt

Programowanie Obiektowe (Java) 1
Programowanie Obiektowe (Java)
Wykład dziewiąty
1.
Kontenery w Javie 5
Jednym z nowych elementów jakie
zostały dodane do języka Java w jej
najnowszym
wydaniu
(numer
środowiska 1.5) są typy ogólne (ang.
1 import java.util.*;
2
3 class Element {
4
generics), które wzbogacają język
Java o mechanizmu podobny do
mechanizmu szablonów, znanego z
języka C++. Są one stosowane
między innymi w kontenerach.
Dzięki nim możemy określić jakiej
klasy obiekty będą przechowywane
w kontenerze (lub jaki interfejs będą
implementowały te obiekty). Dzięki
temu możemy częściowo ograniczyć
1
rzutowanie w dół , które nie jest
bezpieczne, jeśli nie stosujemy
żadnego
mechanizmu
rozpoznawania
typu
podczas
wykonania programu. Nie możemy
2
jednak, stosując typy ogólne ,
zupełnie pominąć rzutowania w dół,
bowiem oprócz obiektów określonej
klasy
możemy
w
kontenerze
przechowywać również obiekty klas
dziedziczących po niej. Typy ogólne
mogą być stosowane nie tylko w
kontenerach,
ale
również
w
zwracanych przez nie iteratorach.
Inną nowością, którą możemy
stosować wraz z kontenerami jest
nowa postać pętli „for”. Oto kod
programu ilustrującego użycie list,
w którym zastosowano opisane
wcześniej nowe elementy Javy:
private int value;
5
6
public String toString() {
7
8
return new Integer(value).toString();
}
9
10
public Element(int x) {
11
12
value=x;
}
13 }
14
15 public class Listy {
16
public static void main(String[] args) {
17
ArrayList<Element> a = new ArrayList<Element>();
18
LinkedList<Element> l = new LinkedList<Element>();
19
Iterator<Element> it;
20
21
for(int i=0;i<20; i++)
22
a.add(new Element((int)(Math.random()*20)));
23
System.out.println(a);
24
for(Element e : a)
25
System.out.print(e+" ");
26
System.out.println();
27
it = a.iterator();
28
while(it.hasNext())
29
System.out.print(it.next()+" ");
30
System.out.println();
31
it=a.iterator();
32
while(it.hasNext()) {
33
l.addFirst(it.next());
34
it.remove();
35
}
36
System.out.println(l);
37
while(!l.isEmpty())
1
Korzystając w kontenerach z typów ogólnych sprawiamy, że kontener pamięta klasę przechowywanych obiektów. Stosowane są więc przez niego
referencje konkretnej klasy, a nie klasy Object.
2
W języku polskim częściej spotyka się obecnie termin „typy generyczne”.
1
Programowanie Obiektowe (Java)
38
System.out.print(l.removeLast()+" ");
39
System.out.println();
40
}
41 }
Należy zaznaczyć, że nowa postać pętli for zapewnia większy stopień bezpieczeństwa korzystania z kontenerów, niż „zwykła” instrukcja for, jeśli
tworzymy pętle zagnieżdżone. Innym udogodnieniem w korzystaniu z kontenerów jest automatyczne „opakowywanie” i „rozpakowywanie” typów
podstawowych w odpowiadające im klasy. Ta technika pozwala na stworzenie kontenera, do którego będzie można dodawać wartości typu int bez
dokonywania dodatkowych zabiegów, które zostaną za nas wykonane automatycznie.
2.
Klasa Arrays
W pakiecie java.util znajduje się klasa Arrays zawierająca wiele metod statycznych pozwalających na manipulowanie zawartością tablic. Metoda
fill() wypełnia wszystkie elementy tablicy tą samą wartością. Metoda equals() służy do porównywania elementów tablicy. Została ona przeciążona
dla typów podstawowych i obiektów klasy Object. Należy pamiętać, że w przypadku obiektów przechowywanych w tablicy, porównywane są ich
referencje, a nie zawartość pól. Jeśli chcemy porównywać pola obiektów, to możemy zrobić to na dwa sposoby. Pierwszy polega na
zaimplementowaniu przez klasę obiektów przechowywanych w tablicy interfejsu Comparable i jego metody compareTo(). Drugi polega na
stworzeniu odrębnej klasy implementującej interfejs Comparator (należy zdefiniować jego metody equals() i compare()). Ten drugi sposób
wykorzystujemy wówczas, gdy nie mamy dostępu do kodu klasy przechowywanych obiektów. Tablicę możemy posortować przy pomocy metody
sort(), natomiast przeszukać ją możemy za pomocą metody binarySearch(). Kopiowanie tablic najlepiej przeprowadzić za pomocą metody
arraycopy(), która jest metodą statyczną klasy System (nie należy ona do klasy Arrays). Na zakończenie należy zaznaczyć, że tablica jest jedynym
3
kontenerem , który „zna” klasę obiektów, które przechowuje (notabene tablice w Javie też są obiektami).
3.
Strumienie
Zbiór klas umożliwiających realizację
operacji wejścia – wyjścia w Javie jest
bardzo duży. Od wersji 1.4 jest on
wzbogacony
o
pakiety
klas
umożliwiających wykonywanie operacji
„niskopoziomowych”
na
danych
znajdujących się w pliku lub innym
zasobie. Te pakiety nie będą jednak
przedstawione
na
tym
wykładzie.
Zajmiemy
się
jedynie
operacjami
„wysokopoziomowymi”, które umożliwiają
nam klasy zgromadzone w pakiecie
java.io. Pierwszą klasą z tego pakietu,
jaka zostanie tu omówiona jest klasa File.
1 import java.io.*;
2 import java.util.*;
3
4 class Rekurencja {
5
private File sciezka;
6
7
public Rekurencja(String nazwa) {
8
9
sciezka = new File(nazwa);
}
10
11
12
13
Klasa ta pozwala na przeprowadzanie
takich operacji na plikach i katalogach,
jak:
tworzenie
nowych
plików
i
katalogów,
przeglądanie
katalogów,
zmianę nazwy, usunięcie pliku, obsługę
ścieżki do pliku lub katalogu, obsługę
atrybutów plików. Oto krótki przykład
jak wykorzystać obiekt takiej klasy do
rekurencyjnego wypisania zawartości
bieżącego katalogu:
public void start() {
walk(sciezka,"");
}
14
15
private void walk(File plik,String indent) {
16
String[] lista;
17
File[] pliki;
Klasa Rekurencja posiada pole sciezka,
które jest klasy File. W konstruktorze tej
18
19
lista = plik.list();
klasy Rekurencja tworzony jest obiekt
20
pliki = plik.listFiles();
klasy
21
22
3
for(int i=0; i<lista.length;i++)
System.out.println(indent+lista[i]);
23
indent+=" ";
24
for(int i=0; i<pliki.length;i++)
File.
Przez
parametr
nazwa
przekazywana będzie ścieżka dostępu do
pliku, z którym będzie związany ten
obiekt (w przypadku tego programu
będzie to katalog bieżący). Metoda start()
rozpoczyna
przeszukiwanie
tego
katalogu, wywołując metodę walk(). Ta
To stwierdzenie jest prawdziwe dla wersji Javy wcześniejszych niż 5.
2
Programowanie Obiektowe (Java)
25
26
System.out.println(pliki[i].getName()+":");
27
walk(pliki[i],indent);
28
29
ostatnia jest metodą rekurencyjną.
Najpierw zapisuje ona referencję do
tablicy nazw plików w zmiennej lokalnej
lista, a następnie w referencji pliki
if(pliki[i].isDirectory() && pliki[i].canRead()) {
zapisuje adres tablicy obiektów klasy File
związanych z tymi plikami. W pierwszej
pętli for wypisywane są nazwy plików
}
}
znajdujących się w bieżącym katalogu, w
drugiej przeglądania jest tablica obiektów
klasy
File i sprawdzane jest, które z nich są
30 }
31
32 public class Katalogi {
33
34
Rekurencja r = new Rekurencja(".");
35
r.start();
36
katalogami i czy można te katalogi
przeczytać (czy program ma do tego
uprawnienia). Dla każdego z tych
obiektów wywoływana jest rekurencyjnie
metoda walk(). Obiekt klasy Rekurencja
public static void main(String[] args) {
tworzony jest w klasie publicznej w
metodzie main. Konstruktorowi tego
}
obiektu
przekazywana
jest
nazwa
katalogu bieżącego, czyli kropka (.). Jeśli
za pomocą metod klasy File, chcielibyśmy wyświetlić pliki, których nazwy pasują do ustalonego przez nas wzorca, to do metody list() powinniśmy
37 }
przekazać obiekt klasy, która implementuje interfejs FilenameFilter i zdefiniować jego metodę accept, która jest deklarowana następująco:
boolean accept(File dir, String name);
Klasa implementująca ten interfejs może być klasą anonimową.
Klasy realizujące operacje wejścia – wyjścia w Javie zakładają, że zarówno źródłem jak i ujściem danych są strumienie. Strumień jest abstrakcją
oznaczającą dowolny element mogący pobierać lub przyjmować dane. W Javie, w pakiecie java.io można wyróżnić trzy grupy klas. Pierwsza grupa
związana jest z operacjami binarnymi (nazywanymi również bajtowymi), druga jest związana z operacjami znakowymi (uwzględnia kodowanie za
pomocą standardu Unicode), natomiast trzecia grupa umożliwia zapis i odczyt obiektów. Podstawowymi klasami pozwalającymi na realizację
4
odczytu i zapisu bajtowego są klasy InputStream i OutputStream . Jak łatwo się domyślić metodą służącą do odczytania pojedynczego bajta w
klasie InputStream jest metoda read(), natomiast do zapisu pojedynczego bajta w drugiej z wymienionych klas jest write(). Z tych klas wywodzą
5
się inne, które związane są z obsługą poszczególnych rodzajów źródeł i ujść. Te klasy zostały zestawione poniżej w tabelę :
Klasa
Sposób działania
Sposób użycia
ByteArrayInputStream
Źródłem danych jest tablica znajdująca się w Parametrem wywołania konstruktora jest
pamięci operacyjnej.
referencja do tablicy z której będą pobierane
dane. Powinno się używać razem z obiektami
FilterInputStream.
StringBuffferInputStream
Wykonuje konwersję łańcucha znaków do Parametrem wywołania konstruktora jest
obiektu klasy InputStream.
referencja do obiektu klasy String. Również
należy
używać
FilterInputStream.
FileInputStream
Umożliwia odczyt danych z pliku.
z
tym
plikiem
lub
klasy
Podobnie jak poprzednio
powinno się używać
FilterInputStream.
Umożliwia
odczyt
danych
nienazwanego (ang. pipe)
klasy
Argumentem wywołania konstruktora jest
łańcuch znaków reprezentujący ścieżkę
dostępu do pliku, lub obiekt klasy File
związany
z
FileDescriptor.
PipedInputStream
obiektami
z
z
obiektami
klasy
potoku Parametrem wywołania konstruktora tej
klasy jest obiekt klasy PipedOutputStream.
Obiekty tej klasy są używane w aplikacjach
wielowątkowych, jako środki umożliwiające
komunikację między wątkami. Reszta jak
wyżej.
4
5
Klasy InputStream i OutputStream są klasami abstrakcyjnymi, więc wszędzie tam, gdzie będzie mowa o obiektach tych klas, będziemy mieć na
myśli obiekty jej klas pochodnych.
Tabela ta jest utworzona na podstawie „Thinking in Java” Bruce'a Eckela.
3
Programowanie Obiektowe (Java)
Klasa
SequenceInputStream
Sposób działania
Sposób użycia
Pozwala połączyć pewną liczbę strumieni Argumentami wywołania są dwa obiekty klasy
wejściowych w jeden strumień.
InputStream lub kontener klasy Enumeration
zawierający takie obiekty.
Jest to klasa abstrakcyjna służąca za klasę
bazową dla klas obiektów „dekoratorów”,
obiektów klasy InputStream i pochodnych,
FilterInputStream
które zostaną opisane później.
ByteArrayOutputStream
Tworzy bufor w pamięci, do którego będą Parametr konstruktora jest opcjonalny i
zapisane dane.
określa początkowy rozmiar bufora. Obiekty
tej klasy powinny być połączone z obiektami
klasy FilterOutputStream.
FileOutputStream
Zapisuje dane do pliku.
Argumentem wywołania konstruktora jest
łańcuch znaków reprezentujący ścieżkę
dostępu do pliku, lub obiekt klasy File
związany
z
FileDescriptor.
tym
plikiem
lub
klasy
Podobnie jak poprzednio
należy
używać
z
FilterOutputStream.
Umożliwia
zapis
danych
nienazwanego (ang. pipe)
PipedOutputStream
do
obiektami
klasy
potoku Parametrem wywołania konstruktora tej
klasy jest obiekt klasy PipedInputStream.
Obiekty tej klasy są używane w aplikacjach
wielowątkowych, jako środki umożliwiające
komunikację między wątkami. Reszta jak
wyżej.
Jest to klasa abstrakcyjna służąca za klasę
bazową dla klas obiektów „dekoratorów”,
obiektów klasy OutputStream i pochodnych,
FilterOutputStream
które zostaną opisane później.
W większości aplikacji napisanych w Javie, aby utworzyć reprezentację pojedynczego strumienia wejściowego lub wyjściowego tworzy się niej
jeden lecz kilka obiektów. Pierwszy z tych obiektów jest obiektem którejś z klas wymienionych wyżej, natomiast pozostałe obiekty są
6
dekoratorami, które zwiększają funkcjonalność strumienia. Oto zestawienie tych dekoratorów :
Klasa
6
Działanie
Sposób użycia
DataInputStream
Umożliwia odczyt danych prostych typów w Argumentem wywołania konstruktora jest
sposób niezależny od ich faktycznego zapisu w obiekt klasy InputStream.
danym systemie. Jego metody, to np.:
readByte(), readFloat().
BufferedInputStream
Używany, w celu przyspieszenia operacji Argument konstruktora taki sam, jak wyżej.
odczytu ze strumienia poprzez zbuforowanie Dodatkowy argument może określać rozmiar
danych.
bufora. Obiekty tej klasy nie dostarczają
własnych interfejsów.
LineNumberInputStream
Śledzi numery wierszy w strumieniu Argument wywołania konstruktora jak
wejściowym.
Do
jego
metod
należą: poprzednio. Zapewnia numerowanie wierszy.
getLineNumber() i setLineNumber(int)
PushBackInputStream
Tworzy bufor o rozmiarze jednego bajta, Argument konstruktora jak wyżej. Używany w
pozwalający na cofnięcie do strumienia tworzeniu kompilatorów i innych programów
realizujących analizę leksykalną wejścia.
ostatniego odczytanego baja.
DataOutputStream
Umożliwia zapis danych podstawowych typów Argumentem konstruktora jest obiekt klasy
do strumienia niezależny od platformy. OutputStream.
Przykładowe metody: writeByte(), writeFloat().
Na podstawie „Thinking in Java” Bruce'a Eckela.
4
Programowanie Obiektowe (Java)
Klasa
Działanie
Formatuje dane wyjściowe,
sposobie ich wyświetlania
PrintStream
Sposób użycia
decydując
o Argument konstruktora jak wyżej. Opcjonalny
drugi argument typu boolean określa, czy
bufor
związany
ze
strumieniem
opróżniany po każdej operacji zapisu.
BufferOutputStream
Buforuje dane do zapisu. Można wymusić ich Argumentem wywołania konstruktora jest
zapisanie wywołując metodę flush() obiektu tej obiekt klasy OutputStream i opcjonalnie
klasy.
rozmiar bufora.
Oto przykład realizujący bajtowy odczyt danych z pliku:
1 import java.io.*;
2 class Odczyt {
3
private FileInputStream fs;
4
5
public Odczyt(String s) {
6
try {
7
fs = new FileInputStream(new File(s));
8
}catch(FileNotFoundException e) {
9
e.printStackTrace();
10
System.err.println(e.getLocalizedMessage());
11
}catch(Exception e) {
12
e.printStackTrace();
13
System.err.println(e.getLocalizedMessage());
14
15
}
}
16
17
public void czytaj() throws IOException {
18
try {
19
int a;
20
while((a=fs.read())!=-1) System.out.print(a);
21
System.out.println();
22
}catch(IOException e) {
23
e.printStackTrace();
24
System.err.println(e.getLocalizedMessage());
25
}catch(Exception e) {
26
e.printStackTrace();
27
System.err.println(e.getLocalizedMessage());
28
}finally {
29
fs.close();
30
31
jest
}
}
32 }
33
5
Programowanie Obiektowe (Java)
34 public class OdczytBinarny {
35
public static void main(String[] args) {
36
Odczyt o = new Odczyt("OdczytBinarny.java");
37
try {
38
o.czytaj();
39
}catch(IOException e) {
40
e.printStackTrace();
41
System.err.println(e.getLocalizedMessage());
42
}catch(Exception e) {
43
e.printStackTrace();
44
System.err.println(e.getLocalizedMessage());
45
46
}
}
47 }
Odczyt i zapis znakowy realizowany jest przez inne klasy, których funkcjonalność odpowiada przedstawionym wyżej klasom. Do tych klas należą:
Reader, Writer, FileReader, FileWriter, StringReader, StringWriter, CharArrayReader, CharArrayWriter, PipedReader i PipedWriter. Dodatkowo
istnieją klasy umożliwiające konwersję strumieni z bajtowych na znakowe. Są to InputStreamReader i OutputStreamWriter. Klasy odczytu i
zapisu znakowego posiadają również własne dekoratory: FilterReader, FilterWriter, BufferReader, BufferWriter, PrintWriter, LineNumberReader,
PushBackReader i StreamTokenizer. Ostatni dekorator służy do rozpoznawania wzorców w odczytywanych ze strumienia danych i od wersji 1.4
Javy w dużej mierze został zastąpiony przez klasy realizujące rozpoznawanie wzorców (ang. pattern matching). Oto dwa przykłady użycia
pokazujące w jaki sposób skorzystać ze znakowych operacji odczytu. Pierwszy czyta dane z pliku, natomiast drugi konwertuje strumień związany
ze standardowym wejściem na strumień znakowy i wypisuje jego zawartość na ekran:
1 import java.io.*;
2 class Odczyt {
3
private FileReader fs;
4
5
public Odczyt(String s) {
6
try {
7
fs = new FileReader(s);
8
}catch(FileNotFoundException e) {
9
e.printStackTrace();
10
System.err.println(e.getLocalizedMessage());
11
}catch(Exception e) {
12
e.printStackTrace();
13
System.err.println(e.getLocalizedMessage());
14
15
}
}
16
17
18
public void czytaj() throws IOException {
try {
19
char a;
20
while((a=(char)fs.read())!=(char)-1) System.out.print(a);
6
Programowanie Obiektowe (Java)
21
System.out.println();
22
}catch(IOException e) {
23
e.printStackTrace();
24
System.err.println(e.getLocalizedMessage());
25
}catch(Exception e) {
26
e.printStackTrace();
27
System.err.println(e.getLocalizedMessage());
28
}finally {
29
fs.close();
30
}
31
}
32 }
33
34 public class OdczytZnakowy {
35
public static void main(String[] args) {
36
Odczyt o = new Odczyt("OdczytZnakowy.java");
37
try {
38
o.czytaj();
39
}catch(IOException e) {
40
e.printStackTrace();
41
System.err.println(e.getLocalizedMessage());
42
}catch(Exception e) {
43
e.printStackTrace();
44
System.err.println(e.getLocalizedMessage());
45
}
46
}
47 }
import java.io.*;
public class Input {
public static void main(String[] args) {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
System.out.println(br.readLine());
} catch(Exception e) {
e.printStackTrace();
}
}
}
7
Programowanie Obiektowe (Java)
Kolejny przykład pokazuje w jaki sposób użyć dekoratora BufferReader do odczytu pliku:
1 import java.io.*;
2 class Odczyt {
3
private BufferedReader br;
4
5
public Odczyt(String s) {
6
try {
7
br = new BufferedReader(new FileReader(s));
8
}catch(FileNotFoundException e) {
9
e.printStackTrace();
10
System.err.println(e.getLocalizedMessage());
11
}catch(Exception e) {
12
e.printStackTrace();
13
System.err.println(e.getLocalizedMessage());
14
15
}
}
16
17
public void czytaj() throws IOException {
18
try {
19
String a;
20
while((a=br.readLine())!=null) System.out.println(a);
21
System.out.println();
22
}catch(IOException e) {
23
e.printStackTrace();
24
System.err.println(e.getLocalizedMessage());
25
}catch(Exception e) {
26
e.printStackTrace();
27
System.err.println(e.getLocalizedMessage());
28
}finally {
29
br.close();
30
31
}
}
32 }
33
34 public class OdczytBuforowany {
35
public static void main(String[] args) {
36
Odczyt o = new Odczyt("OdczytBuforowany.java");
37
try {
38
39
40
o.czytaj();
}catch(IOException e) {
e.printStackTrace();
8
Programowanie Obiektowe (Java)
41
System.err.println(e.getLocalizedMessage());
42
}catch(Exception e) {
43
e.printStackTrace();
44
System.err.println(e.getLocalizedMessage());
45
46
}
}
47 }
Opisane wyżej klasy służą do realizacji operacji wejścia – wyjścia na zasadzie dostępu sekwencyjnego, jeśli jest nam potrzebny dostęp swobodny,
możemy skorzystać z metod klasy RandomAccesFile. Java standardowo zawiera również klasy umożliwiające zapis i odczyt plików
skompresowanych. Istnieją również dwie klasy, związane ze strumieniami binarnymi (bajtowymi), które umożliwiają zapis i odczyt obiektów.
Takie operacje nazywa się operacjami serializacji obiektów. Te klasy to ObjectInputStream i ObjectOutputStream. Pierwsza posiada metodę
readObject(), druga writeObject(). Aby obiekt mógł być zapisany do strumienia jego klasa musi implementować interfejs Serializable, który nie
deklaruje żadnej metody, a pełni tylko funkcję znacznika, wskazującego, że obiekt danej klasy może być zapisany lub odczytany ze strumienia.
Należy pamiętać o tym, że metoda readObject() zwraca referencję klasy Object, a więc konieczne jest rzutowanie w dół, aby odtworzyć właściwą
klasę obiektu. Jeśli nie chcemy, aby pewne informacje przechowywane w polach obiektu były zapisywane w pliku, to takie pole deklarujemy z
użyciem słowa kluczowego transient. Inną metoda polega na implementowaniu interfejsu Externalizable zamiast Serializable i zdefiniowaniu jego
metod writeExternal() i readExternal(), w których trzeba zawrzeć kod odpowiedzialny za zapis poszczególnych pól obiektu.
9

Podobne dokumenty