4. Powszechnie używane klasy
W niniejszym rozdziale przedstawiamy kilka powszechnie używanych klas języka Java. Klasy te posiadają wiele atrybutów, metod i konstruktorów. Za każdym razem przedstawiamy jedynie niewielką część tych klas. Szczegółowe informacje na ich temat można znaleźć w pomocy języka Java, którą teraz przedstawiamy.
4.1. Dokumentacja
Jeśli zainstalowali Państwo pakiet JDK firmy Sun w folderze <jdk>, dokumentacja jest dostępna w folderze <jdk>\docs:

Czasami mamy plik jdk, ale bez dokumentacji. Można ją znaleźć na stronie firmy Sun http://www.sun.com. W folderze docs znajduje się plik index.html, który stanowi punkt wyjścia dla pomocy dotyczącej pliku JDK:


Powyższy link „API & Language” zapewnia dostęp do klas Java. Link „Demos/Tutorials” jest szczególnie przydatny, aby uzyskać przykłady programów w języku Java. Przejdźmy do linku „API & Language”:

Kliknijmy link „Java 2 Platform” API:

Ta strona stanowi prawdziwy punkt wyjścia dla dokumentacji dotyczącej klas. Można utworzyć skrót do tej strony, aby mieć do niej szybki dostęp. Plik URL znajduje się w katalogu <jdk>\docs\api\index.html. Znajdują się tam linki do setek klas Java z biblioteki JDK. Na początku największą trudnością jest ustalenie, do czego służą te różne klasy. Na początku ta pomoc ma więc sens tylko wtedy, gdy znamy nazwę klasy, o której chcemy uzyskać informacje. Można również kierować się nazwami klas, które zazwyczaj wskazują na ich przeznaczenie.
Weźmy przykład i poszukajmy informacji o klasie Vector, która implementuje tablicę dynamiczną. Wystarczy znaleźć na liście klas w lewym panelu link do klasy Vector:

i kliknąć ten link, aby wyświetlić definicję klasy:

