ALGO25 / BLOG

Interviu Java mid: colecții și streamuri

Pregătește interviul Java mid cu un exemplu de groupingBy: contracte de date, ordinea rezultatelor, complexitate și exerciții cu răspunsuri explicate.

La nivel mid, o problemă de colecții devine o discuție despre contracte: ce intră, ce iese, în ce ordine și cine poate modifica rezultatul. Un răspuns bun arată că poți implementa o funcționalitate și că îi controlezi cazurile limită.

Ce pregătești la acest nivel

Pornește de la egalitatea și colecțiile de la nivel junior. Continuă cu generice, colecții, streamuri, tratarea erorilor și teste. După acestea, pregătește separat accesul la date și granițele tranzacțiilor. Aici lucrăm pe o transformare de date în memorie, în Java 21+, fără framework.

Scopul nu este să transformi fiecare buclă într-un stream. Scopul este să alegi forma pe care un coleg o poate înțelege și modifica fără să piardă contractul funcției. La interviu, spune întâi rezultatul dorit și apoi explică mecanismul.

Cerința și contractul

Primești etichete prin argumentele programului. Numără aparițiile fiecărei etichete și afișează grupurile în ordinea primei apariții. Comparația este exactă; java și Java sunt două chei diferite. Argumentele nu sunt null, iar fără argumente nu afișăm nimic. Nu citim o bază de date și nu folosim un flux paralel.

Aceste precizări elimină alegeri ascunse. Dacă cerința ar solicita ordine alfabetică, am schimba colecția sau am sorta explicit rezultatul. Dacă datele ar veni dintr-un API, ar trebui să precizăm și comportamentul pentru câmpuri lipsă ori valori invalide.

Soluție cu groupingBy și counting

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        Map<String, Long> counts = Arrays.stream(args).collect(
            Collectors.groupingBy(
                Function.identity(),
                LinkedHashMap::new,
                Collectors.counting()
            )
        );
        counts.forEach((label, count) ->
            System.out.println(label + "=" + count));
    }
}

Salvează în Main.java, compilează cu javac Main.java și rulează java Main java react java sql react java. Rezultatul este java=3, react=2, sql=1, pe linii separate.

Clasificatorul Function.identity() folosește eticheta însăși drept cheie. Fabrica LinkedHashMap::new alege o hartă cu ordine de inserare. Colectorul din aval, counting(), produce câte un Long pentru fiecare grup. Această suprasarcină este descrisă în documentația Collectors.

De ce contează detaliile

Nu presupune că forma simplă groupingBy(classifier) garantează o anumită implementare a hărții sau ordinea dorită. În exemplu am făcut alegerea explicită și am păstrat fluxul secvențial. Rezultatul este modificabil: tipul declarat Map nu îl face imuabil. Dacă îl expui altui modul, stabilește cine îl poate modifica și dacă ordinea trebuie păstrată în continuare.

O buclă care folosește merge(label, 1L, Long::sum) este o alternativă clară. Poate fi mai ușor de instrumentat când trebuie să raportezi date invalide sau să întrerupi procesarea. Un stream exprimă bine transformări succesive, dar efectele secundare ascunse în funcțiile lui fac analiza mai dificilă.

Pentru n etichete și k etichete distincte, facem în medie O(n) operații pe hartă, presupunând o distribuție bună a codurilor hash. Păstrăm O(k) intrări. Costul prelucrării șirurilor depinde și de lungimea lor. Nu afirma că parallelStream() accelerează automat această operație: combinarea rezultatelor și dimensiunea datelor pot anula câștigul.

Exersează

  1. Pentru x x y X, scrie rezultatul înainte de rulare. Răspuns: x=2, y=1, X=1.
  2. Implementează aceeași cerință cu o buclă și merge. Compară ieșirile pentru input gol, o etichetă, duplicate și ordine diferită.
  3. Schimbă cerința în „doar etichetele cu cel puțin două apariții”. Filtrează după numărare, nu înainte; altfel nu ai încă informația necesară.

Autoevaluare și pasul următor

Poți continua dacă explici cele trei argumente ale colectorului, proprietatea asupra rezultatului și testele care detectează o ordine greșită. Un răspuns mid include și o alternativă mai simplă, cu motive pentru alegere. Urmează Java senior: concurență și limite.

Întrebări frecvente

Un stream modifică automat colecția sursă?

Operația din exemplu construiește o hartă nouă. Nu modifica sursa în timpul parcurgerii; efectele secundare pot încălca ipotezele operației.

De ce numărul este Long?

Acesta este tipul rezultatului produs de Collectors.counting(). Tipurile din semnătura metodei fac parte din contract, chiar dacă exemplul are puține valori.

Pot reutiliza același stream după collect?

Nu. O operație terminală consumă streamul. Creează un stream nou din sursă când ai nevoie de o nouă parcurgere.

Este parallelStream o optimizare sigură?

Nu o introduce fără măsurători și fără verificarea ordinii, a efectelor secundare și a costului combinării. Exemplul rămâne intenționat secvențial.

Este obligatoriu Spring pentru acest articol?

Nu. Colecțiile și contractele sunt independente de framework și trebuie explicate înainte de adăugarea infrastructurii unui serviciu.

Surse și versiuni

Java 21+. Consultă streamuri și ordinea întâlnirii elementelor și LinkedHashMap. Surse consultate la 12 octombrie 2026.