eh in realtà per i confronti con contains (visto che lo usa per la struttura dati) bisognerebbe anche implementare hashcode.
Due oggetti si dicono uguali se hanno uguale hashcode e equals restituisce true. Se equals restituisce true, ma gli hashcode sono differenti, allora gli oggetti sono differenti.
E' buona norma quindi fare l'override di entrambi i metodi, facendo attenzione di considerare per entrambi gli stessi campi.

Altra nota: quando usi le risorse (di qualsiasi tipo) impara a suddividere tutto in 3 fasi
1.apertura risorsa
2.uso risorsa
3.chiusura risorsa

spesso questa fatta in un blocco final per essere sicuri che IN OGNI CASO venga eseguita.
Un ultima osservazione: Esame ha diversi costruttori, uno prende 2 parametri, uno 3.
Ha senso il avere 0 per i crediti formativi? Ha senso per il voto, significa che ancora non hai dato l'esame, per i crediti formativi no. Andrebbe quindi considerato un errore (quanto meno un IllegalArgument) avere inserito 0 come crediti formativi