This is an old revision of the document!
Serializarea reprezintă procesul prin care un obiect Java este transformat într-o reprezentare transferabilă, de regulă o secvență de bytes sau un format text, astfel încât să poată fi:
Operația inversă se numește deserializare și presupune refacerea obiectului original din această reprezentare.
Serializarea este folosită în special pentru:
Pentru ca un obiect să poată fi serializat de JVM folosind mecanismul nativ, clasa sa trebuie să implementeze interfața: java.io.Serializable.
public class User implements Serializable { private String name; private int age; }
Această interfață:
ObjectOutputStream și ObjectInputStream.
Dacă un obiect nu este serializabil, JVM aruncă NotSerializableException.
1. Clasa model
import java.io.Serializable; public class Person implements Serializable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return name + " (" + age + ")"; } }
2. Serializarea unui obiect
import java.io.FileOutputStream; import java.io.ObjectOutputStream; public class SerializeDemo { public static void main(String[] args) throws Exception { Person p = new Person("Ana", 30); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser")); out.writeObject(p); out.close(); System.out.println("Object serialized."); } }
3. Deserializarea unui obiect
import java.io.FileInputStream; import java.io.ObjectInputStream; public class DeserializeDemo { public static void main(String[] args) throws Exception { ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser")); Person p = (Person) in.readObject(); in.close(); System.out.println("Deserialized: " + p); } }
Nu toate câmpurile unui obiect ar trebui serializate. Exemple tipice:
Pentru a exclude un câmp de la serializare folosim cuvântul cheie transient.
public class Account implements Serializable { private String username; private transient String password; }
password ca fiind transient, acesta nu este salvat, iar după deserializare devine null.
Câmpul serialVersionUID identifică versiunea unei clase serializate.
private static final long serialVersionUID = 1L;
Acest identificator este folosit de JVM pentru a verifica dacă o clasă este compatibilă cu obiectul serializat, astfel putem garanta compatibilitatea operațiilor.
Jackson este o bibliotecă externă foarte populară care permite:
Spre deosebire de serializarea nativă:
Serializable,De ce este Jackson preferat în aplicațiile moderne?
Este standardul de facto pentru:
1. Dependința Jackson
Într-un proiect real (Maven), Jackson se adaugă astfel:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.16.1</version> </dependency>
2. Clasa model
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; public class User { private String name; private int age; @JsonIgnore private String password; public User() { // constructor fără parametri NECESAR pentru deserializare } public User(String name, int age, String password) { this.name = name; this.age = age; this.password = password; } public String getName() { return name; } public int getAge() { return age; } @JsonProperty("years") public void setAge(int age) { this.age = age; } public String getPassword() { return password; } @Override public String toString() { return "User{name='" + name + "', age=" + age + "}"; } }
3. Serializare: obiect → JSON
import com.fasterxml.jackson.databind.ObjectMapper; public class JacksonSerializeDemo { public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper(); User user = new User("Ana", 30, "secret123"); String json = mapper.writeValueAsString(user); System.out.println("JSON rezultat:"); System.out.println(json); } }
password nu apare deoarece are adnotarea @JsonIgnore.
4. Deserializare: JSON → obiect
import com.fasterxml.jackson.databind.ObjectMapper; public class JacksonDeserializeDemo { public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper(); String json = """ { "name": "Ion", "years": 25, "password": "shouldBeIgnored" } """; User user = mapper.readValue(json, User.class); System.out.println("Obiect deserializat:"); System.out.println(user); } }
years → age datorită @JsonPropertypassword este ignorat
5. Serializare în fișier și citire din fișier
Scriere în fișier
mapper.writeValue(new File("user.json"), user);
Citire din fișier
User user = mapper.readValue(new File("user.json"), User.class);
Adnotările sunt metadate atașate elementelor din codul Java (clase, metode, câmpuri, parametri).
Ele nu modifică direct logica programului, ci oferă informații suplimentare care pot fi interpretate de:
Cu alte cuvinte, adnotările spun ce este codul, nu ce face.
Adnotările au apărut pentru a elimina:
Astăzi, ele sunt fundamentale în:
@AnnotationName public class Example { }
Adnotările pot avea parametri:
@JsonProperty("user_name") private String name;
sau mai mulți:
@MyAnnotation(value = "test", enabled = true)
Prin @Target putem controla unde este permisă adnotarea:
TYPE – clase, interfețeFIELD – câmpuriMETHOD – metodePARAMETER – parametriCONSTRUCTORLOCAL_VARIABLEExemplu:
@Target(ElementType.FIELD)
@Retention definește cât timp există adnotarea:
@Retention(RetentionPolicy.RUNTIME)
Tipuri:
SOURCE – doar la compilare (ex: @Override)CLASS – în bytecode, dar nu la runtimeRUNTIME – disponibilă prin reflection (cea mai importantă)
@Override public String toString() { ... }
@Deprecated public void oldMethod() { }
@SuppressWarnings("unchecked")
Jackson folosește adnotări pentru a controla serializarea:
@JsonIgnore private String password;
@JsonProperty("years") private int age;
Aceste adnotări:
ObjectMapper1. Definirea adnotării
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Important { String value() default ""; }
@interface
2. Utilizarea adnotării
public class Person { @Important("primary identifier") private String name; private int age; }
import java.lang.reflect.Field; public class AnnotationDemo { public static void main(String[] args) throws Exception { Class<Person> clazz = Person.class; for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(Important.class)) { Important imp = field.getAnnotation(Important.class); System.out.println(field.getName() + " -> " + imp.value()); } } } }
Java definește trei momente distincte în care adnotările pot fi citite sau folosite:
1. La compilare (compile-time)
Adnotările cu:
@Retention(RetentionPolicy.SOURCE)
sunt:
Exemplu:
@Override
Rol:
2. După compilare, înainte de rulare (build-time / processing)
Unele adnotări sunt procesate prin Annotation Processors (APT – Annotation Processing Tool).
Acestea:
Exemple reale:
@Getter, @Setter)
#define), ci un mecanism controlat de compilator.
3. La runtime (execuție)
Adnotările cu:
@Retention(RetentionPolicy.RUNTIME)
sunt:
Exemple:
@JsonProperty)@Test)@Autowired)Acesta este cel mai important caz pentru Java modern.
Reflection este mecanismul prin care un program Java poate:
Cu alte cuvinte, Reflection permite unui program să se auto-analizeze și să se auto-manipuleze în timpul execuției.
Reflection a fost introdus pentru a permite:
Fără Reflection:
Reflection nu înlocuiește programarea clasică. Este:
De aceea este folosită:
Orice operație de Reflection pornește de la un obiect de tip Class<?>.
Există trei modalități principale de a obține un Class:
Class<String> c1 = String.class; Class<?> c2 = "hello".getClass(); Class<?> c3 = Class.forName("java.lang.String");
Toate cele trei referințe indică aceeași clasă la runtime.
Presupunem următoarea clasă:
public class Person { private String name; private int age; public Person(String name, int age) { } public String getName() { return name; } }
Câmpuri
Field[] fields = Person.class.getDeclaredFields(); for (Field f : fields) { System.out.println(f.getName()); }
Metode
Method[] methods = Person.class.getDeclaredMethods(); for (Method m : methods) { System.out.println(m.getName()); }
Constructori
Constructor<?>[] constructors = Person.class.getDeclaredConstructors(); for (Constructor<?> c : constructors) { System.out.println(c); }
Reflection permite accesarea membrilor private, ocolind encapsularea.
Person p = new Person("Ana", 30); Field field = Person.class.getDeclaredField("name"); field.setAccessible(true); String value = (String) field.get(p); System.out.println(value);
Method m = Person.class.getDeclaredMethod("getName"); String result = (String) m.invoke(p); System.out.println(result);
Reflection permite:
Constructor<Person> ctor = Person.class.getConstructor(String.class, int.class); Person p = ctor.newInstance("Ion", 25);
Acesta este mecanismul folosit de:
Reflection este mecanismul prin care adnotările RUNTIME sunt citite.
Exemplu:
@Retention(RetentionPolicy.RUNTIME) @interface Info { String value(); }
@Retention(RetentionPolicy.RUNTIME) @interface Info { String value(); }
Citire la runtime:
Info info = Person.class.getAnnotation(Info.class); System.out.println(info.value());
Reflection este folosit pentru:
Exemple:
Reflection:
Assertions sunt afirmații folosite pentru a verifica ipoteze interne ale programului în timpul execuției. Ele exprimă condiții care ar trebui să fie mereu adevărate dacă programul funcționează corect.
Aserțiile nu sunt mecanisme de tratare a erorilor și nu înlocuiesc excepțiile.
Assertions sunt folosite pentru:
assert condition;
Sau cu mesaj:
assert condition : "Mesaj de eroare";
condition este false, JVM aruncă un AssertionError.
public int divide(int a, int b) { assert b != 0 : "b must not be zero"; return a / b; }
Dacă b este 0, programul va eșua doar dacă assertions sunt activate.
Assertions sunt dezactivate implicit.
Se activează la rulare:
java -ea Main
| Assertions | Excepții |
|---|---|
| pentru bug-uri interne | pentru erori de runtime |
| pot fi dezactivate | sunt mereu active |
| nu se folosesc pentru input user | validează input extern |
Unit testing reprezintă procesul de testare a celor mai mici unități de cod (de regulă metode sau clase) în izolare față de restul aplicației.
Scopul este verificarea faptului că:
Unit testing-ul se concentrează pe corectitudinea logicii, nu pe integrarea componentelor.
În Java, o unitate este de obicei:
Exemplu de unitate bună:
int add(int a, int b)
Exemplu de unitate slabă:
void saveUserAndSendEmailAndLog()
Un test unitar corect este:
În Java, standardul de facto pentru unit testing este JUnit.
Un test JUnit este:
class CalculatorTest { @Test void additionWorks() { Calculator c = new Calculator(); int result = c.add(2, 3); assertEquals(5, result); } }
Elemente cheie:
@Test marchează metoda ca testassertEquals verifică rezultatulJUnit oferă propriul set de assertions:
assertEquals(expected, actual); assertTrue(condition); assertFalse(condition); assertNotNull(object); assertThrows(Exception.class, () -> code);
Exemplu:
assertThrows(IllegalArgumentException.class, () -> calculator.divide(10, 0));
Majoritatea testelor respectă structura:
@Test void exampleTest() { // Arrange Calculator c = new Calculator(); // Act int result = c.add(2, 3); // Assert assertEquals(5, result); }
Această structură:
Pentru inițializare și cleanup:
@BeforeEach void setup() { calculator = new Calculator(); } @AfterEach void clear() { calculator.reset(); }
Aceste metode:
| Unit Testing | Assertions |
|---|---|
| testare automată | verificări interne |
| rulează mereu | pot fi dezactivate |
| parte din CI | doar pentru debug |
| validează API | validează ipoteze |
Acestea aparțin:
Integration testing verifică dacă mai multe componente ale aplicației funcționează corect împreună, nu doar în izolare.
Spre deosebire de unit testing:
Exemple:
| Unit Testing | Integration Testing |
|---|---|
| testează o unitate | testează colaborarea |
| izolat | dependențe reale |
| foarte rapid | mai lent |
| fără infrastructură | poate necesita DB / config |
În practică există mai multe grade:
Laboratoarele se concentrează de obicei pe integrare parțială, pentru control și viteză.
Mocking permite simularea comportamentului unei dependențe, fără a o executa efectiv.
Folosim mocking când:
Mockito este standardul de facto în Java pentru mocking.
Cu Mockito putem:
UserRepository repo = mock(UserRepository.class); when(repo.findById(1L)) .thenReturn(new User("Ana"));
Ce se întâmplă:
repo NU este implementare realăfindById este simulatăclass UserServiceTest { UserRepository repo = mock(UserRepository.class); UserService service = new UserService(repo); @Test void returnsUserName() { when(repo.findById(1L)) .thenReturn(new User("Ana")); String name = service.getUserName(1L); assertEquals("Ana", name); } }
Aici testăm:
UserServiceMockito permite verificarea apelurilor:
verify(repo).findById(1L);
Acest lucru este util când:
În Java, var permite inferarea tipului la compilare, reducând zgomotul sintactic, fără a pierde siguranța tipurilor.
var nu este tip dinamic. Tipul este determinat o singură dată, la compilare.
Fără var:
UserRepository repository = mock(UserRepository.class); UserService service = new UserService(repository);
Cu var:
var repository = mock(UserRepository.class); var service = new UserService(repository);
Codul este:
var poate fi util în scrierea rapidă a codului, trebuie să avem grijă să nu îl folosim în următoarele situații: