Nizi znakov

(Strings; java.lang.String)

Niz znakov ni nič drugega kot zaporedje znakov. Kot smo se že seznanili, so znaki v svoji osnovi kodirani s predpisom UNICODE (16).

V Java so nizi znakov objekti. Javanska platforma ponuja razred String (java.lang.String) za delo s takimi objekti.

Čeprav so nizi objekti, pa smo nize znakov in nekatere operacije že doslej uporabljali, ne da bi nam Java pri tem dala čutiti, da delamo z objekti. Recimo :

System.out.println("Test test test test.").

oz.

String ss = "Test test test test";
System.out.println(ss); 

v tem primeru je zaporedje Test test test test. javanski literal oz. konstanten niz znakov.

ali pa :

int a=30, b=69;
System.out.println(" A="+a+" B="+b);

in še :

public static void main(String[] arg){ int ena = (Integer.valueOf(arg[1]) ).intValue(); int abc=999; String s1= Integer.toString(abc);

ki na zaslon izpiše zaporedje znakov kot enovito besedilo A=30 B=69. Izpisan niz se je pri tem sestavil iz besedila A=, vrednosti spremenljivke a, čemur je na koncu dodan konstante niz B= in v niz spremenjena vrednost spremenljivke b.

Vsak izmed treh primerov kaže, da je niz (string) zaporedje znakov, ki ga v obliki literala prične " in tudi konča ". Vsi znaki, ki so napisani med tema ločiloma, predstavljajo vsebino niza.

Zadnji primer kaže dve posebnosti : operator + je omogočil, da smo sestavili v enovit niz 2 niza in; v dveh primerih avtomatično pretvorbo numerične vrednosti v niz, predenj se izvede izpis celotnega sestavljenega besedila.

Čeprav se nam v zadnjem primeru mogoče zdi, da smo niz, ki smo ga izpisali, pred postopkom izpisa spreminjali, pa dejansko ni tako. Niz, ki ga enkrat ustvarimo, je v Java nespremenljiv (immutable). Kot bomo kasneje videli, to velja za objekte tipa String, ne pa tudi za druge oblike nizov, kot je npr. StringBuffer.

Vsebino niza (objekt tipa String) si poenostavljeno lahko predstavljamo kot tabelo znakov. Z upoštevanjem deklaracij:

char[] ca = {'T','e','s','t',' ','t','e','s','t'};   // tabela znakov

String sa ="Test test";                              // niz znakov z enako vsebino

sta vsebini ca in sa, kot kaže spodnja slika :

Oba, sa(String) in ca(tabela znakov) sta v svoji zasnovi objekta. Vendar lahko elemente tabele naslavljamo direktno, z indeksiranjem:

System.out.println(ca[2]); // izpiše znak s

elemente niza pa le posredno, preko posebnih metod :

System.out.println(sa.charAt(2)); // izpiše znak na poziciji 2 v nizu, pomeni s

Med obema tipoma obstaja še nekaj razlik, ki pa si jih bomo ogledali preko primerov in uporabe obstoječega znanja o tabelah v nadaljevanju.

Pa pričnimo na začetku:

- - - - - - - - - - - - - - -

Kreiranje niza znakov

Objekt tipa String lahko ustvarimo na več načinov:

a) Kreiranje niza s pomočjo konstantnega niza znakov :

poenostavljeno : String s1="Test test";

'objektno' : String s2 = new String("Test test");

zgonja načina sta enakovredna. Java dopušča, da niz naredimo na prvi način, vendar je dejansko izvajanje oz. konstrukcija niza v obeh primerih enaka.

b) Kreiranje niza iz obstočega niza znakov :
String sObstojeci = "Test test"; 
String s3 = new String(sObstojeci);  
c) Kreiranje praznega niza :

String s4 = new String();

d) Kreiranje niza s pomočjo tabele znakov:

char[] ca = {'T','e','s','t',' ','t','e','s','t'};

String s5 = new String(ca);

e) Kreiranje niza iz tabele zlogov:

