Aspectele bonus urmărite sunt:
Clasele declarate în interiorul unei alte clase se numesc clase interne (nested classes). Ele sunt folosite pentru a grupa logic clase care lucrează împreună și pentru a controla mai bine accesul între ele.
Clasele interne sunt de mai multe tipuri:
O clasă internă este o clasă definită în interiorul altei clase și poate fi accesată doar printr-o instanță a clasei externe, la fel ca variabilele și metodele non-statice.
La compilare, Java creează fișiere separate pentru fiecare clasă internă, denumite după modelul: ClasaExterioară$ClasaInternă.class.
În exemplul de mai jos, compilarea va genera fișierele Car.class și Car$OttoEngine.class. Totuși, fișierul Car$OttoEngine.class nu poate fi executat direct, deoarece este dependent de clasa sa exterioară.
interface Engine { public int getFuelCapacity(); } class Car { class OttoEngine implements Engine { private int fuelCapacity; public OttoEngine(int fuelCapacity) { this.fuelCapacity = fuelCapacity; } public int getFuelCapacity() { return fuelCapacity; } } public OttoEngine getEngine() { OttoEngine engine = new OttoEngine(11); return engine; } } public class Test { public static void main(String[] args) { Car car = new Car(); Car.OttoEngine firstEngine = car.getEngine(); Car.OttoEngine secondEngine = car.new OttoEngine(10); System.out.println(firstEngine.getFuelCapacity()); System.out.println(secondEngine.getFuelCapacity()); } }
$ javac Test.java $ ls Car.class Car$OttoEngine.class Engine.class Test.class Test.java
Din interiorul unei clase interne, putem accesa referința la instanța clasei externe folosind numele acesteia urmat de keyword-ul this:
class Car { private String model = "Tesla"; class OttoEngine { private int fuelCapacity; public OttoEngine(int fuelCapacity) { this.fuelCapacity = fuelCapacity; } public void printCarModel() { // Accesăm instanța clasei exterioare folosind Car.this System.out.println("Car model: " + Car.this.model); } } public OttoEngine getEngine() { return new OttoEngine(11); } } public class TestThis { public static void main(String[] args) { Car car = new Car(); Car.OttoEngine engine = car.getEngine(); engine.printCarModel(); // Va afișa: Car model: Tesla } }
model este un câmp privat, clasa internă îl poate accesa, deoarece și clasa internă este un membru al clasei.
Așa cum s-a menționat și în secțiunea Introducere, claselor interne le pot fi asociați orice identificatori de acces, spre deosebire de clasele top-level Java, care pot fi doar public sau package-private. Ca urmare, clasele interne pot fi, în plus, private și protected, aceasta fiind o modalitate de a ascunde implementarea.
Spre deosebire de clasele top-level, care pot fi doar public sau package-private (denumit și default), o clasă internă poate fi și private sau protected.
Această flexibilitate permite ascunderea detaliilor de implementare și controlul strict al accesului.
| Tip clasă | Modificatori de acces permiși | Scop principal |
|---|---|---|
| Clasă top-level | public, package-private | Definirea tipurilor de bază accesibile global |
| Clasă internă | public, protected, private, package-private | Gruparea logică și ascunderea implementării |
Folosind exemplul clasei Car care conține o clasă internă OttoEngine, dacă marcăm această clasă internă ca private, apar erori de compilare, deoarece tipul Car.OttoEngine nu mai este accesibil din exterior.
class Car { private class OttoEngine implements Engine { public void start() { System.out.println("Engine started."); } } public Engine getEngine() { return new OttoEngine(); // upcasting la Engine } } public class Main { public static void main(String[] args) { Car car = new Car(); Engine e = car.getEngine(); // acces permis doar prin interfață e.start(); } }
Car.OttoEngine este privată, deci nu poate fi instanțiată direct din exterior.new Car.OttoEngine() va genera o eroare de compilare.
În dezvoltarea software există situații în care o componentă are o funcționalitate bine definită, dar este utilizată doar într-un context restrâns.
Definirea unei clase separate ar adăuga complexitate inutilă. În aceste cazuri, putem folosi clase interne anonime, adică clase fără nume, definite și instanțiate simultan.
O clasă anonimă extinde o altă clasă sau implementează o interfață și suprascrie metodele acesteia direct în momentul creării obiectului.
Presupunem că avem o interfață Engine și o clasă Car care o folosește. Putem rescrie metoda getEngine() astfel încât să întoarcă o clasă anonimă:
class Car { public Engine getEngine(int fuelCapacity) { return new Engine() { private int fuelCapacity = 11; public int getFuelCapacity() { return fuelCapacity; } }; } }
Sintaxa return new Engine() { … } poate fi citită astfel:
„Creează o clasă care implementează interfața Engine conform implementării următoare.”
Această tehnică reduce volumul codului și îmbunătățește lizibilitatea atunci când nu avem nevoie de o clasă separată reutilizabilă.
return, folosind new.Engine).Engine, deci poate defini metodele interfeței.{ … }.
| Limitare | Explicație |
|---|---|
| Nu pot avea constructori | Deoarece nu au nume, nu se pot defini constructori expliciți. Se folosește implicit constructorul clasei de bază. |
| Pot extinde doar o singură clasă sau implementa o singură interfață | Nu se pot combina moștenirea și implementarea multiplă în același timp. |
| Durata de viață | Obiectele anonime sunt create și folosite pe loc, deci potrivite pentru scopuri scurte. |
Față de clasele interne normale, clasele anonime au câteva restricții în ceea ce privește accesul la variabile și metode în funcție de locația acestora.
Câmpurile clasei externe pot fi accesate din interiorul clasei anonime fără să fie nevoie să se impună anumite restricții.
public class Outer { private String secret = "Private info"; interface Greeter { void greet(); } public void createAnonymous() { Greeter g = new Greeter() { @Override public void greet() { // acces direct la câmpul privat al clasei externe System.out.println("Secretul este: " + secret); } }; g.greet(); // -> "Secretul este: Private info" } public static void main(String[] args) { Outer outer = new Outer(); outer.createAnonymous(); } }
Dacă clasa anonimă extinde o clasă normală sau abstractă, ea primește toate câmpurile (public/protected/package-private sau private prin getteri) ale clasei părinte:
abstract class Animal { protected int age = 5; // câmp moștenit public abstract void showDetails(); } public class Test { public static void main(String[] args) { Animal a = new Animal() { @Override public void showDetails() { System.out.println("Animal has age: " + age); // folosește câmpul moștenit } }; a.showDetails(); // Output: "Animal has age 5" } }
Dacă clasa anonimă implementează o interfață, interfața nu are câmpuri ale instanței, doar constante (declarate public static final) care pot fi accesate direct din clasa anonimă.
interface Greeter { String NAME = "Ana"; // constantă void greet(); } public class Main { public static void main(String[] args) { Greeter g = new Greeter() { @Override public void greet() { // Accesăm direct constanta din interfața implementată System.out.println("Salut, " + NAME); } }; g.greet(); // Output: Salut, Ana } }
class Animal { protected int age = 5; public void speak() { System.out.println("Animal speaks"); } public abstract void showDetails(); } public class Test { public static void main(String[] args) { Animal a = new Animal() { // Implementăm metoda abstractă @Override public void showDetails() { speak(); // folosește metoda moștenită System.out.println("Animal has age: " + age); } }; a.showDetails(); a.speak(); // o putem apela și direct pentru că referința este de tip Animal } }
class Animal { protected int age = 5; public void speak() { System.out.println("Animal speaks"); } } public class Test { public static void main(String[] args) { Animal a = new Animal() { // Declarăm o metodă proprie public void showDetails() { System.out.println("Age: " + age); } }; a.showDetails(); // nu va funcționa pentru că referința este de tip Animal } }
Animal, dar JVM creează pentru fiecare clasa anonimă un nume intern unic, astfel încât suprascrierea să funcționeze corect.
O clasă internă anonimă declarată într-o metodă poate folosi variabilele locale și parametrii acelei metode doar dacă aceștia sunt:
final, sauclass Car { interface Engine { int getFuelCapacity(); } public Engine getEngine(int fuelCapacityParam) { int baseCapacity = fuelCapacityParam; // effectively final return new Engine() { public int getFuelCapacity() { return baseCapacity + 5; // poate fi folosit } }; } public static void main(String[] args) { Car car = new Car(); System.out.println(car.getEngine(10).getFuelCapacity()); // 15 } }
Variabilele locale sunt stocate pe stivă, deci dispar după terminarea metodei. Pentru a fi accesibile în clasa internă, Java creează o copie a acestor variabile și o stochează ca atribut intern al clasei anonime.
final?
A: Variabila s-ar copia inițial în clasa anonimă cu valoarea de la acel moment de timp. Dacă ulterior variabila din metodă își schimbă valoarea, această schimbare nu va fi reflectată și în clasa anonimă, deci vom ajunge să lucrăm cu două valori diferite simultan, ceea ce crează bug-uri ascunse.
final.
De la Java 8 încolo, compilatorul tratează automat variabilele nemodificate ca effectively final.
Începând cu Java 8, clasele anonime care implementează interfețe cu o singură metodă abstractă (așa-numitele Functional Interfaces) pot fi înlocuite cu expresii lambda.
Lambda este o funcție anonimă, o formă mai scurtă de a exprima același comportament. Formatul unei funcții lambda este:
(parametri) -> expresie
De exemplu:
class Car { public Engine getEngine() { return () -> 11; // expresie lambda } }
Dar putem avea și o logică mai complexă:
(parametri) -> { // bloc de cod return valoare; }
() → 11 înlocuiește declarația clasei anonime.return new Engine() { private int fuelCapacity = 11; public int getFuelCapacity() { return fuelCapacity; } };
În secțiunile precedente, am discutat despre clase interne obișnuite, ale căror instanțe există doar în contextul unei instanțe a clasei exterioare. Acestea pot accesa direct membrii obiectului exterior.
Clasele interne pot fi privite ca membri ai clasei exterioare (la fel ca metodele sau câmpurile), deci pot avea aceiași modificatori de acces, inclusiv private și static, pe care clasele top-level nu le pot avea.
O clasă internă statică funcționează similar cu un câmp sau o metodă statică:
class Car { static class OttoEngine { private int fuelCapacity; public OttoEngine(int fuelCapacity) { this.fuelCapacity = fuelCapacity; } public int getFuelCapacity() { return fuelCapacity; } } public OttoEngine getEngine() { return new OttoEngine(11); // contextul permite apelul direct } } public class Test { public static void main(String[] args) { Car.OttoEngine engine = new Car.OttoEngine(10); // fără instanță Car System.out.println(engine.getFuelCapacity()); } }
| Situație | Consecință |
|---|---|
| Instanțiere din interiorul clasei exterioare | Se poate folosi direct new OttoEngine() |
| Instanțiere din afara clasei exterioare | Se folosește new Car.OttoEngine() |
| Acces la câmpuri/membri nestatici ai clasei exterioare | Nu este permis |
Clasele interne statice sunt utile atunci când dorim organizare logică a codului, dar nu avem nevoie de o legătură directă cu o instanță a clasei exterioare.
Ele combină avantajele grupării (aparțin clasei exterioare) cu cele ale independenței (pot fi folosite separat, fără instanța exterioară).
| Situație | Explicație |
|---|---|
| Clasa internă este utilizată doar de clasa exterioară | Oferă o organizare mai clară a codului, evitând “aglomerarea” pachetului cu multe clase top-level. |
| Clasa internă nu are nevoie să acceseze câmpuri sau metode non-statice ale clasei exterioare | O putem face statică pentru a evita referințele inutile către instanța exterioară. |
| Clasa internă reprezintă un concept auxiliar al clasei exterioare | De exemplu, Car.Engine, Database.Connection, Map.Entry. |
| Vrem eficiență — instanța clasei interne nu mai păstrează o referință către cea exterioară | Economisim memorie și evităm scurgerile (memory leaks). |
Când nu e recomandată:
Primele exemple ilustrează cele mai frecvente moduri de utilizare a claselor interne.
Totuși, designul acestora este destul de flexibil, iar Java permite și forme mai puțin întâlnite. Concret, o clasă internă poate fi declarată de asemenea și în:
Aceste variante sunt mai rare, dar oferă un control mai fin asupra vizibilității și duratei de viață a clasei interne.
În mod obișnuit, moștenirea unei clase presupune doar apelarea constructorului clasei părinte. Însă, pentru clasele interne, lucrurile sunt mai complexe: constructorul clasei interne trebuie să se „atașeze” întotdeauna de un obiect al clasei exterioare.
Acest lucru înseamnă că atunci când derivăm o clasă internă, constructorul clasei derivate trebuie să știe de ce instanță a clasei exterioare aparține, deoarece nu există un obiect „default” al clasei exterioare la care să se lege automat.
class Car { class Engine { public void getFuelCapacity() { System.out.println("I am a generic Engine"); } } } class OttoEngine extends Car.Engine { OttoEngine() { } // EROARE, avem nevoie de o legatura la obiectul clasei exterioare OttoEngine(Car car) { // OK car.super(); } } public class Test { public static void main(String[] args) { Car car = new Car(); OttoEngine ottoEngine = new OttoEngine(car); ottoEngine.getFuelCapacity(); } }
Observații:
| Aspect | Detalii |
|---|---|
| Parametru constructor | Constructorul clasei derivate trebuie să primească o referință la instanța clasei exterioare (Car car) |
| Inițializare super | Linia car.super(); creează legătura între clasa internă părinte (Engine) și obiectul exterior (car) |
| Efect | Moștenirea clasei interne se realizează corect fără erori de compilare |
Clasele interne pot părea complicate la prima vedere, dar devin foarte utile în următoarele situații:
// Codul tău cu clasa anonimă public class MyGUI { public void closeWindow() { // Clasa anonimă implementează ActionListener button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { numClicks++; } }); } } // Cum ar putea arăta metoda addActionListener intern class JButton { ... public void addActionListener(ActionListener listener) { // apelează metoda suprascrisă din clasa anonimă listener.actionPerformed(this.event); } ... }
ActionListener și suprascrie metoda actionPerformed, iar implementarea rămâne ascunsă de restul aplicației.JButton pentru a ilustra exact de ce este nevoie să pasăm o clasă ca argument și cum putem scurta codul folosind o clasă anonimă pentru acest caz.
Un obiect de tip String reprezintă o secvență de caractere Unicode. Intern, aceste caractere sunt stocate într-un array Java. Accesul la acest array este strict controlat: poate fi manipulat doar prin metodele puse la dispoziție de obiectul String.
Deși unele operații par să modifice conținutul sau lungimea șirului, în realitate obiectul original rămâne neschimbat. Aceste operații creează un nou obiect String, care poate fie să copieze, fie să facă referire internă la caracterele relevante. Acest comportament se datorează imutabilității obiectelor String: odată creat, conținutul unui șir nu poate fi modificat.
În Java, obiectele de tip String pot fi construite în mai multe moduri, în funcție de sursa datelor și de scopul aplicației.
Un string literal este definit direct în cod între ghilimele duble (”). Java creează automat obiectul și îl atribuie variabilei.
String quote = "To be or not to be";
length():int length = quote.length(); // 18
isEmpty():boolean empty = quote.isEmpty(); // false
+ sau folosind metoda concat():String name = "John " + "Smith"; String name2 = "John ".concat("Smith");
Pentru texte mai lungi, Java 13 a introdus text blocks (”””).
Acestea permit scrierea de șiruri pe mai multe linii și păstrează indentarea. Sunt utile pentru HTML, SQL sau mesaje lungi.
String poem = """ Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe. """; System.out.print(poem); // Try this out, see how it works!
Cum funcționează:
Metoda charAt(int index) permite accesul la caracterele dintr-un șir într-un mod similar cu accesul într-un array.
String s = "Newton"; for (int i = 0; i < s.length(); i++) { System.out.println(s.charAt(i)); }
Clasa String implementează interfața java.lang.CharSequence, astfel:
length() și charAt(int index).În Java, orice obiect sau tip primitiv poate fi transformat într-un String, adică o reprezentare textuală.
toString() definită de obiectul respectiv.
Metoda statică String.valueOf() convertește orice element într-un String. Există mai multe suprascrieri care acceptă toate tipurile primitive:
String one = String.valueOf(1); // întreg → "1" String two = String.valueOf(2.384f); // float → "2.384" String notTrue = String.valueOf(false); // boolean → "false"
Toate obiectele moștenesc metoda toString() din Object.
toString() nu este definită, se afișează un identificator unic:System.out.println(new Student("Andrei", 22)); // Student@<cod_hex_id>
Atunci când este apelată metoda String.valueOf(), ea invocă de fapt metoda toString() a acelui obiect și returnează rezultatul.
Singura diferență reală în utilizarea acestei metode este că dacă îi transmiți o referință null, ea returnează șirul “null” în loc să genereze o excepție de tip NullPointerException:
date = null; d1 = String.valueOf(date); // "null" d2 = date.toString(); // NullPointerException!
Limbajul Java oferă API-uri puternice pentru a interpreta (parsa) și formata textul, fie că este vorba de numere, date, ore sau valori monetare.
În Java, tipurile primitive (precum int, double, boolean) nu sunt obiecte. Totuși, fiecare dintre ele are o clasă wrapper (de exemplu, Integer, Double, Boolean) care oferă metode utile pentru conversii între text și valori numerice.
byte b = Byte.parseByte("16"); int n = Integer.parseInt("42"); long l = Long.parseLong("99999999999"); float f = Float.parseFloat("4.2"); double d = Double.parseDouble("99.9999"); boolean flag = Boolean.parseBoolean("true");
Aceste metode parsează un String și returnează valoarea primitivă corespunzătoare.
În Java, compararea string-urilor se face cu metoda equals(), care verifică dacă două șiruri conțin exact aceleași caractere în aceeași ordine.
Totodată, pentru comparare insensibilă la majuscule, se folosește equalsIgnoreCase():
String one = "FOO"; String two = "foo"; one.equals(two); // false one.equalsIgnoreCase(two); // true
Compararea șirurilor poate fi înșelătoare dacă nu înțelegem cum funcționează memoria și optimizările interne, iar greșeala de a folosi == apare mai ales când lucrăm cu String literals.
Când mai multe șiruri identice apar în aceeași aplicație, Java le stochează într-un pool comun numit Java String Pool. Drept urmare, toate referințele către un șir din pool indică către același obiect.
Avantajele acestei optimizări sunt:
==.
Pentru cazul când folosim String literal:
String str1 = "Java";
Pentru cazul când folosim String cu constructor:
new forțează instanțierea unui obiect separat.String str4 = new String("Java");
Pe baza exemplului de mai sus, expresiile urmǎtoare sunt evaluate astfel:
Operatorul == verifică dacă cele două referințe indică același obiect în memorie.
str1 == str2 → true, deoarece ambele referințe indică același obiect din String Pool.str3 == str4 → false, deoarece str4 a fost creat cu new String(), deci este un obiect diferit în Heap.
Metoda equals() verifică dacă conținutul este identic, indiferent de locația în memorie.
str1.equals(str2) → true, conținutul este “Java”.str1.equals(str3) → false, conținutul lor diferǎstr3.equals(str4) → true, conținutul este “C++”.În Java, lucrul cu șiruri de caractere poate fi realizat folosind String sau StringBuilder. Alegerea între ele are impact asupra performanței și memoriei aplicației, mai ales când lucrăm cu texte mari sau modificări frecvente.
String este imutabil. Orice modificare a unui obiect String creează un nou obiect în memorie.
String s = "Hello"; s += " World"; // se creează un obiect nou System.out.println(s); // Hello World // Se creează 100 de obiecte noi (ineficient) for (int i = 0; i < 100; i++) { s += " World"; System.out.println(s); }
Avantaje:
Dezavantaje:
StringBuilder este mutabil. Modificările se aplică direct obiectului existent, fără a crea noi obiecte.
StringBuilder sb = new StringBuilder("Hello"); sb.append(" World"); // modifică același obiect System.out.println(sb); // Hello World // Se modifică același obiect de 100 de ori (eficient) for (int i = 0; i < 100; i++) { sb.append(" World"); System.out.println(sb); }
Avantaje:
Dezavantaje:
toString() din clasa StringBuilder.
toString() generate de IntelliJ utilizează direct string literals, deoarece concatenările sunt puține. În acest caz, crearea unui obiect StringBuilder, utilizarea lui și apoi convertirea în String ar fi mai puțin eficientă.
Clasa String oferă mai multe metode simple pentru a căuta subșiruri fixe într-un șir. Astfel, metodele startsWith() și endsWith() verifică dacă șirul începe sau se termină cu un anumit subșir:
String url = "http://foo.bar.com/"; if (url.startsWith("http:")) // true
Metoda indexOf() caută prima apariție a unui caracter sau subșir și returnează poziția de start (indexul) al acestuia, sau -1 dacă nu este găsit. În mod similar, metoda lastIndexOf() caută ultima apariție a unui caracter sau subșir, parcurgând șirul invers.
String abcs = "abcdefghijklmnopqrstuvwxyz"; int i = abcs.indexOf('p'); // 15 int i = abcs.indexOf("def"); // 3 int i = abcs.indexOf("Fang"); // -1
Metoda contains() se ocupă de sarcina foarte comună de a verifica dacă un anumit subșir se regǎsește în șirul țintă.
String log = "There is an emergency in sector 6!"; if (log.contains("emergency")) pageSomeone(); // equivalent to if (log.indexOf("emergency") != -1) ...
Pentru căutări complexe în șiruri, Java oferă API-ul pentru expresii regulate (Regular Expressions), care utilizează sintaxa RegEx pentru definirea și identificarea tiparelor în text.
În procesul de prelucrare a textului, apar frecvent situații în care este necesar să extragem și să lucrăm cu porțiuni individuale dintr-un String.
De exemplu, putem dori să analizăm fiecare cuvânt dintr-un paragraf sau să extragem câmpuri dintr-un rând de date. Este mult mai eficient să lucrăm cu segmente mai mici decât cu întregul șir.
Java oferă mai multe metode și clase pentru manipularea textului, uneori cu funcționalități suprapuse.
Una dintre cele mai utile metode pentru tokenizare este split(), disponibilă în clasa String. Această metodă acceptă ca argument o expresie regulată (RegEx) care descrie delimitatorul. Pe baza acestei expresii, metoda împarte șirul original într-un array de șiruri (String[]), fiecare reprezentând un token.
String text1 = "Now is the time for all good people"; String[] words = text1.split("\\s");
Expresia \\s este o expresie regulată care corespunde unui caracter de tip white-space (space, tab, newline). În exemplul de mai sus, rezultatul obținut este un array cu 8 elemente, fiecare conținând un cuvânt din propoziție.
String text2 = "4231, Java Programming, 1000.00"; String[] fields = text2.split("\\s*,\\s*");
Expresia \\s*,\\s* corespunde unui separator de tipul: virgulǎ înconjuratǎ de zero sau mai multe caractere de tip white-space. Astfel, șirul este împărțit în trei câmpuri: “4231”, “Java Programming”, “1000.00”.
În prelucrarea textului, apar frecvent situații în care trebuie să verificăm dacă un șir respectă un anumit format sau să căutăm pattern-uri specifice în interiorul lui.
De exemplu, putem dori să validăm o adresă de email, să identificăm toate numerele dintr-un paragraf sau să extragem linkurile dintr-un text. Pentru astfel de sarcini, expresiile regulate (RegEx) sunt instrumentul ideal.
Sintaxa regex-ului combină caractere obișnuite (litere, numere, sau caractere precum spatiu sau underscore) cu metacaractere speciale.
Metacaracterele sunt caractere care au un inteles special in cadrul unei expresii regulate. Ele sunt expandate in niste caractere obisnuite. Prin utilizarea metacaracterelor nu trebuie specificate toate combinatiile distincte de caractere pentru care vrei sa existe potrivire.
Câteva metacaractere:
Pentru o listă completă de simboluri și exemple cu explicații, puteți consulta acest cheat sheet.
import org.apache.commons.validator.routines.EmailValidator; String email = "myName@example.com"; boolean valid = EmailValidator.getInstance().isValid(email);
În Java, expresiile regulate sunt implementate prin pachetul java.util.regex, care oferă două clase principale: Pattern și Matcher. Clasa Pattern este responsabilă pentru compilarea expresiei regulate, iar Matcher aplică această expresie asupra unui text.
Pentru verificări rapide, putem folosi metoda statică Pattern.matches(regex, text), care întoarce true dacă textul se potrivește complet cu expresia regulată. De exemplu:
boolean match = Pattern.matches("\\d+\\.\\d+f?", "42.0f");
Aceasta verifică dacă șirul este un număr în stil Java, cum ar fi „42.0f”. Dacă vrem să căutăm apariții parțiale sau multiple într-un text, folosim Matcher. Mai întâi compilăm expresia regulată:
Pattern pattern = Pattern.compile("horse|course"); Matcher matcher = pattern.matcher("A horse is a horse, of course of course");
Apoi putem apela matcher.find() într-un loop pentru a găsi toate potrivirile:
while (matcher.find()) { System.out.println("Matched: '" + matcher.group() + "' at position " + matcher.start()); }
Acest cod va afișa toate aparițiile cuvintelor „horse” și „course” din text, împreună cu poziția lor.
Regex-ul este util și pentru împărțirea textului în câmpuri. Metoda split() din clasa String acceptă o expresie regulată. De exemplu:
String text = "Foo, bar , blah"; String[] fields = text.split("\\s*,\\s*");
Rezultatul va fi un array cu elementele „Foo”, „bar” și „blah”, fără spații suplimentare.
Clase interne
Strings
Car
Creați clasa Car cu următoarele proprietăți: prețul, tipul și anul fabricației.
Tipul este reprezentat printr-un enum enum CarType declarat intern în Car. Acesta conține trei valori: Mercedes, Fiat și Skoda.Prețul și anul vor fi de tipul integers.Creați un constructor cu toti cei trei parametri, în ordinea din enunț și suprascrieți metoda toString() pentru afișare în felul următor: Car{price=20000, carType=SKODA, year=2019}
Offer
Creați interfața Offer ce conține metoda: int getDiscount(Car car);.
Dealership
Creați clasa Dealership care se va ocupa cu aplicarea ofertelor pentru mașini.
În clasa Dealership creați trei clase interne private care implementează Offer.
BrandOffer - calculează un discount în funcție de tipul mașinii:DealerOffer - calculează un discount în funcție de vechimea mașinii:SpecialOffer - calculează un discount random, cu seed 20. Generarea se va realiza în constructor utilizându-se o instanța globală a unui obiect de tip Random care a fost inițializat cu seed-ul 20 și cu limita superioară (bound) 1000 Random.
Adăugați o metodă în clasa Dealership care oferă prețul mașinii după aplicarea discount-urilor din oferte: getFinalPrice(Car car)
car primit ca argument cele trei oferte in ordinea: BrandOffer, DealerOffer, SpecialOffer.Testare oferte: Creati 2 obiecte Car pentru fiecare tip de mașină cu urmatoarele valori:.
getFinalPrice) pentru fiecare obiect.
Aăugați în clasa Dealership metoda void negotiate(Car car, Offer offer). Aceasta permite clientului să propună un discount.
În metoda main apelați negotiate dând ca parametru oferta sub formă de clasă anonimă. Implementarea ofertei clientului reprezinta returnarea unui discount de 5%.
Pentru testare folositi urmatorul obiect Car:
-Pret: 20000
-Tip: Mercedes
-An: 2019
Ai primit un mesaj misterios de la o inteligență artificială. Mesajul conține un cod secret și un număr de identificare. Pentru a rezolva misiunea detectivul trebuie să:
codeMessage.POO din codeMessage cu un șir gol de caractere și să verifice dacă String-ul rezultat este gol.codeNumber la codeSecret și să printeze valoarea.codeNumber în int, la care se adună numărul 5, iar apoi rezultatul va fi stocat într-un String numit modifiedCodeNumber.codeNumber cu modifiedCodeNumber folosind metoda compareTo().codeMessage cu message folosind == pentru a testa dacă se folosește aceeași referință.extraCodes la începutul lui codeSecret de 5 ori și să salveze rezultatul în fullCodeMessage.fullCodeMessage cu password folosind equals() și equalsIgnoreCase().