Znajduje się tam
- hierarchię, w której znajduje się ta klasa, w tym przypadku java.util.Vector
- listę pól (atrybutów) klasy
- listę konstruktorów
- listę metod
W dalszej części przedstawiamy różne klasy. Zachęcamy czytelnika do systematycznego sprawdzania pełnych definicji używanych klas.
4.2. Klasy testowe
W poniższych przykładach wykorzystujemy czasami klasy personne oraz enseignant. Przypominamy tutaj ich definicje.
public class personne{
// nazwisko, imię, wiek
private String prenom;
private String nom;
private int age;
// konstruktor 1
public personne(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
// konstruktor 2
public personne(personne P){
this.prenom=P.prenom;
this.nom=P.nom;
this.age=P.age;
}
// toString
public String toString(){
return "personne("+prenom+","+nom+","+age+")";
}
// akcesoria
public String getPrenom(){
return prenom;
}
public String getNom(){
return nom;
}
public int getAge(){
return age;
}
//modyfikatory
public void setPrenom(String P){
this.prenom=P;
}
public void setNom(String N){
this.nom=N;
}
public void setAge(int age){
this.age=age;
}
}
Klasa enseignant wywodzi się z klasy personne i jest zdefiniowana w następujący sposób:
class enseignant extends personne{
// atrybuty
private int section;
// konstruktor
public enseignant(String P, String N, int age,int section){
super(P,N,age);
this.section=section;
}
// toString
public String toString(){
return "etudiant("+super.toString()+","+section+")";
}
}
Wykorzystamy również klasę etudiant, wywodzącą się z klasy personne i zdefiniowaną w następujący sposób:
class etudiant extends personne{
String numero;
public etudiant(String P, String N, int age,String numero){
super(P,N,age);
this.numero=numero;
}
public String toString(){
return "etudiant("+super.toString()+","+numero+")";
}
}
4.3. Klasa String
Klasa String reprezentuje ciągi znaków. Niech nom będzie zmienną typu ciąg znaków:
String nazwa;
nom jest odwołaniem do obiektu, który nie został jeszcze zainicjowany. Można go zainicjować na dwa sposoby:
nom="koń" lub nom=new String("koń")
Obie metody są równoważne. Jeśli później zapiszemy nom="poisson", to nom będzie odnosić się do nowego obiektu. Poprzedni obiekt *String("cheval")* zostanie utracony, a zajmowane przez niego miejsce w pamięci zostanie zwolnione.
Klasa String posiada wiele atrybutów i metod. Oto kilka z nich:
public char charAt(int i) | zwraca znak i z ciągu znaków, przy czym pierwszy znak ma indeks 0. Zatem String("cheval").charAt(3) jest równe 'v' |
public int compareTo(ciąg2) | chaine1.compareTo(ciąg2) porównuje ciąg1 z ciągiem2 i zwraca 0, jeśli ciąg1 = ciąg2, 1, jeśli ciąg1 > ciąg2, oraz -1, jeśli ciąg1 < ciąg2 |
public boolean equals(Object anObject) | chaine1.equals(ciąg2) zwraca wartość true, jeśli ciąg1 = ciąg2, w przeciwnym razie zwraca wartość false |
public String toLowerCase() | chaine1.toLowerCase() zamienia ciąg1 na małe litery |
public String toUpperCase() | chaine1.toUpperCase() zamienia ciąg1 na wielkie litery |
public String trim() | chaine1.trim() usuwa spacje na początku i na końcu ciągu1 |
public String substring(int beginIndex, int endIndex) | String("chapeau").subString(2,4) zwraca ciąg znaków „ape” |
public char[] toCharArray() | umożliwia umieszczenie znaków ciągu w tablicy znaków |
int length() | liczba znaków w ciągu |
int indexOf(String chaine2) | zwraca pozycję, na której w bieżącym ciągu znaków znajduje się chaine2, lub -1, jeśli chaine2 nie występuje |
int indexOf(String chaine2, int startIndex) | zwraca pozycję, na której w bieżącym ciągu znaków znajduje się chaine2, lub -1, jeśli chaine2 nie występuje. Wyszukiwanie rozpoczyna się od znaku o numerze startIndex. |
int lastIndexOf(String chaine2) | zwraca ostatnią pozycję ciągu „chaine2” w bieżącym ciągu lub -1, jeśli ciąg „chaine2” nie występuje |
boolean startsWith(String chaine2) | zwraca wartość true, jeśli bieżący ciąg znaków zaczyna się od chaine2 |
boolean endsWith(String chaine2) | zwraca wartość true, jeśli bieżący ciąg kończy się na chaine2 |
boolean matches(String regex) | zwraca wartość true, jeśli bieżący ciąg znaków pasuje do wyrażenia regularnego regex. |
String[] split(String regex) | Bieżący ciąg składa się z pól oddzielonych ciągiem znaków opisanym wyrażeniem regularnym regex. Metoda split pozwala pobrać pola do tablicy. |
String replace(char oldChar, char newChar) | zastępuje w bieżącym ciągu znaków znak oldChar znakiem newChar. |
Oto przykładowy program:
// importy
import java.io.*;
public class string1{
// klasa demonstracyjna
public static void main(String[] args){
String uneChaine="l'oiseau vole au-dessus des nuages";
affiche("uneChaine="+uneChaine);
affiche("uneChaine.Length="+uneChaine.length());
affiche("chaine[10]="+uneChaine.charAt(10));
affiche("uneChaine.IndexOf(\"vole\")="+uneChaine.indexOf("vole"));
affiche("uneChaine.IndexOf(\"x\")="+uneChaine.indexOf("x"));
affiche("uneChaine.LastIndexOf('a')="+uneChaine.lastIndexOf('a'));
affiche("uneChaine.LastIndexOf('x')="+uneChaine.lastIndexOf('x'));
affiche("uneChaine.substring(4,7)="+uneChaine.substring(4,7));
affiche("uneChaine.ToUpper()="+uneChaine.toUpperCase());
affiche("uneChaine.ToLower()="+uneChaine.toLowerCase());
affiche("uneChaine.Replace('a','A')="+uneChaine.replace('a','A'));
String[] champs=uneChaine.split("\\s+");
for (int i=0;i<champs.length;i++){
affiche("champs["+i+"]=["+champs[i]+"]");
}//dla
affiche("(\" abc \").trim()=["+" abc ".trim()+"]");
}//Main
// wyświetla
public static void affiche(String msg){
// wyświetla komunikat
System.out.println(msg);
}//wyświetla
}//klasa
oraz uzyskane wyniki:
uneChaine=l'oiseau vole au-dessus des nuages
uneChaine.Length=34
chaine[10]=o
uneChaine.IndexOf("vole")=9
uneChaine.IndexOf("x")=-1
uneChaine.LastIndexOf('a')=30
uneChaine.LastIndexOf('x')=-1
uneChaine.substring(4,7)=sea
uneChaine.ToUpper()=L'OISEAU VOLE AU-DESSUS DES NUAGES
uneChaine.ToLower()=l'oiseau vole au-dessus des nuages
uneChaine.Replace('a','A')=l'oiseAu vole Au-dessus des nuAges
champs[0]=[l'oiseau]
champs[1]=[vole]
champs[2]=[au-dessus]
champs[3]=[des]
champs[4]=[nuages]
(" abc ").trim()=[abc]
4.4. Klasa Vector
Wektor to dynamiczna tablica, której elementami są odwołania do obiektów. Jest to zatem tablica obiektów, której rozmiar może się zmieniać w czasie, co nie jest możliwe w przypadku statycznych tablic, z którymi mieliśmy do tej pory do czynienia. Oto niektóre pola, konstruktory i metody tej klasy:
public Vector() | tworzy pusty wektor |
public final int size() | liczba elementów wektora |
public final void addElement(Object obj) | dodaje obiekt, do którego odwołuje się obj, do wektora |
public final Object elementAt(int index) | odwołanie do obiektu o numerze index w tablicy – indeksy zaczynają się od 0 |
public final Enumeration elements() | zbiór elementów wektora w postaci wyliczenia |
public final Object firstElement() | odwołanie do pierwszego elementu wektora |
public final Object lastElement() | odwołanie do ostatniego elementu wektora |
public final boolean isEmpty() | zwraca wartość true, jeśli wektor jest pusty |
public final void removeElementAt(int index) | usuwa element o indeksie index |
public final void removeAllElements() | usuwają wszystkie elementy z wektora |
public final String toString() | zwraca ciąg znaków identyfikujący wektor |
Oto program testowy:
// zaimportowane klasy
import java.util.*;
public class test1{
// program główny main – static – metoda klasy
public static void main(String arg[]){
// tworzenie obiektów – instancji klas
personne p=new personne("Jean","Dupont",30);
enseignant en=new enseignant("Paula","Hanson",56,27);
etudiant et=new etudiant("Chris","Garot",22,"19980405");
System.out.println("p="+p.toString());
System.out.println("en="+en.toString());
System.out.println("et="+et.toString());
// polimorfizm
personne p2=(personne)en;
System.out.println("p2="+p2.toString());
personne p3=(personne)et;
System.out.println("p3="+p3.toString());
// wektor
Vector V=new Vector();
V.addElement(p);V.addElement(en);V.addElement(et);
System.out.println("Taille du vecteur V = "+V.size());
for(int i=0;i<V.size();i++){
p2=(personne) V.elementAt(i);
System.out.println("V["+i+"]="+p2.toString());
}
} // koniec funkcji main
}// koniec klasy
Skompilujmy ten program:
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>dir
10/06/2002 10:41 1 134 personne.class
10/06/2002 10:41 619 enseignant.class
10/06/2002 10:41 610 etudiant.class
10/06/2002 10:42 1 035 test1.java
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>javac test1.java
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>dir
10/06/2002 10:41 1 134 personne.class
10/06/2002 10:41 619 enseignant.class
10/06/2002 10:41 610 etudiant.class
10/06/2002 10:42 1 035 test1.java
10/06/2002 10:43 1 506 test1.class
Uruchommy plik test1.class:
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\vector>java test1
p=personne(Jean,Dupont,30)
en=etudiant(personne(Paula,Hanson,56),27)
et=etudiant(personne(Chris,Garot,22),19980405)
p2=etudiant(personne(Paula,Hanson,56),27)
p3=etudiant(personne(Chris,Garot,22),19980405)
Taille du vecteur V = 3
V[0]=personne(Jean,Dupont,30)
V[1]=etudiant(personne(Paula,Hanson,56),27)
V[2]=etudiant(personne(Chris,Garot,22),19980405)
W dalszej części nie będziemy już powtarzać procesu kompilacji i uruchamiania programów testowych. Wystarczy powtórzyć czynności opisane powyżej.
4.5. Klasa ArrayList
Klasa ArrayList jest analogiczna do klasy Vector. Różni się od niej zasadniczo tylko wtedy, gdy jest używana jednocześnie przez wiele wątków wykonawczych. Metody synchronizacji wątków w celu uzyskania dostępu do obiektu Vector lub ArrayList różnią się. Poza tym przypadkiem można stosować dowolną z tych klas. Oto niektóre pola, konstruktory i metody tej klasy:
ArrayList() | tworzy pustą listę |
int size() | liczba elementów tablicy |
void add(Object obj) | dodaje obiekt, do którego odwołuje się obj, do tablicy |
void add(int index, Object obj) | dodaje obiekt, do którego odwołuje się obj, do tablicy w pozycji index |
Object get(int index) | odwołanie do obiektu nr index z tablicy – indeksy zaczynają się od 0 |
boolean isEmpty() | zwraca wartość true, jeśli tablica jest pusta |
void remove(int index) | usuwa element o indeksie index |
void clear() | czyści tablicę ze wszystkich elementów |
Object[] toArray() | przekształca tablicę dynamiczną w tablicę klasyczną |
String toString() | zwraca ciąg znaków identyfikujący tablicę |
Oto program testowy:
// importowane klasy
import java.util.*;
public class test1{
// program główny main – static – metoda klasy
public static void main(String arg[]){
// tworzenie obiektów – instancji klas
personne p=new personne("Jean","Dupont",30);
enseignant en=new enseignant("Paula","Hanson",56,27);
etudiant et=new etudiant("Chris","Garot",22,"19980405");
System.out.println("p="+p);
System.out.println("en="+en);
System.out.println("et="+et);
// polimorfizm
personne p2=(personne)en;
System.out.println("p2="+p2);
personne p3=(personne)et;
System.out.println("p3="+p3);
// wektor
ArrayList personnes=new ArrayList();
personnes.add(p);personnes.add(en);personnes.add(et);
System.out.println("Nombre de personnes = "+personnes.size());
for(int i=0;i<personnes.size();i++){
p2=(personne) personnes.get(i);
System.out.println("personnes["+i+"]="+p2);
}
} // koniec funkcji main
}// koniec klasy
Uzyskane wyniki są takie same jak poprzednio.
4.6. Klasa Arrays
Klasa java.util.Arrays udostępnia metody statyczne umożliwiające wykonywanie różnych operacji na tablicach, w szczególności sortowanie i wyszukiwanie elementów. Oto kilka z tych metod:
static void sort(tablica) | sortuje tableau, wykorzystując do tego domyślną kolejność typu danych tablicy, czyli liczb lub ciągów znaków. |
static void sort (Object[] tablica, Comparator C) | sortuje tablicę tableau, wykorzystując do porównywania elementów funkcję porównawczą C |
static int binarySearch(tablica, element) | zwraca pozycję elementu o indeksie élément w tablicy tableau lub wartość <0 w przeciwnym razie. Tablica musi być wcześniej posortowana. |
static int binarySearch(Object[] tablica, Object element, Comparator C) | to samo, ale wykorzystuje funkcję porównawczą C do porównania dwóch elementów tablicy. |
Oto pierwszy przykład:
import java.util.*;
public class sort2 implements Comparator{
// klasa wewnętrzna prywatna
private class personne{
private String nom;
private int age;
public personne(String nom, int age){
this.nom=nom; // imię i nazwisko osoby
this.age=age; // jej wiek
}
// pobierz wiek
public int getAge(){
return age;
}
// tożsamość osoby
public String toString(){
return ("["+nom+","+age+"]");
}
}; // klasa osoby
// twórca
public sort2() {
// tablica osób
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// sortowanie tablicy osób
Arrays.sort(amis,this);
// weryfikacja
for(int i=0;i<3;i++)
System.out.println(amis[i]);
}//konstruktor
// funkcja porównująca osoby
public int compare(Object o1, Object o2){
// musi zwrócić
// -1, jeśli o1 jest „mniejsze niż” o2
// 0, jeśli o1 „jest równe” o2
// +1, jeśli o1 jest „większe niż” o2
personne p1=(personne)o1;
personne p2=(personne)o2;
int age1=p1.getAge();
int age2=p2.getAge();
if(age1<age2) return (-1);
else if (age1==age2) return (0);
else return +1;
}//porównuje
// funkcja testowa
public static void main(String[] arg){
new sort2();
}//ręka
}//klasa
Przyjrzyjmy się temu programowi. Funkcja main tworzy obiekt sort2. Konstruktor klasy sort2 wygląda następująco:
// producent
public sort2() {
// tabela osób
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// sortowanie listy osób
Arrays.sort(amis,this);
// weryfikacja
for(int i=0;i<3;i++)
System.out.println(amis[i]);
}//twórca
Tablą, którą należy posortować, jest tabela obiektów personne. Klasa personne jest zdefiniowana jako prywatna (private) wewnątrz klasy sort2. Metoda statyczna sort klasy Arrays nie wie, jak posortować tablicę obiektów personne, dlatego w tym przypadku konieczne jest użycie formy void sort(Object[] obj, Comparator C). Comparator jest interfejsem definiującym tylko jedną metodę:
i która musi zwracać 0: jeśli o1=o2, -1: jeśli o1 < o2, +1: jeśli o1 > o2. W prototypie void sort(Object[] obj, Comparator C) drugi argument C musi być obiektem implementującym interfejs Comparator. W konstruktorze sort2 wybrano bieżący obiekt this:
Wymaga to od nas wykonania dwóch czynności:
- wskazać, że klasa sort2 implementuje interfejs Comparator
- napisać funkcję compare w klasie sort2.
Wygląda ona następująco:
// funkcja porównująca osoby
public int compare(Object o1, Object o2){
// musi zwrócić
// -1, jeśli o1 jest „mniejsze niż” o2
// 0, jeśli o1 „jest równe” o2
// +1, jeśli o1 jest „większe niż” o2
personne p1=(personne)o1;
personne p2=(personne)o2;
int age1=p1.getAge();
int age2=p2.getAge();
if(age1<age2) return (-1);
else if (age1==age2) return (0);
else return +1;
}//porównuje
Aby porównać dwa obiekty personne, wykorzystujemy tutaj wiek (można by było użyć nazwy).
Wyniki wykonania są następujące:
Można było postąpić inaczej, aby zaimplementować interfejs Comparator:
import java.util.*;
public class sort2 {
// wewnętrzna klasa prywatna
private class personne{
…….
}; // klasa osoba
// konstruktor
public sort2() {
// tablica osób
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// sortowanie tablicy osób
Arrays.sort(amis,
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare1(o1,o2);
}//porównanie
}//klasa
);
// weryfikacja
for(int i=0;i<3;i++)
System.out.println(amis[i]);
}//generator
// funkcja porównująca osoby
public int compare1(Object o1, Object o2){
// musi zwrócić
// -1, jeśli o1 jest „mniejsze niż” o2
// 0, jeśli o1 „jest równe” o2
// +1, jeśli o1 jest „większe niż” o2
personne p1=(personne)o1;
personne p2=(personne)o2;
int age1=p1.getAge();
int age2=p2.getAge();
if(age1<age2) return (-1);
else if (age1==age2) return (0);
else return +1;
}//porównanie1
// main
public static void main(String[] arg){
new sort2();
}//main
}//klasa
Instrukcja sortowania przybrała następującą postać:
// sortowanie tablicy osób
Arrays.sort(amis,
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare1(o1,o2);
}//porównanie
}//klasa
);
Drugi parametr metody sort musi być obiektem implementującym interfejs Comparator. W tym miejscu tworzymy taki obiekt za pomocą metody new java.util.Comparator(), a tekst następujący po {…} definiuje klasę, której obiekt tworzymy. Nazywamy to klasą anonimową, ponieważ nie ma ona nazwy. W tej anonimowej klasie, która musi implementować interfejs Comparator, definiujemy metodę compare tego interfejsu. Metoda ta ogranicza się do wywołania metody compare1 klasy sort2. W ten sposób powracamy do poprzedniego przypadku.
Klasa sort2 nie implementuje już interfejsu Comparator. W związku z tym jej deklaracja przyjmuje następującą postać:
Teraz przetestujemy metodę binarySearch klasy Arrays na poniższym przykładzie:
import java.util.*;
public class sort4 {
// klasa wewnętrzna prywatna
private class personne{
// atrybuty
private String nom;
private int age;
// konstruktor
public personne(String nom, int age){
this.nom=nom; // imię i nazwisko osoby
this.age=age; // jej wiek
}
// pobierz imię
public String getNom(){
return nom;
}
// pobierz wiek
public int getAge(){
return age;
}
// tożsamość osoby
public String toString(){
return ("["+nom+","+age+"]");
}
}; // klasa osoby
// konstruktor
public sort4() {
// tabela osób
personne[] amis=new personne[]{new personne("tintin",100),new personne("milou",80),
new personne("tournesol",40)};
// komparatory
java.util.Comparator comparateur1=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare1(o1,o2);
}//porównuje
}//klasa
;
java.util.Comparator comparateur2=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare2(o1,o2);
}//porównuje
}//klasyfikuje
;
// sortowanie tabeli osób
Arrays.sort(amis,comparateur1);
// weryfikacja
for(int i=0;i<3;i++)
System.out.println(amis[i]);
// wyszukiwanie
cherche("milou",amis,comparateur2);
cherche("xx",amis,comparateur2);
}//generator
// funkcja porównująca osoby
public int compare1(Object o1, Object o2){
// musi zwrócić
// -1, jeśli o1 jest „mniejsze niż” o2
// 0, jeśli o1 „jest równe” o2
// +1, jeśli o1 jest „większe niż” o2
personne p1=(personne)o1;
personne p2=(personne)o2;
int age1=p1.getAge();
int age2=p2.getAge();
if(age1<age2) return (-1);
else if (age1==age2) return (0);
else return +1;
}//compare1
// funkcja porównująca osobę z imieniem
public int compare2(Object o1, Object o2){
// o1 jest osobą
// o2 jest ciągiem znaków, imieniem nom2 danej osoby
// musi zwrócić
// -1, jeśli o1.nom jest „mniejsze niż” nom2
// 0, jeśli o1.nom jest „równe” imieniu2
// +1, jeśli o1.nom jest „większe niż” nazwa2
personne p1=(personne)o1;
String nom1=p1.getNom();
String nom2=(String)o2;
return nom1.compareTo(nom2);
}//porównanie2
public void cherche(String ami,personne[] amis, Comparator comparateur){
// szuka znajomego w tablicy znajomych
int position=Arrays.binarySearch(amis,ami,comparateur);
// znaleziono?
if(position>=0)
System.out.println(ami + " a " + amis[position].getAge() + " ans");
else System.out.println(ami + " n'existe pas dans le tableau");
}//szuka
// ręka
public static void main(String[] arg){
new sort4();
}//ręka
}//klasa
W tym przypadku postąpiliśmy nieco inaczej niż w poprzednich przykładach. Dwa obiekty Comparator, niezbędne dla metod sort i binarySearch, zostały utworzone i przypisane do zmiennych comparateur1 oraz comparateur2.
// porównywarki
java.util.Comparator comparateur1=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare1(o1,o2);
}//porównuje
}//klasa
;
java.util.Comparator comparateur2=
new java.util.Comparator(){
public int compare(Object o1, Object o2){
return compare2(o1,o2);
}//porównuje
}//klasa
;
W kreatorze sort4 dwukrotnie przeprowadzane jest wyszukiwanie dychotomiczne w tabeli amis:
Metoda cherche otrzymuje wszystkie parametry potrzebne do wywołania metody binarySearch:
public void cherche(String ami,personne[] amis, Comparator comparateur){
// wyszukiwanie znajomego w tabeli znajomych
int position=Arrays.binarySearch(amis,ami,comparateur);
// znaleziono?
if(position>=0)
System.out.println(ami + " a " + amis[position].getAge() + " ans");
else System.out.println(ami + " n'existe pas dans le tableau");
}//szuka
Metoda binarySearch współpracuje z komparatorem comparateur2, który z kolei wywołuje metodę compare2 klasy sort4. Metoda rend zwraca pozycję szukanego nazwy w tablicy, jeśli istnieje, lub liczbę <0 w przeciwnym razie. Metoda compare2 służy do porównywania obiektu typu personne z nazwą typu String.
// funkcja porównująca osobę z imieniem
public int compare2(Object o1, Object o2){
// o1 to osoba
// o2 to ciąg znaków, imię „nom2” danej osoby
// musi zwrócić
// -1, jeśli o1.nom jest „mniejsze niż” nazwa2
// 0, jeśli o1.nom „jest równe” nazwa2
// +1, jeśli o1.nom „większe niż” nazwa2
personne p1=(personne)o1;
String nom1=p1.getNom();
String nom2=(String)o2;
return nom1.compareTo(nom2);
}//porównanie2
W przeciwieństwie do metody sort, metoda binarySearch nie przyjmuje dwóch obiektów personne, lecz jeden obiekt personne i jeden obiekt String w tej kolejności. Pierwszy parametr jest elementem tablicy amis, a drugi to imię i nazwisko poszukiwanej osoby.
4.7. Klasa Enumeration
Enumeration jest interfejsem, a nie klasą. Posiada następujące metody:
public abstract boolean hasMoreElements() | zwraca wartość true, jeśli wyliczenie zawiera jeszcze elementy |
public abstract Object nextElement() | zwraca odwołanie do następnego elementu wyliczenia |
Jak korzystać z wyliczenia? Zazwyczaj w następujący sposób:
Enumeration e=… // pobieramy obiekt typu wyliczeniowego
while(e.hasMoreElements()){
// wykorzystanie elementu e.nextElement()
}
Oto przykład:
// zaimportowane klasy
import java.util.*;
public class test1{
// program główny main – metoda statyczna klasy
public static void main(String arg[]){
// tworzenie obiektów – instancji klas
personne p=new personne("Jean","Dupont",30);
enseignant en=new enseignant("Paula","Hanson",56,27);
etudiant et=new etudiant("Chris","Garot",22,"19980405");
System.out.println("p="+p.toString());
System.out.println("en="+en.toString());
System.out.println("et="+et.toString());
// polimorfizm
personne p2=(personne)en;
System.out.println("p2="+p2.toString());
personne p3=(personne)et;
System.out.println("p3="+p3.toString());
// wektor
Vector V=new Vector();
V.addElement(p);V.addElement(en);V.addElement(et);
System.out.println("Taille du vecteur V = "+V.size());
int i;
for(i=0;i<V.size();i++){
p2=(personne) V.elementAt(i);
System.out.println("V["+i+"]="+p2.toString());
}
// wyliczenie
Enumeration E=V.elements();
i=0;
while(E.hasMoreElements()){
p2=(personne) E.nextElement();
System.out.println("V["+i+"]="+p2.toString());
i++;
}
}// koniec funkcji main
}//koniec klasy
Otrzymujemy następujące wyniki:
p=personne(Jean,Dupont,30)
en=enseignant(personne(Paula,Hanson,56),27)
et=etudiant(personne(Chris,Garot,22),19980405)
p2=enseignant(personne(Paula,Hanson,56),27)
p3=etudiant(personne(Chris,Garot,22),19980405)
Taille du vecteur V = 3
V[0]=personne(Jean,Dupont,30)
V[1]=enseignant(personne(Paula,Hanson,56),27)
V[2]=etudiant(personne(Chris,Garot,22),19980405)
V[0]=personne(Jean,Dupont,30)
V[1]=enseignant(personne(Paula,Hanson,56),27)
V[2]=etudiant(personne(Chris,Garot,22),19980405)
4.8. Klasa Hashtable
Klasa Hashtable umożliwia implementację słownika. Słownik można postrzegać jako tablicę o dwóch kolumnach:
klucz | wartość |
klucz1 | wartość1 |
klucz2 | wartość2 |
.. | ... |
Klucze są unikalne, np. c.a.d, co oznacza, że nie mogą istnieć dwa identyczne klucze. Główne metody i właściwości klasy Hashtable są następujące:
public Hashtable() | konstruktor – tworzy pusty słownik |
public int size() | liczba elementów w słowniku – przy czym elementem jest para (klucz, wartość) |
public Object put(Object key, Object value) | dodaje parę (key, value) do słownika |
public Object get(Object key) | pobiera obiekt powiązany z kluczem key lub null, jeśli klucz key nie istnieje |
public boolean containsKey(Object key) | zwraca wartość true, jeśli klucz key istnieje w słowniku |
public boolean contains(Object value) | prawda, jeśli wartość value występuje w słowniku |
public Enumeration keys() | zwraca klucze słownika w postaci wyliczenia |
public Object remove(Object key) | usuwa parę (klucz, wartość), gdzie klucz = key |
public String toString() | identyfikuje słownik |
Oto przykład:
// importowane klasy
import java.util.*;
public class test1{
// program główny main – static – metoda klasy
public static void main(String arg[]){
// tworzenie obiektów – instancji klas
personne p=new personne("Jean","Dupont",30);
enseignant en=new enseignant("Paula","Hanson",56,27);
etudiant et=new etudiant("Chris","Garot",22,"19980405");
System.out.println("p="+p.toString());
System.out.println("en="+en.toString());
System.out.println("et="+et.toString());
// polimorfizm
personne p2=(personne)en;
System.out.println("p2="+p2.toString());
personne p3=(personne)et;
System.out.println("p3="+p3.toString());
// słownik
Hashtable H=new Hashtable();
H.put("personne1",p);
H.put("personne2",en);
H.put("personne3",et);
Enumeration E=H.keys();
int i=0;
String cle;
while(E.hasMoreElements()){
cle=(String) E.nextElement();
p2=(personne) H.get(cle);
System.out.println("clé "+i+"="+cle+" valeur="+p2.toString());
i++;
}
}//koniec funkcji main
}//koniec klasy
Otrzymane wyniki są następujące:
p=personne(Jean,Dupont,30)
en=enseignant(personne(Paula,Hanson,56),27)
et=etudiant(personne(Chris,Garot,22),19980405)
p2=enseignant(personne(Paula,Hanson,56),27)
p3=etudiant(personne(Chris,Garot,22),19980405)
clé 0=personne3 valeur=etudiant(personne(Chris,Garot,22),19980405)
clé 1=personne2 valeur=enseignant(personne(Paula,Hanson,56),27)
clé 2=personne1 valeur=personne(Jean,Dupont,30)
4.9. Pliki tekstowe
4.9.1. Zapis
Aby zapisać dane do pliku, należy dysponować strumieniem zapisu. W tym celu można wykorzystać klasę FileWriter. Najczęściej stosowane konstruktory to:
FileWriter(String fileName) | tworzy plik o nazwie fileName – można następnie zapisywać w nim dane – ewentualny plik o tej samej nazwie zostanie nadpisany |
FileWriter(String fileName, boolean append) | to samo – istniejący plik o tej samej nazwie można wykorzystać, otwierając go w trybie dołączania (append=true) |
Klasa FileWriter udostępnia szereg metod do zapisu w pliku, odziedziczonych po klasie Writer. Do zapisu w pliku tekstowym zaleca się użycie klasy PrintWriter, której najczęściej stosowane konstruktory to:
PrintWriter(Writer out) | argumentem jest obiekt typu Writer lub c.a.d – strumień zapisu (do pliku, w sieci itp.) |
PrintWriter(Writer out, boolean autoflush) | to samo. Drugi argument zarządza buforowaniem wierszy. Gdy ma wartość fałsz (wartość domyślna), wiersze zapisywane do pliku przechodzą przez bufor w pamięci. Gdy bufor jest pełny, zawartość jest zapisywana do pliku. Poprawia to dostęp do dysku. Jednak czasami takie zachowanie jest niepożądane, zwłaszcza podczas zapisu w sieci. |
Przydatne metody klasy PrintWriter to:
void print(Type T) | zapisuje dane typu T (String, int, …) |
void println(Type T) | to samo, z dodatkiem znaku końca linii |
void flush() | opróżnia bufor, jeśli nie jesteśmy w trybie automatycznego opróżniania |
void close() | zamyka strumień zapisu |
Oto program, który zapisuje kilka wierszy do pliku tekstowego:
// importy
import java.io.*;
public class ecrire{
public static void main(String[] arg){
// otwarcie pliku
PrintWriter fic=null;
try{
fic=new PrintWriter(new FileWriter("out"));
} catch (Exception e){
Erreur(e,1);
}
// zapis do pliku
try{
fic.println("Jean,Dupont,27");
fic.println("Pauline,Garcia,24");
fic.println("Gilles,Dumond,56");
} catch (Exception e){
Erreur(e,3);
}
// zamknięcie pliku
try{
fic.close();
} catch (Exception e){
Erreur(e,2);
}
}// koniec funkcji main
private static void Erreur(Exception e, int code){
System.err.println("Erreur : "+e);
System.exit(code);
}//Błąd
}//klasa
Plik out uzyskany w wyniku wykonania wygląda następująco:
4.9.2. Odczyt
Aby odczytać zawartość pliku, należy dysponować strumieniem odczytu powiązanym z tym plikiem. W tym celu można wykorzystać klasę FileReader oraz następujący konstruktor:
FileReader(String nazwaPliku) | otwiera strumień odczytu z podanego pliku. W przypadku niepowodzenia operacji zgłasza wyjątek. |
Klasa FileReader posiada szereg metod służących do odczytu z pliku, które odziedziczyła po klasie Reader. Aby odczytać wiersze tekstu z pliku tekstowego, zaleca się użycie klasy BufferedReader wraz z następującym konstruktorem:
BufferedReader(Reader in) | otwiera buforowany strumień odczytu z strumienia wejściowego typu in. Strumień ten, typu Reader, może pochodzić z klawiatury, pliku, sieci itp. |
Przydatne metody klasy BufferedReader to:
int read() | odczytuje znak |
String readLine() | odczytuje wiersz tekstu |
int read(char[] buffer, int offset, int taille) | odczytuje taille znaków z pliku i umieszcza je w tablicy buffer, zaczynając od pozycji offset. |
void close() | zamyka strumień odczytu |
Oto program, który odczytuje zawartość utworzonego wcześniej pliku:
// zaimportowane klasy
import java.util.*;
import java.io.*;
public class lire{
public static void main(String[] arg){
personne p=null;
// otwarcie pliku
BufferedReader IN=null;
try{
IN=new BufferedReader(new FileReader("out"));
} catch (Exception e){
Erreur(e,1);
}
// dane
String ligne=null;
String[] champs=null;
String prenom=null;
String nom=null;
int age=0;
// obsługa ewentualnych błędów
try{
while((ligne=IN.readLine())!=null){
champs=ligne.split(",");
prenom=champs[0];
nom=champs[1];
age=Integer.parseInt(champs[2]);
System.out.println(""+new personne(prenom,nom,age));
}// koniec pętli while
} catch (Exception e){
Erreur(e,2);
}
// zamknięcie pliku
try{
IN.close();
} catch (Exception e){
Erreur(e,3);
}
}// koniec funkcji main
// Błąd
public static void Erreur(Exception e, int code){
System.err.println("Erreur : "+e);
System.exit(code);
}
}// koniec klasy
Po uruchomieniu programu otrzymujemy następujące wyniki:
4.9.3. Zapisywanie obiektu osoby
Wykorzystujemy to, co właśnie omówiliśmy, aby zapewnić klasie personne metodę umożliwiającą zapisanie atrybutów osoby w pliku. Dodajemy metodę sauveAttributs do definicji klasy personne:
// ------------------------------
// zapis do pliku tekstowego
// ------------------------------
public void sauveAttributs(PrintWriter P){
P.println(""+this);
}
Przed zdefiniowaniem klasy personne należy pamiętać o zaimportowaniu pakietu java.io:
Metoda sauveAttributs przyjmuje jako jedyny parametr strumień PrintWriter, do którego ma zapisywać dane. Program testowy mógłby wyglądać następująco:
// importy
import java.io.*;
// import osoby;
public class sauver{
public static void main(String[] arg){
// otwarcie pliku
PrintWriter fic=null;
try{
fic=new PrintWriter(new FileWriter("out"));
} catch (Exception e){
Erreur(e,1);
}
// zapis do pliku
try{
new personne("Jean","Dupont",27).sauveAttributs(fic);
new personne("Pauline","Garcia",24).sauveAttributs(fic);
new personne("Gilles","Dumond",56).sauveAttributs(fic);
} catch (Exception e){
Erreur(e,3);
}
// zamknięcie pliku
try{
fic.close();
} catch (Exception e){
Erreur(e,2);
}
}// koniec funkcji main
// Błąd
private static void Erreur(Exception e, int code){
System.err.println("Erreur : "+e);
System.exit(code);
}//Błąd
}//klasa
Skompilujmy i uruchommy ten program:
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\sauveAttributs>javac sauver.java
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\sauveAttributs>dir
10/06/2002 10:52 1 352 personne.class
10/06/2002 10:53 842 sauver.java
10/06/2002 10:53 1 258 sauver.class
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\sauveAttributs>java sauver
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\sauveAttributs>dir
10/06/2002 10:52 1 352 personne.class
10/06/2002 10:53 842 sauver.java
10/06/2002 10:53 1 258 sauver.class
10/06/2002 10:53 83 out
E:\data\serge\JAVA\poly juin 2002\Chapitre 3\sauveAttributs>more out
personne(Jean,Dupont,27)
personne(Pauline,Garcia,24)
personne(Gilles,Dumond,56)
4.10. Pliki binarne
4.10.1. Klasa RandomAccessFile
Klasa RandomAccessFile umożliwia zarządzanie plikami binarnymi, w szczególności tymi o stałej strukturze, znanymi z języków C/C++. Oto kilka przydatnych metod i konstruktorów:
RandomAccessFile(String nazwaPliku, String tryb) | konstruktor – otwiera wskazany plik w określonym trybie. Tryb ten przyjmuje następujące wartości: r: otwarcie do odczytu rw: otwarcie do odczytu i zapisu |
void writeTTT(TTT wartość) | zapisuje wartość do pliku. TTT określa typ wartości. Reprezentacja pamięciowa wartości jest zapisywana w pliku w niezmienionej postaci. W ten sposób występują: writeBoolean, writeByte, writeInt, writeDouble, writeLong, writeFloat, ... Aby zapisać ciąg znaków, używa się writeBytes(String chaine). |
TTT readTTT() | odczytuje i zwraca wartość typu TTT. Znajdujemy zatem readBoolean, readByte, readInt, readDouble, readLong, readFloat, ... Metoda read() odczytuje jeden bajt. |
long length() | rozmiar pliku w bajtach |
long getFilePointer() | aktualna pozycja wskaźnika pliku |
void seek(long pos) | ustawia kursor pliku na bajt o pozycji pos |
4.10.2. Klasa artykułu
Wszystkie poniższe przykłady będą wykorzystywać następującą klasę article:
// struktura artykułu
private static class article{
// definiujemy strukturę
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//klasa artykułu
Powyższa klasa Java article będzie odpowiadać następującej strukturze article w języku C
struct article{
char code[4];
char nom[20];
double prix;
int stockActuel;
int stockMinimum;
}//struktura
W ten sposób ograniczymy długość kodu do 4 znaków, a nazwy do 20.
4.10.3. Zapisanie rekordu
Poniższy program zapisuje wpis do pliku o nazwie „data”:
// klasy importowane
import java.io.*;
public class test1{
// testuje zapis struktury (w rozumieniu języka C) w pliku binarnym
// struktura artykułu
private static class article{
// definiujemy strukturę
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//klasa „article”
public static void main(String arg[]){
// definiujemy plik binarny, w którym będą przechowywane artykuły
RandomAccessFile fic=null;
// definiuje się artykuł
article art=new article();
art.code="a100";
art.nom="velo";
art.prix=1000.80;
art.stockActuel=100;
art.stockMinimum=10;
// definiuje się plik
try{
fic=new RandomAccessFile("data","rw");
} catch (Exception E){
erreur("Impossible d'ouvrir le fichier data",1);
}//try-catch
// zapisujemy
try{
ecrire(fic,art);
} catch (IOException E){
erreur("Erreur lors de l'écriture de l'enregistrement",2);
}//try-catch
// koniec
try{
fic.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data",2);
}//try-catch
}//main
// metoda zapisu
public static void ecrire(RandomAccessFile fic, article art) throws IOException{
// kod
fic.writeBytes(art.code);
// nazwa ograniczona do 20 znaków
art.nom=art.nom.trim();
int l=art.nom.length();
int nbBlancs=20-l;
if(nbBlancs>0){
String blancs="";
for(int i=0;i<nbBlancs;i++) blancs+=" ";
art.nom+=blancs;
} else art.nom=art.nom.substring(0,20);
fic.writeBytes(art.nom);
// cena
fic.writeDouble(art.prix);
// stan magazynowy
fic.writeInt(art.stockActuel);
fic.writeInt(art.stockMinimum);
}// koniec zapisu
// ------------------------błąd
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// koniec błędu
}// koniec klasy
Poniższy program pozwala nam sprawdzić, czy operacja została poprawnie wykonana.
4.10.4. Odczytanie rekordu
// zaimportowane klasy
import java.io.*;
public class test2{
// testuje zapis struktury (w rozumieniu języka C) w pliku binarnym
// struktura article
private static class article{
// definiujemy strukturę
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//klasa „article”
public static void main(String arg[]){
// definiujemy plik binarny, w którym będą przechowywane artykuły
RandomAccessFile fic=null;
// otwieramy plik do odczytu
try{
fic=new RandomAccessFile("data","r");
} catch (Exception E){
erreur("Impossible d'ouvrir le fichier data",1);
}//try-catch
// odczytujemy pojedynczy artykuł z pliku
article art=new article();
try{
lire(fic,art);
} catch (IOException E){
erreur("Erreur lors de la lecture de l'enregistrement",2);
}//try-catch
// wyświetlamy odczytany rekord
affiche(art);
// koniec
try{
fic.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data",2);
}//try-catch
}// koniec ręcznego wprowadzania
// metoda odczytu
public static void lire(RandomAccessFile fic, article art) throws IOException{
// odczyt kodu
art.code="";
for(int i=0;i<4;i++) art.code+=(char)fic.readByte();
// nazwa
art.nom="";
for(int i=0;i<20;i++) art.nom+=(char)fic.readByte();
art.nom=art.nom.trim();
// cena
art.prix=fic.readDouble();
// stan magazynowy
art.stockActuel=fic.readInt();
art.stockMinimum=fic.readInt();
}// koniec zapisu
// ---------------------wyświetla
public static void affiche(article art){
System.out.println("code : "+art.code);
System.out.println("nom : "+art.nom);
System.out.println("prix : "+art.prix);
System.out.println("Stock actuel : "+art.stockActuel);
System.out.println("Stock minimum : "+art.stockMinimum);
}// koniec wyświetlania
// ------------------------błąd
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// koniec błędu
}// koniec klasy
Wyniki wykonania są następujące:
E:\data\serge\JAVA\random>java test2
code : a100
nom : velo
prix : 1000.8
Stock actuel : 100
Stock minimum : 10
Pobieramy poprawnie rekord, który został zapisany przez program zapisujący.
4.10.5. Konwersja tekstu na format binarny
Poniższy program stanowi rozszerzenie programu do zapisywania pojedynczego rekordu. Teraz zapisujemy kilka rekordów do pliku binarnego o nazwie data.bin. Dane pochodzą z następującego pliku data.txt:
E:\data\serge\JAVA\random>more data.txt
a100:velo:1000:100:10
b100:pompe:65:6:2
c100:arc:867:10:5
d100:fleches - lot de 6:450:12:8
e100:jouet:10:2:3
// zaimportowane klasy
import java.io.*;
import java.util.*;
public class test3{
// plik tekstowy --> plik binarny
// struktura artykułu
private static class article{
// definiujemy strukturę
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//klasa artykułu
public static void main(String arg[]){
// definiuje się plik binarny, w którym będą przechowywane artykuły
RandomAccessFile dataBin=null;
try{
dataBin=new RandomAccessFile("data.bin","rw");
} catch (Exception E){
erreur("Impossible d'ouvrir le fichier data.bin",1);
}
// dane są pobierane z pliku tekstowego
BufferedReader dataTxt=null;
try{
dataTxt=new BufferedReader(new FileReader("data.txt"));
} catch (IOException E){
erreur("Impossible d'ouvrir le fichier data.txt",2);
}
// plik .txt --> plik .bin
String ligne=null;
String[] champs=null;
int numLigne=0;
String champ=null;
article art=new article(); // artykuł do utworzenia
try{
while((ligne=dataTxt.readLine())!=null){
// wiersz z znakiem „+”
numLigne++;
// podział na pola
champs=ligne.split(":");
// potrzeba 5 pól
if(champs.length!=5)
erreur("Ligne "+numLigne+" erronée dans data.txt",3);
//kod
art.code=champs[0];
if(art.code.length()!=4)
erreur("Code erroné en ligne "+numLigne+" du fichier data.txt",12);
// nazwisko, imię
art.nom=champs[1];
// cena
try{
art.prix=Double.parseDouble(champs[2]);
} catch (Exception E){
erreur("Prix erroné en ligne "+numLigne+" du fichier data.txt",4);
}
// aktualny stan magazynowy
try{
art.stockActuel=Integer.parseInt(champs[3]);
} catch (Exception E){
erreur("Stock actuel erroné en ligne "+ numLigne + " du fichier data.txt",5);
}
// aktualny stan magazynowy
try{
art.stockActuel=Integer.parseInt(champs[3]);
} catch (Exception E){
erreur("Stock actuel erroné en ligne "+ numLigne + " du fichier data.txt",5);
}
// zapisujemy rekord
try{
ecrire(dataBin,art);
} catch (IOException E){
erreur("Erreur lors de l'écriture de l'enregistrement "+numLigne,7);
}
// przechodzimy do następnego wiersza
}// koniec pętli while
} catch (IOException E){
erreur("Erreur lors de la lecture du fichier data.txt après la ligne "+numLigne,8);
}
// koniec
try{
dataBin.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.bin",10);
}
try{
dataTxt.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.txt",11);
}
}// koniec funkcji main
// metoda zapisu
public static void ecrire(RandomAccessFile fic, article art) throws IOException{
// kod
fic.writeBytes(art.code);
// nazwa ograniczona do 20 znaków
art.nom=art.nom.trim();
int l=art.nom.length();
int nbBlancs=20-l;
if(nbBlancs>0){
String blancs="";
for(int i=0;i<nbBlancs;i++) blancs+=" ";
art.nom+=blancs;
} else art.nom=art.nom.substring(0,20);
fic.writeBytes(art.nom);
// cena
fic.writeDouble(art.prix);
// stany magazynowe
fic.writeInt(art.stockActuel);
fic.writeInt(art.stockMinimum);
}// koniec zapisu
// ------------------------błąd
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// koniec błędu
}// koniec klasy
Poniższy program pozwala sprawdzić, czy poprzedni program zadziałał poprawnie.
4.10.6. Konwersja z formatu binarnego na tekst
Poniższy program odczytuje zawartość utworzonego wcześniej pliku binarnego data.bin i zapisuje ją w pliku tekstowym data.text. Jeśli wszystko przebiegnie pomyślnie, plik data.text powinien być identyczny z oryginalnym plikiem data.txt.
// zaimportowane klasy
import java.io.*;
import java.util.*;
public class test5{
// plik tekstowy --> plik binarny
// struktura artykułu
private static class article{
// definiujemy strukturę
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//klasa artykułu
// main
public static void main(String arg[]){
// definiujemy plik binarny, w którym będą przechowywane artykuły
RandomAccessFile dataBin=null;
try{
dataBin=new RandomAccessFile("data.bin","r");
} catch (Exception E){
erreur("Impossible d'ouvrir le fichier data.bin en lecture",1);
}
// dane są zapisywane w pliku tekstowym
PrintWriter dataTxt=null;
try{
dataTxt=new PrintWriter(new FileWriter("data.text"));
} catch (IOException E){
erreur("Impossible d'ouvrir le fichier data.text en écriture",2);
}
// plik .bin --> plik .text
article art=new article(); // artykuł do utworzenia
// przetwarzamy plik binarny
int numRecord=0;
long l=0; // rozmiar pliku
try{
l=dataBin.length();
} catch (IOException e){
erreur("Erreur lors du calcul de la longueur du fichier data.bin",2);
}
long pos=0; // bieżąca pozycja w pliku
try{
pos=dataBin.getFilePointer();
} catch (IOException e){
erreur("Erreur lors de la lecture de la position courante dans data.bin",2);
}
// dopóki nie przekroczono końca pliku
while(pos<l){
// odczyt bieżącego rekordu i przetworzenie go
numRecord++;
try{
lire(dataBin,art);
} catch (Exception e){
erreur("Erreur lors de la lecture de l'enregistrement "+numRecord,2);
}
affiche(art);
// zapis odpowiedniego wiersza tekstu w dataTxt
dataTxt.println(art.code.trim()+":"+art.nom.trim()+":"+art.prix+":"+art.stockActuel+":"+art.stockMinimum);
// kontynuować?
try{
pos=dataBin.getFilePointer();
} catch (IOException e){
erreur("Erreur lors de la lecture de la position courante dans data.bin",2);
}
}// koniec pętli while
// koniec
try{
dataBin.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.bin",2);
}
try{
dataTxt.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.text",2);
}
}// koniec funkcji main
// metoda odczytu
public static void lire(RandomAccessFile fic, article art) throws IOException{
// odczyt kodu
art.code="";
for(int i=0;i<4;i++) art.code+=(char)fic.readByte();
// nazwa
art.nom="";
for(int i=0;i<20;i++) art.nom+=(char)fic.readByte();
art.nom=art.nom.trim();
// cena
art.prix=fic.readDouble();
// zapas
art.stockActuel=fic.readInt();
art.stockMinimum=fic.readInt();
}// koniec zapisu
// ---------------------wyświetla
public static void affiche(article art){
System.out.println("code : "+art.code);
System.out.println("nom : "+art.nom);
System.out.println("prix : "+art.prix);
System.out.println("Stock actuel : "+art.stockActuel);
System.out.println("Stock minimum : "+art.stockMinimum);
}// koniec wyświetlania
// ------------------------błąd
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// koniec błędu
}// koniec klasy
Oto przykładowe wyniki działania:
E:\data\serge\JAVA\random>java test5
code : a100
nom : velo
prix : 1000.0
Stock actuel : 100
Stock minimum : 0
code : b100
nom : pompe
prix : 65.0
Stock actuel : 6
Stock minimum : 0
code : c100
nom : arc
prix : 867.0
Stock actuel : 10
Stock minimum : 0
code : d100
nom : fleches - lot de 6
prix : 450.0
Stock actuel : 12
Stock minimum : 0
code : e100
nom : jouet
prix : 10.0
Stock actuel : 2
Stock minimum : 0
E:\data\serge\JAVA\random>more data.text
a100:velo:1000.0:100:0
b100:pompe:65.0:6:0
c100:arc:867.0:10:0
d100:fleches - lot de 6:450.0:12:0
e100:jouet:10.0:2:0
4.10.7. Bezpośredni dostęp do rekordów
Ten ostatni program ilustruje możliwość bezpośredniego dostępu do rekordów w pliku binarnym. Wyświetla on rekord z pliku data.bin, którego numer jest przekazywany jako parametr, przy czym pierwszy rekord ma numer 1.
// zaimportowane klasy
import java.io.*;
import java.util.*;
public class test6{
// plik tekstowy --> plik binarny
// struktura artykułu
private static class article{
// definiujemy strukturę
public String code;
public String nom;
public double prix;
public int stockActuel;
public int stockMinimum;
}//klasa artykułu
// main
public static void main(String[] args){
// sprawdzamy argumenty
int nbArguments=args.length;
String syntaxe="syntaxe : pg numéro_de_fiche";
if(nbArguments!=1)
erreur(syntaxe,20);
// sprawdzanie numeru karty
int numRecord=0;
try{
numRecord=Integer.parseInt(args[0]);
} catch(Exception e){
erreur(syntaxe+"\nNuméro de fiche incorrect",21);
}
// otwieramy plik binarny w trybie odczytu
RandomAccessFile dataBin=null;
try{
dataBin=new RandomAccessFile("data.bin","r");
} catch (Exception E){
erreur("Impossible d'ouvrir le fichier data.bin en lecture",1);
}
// przechodzimy do żądanego rekordu
try{
dataBin.seek((numRecord-1)*40);
} catch (Exception e){
erreur("La fiche "+numRecord+" n'existe pas",23);
}
// odczytuje się ją
article art=new article();
try{
lire(dataBin,art);
} catch (Exception e){
erreur("Erreur lors de la lecture de l'enregistrement "+numRecord,2);
}
// wyświetla się
affiche(art);
// koniec
try{
dataBin.close();
} catch (Exception E){
erreur("Impossible de fermer le fichier data.bin",2);
}//try-catch
}// koniec funkcji main
// metoda odczytu
public static void lire(RandomAccessFile fic, article art) throws IOException{
// odczyt kodu
art.code="";
for(int i=0;i<4;i++) art.code+=(char)fic.readByte();
// nazwa
art.nom="";
for(int i=0;i<20;i++) art.nom+=(char)fic.readByte();
art.nom=art.nom.trim();
// cena
art.prix=fic.readDouble();
// zapas
art.stockActuel=fic.readInt();
art.stockMinimum=fic.readInt();
}// koniec zapisu
// ---------------------wyświetla
public static void affiche(article art){
System.out.println("code : "+art.code);
System.out.println("nom : "+art.nom);
System.out.println("prix : "+art.prix);
System.out.println("Stock actuel : "+art.stockActuel);
System.out.println("Stock minimum : "+art.stockMinimum);
}// koniec wyświetlania
// ------------------------błąd
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// koniec błędu
}// koniec klasy
Oto przykłady wyników:
E:\data\serge\JAVA\random>java test6 2
code : b100
nom : pompe
prix : 65.0
Stock actuel : 6
Stock minimum : 0
E:\data\serge\JAVA\random>java.bat test6 20
Erreur lors de la lecture de l'enregistrement 20
4.11. Korzystanie z wyrażeń regularnych
4.11.1. Pakiet java.util.regex
Pakiet java.util.regex umożliwia stosowanie wyrażeń regularnych. Pozwalają one na sprawdzanie formatu ciągu znaków. Dzięki temu można zweryfikować, czy ciąg znaków reprezentujący datę ma format dd/mm/rr. W tym celu stosuje się wzorzec i porównuje się ciąg znaków z tym wzorcem. W tym przykładzie znaki j, m i a muszą być cyframi. Szablon prawidłowego formatu daty to zatem „\d\d/\d\d/\d\d”, gdzie symbol \d oznacza cyfrę. Symbole, których można używać w szablonie, są następujące (dokumentacja Microsoft):
Znak | Opis |
\ | Oznacza następujący znak jako znak specjalny lub literał. Na przykład „n” odpowiada znakowi „n”. „\n” odpowiada znakowi nowego wiersza. Sekwencja „\\” odpowiada znakowi „\”, natomiast „\(" odpowiada znakowi „(”. |
^ | Odpowiada początkowi wprowadzania tekstu. |
$ | Odpowiada końcowi wpisu. |
* | Odpowiada poprzedniemu znakowi zero lub więcej razy. Tak więc „zo*” odpowiada „z” lub „zoo”. |
+ | Odpowiada poprzedniemu znakowi jeden lub więcej razy. Na przykład „zo+” odpowiada „zoo”, ale nie odpowiada „z”. |
? | Odpowiada poprzedniemu znakowi zero lub jeden raz. Na przykład „a?ve?” odpowiada „ve” w słowie „lever”. |
. | Odpowiada dowolnemu pojedynczemu znakowi, z wyjątkiem znaku nowej linii. |
(wzór) | Wyszukuje ciąg modèle i zapisuje dopasowanie. Odpowiedni podciąg można wyodrębnić z uzyskanej kolekcji Matches za pomocą elementu [0]...[n]. Aby znaleźć dopasowania zawierające znaki w nawiasach ( ), należy użyć znaków „\(" lub „\)”. |
x|y | Odpowiada albo x, albo y. Na przykład „z|foot" correspond à "z" ou à "foot". "(z|f)oo” odpowiada „zoo” lub „foo”. |
{n} | n jest liczbą całkowitą nieujemną. Odpowiada dokładnie n razy danemu znakowi. Na przykład „o{2}” nie odpowiada „o” w „Bob”, ale dwóm pierwszym „o” w „fooooot”. |
{n,} | n jest liczbą całkowitą nieujemną. Odpowiada co najmniej n-krotności danego znaku. Na przykład „o{2,}” nie odpowiada literze „o” w słowie „Bob”, ale wszystkim literom „o” w słowie „fooooot”. „o{1,}” jest równoważne „o+”, a „o{0,}” jest równoważne „o*”. |
{n,m} | m i n są liczbami całkowitymi nieujemnymi. Odpowiada co najmniej n i co najwyżej m wystąpieniom znaku. Na przykład „o{1,3}” odpowiada trzem pierwszym „o” w słowie „foooooot”, a „o{0,1}” odpowiada „o?”. |
[xyz] | Zbiór znaków. Odpowiada jednemu z podanych znaków. Na przykład „[abc]” odpowiada znakowi „a” w słowie „plat”. |
[^xyz] | Zbiór znaków ujemnych. Odpowiada każdemu znakowi, który nie został wymieniony. Na przykład „[^abc]” odpowiada literze „p” w słowie „plat”. |
[a-z] | Zakres znaków. Odpowiada dowolnemu znakowi z podanego zakresu. Na przykład „[a-z]” odpowiada dowolnemu małej literze alfabetu od „a” do „z”. |
[^m-z] | Zakres znaków ujemny. Odpowiada dowolnemu znakowi, który nie występuje w podanej serii. Na przykład „[^m-z]” odpowiada dowolnemu znakowi, który nie występuje między „m” a „z”. |
\b | Odpowiada granicy wyznaczającej słowo, czyli pozycji między słowem a spacją. Na przykład „er\b” odpowiada „er” w słowie „lever”, ale nie odpowiada „er” w słowie „verbe”. |
\B | Odpowiada granicy, która nie reprezentuje słowa. „en*t\B” odpowiada „ent” w słowie „bien entendu”. |
\d | Odpowiada znakowi reprezentującemu cyfrę. Odpowiada [0-9]. |
\D | Odpowiada znakowi, który nie jest cyfrą. Odpowiada ciągowi [^0-9]. |
\f | Odpowiada znakowi końca strony. |
\n | Odpowiada znakowi nowego wiersza. |
\r | Odpowiada znakowi powrotu karetki. |
\s | Odpowiada dowolnemu znakowi spacji, w tym spacji, tabulacji, znakowi końca strony itp. Odpowiada ciągowi znaków „[ \f\n\r\t\v]”. |
\S | Odpowiada dowolnemu znakowi niebędącemu spacją. Odpowiada „[^ \f\n\r\t\v]”. |
\t | Odpowiada znakowi tabulacji. |
\v | Odpowiada znakowi pionowego tabulacji. |
\w | Odpowiada dowolnemu znakowi reprezentującemu słowo, w tym znakowi podkreślenia. Odpowiada ciągowi znaków „[A-Za-z0-9_]”. |
\W | Odpowiada dowolnemu znakowi, który nie stanowi słowa. Odpowiada ciągowi znaków „[^A-Za-z0-9_]”. |
\num | Odpowiada num, gdzie num jest dodatnią liczbą całkowitą. Odnosi się do zapisanych reguł dopasowania. Na przykład „(.)\1” odpowiada dwóm identycznym, następującym po sobie znakom. |
|
Element w szablonie może występować w jednej lub wielu instancjach. Rozważmy kilka przykładów dotyczących symbolu \d, który reprezentuje jedną cyfrę:
szablon | znaczenie |
\d | cyfra |
\d? | 0 lub 1 cyfra |
\d* | 0 lub więcej cyfr |
\d+ | 1 lub więcej cyfr |
\d{2} | 2 cyfry |
\d{3,} | co najmniej 3 cyfry |
\d{5,7} | od 5 do 7 cyfr |
Wyobraźmy sobie teraz wzorzec, który może opisać oczekiwany format ciągu znaków:
poszukiwany ciąg znaków | model |
data w formacie dd/mm/rr | \d{2}/\d{2}/\d{2} |
godzina w formacie hh:mm:ss | \d{2}:\d{2}:\d{2} |
liczba całkowita bez znaku | \d+ |
sekwencja spacji, która może być pusta | \s* |
liczba całkowita bez znaku, przed którą lub po której mogą występować spacje | \s*\d+\s* |
liczba całkowita, która może być ze znakiem i może być poprzedzona lub następować po spacjach | \s*[+|-]?\s*\d+\s* |
liczba rzeczywista bez znaku, przed którą lub po której mogą występować spacje | \s*\d+(.\d*)?\s* |
liczba rzeczywista, która może być ze znakiem i może być poprzedzona lub następowana przez spacje | \s*[+|]?\s*\d+(.\d*)?\s* |
ciąg znaków zawierający słowo „juste” | \bjuste\b |
Można określić, w którym miejscu ciągu ma być wyszukiwany wzorzec:
wzór | znaczenie |
^wzorzec | wzorzec rozpoczyna ciąg |
wzór$ | szablon kończy ciąg |
^wzorzec$ | szablon rozpoczyna i kończy ciąg |
wzorzec | wzorzec jest wyszukiwany w całym ciągu, zaczynając od jego początku. |
wyszukiwany ciąg | wzorzec |
ciąg kończący się wykrzyknikiem | !$ |
ciąg kończący się kropką | \.$ |
ciąg znaków rozpoczynający się sekwencją // | ^// |
ciąg zawierający tylko jedno słowo, po którym mogą następować lub przed którym mogą występować spacje | ^\s*\w+\s*$ |
ciąg zawierający dwa słowa, po których mogą następować lub przed którymi mogą występować spacje | ^\s*\w+\s*\w+\s*$ |
ciąg zawierający słowo „secret” | \bsecret\b |
Podzbiory wzorca można „wyodrębnić”. Dzięki temu nie tylko można sprawdzić, czy ciąg znaków odpowiada konkretnemu wzorcowi, ale także wyodrębnić z tego ciągu elementy odpowiadające podzbiorom wzorca, które zostały ujęte w nawiasy. Jeśli więc analizujemy ciąg zawierający datę dd/mm/rr i chcemy dodatkowo wyodrębnić elementy dd, mm, rr z tej daty, użyjemy wzorca (\d\d)/(\d\d)/(\d\d).
4.11.2. Sprawdzanie, czy ciąg znaków odpowiada podanemu wzorcowi
Klasa Pattern umożliwia sprawdzenie, czy ciąg znaków odpowiada podanemu wzorcowi. W tym celu stosuje się metodę statyczną
gdzie: modèle – wzorzec do sprawdzenia, chaine – ciąg znaków do porównania ze wzorcem. Wynikiem jest wartość logiczna true, jeśli ciąg znaków odpowiada wzorcowi, a false w przeciwnym razie.
Oto przykład:
import java.io.*;
import java.util.regex.*;
// obsługa wyrażeń regularnych
public class regex1 {
public static void main(String[] args){
// wyrażenie regularne jako wzorzec
String modèle1="^\\s*\\d+\\s*$";
// porównanie wzorca z przykładem
String exemplaire1=" 123 ";
if (Pattern.matches(modèle1,exemplaire1)){
affiche("["+exemplaire1 + "] correspond au modèle ["+modèle1+"]");
}else{
affiche("["+exemplaire1 + "] ne correspond pas au modèle ["+modèle1+"]");
}//if
String exemplaire2=" 123a ";
if (Pattern.matches(modèle1,exemplaire2)){
affiche("["+exemplaire2 + "] correspond au modèle ["+modèle1+"]");
}else{
affiche("["+exemplaire2 + "] ne correspond pas au modèle ["+modèle1+"]");
}//if
}//main
public static void affiche(String msg){
System.out.println(msg);
}//wyświetla
}//klasa
oraz wyniki wykonania:
Należy zauważyć, że w wzorcu „^\s*\d+\s*$” znak \ musi być podwójny ze względu na specyficzną interpretację tego znaku przez język Java. Piszemy zatem: String wzorzec1 = „^\\s*\\d+\\s*$”;
4.11.3. Wyszukiwanie wszystkich elementów ciągu znaków pasujących do wzorca
Rozważmy wzorzec „\d+” oraz ciąg znaków „ 123 456 789 ”. Wzorzec ten występuje w trzech różnych miejscach ciągu. Klasy Pattern i Matcher umożliwiają pobranie poszczególnych wystąpień wzorca w ciągu znaków. Klasa Pattern jest klasą obsługującą wyrażenia regularne. Wyrażenie regularne używane więcej niż raz wymaga „kompilacji”. Przyspiesza to wyszukiwanie wzorca w ciągach znaków. Zadanie to wykonuje statyczna metoda compile:
Jako parametr przyjmuje ona ciąg znaków zawierający wzorzec i zwraca obiekt typu Pattern. Aby porównać wzorzec obiektu typu Pattern z ciągiem znaków, wykorzystuje się klasę Matcher. Klasa ta umożliwia porównanie wzorca z ciągiem znaków. Na podstawie obiektu typu Pattern można uzyskać obiekt typu Matcher za pomocą metody matcher:
input to ciąg znaków, który ma zostać porównany z wzorcem.
Aby więc porównać wzorzec „\d+” z ciągiem „ 123 456 789 ”, można utworzyć obiekt Matcher w następujący sposób:
Na podstawie poprzedniego obiektu résultats będziemy mogli pobrać różne wystąpienia wzorca w ciągu znaków. W tym celu wykorzystujemy metody suivantes klasy Matcher:
Metoda find wyszukuje w analizowanym ciągu pierwsze wystąpienie wzorca. Drugie wywołanie metody find wyszuka kolejne wystąpienie. I tak dalej. Metoda zwraca wartość true, jeśli znajdzie wzorzec, a w przeciwnym razie – false. Fragment ciągu odpowiadający ostatniemu wystąpieniu znalezionemu przez metodę find uzyskuje się za pomocą metody group, a jego pozycję – za pomocą metody start. Tak więc, kontynuując poprzedni przykład i chcąc wyświetlić wszystkie wystąpienia wzorca „\d+” w ciągu „ 123 456 789 ”, należy napisać:
while(résultats.find()){
System.out.println("séquence " + résultats.group() + " trouvée en position " + résultats.start());
}//while
Metoda reset pozwala zresetować obiekt Matcher do początku ciągu porównywanego z wzorcem. Dzięki temu metoda find ponownie znajdzie pierwsze wystąpienie wzorca.
Oto pełny przykład:
import java.io.*;
import java.util.regex.*;
// obsługa wyrażeń regularnych
public class regex2 {
public static void main(String[] args){
// wiele wystąpień wzorca w egzemplarzu
String modèle2="\\d+";
Pattern regex2=Pattern.compile(modèle2);
String exemplaire3=" 123 456 789";
// wyszukiwanie wystąpień wzorca w egzemplarzu
Matcher matcher2=regex2.matcher(exemplaire3);
while(matcher2.find()){
affiche("séquence " + matcher2.group() + " trouvée en position " + matcher2.start());
}//while
}//Główna
public static void affiche(String msg){
System.out.println(msg);
}//wyświetla
}//klasa
Wyniki wykonania:
Modèle=[\d+],exemplaire=[ 123 456 789 ]
Il y a 3 occurrences du modèle dans l'exemplaire
123 en position 2
456 en position 7
789 en position 12
4.11.4. Pobieranie części wzorca
Podzbiory wzorca można „wyodrębnić”. Dzięki temu nie tylko można sprawdzić, czy ciąg znaków odpowiada konkretnemu wzorcowi, ale także wyodrębnić z tego ciągu elementy odpowiadające podzbiorom wzorca, które zostały ujęte w nawiasy. Jeśli więc analizujemy ciąg znaków zawierający datę dd/mm/rr i chcemy dodatkowo wyodrębnić elementy dd, mm, rr z tej daty, użyjemy wzorca (\d\d)/(\d\d)/(\d\d).
Rozważmy następujący przykład:
import java.io.*;
import java.util.regex.*;
// obsługa wyrażeń regularnych
public class regex3 {
public static void main(String[] args){
// pobieranie elementów z szablonu
String modèle3="(\\d\\d):(\\d\\d):(\\d\\d)";
Pattern regex3=Pattern.compile(modèle3);
String exemplaire4="Il est 18:05:49";
// weryfikacja szablonu
Matcher résultat=regex3.matcher(exemplaire4);
if (résultat.find()){
// egzemplarz odpowiada szablonowi
affiche("L'exemplaire ["+exemplaire4+"] correspond au modèle ["+modèle3+"]");
// wyświetlanie grup
for (int i=0;i<=résultat.groupCount();i++){
affiche("groupes["+i+"]=["+résultat.group(i)+"] en position "+résultat.start(i));
}//for
}else{
// egzemplarz nie odpowiada szablonowi
affiche("L'exemplaire["+exemplaire4+" ne correspond pas au modèle ["+modèle3+"]");
}
}//Główna
public static void affiche(String msg){
System.out.println(msg);
}//wyświetla
}//klasyfikuje
Uruchomienie tego programu daje następujące wyniki:
L'exemplaire [Il est 18:05:49] correspond au modèle [(\d\d):(\d\d):(\d\d)]
groupes[0]=[18:05:49] en position 7
groupes[1]=[18] en position 7
groupes[2]=[05] en position 10
groupes[3]=[49] en position 13
Nowość znajduje się w następującym fragmencie kodu:
// weryfikacja wzorca
Matcher résultat=regex3.matcher(exemplaire4);
if (résultat.find()){
// egzemplarz odpowiada wzorcowi
affiche("L'exemplaire ["+exemplaire4+"] correspond au modèle ["+modèle3+"]");
// wyświetla się lista grup
for (int i=0;i<=résultat.groupCount();i++){
affiche("groupes["+i+"]=["+résultat.group(i)+"] en position "+résultat.start(i));
}//dla
}else{
// egzemplarz nie odpowiada szablonowi
affiche("L'exemplaire["+exemplaire4+" ne correspond pas au modèle ["+modèle3+"]");
}
Ciąg znaków exemplaire4 jest porównywany z wzorcem regex3 przy użyciu metody find. W ciągu znaków exemplaire4 znaleziono wówczas wystąpienie wzorca regex3. Jeśli wzorzec zawiera podzbiory ujęte w nawiasy, są one dostępne dzięki różnym metodom klasy Matcher:
public int groupCount()
public String group(int group)
public int start(int group)
Metoda groupCount zwraca liczbę podzbiorów znalezionych w modelu, a group(i) – podzbiór o numerze i. Znajduje się on w ciągu znaków w pozycji określonej przez start(i). Tak więc w przykładzie:
L'exemplaire [Il est 18:05:49] correspond au modèle [(\d\d):(\d\d):(\d\d)]
groupes[0]=[18:05:49] en position 7
groupes[1]=[18] en position 7
groupes[2]=[05] en position 10
groupes[3]=[49] en position 13
Pierwsze wywołanie metody find znajdzie ciąg znaków 18:05:49 i automatycznie utworzy trzy podzbiory określone przez nawiasy w szablonie, odpowiednio 18, 05 i 49.
4.11.5. Program do nauki
Znalezienie wyrażenia regularnego, które pozwoli nam sprawdzić, czy dany ciąg znaków pasuje do określonego wzorca, bywa prawdziwym wyzwaniem. Poniższy program pozwala poćwiczyć tę umiejętność. Wymaga on podania wzorca i ciągu znaków, a następnie wskazuje, czy ciąg ten pasuje do wzorca, czy nie.
import java.io.*;
import java.util.regex.*;
// obsługa wyrażeń regularnych
public class regex4 {
public static void main(String[] args){
// dane
String modèle=null,chaine=null;
Pattern regex=null;
BufferedReader IN=null;
Matcher résultats=null;
int nbOccurrences=0;
// obsługa błędów
try{
// użytkownik jest proszony o podanie wzorców i egzemplarzy, które mają zostać porównane z danym egzemplarzem
while(true){
// strumień danych wejściowych
IN=new BufferedReader(new InputStreamReader(System.in));
// wyświetla się prośba o podanie szablonu
System.out.print("Tapez le modèle à tester ou fin pour arrêter :");
modèle=IN.readLine();
// zakończone?
if(modèle.trim().toLowerCase().equals("fin")) break;
// tworzy się wyrażenie regularne
regex=Pattern.compile(modèle);
// pyta się użytkownika o egzemplarze do porównania z wzorcem
while(true){
System.out.print("Tapez la chaîne à comparer au modèle ["+modèle+"] ou fin pour arrêter :");
chaine=IN.readLine();
// zakończone?
if(chaine.trim().toLowerCase().equals("fin")) break;
// tworzymy obiekt dopasowujący
résultats=regex.matcher(chaine);
// wyszukuje się wystąpienia wzorca
nbOccurrences=0;
while(résultats.find()){
// znaleziono wystąpienie
nbOccurrences++;
// wyświetla się
System.out.println("J'ai trouvé la correspondance ["+résultats.group()
+"] en position "+résultats.start());
// wyświetlanie podelementów
if(résultats.groupCount()!=1){
for(int j=1;j<=résultats.groupCount();j++){
System.out.println("\tsous-élément ["+résultats.group(j)+"] en position "+
résultats.start(j));
}//dla j
}//if
// następny ciąg
}//while(résultats.find())
// czy znaleziono co najmniej jedno wystąpienie?
if(nbOccurrences==0){
System.out.println("Je n'ai pas trouvé de correspondance au modèle ["+modèle+"]");
}//if
// następny wzorzec
}//while(true)
}//while(true)
}catch(Exception ex){
// błąd
System.err.println("Erreur : "+ex.getMessage());
// koniec z błędem
System.exit(1);
}//try-catch
// koniec
System.exit(0);
}//Main
}//klasa
Oto przykładowe wyniki:
Tapez le modèle à tester ou fin pour arrêter :\d+
Tapez la chaîne à comparer au modèle [\d+] ou fin pour arrêter :123 456 789
J'ai trouvé la correspondance [123] en position 0
J'ai trouvé la correspondance [456] en position 4
J'ai trouvé la correspondance [789] en position 8
Tapez la chaîne à comparer au modèle [\d+] ou fin pour arrêter :fin
Tapez le modèle à tester ou fin pour arrêter :(\d\d):(\d\d)
Tapez la chaîne à comparer au modèle [(\d\d):(\d\d)] ou fin pour arrêter :14:15
abcd 17:18 xyzt
J'ai trouvé la correspondance [14:15] en position 0
sous-élément [14] en position 0
sous-élément [15] en position 3
J'ai trouvé la correspondance [17:18] en position 11
sous-élément [17] en position 11
sous-élément [18] en position 14
Tapez la chaîne à comparer au modèle [(\d\d):(\d\d)] ou fin pour arrêter :fin
Tapez le modèle à tester ou fin pour arrêter :^\s*\d+\s*$
Tapez la chaîne à comparer au modèle [^\s*\d+\s*$] ou fin pour arrêter : 1456
J'ai trouvé la correspondance [ 1456] en position 0
Tapez la chaîne à comparer au modèle [^\s*\d+\s*$] ou fin pour arrêter :fin
Tapez le modèle à tester ou fin pour arrêter :^\s*(\d+)\s*$
Tapez la chaîne à comparer au modèle [^\s*(\d+)\s*$] ou fin pour arrêter :1456
J'ai trouvé la correspondance [1456] en position 0
sous-élément [1456] en position 0
Tapez la chaîne à comparer au modèle [^\s*(\d+)\s*$] ou fin pour arrêter :abcd 1
456
Je n'ai pas trouvé de correspondances
Tapez la chaîne à comparer au modèle [^\s*(\d+)\s*$] ou fin pour arrêter :fin
Tapez le modèle à tester ou fin pour arrêter :fin
4.11.6. Metoda split klasy Pattern
Rozważmy ciąg znaków składający się z pól oddzielonych ciągiem rozdzielającym wyrażonym za pomocą wyrażenia regularnego. Na przykład, jeśli pola są oddzielone znakiem , poprzedzonym lub następującym po dowolnej liczbie spacji, wyrażenie regularne modelujące ciąg rozdzielający pola brzmiałoby „\s*,\s*”. Metoda split klasy Pattern pozwala nam pobrać pola do tablicy:
public String[] split(CharSequence input)
Ciąg input jest rozkładany na pola, które są oddzielone separatorem odpowiadającym wzorcowi bieżącego obiektu Pattern. Aby pobrać pola z wiersza, w którym separatorem pól jest przecinek poprzedzony lub następujący po dowolnej liczbie spacji, należy wpisać:
// jedna linia
String ligne="abc ,, def , ghi";
// szablon
Pattern modèle=Pattern.compile("\\s*,\\s*");
// podział wiersza na pola
String[] champs=modèle.split(ligne);
Ten sam wynik można uzyskać za pomocą metody split z klasy String:
public String[] split(String regex)
Oto program testowy:
import java.io.*;
import java.util.regex.*;
// obsługa wyrażeń regularnych
public class split1 {
public static void main(String[] args){
// jedna linia
String ligne="abc ,, def , ghi";
// szablon
Pattern modèle=Pattern.compile("\\s*,\\s*");
// podział wiersza na pola
String[] champs=modèle.split(ligne);
// wyświetlanie
for(int i=0;i<champs.length;i++){
System.out.println("champs["+i+"]=["+champs[i]+"]");
}//for
// inny sposób wykonania
champs=ligne.split("\\s*,\\s*");
// wyświetlanie
for(int i=0;i<champs.length;i++){
System.out.println("champs["+i+"]=["+champs[i]+"]");
}//for
}//Strona główna
}//klasa
Wyniki wykonania:
champs[0]=[abc]
champs[1]=[]
champs[2]=[def]
champs[3]=[ghi]
champs[0]=[abc]
champs[1]=[]
champs[2]=[def]
champs[3]=[ghi]
4.12. Ćwiczenia
4.12.1. Ćwiczenie 1
W systemie Unix programy są często wywoływane w następujący sposób:
$ pg -o1 v1 v2 ... -o2 v3 v4 …
gdzie -oi oznacza opcję, a vi – wartość powiązaną z tą opcją. Chcemy utworzyć klasę options, która umożliwiłaby analizę ciągu argumentów -o1 v1 v2 ... -o2 v3 v4 … w celu skonstruowania następujących elementów:
optionsValides | słownik (Hashtable), którego klucze stanowią prawidłowe opcje oi. Wartością powiązaną z kluczem oi jest wektor (Vector), którego elementami są wartości v1 v2 … powiązane z opcją -oi |
optionsInvalides | słownik (Hashtable), którego kluczami są nieprawidłowe opcje oi. Wartością powiązaną z kluczem oi jest wektor (Vector), którego elementami są wartości v1, v2 … powiązane z opcją -oi |
optionsSans | ciąg znaków (String) zawierający listę wartości vi niepowiązanych z żadną opcją |
erreur | liczba całkowita równa 0, jeśli w wierszu argumentów nie ma błędów, w przeciwnym razie: 1: występują nieprawidłowe parametry wywołania 2: występują nieprawidłowe opcje 4: występują wartości niepowiązane z opcjami Jeśli występuje kilka rodzajów błędów, wartości te sumują się. |
Obiekt opcji można utworzyć na 4 różne sposoby:
public options (String arguments, String optionsAcceptables)
arguments | wiersz argumentów -o1 v1 v2 ... -o2 v3 v4 … do analizy |
optionsAcceptables | lista dopuszczalnych opcji oi |
Przykład wywołania: options opt=new options("-u u1 u2 u3 -g g1 g2 -x","-u -g");
W tym przypadku oba argumenty są ciągami znaków. Dopuszczalne są przypadki, w których ciągi te zostały podzielone na słowa umieszczone w tablicy ciągów znaków. Wymaga to trzech dodatkowych konstruktorów:
public options (String[] arguments, String optionsAcceptables)
public options (String arguments, String[] optionsAcceptables)
public options (String[] arguments, String[] optionsAcceptables)
Klasa options będzie miała następujący interfejs (metody dostępowe):
zwraca odwołanie do tablicy optionsValides utworzonej podczas tworzenia obiektu opcji
zwraca odwołanie do tablicy optionsInvalides utworzonej podczas tworzenia obiektu opcji
zwraca odwołanie do ciągu znaków optionsSans utworzonego podczas tworzenia obiektu opcji
zwraca wartość atrybutu „błąd” utworzoną podczas tworzenia obiektu opcji
jeśli nie ma błędu, wyświetla wartości atrybutów optionsValides, optionsInvalides, optionsSans, w przeciwnym razie wyświetla numer błędu.
Oto przykładowy program:
import java.io.*;
//opcje importu;
public class test1{
public static void main (String[] arg){
// otwarcie strumienia wejściowego
String ligne;
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
affiche(e);
System.exit(1);
}
// odczyt argumentów konstruktora options(String, string)
String options=null;
String optionsAcceptables=null;
while(true){
System.out.print("Options : ");
try{
options=IN.readLine();
} catch (Exception e){
affiche(e);
System.exit(2);
}
if(options.length()==0) break;
System.out.print("Options acceptables: ");
try{
optionsAcceptables=IN.readLine();
} catch (Exception e){
affiche(e);
System.exit(2);
}
System.out.println(new options(options,optionsAcceptables));
}// koniec pętli while
}//koniec funkcji main
public static void affiche(Exception e){
System.err.println("Erreur : "+e);
}
}//koniec klasy
Niektóre wyniki:
C:\Serge\java\options>java test1
Options : 1 2 3 -a a1 a2 -b b1 -c c1 c2 c3 -b b2 b3
Options acceptables: -a -b
Erreur 6
Options valides :(-b,b1,b2,b3) (-a,a1,a2)
Options invalides : (-c,c1,c2,c3)
Sans options : 1 2 3
4.12.2. Ćwiczenie 2
Chcemy utworzyć klasę **stringtovector**, umożliwiającą przeniesienie zawartości obiektu *String do obiektu *Vector. Klasa ta będzie pochodną klasy *Vector*:
i miałby następujący kod źródłowy:
private void stringtovector(String S, String separateur, int[] tChampsVoulus,
boolean strict){
// tworzy wektor zawierający pola ciągu S
// który składa się z pól oddzielonych separatorem
// jeśli separator = null, ciąg znaków tworzy tylko jedno pole
// pożądane są tylko te pola, których indeksy znajdują się w tablicy tChampsVoulus
// są pożądane. Indeksy zaczynają się od 1
// jeśli tChampsvoulus = null lub ma zerową długość, pobierane są wszystkie pola
// jeśli strict=prawda, wszystkie wymagane pola muszą być obecne
Klasa miałaby następujący atrybut prywatny:
Atrybut ten jest ustalany przez poprzedni konstruktor z następującymi wartościami:
0: tworzenie przebiegło pomyślnie
4: brakuje niektórych wymaganych pól, mimo że strict=true
Klasa będzie miała również dwie metody:
, która zwraca wartość prywatnego atrybutu erreur.
która wyświetla wartość obiektu w postaci (błąd, element 1, element 2, …) gdzie elementy i są elementami wektora utworzonego na podstawie ciągu znaków.
Program testowy mógłby wyglądać następująco:
import java.io.*;
//import stringtovector;
public class essai2{
public static void main(String arg[]){
int[] T1={1,3};
System.out.println(new stringtovector("a : b : c :d:e",":",T1,true).identite());
int[] T2={1,3,7};
System.out.println(new stringtovector("a : b : c :d:e",":",T2,true).identite());
int [] T3={1,4,7};
System.out.println(new stringtovector("a : b : c :d:e",":",T3,false).identite());
System.out.println(new stringtovector("a : b : c :d:e","",T1,false).identite());
System.out.println(new stringtovector("a : b : c :d:e",null,T1,false).identite());
int[] T4={1};
System.out.println(new stringtovector("a : b : c :d:e","!",T4,true).identite());
int[] T5=null;
System.out.println(new stringtovector("a : b : c :d:e",":",T5,true).identite());
System.out.println(new stringtovector("a : b : c :d:e",null,T5,true).identite());
int[] T6=new int[0];
System.out.println(new stringtovector("a : b : c :d:e","",T6,true).identite());
int[] T7={1,3,4};
System.out.println(new stringtovector("a b c d e"," ",T6,true).identite());
}
}
Wyniki:
(0,a,c)
(4,a,c)
(0,a,d)
(0,a : b : c :d:e)
(0,a : b : c :d:e)
(0,a : b : c :d:e)
(0,a,b,c,d,e)
(0,a : b : c :d:e)
(0,a : b : c :d:e)
(0,a,b,c,d,e)
Kilka wskazówek:
- Aby podzielić ciąg znaków S na pola, należy użyć metody split z klasy String.
- Umieścić pola z S w słowniku D indeksowanym według numeru pola
- Pobierz ze słownika D tylko te pola, których klucz (indeks) znajduje się w tablicy tChampsVoulus.
4.12.3. Ćwiczenie 3
Chcemy dodać do klasy stringtovector następujący konstruktor:
public stringtovector(String S, String separateur, String sChampsVoulus,boolean strict){
// tworzy wektor zawierający pola z ciągu znaków S
// który składa się z pól oddzielonych separatorem
// jeśli separator=null, ciąg znaków tworzy tylko jedno pole
// pożądane są tylko te pola, których indeksy znajdują się w sChampsVoulus
// indeksy zaczynają się od 1
// jeśli sChampsvoulus = null lub „”, pobierane są wszystkie pola
// jeśli strict=true, wszystkie wymagane pola muszą być obecne
Lista żądanych pól znajduje się zatem w ciągu znaków (String), a nie w tablicy liczb całkowitych (int[]). Prywatnemu atrybutowi klasy o nazwie erreur można przypisać nową wartość:
2: ciąg indeksów wybranych pól jest nieprawidłowy
Oto przykładowy program:
import java.io.*;
//import stringtovector;
public class essai1{
public static void main(String arg[]){
String champs=null;
System.out.println(new stringtovector("a: b :c :d:e ",":","1 3",true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",":","1 3 7",true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",":","1 4 7",false).identite());
System.out.println(new stringtovector("a: b :c :d:e ","","1 3",false).identite());
System.out.println(new stringtovector("a: b :c :d:e ",null,"1 3",false).identite());
System.out.println(new stringtovector("a: b :c :d:e ","!","1",true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",":","",true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",":",champs,true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",null,champs,true).identite());
System.out.println(new stringtovector("a: b :c :d:e ","","",true).identite());
System.out.println(new stringtovector("a: b :c :d:e ",":","1 !",true).identite());
System.out.println(new stringtovector("a b c d e "," ","1 3",false).identite());
}
}
Niektóre wyniki:
(0,a,c)
(4,a,c)
(0,a,d)
(0,a: b :c :d:e)
(0,a: b :c :d:e)
(0,a: b :c :d:e)
(0,a,b,c,d,e)
(0,a,b,c,d,e)
(0,a: b :c :d:e)
(0,a: b :c :d:e)
(2)
(0,a,c)
Kilka wskazówek:
- Należy powrócić do przypadku poprzedniego konstruktora, przenosząc pola ciągu znaków sChampsVoulus do tablicy liczb całkowitych. W tym celu należy podzielić ciąg sChampsVoulus na pola za pomocą obiektu StringTokenizer, którego atrybut countTokens poda liczbę uzyskanych pól. Następnie można utworzyć tablicę liczb całkowitych o odpowiednim rozmiarze i wypełnić ją uzyskanymi polami.
- Aby sprawdzić, czy pole jest liczbą całkowitą, należy użyć metody Integer.parseInt w celu przekształcenia pola w liczbę całkowitą oraz obsłużyć wyjątek, który zostanie wygenerowany, gdy konwersja ta będzie niemożliwa.
4.12.4. Ćwiczenie 4
Chcemy utworzyć klasę **filetovector,* umożliwiającą przeniesienie zawartości pliku tekstowego do obiektu *Vector. Klasa ta będzie pochodną klasy *Vector*:
i posiadałaby następujący konstruktor:
// --------------------- konstruktor
public filetovector(String nomFichier, String separateur, int [] tChampsVoulus,boolean strict, String tagCommentaire){
// tworzy wektor zawierający wiersze z pliku tekstowego nomFichier
// wiersze składają się z pól oddzielonych separatorem
// jeśli separator=null, wiersz składa się tylko z jednego pola
// pożądane są tylko te pola, których indeksy znajdują się w tChampsVoulus
// indeksy zaczynają się od 1
// jeśli tChampsvoulus = null lub jest puste, pobierane są wszystkie pola
// jeśli strict=prawda, wszystkie wymagane pola muszą być obecne
// jeśli tak nie jest, wiersz nie jest zapisywany, a jego indeks
// jest umieszczany wektorze lignesErronees
// puste wiersze są ignorowane
// podobnie jak wiersze zaczynające się od tagCommentaire, jeśli tagCommentaire != null
Klasa miałaby następujące atrybuty prywatne:
Atrybut erreur jest ustawiany przez poprzedni konstruktor z następującymi wartościami:
0: operacja przebiegła pomyślnie
1: nie udało się otworzyć pliku, który ma zostać przetworzony
4: brakuje niektórych wymaganych pól, mimo że strict=true
8: podczas przetwarzania pliku wystąpił błąd wejścia/wyjścia
Atrybut lignesErronees jest wektorem, którego elementami są numery błędnych wierszy w postaci ciągu znaków. Wiersz jest błędny, jeśli nie może dostarczyć wymaganych pól, mimo że strict=true.
Klasa będzie posiadała również dwie metody:
która zwraca wartość atrybutu prywatnego erreur.
który wyświetla wartość obiektu w postaci (błąd, element 1, element 2 …, (l1, l2, …)), gdzie elementy i są elementami wektora utworzonego na podstawie pliku, a li to numery wierszy zawierających błędy.
Oto przykładowy test:
import java.io.*;
//import filetovector;
public class test2{
public static void main(String arg[]){
int[] T1={1,3};
System.out.println(new filetovector("data.txt",":",T1,false,"#").identite());
System.out.println(new filetovector("data.txt",":",T1,true,"#").identite());
System.out.println(new filetovector("data.txt","",T1,false,"#").identite());
System.out.println(new filetovector("data.txt",null,T1,false,"#").identite());
int[] T2=null;
System.out.println(new filetovector("data.txt",":",T2,false,"#").identite());
System.out.println(new filetovector("data.txt",":",T2,false,"").identite());
int[] T3=new int[0];
System.out.println(new filetovector("data.txt",":",T3,false,null).identite());
}
}
Wyniki wykonania:
[0,(0,a,c) (0,1,3) (0,azerty,cvf) (0,s)]
[4,(0,a,c) (0,1,3) (0,azerty,cvf),[5]]
[0,(0,a:b:c:d:e) (0,1 :2 : 3: 4: 5) (0,azerty : 1 : cvf : fff: qqqq) (0,s)]
[0,(0,a:b:c:d:e) (0,1 :2 : 3: 4: 5) (0,azerty : 1 : cvf : fff: qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,azerty,1,cvf,fff,qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,# komentarz) (0,azerty,1,cvf,fff,qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,# komentarz) (0,azerty,1,cvf,fff,qqqq) (0,s)]
Kilka wskazówek
-
Plik tekstowy jest przetwarzany wiersz po wierszu. Wiersz jest dzielony na pola za pomocą omówionej wcześniej klasy
**stringtovector**. -
Elementy wektora utworzonego na podstawie pliku tekstowego są zatem obiektami typu stringtovector.
-
Metoda identite klasy filetovector może wykorzystywać metodę stringtovector.identite() do wyświetlania swoich elementów, a także metodę Vector.toString() do wyświetlania numerów ewentualnych błędnych wierszy.
4.12.5. Ćwiczenie 5
Chcemy dodać do klasy **filetovector** następujący konstruktor:
public filetovector(String nomFichier, String separateur, String sChampsVoulus,
boolean strict, String tagCommentaire){
// tworzy wektor zawierający wiersze z pliku tekstowego nomFichier
// wiersze składają się z pól oddzielonych separatorem
// jeśli separator = null, wiersz stanowi jedno pole
// pożądane są tylko te pola, których indeksy znajdują się w tChampsVoulus
// indeksy zaczynają się od 1
// jeśli sChampsvoulus = null lub jest puste, pobierane są wszystkie pola
// jeśli strict=prawda, wszystkie wymagane pola muszą być obecne
// jeśli tak nie jest, wiersz nie jest zapisywany, a jego indeks
// jest umieszczany wektorze lignesErronees
// puste wiersze są ignorowane
// podobnie jak wiersze zaczynające się od tagCommentaire, jeśli tagCommentaire != null
Lista indeksów żądanych pól znajduje się teraz w ciągu znaków (String) zamiast w tablicy liczb całkowitych.
Prywatny atrybut erreur może przyjmować dodatkową wartość:
2: ciąg indeksów żądanych pól jest nieprawidłowy
Oto przykładowy test:
import java.io.*;
//import filetovector;
public class test1{
public static void main(String arg[]){
System.out.println(new filetovector("data.txt",":","1 3",false,"#").identite());
System.out.println(new filetovector("data.txt",":","1 3",true,"#").identite());
System.out.println(new filetovector("data.txt","","1 3",false,"#").identite());
System.out.println(new filetovector("data.txt",null," 1 3",false,"#").identite());
String S2=null;
System.out.println(new filetovector("data.txt",":",S2,false,"#").identite());
System.out.println(new filetovector("data.txt",":",S2,false,"").identite());
String S3="";
System.out.println(new filetovector("data.txt",":",S3,false,null).identite());
}
}
Wyniki:
[0,(0,a,c) (0,1,3) (0,azerty,cvf) (0,s)][4,(0,a,c) (0,1,3) (0,azerty,cvf),[5]]
[0,(0,a:b:c:d:e) (0,1 :2 : 3: 4: 5) (0,azerty : 1 : cvf : fff: qqqq) (0,s)]
[0,(0,a:b:c:d:e) (0,1 :2 : 3: 4: 5) (0,azerty : 1 : cvf : fff: qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,azerty,1,cvf,fff,qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,# komentarz) (0,azerty,1,cvf,fff,qqqq) (0,s)]
[0,(0,a,b,c,d,e) (0,1,2,3,4,5) (0,# komentarz) (0,azerty,1,cvf,fff,qqqq) (0,s)]
Kilka wskazówek
- Przekształcimy ciąg znaków sChampsVoulus w tablicę liczb całkowitych tChampVoulus, aby powrócić do przypadku poprzedniego konstruktora.