byte[] ba = {82,107,108,109,32,110,111,112,113};

String s6 = new String(ba);

Izvedemo še preverbe zgornjih načinov z izvedbo naslednjega koščka kode:

System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
System.out.println(s5);
System.out.println(s6); 

ki povzroči naslednji odziv

Test test
Test test

Test test


Test test
Test test

Opazimo lahko, da je učinek vseh načinov pričakovano enak. Razlika nastopa le v primeru, kjer ustvarimo prazen niz oz. objekt, kateremu nismo ob nastanku določili nobene vsebine. Katerega od načinov med kodiranjem uporabimo, je odvisno zgolj od tega, katere objekte oz. podatke imamo na voljo v trenutku, ko je potrebno tak niz kreirati.

Načini 'konstruiranja' nizov so podani z metodami, imenovanimi konstruktorji (constructors). Vsakič, ko ustvarimo nov niz, se izvede ustrezna izmed teh metod. Vrzite kratek pogled na dokumentacijo o razredu java.lang.String in v njej poiščite vse metode, ki so navedene v razdelku Constructors.

 

Dolžina niza

Dolžina niza je lastnost niza (objekta) in do nje ne moremo dostopati direktno, lahko pa v ta namen uporabimo metodo length() :

String s6="Test test";
int dolzinaNiza = s6.length();
System.out.println( "Niz : " + s6 + " vsebuje natanko "+ dolzinaNiza + " znakov.");

Odziv:

Niz: Test test vsebuje natanko 9 znakov.

V primeru je tudi nakazano, kako naslovimo (izvedemo) metodo določenega objekta. Objekt, ki predstavlja niz je bil poimenovan s6, metodo v tem objektu smo naslovili tako, da smo uporabili operator . (pika) za dostop do metode length() objekta :

s6.length();

kar kaže, da je metoda length() članica objekta s6 .

Pazite:

pri tabelah za ugotavljanje števila elementov v tabeli uporabimo lastnost tabele length, pri nizih morate za enako operacijo uporabiti metodo length():

String s="Test test";
char[] tab = {'T','e','s','t',' ','t','e','s','t'};
int dolzinaNiza = s6.length(); 
int dolzinaTabele = tab.length; 

-----

Naslavljanje posameznih znakov niza

Kot je bilo navedeno v uvodu, do posameznih znakov niza ne morete dostopati direktno (z indeksiranjem) temveč le preko v razredu String definiranih metod. V tem razdelku si bomo ogledali, kako ugotovimo vrednost znaka, ki stoji na določenem mestu v nizu. Vsak znak v nizu znakov ima v nizu svojo pozicijo; tako je pozicija oz. zaporedna številka začetnega znaka v nizu 0, druge znaka 1, tretjega 3, ....

Recimo, da je dan niz s:

String s="abcdef"; // niz šestih znakov

Prvi znak v nizu s naslovimo :

s.charAt(0);

zadnjega pa :

s.charAt(5);

( zadnjega tudi na naslednji način: )

s.charAt(s.length()-1);

Matoda charAt vrne vrednost znaka, ki ga v spodnjem primeru izpišemo na zaslon :

String s="abcdef"; 
System.out.println(s.charAt(0)+"  "+s.charAt(5)); 

 

Spodnji primer podaja izpis niza znakov, pri čemer se vsak znak niza izpiše v lastno vrstico

String s = new String("Ali Baba in ...");


for (int i=0;i<abc.length();i++)
   System.out.println( abc.charAt(i) ); 
A
l
i
 
B
a
b
a
 
i
n


.
.
.

 

Metoda charAt(stevilka) je torej tista, ki nam omogoča nasloviti posamezen element.

Če naslovimo znak, ki ni v nizu (recimo, da je niz pekratek) javansko okolje generira izjemo StringIndexOutOfBoundsException.

(ops : naloge : z zamikom enega znaka, diagonalno, druga diagonala, printf/println, niz palindrom, izpis elementov niza brez prvih treh in zadnjih treh elementov)

-----

Konkatenacija nizov

je operacija, ki nam omogoča, da sestavimo več nizov skupaj. V Java lahko za konkatenacijo uporabimo posebno metodo concat razreda String ali pa (pogosteje) operator +, ki vrši isto operacijo.

Izvajanje spodnjih dveh zaporedi

operator + :

String osnoven = new String("lepi ");
String dodan   = new String("Sime");

System.out.println( osnoven + dodan );

concat :

String osnoven = new String("lepi ");
String dodan   = new String("Sime");


System.out.println( osnoven.concat(dodan) );

rezultira v izpisu

lepi Sime

Dejansko se med kodiranjem pogosteje uporablja operator. Razlogi so preprosti : racionalnost pri tipkanju (pritisniti je potrebno manj tipk za +) in ni potrebno poznati oblike (sintakse) metode concat.

Vendar drugi primer, z uporabo metode, bolj nazorno pokaže, kaj se dejansko dogaja oz. kaj je rezultet izvajanja konkatenacije. Poglejmo si primer, podan z naslednjo kodo :

String osnoven = new String("lepi ");
String dodan = new String("Sime");

osnoven = osnoven.concat(dodan); 

Po izvajanju je v nizu osnoven sestavljen niz "lepi Sime". Vse se zdi, kot da bi se niz osnoven spremenil s tem, da se je na konec obstoječega niza dodal niz dodan.

Ker pa so nizi v java nespremenljivi, se izvajanje stavka dejansko izvrši takole :

ker niz ni spremenljiv, prireditev ustvari nov objekt, vsebina novega objekta se ustvari s konkatenacijo vsebin obeh prvotnih objektov, kot kaže drugi del stavka osnoven = osnoven.concat(dodan);:

nato se izvrši še prvi del stavka, ki vsebuje prireditev ( osnoven = osnoven.concat(dodan); ):

prireditev povzroči, da referenca, ki je do sedaj kazala na objekt z vsebino niza "lepi" prične referencirati novo zgrajen objekt. Pri tem postane objekt z vsebino "lepi" nereferenciran. Ker se nanj sedaj v programu ne sklicujemo z ničemer več, ga javanski "garbage collector" ob priliki odstrani iz pomnilnika. Stanje, ki ga dobimo po celotnem postopku, kaže spodnja slika :

Primerjate to zadnjo sliko s prvotno !

Vse metode iz java.lang.String, ki navidezno spreminjajo del ali celoten niz, delujejo na enak način, kot je bilo prikazano v zadnjem primeru; s kreiranjem novega primerka razreda String.

------

Pretvorbe iz niza ....in v niz

V tem razdelku bomo govorili o pretvorbah med nizom znakov in primitivnimi podatkovnimi tipi. Take pretvorbe so med pomembnejšimi vsled kodiranja javanskih programov. Primitivni podatkovni tipi oz. objekti ustreznih ekvivalentnih tipov so enostavno preprostejši za manipulacijo, kadar je potrebno nad posameznim ali množico takih podatkov izvesti neko računsko operacijo. Vendar pa so podatki, ki jih vnašamo v proces izvajanja, v večini primerov v obliki nizov. Pomislite vsaj na dosedanje primere branja podatkov, kjer smo jih vnašali kot parametre programa. Drug primer so vnosna polja grafičnih aplikacij. Tudi v tem primeru številske podatke vnašamo kot nize znakov.

a) Pretvorbe iz niza v število

valueOf in parseTIP

public static void main(String[] argumenti) {


            //pretvorba niza v celo število
            int a = (Integer.valueOf(argumenti[0]) ).intValue();  
            int b = (Integer.valueOf(argumenti[1]) ).intValue();

            //zračun vsote
            int vsota = a + b;
}

    


Tipično izvajanje Integer.valueOf(niz) vrne referenco na objekt tipa Integer, Float.valueOf(niz) referenco na objekt tipa Float, zato je potrebno za pretvorbo v primitiven podatkovni tip uporabiti še metode tipValue(). V zgornjem primeru je to metoda intValue(), ki to počne v primeru celoštevilskih tipov.

Zato ponavadi pri pretvorbi iz niza v primitivne javanske tipe porabimo metode parseTIP, kot je prikazano v naslednjem primeru:

public static void main(String[] argumenti) {


    //pretvorba niza v celo število 
    int a = Integer.parseInt(argumenti[0]) ; 
    int b = Integer.parseInt(argumenti[1]) ; 

    //zračun vsote
    int vsota = a + b; 
}

V primeru, da niz pretvarjamo v necelo število, seveda uporabimo eno izmed metod Float.parseFloat(niz) oz. Double.parseDouble(niz).

metodi valueOf in parseTip v zgornjim primerih nista bili del razreda String, temveč posameznih razredov tipov iz katerih smo vrednosti pretvarjali v niz.

Pogosto potrebujemo numerične podatke v obliki niza. Najpogosteje takrat, kadar želimo nek podatek oz. zaporedje podatkov izpisovati v neki določeni obliki.

b) Pretvorbe iz števila v niz

poglejmo si primera pretvorbe v niz, ki ju že poznamo:

int i=3; float f=3.14
System.out.println(""+i+" in "+f);

oz.:

int i=3; float f=3.14;
String s=""+i+" in "+f;
System.out.println(s); 

povzroči izpis

3 in 3.14

izpis je v obliki niza znakov, ki se je ustvaril s konkatenacijo praznega niza "" z ostali podatki, potrebnimi za želen izpis.Drugi primer predstavlja uporaba metode valueOf razreda String :

int i=3;
String s = String.valueOf(i); 

metoda valueOf razreda String uspe poljuben javanski primitivni tip pretvoriti v niz znakov

Tudi vsak izmed številskih razredov Integer, Float, Double, ... vsebujemo metodo, ki je zmožna pretvorbe v niz v primitiven številski tip:

toString

int i=3;
double d=3.14;
String s1 = Integer.toString(i); 
String s2 = Double.toString(d);

 

-----

Matode razreda String za manipulacijo z nizem ali delom niza

Primerjava nizov

a) enakost nizov

ugotavlja, ali sta dva niza enaka (vsebujeta enako zaporedje znakov). V primeru, da sta niza enaka, metoda vrne vrednost true, sicer false:

Slednji deluj enako kot prvi, le da ne dela razlike med velikimi in malimi črkami. Tako sta niza "ecikapecika" in "ECIKApecika" v taki primerjavi enaka.

Primer:

String s1 = "ecikapecika";
String s2 = new String("ECIKApecika");
String s3 = new String(s1);


System.out.println(s1.equals(s3));
System.out.println(s1.equals(s2));
System.out.println(s1.equalsIgnoreCase(s2));

generira izpis

true
false
true 
 
b) leksikalna primerjava nizov

se razlikuje od enakosti v tem, da zna nize primerjati "po velikosti". Kriterij velikost je pri tem mesto črke v tabeli nabora znakov. Ker so v tej tabeli simboli za črke navedeni v abecednem vrstenem redu, pravimo taki primerjavi tudi leksikalna primerjava.

Rezultat leksikalne primerjave je vrednost 0 v primeru, da je niz enak argumentu metode, negativna vrednost v primeru, da je niz manjši od argumenta in pozitivna vrednost v primeru, da je niz večji od argumenta

Recimo, da imamo dane štiri nize:

String s1 = "abcdefgh";
String s2 = "aaadefgh"; 
String s3 = "aaa"; 
String s4 = "Abcdefgh"; 

leksikalno so dani nizi urejeni (če upoštevamo razlikovanje med velikimi in malimi črkami) na naslednji način:

s4 < s3 < s2 < s1

Izvedba naslednjega zaporedja programskih stavkov:

System.out.println(s1.compareTo(s3)); 
System.out.println(s3.compareTo(s1)); 
System.out.println(s4.compareTo(s1)); 
System.out.println(s4.compareToIgnoreCase(s1)); 

tako generira izpis :

1
-1
-32
0

 

Vrednost 1 iz prve vrstice izpisa pomeni, da je s1 leksikalno večji od s3, in je zato s3 manjši od s1 (druga vrstica izpisa). Tretji in četrti izpis primerjata dva niza, sestavljena iz istih črk, razlika je le tem, da se v nizu s4 beseda prične z veliko začetnico. Primerjava teh nizov vrne, da je niz s4 manjši ( v naboru znakov so simboli velikih črk zapisani pred vsemi simboli za male črke), v primeru, da ignoriramo pomen velikih in malih črk, pa primerjava istih nizov vrne vrednost 0 (niza sta enaka).

c) preverjanje ali se nizi začno oz. končajo z določenim podnizom

Spodnje metode delujejo enako kot metode equals s to razliko, da ne primerjajo celotnega niza, temveč le začeten del ali končen del niza:

Primer

System.out.println("ecikapecika".startsWith("ecik"));
System.out.println("ecikapecika".startsWith("ecikX"));

primerjavo si lahko ponazorimo s sliko

Izpis zgornjih dveh stavkov je tako enak :

true
false

 

V naslednjem primeru primerjavo ne pričnemu pri prvem elementu, temveč pri elementu, katerega zaporedna številka je podana s parametrom metode startsWith :

System.out.println("ecikapecika".startsWith("ecik",2));

Neumestna raba drugega parametra lahko med izvajanje rezultira v izjemi StringIndexOutOfBoundsException.

Zadnji primer vrši primerjanje na koncu niza. Uporabimo metodo endsWith :

System.out.println("ecikapecika".endsWith("ecika"));

za primerjavo lahko v nizih uporabimo tudi metode vrste matches, ki pa kot argumente uporabljajo regularne izraze (regular expressions, regex). Regularni izrazi so tema nadaljevalnega javanskega tečaja, zato teh metod ta trenutek ne bi omenjali.

Izločanje podniza iz niza

Spodnji metodi omogočata kreiranje novega niza iz dela obstoječega niza.

V splošnem metodi vrste substring uporabljamo, kot da imata dva parametra : prvi določa, kje se bo rezultativni podniz pričel, drugi pa kje bo njegov konec. Če drugi parameter pri tem izpustimo, je konec rezultativnega niza zadnji znak prvotnega niza.

System.out.println( "ecikapecika".substring(3,7));

rezultira v izpisu:

kape

in

System.out.println( "ecikapecika".substring(3));

v izpisu :

kapecika

 

Kot je videti iz primerov, drugi parameter metode substring ne določa indeksa elementa, ki je zadnji v nizu, temveč njegovega naslednika. Razlog je v verjetno v tem, da razlika parametrov do_kje-od_kje določa število znakov, ki se prepišejo iz originalnega niza v rezultativni podniz.

 

Vsi primeri nemogoče rabe rezultirajo v izjemi StringIndexOutOfBoundsException :

System.out.println(abc.substring(3,17));
System.out.println(abc.substring(7,3));

razlogi so vidni iz spodnje slike:

razen primere, kje sta oba parametra iste vrednosti:

System.out.println(abc.substring(3,3));

v tem primeru je rezultat prazen niz.

Iskalne metode

Vrnejo mesto prve pojavitve znaka ali podniza v nizu. Če se iskani v nizu ne nahaja, metoda vrne vrednost -1.

kot aterntivno lahko preiskujemo niz tudi od zadnjega znaka proti prvemu :

