1. Metod distinct: dublikatları silirik
Praktiki tapşırıqlarda tez-tez təkcə məlumatı filtrələmək və çevirmək deyil, həm də kolleksiyadan yalnız unikal elementləri seçmək, nəticənin ölçüsünü məhdudlaşdırmaq və ya əksinə, ilk bir neçə elementi ötürmək tələb olunur. Məsələn:
- İstifadəçilərin unikal adlarının siyahısını almaq.
- Səhifədə göstərmək üçün yalnız ilk 10 qeydi götürmək.
- İlk 5 elementi ötürmək (məsələn, səhifələmə zamanı — “2‑ci səhifədən göstərmək”).
Belə tapşırıqlar üçün Stream API-də xüsusi metodlar var: distinct, limit, skip.
distinct necə işləyir?
distinct() metodu bütün dublikat elementləri silinmiş yeni bir axın (stream) qaytarır. Dublikatlar müvafiq sinfin equals və hashCode metodları ilə müəyyən edilir.
Təsəvvür edin ki, siz numizmatsınız və əsas kolleksiyada dublikatların olmaması vacibdir. Bütün təkrar sikkələr mübadilə fonduna gedəcək. Məhz distinct() əsas kolleksiyadan dublikatları ayırıb çıxarmağa qadirdir.
Nümunə: istifadəçi adlarının unikal siyahısı
List<String> names = List.of(
"Alisa", "Bob", "Alisa", "Eva", "Bob", "Denis", "Gleb", "Eva"
);
Unikal adların siyahısını alaq:
List<String> uniqueNames = names.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueNames);
// Çıxış: [Alisa, Bob, Eva, Denis, Gleb]
Nümunə: istifadəçilərin unikal email-ləri
Tutaq ki, bizdə User sinfi var:
public class User {
String name;
String email;
// Konstruktor, getter-lər, toString() — rahatlıq üçün
public User(String name, String email) {
this.name = name;
this.email = email;
}
@Override
public String toString() {
return name + " <" + email + ">";
}
}
Dublikat email-ləri olan istifadəçilərin siyahısı:
List<User> users = List.of(
new User("Alisa", "alice@mail.com"),
new User("Bob", "bob@mail.com"),
new User("Eva", "eva@mail.com"),
new User("Alisa2", "alice@mail.com"), // email dublikatı!
new User("Gleb", "gleb@mail.com"),
new User("Eva2", "eva@mail.com") // email dublikatı!
);
Əgər sadəcə users.stream().distinct() çağırsanız, dublikatlar silinməyəcək, çünki User obyektlərində standart olaraq equals və hashCode metodları override edilməyib. Bu halda distinct yalnız istinad səviyyəsində üst-üstə düşmələr üçün işləyir.
Həll: equals və hashCode metodlarını elə override edin ki, unikalıq email-ə görə müəyyən olunsun.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(email);
}
İndi:
List<User> uniqueUsers = users.stream()
.distinct()
.collect(Collectors.toList());
uniqueUsers.forEach(System.out::println);
// Alisa <alice@mail.com>
// Bob <bob@mail.com>
// Eva <eva@mail.com>
// Gleb <gleb@mail.com>
Vacib: Öz sinifləriniz üçün equals və hashCode-u həmişə override edin, əgər distinct-in normal işləməsini istəyirsiniz!
2. Metod limit: element sayını məhdudlaşdırırıq
limit(long maxSize) metodu başlanğıc axının ilk maxSize elementindən çox olmayan yeni axın qaytarır.
Analoji: Şirniyyat mağazasına gəldiniz və 100-dən yalnız 3 pirojqini dadmaq istəyirsiniz. limit(3) — sizə dəqiq ilk üçü verirlər, qalanları — “təəssüf, bu gün yox”.
Nümunə: ilk 3 ad
List<String> firstThree = names.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(firstThree);
// Çıxış: [Alisa, Bob, Alisa]
Tətbiqimizdən nümunə: top‑3 yeni istifadəçi
List<User> firstUsers = users.stream()
.limit(3)
.collect(Collectors.toList());
firstUsers.forEach(System.out::println);
// Alisa <alice@mail.com>
// Bob <bob@mail.com>
// Eva <eva@mail.com>
Sıralama ilə nümunə
Sıralama ilə kombinə etmək olar — məsələn, ən qısa email-ə malik top‑2 istifadəçini götürmək:
List<User> top2ShortEmail = users.stream()
.sorted(Comparator.comparingInt(u -> u.email.length()))
.limit(2)
.collect(Collectors.toList());
top2ShortEmail.forEach(System.out::println);
// Bob <bob@mail.com>
// Eva <eva@mail.com>
3. Metod skip: ilk elementləri ötürürük
skip(long n) metodu başlanğıc axının ilk n elementi ötürülmüş yeni axın qaytarır.
Nümunə: ilk 2 adı ötürmək
List<String> afterTwo = names.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println(afterTwo);
// Çıxış: [Alisa, Eva, Bob, Denis, Gleb, Eva]
Nümunə: səhifələmə (pagination)
Saytda çox vaxt, məsələn, “səhifə başına 3 istifadəçi” göstərmək lazımdır. İkinci səhifə üçün ilk 3-ü ötürmək, növbəti 3-ü götürmək lazımdır:
int pageSize = 3;
int pageNumber = 2; // İkinci səhifə
List<User> page = users.stream()
.skip(pageSize * (pageNumber - 1))
.limit(pageSize)
.collect(Collectors.toList());
page.forEach(System.out::println);
// Alisa2 <alice@mail.com>
// Gleb <gleb@mail.com>
// Eva2 <eva@mail.com>
4. distinct, limit, skip kombinasiyası
Bu metodları tapşırıqdan asılı olaraq kombinə etmək olar.
Nümunə: ardıcıllıqda üçüncüdən başlayaraq 2 unikal adı almaq
List<String> result = names.stream()
.distinct() // Dublikatları çıxarırıq: [Alisa, Bob, Eva, Denis, Gleb]
.skip(2) // Alisa və Bob-u ötürürük: [Eva, Denis, Gleb]
.limit(2) // Yalnız ikisini götürürük: [Eva, Denis]
.collect(Collectors.toList());
System.out.println(result);
// Çıxış: [Eva, Denis]
Filtrləmə ilə nümunə
Tutaq ki, tərkibində “a” hərfi olan ilk 2 unikal email-i almaq lazımdır:
List<String> emails = users.stream()
.map(user -> user.email)
.filter(email -> email.contains("a"))
.distinct()
.limit(2)
.collect(Collectors.toList());
System.out.println(emails);
// Çıxış: [alice@mail.com, eva@mail.com]
5. Praktika: tətbiq tapşırıqları
Tapşırıq 1. Uzunluğu 3 simvoldan böyük olan istifadəçi adlarının unikal siyahısını almaq
List<String> longUniqueNames = names.stream()
.filter(name -> name.length() > 3)
.distinct()
.collect(Collectors.toList());
System.out.println(longUniqueNames);
// Məsələn: [Alisa, Denis]
Tapşırıq 2. İstifadəçilər siyahısından 3‑cü və 4‑cü unikal email-i əldə etmək
List<String> thirdAndFourthEmail = users.stream()
.map(user -> user.email)
.distinct()
.skip(2)
.limit(2)
.collect(Collectors.toList());
System.out.println(thirdAndFourthEmail);
// Məsələn: [eva@mail.com, gleb@mail.com]
Tapşırıq 3. 10‑dan böyük olan ilk 5 unikal ədədi almaq
List<Integer> numbers = List.of(5, 12, 17, 5, 23, 17, 42, 19, 12, 8);
List<Integer> result = numbers.stream()
.filter(n -> n > 10)
.distinct()
.limit(5)
.collect(Collectors.toList());
System.out.println(result);
// Çıxış: [12, 17, 23, 42, 19]
6. Vizual sxem: əməliyyatların tətbiq sırası
graph TD
A[İlkin siyahı] --> B[filter]
B --> C[distinct]
C --> D[skip]
D --> E[limit]
E --> F[collect]
Şərh:
Adətən əvvəlcə filter tətbiq olunur, sonra distinct ilə dublikatlar çıxarılır, daha sonra skip və limit, sonda isə — collect. Lakin tapşırıq tələb edirsə, ardıcıllığı dəyişmək olar.
7. distinct, limit, skip ilə işləyərkən tipik səhvlər
Səhv №1: distinct-in “istənilən” əlamətə görə dublikatları sildiyini gözləmək.
Əslində, distinct obyektin equals metoduna əsasən işləyir. Unikalığı hər hansı bir sahəyə (məsələn, yalnız email) görə istəyirsinizsə — ya equals/hashCode-u override etmək, ya da Collectors.toMap() və ya əlavə filtrləmə kimi fəndlərdən istifadə etmək lazımdır.
Səhv №2: Əməliyyatların ardıcıllığını pozmaq.
Əgər əvvəlcə limit, sonra distinct tətbiq etsəniz, dublikatlar qala bilər — axını ilk N elementlə məhdudlaşdırmısınız və onlar arasında eyni olanlar ola bilər.
Səhv №3: skip ilə mövcud olandan artıq elementi ötürmək.
Axında olandan çox elementi ötürməyə çalışsanız, nəticə sadəcə boş siyahı olacaq — xəta olmayacaq, amma nəticə sizi təəccübləndirə bilər.
Səhv №4: Performansı nəzərə almamaq.
distinct, limit, skip çox böyük axınlarda səmərəsiz ola bilər, xüsusən axın nizamsızdırsa və ya elementlər mürəkkəbdirsə. Adi tapşırıqların 99%-ində bu problem deyil, amma milyonlarla yazı ilə işləyirsinizsə — düşünməyə dəyər.
Səhv №5: Öz siniflər üçün equals/hashCode override edilməsini unutmaq.
Əgər istifadəçi obyektləri ilə işləyirsinizsə (məsələn, User), bu metodlar override edilmədən distinct obyektləri “məntiqən” eyni olsalar belə, fərqli hesab edəcək.
GO TO FULL VERSION