Prin acest test am urmărit verificarea înțelegerii conceptelor de bază studiate la laborator, în special cum se aplică conceptele OOP în Java (polimorfism, moștenire, agregare, constructori, abstractizare), structurile de date cu care puteți lucra în Java și utilitatea design patterns.
Au fost 20 de întrebări, 4 variante de răspuns, un singur răspuns corect. 4 numere, aceleași 20 de întrebări în ordine diferită.
Metoda de evaluare: grilă franceză, -1/4 din punctajul unei întrebări la răspuns greșit, 0 dacă nu este marcat niciun răspuns.
Analizăm aici întrebările pe rând, structurat pe secțiuni.
1. Care dintre următoarele cuvinte cheie realizează moștenirea în Java?
R: Cuvântul cheie extends este folosit pentru relația de moștenire dintre clase sau dintre interfețe. implements este folosit când o clasă implementează o interfață, iar super pentru a apela constructorii clasei extinse sau metodele acesteia.
2. Care dintre următoarele concepte reprezintă o relație HAS-A?
R: Agregarea reprezintă o relație HAS-A (vedeți detalii în laboratorul Agregare și moștenire), o clasă ținând referință către obiecte ale altei clase. Moștenirea este o relație IS-A, polimorfismul reprezintă abilitatea unei clase să se comporte ca o altă clasă de pe lanțul de moștenire, iar încapsularea se referă la felul în care împachetăm informațiile dintr-o clasă (ce se poate accesa din exterior).
3. Care variantă reprezintă o supraîncărcare corectă pentru metoda:
protected int getGrade(String course)
protected int getGrade(String course) throws IOException
private int getGrade(String course)
protected long getGrade(String course)
public long getGrade(int studID)
R: Regula de bază a suprîncărcarii(overloading-ului) este că numele metodei rămâne același dar antetul acesteia se schimbă, adică diferă numărul parametrilor și/sau tipul acestora. Variantele de răspuns greșite au lăsat parametrul identic, schimbând doar modificatorul de acces, excepțiile aruncate și tipul de return, acest lucru nefiind suficient. Detalii despre overloading găsiți în laboratorul More OOP & Visitor Pattern.
4. Ce se afișează?
public class Test { public static void main(String []args) { Drink tea = new Tea(); tea.make(); } } class Drink { public static void make() { System.out.println("Making drink"); } } class Tea extends Drink { public static void make() { System.out.println("Making tea"); } }
R: Metodele statice țin de clasă și nu de instanță, deci pentru ele nu are sens suprascrierea (overriding-ul). La execuția main-ului se va apelea metoda make din clasa Tea, același comportament obținându-l și dacă apelam Drink.make(). Sintaxa Java permite să apelați metodele statice și pe instanță dar nu recomandăm acest lucru pentru că afectează lizibilitatea codului, putând fi confundate la o citire rapidă cu metodele ne-statice.
5. Ce se afișează?
public class BasicInit { private int x; private boolean flag; protected String s; @Override public String toString() { return x + " " + flag + " " + s; } public static void main(String []args) { BasicInit basicInit = new BasicInit(); System.out.println(basicInit); } }
R: Java inițializează variabilele instanțelor cu valori default: tipurile primitive primesc 0, boolean iar referințele la obiect cu null (vedeți laboratorul Constructori și referințe).
6. Ce se afișează?
class Device { public Device() { System.out.print("D"); } } public class Watch extends Device { public Watch() { System.out.print("W"); } public Watch(String name) { this(); System.out.print(name); } public static void main(String []args) { new Watch("F"); } }
R: Se afișează DWF datorită apelului constructorilor pe ierarhia de moștenire. În constructorul cu parametru din Watch se apelează explicit constructorul său fără parametru (apelul this()), iar în acesta se apelează implicit constructorul clasei părinte, Device, care apelează implicit constructorul din Object. Apoi ne întoarcem pe lanțul de apeluri și se afișează D, apoi W, apoi F.
7. Ce înseamnă constructorul implicit (default)?
R: Dacă nu adăugați vreun constructor în clasa pe care ați creat-o, atunci Java vă adaugă un constructor default, vedeți laboratorul Constructori și referințe.
8. Ce cuvinte cheie pot fi folosite pentru membrul MY_VALUE?
public interface Status { /* insert qualifier here */ int MY_VALUE = 10; }
R: Implicit, variabilele declarate în interfețe sunt final, static și public. Atunci când le declarați puteți scrie toate aceste cuvinte cheie sau le puteți omite (IDE-ul chiar vă sugerează să le omiteți). Ele sunt publice pentru ca este vorba de o interfață. Sunt statice din motive de încapsulare, altfel am declara variabile ale instanței publice. Sunt finale pentru a ne asigura că vor fi constante și nu li se vor atribui alte valori (e.g. dacă ar fi doar public static, atunci două sau mai multe clase care implementează interfața vor putea modifica la comun acea variabilă).
9. Care variantă definește cel mai bine legătura dintre interfețe și clase?
R: Self-explanatory
10. Dacă B extinde clasa abstractă A și C extinde B, atunci care instanțiere este corectă?
R: Varianta corectă este A ab = new B()
. Restul variantelor sunt incorecte pentru că nu putem atribui unei referinte de un anumit tip un obiect al supertipului. Dacă s-ar permite așa ceva, atunci la runtime, când apelăm o metodă a lui C, obiectul nu o va avea, pentru ca e de tip B. Încă ceva greșit în acele variante este că încercăm să instanțiem o clasă abstractă, iar acestea, ca și interfețele, nu se instanțiază.
11. Care din următoarele afirmații sunt adevarate despre clase interne statice?
A. Nu au acces la membri non-statici ai clasei exterioare
B. Este nevoie de o instanță a clasei externe pentru a o instanția
C. Trebuie sa moștenească clasa exterioară
D. Trebuie instanțiată astfel: Outer out = new Outer(); Inner in = out.new Inner();
R: Clasele interne statice (nested classes) nu au nevoie de o instanță a clasei externe, la fel ca și câmpurile și metodele statice. Din acest motiv nici nu pot avea acces la membrii non-statici ai acesteia, afirmația A fiind corectă și B incorectă. Afirmația C este falsă, nu există această restricție. Afirmația D este falsă datorită exprimării “trebuie”.
12. Care dintre următoarele afirmații este adevărată cu privire la clasele anonime?
R: Clasele anonime nu au constructor, în laboratorul Clase interne puteți vedea mai multe detalii și exemple.
13. Ce se afișează?
Set<Integer> mySet = new LinkedHashSet<>(); mySet.add(1); mySet.add(10); mySet.add(100); System.out.println(mySet);
[10, 1, 100]
[100, 10, 1]
[1, 10, 100]
R: Implementările interfeței Set variază în funcție de următoarele caracteristici: dacă oferă un set ordonat sau nu, sau daca oferă un set sortat sau nu. Varianta HashSet oferă un set neordonat și nesortat. LinkedHashSet însă reține ordinea în care s-au adăugat elementele, fiind implementată folosind o listă dublu înlănțuită.
14. Ce colecție ar fi mai eficientă de folosit dacă dorim să stocăm o secvență de elemente pe care să o modificăm rar dar pe care să o accesăm foarte des?
R: ArrayList este mai eficientă de folosit dacă accesăm des elementele din ea pentru că are complexitate O(1) pt acces. Implementările intefeței List variază în funcție de structura de date folosită pentru reținerea elementelor, astfel oferind complexități diferite pentru acces și modificări, fapt menționat și în laboratorul Colecții. În plus, una din implementări, Vector, este ineficientă deoarece toate metodele sale sunt sincronizate, permițând unui singur thread să le acceseze la un moment dat (este și depracated pentru că acum există alternative thread-safe mai eficiente e.g. Collections#synchronizedList).
15. Care instanțiere este corectă?
Set<Integer> set = new HashSet<Object>();
HashSet<Integer> set = new Set<Integer>();
Set<Integer> set = new HashSet<Integer>();
HashSet<Object> set = new HashSet<Integer>();
R: Varianta corectă este Set<Integer> set = new HashSet<Integer>();
. Prima și ultima variantă sunt incorecte pentru că nu avem covarianță în cazul genericității, după cum este menționat și în laborator, dacă ChildType este un subtip (clasă descendentă sau subinterfață) al lui ParentType, atunci o structură generică GenericStructure<ChildType> nu este un subtip al lui GenericStructure<ParentType>. Această restricție se datorează faptului că genericitatea este aplicată la compilare, iar la runtime nu se fac verificări de tipuri.
A doua variantă este greșită pentru că Set e super tipul lui HashSet și Set este și interfață, deci nu poate fi instanțiată.
16. Ce este JUnit?
R: JUnit este framework-ul pentru unit testing studiat la laborator. Chiar dacă JUnit poate fi folosit împreună cu alte tool-uri și pentru integration tests (testează mai multe componente, e dependent de sisteme externe, e.g. o bază de date), scopul lui principal este unit testing-ul, iar la laborator s-a folosit pentru acest lucru.
17. Ce se afișează?
public class Test { int count = 0; void modifyCount() throws Exception { try { count++; try { count++; try { count++; throw new Exception(); } catch(Exception e) { count++; throw new Exception(); } finally { count++; } } catch(Exception e) { count++; } } catch(Exception e) { count++; } } public static void main(String[] args) throws Exception { Test test = new Test(); test.modifyCount(); System.out.println(test.count); } }
R: Întrebarea aceasta verifică înțelegerea conceptului de finally dintr-un bloc de prindere a excepțiilor. Ca și în alte limbaje (e.g. C#, Python, Javascript), și în Java, finally se execută de fiecare dată, după execuția blocului try sau catch. Urmărind codul, avem: try - count=1 —> try - count=2 —> try - count=3 —> catch count=4 —> finally - count=5 —> catch (pt ca primul catch aruncă excepția) count=6.
18. Care dintre următoarele patternuri ar fi mai util dacă dorim ca anumite clase să fie notificate de schimbări ale altor clase?
R: Folosind Observer putem avea obiecte “observabile” care atunci când își modifică starea să notifice alte obiecte dependente de acestea “observatori”.
19. Ce se afișează?
public class NetworkService { private static NetworkService instance = new NetworkService(); public String str; private NetworkService() { str = ""Hello, I am a string""; } public static NetworkService getService() { return instance; } public static void main(String[] args) { NetworkService a = NetworkService.getService(); NetworkService b = NetworkService.getService(); System.out.println(a.str == b.str); } }
R: NetworkService este implementat ca un Singleton, existând o singură instanță a sa. Variabilele a și b reprezintă același obiect, deci comparația referințelor == este true.
20. Pentru a modela mai multe butoane din interfața grafică cu o anumită funcționalitate (ce este executată la apăsare) și diverse proprietăți (icon, text, animație), este o buna idee de a folosi urmatorul design pattern:
R: Folosind Command Pattern putem modela ușor operațiile efectuate sub forma unor comenzi. Butoanele din GUI (interfața grafică a utilizatorului) și elementele din meniuri sunt de multe ori obiecte de tip Command. În plus față de abilitatea de a efectua comanda dorită, o acțiune poate avea o pictogramă asociată, un text sau altele. Hint ar fi trebuit să fie cuvântul “executată” , care duce cu gândul la metoda execute() specifică pattern-ului.