Primeri:
System.out.println("ecikapecika".indexOf('a');
System.out.println("ecikapecika".indexOf("ka");
System.out.println("ecikapecika".lastIndexOf("ka"); 

rezultirajo v naslednjem izpisu:

4
3
9

 

Še nekaj izmed preostalih metod, ki lahko ali pa tudi ne, pridejo prav med reševanjem zadanih problemov:

ustvari nov niz iz obstoječega, s tem na v novem nizu zamenja vse znake obstojec obstoječega niza z znaki nov

System.out.println("ecikapecika".replace('X','e'));

ustvari nov niz, na osnovi s parametrom podanega niza. Nov niz je vsebinsko enak podanemu, le vse črke niza so male. Znakov prvotnega niza, ki niso črke, spremembe ne zadevajo.

System.out.println( "ECIKA*12_pecika".toLowerCase() );

podobni kot predhodni, le da vse pojavitve malih črk zamenja z istopomenskimi velikimi.

System.out.println( "ecika*12_pecika".toUpperCase() );

ustvari nov niz iz obstoječega, pri tem odstrani vse morebitne vodilne in sledilne presledke.

System.out.println( "   ecikapecika     "+"abc" );
System.out.println( "   ecikapecika     ".trim()+"abc" ); 

 

    ecikapecika     abc
ecikapecikaabc 

 


Opomba:

Poglejte si tudi razred StringBuilder !


Programske naloge

Naloga 1

Dan je program, ki izvede ‚statistiko’ pojavljanja posameznih črk v nizu znakov, ki ga podamo kot parameter temu programu:

 class Nal11_1{


     public static void main(String[] arg){
        int stevilo=0;


        for (char c=’a’; c<=’z’; c++){
           stevilo=0;
           for (int i=0;i<arg[0].length();i++)
              if (arg[0].charAt(i) == c )
                 stevilo++;
              if (stevilo > 0)
           System.out.println(“”+c+”: ”+stevilo);
       }
     }

}

Izveden program z denim parametrom, bi moral izvršiti izpis, kot je podano v primeru :

 >java Nal11_1 ecikapecika
 a: 2
 c: 2
 e: 2
 i: 2
 k: 2
 p: 2

 

Naloga 2

Postopek, uporabljen v predhodni nalogi je časovno prezahteven. Npr.,ko preverjamo, kolikokrat se črka ‘a’ nahaja v nizu, to preverjamo za vsak element niza posebej. Nato to storimo za vsako nadaljno črko posebej. Iz tega sledi, da vsak element niza preverimo z vsako črko ! Število primerjav, ki jih izvedemo, je tako 25(recimo, da je toliko vseh črk)*število_črk_v_nizu.

Poskusimo postopek izboljšati:

 

Ideja je uporabiti postopek direktnega razporševanja. Postopamo takole : naredimo podatkovno strukturo (recimo tabelo), ki je toliko dolga, kot imamo vseh črk, ki jih preverjamo. Nato za vsak element niza storimo naslednje: pogledamo katera črka to je, izračunamo njeno mesto v tabeli glede na njeno mesto v zaporedju črk abecede in vrednost tabele glede na to zaporedno mesto povečamo za 1. Po koncu postopka v tabeli dobimo pogostost črk v številu njihovih pojavitev v nizu, s tem, da vsak element niza primerjamo le po enkrat !

 

Napišite tak program, pri tem upoštevajte, da se v nizu črka ‘a’ lahko nahaja v dveh oblikah: kot mala ali kot velika črka (obe tretitamo kot isto črko).

 

(V pomoč naj vam bo del slike tabele po koncu postopka : spodnja vrstica predstavlja vsebino tabele, nad njo so indeksi tabele, v zgornji vrstici ‘pomen’ vsebine tabele

 

a

b

c

d

e

f

g

h

i

j

k

l

m

n

o

p

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

 

2

0

2

0

2

0

0

0

2

0

2

0

0

0

0

1

 )

 

Naloga 3

Napišite program, ki bo vsebino niza tranformiral tako, dabo zamenjal vrstni red besed v originalnem nizu. Npr. :

 

 String s1=new String(“velika teta je nasla majhno kozo”);

    . . .

 System.out.println(s2); 

 

izpiše :

 kozo majhno nasla je teta velika

pri tem besede strikno ločujejo presledki.

 

Naloga 4

Napišite program, ki bo ugotovil, kolikokrat se v nizu, podanem s parametrom nahaja zahtevani podniz. Npr.:

 

 >java Nal11_4 kakadukakadu ka
 ka se v nizu kakadukakadu nahaja 4 krat 

in

 >java Nal11_4 kakadukakadu du
 du se v nizu kakadukakadu nahaja 2 krat