This is an old revision of the document!
Scopul acestui laborator este de a familiariza studenții cu principiile de bază ale programării orientate pe obiecte și cu modul în care memoria este gestionată în Java.
Aspectele urmărite sunt:
Aspectele bonus urmărite sunt:
Programarea orientată pe obiecte reprezintă unul dintre fundamentele limbajului Java. Aceasta presupune organizarea aplicațiilor sub forma unor obiecte care colaborează între ele pentru a rezolva probleme complexe.
Prin împărțirea unei aplicații în componente independente, procesul de dezvoltare, întreținere și reutilizare a codului devine mai simplu și mai eficient.
Java a fost conceput de la început ca un limbaj orientat pe obiecte, iar toate bibliotecile și API-urile sale reflectă această filozofie de design.
Succesul acestei paradigme se poate datora și faptului că multe dintre cele mai folosite limbaje se întamplă să fie limbaje orientate pe obiect, cum ar fi:
Android (Front-end) | iOS (Back-end) | Web (Front-end or Back-end) |
---|---|---|
Java | Swift | JavaScript |
C++ | Objective-C | Python |
Kotlin | PHP | |
Ruby | ||
C# | ||
Java |
Spre deosebire de programarea procedurală, care folosește o listă de instrucțiuni pentru a spune computerului pas cu pas ce să facă, programarea orientată-obiect se folosește de componente din program care știu să desfășoare anumite acțiuni și să interacționeze cu celelalte.
Astfel, POO oferă modularitate și ordine programului, reușind să modeleze situații din viața reală - fiind mai avansată decât programarea procedurală, care reflectă un mod simplu, direct de a rezolva problema.
Deoarece în acest laborator studiem clase și obiecte, vom avea acces și la tipul special String
, fiind singurul tip fundamental care nu este de tip primitiv, ci de tip obiect.
El este folosit pentru definirea șirurilor de caractere, astfel se elimină nevoia de folosire a contrucțiilor de tip char array
.
String name = "Ion"; String surname = "Popescu"; String fullname = name + surname; // concatenarea va rezulta într-un String, deoarece cel puțin un membru este String
String
.
Clasele reprezintă tipuri de date definite de utilizator sau deja existente în sistem (din class library
- set de biblioteci dinamice oferite pentru a asigura portabilitatea, eliminând dependența de sistemul pe care rulează programul).
Clasele sunt blocurile de construcție ale unei aplicații Java. Ele definesc structura și comportamentul componentelor din program.
O clasă poate conține:
O clasă servește ca schelet pentru crearea instanțelor (denumite și obiecte la runtime), care implementează structura clasei. Fiecare instanță este o copie individuală a clasei.
Declararea unei clase se face folosind cuvântul cheie class
și un nume ales de tine.
După cum am menționat mai sus, clasele conțin:
În contextul unei clase avem totuși următoarele denumiri speciale:
package objects.examples; // pachet în care se află clasa // Declararea unei clase class Apple { // Variabilele clasei float mass; float diameter = 1.0f; int x, y; // Metoda clasei boolean isFalling() { // Cod pentru logica metodei return false; } }
diameter
poate folosi variabila mass
la inițializare, dar variabila mass
nu poate folosi diameter
pentru inițializare pentru că ea nu există încă).
Prin instanțierea unei clase se înțelege crearea unui obiect care corespunde unui șablon definit.
Procesul de inițializare implică: declarare, instanțiere și atribuire.
După ce am definit clasa Apple
, putem crea obiecte Apple
, folosind următoarea regulă:
Class
numeVar
= new
Class()
.
Membrul stâng până la egal conține referința de tip Class
, iar membrul drept conține instanța de tip Class
.
Keyword-ul new
este folosit pentru crearea instanței sau crearea obiectului, conform următorului exemplu:
Apple a1; // declararea variabilei a1 = new Apple(); // instanțierea obiectului Apple a2 = new Apple(); // declarare și instanțiere pe aceeași linie
null
, deoarece nu a fost alocată memorie pentru acesta. Deci considerăm că un obiect poate să fie inițializat sau null
:
Apple a; System.out.println(a); // se va afișa "null"
Clasa String se poate instanția fără să se folosească keyword-ul new
, fiind un caz special:
String s1, s2; s1 = "My first string"; s2 = "My second string";
Aceasta este varianta preferată pentru instanțierea string-urilor. De remarcat că și varianta următoare este corectă, dar ineficientă, din motive ce vor fi explicate în următoarele laboratoare.
s = new String("str");
Odată creat obiectul, putem accesa variabilele și metodele sale folosind dot notation .
după numele variabilei:
package objects.examples; public class PrintAppleDetails { // Metoda main public static void main(String args[]) { // Creăm un obiect "Apple" Apple a1 = new Apple(); // Printarea câmpurilor System.out.println("Apple a1:"); System.out.println(" mass: " + a1.mass); System.out.println(" diameter: " + a1.diameter); System.out.println(" position: (" + a1.x + ", " + a1.y +")"); // Folosirea unei metode System.out.println(" is falling: " + a1.isFalling()); } }
bool
→ false
, float
→ 0.f
). Din acest motiv, variabila membru mass
are valoarea 0
.
Metodele sunt blocuri de cod care descriu acțiuni sau comportamente ale obiectelor.
Exemple:
main()
- punctul de intrare al programului;printDetails()
- afișează informații despre un obiect;moveTo()
- modifică poziția unui obiect.Din punct de vedere al caracteristicilor, metodele pot:
void
dacă nu returnează nimic);O variabilă locală este:
void myMethod() { int bar; bar = 42; // trebuie să primească o valoare înainte de a fi folosită System.out.println(bar); }
Java nu permite folosirea unei variabile locale fără inițializare sigură.
Un exemplu de cod greșit este următorul:
int bar; if (cond) { bar = 42; } bar++; // eroare: bar poate fi neinițializat
Pentru a evita erori, fie ne asigurăm că inițializăm variabila din start, fie ne asigurăm că primește o valoare pe toate ramurile posibile (else
, return
etc.).
Dacă o metodă are parametri cu același nume ca variabilele care aparțin instanței, parametrii ascund acele variabile.
class Apple { int x, y; void moveTo(int x, int y) { System.out.println("Moving apple to " + x + ", " + y); } }
În acest exemplu, x
și y
din metodă sunt parametrii, nu variabilele ale instanței.
Pentru a accesa variabilele care aparțin instaței, trebuie să folosim keyword-ul this
, prezentat mai jos.
Supraincărcarea metodelor (Method Overloading) reprezintă abilitatea unei clase de a defini mai multe metode cu același nume, dar cu liste de argumente diferite (prin număr, tip sau ordine).
Compilatorul determină care versiune a metodei trebuie apelată în funcție de semnătura apelului, verificând tipurile și numărul parametrilor transmiși.
class Calculator { int sum(int a, int b) { return a + b; } double sum(double a, double b) { return a + b; } int sum(int a, int b, int c) { return a + b + c; } }
public class Test { public static void main(String[] args) { Calculator calc = new Calculator(); System.out.println(calc.sum(2, 3)); // apelează versiunea int,int System.out.println(calc.sum(2.5, 3.7)); // apelează versiunea double,double System.out.println(calc.sum(1, 2, 3)); // apelează versiunea cu 3 parametri } }
Un alt exemplu cu care sunteți deja familiari este chiar metoda print()
în care puteți introduce mai multe tipuri de argumente.
class PrintStream { void print(char[] arg) { ... } void print(int arg) { ... } void print(double arg) { ... } // etc. }
System.out.print(232.4f); // apelează print(float) System.out.print(123); // apelează print(int) System.out.print(true); // apelează print(boolean)
Compilatorul Java folosește următorul algoritm pentru supraîncărcare:
int
→ long
).
Un constructor este o metodă specială care:
Dacă nu este definit niciun constructor, compilatorul Java adaugă un constructor implicit fără parametri.
class SpecialDate { int day = 1; SpecialDate(int day) { this.day = day; } }
// Nu funcționează pentru că nu a fost generat constructorul default SpecialDate date = new SpecialDate(); // Funcționează pentru că am definit acest constructor SpecialDate date2 = new SpecialDate(30);
Java permite existența mai multor constructori în aceeași clasă, cu semnături diferite. Alegerea constructorului potrivit se face la compilare, conform regulilor de selecție a metodelor supraincărcate.
class Car { String model; int doors; Car(String model, int doors) { this.model = model; this.doors = doors; } Car(String model) { this(model, 4); // apel către alt constructor din aceeași clasă } }
În Java, există conceptul de copy constructor, acesta reprezentând un constructor care ia ca parametru un obiect de același tip cu clasa în care se află constructorul respectiv. Cu ajutorul acestui constructor, putem să copiem obiecte, prin copierea membru cu membru în constructor.
public class Student { private String name; private int averageGrade; public Student(String name, int averageGrade) { this.name = name; this.averageGrade = averageGrade; } // copy constructor public Student(Student student) { // name este camp privat, noi il putem accesa direct (student.name) // deoarece ne aflam in interiorul clasei this.name = student.name; this.averageGrade = student.averageGrade; } }
student.name
poate fi accesat deoarece ne aflăm în interiorul aceleași clasei. Chiar dacă obiectul student
nu se referă la instanța curentă (aceasta fiind this
), compilatorul Java ne permite să avem acces la membrii privați în această situație specială.
Java gestionează automat distrugerea obiectelor printr-un proces numit Garbage collection (GC). Astfel, programatorul nu trebuie să elibereze manual memoria, evitând erori frecvente precum memory leaks.
Mecanismul de garbage collection are rolul de a elibera memoria ocupată de obiectele care nu mai sunt accesibile. Un obiect devine neaccesibil atunci când nu mai există nicio referință activă către el.
Date christmas = new Date("Dec 25, 2022"); christmas = null; // obiectul devine eligibil pentru colectare
Java poate detecta chiar și referințe circulare (obiecte care se referă reciproc), marcându-le corect pentru colectare.
Keyword-ul this
este o referință la obiectul curent, adică la instanța clasei care execută metoda.
this
se folosește pentru:
1. Dezambiguizare, prin accesarea la variabile care aparțin instanței ce sunt ascunse de parametri:
class Apple { int x, y; void moveTo(int x, int y) { this.x = x; // accesează variabila obiectului curent this.y = y; } }
this
în mod excesiv. În Java este recomandat să folosim this
doar când vrem să scăpăm de shadowing.
2. Pasarea referinței la obiectul curent altor metode:
class Group { private int numberStudents; private Student[] students; public Group () { numberStudents = 0; students = new Student[10]; } public boolean addStudent(String name, int grade) { if (numberStudents < students.length) { // Pasăm "this" pentru parametrul de tip "Group" students[numberStudents++] = new Student(this, name, grade); // (1) return true; } return false; } }
class Student { private String name; private int averageGrade; private Group group; public Student(Group group, String name, int averageGrade) { this.group = group; // (2) this.name = name; this.averageGrade = averageGrade; } }
this
ca argument am pasat de fapt o referință la instanța curentă a obiectului de tip Group
care va fi memorată în obiectul Student
, astfel am creat o legătură directă între un obiect de tip Student
și un obiect de tip Group
.
3. Apelul către un alt constructor din aceeași clasă:
Keyword-ul this
are un al treilea scenariu în care poate fi folosit, concret apelul către un alt constructor din aceeași clasă se face cu this(…)
și trebuie să fie prima instrucțiune din constructorul curent.
class Car { String model; int doors; Car(String model, int doors) { this.model = model; this.doors = doors; } Car(int doors) { if (doors < 2) { System.out.println("Is this the batmobile?"); } this(model, 4); // eroare, apelul "this" nu se află pe prima linie din constructor } }
this()
să nu se afle pe prima linie din constructor.
this
nu poate fi folosit, deoarece this
se referă la instanța curentă a clasei, iar la momentul executării codului static niciun obiect nu există.
În limbajul Java (şi în majoritatea limbajelor de programare de tipul OOP), orice clasă, atribut sau metodă posedă un specificator de acces, al cărui rol este de a restricţiona accesul la entitatea respectivă, din perspectiva altor clase.
Specificator | Definitie |
---|---|
private | limitează accesul doar în cadrul clasei curente |
default | accesul este permis doar în cadrul pachetului (package private) |
protected | accesul este permis doar în cadrul pachetului si în clasele ce moștenesc clasa curentă |
public | permite acces complet |
protected
.
Până în acest moment, putem aplica specificatori de acces pentru clase, metode și câmpuri.
Element / Modificator | public | protected | default | private |
---|---|---|---|---|
Clasă (top-level) | ✅ Vizibilă din orice pachet | ❌ Nu este permis | ✅ Vizibilă doar în același pachet | ❌ Nu este permis |
Metodă | ✅ Accesibilă de oriunde | ✅ Accesibilă în același pachet și în subclase | ✅ Accesibilă doar în același pachet | ✅ Accesibilă doar în clasa curentă |
Câmp (atribut) | ✅ Accesibil de oriunde | ✅ Accesibil în același pachet și în subclase | ✅ Accesibil doar în același pachet | ✅ Accesibil doar în clasa curentă |
package org.poo // Acces "public" vizibilă în orice pachet public class Student { private String fullName = "Ion Popescu"; public String prefferedName = "Ion"; float avgGrade = 9.4f; int getAvgRoundedGrade() { return (int)(avgGrade + 0.5f); } public String getPromotedMsg() { return avgGrade > 5.f ? "Congrats, you passed!" : "Oh no..."; } }
package org.poo.main public class Main { public static void Main(String[] args) { // Avem acces la această clasă fiind declarată în același fișier Student st = new Student(); // Eroare de compilare (private) System.out.println(st.fullName); // Avem acces (public) System.out.println(st.prefferedName); // Eroare de compilare pentru că nu ne aflăm în același pachet (default) System.out.println(st.getAvgRoundedGrade()); // Avem acces (public) System.out.println(st.getPromotedMsg()); } }
Încapsularea (Encapsulation) reprezintă reunirea într-o clasă a atributelor și metodelor specifice unei anumite categorii de obiecte. Totodată, acest concept presupune ascunderea stării interne a obiectului (atribute și valorile lor) și controlul accesului la acestea exclusiv prin intermediul metodelor clasei.
Încapsularea conduce la izolarea modului de implementare a unei clase (atributele acesteia şi cum sunt manipulate) de utilizarea acesteia. Utilizatorii unei clase pot conta pe funcţionalitatea expusă de aceasta, indiferent de implementarea ei internă (chiar şi dacă se poate modifica în timp).
private
sau protected
). Astfel, se evită situațiile în care un coleg ar modifica accidental părți ale codului care nu ar trebui să fie accesibile public.
În secțiunile de mai sus am menționat că o clasă poate conține și proprietăți. O proprietate este un câmp (membru) căruia i se atașează două metode ce îi pot expune sau modifica starea. Aceste doua metode se numesc getter
si setter
.
class PropertiesExample { String myString; String getMyString() { return myString; } void setMyString(String myString) { this.myString = myString; } }
Declarăm un obiect de tip PropertiesExample
și îi inițializăm membrul myString
astfel:
PropertiesExample pe = new PropertiesExample(); pe.setMyString("This is my string!"); System.out.println(pe.getMyString());
O întrebare firească este dacă avem declarat un getter
și setter
pentru un câmp private
, nu este ca și cum acel câmp ar fi public
?
Getter-ele și setter-ele, chiar și în forma lor simplă, au efectul de a controla accesul la variabilele interne ale unei clase. În plus, ele pot include mecanisme de validare sau sanitizare, astfel încât să expună în siguranță variabila internă, reducând semnificativ riscul de utilizare incorectă sau de corupere a datelor.
public class BankAccount { private double balance; // variabilă internă, încapsulată public boolean isConfidential = false; // Getter - permite citirea soldului public String getBalance() { // returnăm valoarea doar dacă clientul ne permite interogarea return isConfidential ? "Cannot access balance." : "Your balance is: " + balance; } // Setter - permite modificarea soldului, cu validare public void setBalance(double balance) { if (balance >= 0) { // validare: soldul nu poate fi negativ this.balance = balance; } else { System.out.println("Error: Balance cannot be negative for debit cards!"); } } public static void main(String[] args) { BankAccount account = new BankAccount (); account.setBalance(1000); // valid System.out.println(account.getBalance()); // 1000.0 account.setBalance(-500); // invalid System.out.println(account.getBalance()); // 1000.0 (nu s-a schimbat) account.isConfidential = true; System.out.println(account.getBalance()); // null } }
În Java există o separare fundamentală între:
int
, double
, boolean
, char
etc.);java.lang.Object
.
Obiectele oferă funcționalități suplimentare sub forma unor metode (rotateLeft()
, toHex()
etc.), dar aceste metode trebuie stocate în memorie pentru fiecare instanță creată. De asemenea, obiectele trebuie eliberate din memorie periodic (mai multe detalii în următoarele secțiuni), ceea ce face execuția mai lentă.
Din aceste motive, Java a ales să păstreze tipurile primitive pentru a evita costurile suplimentare ale obiectelor, mai ales în calculele numerice intensive.
numNodes == -1
) puteți folosi clasele de tip wrapper care permit și starea null
(ex. numNodes == null
).
Pentru a permite utilizarea valorilor primitive în contexte ce necesită obiecte (de exemplu, în colecții despre care vom vorbi în următoarele laboratore), Java oferă clase wrapper dedicate fiecărui tip primitiv.
Tip primitiv | Clasă wrapper corespunzătoare |
---|---|
void | java.lang.Void |
boolean | java.lang.Boolean |
char | java.lang.Character |
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
Un obiect wrapper poate fi construit:
Float pi = new Float(3.14); Float pi2 = new Float("3.14");
Dacă șirul nu poate fi convertit într-o valoare numerică validă, se afișează o eroare la run-time de tip NumberFormatException
.
Toate clasele numerice (Integer, Double, Float etc.) au la dispoziție metode pentru conversia valorii interne în alte forme primitive:
Double size = new Double(32.76); double d = size.doubleValue(); // 32.76 float f = size.floatValue(); // 32.76f long l = size.longValue(); // 32L int i = size.intValue(); // 32
Aceste metode sunt echivalente cu operațiile de conversie explicită (cast) între tipurile primitive.
Începând cu Java 5, conversia între tipurile primitive și wrapper se face automat:
int primitiveValue = 42; Integer objectValue = primitiveValue; // autoboxing automat de la int → Integer Integer anotherObject = new Integer(99); int anotherPrimitive = anotherObject; // unboxing automat de la Integer → int Integer a = 10; Integer b = 20; int sum = a + b; // a și b sunt unboxed automat, apoi rezultatul e autoboxed dacă e atribuit unui Integer
Compilatorul inserează conversiile în mod implicit, făcând codul mai concis și mai lizibil.
De fiecare dată când o metodă este apelată, JVM creează un stack frame care conține:
Când metoda se încheie, frame-ul este eliminat, iar memoria este eliberată automat.
public void test() { int x = 10; // stocat pe stack Student s; // referință către un obiect Student (dar neinițializată) }
Variabilele simple (de tip primitiv) sunt stocate direct în stivă, iar variabilele de tip obiect conțin doar o referință către adresa din heap.
Obiectele în Java sunt stocate în heap, o zonă de memorie dedicată alocărilor dinamice.
Pentru a crea un obiect, folosim operatorul new
, care:
public class Student { private String name; private int grade; public Student(String name, int grade) { this.name = name; this.grade = grade; } public String getName() { return name; } public int getGrade() { return grade; } }
public void test() { Student st = new Student("Mihai", 8); }
În exemplul de mai sus:
st
este pe stack;Student
este alocat pe heap;st
.
Când metoda în care a fost creat obiectul se termină, referința st
dispare, dar obiectul rămâne în heap până când Garbage Collector-ul decide că nu mai este utilizat.
Pentru a lucra cu obiectele din heap, avem nevoie de adresa la care sunt stocate. În limbaje precum C, această adresă este memorată într-un pointer, oferind control direct asupra memoriei.
Java nu permite pointeri direcți, pentru a evita erorile de acces și scurgerile de memorie. În schimb, folosește referințe, care funcționează asemănător, dar sunt gestionate automat de JVM.
Date date; // referință de tip "Date"
Dacă referința nu a fost încă asociată unui obiect, valoarea sa este null
, iar accesarea membrilor va produce o eroare de tip NullPointerException
.
În Java, toți parametrii sunt transmiși prin copiere. Diferența apare în ce anume se copiază:
class TestParams { static void changeReference(Student st) { st = new Student("Bob", 10); // schimbă doar copia referinței } static void changeObject(Student st) { st.averageGrade = 10; // modifică obiectul real din heap } public static void main(String[] args) { Student s = new Student("Alice", 5); changeReference(s); // (1) System.out.println(s.getName()); // "Alice" – obiectul nu a fost înlocuit changeObject(s); // (2) System.out.println(s.averageGrade); // "10" – atributul a fost modificat } }
În apelul (1), metoda changeReference
schimbă doar copia referinței, nu și referința originală.
În apelul (2), metoda modifică structura internă a obiectului, deci schimbarea este vizibilă și după apel.
După cum am putut observa până acum, de fiecare dată când creăm o instanță a unei clase, valorile câmpurilor din cadrul instanței sunt unice pentru aceasta și pot fi utilizate fără pericolul ca instanţierile următoare să le modifice în mod implicit.
Apple a1 = new Apple(); Apple a2 = new Apple(); a1.mass = 12; a2.mass = 15; System.out.println("Masa mărului a1 este: " + a1.mass); System.out.println("Masa mărului a2 este: " + a2.mass);
După cum se poate observa, a1
și a2
vor funcționa ca entități independente una de cealaltă, astfel că modificarea câmpului mass
din a1
nu va avea nici un efect implicit și automat în a2
. Există totuși situații când dorim să creăm câmpuri care să fie partajate și să nu fie memorate separat pentru fiecare instanță.
Membrii statici nu aparțin unei instanțe anume, ci clasei în sine. Aceștia sunt împărtășiți de-a lungul tuturor obiectelor create din acea clasă și pot să fie accesate fără să se creeze o instanță, având o locație specială în memorie (diferită de Heap și Stack).
Când declarați o variabilă sau o metodă înăuntrul unei clase:
a1.mass
și a2.mass
pot avea valori diferite).Tip | Apartenență | Accesat prin | Cum este valoarea reținută în memorie |
---|---|---|---|
Variabilă a instanței | Obiect | a1.mass | Variază de la obiect la obiect |
Variabilă statică | Clasă | Apple.gravAcc | Împărtășită de toate obiectele |
Pentru a declara o variabilă statică, se folosește keyword-ul static
:
class Apple { // variabilele instanței float mass; float diameter; // variabilă statică static float gravAcc = 9.8f; }
În cazul de mai sus, gravAcc
este stocat o dată per clasă, nu per instanță. Dacă îi modificați valoarea, schimbarea se va reflecta pentru toate obiectele de tip Apple
.
Apple a1 = new Apple(); Apple.gravAcc = 3.7f; // acum toate merele cad pe Marte! Apple a2 = new Apple; System.out.println("Apple a1: " + a1.gravAcc); System.out.println("Apple a2: " + a2.gravAcc);
Membrii statici pot fi accesați în două moduri:
1. Dinăuntrul aceleași clasei, direct după nume:
class Apple { // variabilele instanței float mass; float diameter; // variabilă statică static float gravAcc = 9.8f; float getWeight() { return mass * gravAcc; } }
2. Dinafara clasei, folosind numele clasei:
a1.gravAcc
), acest lucru nu este recomandat, deoarece reduce claritatea codului și pentru că o variabilă statică aparține unei clase, nu unei instanțe. Este mai corect să folosim direct numele clasei Apple.gravAcc
.
Pentru a defini constante care nu se schimbă niciodată, combinați static
cu keyword-ul special final
:
class Apple { static final float EARTH_ACC = 9.8f; }
static
→ aparține claseifinal
→ nu poate fi modificat o dată ce a fost asignat
_
(ex. EARTH_ACC
, MAX_SPEED
etc.).
final
are mult mai multe funcționalități pe care le vom discuta în laboratoarele următoare.
Puteți accesa constantele din orice loc datorită proprietății lor statice:
float g = Apple.EARTH_ACC;
Pentru a facilita inițializarea câmpurilor statice pe care o clasă le deține, limbajul Java pune la dispoziție posibilitatea de a folosi blocuri statice de cod. Aceste blocuri de cod sunt executate atunci când clasa în cauză este încărcată de către mașina virtuală de Java.
Încărcarea unei clase se face în momentul în care aceasta este referită pentru prima dată în cod (se creează o instanță, se apelează o metodă statică etc.)
În consecință, blocul static de cod se va executa întotdeauna înainte ca un obiect să fie creat.
class TestStaticBlock { static int staticInt; int objectFieldInt; static { staticInt = 10; System.out.println("static block called"); } }
class Main { public static void main(String args[]) { /* Chiar dacă nu am creat o instanță a clasei TestStaticBlock, blocul static tot este executat și rezultatul va fi "10" */ System.out.println(TestStaticBlock.staticInt); } }
static
pentru constante, contoare, configurații globale sau metode utilitare (ex. Math.sqrt()
). Nu folosiți foarte des static
, deoarece poate strica încapsularea ceea ce va face codul mai greu de testat.
O metodă statică este o metodă care aparține clasei și poate fi apelată fără a crea un obiect al clasei, având acces doar la membri statici.
class Apple { public static final int SMALL = 0, MEDIUM = 1, LARGE = 2; public int size; // Funcționează public static String[] getAppleSizes() { return new String[] { "SMALL", "MEDIUM", "LARGE" }; } // Eroare, "size" nu este static public static int getSize() { return size; } }
size
, nu ar fi încă inițializată în momentul în care metoda statică getSize()
este apelată.
Să se creeze o clasă numită Complex, care are doi membri de tip int
(real
și imaginary
), care vor fi de tip private
. Realizați următoarele subpuncte:
int
(primul desemnează partea reală a unui număr complex, al doilea partea imaginară), al doilea nu primește niciun parametru și apelează primul constructor cu valorile 0 și 0, iar al treilea reprezinta un copy constructor, care primește ca parametru un obiect de tip Complex, care este copiat în obiectul thisaddWithComplex
, care primește ca parametru un obiect de tip Complex, prin care se adună numărul complex dat ca parametru la numărul care apelează funcția (adică la this)showNumber
, prin care se afișează numărul complex astfel:Pe Code Devmind, aveți in clasa Student si Main două greșeli legate de referințe. Rolul vostru este să corectați aceste greșeli încât codul să aibă comportamentul dorit (există comentarii în cod despre modul cum ar trebui să se comporte).
Să se implementeze o clasă Point care să conțină:
Să se implementeze o clasă Polygon cu următoarele:
Sa se creeze clasa ContBancar care are urmatoarele campuri:
Hint: fiecare detinator de cont are aceeasi dobanda
Realizati urmatoarele: