La prochaine fois, fourni plutôt le code via pastebin ou un service du même tonneau, c'est plus pratique
Vous êtes en train de voir l'héritage, non ?
a1 est déclarée A et est de type A.
a2 est déclarée A et est de type B.
b est déclarée B et est de type B.
Toute la difficulté ici est de déterminer quelle méthode est appelée suivant le type.
a1.f(a1) => A et A. Les deux sont des A, pas besoin d'aller plus loin.
a1.f(a2) => a1 est déclarée A et est A, a2 est déclarée A et est B. La méthode f appelée est celle de A. Nous avons ici pour seule certitude que a2 est A donc "A et A".
a2.f(a1) => a2 est déclarée A et est B, a1 est déclarée A et est A. a2 est B, donc c'est la méthode f de B qui est appelée. a1 étant A => "B et A".
a2.f(a2) => même explication que pour a1.f(a2), sauf que la méthode appelée est celle de B.
a2.f(b) => a2 est déclarée A et est B, b est déclarée B et est B. a2 est B, donc c'est la méthode f de B qui est utilisée. b est B donc => "B et B".
b.f(a2) => même explication que pour a1.f(a2), sauf que c'est la méthode f de B qui est invoquée.
Si tu comptes passer un jour les certifications Java, ce type de question (en plus complexe) est fréquent.